CVE-2010-3081

As pretty much every system administrator is now aware, a major security flaw was introduced to the Linux kernel in April of 2008, and just recently got some big exposure when a public exploit was published for it. Red Hat describes this vulnerability as:

an issue in the 32/64-bit compatibility layer implementation in the Linux kernel, versions
2.6.26-rc1 to 2.6.36-rc4. The compat_alloc_user_space() function is missing a sanity check
on the length argument, and also a check to make sure the pointer to the block of memory in
user-space that the process is attempting to write to is valid.

When panic struck, and systems began to be compromised, users of Red Hat Enterprise and similar rebuild-distributions such as CentOS remained vulnerable for some days. Red Hat is known for stability, and thus it makes perfect sense that they were not so quick to apply a patch, build an RPM, and push it to their mirrors. They tested their patch thoroughly.

However, some companies, like the one I work for, wanted a fix in place before an official release was available. Seeing how I build all of the RPM's around here, I was tasked with patching the kernel into a distributable format for all of our managed customers.

I first went searching for any patches that already existed. I was not about to try writing my own and be held responsible for it. On September 19th, 2010, Roberto Yokota posted a patch on the Redhat Bugzilla page. His patch does indeed work, as he demonstrates in his 3 posts from the 19th. However, his patch would not apply cleanly to the current RHEL kernel build, as it duplicated patches from previous releases. I consequently needed to modify his patch slightly to build cleanly into the kernel RPM's that we released. Essentially all I needed to do was to remove all instances where "%eax" was replaced by "%rax", as those bug fixes were included in other patches. I added my patch to the RPM (%patch25159):

--- a/include/asm-ia64/compat.h 2006-09-20 00:42:06.000000000 -0300
+++ b/include/asm-ia64/compat.h 2010-09-19 10:53:05.000000000 -0300
@@ -196,7 +196,7 @@ ptr_to_compat(void __user *uptr)
 }

 static __inline__ void __user *
-compat_alloc_user_space (long len)
+arch_compat_alloc_user_space (long len)
 {
    struct pt_regs *regs = task_pt_regs(current);
    return (void __user *) (((regs->r12 & 0xffffffff) & -16) - len);
--- a/include/asm-mips/compat.h 2006-09-20 00:42:06.000000000 -0300
+++ b/include/asm-mips/compat.h 2010-09-19 10:53:51.000000000 -0300
@@ -138,7 +138,7 @@ static inline compat_uptr_t ptr_to_compa
    return (u32)(unsigned long)uptr;
 }

-static inline void __user *compat_alloc_user_space(long len)
+static inline void __user *arch_compat_alloc_user_space(long len)
 {
    struct pt_regs *regs = (struct pt_regs *)
        ((unsigned long) current_thread_info() + THREAD_SIZE - 32) - 1;
--- a/include/asm-parisc/compat.h   2006-09-20 00:42:06.000000000 -0300
+++ b/include/asm-parisc/compat.h   2010-09-19 10:54:47.000000000 -0300
@@ -144,7 +144,7 @@ static inline compat_uptr_t ptr_to_compa
    return (u32)(unsigned long)uptr;
 }

-static __inline__ void __user *compat_alloc_user_space(long len)
+static __inline__ void __user *arch_compat_alloc_user_space(long len)
 {
    struct pt_regs *regs = &current->thread.regs;
    return (void __user *)regs->gr[30];
--- a/include/asm-powerpc/compat.h  2006-09-20 00:42:06.000000000 -0300
+++ b/include/asm-powerpc/compat.h  2010-09-19 10:55:33.000000000 -0300
@@ -131,7 +131,7 @@ static inline compat_uptr_t ptr_to_compa
    return (u32)(unsigned long)uptr;
 }

-static inline void __user *compat_alloc_user_space(long len)
+static inline void __user *arch_compat_alloc_user_space(long len)
 {
    struct pt_regs *regs = current->thread.regs;
    unsigned long usp = regs->gpr[1];
--- a/include/asm-s390/compat.h 2006-09-20 00:42:06.000000000 -0300
+++ b/include/asm-s390/compat.h 2010-09-19 10:56:15.000000000 -0300
@@ -133,7 +133,7 @@ static inline compat_uptr_t ptr_to_compa
    return (u32)(unsigned long)uptr;
 }

