A recent vulnerability in the Linux kernel (versions <= 2.6.32-rc1) allows the leakage of certain register contents. The x86-64 registers r8-r11 may be leaked to 32-bit unprivileged userspace applications that switch themselves into 64-bit mode.
In order to understand this vulnerability, we first need to talk a bit about the x86-64 architecture (aka amd64). One of the key design decisions of x86-64, which fueled its adoption over IA-64, is its backward compatibility with 32-bit code. In x86-64 long mode, there are two sub-modes controlled by the code segment descriptor: compatibility mode and 64-bit mode. Therefore, our 64-bit operating system can execute both 32-bit and 64-bit binaries without issue. Of course, if we're executing a 32-bit binary, we can access our traditional 32-bit architecture registers and not any of the additional register file added by x86-64.
However, it is possible to mix both 32-bit and 64-bit code within a single executable. A 32-bit process can switch into 64-bit mode and jump right to x86-64 machine code. In addition, a 64-bit process is capable of invoking 32-bit syscalls. This type of switching can often be abused to bypass syscall filtering mechanisms as Chris Evans discovered (since syscall numbers differ between 32-bit and 64-bit). In this case, we will switch between 32-bit and 64-bit code in order to exploit an information leak in the kernel.
The underlying issue that causes this vulnerability is a lack of zeroing out several of the x86-64 registers upon returning from a syscall. A 32-bit application may be able to switch to 64-bit mode and access the r8, r9, r10, and r11 registers to leak their previous values. This issue was discovered by Jan Beulich and patched on October 1st. The fix is obviously to zero out these registers to avoid leaking any information to userspace.
A snippet from the patch demonstrating the fix:
As we previously mentioned, our 32-bit userspace application needs to switch into 64-bit mode in order to access the x86-64 registers and expose the leaked information. To mix our 32-bit and 64-bit code, we compile our binary with -m32 but use gcc's inline asm to mix in the necessary 64-bit code without conflict. In order to switch to 64-bit mode, we simply need to perform a ljmp/lcall with the USER_CS code segment (0x33 on Linux):
.code32 ljmp $0x33, $1f .code64 1: /* 64-bit code here */ xorq %rax, %rax ...
For each of the 64-bit registers r8, r9, r10, and r11, we simply cram the 64-bits into two 32-bit GPRs. For example, we throw the upper and lower 32-bits of r8 into rax and rcx respectively (later to be retrieved through eax and ecx):
movq %r8, %rcx shr $32, %r8 movq %r8, %rax
Finally, to retrieve the leaked values, we jmp to an invalid address (0xdeadbeef) to cause a SIGSEGV and then use 'info regs' in gdb to inspect the register values.
The full exploit is available here. Example output:
$ gcc -m32 x86_64-reg-leak.c -o x86_64-reg-leak $ gdb ./x86_64-reg-leak GNU gdb 6.8-debian ... (gdb) run [+] Switching to x86_64 long mode via far jmp... ... Program received signal SIGSEGV, Segmentation fault. 0xdeadbeef in ?? () (gdb) info reg eax 0xffff8800 -30720 <-- r8 upper ecx 0xa5cc6000 -1513332736 <-- r8 lower edx 0x0 0 <-- r9 upper ebx 0x1 1 <-- r9 lower esp 0xffff8800 0xffff8800 <-- r10 upper ebp 0xa5cc6000 0xa5cc6000 <-- r10 lower esi 0x0 0 <-- r11 upper edi 0xffffffff -1 <-- r11 lower ...
spender also wrote an exploit simultaneously which is available here. It will loop and call a bunch of random syscalls and printf the leaked data so you don't have to use gdb.