为什么这段代码会随着地址随机化而崩溃?
Why does this code crash with address randomization on?
我正在学习 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
本程序的目的是从stdin读取最多64kB,存入栈上的缓冲区,将读取的数据逐字节复制到输出缓冲区,并将输出缓冲区的内容写入标准输出流。本质上,它应该作为 cat
.
的有限版本
在我的计算机上,它要么按预期工作,要么因 SIGSEGV 而崩溃,大约有 1 次成功 运行 4 次崩溃。
amd64 中的红色区域只有 128 字节长,但你在 rsp 下面使用了 131072 字节。向下移动堆栈指针以包含要存储在堆栈上的缓冲区。
sub rsp, <size>
在触摸它之前保留堆栈 space,如果你在 RSP 以下使用超过 128 个字节。
当它崩溃时,查看您的进程内存映射。您可能使用的内存远远低于 RSP,以至于内核不会增加堆栈映射,因此它只是对未映射页面的普通访问 = 无效页面错误 => 内核提供 SIGSEGV。
(ABI 只定义了一个 128 字节的 red-zone,但在实践中,唯一可以破坏内存的是信号处理程序(你没有安装)或 GDB 运行 print some_func()
使用程序的堆栈调用程序中的函数。)
通常 Linux 非常愿意在不触及中间页面的情况下增加堆栈映射,但显然会检查 RSP 的值。通常你移动 RSP 而不是仅仅使用远低于堆栈指针的内存(因为不能保证它是安全的)。参见
另一个副本: 在接触新堆栈内存之前使用 sub rsp, 5555555
就足够了。
Stack ASLR 可能会在相对于页面边界的不同位置启动 RSP,因此您有时可能勉强能够摆脱它。 Linux 最初映射 132kiB 的堆栈 space,其中包括用于环境的 space 和进入 _start
时堆栈上的参数.你的 128kiB 非常接近,所以它有时随机工作是完全合理的。
顺便说一句,在 user-space 中实际复制内存的理由为零,尤其是一次不是 1 个字节。只需将相同的地址传递给 write
.
或者如果可能,至少过滤 in-place,这样您的缓存占用空间就会更小。
此外,加载一个字节的正常方式是movzx eax, byte [mem]
。如果您特别想要 合并 与 RAX 的旧值,请仅使用 mov al, [mem]
。在某些 CPU 上,mov
到 al
对旧值有错误的依赖性,您可以通过写入完整的寄存器来打破它。
顺便说一句,如果你的程序总是使用这个space,你还不如在BSS中静态分配它。如果您选择 assemble 一个 position-dependent (non-PIE) 可执行文件,这使得更有效的索引寻址成为可能。
我正在学习 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
本程序的目的是从stdin读取最多64kB,存入栈上的缓冲区,将读取的数据逐字节复制到输出缓冲区,并将输出缓冲区的内容写入标准输出流。本质上,它应该作为 cat
.
在我的计算机上,它要么按预期工作,要么因 SIGSEGV 而崩溃,大约有 1 次成功 运行 4 次崩溃。
amd64 中的红色区域只有 128 字节长,但你在 rsp 下面使用了 131072 字节。向下移动堆栈指针以包含要存储在堆栈上的缓冲区。
sub rsp, <size>
在触摸它之前保留堆栈 space,如果你在 RSP 以下使用超过 128 个字节。
当它崩溃时,查看您的进程内存映射。您可能使用的内存远远低于 RSP,以至于内核不会增加堆栈映射,因此它只是对未映射页面的普通访问 = 无效页面错误 => 内核提供 SIGSEGV。
(ABI 只定义了一个 128 字节的 red-zone,但在实践中,唯一可以破坏内存的是信号处理程序(你没有安装)或 GDB 运行 print some_func()
使用程序的堆栈调用程序中的函数。)
通常 Linux 非常愿意在不触及中间页面的情况下增加堆栈映射,但显然会检查 RSP 的值。通常你移动 RSP 而不是仅仅使用远低于堆栈指针的内存(因为不能保证它是安全的)。参见
另一个副本:sub rsp, 5555555
就足够了。
Stack ASLR 可能会在相对于页面边界的不同位置启动 RSP,因此您有时可能勉强能够摆脱它。 Linux 最初映射 132kiB 的堆栈 space,其中包括用于环境的 space 和进入 _start
时堆栈上的参数.你的 128kiB 非常接近,所以它有时随机工作是完全合理的。
顺便说一句,在 user-space 中实际复制内存的理由为零,尤其是一次不是 1 个字节。只需将相同的地址传递给 write
.
或者如果可能,至少过滤 in-place,这样您的缓存占用空间就会更小。
此外,加载一个字节的正常方式是movzx eax, byte [mem]
。如果您特别想要 合并 与 RAX 的旧值,请仅使用 mov al, [mem]
。在某些 CPU 上,mov
到 al
对旧值有错误的依赖性,您可以通过写入完整的寄存器来打破它。
顺便说一句,如果你的程序总是使用这个space,你还不如在BSS中静态分配它。如果您选择 assemble 一个 position-dependent (non-PIE) 可执行文件,这使得更有效的索引寻址成为可能。