为什么linux内核将我的RW段映射为RWX?

Ken*_*her 1 linux glibc dynamic-linking linux-kernel

我有一个非常简单的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
Run Code Online (Sandbox Code Playgroud)

现在,根据我对ELF如何工作的理解,我希望有三个部分:

  • 一个RW来自 0x3ff000-0x400000
  • 一个RX来自 0x400000-0x401000
  • 来自0x600000-0x602000(0xea0+0x180 > 0x1000)的一个RW

但是,当我实际查看在运行可执行文件时获得的内容时/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
Run Code Online (Sandbox Code Playgroud)

这根本不是我的预期.这里发生了什么?

Ken*_*her 5

这里的答案是双重的,一部分由动态链接器提供,另一部分由内核提供.为了看到这一点,让我们在进入动态链接器后立即查看内存映射(例如,通过在_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
Run Code Online (Sandbox Code Playgroud)

这至少更接近我们想要的东西(它在正确的位置有正确的段).现在,最后一个片段被拆分的原因是因为GNU_RELRO程序头,它对动态链接器说"嘿,在你完成初始重定位后我不再需要写这个了",所以动态链接器忠实地尝试将该内存区域设置为PROT_READ(注意它忽略了程序头中设置的实际权限标志,尽管它们看起来通常设置为PF_R).

这只是神秘的一半.我们还有那些令人讨厌的PROT_EXEC位,我们没有订购.结果归结为linux内核的一个名为READ_IMPLIES_EXEC个性的功能,导致所有具有PROT_READ权限的地图也具有PROT_EXEC权限(参见个性化手册页(2)).事实证明,出于兼容性原因,除非PT_GNU_STACK程序头告诉它不要,否则linux会自动设置此个性.如果所有输入对象都有(空).note.GNU-stack部分,则链接器会自动创建此程序头.有关该机制的更多信息,请参见此处.