Search code examples
linuxlinux-kernelglibcdynamic-linking

Why does the linux kernel map my RW segment as RWX?


I have a very simple ELF executable:

$ 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

Now, from my understanding of how ELF works, I would expect three segments:

  • One RW from 0x3ff000-0x400000
  • One RX from 0x400000-0x401000
  • One RW from 0x600000-0x602000 (0xea0+0x180 > 0x1000)

However, when I actually look at what I get while the executable is running using /proc/pid/maps, I see the following:

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

which is not at all what I expected. What's going on here?


Solution

  • The answer here is two-fold, one part being contributed by the dynamic linker, the other by the kernel. To see, this, let us look at the memory map right after entering the dynamic linker (e.g. by setting a breakpoint in _dl_start). We see:

    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
    

    which is at least closer to what we wanted (it has the correct segments, in the right places). Now, the reason the last segment gets split up is because of the GNU_RELRO program header, which says to the dynamic linker "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 (note it ignores the actual permission flags set in the program header, though they appear to be conventionally set to PF_R).

    That's only half the mystery though. We still have those pesky PROT_EXEC bits left that we didn't order. Those turn out to come down to a feature of the linux kernel called the READ_IMPLIES_EXEC personality, causing all maps with PROT_READ permission to also have PROT_EXEC permission (see the man page for personality(2)). It turns out that for compatibility reasons, linux automatically sets this personality unless a PT_GNU_STACK program header tells it not to. The linker automatically creates this program header, if all input objects have an (empty) .note.GNU-stack section. See here for more information on that mechanism.