elf

GNU_RELRO program header is not page aligned


RELRO protection makes previously writable sections read-only after loading it. Most GNU_RELRO segment headers I find are however not page aligned.

How would the memory protections work in this case as memory permissions are always assigned on a page basis.

Either it would overlap into adjacent sections or it would not protect all data its supposed to protect.

   Section Headers:
  [Nr] Name              Type            Address          Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            0000000000000000 000000 000000 00      0   0  0
  [ 1] .interp           PROGBITS        0000000000000238 000238 00001b 00   A  0   0  1
  [ 2] .note.gnu.build-id NOTE            0000000000000254 000254 000024 00   A  0   0  4
  [ 3] .note.ABI-tag     NOTE            0000000000000278 000278 000020 00   A  0   0  4
  [ 4] .gnu.hash         GNU_HASH        0000000000000298 000298 000024 00   A  5   0  8
  [ 5] .dynsym           DYNSYM          00000000000002c0 0002c0 000ab0 18   A  6   3  8
  [ 6] .dynstr           STRTAB          0000000000000d70 000d70 00041f 00   A  0   0  1
  [ 7] .gnu.version      VERSYM          0000000000001190 001190 0000e4 02   A  5   0  2
  [ 8] .gnu.version_r    VERNEED         0000000000001278 001278 000040 00   A  6   2  8
  [ 9] .rela.dyn         RELA            00000000000012b8 0012b8 0051f0 18   A  5   0  8
  [10] .rela.plt         RELA            00000000000064a8 0064a8 0009c0 18  AI  5  23  8
  [11] .init             PROGBITS        0000000000006e68 006e68 000014 00  AX  0   0  4
  [12] .plt              PROGBITS        0000000000006e80 006e80 0006a0 00  AX  0   0 16
  [13] .text             PROGBITS        0000000000007520 007520 01cb74 00  AX  0   0 16
  [14] .fini             PROGBITS        0000000000024094 024094 000010 00  AX  0   0  4
  [15] .rodata           PROGBITS        00000000000240a8 0240a8 00b683 00   A  0   0  8
  [16] .eh_frame_hdr     PROGBITS        000000000002f72c 02f72c 000554 00   A  0   0  4
  [17] .eh_frame         PROGBITS        000000000002fc80 02fc80 00279c 00   A  0   0  8
  [18] .init_array       INIT_ARRAY      0000000000042e58 032e58 000008 08  WA  0   0  8
  [19] .fini_array       FINI_ARRAY      0000000000042e60 032e60 000008 08  WA  0   0  8
  [20] .data.rel.ro      PROGBITS        0000000000042e68 032e68 000b28 00  WA  0   0  8
  [21] .dynamic          DYNAMIC         0000000000043990 033990 000200 10  WA  6   0  8
  [22] .got              PROGBITS        0000000000043b90 033b90 000458 08  WA  0   0  8
  [23] .got.plt          PROGBITS        0000000000043fe8 033fe8 000358 08  WA  0   0  8
  [24] .data             PROGBITS        0000000000044340 034340 0013f0 00  WA  0   0  8
  [25] .bss              NOBITS          0000000000045730 035730 04e740 00  WA  0   0  8
  [26] .gnu_debugaltlink PROGBITS        0000000000000000 035730 000044 00      0   0  1
  [27] .gnu_debuglink    PROGBITS        0000000000000000 035774 000034 00      0   0  4
  [28] .shstrtab         STRTAB          0000000000000000 0357a8 000118 00      0   0  1

Program Headers:
  Type           Offset   VirtAddr           PhysAddr           FileSiz  MemSiz   Flg Align
  PHDR           0x000040 0x0000000000000040 0x0000000000000040 0x0001f8 0x0001f8 R   0x8
  INTERP         0x000238 0x0000000000000238 0x0000000000000238 0x00001b 0x00001b R   0x1
      [Requesting program interpreter: /lib/ld-linux-aarch64.so.1]
  LOAD           0x000000 0x0000000000000000 0x0000000000000000 0x03241c 0x03241c R E 0x10000
  LOAD           0x032e58 0x0000000000042e58 0x0000000000042e58 0x0028d8 0x051018 RW  0x10000
  DYNAMIC        0x033990 0x0000000000043990 0x0000000000043990 0x000200 0x000200 RW  0x8
  NOTE           0x000254 0x0000000000000254 0x0000000000000254 0x000044 0x000044 R   0x4
  GNU_EH_FRAME   0x02f72c 0x000000000002f72c 0x000000000002f72c 0x000554 0x000554 R   0x4
  GNU_STACK      0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW  0x10
  GNU_RELRO      0x032e58 0x0000000000042e58 0x0000000000042e58 0x0011a8 0x0011a8 R   0x1

