This post is dedicated to Wojciech “cliph” Purczynski.
In part 1 of “linux compat vulns” I stumbled across a vulnerability in users of a poorly designed compatibility allocation routine in the compatibility layer. The compatibility layer is a translation abstraction between 32-bit system calls coming in (for instance) via the IA32 emulated int 0x80 gate, and the actual 64-bit implementations compiled in to the kernel.
The IA32 emulated system call handler is used on 64-bit systems to dispatch 32-bit system call requests to a special compatibility syscall table. I was perusing through the code in arch/x86/ia32/ia32entry.S one day (these things inevitably happen when @taviso is in the room) when I noticed something a little bit suspect:
cmpl $(IA32_NR_syscalls-1),%eax ja ia32_badsys ia32_do_call: IA32_ARG_FIXUP call *ia32_sys_call_table(,%rax,8)
The value for %eax is used in the syscall table length comparison, but the full-width %rax is used when actually indexing into the table. It turns out that the “usual” path for a syscall zero-extends %eax with “movl %eax,%eax”, meaning that the top half of %rax is always zero.
Enter CVE-2007-4573. This bug was found by the famous polish kernel hacker cliph. It turns out that a second path into the table call is possible via ptrace and the PTRACE_SYSCALL flag. Furthermore that it was possible to modify the full contents of %rax via a POKEUSER/SETREGS when the syscall trapped due to tracing, and thus bypass the length check leading to a deference of an arbitrary value to use as the branch target.
All well and good, but this bug was patched in 22.214.171.124. They fixed the bug by reloading (and thus zero-extending) the original value of eax from the stack. But… strangely enough, in the LOAD_ARGS32 macro that was responsible for this reloading, I couldn’t actually see a specific reloading of eax anymore:
/* * Reload arg registers from stack in case ptrace changed them. * We don't reload %eax because syscall_trace_enter() returned * the value it wants us to use in the table lookup. */ .macro LOAD_ARGS32 offset, _r9=0 .if \_r9 movl \offset+16(%rsp),%r9d .endif movl \offset+40(%rsp),%ecx movl \offset+48(%rsp),%edx movl \offset+56(%rsp),%esi movl \offset+64(%rsp),%edi .endm
I showed this to my friend Robert Swiecki who had written an exploit for the original bug in 2007, and he immediately said something along the lines of “well this is interesting”. We pulled up his old exploit from 2007, and with a few minor modifications to the privilege escalation code, we had a root shell. So what happened to the patch for CVE-2007-4573? Unfortunately in early 2008 there was a regression that removed eax reloading from LOAD_ARGS32:
This bug has now been re-ordained as CVE-2010-3301. You can see the patches here:
Here is the proof of concept exploit (archive.org link).