By Robert Seacord, with Noopur Davis, Chad Doulgherty, Nancy Mead, and Robert Mead
Many flaws are exploitable because the address space of the vulnerable
program has memory that is both writable and executable. As a result,
an attacker can write to memory using a buffer overflow, for example,
and then transfer control to the address of the injected code.
This problem can be addressed by ensuring that memory pages have the
minimal permissions required for proper operation (an application of the principle of least
privilege). This is not a comprehensive defense against all
exploits, but simply a mechanism designed to extend your
defense-in-depth strategy by increasing the difficulty for an attacker
to exploit a vulnerability.
Several existing systems enforce reduced privileges in the kernel so
that no part of the process address space is both writable and
executable. This policy has been named "W xor X" or more concisely WAX.
Enforcement of this policy results, for example, in a nonexecutable
stack.
OpenBSD is an example
of a system that implements a WAX policy. In OpenBSD, an application
can still request explicit permissions with mprotect() to override the
default.
The implementation of a WnX policy depends on the central processing
unit (CPU) and memory management unit (MMU) architecture. For
processors that feature an execute bit for each page of memory; OpenBSD
implements two changes to make this possible:
1. The signal trampoline is
removed from the stack. The signal trampoline page is given read and
execute permissions, while the stack segment is given read and write
permissions. By doing this, the stack becomes nonexecutable.
2. The mapping of shared
libraries and heap memory space is changed so that the data segments do
not contain objects which are read, write, and executable. This entails
moving the shared library global offset table (GOT), the shared library
procedure linkage table (PLT), C++ constructors (.ctors), and C++
destructors (.dtors).
The GOT and PLT are mapped onto their own pages, which are made
nonwritable. Minor changes to the dynamic linker are needed to
accommodate this change. The . dtors and . ctors sections are moved in
with the GOT and consequently become nonwritable as well. Making these
changes eliminates executable objects from the data segment.
The implementation for architectures that do not support a per-page
execute bit (such as IA-32) is more involved but achieves a similar
result.
To supplement this scheme, OpenBSD also reduces permissions on
readonly strings and pointers stored as constant data. Historically,
these objects were stored in the text segment and had permissions of
PROT READ IPROT EXEC. This meant that constant data could potentially
be executed as shell code.
Because OpenBSD uses the ELF object file format, a new segment,
aptly named . rodata, was created to store this data. As a result, this
behavior was changed so that now these objects only have PROT READ and
they lose PROT EXEC.
Open BSD's PaX
PaX is a similar set of enhancements for Linux systems that predates
the OpenBSD implementation. PaX implements nonexecutable memory pages
and address space layout randomization. The PaX implementation is also
dependent on the CPU and MMU of the underlying platform.
For systems that do not support the per-page execute bit, PaX
implements a technique termed virtual memory area mirroring that
duplicates every executable page in a data section in a corresponding
code section. If an attempt is made to fetch an instruction at
addresses located in the data segment that does not have any code
located at the corresponding mirrored address, a page fault occurs and
the kernel kills the process.