You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 02:44:53