xorl %eax, %eax

CVE-2011-0711: Linux kernel XFS Information Leak

leave a comment »

As you probably have been expecting, this bug was discovered and reported by Dan Rosenberg and you can find the susceptible code at fs/xfs/xfs_fsops.c file. Here is the exact code snippet that demonstrates the issue.

/*
 * File system operations
 */

int
xfs_fs_geometry(
        xfs_mount_t             *mp,
        xfs_fsop_geom_t         *geo,
        int                     new_version)
{
   ...
        if (new_version >= 3) {
                geo->version = XFS_FSOP_GEOM_VERSION;
   ...
                geo->logsectsize = xfs_sb_version_hassector(&mp->m_sb) ?
                                mp->m_sb.sb_logsectsize : BBSIZE;
                geo->rtsectsize = mp->m_sb.sb_blocksize;
                geo->dirblocksize = mp->m_dirblksize;
        }
   ...
        return 0;
}

For better understanding, here is the definition of ‘xfs_fsop_geom_t’ type which is available in fs/xfs/xfs_fs.h header file.

/*
 * Output for XFS_IOC_FSGEOMETRY
 */
typedef struct xfs_fsop_geom {
        __u32           blocksize;      /* filesystem (data) block size */
        __u32           rtextsize;      /* realtime extent size         */
        __u32           agblocks;       /* fsblocks in an AG            */
        __u32           agcount;        /* number of allocation groups  */
        __u32           logblocks;      /* fsblocks in the log          */
        __u32           sectsize;       /* (data) sector size, bytes    */
        __u32           inodesize;      /* inode size in bytes          */
        __u32           imaxpct;        /* max allowed inode space(%)   */
        __u64           datablocks;     /* fsblocks in data subvolume   */
        __u64           rtblocks;       /* fsblocks in realtime subvol  */
        __u64           rtextents;      /* rt extents in realtime subvol*/
        __u64           logstart;       /* starting fsblock of the log  */
        unsigned char   uuid[16];       /* unique id of the filesystem  */
        __u32           sunit;          /* stripe unit, fsblocks        */
        __u32           swidth;         /* stripe width, fsblocks       */
        __s32           version;        /* structure version            */
        __u32           flags;          /* superblock version flags     */
        __u32           logsectsize;    /* log sector size, bytes       */
        __u32           rtsectsize;     /* realtime sector size, bytes  */
        __u32           dirblocksize;   /* directory block size, bytes  */
        __u32           logsunit;       /* log stripe unit, bytes */
} xfs_fsop_geom_t;

A quick look in xfs_fs_geometry() reveals that ‘logsunit’ member remains uninitialized in the ‘new_version >= 3’ case. Now, if we have a look at the IOCTL handler routine we can easily notice that the following IOCTL command will use the above function with a ‘new_version’ of three.

long
xfs_file_compat_ioctl(
        struct file             *filp,
        unsigned                cmd,
        unsigned long           p)
{
   ...
        switch (cmd) {
        /* No size or alignment issues on any arch */
   ...
        case XFS_IOC_FSGEOMETRY_V1:
        case XFS_IOC_FSGROWFSDATA:
        case XFS_IOC_FSGROWFSRT:
        case XFS_IOC_ZERO_RANGE:
                return xfs_file_ioctl(filp, cmd, p);
   ...
        default:
                return -XFS_ERROR(ENOIOCTLCMD);
        }
}

Assuming that we use the ‘XFS_IOC_FSGEOMETRY_V1’ command, the result will be…

/*
 * Note: some of the ioctl's return positive numbers as a
 * byte count indicating success, such as readlink_by_handle.
 * So we don't "sign flip" like most other routines.  This means
 * true errors need to be returned as a negative value.
 */
long
xfs_file_ioctl(
        struct file             *filp,
        unsigned int            cmd,
        unsigned long           p)
{
   ...
        switch (cmd) {
   ...
        case XFS_IOC_FSGEOMETRY_V1:
                return xfs_ioc_fsgeometry_v1(mp, arg);
   ...
        default:
                return -ENOTTY;
        }
}

That ends up calling this:

STATIC int
xfs_ioc_fsgeometry_v1(
        xfs_mount_t             *mp,
        void                    __user *arg)
{
        xfs_fsop_geom_v1_t      fsgeo;
        int                     error;

        error = xfs_fs_geometry(mp, (xfs_fsop_geom_t *)&fsgeo, 3);
        if (error)
                return -error;

        if (copy_to_user(arg, &fsgeo, sizeof(fsgeo)))
                return -XFS_ERROR(EFAULT);
        return 0;
}

Since xfs_fs_geometry() doesn’t properly initialize ‘fsgeo’ in case of a new version equal to 3 it will end up leaking 4 Bytes of uninitialized kernel memory through ‘logsunit’ member.
Clearly, the fix to this bug was to add the missing initialization as shown below.

 		geo->rtsectsize = mp->m_sb.sb_blocksize;
 		geo->dirblocksize = mp->m_dirblksize;
+		geo->logsunit = 0;
 	}
 	if (new_version >= 4) {

Written by xorl

February 20, 2011 at 14:27

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