【问题标题】:Creating a program header with read only flag causes segfault创建带有只读标志的程序头会导致段错误
【发布时间】:2021-08-19 16:15:10
【问题描述】:

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

我想出的唯一解决方案是在rodata 段中设置可执行标志,使其具有读取/执行权限,但这很麻烦,我希望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

【问题讨论】:

  • “呃,0000000000400000 处的精灵段已请求但内存已映射”
  • @Jester 我不明白你在说什么。
  • 这就是内核所说的:) 无论如何,看起来您尝试将rodata 和文本映射到具有不同权限的同一页面。将rodata移到虚拟地址空间中。
  • 请创建一个minimal reproducible example 并在此处发布所有代码。人们不会点击外部链接,并且这些链接容易发生链接失效,从而使问题无效
  • @phuclv 我在这里内联了代码,我仍然把a留给replit代码,因为它基本上是一样的,这是我能得到的最小的。

标签: linux assembly x86-64 elf


【解决方案1】:
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)执行两个mmaps。这些mmaps 中的第二个映射到第一个,导致以下/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]

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

(gdb) run 
Starting program: /tmp/t 

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

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

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

【讨论】:

  • 经过一些测试,将 0x1000 添加到rodata 段的虚拟地址字段修复了它,但我真的不明白为什么它工作得这么好。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-10
  • 2018-07-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多