airs-notes/linker-relro.md

57 lines
3.1 KiB
Markdown
Raw Normal View History

2021-01-12 20:17:52 +00:00
# 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.