-static inline void __user *compat_alloc_user_space(long len)
+static inline void __user *arch_compat_alloc_user_space(long len)
 {
    unsigned long stack;

--- a/include/asm-sparc64/compat.h  2006-09-20 00:42:06.000000000 -0300
+++ b/include/asm-sparc64/compat.h  2010-09-19 10:57:21.000000000 -0300
@@ -164,7 +164,7 @@ static inline compat_uptr_t ptr_to_compa
    return (u32)(unsigned long)uptr;
 }

-static __inline__ void __user *compat_alloc_user_space(long len)
+static __inline__ void __user *arch_compat_alloc_user_space(long len)
 {
    struct pt_regs *regs = current_thread_info()->kregs;
    unsigned long usp = regs->u_regs[UREG_I6];
--- a/include/asm-x86_64/compat.h   2006-09-20 00:42:06.000000000 -0300
+++ b/include/asm-x86_64/compat.h   2010-09-19 10:41:32.000000000 -0300
@@ -196,7 +196,7 @@ static inline compat_uptr_t ptr_to_compa
    return (u32)(unsigned long)uptr;
 }

-static __inline__ void __user *compat_alloc_user_space(long len)
+static __inline__ void __user *arch_compat_alloc_user_space(long len)
 {
    struct pt_regs *regs = task_pt_regs(current);
    return (void __user *)regs->rsp - len; 
--- a/include/linux/compat.h    2010-09-19 10:45:53.000000000 -0300
+++ b/include/linux/compat.h    2010-09-19 10:41:32.000000000 -0300
@@ -235,6 +235,7 @@ static inline int compat_timespec_compar
 asmlinkage long compat_sys_adjtimex(struct compat_timex __user *utp);

 extern int compat_printk(const char *fmt, ...);
+extern void __user *compat_alloc_user_space(unsigned long len);

 #endif /* CONFIG_COMPAT */
 #endif /* _LINUX_COMPAT_H */
--- a/kernel/compat.c   2006-09-20 00:42:06.000000000 -0300
+++ b/kernel/compat.c   2010-09-19 10:41:32.000000000 -0300
@@ -22,6 +22,7 @@
 #include <linux/security.h>
 #include <linux/timex.h>
 #include <linux/migrate.h>
+#include <linux/module.h>

 #include <asm/uaccess.h>

@@ -950,3 +951,24 @@ asmlinkage long compat_sys_move_pages(pi
    return sys_move_pages(pid, nr_pages, pages, nodes, status, flags);
 }
 #endif
+
+/*
+ * Allocate user-space memory for the duration of a single system call,
+ * in order to marshall parameters inside a compat thunk.
+ */
+void __user *compat_alloc_user_space(unsigned long len)
+{
+       void __user *ptr;
+
+       /* If len would occupy more than half of the entire compat space... */
+       if (unlikely(len > (((compat_uptr_t)~0) >> 1)))
+               return NULL;
+
+       ptr = arch_compat_alloc_user_space(len);
+
+       if (unlikely(!access_ok(VERIFY_WRITE, ptr, len)))
+               return NULL;
+
+       return ptr;
+}
+EXPORT_SYMBOL_GPL(compat_alloc_user_space);

I also modified the %buildid to avoid interfering with dependencies for future kernel updates, and then built it out. Upon completion I was left with the following RPM's:

[root@srv17 x86_64]# ls
kernel-2.6.18-194.11.3.el5.codero.x86_64.rpm
kernel-debug-2.6.18-194.11.3.el5.codero.x86_64.rpm
kernel-debug-debuginfo-2.6.18-194.11.3.el5.codero.x86_64.rpm
kernel-debug-devel-2.6.18-194.11.3.el5.codero.x86_64.rpm
kernel-debuginfo-2.6.18-194.11.3.el5.codero.x86_64.rpm
kernel-debuginfo-common-2.6.18-194.11.3.el5.codero.x86_64.rpm
kernel-devel-2.6.18-194.11.3.el5.codero.x86_64.rpm
kernel-headers-2.6.18-194.11.3.el5.codero.x86_64.rpm
kernel-xen-2.6.18-194.11.3.el5.codero.x86_64.rpm
kernel-xen-debuginfo-2.6.18-194.11.3.el5.codero.x86_64.rpm
kernel-xen-devel-2.6.18-194.11.3.el5.codero.x86_64.rpm

I tried the RPM's out on a few different test VM's that I had and they all seemed to have the same results before and after. I compiled Ac1dB1tCh3z public exploit on a 32-bit virtual machine and then copied it on over to the 64-bit guinea pigs to test:

Before:

[test@demo ~]$ uname -a
Linux demo 2.6.18-164.11.1.el5.centos #1 SMP Wed Jan 20 08:16:13 EST 2010 x86_64 x86_64 x86_64 GNU/Linux
[test@demo ~]$ ./exploit
Ac1dB1tCh3z VS Linux kernel 2.6 kernel 0d4y
$$$ Kallsyms +r
$$$ K3rn3l r3l3as3: 2.6.18-194.el5
??? Trying the F0PPPPPPPPPPPPPPPPpppppppppp_____ m3th34d
$$$ L00k1ng f0r kn0wn t4rg3tz..
$$$ c0mput3r 1z aqu1r1ng n3w t4rg3t...
$$$ selinux_ops->ffffffff80328ac0
$$$ dummy_security_ops->ffffffff804bb540
$$$ capability_ops->ffffffff8032a380
$$$ selinux_enforcing->ffffffff804be2a0
$$$ audit_enabled->ffffffff804a9124
$$$ Bu1ld1ng r1ngzer0c00l sh3llc0d3 - F0PZzzZzZZ/LSD(M) m3th34d
$$$ Prepare: m0rn1ng w0rk0ut b1tch3z
$$$ Us1ng st4nd4rd s3ash3llz
$$$ 0p3n1ng th3 m4giq p0rt4l
$$$ bl1ng bl1ng n1gg4 : PppPpPPpPPPpP
sh-3.2# id
uid=0(root) gid=500(test) groups=500(test) context=user_u:system_r:unconfined_t

After:

[test@demo ~]$ uname -a
Linux demo 2.6.18-194.11.3.el5.codero #1 SMP Mon Sep 20 11:46:50 PDT 2010 x86_64 x86_64 x86_64 GNU/Linux
[test@demo ~]$ ./exploit
Ac1dB1tCh3z VS Linux kernel 2.6 kernel 0d4y
$$$ Kallsyms +r
$$$ K3rn3l r3l3as3: 2.6.18-194.11.3.el5.codero
??? Trying the F0PPPPPPPPPPPPPPPPpppppppppp_____ m3th34d
$$$ L00k1ng f0r kn0wn t4rg3tz..
$$$ c0mput3r 1z aqu1r1ng n3w t4rg3t...
$$$ selinux_ops->ffffffff80327ac0
$$$ dummy_security_ops->ffffffff804b9540
$$$ capability_ops->ffffffff80329380
$$$ selinux_enforcing->ffffffff804bc2a0
$$$ audit_enabled->ffffffff804a7124
$$$ Bu1ld1ng r1ngzer0c00l sh3llc0d3 - F0PZzzZzZZ/LSD(M) m3th34d
$$$ Prepare: m0rn1ng w0rk0ut b1tch3z
$$$ Us1ng st4nd4rd s3ash3llz
$$$ 0p3n1ng th3 m4giq p0rt4l
!!! y0u fuq1ng f41l. g3t th3 fuq 0ut!
[test@demo ~]$

Success! My RPM worked like a charm. Of course I wasn't about to mass-deploy it, but for certain customers who had local users with shell accounts enabled, this kernel made sense to install while we waited the additional 2 days for Red Hat to release the official kernel-2.6.18-194.11.4.el5 kernel, and then for CentOS to recompile it and push it to our mirror server.