xorl %eax, %eax

CVE-2005-4605: Linux kernel ProcFS Information Leak

leave a comment »

I just saw an exploit code published by Jon Oberheide on milw0rm for that (old but nice) bug and decided to write about it! :)
As the CVE ID states, this issue affects Linux kernel prior to 2.6.15. The bug was disclosed by Karl Janmar on 23 December 2005 at Full-Disclosure mailing list. The bug was present in numerous procfs functions as you can see from his advisory. An example can be found at fs/proc/proc_misc.c (from 2.6.14 release of the Linux kernel).

static int proc_calc_metrics(char *page, char **start, off_t off,
                                 int count, int *eof, int len)
{
        if (len <= off+count) *eof = 1;
        *start = page + off;
        len -= off;
        if (len>count) len = count;
        if (len<0) len = 0;
        return len;
}
&#91;/sourcecode&#93;

The problem with that routine is that 'len', 'off', and 'count' are all signed integers (since off_t is long as we can read at include/asm-i386/posix_types.h). Because of this, the addition of 'off' and 'count' can result to a negative integer which would be larger than 'len' if it was handled as unsigned value. The subsequent access to 'page + off' can result into kernel memory disclosure. This neat bug was fixed by applying this patch:

&#91;sourcecode language="c"&#93;
        struct proc_dir_entry * dp;
+       unsigned long long pos;
+
+       /*
+        * Gaah, please just use "seq_file" instead. The legacy /proc
+        * interfaces cut loff_t down to off_t for reads, and ignore
+        * the offset entirely for writes..
+        */
+       pos = *ppos;
+       if (pos > MAX_NON_LFS)
+               return 0;
+       if (nbytes > MAX_NON_LFS - pos)
+               nbytes = MAX_NON_LFS - pos;
 
        dp = PDE(inode);

To proc_file_read() in order to avoid access on positions larger than MAX_NON_LFS which is defined at include/linux/fs.h like this:

#define MAX_NON_LFS     ((1UL<<31) - 1)
&#91;/sourcecode&#93;

And request for bytes more than 'MAX_NON_LFS - pos'. In addition to this, the equivalent proc_file_write() was also changed like this:

&#91;sourcecode language="c"&#93;
 {
-    lock_kernel();
-
-    switch (orig) {
-    case 0:
-       if (offset < 0)
-           goto out;
-       file->f_pos = offset;
-       unlock_kernel();
-       return(file->f_pos);
-    case 1:
-       if (offset + file->f_pos < 0)
-           goto out;
-       file->f_pos += offset;
-       unlock_kernel();
-       return(file->f_pos);
-    case 2:
-       goto out;
-    default:
-       goto out;
-    }
-
-out:
-    unlock_kernel();
-    return -EINVAL;
+       loff_t retval = -EINVAL;
+       switch (orig) {
+       case 1:
+               offset += file->f_pos;
+       /* fallthrough */
+       case 0:
+               if (offset < 0 || offset > MAX_NON_LFS)
+                       break;
+               file->f_pos = retval = offset;
+       }
+       return retval;
 }

Written by xorl

August 6, 2009 at 12:15

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