Design Article
How to write secure C/C++ application code for your embedded design: Part 5
Robert Seacord, with Noopur Davis, Chad Doulgherty, Nancy Mead, and Robert Mead
11/6/2007 12:05 AM EST
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.


