OpenBSD 7.3-7.8下write(2)访问.rodata返回EFAULT的问题求助
问题:OpenBSD 7.8 i386汇编程序中.rodata段内容无法被write(2)读取返回EFAULT
我正在为OpenBSD 7.8编写i386汇编的hello-world程序。当write(2)的buf参数位于.data段或栈上时程序正常运行,但参数位于.rodata段时,write(2)返回EFAULT(错误值14)。这些程序是ET_EXEC格式的非位置无关可执行文件(PIE)。
字符串位于.rodata段的程序(返回EFAULT)
该程序在OpenBSD 7.3、7.4、7.7和7.8中调用write(2)返回EFAULT;但在OpenBSD 6.0、7.0和7.2中可成功运行。
编译命令:nasm -O0 -o w1 w1.nasm && chmod +x w1
cpu 386 bits 32 org 0x08048000 Ehdr_OSABI equ 0 ; SYSV. PT: .LOAD equ 1 .NOTE equ 4 .OPENBSD_SYSCALLS equ 0x65a3dbe9 Elf32_Ehdr: ; ELF-32 i386 header. db 0x7f, 'ELF', 1, 1, 1, Ehdr_OSABI db 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 3, 0 dd 1, _start, Elf32_Phdr0-$$, 0, 0 dw Elf32_Phdr0-$$, 0x20, (Elf32_Phdr_end-Elf32_Phdr0)>>5 dw 0x28, 0, 0 Elf32_Phdr0: dd PT.LOAD, 0, $$, $$, text_end-$$, text_end-$$, 5, 0x1000 Elf32_Phdr1: dd PT.LOAD, text_end-$$, text_end+0x1000, text_end+0x1000 dd file_end-text_end, mem_end-text_end, 6, 0x1000 Elf32_Phdr2: dd PT.NOTE, Elf32_Note-$$, Elf32_Note, Elf32_Note, dd Elf32_Note_end-Elf32_Note, Elf32_Note_end-Elf32_Note, 4, 4 Elf32_Phdr3: dd PT.OPENBSD_SYSCALLS, Elf32_osc-$$, 0, 0 dd Elf32_osc_end-Elf32_osc, Elf32_osc_end-Elf32_osc, 4, 4 Elf32_Phdr_end: Elf32_Note: ; Same PT_NOTE as in /bin/cat in Minix 3.3.0. dd 8, 4, 1 ; Size of the name, size of the value, node type. db 'OpenBSD', 0 ; 8-byte name. dd 0 ; Version number. Elf32_Note_end: Elf32_osc: dd syscall_location1, 4, syscall_location2, 1 Elf32_osc_end: _start: push byte msg_hello.end-msg_hello push dword msg_hello push byte 1 ; STDOUT_FILENO push eax ; Dummy return adddress. push byte 4 ; SYS.write. pop eax syscall_location1: equ $ int 0x80 ; OpenBSD i386 syscall. jc short .exit xor eax, eax ; EXIT_SUCCESS. .exit: push eax ; Dummy return address. push eax ; Exit code will be errno from the write(2) above. push byte 1 ; SYS.exit. pop eax syscall_location2: equ $ int 0x80 ; OpenBSD i386 syscall. msg_hello: ; We put it to .rodata, which is the same PT_LOAD as .text. db 'Hello!', 10 .end: text_end: db 0 ; Make .data nonempty. file_end: mem_end:
字符串位于栈上(从.rodata复制)的程序(运行正常)
该程序通过将.rodata中的字符串复制到栈上,调用write(2)可成功运行。
编译命令:nasm -O0 -o w2 w2.nasm && chmod +x w2
cpu 386 bits 32 org 0x08048000 Ehdr_OSABI equ 0 ; SYSV. PT: .LOAD equ 1 .NOTE equ 4 .OPENBSD_SYSCALLS equ 0x65a3dbe9 Elf32_Ehdr: ; ELF-32 i386 header. db 0x7f, 'ELF', 1, 1, 1, Ehdr_OSABI db 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 3, 0 dd 1, _start, Elf32_Phdr0-$$, 0, 0 dw Elf32_Phdr0-$$, 0x20, (Elf32_Phdr_end-Elf32_Phdr0)>>5 dw 0x28, 0, 0 Elf32_Phdr0: dd PT.LOAD, 0, $$, $$, text_end-$$, text_end-$$, 5, 0x1000 Elf32_Phdr1: dd PT.LOAD, text_end-$$, text_end+0x1000, text_end+0x1000 dd file_end-text_end, mem_end-text_end, 6, 0x1000 Elf32_Phdr2: dd PT.NOTE, Elf32_Note-$$, Elf32_Note, Elf32_Note, dd Elf32_Note_end-Elf32_Note, Elf32_Note_end-Elf32_Note, 4, 4 Elf32_Phdr3: dd PT.OPENBSD_SYSCALLS, Elf32_osc-$$, 0, 0 dd Elf32_osc_end-Elf32_osc, Elf32_osc_end-Elf32_osc, 4, 4 Elf32_Phdr_end: Elf32_Note: ; Same PT_NOTE as in /bin/cat in Minix 3.3.0. dd 8, 4, 1 ; Size of the name, size of the value, node type. db 'OpenBSD', 0 ; 8-byte name. dd 0 ; Version number. Elf32_Note_end: Elf32_osc: dd syscall_location1, 4, syscall_location2, 1 Elf32_osc_end: _start: push byte msg_hello.end-msg_hello pop edx sub esp, byte (msg_hello.end-msg_hello+3)&~3 mov edi, esp mov esi, msg_hello mov ecx, edx cld rep movsd ; Copy msg_hello from .rodata to stack. mov eax, esp push edx push eax push byte 1 ; STDOUT_FILENO push eax ; Dummy return adddress. push byte 4 ; SYS.write. pop eax syscall_location1: equ $ int 0x80 ; OpenBSD i386 syscall. jc short .exit xor eax, eax ; EXIT_SUCCESS. .exit: push eax ; Exit code will be errno from the write(2) above. push eax ; Dummy return address. push byte 1 ; SYS.exit. pop eax syscall_location2: equ $ int 0x80 ; OpenBSD i386 syscall. msg_hello: ; We put it to .rodata, which is the same PT_LOAD as .text. db 'Hello!', 10 .end: text_end: db 0 ; Make .data nonempty. file_end: mem_end:
原因分析与解决方法
- 原因:OpenBSD 7.3及以后版本的内核中,write(2)的权限检查会将包含.text的PT_LOAD段判定为仅执行权限,即便ELF程序头指定了读-执行权限,因此拒绝从该段读取buf数据,触发EFAULT错误。
- 解决方法1:拆分PT_LOAD段,为.rodata单独创建一个只读权限的PT_LOAD段,为.text创建读-执行权限的PT_LOAD段(内核实际会将其处理为仅执行),这是当前OpenBSD 7.8的ELF-32可执行程序采用的标准方式。
- 解决方法2:尝试在进程启动时调用
mprotect(2)并传入PROT_READ|PROT_EXEC参数,但该方法大概率失败,因为OpenBSD的execve(2)会将所有PT_LOAD段标记为不可修改(mimmutable)。
内容的提问来源于stack exchange,提问作者pts
相关产品推荐
相关产品推荐

