xorl %eax, %eax

Linux kernel do_xip_mapping_read Memory Corruption

leave a comment »

This bug reported by Martin Schwidefsky of IBM on 31 March 2009 and affects Linux kernel prior to 2.6.29.2. The code demonstrated here is taken from 2.6.29 release of the Linux kernel. Here is the buggy function from mm/filemap_xip.c:

43 /*
44  * This is a file read routine for execute in place files, and uses
45  * the mapping->a_ops->get_xip_mem() function for the actual low-level
46  * stuff.
47  *
48  * Note the struct file* is not used at all.  It may be NULL.
49  */
50 static ssize_t
51 do_xip_mapping_read(struct address_space *mapping,
52                     struct file_ra_state *_ra,
53                     struct file *filp,
54                     char __user *buf,
55                     size_t len,
56                     loff_t *ppos)
57 {


Here is the problem with this:

62        size_t copied = 0, error = 0;
         ...
75        do {
76                unsigned long nr, left;
         ...
81                /* nr is the maximum number of bytes to copy from this page */
82                nr = PAGE_CACHE_SIZE;
83                if (index >= end_index) {
         ...
90                }
91                nr = nr - offset;
92                if (nr > len)
93                        nr = len;
         ...
112                /*
113                 * Ok, we have the mem, so now we can copy it to user space...
114                 *
115                 * The actor routine returns how many bytes were actually used..
116                 * NOTE! This may not be the same as how much of a user buffer
117                 * we filled up (we may be padding etc), so we can only update
118                 * "pos" here (the actor routine has to update the user buffer
119                 * pointers and the remaining count).
120                 */
121                if (!zero)
122                        left = __copy_to_user(buf+copied, xip_mem+offset, nr);
123                else
124                        left = __clear_user(buf + copied, nr);
         ...
131                copied += (nr - left);
         ...
135        } while (copied < len);
         ...
143 }


As Schwidefsky noticed, if the copy to the userspace operation requires more than one iterations in this do-while loop (lines 75-135), and since ‘copied‘ will not be zero (check out line 131), this could lead to a situation where the first argument of the __clear_user() at line 124 is out of buf‘s bounds since it is:

__clear_user(buf + copied, nr);

And thus, buf+copied pointer is passed to be freed, however, the maximum length alowed is:

len - copied

but since ‘copied‘ is not being checked, ‘buf‘ can be dereferenced to point to arbitrary locations, and the only checke being performed is at line 92 where nr is checked against len but nr contains an invalid maximum value because of the previous miscalculation. To fix this the following patch was applied:

 		nr = nr - offset;
-		if (nr > len)
-			nr = len;
+		if (nr > len - copied)
+			nr = len - copied;


Now, the bytes to be copied are tested correctly. This could be easily reached though EXT2 filesystems configured with XIP support. Using just a common read operation… Here is a snippet from fs/ext2/file.c:

63 #ifdef CONFIG_EXT2_FS_XIP
64 const struct file_operations ext2_xip_file_operations = {
      ...
66        .read           = xip_file_read,
      ...
76 };


Where xip_file_read is just a wrapper around the above vulnerable function which can be found at mm/filemap_xip.c:

145 ssize_t
146 xip_file_read(struct file *filp, char __user *buf, size_t len, loff_t *ppos)
147 {
148        if (!access_ok(VERIFY_WRITE, buf, len))
149                return -EFAULT;
150
151        return do_xip_mapping_read(filp->f_mapping, &filp->f_ra, filp,
152                            buf, len, ppos);
153 }
154 EXPORT_SYMBOL_GPL(xip_file_read);

Written by xorl

April 27, 2009 at 23: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