为什么此代码在打开地址随机化后崩溃?

KAc*_*ion 7 linux x86-64 fasm

我正在学习amd64汇编程序,并尝试实现一个简单的Unix过滤器。由于未知的原因,甚至简化为最低版本(下面的代码),它也会随机崩溃。

我试图在GNU调试器(gdb)中调试该程序。在gdb的默认配置中,该程序运行良好,但是如果启用地址随机化(set disable-randomization off),该程序将开始崩溃(SIGSEGV)。清单中标记了有问题的说明:

format ELF64 executable

sys_read                        =       0
sys_write                       =       1
sys_exit                        =       60

entry $
foo:
    label .inbuf   at rbp - 65536
    label .outbuf  at .inbuf - 65536
    label .endvars at .outbuf
    mov rbp, rsp

    mov rax, sys_read
    mov rdi, 0
    lea rsi, [.inbuf]
    mov rdx, 65536
    syscall

    xor ebx, ebx
    cmp eax, ebx
    jl .read_error
    jz .exit

    mov r8, rax  ; r8  - count of valid bytes in input buffer
    xor r9, r9   ; r9  - index of byte in input buffer, that is being processed.
    xor r10, r10 ; r10 - index of next free position in output buffer.

.next_byte:
    cmp r9, r8
    jg .exit
    mov al, [.inbuf + r9]
    mov [.outbuf + r10], al ;; SIGSEGV here in GDB
    inc r10
    inc r9
    jmp .next_byte

.read_error:
    mov rax, sys_exit
    mov rdi, 1
    syscall
.exit:
    mov rax, sys_write
    mov rdi, 1
    lea rsi, [.outbuf]
    mov rdx, r10
    syscall

    mov rax, sys_exit
    xor rdi, rdi
    syscall


Run Code Online (Sandbox Code Playgroud)

该程序旨在从stdin读取最多64kB的数据,将其存储在堆栈中的缓冲区中,将读取的数据逐字节复制到输出缓冲区中,并将输出缓冲区的内容写入标准输出流。本质上,它应作为的受限版本cat

在我的计算机上,它要么按预期工作,要么因SIGSEGV崩溃,成功运行1次至崩溃4次的大概速度。

Pet*_*des 5

sub rsp, <size> 如果您使用的RSP低于128字节,则在触摸之前保留堆栈空间。


当它崩溃时,请查看您的进程内存映射。您可能使用的内存远低于RSP,以至于内核不会增加堆栈映射,因此,这是对未映射页面的常规访问=无效页面错误=>内核提供了SIGSEGV。

(ABI仅定义了一个128字节的红色区域,但实际上,唯一可以破坏该内存的是信号处理程序(您未安装)或GDB在print some_func()使用程序堆栈运行以在程序中调用函数)

通常,Linux非常愿意在不接触中间页面的情况下增加堆栈映射,但是显然可以检查RSP的值。通常,您移动RSP而不是仅仅使用远低于堆栈指针的内存(因为无法保证它的安全性)。请参见使用“推”或“子” x86指令时如何分配堆栈内存?

另一个重复:当减去ESP或RSP寄存器时会产生哪个异常?(堆栈增长)sub rsp, 5555555在接触新堆栈内存之前使用就足够了。

堆栈ASLR可能在相对于页面边界的不同位置启动RSP,因此有时您可能只是勉强摆脱了它。 Linux最初会映射132kiB的堆栈空间,其中包括环境空间和进入时堆栈上的args _start。您的128kiB非常接近于此,因此有时随机工作是完全合理的。


顺便说一句,实际上没有理由在用户空间中复制内存,尤其是一次不复制1个字节。只需将相同的地址传递给即可write

或者,如果可能的话,至少要进行适当的过滤,因此您的缓存占用空间较小。

另外,加载字节的常规方法是movzx eax, byte [mem]。仅mov al, [mem]在您特别想与RAX的旧值合并时使用。在某些CPU上,moval旧值有错误的依赖关系,您可以通过写入完整的寄存器来破坏该旧值。


顺便说一句,如果您的程序始终使用此空间,则最好在BSS中静态分配它。如果您选择汇编位置相关的(非PIE)可执行文件,则可以更有效地进行索引寻址。


Jos*_*ica 4

amd64 中的红色区域只有 128 字节长,但您使用的长度低于 rsp 的 131072 字节。向下移动堆栈指针以包含要存储在堆栈上的缓冲区。