airs-notes/linker-relro.md

3.1 KiB

Linker relro

gcc, the GNU linker, and the glibc dynamic linker cooperate to implement an idea called read-only relocations, or relro. This permits the linker to designate a part of an executable or (more commonly) a shared library as being read-only after dynamic relocations have been applied.

This may be used for read-only global variables which are initialized to something which requires a relocation, such as the address of a function or a different global variable. Because the global variable requires a runtime initialization in the form of a dynamic relocation, it can not be placed in a read-only segment. However, because it is declared to be constant, and therefore may not be changed by the program, the dynamic linker can mark it as read-only after the dynamic relocation has been applied.

For some targets this technique may also be used for the PLT or parts of the GOT.

Making these pages read-only helps catch some cases of memory corruption, and making the PLT in particular read-only helps prevent some types of buffer overflow exploits.

The first step is in gcc. When gcc sees a variable which is constant but requires a dynamic relocation, it puts it into a section named .data.rel.ro (this functionality unfortunately relies on magic section names). A variable which requires a dynamic relocation against a local symbol is put into a .data.rel.ro.local section; this helps group such variables together, so that the dynamic linker may apply the relocations, which will always be RELATIVE relocations, more efficiently, especially when using combreloc.

The linker groups .data.rel.ro and .data.rel.ro.local sections as usual. The new step is that the linker then emits a PT_GNU_RELRO program segment which covers these sections. If the PLT and/or GOT can be read-only after dynamic relocations, they are put next to the .data.rel.ro sections and also become part of the new segment. This segment will enclosed within a PT_LOAD segment. The p_vaddr field of the PT_GNU_RELRO segment gives the virtual address of the start of the read-only after dynamic relocations code, and the p_memsz field gives its length.

When the dynamic linker sees a PT_GNU_RELRO segment, it uses mprotect to mark the pages as read-only after the dynamic relocations have been applied. Of course this only works if the segment does in fact cover an entire page. The linker will try to force this to happen.

Note that the current dynamic linker code will only work correctly if the PT_GNU_RELRO segment starts on a page boundary. This is because the dynamic linker rounds the p_vaddr field down to the previous page boundary. If there is anything on the page which should not be read-only, the program is likely to fail at runtime. So in effect the linker must only emit a PT_GNU_RELRO segment if it ensures that it starts on a page boundary.

I see this as a relatively minor security benefit. It is not an optimization as far as I can see. I am documenting it here as part of my general documentation of obscure linker features. The current description of this feature in the GNU linker manual is rather obscure.