airs-notes/piece-of-pie.md

50 lines
2.9 KiB
Markdown

# Piece of PIE
Modern ELF systems can randomize the address at which shared libraries are
loaded. This is generally referred to as Address Space Layout Randomization, or
ASLR. Shared libraries are always position independent, which means that they
can be loaded at any address. Randomizing the load address makes it slightly
harder for attackers of a running program to exploit buffer overflows or
similar problems, because they have no fixed addresses that they can rely on.
ASLR is part of defense in depth: it does not by itself prevent any attacks,
but it makes it slightly more difficult for attackers to exploit certain kinds
of programming errors in a useful way beyond simply crashing the program.
Although it is straightforward to randomize the load address of a shared
library, an ELF executable is normally linked to run at a fixed address that
can not be changed. This means that attackers have a set of fixed addresses
they can rely on. Permitting the kernel to randomize the address of the
executable itself is done by generating a Position Independent Executable, or
PIE.
It turns out to be quite simple to create a PIE: a PIE is simply an executable
shared library. To make a shared library executable you just need to give it a
`PT_INTERP` segment and appropriate startup code. The startup code can be the
same as the usual executable startup code, though of course it must be compiled
to be position independent.
When compiling code to go into a shared library, you use the `-fpic` option.
When compiling code to go into a PIE, you use the `-fpie` option. Since a PIE
is just a shared library, these options are almost exactly the same. The only
difference is that since `-fpie` implies that you are building the main
executable, there is no need to support symbol interposition for defined
symbols. In a shared library, if function `f1` calls `f2`, and `f2` is globally
visible, the code has to consider the possibility that `f2` will be interposed.
Thus, the call must go through the PLT. In a PIE, `f2` can not be interposed,
so the call may be made directly, though of course still in a position
independent manner. Similarly, if the processor can do PC-relative loads and
stores, all global variables can be accessed directly rather than going through
the GOT.
Other than that ability to avoid the PLT and GOT in some cases, a PIE is really
just a shared library. The dynamic linker will ask the kernel to map it at a
random address and will then relocate it as usual.
This does imply that a PIE must be dynamically linked, in the sense of using
the dynamic linker. Since the dynamic linker and the C library are closely
intertwined, linking the PIE statically with the C library is unlikely to work
in general. It is possible to design a statically linked PIE, in which the
program relocates itself at startup time. The dynamic linker itself does this.
However, there is no general mechanism for this at present.