创建带有只读标志的程序头会导致段错误

Creating a program header with read only flag causes segfault

我一直在使用 NASM 编写 ELF 二进制文件,并且我创建了一个打开了只读标志的段。 运行 程序导致段错误。我在 replit 中测试了这个程序,它 运行 很好所以问题是什么?我用 .rodata 部分中的 hello world 字符串创建了一个常规的 NASM hello world 程序,运行 很好。我用 readelf 检查了二进制文件以确保字符串在只读段中。

我想出的唯一解决方案是在 rodata 段中设置可执行标志,以便它具有读取/执行权限,但这是 hacky,我希望 rodata 段是只读的。

这是 ELF-64 hello world 的代码。

; hello.asm
[bits 64]
[org 0x400000]

fileHeader:
    db 0x7F, "ELF"
    db 2 ; ELF-64
    db 1 ; little endian
    db 1 ; ELF version
    db 0 ; System V ABI
    db 0 ; ABI version
    db 0, 0, 0, 0, 0, 0, 0 ; unused
    dw 2 ; executable object file
    dw 0x3E ; x86-64
    dd 1 ; ELF version
    dq text ; entry point
    dq 64 ; program header table offset
    dq nullSection - $$ ; section header table offset
    dd 0 ; flags
    dw 64 ; size of file header
    dw 56 ; size of program header
    dw 3 ; program header count
    dw 64 ; size of section header
    dw 4 ; section header count
    dw 3 ; section header string table index
nullSegment:
    times 56 db 0
textSegment:
    dd 1 ; loadable segment
    dd 0x4 ; read / execute permissions
    dq text - $$ ; segment offset
    dq text ; virtual address of segment
    dq 0 ; physical address of segment
    dq textSize ; size of segment in file
    dq textSize ; size of segment in memory
    dq 0x1000 ; alignment
rodataSegment:
    dd 1 ; loadable segment
    dd 0x4 ; read permission (setting this flag to 0x5 causes the program to run just fine)
    dq rodata - $$ ; segment offset
    dq rodata ; virtual address of segment
    dq 0 ; physical address of segment
    dq rodataSize ; size of segment in file
    dq rodataSize ; size of segment in memory
    dq 0x1000 ; alignment
text:
    mov rax, 1
    mov rdi, 1
    mov rsi, message
    mov rdx, messageLength
    syscall

    mov rax, 60
    xor rdi, rdi
    syscall
textSize equ $ - text
rodata:
    message db "Hello world!", 0xA, 0
    messageLength equ $ - message
rodataSize equ $ - rodata
stringTable:
    db 0
    db ".text", 0
    db ".rodata", 0
    db ".shstrtab", 0
stringTableSize equ $ - stringTable
nullSection:
    times 64 db 0
textSection:
    dd 1 ; index into string table
    dd 1 ; program data
    dq 0x6 ; occupies memory & executable
    dq text ; virtual address of section
    dq text - $$ ; offset of section in file
    dq textSize ; size of section in file
    dq 0 ; unused
    dq 0x1000 ; alignment
    dq 0 ; unused
rodataSection:
    dd 7 ; index into string table
    dd 1 ; program data
    dq 0x2 ; occupies memory
    dq rodata ; virtual address of section
    dq rodata - $$ ; offset of section in file
    dq rodataSize ; size of section in file
    dq 0 ; unused
    dq 0x1000 ; no alignment
    dq 0 ; unused
stringTableSection:
    dd 15 ; index into string table
    dd 3 ; string table
    dq 0 ; no attributes
    dq stringTable ; virtual address of section
    dq stringTable - $$ ; offset of section in file
    dq stringTableSize ; size of section in file
    dq 0 ; unused
    dq 0 ; no alignment
    dq 0 ; unused

replitHello.asm: https://hastebin.com/ujanoguveq.properties // 它应该与第

行几乎相同

这是最小的 nasm hello world 程序。

; helloNasm.asm
section .text
global _start
_start:
    mov rax, 1
    mov rdi, 1
    mov rsi, message
    mov rdx, messageLength
    syscall

    mov rax, 60
    xor rdi, rdi
    syscall

section .rodata
    message db "Hello NASM!", 0xA, 0
    messageLength equ $ - message
textSegment:
    dd 1 ; loadable segment
    dd 0x4 ; read / execute permissions

我假设你的意思是 0x5 上面的标志。

修复后,我看到以下片段:

Program Headers:
  Type           Offset   VirtAddr           PhysAddr           FileSiz  MemSiz   Flg Align
  NULL           0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000     0
  LOAD           0x0000e8 0x00000000004000e8 0x0000000000000000 0x000025 0x000025 R E 0x1000
  LOAD           0x00010d 0x000000000040010d 0x0000000000000000 0x00000e 0x00000e R   0x1000

这要求内核在同一地址(0x400000)执行两个mmap。这些 mmap 中的第二个映射到第一个,导致以下 /proc/$pid/maps:

00400000-00401000 r--p 00000000 fe:02 22548440                           /tmp/t
7ffff7ff9000-7ffff7ffd000 r--p 00000000 00:00 0                          [vvar]
7ffff7ffd000-7ffff7fff000 r-xp 00000000 00:00 0                          [vdso]
7ffffffdd000-7ffffffff000 rw-p 00000000 00:00 0                          [stack]

如您所见,程序文本 不可执行 ,因此程序 SIGSEGV 在第一条指令上执行:

(gdb) run 
Starting program: /tmp/t 

Program received signal SIGSEGV, Segmentation fault.
0x00000000004000e8 in ?? ()
(gdb) x/i $pc
=> 0x4000e8:    mov    [=13=]x1,%eax

要解决此问题,您必须将其中一个片段移动到不同的页面(正如 Jester 正确指出的那样)。

另请注意,部分 是完全没有必要的(只有部分很重要)。特别是在 .text 部分中设置 A X 标志对任何事情都没有影响。