xorl %eax, %eax

CVE-2011-4077: Linux kernel XFS readlink() Memory Corruption

leave a comment »

This interesting vulnerability was recently reported by Carlos Maiolino on xfs mailing list. This vulnerability becomes critical on kernels that do not have ‘CONFIG_XFS_DEBUG’ option enabled and we will see now why.
The bug is part of fs/xfs/xfs_vnodeops.c file in the C routine you see below.

int
xfs_readlink(
        xfs_inode_t     *ip,
        char            *link)
{
        xfs_mount_t     *mp = ip->i_mount;
        int             pathlen;
        int             error = 0;

        trace_xfs_readlink(ip);

        if (XFS_FORCED_SHUTDOWN(mp))
                return XFS_ERROR(EIO);

        xfs_ilock(ip, XFS_ILOCK_SHARED);

        ASSERT(S_ISLNK(ip->i_d.di_mode));
        ASSERT(ip->i_d.di_size <= MAXPATHLEN);

        pathlen = ip->i_d.di_size;
        if (!pathlen)
                goto out;

        if (ip->i_df.if_flags & XFS_IFINLINE) {
                memcpy(link, ip->i_df.if_u1.if_data, pathlen);
                link[pathlen] = '\0';
        } else {
                error = xfs_readlink_bmap(ip, link);
        }

 out:
        xfs_iunlock(ip, XFS_ILOCK_SHARED);
        return error;
}

As you can see, there are two ASSERT() calls to ensure that the file is a symbolic link and that it does not exceed the ‘MAXPATHLEN’ length. However, if the kernel is not compiled with ‘CONFIG_XFS_DEBUG’ and a symbolic link longer than ‘MAXPATHLEN’ is used on an XFS image, then the two ASSERT() checks won’t be executed and the subsequent memcpy() call will result in heap memory corruption since ‘pathlen’ is initialized directly with the file’s size via ‘ip->i_d.di_size’ variable.

The fix to this vulnerability was to remove the two ASSERT() calls…

        xfs_ilock(ip, XFS_ILOCK_SHARED);
 
-       ASSERT(S_ISLNK(ip->i_d.di_mode));
-       ASSERT(ip->i_d.di_size <= MAXPATHLEN);
-
        pathlen = ip->i_d.di_size;

Since the first check is performed on the VFS layer and xfs_readlink_by_handle() prior to this routine and the second one becauce the following code was added to always check for maximum path length.

                goto out;
 
+       if (pathlen > MAXPATHLEN) {
+               xfs_alert(mp, "%s: inode (%llu) symlink length (%d) too long",
+                        __func__, (unsigned long long)ip->i_ino, pathlen);
+               ASSERT(0);
+               return XFS_ERROR(EFSCORRUPTED);
+       }
+
+
        if (ip->i_df.if_flags & XFS_IFINLINE) {

Written by xorl

December 7, 2011 at 22:28

Posted in bugs, linux

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s