State of memory safety in Linux

On a high level, memory safety can be classified into two types, temporal and spatial memory safety. A pointer pointing to a deleted object is called a dangling pointer and de-referencing a dangling pointer causes a temporal memory error. Common example of temporal memory error include *use-after-free and double-free. Spatial memory errors correspond to de-referencing an out-of-bounds pointer. Common examples of spatial memory errors include buffer-overflow, null pointer der-reference.

Over 50% CVEs are memory safety related

A total of approximately 580 memory safety related CVEs were reported against Linux in a 10 year period between 2007-2016. This comprises of about 52.4% of the total of about 1109 CVEs that were reported in that period. These include un-initialized data related vulnerabilities, user-after-free vulnerabilities, various types of buffer overflows including stack, heap and integer overflows and null pointer de-references.

Are they all so bad?

However, there exists several proactive defense techniques that can render a large portion of these vulnerabilities in-exploitable under most circumstances. A large portion of these defense techniques exist in the form of PAX/grsecurity patches. Several of the defense techniques and ideas have also been ported to upstream Linux as a part of Kernel Self Protection Project, meant to improve the state of pro-active defenses in Linux. We will now look in depth at each of the categories mentioned above.

Uninitialized memory

Un-initialized data in Kernel, when copied across privilege boundaries like user-space or network, can leak sensitive information. While information leaks by itself is a critical vulnerability, the fact that they can be used to break other defenses like Address Space Layout Randomization (ASLR) makes then even more dangerous. Compiler added paddings to byte-align certain objects in memory to word-length can also leak sensitive information without the knowledge of programmer. PAX_MEMORY_STACKLEAK is a GCC plugin that clears the stack before a function returns to user-space. This prevents leakages from previous system calls. PAX_MEMORY_STRUCTLEAK is another GCC plugin which zero initializes all the object fields with __user annotations, which unfortunately doesn’t cover all the structures that will be copied to user-space. PAX_MEMORY_SANITIZE erases memory pages and slab objects as soon as they are freed by writing NULL values to them. STACKLEAK and STRUCTLEAK have been partially ported to Linux in some form.

Use-after-free

Use-after-free vulnerabilities can occur when an object is pre-maturely deleted, while a reference to it still exists. This causes the reference to point to a different object allocated on the same memory region. While not all, a large number of use-after-free vulnerabilities are a result of improper reference counting, which Linux uses to garbage collect memory. Both PAX and Linux have implemented measures to prevent reference count overflows due to integer arithmetic errors. The approach taken by both of them is a little different, with PAX adding additional checks on all atomic_t fields and Linux creating a new opt-in field refcount_t which has additional checks to prevent overflows. Another interesting technique, added in Linux v4.8, to mitigate use-after-free vulnerabilities included randomization of object-list (a.k.a freelists) to make it harder to reliably allocate a desired object in the memory location pointed to by the dangling pointer.

Buffer overflow and underflow

Buffer overflow related vulnerabilities have been around for a very long time. Most modern compilers today implement stack canaries to prevent direct overwrite of return addresses in a stack frame. Data Execution Prevention (DEP) techniques can be used to avoid code injection attacks. Most modern processors today allow setting No eXecute (NX) bit on memory pages to mark them non-executable. PAX_PAGEEXEC and PAX_SEGMEXEC can emulate this older hardware that don’t have similar support. Guard pages can also be used in some cases to prevent overflow across page boundaries. Since Linux 4.9, stack can be virtually mapped without needing to be physically contiguous, which allows adding guard pages without having to waste precious kernel memory. GRSECURITY_KSTACKOVERFLOW feature from grsecurity included this feature before it was added in Linux. PAX_USERCOPY, another feature from PAX, added bounds check to any data that is copied to and from user-space. Depending on architecture support for stack frame pointers, it can also prevent the writes past the current stack frame. PAX_SIZEOVERFLOW is a GCC plugin that allows checking for overflow by allocating double sized data structures to compute the output of expressions.

NULL pointer deference

NULL pointer deference can be malicious in kernel-space if the attacker has capability to map page zero. In Linux, processes with CAP_SYS_RAWIO are allowed to map to page zero and still exists as some legacy applications require this. However, it is possible to set the minimum address that any process can map by a sysctl knob called vm.mmap_min_addr. SMEP or Supervisor Mode Exec Prevention (SMEP), a feature in recent Intel processors prevents Kernel from executing user-space memory. ARM processors have a similar feature called Privielged eXecute Never (PXN). PAX_KERNEXEC uses memory segmentation to provide similar semantics in older hardware. Another feature in Intel Processors called Supervisor Mode Access Prevention (SMAP), prevents kernel from de-referencing any user-space pointer. PAX_UDEREF can emulate this feature in software to provide similar semantics.

Proactive defenses

We looked at various memory corruption bugs and from the looks of it, there seem to exist various protection mechanisms for them. However, there are a few caveats when talking about these defenses. Their affect on application performance is a major one. Prevention against memory errors often requires adding additional checks and validations for pointers that can go out-of-bounds or point to deleted objects. Another important point to note here is that several of these defenses can be broken by attacks that they do not prevent against. For example, temporal memory errors can be used to bypass defenses against spatial memory errors and vice-versa. It looks like a game of all-or-nothing to be completely secure, but, each additional layer of defense brings it closer to the goal.

Thanks to Prof. Rakesh Bobba, my advisor, who helped and guided me to work on this research as a part of my Master’s Thesis.

A copy of my research with complete references is available here.