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

时间:2016-07-21 13:03:40

标签: linux assembly nasm x86-64

以下代码使用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。

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

1 个答案:

答案 0 :(得分:6)

TL; DR int 0x80隐含 R8 R9 <返回用户态代码之前,64位Linux系统上的/ em>, R10 R11 。在2.6.32-rc1之后的内核上会发生此行为。对于首选的64位 SYSCALL 调用约定,情况并非如此。

在2.6.32-rc1版本之后,您正在体验Linux内核的特性。对于Linux内核版本&lt; = 2.6.32-rc1,您可能会得到您期望的行为。由于信息泄漏错误(和漏洞利用),寄存器 R8 R9 R10 R11 现在当内核从int 0x80返回时归零。

您可能认为这些寄存器在兼容模式(32位代码)中不重要,因为那些较新的寄存器不可用。这是一个错误的假设,因为32位应用程序可以切换到64位长模式并访问这些寄存器。确定此问题的Linux Kernel Mailing List post有这样的说法:

  

x86:不要将64位内核寄存器值泄漏到32位进程

     

虽然32位进程无法直接访问R8 ... R15,但它们可以获得   通过临时切换自己来访问这些寄存器   64位模式。

Jon Oberheide提供了演示早期内核上的寄存器泄漏的代码。它创建了一个32位应用程序,可在x86-64系统上运行,并启用IA32兼容性。程序切换到64位长模式,然后将寄存器 R8 - R11 存储到通用寄存器中,这些寄存器在兼容模式(32位模式)下可用。 John讨论了这个article中的细节。他在这段摘录中总结了漏洞和内核修复:

  

漏洞

     

导致此漏洞的根本问题是缺乏   从a返回时将几个x86-64寄存器归零   系统调用。 32位应用程序可能能够切换到64位模式   并访问 r8,r9,r10和r11 寄存器以泄漏其先前的   值。这个问题是由Jan Beulich发现并修补的   10月1日。 修复显然是将这些寄存器清零   避免将任何信息泄露给用户空间

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