为什么 linux 内核将我的 RW 段映射为 RWX?
Why does the linux kernel map my RW segment as RWX?
我有一个非常简单的 ELF 可执行文件:
$ readelf -l ./plt.out
Elf file type is EXEC (Executable file)
Entry point 0x400338
There are 7 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x00000000003ff040 0x00000000003ff040
0x0000000000000188 0x0000000000000188 R E 8
LOAD 0x0000000000000000 0x00000000003ff000 0x00000000003ff000
0x0000000000001000 0x0000000000001000 RW 1000
INTERP 0x00000000000001c8 0x00000000003ff1c8 0x00000000003ff1c8
0x0000000000000032 0x0000000000000032 R 1
[Requesting program interpreter: /data/keno/new_glibc/usr/lib/ld-linux-x86-64.so.2]
LOAD 0x0000000000001000 0x0000000000400000 0x0000000000400000
0x00000000000003b0 0x00000000000003b0 R E 1000
LOAD 0x0000000000001ea0 0x0000000000600ea0 0x0000000000600ea0
0x0000000000000180 0x0000000000000180 RW 1000
DYNAMIC 0x0000000000001ea0 0x0000000000600ea0 0x0000000000600ea0
0x0000000000000150 0x0000000000000150 RW 8
GNU_RELRO 0x0000000000001ea0 0x0000000000600ea0 0x0000000000600ea0
0x0000000000000160 0x0000000000000160 R 1
现在,根据我对 ELF 工作原理的理解,我希望分为三个部分:
- 一个 RW 来自
0x3ff000-0x400000
- 来自
0x400000-0x401000
的一个 RX
- 一个 RW 来自
0x600000-0x602000
(0xea0+0x180 > 0x1000
)
但是,当我实际查看可执行文件 运行 使用 /proc/pid/maps
时得到的内容时,我看到以下内容:
003ff000-00400000 rwxp 00000000 00:28 1456774 plt.out
00400000-00401000 r-xp 00001000 00:28 1456774 plt.out
00600000-00601000 r-xp 00001000 00:28 1456774 plt.out
00601000-00602000 rwxp 00002000 00:28 1456774 plt.out
这完全不是我所期望的。这是怎么回事?
这里的答案有两个,一部分是动态链接器贡献的,另一部分是内核贡献的。为了看到这一点,让我们在进入动态链接器后立即查看内存映射(例如,通过在 _dl_start 中设置断点)。我们看到:
003ff000-00400000 rwxp 00000000 00:28 1456774 plt.out
00400000-00401000 r-xp 00001000 00:28 1456774 plt.out
00600000-00602000 rwxp 00001000 00:28 1456774 plt.out
这至少更接近我们想要的(它在正确的位置有正确的片段)。现在,最后一段被拆分的原因是 GNU_RELRO 程序头,它告诉动态链接器 "Hey, I'm not gonna need to write to this anymore after you're done with your initial relocations"、so the dynamic linker faithfully tries to set that region of memory to PROT_READ(注意它忽略了在程序头,尽管它们似乎通常设置为 PF_R
)。
但这只是谜团的一半。我们还有那些讨厌的 PROT_EXEC 位,我们没有订购。这些结果归结为 linux 内核的一个特性,称为 READ_IMPLIES_EXEC 个性,导致所有具有 PROT_READ 权限的地图也具有 PROT_EXEC 权限(参见 man page for personality(2)). It turns out that for compatibility reasons, linux automatically sets this personality 除非 PT_GNU_STACK 程序头
告诉它不要。如果所有输入对象都有一个(空的).note.GNU-stack
部分,链接器会自动创建此程序头。有关该机制的更多信息,请参阅 here。
我有一个非常简单的 ELF 可执行文件:
$ readelf -l ./plt.out
Elf file type is EXEC (Executable file)
Entry point 0x400338
There are 7 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x00000000003ff040 0x00000000003ff040
0x0000000000000188 0x0000000000000188 R E 8
LOAD 0x0000000000000000 0x00000000003ff000 0x00000000003ff000
0x0000000000001000 0x0000000000001000 RW 1000
INTERP 0x00000000000001c8 0x00000000003ff1c8 0x00000000003ff1c8
0x0000000000000032 0x0000000000000032 R 1
[Requesting program interpreter: /data/keno/new_glibc/usr/lib/ld-linux-x86-64.so.2]
LOAD 0x0000000000001000 0x0000000000400000 0x0000000000400000
0x00000000000003b0 0x00000000000003b0 R E 1000
LOAD 0x0000000000001ea0 0x0000000000600ea0 0x0000000000600ea0
0x0000000000000180 0x0000000000000180 RW 1000
DYNAMIC 0x0000000000001ea0 0x0000000000600ea0 0x0000000000600ea0
0x0000000000000150 0x0000000000000150 RW 8
GNU_RELRO 0x0000000000001ea0 0x0000000000600ea0 0x0000000000600ea0
0x0000000000000160 0x0000000000000160 R 1
现在,根据我对 ELF 工作原理的理解,我希望分为三个部分:
- 一个 RW 来自
0x3ff000-0x400000
- 来自
0x400000-0x401000
的一个 RX
- 一个 RW 来自
0x600000-0x602000
(0xea0+0x180 > 0x1000
)
但是,当我实际查看可执行文件 运行 使用 /proc/pid/maps
时得到的内容时,我看到以下内容:
003ff000-00400000 rwxp 00000000 00:28 1456774 plt.out
00400000-00401000 r-xp 00001000 00:28 1456774 plt.out
00600000-00601000 r-xp 00001000 00:28 1456774 plt.out
00601000-00602000 rwxp 00002000 00:28 1456774 plt.out
这完全不是我所期望的。这是怎么回事?
这里的答案有两个,一部分是动态链接器贡献的,另一部分是内核贡献的。为了看到这一点,让我们在进入动态链接器后立即查看内存映射(例如,通过在 _dl_start 中设置断点)。我们看到:
003ff000-00400000 rwxp 00000000 00:28 1456774 plt.out
00400000-00401000 r-xp 00001000 00:28 1456774 plt.out
00600000-00602000 rwxp 00001000 00:28 1456774 plt.out
这至少更接近我们想要的(它在正确的位置有正确的片段)。现在,最后一段被拆分的原因是 GNU_RELRO 程序头,它告诉动态链接器 "Hey, I'm not gonna need to write to this anymore after you're done with your initial relocations"、so the dynamic linker faithfully tries to set that region of memory to PROT_READ(注意它忽略了在程序头,尽管它们似乎通常设置为 PF_R
)。
但这只是谜团的一半。我们还有那些讨厌的 PROT_EXEC 位,我们没有订购。这些结果归结为 linux 内核的一个特性,称为 READ_IMPLIES_EXEC 个性,导致所有具有 PROT_READ 权限的地图也具有 PROT_EXEC 权限(参见 man page for personality(2)). It turns out that for compatibility reasons, linux automatically sets this personality 除非 PT_GNU_STACK 程序头
告诉它不要。如果所有输入对象都有一个(空的).note.GNU-stack
部分,链接器会自动创建此程序头。有关该机制的更多信息,请参阅 here。