Solution

  • TL;DR: the loader rounds down .p_vaddr before applying read-only protection to the GNU_RELRO segment.

    Either it would overlap into adjacent sections or it would not protect all data its supposed to protect.

    You've cut off the section to segment mapping that is relevant here.

    Here is the output from readelf -Wl /bin/date on my system:

    Elf file type is DYN (Position-Independent Executable file)
    Entry point 0x2830
    There are 12 program headers, starting at offset 64
    
    Program Headers:
      Type           Offset   VirtAddr           PhysAddr           FileSiz  MemSiz   Flg Align
      PHDR           0x000040 0x0000000000000040 0x0000000000000040 0x0002a0 0x0002a0 R   0x8
      INTERP         0x010000 0x0000000000010000 0x0000000000010000 0x00001c 0x00001c R   0x1
          [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
      LOAD           0x000000 0x0000000000000000 0x0000000000000000 0x00fc41 0x00fc41 R E 0x1000
      LOAD           0x010000 0x0000000000010000 0x0000000000010000 0x005828 0x005828 R   0x1000
      LOAD           0x015f50 0x0000000000016f50 0x0000000000016f50 0x001148 0x001280 RW  0x1000
      DYNAMIC        0x016b20 0x0000000000017b20 0x0000000000017b20 0x000220 0x000220 RW  0x8
      NOTE           0x010020 0x0000000000010020 0x0000000000010020 0x000050 0x000050 R   0x8
      NOTE           0x010070 0x0000000000010070 0x0000000000010070 0x0000d0 0x0000d0 R   0x4
      GNU_PROPERTY   0x010020 0x0000000000010020 0x0000000000010020 0x000050 0x000050 R   0x8
      GNU_EH_FRAME   0x015010 0x0000000000015010 0x0000000000015010 0x00015c 0x00015c R   0x4
      GNU_STACK      0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW  0x10
      GNU_RELRO      0x015f50 0x0000000000016f50 0x0000000000016f50 0x0010b0 0x0010b0 R   0x1
    
     Section to Segment mapping:
      Segment Sections...
       00
       01     .interp
       02     .init .plt .plt.sec .text .fini
       03     .interp .note.gnu.property .note.gnu.build-id .note.ABI-tag .note.package .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .relr.dyn .rodata .eh_frame_hdr .eh_frame
       04     .init_array .fini_array .data.rel.ro .dynamic .got .data .bss
       05     .dynamic
       06     .note.gnu.property
       07     .note.gnu.build-id .note.ABI-tag .note.package
       08     .note.gnu.property
       09     .eh_frame_hdr
       10
       11     .init_array .fini_array .data.rel.ro .dynamic .got
    

    The sections that GNU_RELRO (segment 11) is intended to protect are easy to see here (the last line of output).

    The loader will round down the .p_vaddr to the page size (yielding 0x16000, round up the .p_memsize + 0xf50 to page size (yielding 0x2000), and will mprotect the [0x16000, 0x18000) memory range.

    Which sections are in that range?

    Exactly the ones listed above as being mapped to segment 11. You can verify this by using readelf -WS /bin/date:

    Section Headers:
      [Nr] Name              Type            Address          Off    Size   ES Flg Lk Inf Al
      [ 0]                   NULL            0000000000000000 000000 000000 00      0   0  0
      [ 1] .init             PROGBITS        0000000000001000 001000 00001b 00  AX  0   0  4
      [ 2] .plt              PROGBITS        0000000000001020 001020 000490 10  AX  0   0 16
      [ 3] .plt.sec          PROGBITS        00000000000014b0 0014b0 000480 10  AX  0   0 16
      [ 4] .text             PROGBITS        0000000000001940 001940 00e2f2 00  AX  0   0 64
      [ 5] .fini             PROGBITS        000000000000fc34 00fc34 00000d 00  AX  0   0  4
      [ 6] .interp           PROGBITS        0000000000010000 010000 00001c 00   A  0   0  1
      [ 7] .note.gnu.property NOTE            0000000000010020 010020 000050 00   A  0   0  8
      [ 8] .note.gnu.build-id NOTE            0000000000010070 010070 000024 00   A  0   0  4
      [ 9] .note.ABI-tag     NOTE            0000000000010094 010094 000020 00   A  0   0  4
      [10] .note.package     NOTE            00000000000100b4 0100b4 00008c 00   A  0   0  4
      [11] .gnu.hash         GNU_HASH        0000000000010140 010140 00001c 00   A 12   0  8
      [12] .dynsym           DYNSYM          0000000000010160 010160 000810 18   A 13   1  8
      [13] .dynstr           STRTAB          0000000000010970 010970 000414 00   A  0   0  1
      [14] .gnu.version      VERSYM          0000000000010d84 010d84 0000ac 02   A 12   0  2
      [15] .gnu.version_r    VERNEED         0000000000010e30 010e30 0000b0 00   A 13   1  8
      [16] .rela.dyn         RELA            0000000000010ee0 010ee0 000120 18   A 12   0  8
      [17] .rela.plt         RELA            0000000000011000 011000 0006c0 18  AI 12  26  8
      [18] .relr.dyn         RELR            00000000000116c0 0116c0 000048 08   A  0   0  8
      [19] .rodata           PROGBITS        0000000000011720 011720 0038f0 00   A  0   0 32
      [20] .eh_frame_hdr     PROGBITS        0000000000015010 015010 00015c 00   A  0   0  4
      [21] .eh_frame         PROGBITS        0000000000015170 015170 0006b8 00   A  0   0  8
      [22] .init_array       INIT_ARRAY      0000000000016f50 015f50 000008 08  WA  0   0  8
      [23] .fini_array       FINI_ARRAY      0000000000016f58 015f58 000008 08  WA  0   0  8
      [24] .data.rel.ro      PROGBITS        0000000000016f60 015f60 000bc0 00  WA  0   0 32
      [25] .dynamic          DYNAMIC         0000000000017b20 016b20 000220 10  WA 13   0  8
      [26] .got              PROGBITS        0000000000017d40 016d40 0002b8 08  WA  0   0  8
      [27] .data             PROGBITS        0000000000018000 017000 000098 00  WA  0   0 32
      [28] .bss              NOBITS          00000000000180a0 017098 000130 00  WA  0   0 32
      [29] .gnu.build.attributes NOTE            000000000001a1d0 017098 000048 00      0   0  4
      [30] .gnu_debuglink    PROGBITS        0000000000000000 0170e0 000024 00      0   0  4
      [31] .gnu_debugdata    PROGBITS        0000000000000000 017104 000698 00      0   0  1
      [32] .shstrtab         STRTAB          0000000000000000 01779c 000156 00      0   0  1
    

    P.S. Since this is a position-independent binary, the actual address is going to be ${image_base} + 0x16000 and not 0x16000 itself.