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
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.