使用 r8 寄存器作为循环计数器会导致无限循环 - 为什么?

Using r8 register as a loop counter results in endless loop - why?

以下代码通过使用 rsi 寄存器作为循环计数器打印 hello world 10 次。

section .data
    hello:     db 'Hello world!',10   
    helloLen:  equ $-hello             

section .text
    global _start

_start:
    mov rsi, 0                 ;<--- use r8 here

do_loop:
    inc rsi                    ;<--- use r8 here

    ;print hello world
    mov eax,4             
    mov ebx,1            
    mov ecx,hello       
    mov edx,helloLen     

    int 80h              

    cmp rsi, 10                ;<--- use r8 here
    jnz do_loop

    ;system exit
    mov eax,1            ; The system call for exit (sys_exit)
    mov ebx,0            ; Exit with return code of 0 (no error)
    int 80h;

如果我尝试使用 r8 寄存器而不是 rsi 作为循环计数器,它会导致无限循环。这里的 r8 寄存器只是一个例子。它也发生在寄存器 r9、r10 上。

谁能解释一下,因为我认为这些都是通用寄存器,你应该被允许使用它们?

TL;DR : int 0x80 隐式 zeroes out R8R9R10R11 在 64 位 Linux 返回用户态代码之前的系统。此行为发生在 2.6.32-rc1 之后的内核上。对于首选的 64 位 SYSCALL 调用约定,情况并非如此。


您遇到了 Linux 内核版本 2.6.32-rc1 之后的一个特性。对于 Linux 内核版本 <= 2.6.32-rc1,您可能会得到预期的行为。由于信息泄漏错误(和利用),寄存器 R8R9R10R11 现在当内核 returns 来自 int 0x80.

时清零

您可能认为这些寄存器在兼容模式(32 位代码)下应该无关紧要,因为这些较新的寄存器不可用。这是一个错误的假设,因为 32 位应用程序可以切换到 64 位长模式并访问这些寄存器。发现这个问题的 Linux Kernel Mailing List post 是这样说的:

x86: Don't leak 64-bit kernel register values to 32-bit processes

While 32-bit processes can't directly access R8...R15, they can gain access to these registers by temporarily switching themselves into 64-bit mode.

Jon Oberheide. It creates a 32-bit application to be run on an x86-64 system with IA32 compatibility enabled. The program switches to 64-bit long mode and then stores registers R8-R11 into general purpose registers that are available in compatibility mode (32-bit mode). John discusses the specifics in this article 提供了在早期内核上演示寄存器泄漏的代码。他在这段摘录中很好地总结了漏洞和内核修复:

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.


如果您在 GDB 这样的调试器中单步执行代码,您应该会发现 R8 实际上在之后设置为零int 0x80。因为它是你的循环计数器,所以你的程序最终会无限循环打印 Hello world!