Code Pointer Integrity
Code Pointer Integrity
Appears in Proceedings of the 11th USENIX Symposium on Operating Systems Design and Implementation (OSDI), October 2014
overhead [35]. We observe that, in order to render CPS are effective: they prevent 100% of the attacks in the
control-flow hijacks impossible, it is sufficient to guaran- RIPE benchmark and the recent attacks [19, 15, 9] that
tee the integrity of code pointers, i.e., those that are used bypass CFI, ASLR, DEP, and all other Microsoft Win-
to determine the targets of indirect control-flow transfers dows protections. We compile and run with CPI/CPS a
(indirect calls, indirect jumps, or returns). complete FreeBSD distribution along with ≥ 100 widely
This paper introduces code-pointer integrity (CPI), a used packages, demonstrating that the approach is prac-
way to enforce precise, deterministic memory safety for tical. This paper makes the following contributions:
all code pointers in a program. The key idea is to split 1. Definition of two new program properties that of-
process memory into a safe region and a regular region. fer a security-benefit to enforcement-cost ratio su-
CPI uses static analysis to identify the set of memory ob- perior to the state of the art: code-pointer in-
jects that must be protected in order to guarantee memory tegrity (CPI) guarantees control flow cannot be hi-
safety for code pointers. This set includes all memory jacked via memory bugs, and code-pointer sepa-
objects that contain code pointers and all data pointers ration (CPS) provides stronger security guarantees
used to access code pointers indirectly. All objects in the than control-flow integrity but at negligible cost.
set are then stored in the safe region, and the region is 2. An efficient compiler-based implementation of CPI
isolated from the rest of the address space (e.g., via hard- and CPS for unmodified C/C++ code.
ware protection). The safe region can only be accessed 3. The first practical and complete OS distribution
via memory operations that are proven at compile time (based on FreeBSD) with full protection built-in
to be safe or that are safety-checked at runtime. The reg- against control-flow hijack attacks.
ular region is just like normal process memory: it can be In the rest of the paper we introduce our threat
accessed without runtime checks and, thus, with no over- model (§2), describe CPI and CPS (§3), present our im-
head. In typical programs, the accesses to the safe region plementation (§4), evaluate our approach (§5), discuss
represent only a small fraction of all memory accesses related work (§6), and conclude (§7). We formalize the
(6.5% of all pointer operations in SPEC CPU2006 need CPI enforcement mechanism and provide a sketch of its
protection). Existing memory safety techniques cannot correctness proof in Appendix A.
efficiently protect only a subset of memory objects in a
program, rather they require instrumenting all potentially 2 Threat Model
dangerous pointer operations.
CPI fully protects the program against all control-flow This paper is concerned solely with control-flow hijack
hijack attacks that exploit program memory bugs. CPI attacks, namely ones that give the attacker control of the
requires no changes to how programmers write code, instruction pointer. The purpose of this type of attack is
since it automatically instruments pointer accesses at to divert control flow to a location that would not oth-
compile time. CPI achieves low overhead by selectively erwise be reachable in that same context, had the pro-
instrumenting only those pointer accesses that are neces- gram not been compromised. Examples of such attacks
sary and sufficient to formally guarantee the integrity of include forcing a program to jump (i) to a location where
all code pointers. The CPI approach can also be used for the attacker injected shell code, (ii) to the start of a chain
data, e.g., to selectively protect sensitive information like of return-oriented program fragments (“gadgets”), or (iii)
the process UIDs in a kernel. to a function that performs an undesirable action in the
We also introduce code-pointer separation (CPS), a re- given context, such as calling system() with attacker-
laxed variant of CPI that is better suited for code with supplied arguments. Data-only attacks, i.e., that modify
abundant virtual function pointers. In CPS, all code or leak unprotected non-control data, are out of scope.
pointers are placed in the safe region, but pointers used to We assume powerful yet realistic attacker capabilities:
access code pointers indirectly are left in the regular re- full control over process memory, but no ability to mod-
gion (such as pointers to C++ objects that contain virtual ify the code segment. Attackers can carry out arbitrary
functions). Unlike CPI, CPS may allow certain control- memory reads and writes by exploiting input-controlled
flow hijack attacks, but it still offers stronger guarantees memory corruption errors in the program. They can-
than CFI and incurs negligible overhead. not modify the code segment, because the corresponding
Our experimental evaluation shows that our proposed pages are marked read-executable and not writable, and
approach imposes sufficiently low overhead to be de- they cannot control the program loading process. These
ployable in production. For example, CPS incurs an assumptions ensure the integrity of the original program
average overhead of 1.2% on the C programs in SPEC code instrumented at compile time, and enable the pro-
CPU2006 and 1.9% for all C/C++ programs. CPI incurs gram loader to safely set up the isolation between the
on average 2.9% overhead for the C programs and 8.4% safe and regular memory regions. Our assumptions are
across all C/C++ SPEC CPU2006 programs. CPI and consistent with prior work in this area.
2
3 Design
We now present the terminology used to describe our
design, then define the code-pointer integrity prop-
erty (§3.1), describe the corresponding enforcement
mechanism (§3.2), and define a relaxed version that
trades some security guarantees for performance (§3.3). Figure 1: CPI protects code pointers 3 and 4 and pointers 1 and
We further formalize the CPI enforcement mechanism 2 (which may access pointers 3 and 4 indirectly). Pointer 2 of
and sketch its correctness proof in Appendix A. type void* may point to different objects at different times. The
We say a pointer dereference is safe iff the memory it int* pointer 5 and non-pointer data locations are not protected.
accesses lies within the target object on which the deref-
erenced pointer is based. A target object can either be a code pointers and pointers that may later be used to ac-
memory object or a control flow destination. By pointer cess sensitive pointers. Note that the sensitive pointer
dereference we mean accessing the memory targeted by definition is recursive, as illustrated in Fig. 1. According
the pointer, either to read/write it (for data pointers) or to case (iv) of the based-on definition above, dereferenc-
to transfer control flow to its location (for code pointers). ing a pointer to a pointer will correspondingly propagate
A memory object is a language-specific unit of memory the based-on information; e.g., an expression *p = &q
allocation, such as a global or local variable, a dynami- copies the result of &q, which is a pointer based on q,
cally allocated memory block, or a sub-object of a larger to a location pointed to by p, and associates the based-
memory object (e.g., a field in a struct). Memory objects on metadata with that location. Hence, the integrity of
can also be program-specific, e.g., when using custom the based-on metadata associated with sensitive pointers
memory allocators. A control flow destination is a loca- requires that pointers used to update sensitive pointers
tion in the code, such as the start of a function or a return be sensitive as well (we discuss implications of relaxing
location. A target object always has a well defined life- this definition in §3.3). The notion of a sensitive pointer
time; for example, freeing an array and allocating a new is dynamic. For example, a void* pointer 2 in Fig. 1 is
one with the same address creates a different object. sensitive when it points at another sensitive pointer at run
We say a pointer is based on a target object X iff the time, but it is not sensitive when it points to an integer.
pointer is obtained at runtime by (i) allocating X on the A memory-safe program execution trivially satisfies
heap, (ii) explicitly taking the address of X, if X is allo- the CPI property, but memory-safety instrumentation
cated statically, such as a local or global variable, or is a typically has high runtime overhead, e.g., ≥ 2× in state-
control flow target (including return locations, whose ad- of-the-art implementations [35]. Our observation is
dresses are implicitly taken and stored on the stack when that only a small subset of all pointers are responsible
calling a function), (iii) taking the address of a sub-object for making control-flow transfers, and so, by enforc-
y of X (e.g., a field in the X struct), or (iv) computing ing memory safety only for control-sensitive data (and
a pointer expression (e.g., pointer arithmetic, array in- thus incurring no overhead for all other data), we ob-
dexing, or simply copying a pointer) involving operands tain important security guarantees while keeping the cost
that are either themselves based on object X or are not of enforcement low. This is analogous to the control-
pointers. This is slightly stricter version of C99’s “based plane/data-plane separation in network routers and mod-
on” definition: we ensure that each pointer is based on at ern servers [5], with CPI ensuring the safety of data that
most one object. influences, directly or indirectly, the control plane.
The execution of a program is memory-safe iff all Determining precisely the set of pointers that are
pointer dereferences in the execution are safe. A pro- sensitive can only be done at run time. However,
gram is memory-safe iff all its possible executions (for the CPI property can still be enforced using any over-
all inputs) are memory-safe. This definition is consis- approximation of this set, and such over-approximations
tent with the state of the art for C/C++, such as Soft- can be obtained at compile time, using static analysis.
Bounds+CETS [34, 35]. Precise memory safety enforce- 3.2 The CPI Enforcement Mechanism
ment [34, 36, 25] tracks the based-on information for
each pointer in a program, to check the safety of each We now describe a way to retrofit the CPI property into
pointer dereference according to the definition above; the a program P using a combination of static instrumenta-
detection of an unsafe dereference aborts the program. tion and runtime support. Our approach consists of a
static analysis pass that identifies all sensitive pointers in
3.1 The Code-Pointer Integrity (CPI) Property P and all instructions that operate on them (§3.2.1), an
A program execution satisfies the code-pointer integrity instrumentation pass that rewrites P to “protect” all sen-
property iff all its dereferences that either dereference or sitive pointers, i.e., store them in a separate, safe memory
access sensitive pointers are safe. Sensitive pointers are region and associate, propagate, and check their based-
3
on metadata (§3.2.2), and an instruction-level isolation pointer casts and casts between pointers and integers. If
mechanism that prevents non-protected memory opera- a value v is ever cast to a sensitive pointer type within
tions from accessing the safe region (§3.2.3). For perfor- the function being analyzed, or is passed as an argument
mance reasons, we handle return addresses stored on the or returned to another function where it is cast to a sen-
stack separately from the rest of the code pointers using sitive pointer, the analysis considers v to be sensitive as
a safe stack mechanism (§3.2.4). well. This analysis may fail when the data flow between
v and its cast to a sensitive pointer type cannot be fully re-
3.2.1 CPI Static Analysis
covered statically, which might cause false violation re-
We determine the set of sensitive pointers using type- ports (we have not observed any during our evaluation).
based static analysis: a pointer is sensitive if its type Such casts are a common problem for all pointer-based
is sensitive. Sensitive types are: pointers to functions, memory safety mechanisms for C/C++ that do not re-
pointers to sensitive types, pointers to composite types quire source code modifications [34].
(such as struct or array) that contains one or more mem- A key benefit of CPI is its selectivity: the number of
bers of sensitive types, or universal pointers (i.e., void*, pointer operations deemed to be sensitive is a small frac-
char* and opaque pointers to forward-declared structs tion of all pointer operations in a program. As we show
or classes). A programmer could additionally indicate, in §5, for SPEC CPU2006, the CPI type-based analy-
if desired, other types to be considered sensitive, such sis identifies for instrumentation 6.5% of all pointer ac-
as struct ucred used in the FreeBSD kernel to store pro- cesses; this translates into a reduction of performance
cess UIDs and jail information. All code pointers that a overhead of 16 – 44× relative to full memory safety.
compiler or runtime creates implicitly (such as return ad- Nevertheless, we still think CPI can benefit from more
dresses, C++ virtual table pointers, and setjmp buffers) sophisticated analyses. CPI can leverage any kind of
are sensitive as well. points-to static analysis, as long as it provides an over-
Once the set of sensitive pointers is determined, we approximate set of sensitive pointers. For instance, when
use static analysis to find all program instructions that extending CPI to also protect select non-code-pointer
manipulate these pointers. These instructions include data, we think DSA [27, 28] could prove more effective.
pointer dereferences, pointer arithmetic, and memory
(de-)allocation operations that calls to either (i) corre- 3.2.2 CPI Instrumentation
sponding standard library functions, (ii) C++ new/delete CPI instruments a program in order to (i) ensure that all
operators, or (iii) manually annotated custom allocators. sensitive pointers are stored in a safe region, (ii) create
The derived set of sensitive pointers is over- and propagate metadata for such pointers at runtime, and
approximate: it may include universal pointers that never (iii) check the metadata on dereferences of such pointers.
end up pointing to sensitive values at runtime. For in- In terms of memory layout, CPI introduces a safe re-
stance, the C/C++ standard allows char* pointers to point gion in addition to the regular memory region (Fig. 2).
to objects of any type, but such pointers are also used for Storage space for sensitive pointers is allocated in both
C strings. As a heuristic, we assume that char* pointers the safe region (the safe pointer store) and the regular
that are passed to the standard libc string manipulation region (as usual); one of the two copies always remains
functions or that are assigned to point to string constants unused. This is necessary for universal pointers (e.g.,
are not universal. Neither the over-approximation nor the void*), which could be stored in either region depend-
char* heuristic affect the security guarantees provided by ing on whether they are sensitive at run time or not, and
CPI: over-approximation merely introduces extra over- also helps to avoid some compatibility issues that arise
head, while heuristic errors may result in false violation from the change in memory layout. The address in regu-
reports (though we never observed any in practice). lar memory is used as an offset to to look up the value of
Memory manipulation functions from libc, such as a sensitive pointer in the safe pointer store.
memset or memcpy, could introduce a lot of overhead in The safe pointer store maps the address &p of sensi-
CPI: they take void* arguments, so a libc compiled with tive pointer p, as allocated in the regular region, to the
CPI would instrument all accesses inside the functions, value of p and associated metadata. The metadata for p
regardless of whether they are operating on sensitive data describes the target object on which p is based: lower
or not. CPI’s static analysis instead detects such cases by and upper address bounds of the object, and a temporal
analyzing the real types of the arguments prior to being id (see Fig. 2). The layout of the safe pointer store is
cast to void*, and the subsequent instrumentation pass similar to metadata storage in SoftBounds+CETS [35],
handles them separately using type-specific versions of except that CPI also stores the value of p in the safe
the corresponding memory manipulation functions. pointer store. Combined with the isolation of the safe re-
We augmented type-based static analysis with a data- gion (§3.2.3), this allows CPI to guarantee full memory
flow analysis that handles most practical cases of unsafe safety of all sensitive pointers without having to instru-
4
Regular Region Safe Region
Code (RX) Heap (RW) Unsafe Stacks (RW) Safe Pointer Store (RW) Safe Stacks (RW) (for example, callback addresses) but not write them.
struct A
T1' T2' T3' value | upper | lower | id T1 T2 T3 Modern compilers contain powerful static analysis
passes that can often prove statically that certain memory
Figure 2: CPI memory layout: The safe region contains the accesses are always safe. The CPI instrumentation pass
safe pointer store and the safe stacks. The location of a sensitive precedes compiler optimizations, thus allowing them to
pointer on the left (shaded) remains unused, while the value of potentially optimize away some of the inserted checks
this pointer and its metadata are stored in the safe pointer store. while preserving the security guarantees.
The safe stacks T1 , T2 , T3 have corresponding stacks T10 , T20 , T30
3.2.3 Isolating the Safe Region
in regular memory to allocate unsafe stack objects.
The safe region can only be accessed via CPI intrinsic
ment all pointer operations. instructions, and they properly handle pointer metadata
The instrumentation step changes instructions that op- and the safe stack (§3.2.4). The mechanism for achieving
erate on sensitive pointers, as found by CPI’s static anal- this isolation is architecture-dependent.
ysis, to create and propagate the metadata directly fol- On x86-32, we rely on hardware segment protection.
lowing the based-on definition in §3.1. Instructions that We make the safe region accessible through a dedicated
explicitly take addresses of a statically allocated memory segment register, which is otherwise unused, and con-
object or a function, allocate a new object on the heap, or figure limits for all other segment registers to make the
take an address of a sub-object are instrumented to create region inaccessible through them. The CPI intrinsics are
metadata that describe the corresponding object. Instruc- then turned into code that uses the dedicated register and
tions that compute pointer expressions are instrumented ensures that no other instructions in the program use that
to propagate the metadata accordingly. Instructions that register. The segment registers are configured by the pro-
load or store sensitive pointers to memory are replaced gram loader, whose integrity we assume in our threat
with CPI intrinsic instructions (§3.2.3) that load or store model; we also prevent the program from reconfiguring
both the pointer values and their metadata from/to the the segment registers via system calls. None of the pro-
safe pointer store. In principle, call and return instruc- grams we evaluated use the segment registers.
tions also store and load code pointers, and so would On x86-64, CPI relies on the fact that no addresses
need to be instrumented, but we instead protect return pointing into the safe region are ever stored in the regular
addresses using a safe stack (§3.2.4). region. This architecture no longer enforces the segment
Every dereference of a sensitive pointer is instru- limits, however it still provides two segment registers
mented to check at runtime whether it is safe, using the with configurable base addresses. Similarly to x86-32,
metadata associated with the pointer being dereferenced. we use one of these registers to point to the safe region,
Together with the restricted access to the safe region, this however, we choose the base address of the safe region
results in precise memory safety for all sensitive pointers. at random and rely on preventing access to it through
Universal pointers (void* and char*) are stored in ei- information hiding. Unlike classic ASLR though, our
ther the safe pointer store or the regular region, de- hiding is leak-proof: since the objects in the safe region
pending on whether they are sensitive at runtime or not. are indexed by addresses allocated for them in the regu-
CPI instruments instructions that cast from non-sensitive lar region, no addresses pointing into the safe region are
to universal pointer types to assign special “invalid” ever stored in regular memory at any time during execu-
metadata (e.g., with lower bound greater than the upper tion. The 48-bit address space of modern x86-64 CPUs
bound) for the resulting universal pointers. These point- makes guessing the safe region address impractical, be-
ers, as a result, would never be allowed to access the safe cause most failed guessing attempts would crash the pro-
region. CPI intrinsics for universal pointers would only gram, and such frequent crashes can easily be detected
store a pointer in the safe pointer store if it had valid by other means.
metadata, and only load it from the safe pointer store if it Other architectures could use randomization-based
contained valid metadata for that pointer; otherwise, they protection as well, or rely on precise software fault isola-
would store/load from the regular region. tion (SFI) [11]. SFI requires that all memory operations
CPI can be configured to simultaneously store pro- in a program are instrumented, but the instrumentation is
tected pointers in both the safe pointer store and regu- lightweight: it could be as small as a single and opera-
lar regions, and check whether they match when loading tion if the safe region occupies the entire upper half of
them. In this debug mode, CPI detects all attempts to hi- the address space of a process. In our experiments, the
jack control flow using non-protected pointer errors; in additional overhead introduced by SFI was less than 5%.
the default mode, such attempts are silently prevented. Since sensitive pointers form a small fraction of all
This debug mode also provides better compatibility with data stored in memory, the safe pointer store is highly
non-instrumented code that may read protected pointers sparse. To save memory, it can be organized as a hash ta-
5
ble, a multi-level lookup table, or as a simple array rely- separate stack for arrays and variables whose addresses
ing on the sparse address space support of the underlying are taken. However, we use the safe stack to enforce
OS. We implemented and evaluated all three versions, the CPI property instead of implementing software fault
and we discuss the fastest choice in §4. isolation. The safe stack is also comparable to language-
In the future, we plan to leverage Intel MPX [24] for based approaches like Cyclone [25] or CCured [36] that
implementing the safe region, as described in §4. simply allocate these objects on the heap, but our ap-
proach has significantly lower performance overhead.
3.2.4 The Safe Stack
Compared to a shadow stack like in CFI [1], which
CPI treats the stack specially, in order to reduce perfor- duplicates return instruction pointers outside of the at-
mance overhead and complexity. This is primarily be- tacker’s access, the CPI safe stack presents several ad-
cause the stack hosts values that are accessed frequently, vantages: (i) all return instruction pointers and most local
such as return addresses that are code pointers accessed variables are protected, whereas a shadow stack only pro-
on every function call, as well as spilled registers (tempo- tects return instruction pointers; (ii) the safe stack is com-
rary values that do not fit in registers and compilers store patible with uninstrumented code that uses just the regu-
on the stack). Furthermore, tracking which of these val- lar stack, and it directly supports exceptions, tail calls,
ues will end up at run time in memory (and thus need to and signal handlers; (iii) the safe stack has near-zero
be protected) vs. in registers is difficult, as the compiler performance overhead (§5.2), because only a handful
decides which registers to spill only during late stages of of functions require extra stack frames, while a shadow
code generation, long after CPI’s instrumentation pass. stack allocates a shadow frame for every function call.
A key observation is that the safety of most accesses The safe stack can be employed independently from
to stack objects can be checked statically during com- CPI, and we believe it can replace stack cookies [14]
pilation, hence such accesses require no runtime checks in modern compilers. By providing precise protection
or metadata. Most stack frames contain only memory of all return addresses (which are the target of ROP at-
objects that are accessed exclusively within the corre- tacks today), spilled registers, and some local variables,
sponding function and only through the stack pointer the safe stack provides substantially stronger security
register with a constant offset. We therefore place all than stack cookies, while incurring equal or lower per-
such proven-safe objects onto a safe stack located in the formance overhead and deployment complexity.
safe region. The safe stack can be accessed without any
3.3 Code-Pointer Separation (CPS)
checks. For functions that have memory objects on their
stack that do require checks (e.g., arrays or objects whose The code-pointer separation property trades some of
address is passed to other functions), we allocate separate CPI’s security guarantees for reduced runtime overhead.
stack frames in the regular memory region. In our expe- This is particularly relevant to C++ programs with many
rience, less than 25% of functions need such additional virtual functions, where the fraction of sensitive point-
stack frames (see Table 2). Furthermore, this fraction is ers instrumented by CPI can become high, since every
much smaller among short functions, for which the over- pointer to an object that contains virtual functions is sen-
head of setting up the extra stack frame is non-negligible. sitive. We found that, on average, CPS reduces overhead
The safe stack mechanism consists of a static analysis by 4.3× (from 8.4% for CPI down to 1.9% for CPS), and
pass, an instrumentation pass, and runtime support. The in some cases by as much as an order of magnitude.
analysis pass identifies, for every function, which objects CPS further restricts the set of protected pointers to
in its stack frame are guaranteed to be accessed safely code pointers only, leaving pointers that point to code
and can thus be placed on the safe stack; return addresses pointers uninstrumented. We additionally restrict the
and spilled registers always satisfy this criterion. For the definition of based-on by requiring that a code pointer be
objects that do not satisfy this criterion, the instrumen- based only on a control flow destination. This restriction
tation pass inserts code that allocates a stack frame for prevents attackers from “forging” a code pointer from a
these objects on the regular stack. The runtime support value of another type, but still allows them to trick the
allocates regular stacks for each thread and can be imple- program into reading or updating wrong code pointers.
mented either as part of the threading library, as we did CPS is enforced similarly to CPI, except (i) for the
on FreeBSD, or by intercepting thread create/destroy, as criteria used to identify sensitive pointers during static
we did on Linux. CPI stores the regular stack pointer in- analysis, and (ii) that CPS does not need any metadata.
side the thread control block, which is pointed to by one Control-flow destinations (pointed to by code pointers)
of the segment registers and can thus be accessed with a do not have bounds, because the pointer value must al-
single memory read or write. ways match the destination exactly, hence no need for
Our safe stack layout is similar to double stack ap- bounds metadata. Furthermore, they are typically static,
proaches in ASR [6] and XFI [18], which maintain a hence do not need temporal metadata either (there are
6
a few rare exceptions, like unloading a shared library, piler infrastructure [30], with modifications to LLVM li-
which are handled separately). This reduces the size braries, the clang compiler, and the compiler-rt runtime.
of the safe region and the number of memory accesses To use Levee, one just needs to pass additional flags to
when loading or storing code pointers. If the safe region the compiler to enable CPI (-fcpi), CPS (-fcps), or safe-
is organized as a simple array, a CPS-instrumented pro- stack protection (-fstack-protector-safe). Levee works
gram performs essentially the same number of memory on unmodified programs and supports Linux, FreeBSD,
accesses when loading or storing code pointers as a non- and Mac OS X in both 32-bit and 64-bit modes.
instrumented one; the only difference is that the pointers Levee can be downloaded from the project home-
are being loaded or stored from the safe pointer store in- page http://levee.epfl.ch, and we plan to push our
stead of their original location (universal pointer load or changes to the upstream LLVM.
store instructions still introduce one extra memory access Analysis and instrumentation passes: We imple-
per such instruction). As a result, CPS can be enforced mented the static analysis and instrumentation for CPI
with low performance overhead. as two LLVM passes, directly following the design
CPS guarantees that (i) code pointers can only be from §3.2.1 and §3.2.2. The LLVM passes operate on the
stored to or modified in memory by code pointer store LLVM intermediate representation (IR), which is a low-
instructions, and (ii) code pointers can only be loaded by level strongly-typed language-independent program rep-
code pointer load instructions from memory locations to resentation tailored for static analyses and optimization
which previously a code pointer store instruction stored purposes. The LLVM IR is generated from the C/C++
a value. Combined with the safe stack, CPS precisely source code by clang, which preserves most of the type
protects return addresses. CPS is stronger than most CFI information that is required by our analysis, with a few
implementations [1, 54, 53], which allow any vulnerable corner cases. For example, in certain cases, clang does
instruction in a program to modify any code pointer; they not preserve the original types of pointers that are cast
only check that the value of a code pointer (when used in to void* when passing them as an argument to memset
an indirect control transfer) points to a function defined or similar functions, which is required for the memset-
in a program (for function pointers) or directly follows related optimizations discussed in §3.2.2. The IR also
a call instruction (for return addresses). CPS guarantee does not distinguish between void* and char* (represents
(i) above restricts the attack surface, while guarantee (ii) both as i8*), but this information is required for our string
restricts the attacker’s flexibility by limiting the set of lo- pointers detection heuristic. We augmented clang to al-
cations to which the control can be redirected—the set ways preserve such type information as LLVM metadata.
includes only entry points of functions whose addresses Safe stack instrumentation pass: The safe stack instru-
were explicitly taken by the program. mentation targets functions that contain on-stack mem-
To illustrate this difference, consider the case of the ory objects that cannot be put on the safe stack. For such
Perl interpreter, which implements its opcode dispatch functions, it allocates a stack frame on the unsafe stack
by representing internally a Perl program as a sequence and relocates corresponding variables to that frame.
of function pointers to opcode handlers and then calling Given that most of the functions do not need an un-
in its main execution loop these function pointers one safe stack, Levee uses the usual stack pointer (rsp reg-
by one. CFI statically approximates the set of legitimate ister on x86-64) as the safe stack pointer, and stores the
control-flow targets, which in this case would include all unsafe stack pointer in the thread control block, which is
possible Perl opcodes. CPS however permits only calls accessible directly through one of the segment registers.
through function pointers that are actually assigned. This When needed, the unsafe stack pointer is loaded into an
means that a memory bug in a CFI-protected Perl in- IR local value, and Levee relies on the LLVM register
terpreter may permit an attacker to divert control flow allocator to pick the register for the unsafe stack pointer.
and execute any Perl opcode, whereas in a CPS-protected Levee explicitly encodes unsafe stack operations as IR
Perl interpreter the attacker could at most execute an op- instructions that manipulate an unsafe stack pointer; it
code that exists in the running Perl program. leaves all operations that use a safe stack intact, letting
CPS provides strong control-flow integrity guarantees the LLVM code generator manage them. Levee performs
and incurs low overhead (§5). We found that it prevents these changes as a last step before code generation (di-
all recent attacks designed to bypass CFI [19, 15, 9]. We rectly replacing LLVM’s stack-cookie protection pass),
consider CPS to be a solid alternative to CPI in those thus ensuring that it operates on the final stack layout.
cases when CPI’s (already low) overhead seems too high. Certain low-level functions modify the stack pointer
directly. These functions include setjmp/longjmp and
4 Implementation exception handling functions (which store/load the stack
We implemented a CPI/CPS enforcement tool for pointer), and thread create/destroy functions, which al-
C/C++, called Levee, on top of the LLVM 3.3 com- locate/free stacks for threads. On FreeBSD we provide
7
full-system CPI, so we directly modified these functions come from external libraries compiled without Levee, in-
to support the dual stacks. On Linux, our instrumentation line assembly, or dynamically generated code. Levee can
pass finds setjmp/longjmp and exception handling func- be configured to simultaneously store sensitive pointers
tions in the program and inserts required instrumentation in both the safe and the regular regions, in which case
at their call sites, while thread create/destroy functions non-instrumented code works fine as long as it only reads
are intercepted and handled by the Levee runtime. sensitive pointers but doesn’t write them.
Runtime support library: Most of the instrumentation Inline assembly and dynamically generated code can
by the above passes are added as intrinsic function calls, still update sensitive pointers if instrumented with appro-
such as cpi ptr store() or cpi memcpy(), which are im- priate calls to the Levee runtime, either manually by a
plemented by Levee’s runtime support library (a part programmer or directly by the code generator.
of compiler-rt). This design cleanly separates the safe Dynamically generated code (e.g., for JIT compila-
pointer store implementation from the instrumentation tion) poses an additional problem: running the generated
pass. In order to avoid the overhead associated with ex- code requires making writable pages executable, which
tra function calls, we ensure that some of the runtime violates our threat model (this is a common problem for
support functions are always inlined. We compile these most control-flow integrity mechanisms). One solution
functions into LLVM bitcode and instruct clang to link is to use hardware or software isolation mechanisms to
this bitcode into every object file it compiles. Functions isolate the code generator from the code it generates.
that are called rarely (e.g., cpi abort(), called when a CPI Sensitive data protection: Even though the main focus
violation is detected) are never inlined, in order to reduce of CPI is control-flow hijack protection, the same tech-
the instruction cache footprint of the instrumentation. nique can be applied to protect other types of sensitive
We implemented and benchmarked several versions of data. Levee can treat programmer-annotated data types
the safe pointer store map in our runtime support library: as sensitive and protect them just like code pointers. CPI
a simple array, a two-level lookup table, and a hashtable. could also selectively protect individual program vari-
The array implementation relies on the sparse address ables (as opposed to types), however it would require re-
space support of the underlying OS. Initially we found it placing the type-based static analysis described in §3.2.1
to perform poorly on Linux, due to many page faults (es- with data-based points-to analysis such as DSA [27, 28].
pecially at startup) and additional TLB pressure. Switch- Future MPX-based implementation: Intel announced
ing to superpages (2 MB on Linux) made this simple ta- a hardware extension, Intel MPX, to be used for
ble the fastest implementation of the three. hardware-enforced memory safety [23]. It is proposed as
a testing tool, probably due to the associated overhead;
Binary level functionality: Some code pointers in bina-
no overhead numbers are available at the time of writing.
ries are generated by the compiler and/or linker, and can-
We believe MPX (or similar) hardware can be re-
not be protected on the IR level. Such pointers include
purposed to enforce CPI with lower performance over-
the ones in jump tables, exception handler tables, and the
head than our existing software-only implementation.
global offset table. Bounds checks for the jump tables
MPX provides special registers to store bounds along
and the exception handler tables are already generated
with instructions to check them, and a hardware-based
by LLVM anyway, and the tables themselves are placed
implementation of a pointer metadata store (analogous to
in read-only memory, hence cannot be overwritten. We
the safe pointer store in our design), organized as a two-
rely on the standard loader’s support for read-only global
level lookup table. Our implementation can be adapted
offset tables, using the existing RTLD NOW flag.
to use these facilities once MPX-enabled hardware be-
Limitations: The CPI design described in §3 includes comes available. We believe that a hardware-based CPI
both spatial and temporal memory safety enforcement implementation can reduce the overhead of a software-
for sensitive pointers, however our current prototype im- only CPI in much the same way as HardBound [16] or
plements spatial memory safety only. It can be easily Watchdog [33] reduced the overhead of SoftBound.
extended to enforce temporal safety by directly applying Adopting MPX for CPI might require implementing
the technique described in [35] for sensitive pointers. metadata loading logic in software. Like CPI, MPX also
Levee currently supports Linux, FreeBSD and Mac stores the pointer value together with the metadata. How-
OS user-space applications. We believe Levee can be ever, being a testing tool, MPX chooses compatibility
ported to protect OS kernels as well. Related technical with non-instrumented code over security guarantees: it
challenges include integration with the kernel memory uses the stored pointer value to check whether the origi-
management subsystem and handling of inline assembly. nal pointer was modified by non-instrumented code and,
CPI and CPS require instrumenting all code that ma- if yes, resets the bounds to [0, ∞]. In contrast, CPI’s guar-
nipulates sensitive pointers; non-instrumented code can antees depend on preventing any non-instrumented code
cause unnecessary aborts. Non-instrumented code could from ever modifying sensitive pointer values.
8
5 Evaluation
In this section we evaluate Levee’s effectiveness, effi-
ciency, and practicality. We experimentally show that
both CPI and CPS are 100% effective on RIPE, the most
recent attack benchmark we are aware of (§5.1). We eval-
uate the efficiency of CPI, CPS, and the safe stack on
SPEC CPU2006, and find average overheads of 8.4%,
1.9%, and 0% respectively (§5.2). To demonstrate prac-
ticality, we recompile with CPI/CPS/ safe stack the base
FreeBSD plus over 100 packages and report results on
several benchmarks (§5.3).
We ran our experiments on an Intel Xeon E5-2697
with 24 cores @ 2.7GHz in 64-bit mode with 512GB
RAM. The SPEC benchmarks ran on an Ubuntu Precise
Pangolin (12.04 LTS), and the FreeBSD benchmarks in
a KVM-based VM on this same system.
5.1 Effectiveness on the RIPE Benchmark
We described in §3 the security guarantees provided by Figure 3: Levee performance for SPEC CPU2006, under three
CPI, CPS, and the safe stack based on their design; to configurations: full CPI, CPS only, and safe stack only.
experimentally evaluate their effectiveness, we use the Safe Stack CPS CPI
RIPE [49] benchmark. This is a program with many dif- Average (C/C++) 0.0% 1.9% 8.4%
ferent security vulnerabilities and a set of 850 exploits Median (C/C++) 0.0% 0.4% 0.4%
that attempt to perform control-flow hijack attacks on the Maximum (C/C++) 4.1% 17.2% 44.2%
program using various techniques. Average (C only) -0.4% 1.2% 2.9%
Levee deterministically prevents all attacks, both in Median (C only) -0.3% 0.5% 0.7%
CPS and CPI mode; when using only the safe stack, it Maximum (C only) 4.1% 13.3% 16.3%
prevents all stack-based attacks. On vanilla Ubuntu 6.06, Table 1: Summary of SPEC CPU2006 performance overheads.
which has no built-in defense mechanisms, 833–848 ex-
ploits succeed when Levee is not used (some succeed
probabilistically, hence the range). On newer systems, C++ objects that contain virtual function tables—such
fewer exploits succeed, due to built-in protection mech- pointers are sensitive for CPI, and so all operations on
anisms, changes in the run-time layout, and compatibil- them are instrumented. Same reason holds for gcc: it
ity issues with the RIPE benchmark. On vanilla Ubuntu embeds function pointers in some of its data structures
13.10, with all protections (DEP, ASLR, stack cookies) and then uses pointers to these structures frequently.
disabled, 197–205 exploits succeed. With all protections The next-most important source of overhead are libc
enabled, 43–49 succeed. With CPS or CPI, none do. memory manipulation functions, like memset and mem-
The RIPE benchmark only evaluates the effectiveness cpy. When our static analysis cannot prove that a call
of preventing existing attacks; as we argued in §3 and to such a function uses as arguments only pointers to
according to the proof outlined in Appendix A, CPI ren- non-sensitive data, Levee replaces the call with one to a
ders all (known and unknown) memory corruption-based custom version of an equivalent function that checks the
control-flow hijack attacks impossible. safe pointer store for each updated/copied word, which
introduces overhead. We expect to remove some of this
5.2 Efficiency on SPEC CPU2006 Benchmarks overhead using improved static analysis and heuristics.
In this section we evaluate the runtime overhead of CPI, CPS averages 1.2–1.8% overhead, and exceeds 5% on
CPS, and the safe stack. We report numbers on all SPEC only two benchmarks, omnetpp and perlbench. The for-
CPU2006 benchmarks written in C and C++ (our pro- mer is due to the large number of virtual function calls
totype does not handle Fortran). The results are sum- occurring at run time, while the latter is caused by a
marized in Table 1 and presented in detail in Fig. 3. specific way in which perl implements its opcode dis-
We also compare Levee to two related approaches, Soft- patch: it internally represents a program as a sequence of
Bound [34] and control-flow integrity [1, 54, 53]. function pointers to opcode handlers, and its main execu-
CPI performs well for most C benchmarks, however it tion loop calls these function pointers one after the other.
can incur higher overhead for programs written in C++. Most other interpreters use a switch for opcode dispatch.
This overhead is caused by abundant use of pointers to Safe stack provided a surprise: in 9 cases (out of
9
19), it improves performance instead of hurting it; in Benchmark FNUStack MOCPS MOCPI
one case (namd), the improvement is as high as 4.2%, 400 perlbench 15.0% 1.0% 13.8%
more than the overhead incurred by CPI and CPS. This 401 bzip2 27.2% 1.3% 1.9%
403 gcc 19.9% 0.3% 6.0%
is because objects that end up being moved to the regular
429 mcf 50.0% 0.5% 0.7%
(unsafe) stack are usually large arrays or variables that
433 milc 50.9% 0.1% 0.7%
are used through multiple stack frames. Moving such 444 namd 75.8% 0.6% 1.1%
objects away from the safe stack increases the locality 445 gobmk 10.3% 0.1% 0.4%
of frequently accessed values on the stack, such as CPU 447 dealII 12.3% 6.6% 13.3%
register values temporarily stored on the stack, return ad- 450 soplex 9.5% 4.0% 2.5%
dresses, and small local variables. 453 povray 26.8% 0.8% 4.7%
The safe stack overhead exceeds 1% in only three 456 hmmer 13.6% 0.2% 2.0%
cases, perlbench, xalanbmk, and povray. We studied 458 sjeng 50.0% 0.1% 0.1%
the disassembly of the most frequently executed func- 462 libquantum 28.5% 0.4% 2.3%
tions that use unsafe stack frames in these programs and 464 h264ref 20.5% 1.5% 2.8%
470 lbm 16.6% 0.6% 1.5%
found that some of the overhead is caused by inefficient
471 omnetpp 6.9% 10.5% 36.6%
handling of the unsafe stack pointer by LLVM’s register
473 astar 9.0% 0.1% 3.2%
allocator. Instead of keeping this pointer in a single regis- 482 sphinx3 19.7% 0.1% 4.6%
ter and using it as a base for all unsafe stack accesses, the 483 xalancbmk 17.5% 17.5% 27.1%
program keeps moving the unsafe stack pointer between
different registers and often spills it to the (safe) stack. Table 2: Compilation statistics for Levee: FNUStack lists what
We believe this can be resolved by making the register fraction of functions need an unsafe stack frame; MOCPS and
allocator algorithm aware of the unsafe stack pointer. MOCPI show the fraction of memory operations instrumented
for CPS and CPI, respectively.
In contrast to the safe stack, stack cookies deployed
today have an overhead of up to 5%, and offer strictly
Benchmark Safe Stack CPS CPI SoftBound
weaker protection than our safe stack implementation.
401 bzip2 0.3% 1.2% 2.8% 90.2%
The data structures used for the safe stack and the safe
447 dealII 0.8% -0.2% 3.7% 60.2%
memory region result in memory overhead compared to 458 sjeng 0.3% 1.8% 2.6% 79.0%
a program without protection. We measure the memory 464 h264ref 0.9% 5.5% 5.8% 249.4%
overhead when using either a simple array or a hash ta-
ble. For SPEC CPU2006 the median memory overhead Table 3: Overhead of Levee and SoftBound on SPEC programs
for the safe stack is 0.1%; for CPS the overhead is 2.1% that compile and run errors-free with SoftBound.
for the hash table and 5.6% for the array; and for CPI the
overhead is 13.9% for the hash table and 105% for the much less frequently for CPI, because CPI instruments
array. We did not optimize the memory overhead yet and much fewer pointers. Many of the SPEC benchmarks
believe it can be improved in future prototypes. either don’t compile or terminate with an error when in-
In Table 2 we show compilation statistics for Levee. strumeted by SoftBound, which illustrates the practical
The first column shows that only a small fraction of all impact of this difference.
functions require an unsafe stack frame, confirming our Comparison to control-flow integrity (CFI): The av-
hypothesis from §3.2.4. The other two columns con- erage overhead for compiler-enforced CFI is 21% for
firm the key premises behind our approach, namely that a subset of the SPEC CPU2000 benchmarks [1] and 5-
CPI requires much less instrumentation than full memory 6% for MCFI [39] (without stack pointer integrity). CC-
safety, and CPS needs much less instrumentation than FIR [53] reports an overhead of 3.6%, and binCFI [54]
CPI. The numbers also correlate with Fig. 3. reports 8.54% for SPEC CPU2006 to enforce a weak
Comparison to SoftBound: We compare with Soft- CFI property with globally merged target sets. WIT [3],
Bound [34] on the SPEC benchmarks. We cannot fairly a source-based mechanism that enforces both CFI and
reuse the numbers from [34], because they are based on write integrity protection, has 10% overhead1 .
an older version of SPEC. In Table 3 we report numbers At less than 2%, CPS has the lowest overhead among
for the four C/C++ SPEC benchmarks that can compile all existing CFI solutions, while providing stronger pro-
with the current version of SoftBound. This comparison tection guarantees. Also, CPI’s overhead is bested only
confirms our hypothesis that CPI requires significantly by CCFIR. However, unlike any CFI mechanism, CPI
lower overhead compared to full memory safety. guarantees the impossibility of any control-flow hijack
Theoretically, CPI suffers from the same compatibil- attack based on memory corruptions. In contrast, there
ity issues (e.g., handling unsafe pointer casts) as pointer- 1 Wewere unable to find open-source implementations of compiler-
based memory safety. In practice, such issues arise based CFI, so we can only compare to published overhead numbers.
10
Benchmark Safe Stack CPS CPI
Static page 1.7% 8.9% 16.9%
Wsgi test page 1.0% 4.0% 15.3%
Dynamic page 1.4% 15.9% 138.8%
11
Attack step Property Mechanism Stops all control-flow hijacks? Avg. overhead
Randomization ASLR [40], ASLP [26] No: vulnerable to information leaks ~10%
… to address of PointGuard [13] No: vulnerable to information leaks 10%
③ gadget/shellcode DSR [6] No: vulnerable to information leaks 20%
NOP insertion [21] No: vulnerable to information leaks 2%
Figure 5: Summary of control-flow hijack defense mechanisms aligned with individual steps that are necessary for a successful
attack. The figure on the left is a simplified version of the complete memory corruption diagram in [46].
Bound). We believe CPI is the first to stop all control- ing illegal modification of the code pointer whenever it
flow hijack attacks at this step. is used, while CPI protects the load and store of a code
pointer, thus preventing the corruption in the first place.
Randomization techniques, like ASLR [40] and
CPI provides precise and provable security guarantees.
ASLP [26], mitigate attacks by restricting the attacker’s
In Step 5, the execution of injected code is prevented
knowledge of the memory layout of the application in
by enforcing the non-executable (NX) data policy, but
Step 3. PointGuard [13] and DSR [7] (which is similar to
code-reuse attacks remain possible.
probabilistic WIT) randomize the data representation by
High level policies, e.g., restricting the allowed sys-
encrypting pointer values, but face compatibility prob-
tem calls of an application, limit the power of the at-
lems. Software diversity [21] allows fine-grained, per-
tacker even in the presence of a successful control-flow
instance code randomization. Randomization techniques
hijack attack in Step 6. Software fault isolation (SFI)
are defeated by information leaks through, e.g., memory
techniques [32, 18, 11, 50, 52] restrict indirect control-
corruption bugs [45] or side channel attacks [22].
flow transfers and memory accesses to part of the ad-
Control-flow integrity [1] ensures that the targets of dress space, enforcing a sandbox that contains the attack.
all indirect control-flow transfers point to valid code lo- SFI prevents an attack from escaping the sandbox and al-
cations in Step 4. All CFI solutions rely on statically lows the enforcement of a high-level policy, while CPI
pre-computed context-insensitive sets of valid control- enforces the control-flow inside the application.
flow target locations. Many practical CFI solutions sim-
ply include every function in a program in the set of 7 Conclusion
valid targets [53, 54, 29, 47]. Even if precise static This paper describes code-pointer integrity (CPI), a way
analysis was be feasible, CFI could not guarantee pro- to protect systems against all control-flow hijacks that
tection against all control-flow hijack attacks, but rather exploit memory bugs, and code-pointer separation, a re-
merely restrict the sets of potential hijack targets. In- laxed form of CPI that still provides strong guarantees.
deed, recent results [19, 15, 9] show that many exist- The key idea is to selectively provide full memory safety
ing CFI solutions can be bypassed in a principled way. for just a subset of a program’s pointers, namely code
CFI+SFI [52], Strato [51] and MIPS [38] enforce an even pointers. We implemented our approach and showed that
more relaxed, statically defined CFI property in order to it is effective, efficient, and practical. Given its advan-
enforce software-based fault isolation (SFI). CCFI [31] tageous security-to-overhead ratio, we believe our ap-
encrypts code pointers in memory and provides secu- proach marks a step toward deterministically secure sys-
rity guarantees close to CPS. Data-flow based techniques tems that are fully immune to control-flow hijack attacks.
like data-flow integrity (DFI) [10] or dynamic taint anal-
ysis (DTA) [42] can enforce that the used code pointer Acknowledgments
was not set by an unrelated instruction or to untrusted We thank the anonymous reviewers and our shepherd
data, respectively. These techniques may miss some at- Junfeng Yang for their valuable input. We are grate-
tacks or cause false positives, and have higher perfor- ful to Martin Abadi, Herbert Bos, Miguel Castro, Vijay
mance costs than CPI and CPS. Stack cookies, CFI, DFI, D’Silva, Ulfar Erlingsson, Johannes Kinder, Per Larsen,
and DTA protect control-transfer instructions by detect- Jim Larus, Santosh Nagarakatte, and Jonas Wagner for
12
Atomic Types a ::= int | p∗ Operation Semantics
readu Mu l return Mu [l]
Pointer Types p ::= a | s | f | void writeu Mu l v set Mu [l] = v
Struct Types s ::= struct{ . . . ; ai : idi ; . . . } reads Ms l return Ms [l], if l is allocated; return none otherwise
LHS Expressions lhs ::= x | ∗lhs | lhs.id | lhs−>id writes Ms l v(b,e) set Ms [l] = v(b,e) , if l is allocated;
do nothing otherwise
RHS Expressions rhs ::= i | & f | rhs + rhs | lhs | &lhs writes Ms l none set Ms [l] = none, if l is allocated;
| (a) rhs | sizeof(p) | malloc(rhs) do nothing otherwise
Commands c ::= c; c | lhs = rhs | f () | (∗lhs)() malloc E i allocate a memory object of size i in both E.Mu and
E.Ms (at the same address); fail when out of memory
13
erational semantics of CPI through three classes of rules. Ms . In particular, any attempts to access values of sensi-
The (E, lhs) ⇒l ls : a and (E, lhs) ⇒l lu : a rules spec- tive types through regular lvalues cause aborts:
ify how left hand side expressions are evaluated to a sensitive a sensitive a
safe or regular locations, respectively. The (E, rhs) ⇒r (E, lhs) ⇒l lu : a∗ (E, lhs) ⇒l lu : a
(v(b,e) , E 0 ) and (E, rhs) ⇒r (v, E 0 ) rules specify how right
(E, ∗lhs) ⇒l Abort (E, lhs = rhs) ⇒c (Abort, E)
hand side expressions are evaluated to safe values with
bounds or regular values, respectively, possibly modify- Note that these rules can only be invoked if the value of
ing the environment through memory allocation (turning the sensitive type was obtained by casting from a regu-
it from E to E 0 ). Finally, the (E, c) ⇒c (r, E 0 ) rules spec- lar type using a corresponding type casting rule. Levee
ify how commands are executed, possibly modifying the relaxes the casting rules to allow propagation of bounds
environment, where r can be either OK or an error code. information through certain right-hand-side expressions
We only present the rules that are most important for the of regular types. This relaxation handles most common
CPI semantics, omitting rules that simply represent the cases of unsafe type casting; it affects performance (in-
standard semantics of the C language. ducing more instrumentation) but not correctness.
Bounds information is initially assigned when allocat- Some sensitive types (only void∗ in our simplified
ing a memory object or when taking a function’s address version of C), can hold regular values at runtime. For ex-
(both operations always return safe values): ample, a variable of void∗ type can first be used to store
a function pointer and subsequently re-used to store an
(E, rhs) = i int∗ value. The following rules handle such cases:
address( f ) = l malloc E i = (l, E 0 )
sensitive a
(E, & f ) ⇒r (l(l,l) ) (E, malloc(i)) ⇒r (l(l,l+i) , E 0 ) sensitive a (E, lhs) ⇒l ls : a
(E, lhs) ⇒l ls : a∗ (E, rhs) ⇒r v : a
Taking the address of a variable from S if its type is reads (E.Ms )l = none E 0 .Mu = writeu (E.Mu ) l v
sensitive is analogous. Structure field access operations readu (E.Mu )l = l 0 E 0 .Ms = writes (E.Ms ) l none
either narrow bounds information accordingly, or strip it (E, ∗lhs) ⇒l lu0 : a (E, lhs = rhs) ⇒c (OK, E 0 )
if the type of the accessed field is regular.
Type casting results in a safe value iff a safe value is Memory operations on regular types always access
cast to a sensitive type: regular memory, without any additional runtime checks,
following the unsafe memory semantics of C.
sensitive a0 ¬sensitive a0
(E, rhs) ⇒l v(b,e) : a (E, rhs) ⇒l v(b,e) : a ¬sensitive a
¬sensitive a (E, lhs) ⇒l l : a
(E, (a0 )rhs) ⇒r (v(b,e) , E) (E, (a0 )rhs) ⇒r (v, E) (E, lhs) ⇒l l : a∗ (E, rhs) ⇒r v : a
readu (E.Mu )l = l 0 E 0 .Mu = writeu (E.Mu ) l v
(E, rhs) ⇒l v : a
(E, ∗lhs) ⇒l lu0 : a (E, lhs = rhs) ⇒c (OK, E 0 )
(E, (a0 )rhs) ⇒r (v, E)
These accesses to regular memory can go out of bounds
The next set of rules describes memory operations but, given that readu and writeu operations can only
(pointer dereference and assignment) on sensitive types modify regular memory Mu , it does not violate memory
and safe values: safety of the safe memory.
Finally, indirect calls abort if the function pointer be-
sensitive a sensitive a
ing called is not safe:
(E, lhs) ⇒l ls : a∗ (E, lhs) ⇒l ls : a∗
0
reads (E.Ms )ls = some l(b,e) 0
reads (E.Ms )ls = some l(b,e) (E, lhs) ⇒r ls : f ∗ (E, lhs) ⇒r lu : f ∗
0
l ∈ [b, e − sizeof(a)] 0
l 6∈ [b, e − sizeof(a)] (E, (∗lhs)()) ⇒c (OK, E 0 ) (E, (∗lhs)()) ⇒c (Abort, E)
(E, ∗lhs) ⇒l ls0 :a (E, ∗lhs) ⇒l Abort Note that the operational rules for values that are safe
at runtime are fully equivalent to the corresponding Soft-
sensitive a Bound rules [34] and, therefore, satisfy the SoftBound
(E, lhs) ⇒l ls : a
safety invariant which, as proven in [34], ensures mem-
(E, rhs) ⇒r v(b,e) : a
ory safety for these values. According to the sensitive
E 0 .Ms = writes (E.Ms )ls v(b,e)
criterion and the safe location dereference and indirect
(E, lhs = rhs) ⇒c (OK, E 0 ) function call rules above, all dereferences of pointers that
These rules are identical to the corresponding rules of require protection according to the CPI property are al-
SoftBound [34] and ensure full spatial memory safety of ways safe at runtime, or the program aborts. Therefore,
all memory objects in the safe memory. Only operations the operational semantics defined above indeed ensure
matching those rules are allowed to access safe memory the CPI property as defined in §3.1.
14
References [13] C. Cowan, S. Beattie, J. Johansen, and P. Wa-
[1] M. Abadi, M. Budiu, U. Erlingsson, and J. Ligatti. gle. PointguardTM: protecting pointers from buffer
Control-flow integrity. In ACM Conf. on Computer overflow vulnerabilities. In USENIX Security Sym-
and Communication Security, 2005. posium, 2003.
[14] C. Cowan, C. Pu, D. Maier, H. Hintony, J. Walpole,
[2] P. Akritidis. Cling: A memory allocator to mitigate
P. Bakke, S. Beattie, A. Grier, P. Wagle, and
dangling pointers. In USENIX Security Symposium,
Q. Zhang. StackGuard: Automatic adaptive detec-
2010.
tion and prevention of buffer-overflow attacks. In
[3] P. Akritidis, C. Cadar, C. Raiciu, M. Costa, and USENIX Security Symposium, 1998.
M. Castro. Preventing memory error exploits with
[15] L. Davi, A.-R. Sadeghi, D. Lehmann, and F. Mon-
WIT. In IEEE Symp. on Security and Privacy, May
rose. Stitching the gadgets: On the ineffectiveness
2008.
of coarse-grained control-flow integrity protection.
[4] P. Akritidis, M. Costa, M. Castro, and S. Hand. In USENIX Security Symposium, 2014.
Baggy Bounds Checking: An Efficient and
[16] J. Devietti, C. Blundell, M. M. K. Martin, and
Backwards-compatible Defense Against Out-of-
S. Zdancewic. Hardbound: Architectural support
bounds Errors. In USENIX Security Symposium,
for spatial safety of the c programming language. In
2009.
Intl. Conf. on Architectural Support for Program-
[5] G. Altekar and I. Stoica. Focus replay debugging ming Languages and Operating Systems, 2008.
effort on the control plane. USENIX Workshop on [17] D. Dhurjati, S. Kowshik, and V. Adve. SAFE-
Hot Topics in Dependability, 2010. Code: enforcing alias analysis for weakly typed
[6] S. Bhatkar, E. Bhatkar, R. Sekar, and D. C. Duvar- languages. SIGPLAN Notices, 41(6):144–157, June
ney. Efficient techniques for comprehensive pro- 2006.
tection from memory error exploits. In USENIX [18] Ú. Erlingsson, M. Abadi, M. Vrable, M. Budiu, and
Security Symposium, 2005. G. C. Necula. XFI: Software guards for system ad-
[7] S. Bhatkar and R. Sekar. Data Space Randomiza- dress spaces. In Symp. on Operating Systems De-
tion. In Intl. Conf. on Detection of Intrusions and sign and Implementation, 2006.
Malware, and Vulnerability Assessment, 2008. [19] E. Göktaş, E. Athanasopoulosy, H. Bos, and G. Por-
[8] T. Bletsch, X. Jiang, V. W. Freeh, and Z. Liang. tokalidis. Out of control: Overcoming control-flow
Jump-oriented programming: a new class of code- integrity. In IEEE Symp. on Security and Privacy,
reuse attack. In ACM Symp. on Information, Com- 2014.
puter and Communications Security, 2011. [20] N. Hasabnis, A. Misra, and R. Sekar. Light-weight
[9] N. Carlini and D. Wagner. Rop is still dangerous: bounds checking. In IEEE/ACM Symp. on Code
Breaking modern defenses. In USENIX Security Generation and Optimization, 2012.
Symposium, 2014. [21] A. Homescu, S. Neisius, P. Larsen, S. Brunthaler,
and M. Franz. Profile-guided automated software
[10] M. Castro, M. Costa, and T. Harris. Securing soft-
diversity. In IEEE/ACM Symp. on Code Generation
ware by enforcing data-flow integrity. In Symp.
and Optimization, 2013.
on Operating Systems Design and Implementation,
2006. [22] R. Hund, C. Willems, and T. Holz. Practical timing
side channel attacks against kernel space aslr. In
[11] M. Castro, M. Costa, J.-P. Martin, M. Peinado,
IEEE Symp. on Security and Privacy, 2013.
P. Akritidis, A. Donnelly, P. Barham, and R. Black.
Fast byte-granularity software fault isolation. In [23] Intel Architecture Instruction Set Exten-
ACM Symp. on Operating Systems Principles, sions Programming Reference. http:
2009. //download-software.intel.com/sites/
default/files/319433-015.pdf, 2013.
[12] S. Checkoway, L. Davi, A. Dmitrienko, A.-R.
Sadeghi, H. Shacham, and M. Winandy. Return- [24] Intel. Introduction to Intel memory protec-
oriented programming without returns. In ACM tion extensions. https://software.intel.com/en-
Conf. on Computer and Communication Security, us/articles/introduction-to-intel-memory-
2010. protection-extensions, July 2013.
15
[25] T. Jim, J. G. Morrisett, D. Grossman, M. W. Hicks, [36] G. Necula, J. Condit, M. Harren, S. McPeak, and
J. Cheney, and Y. Wang. Cyclone: A safe dialect of W. Weimer. CCured: Type-safe retrofitting of
C. In USENIX Annual Technical Conf., 2002. legacy software. ACM Trans. on Programming
Languages and Systems, 27(3):477–526, 2005.
[26] C. Kil, J. Jun, C. Bookholt, J. Xu, and P. Ning. Ad-
dress space layout permutation (ASLP): Towards [37] Nergal. The advanced return-into-lib(c) ex-
fine-grained randomization of commodity softwar. ploits. Phrack, 11(58):http://phrack.com/
In Annual Computer Security Applications Conf., issues.html?issue=67&id=8, Nov. 2007.
2006. [38] B. Niu and G. Tan. Monitor integrity protection
with space efficiency and separate compilation. In
[27] C. Lattner and V. Adve. Automatic Pool Alloca-
ACM Conf. on Computer and Communication Se-
tion: Improving Performance by Controlling Data
curity, 2013.
Structure Layout in the Heap. In ACM Conf. on
Programming Language Design and Implementa- [39] B. Niu and G. Tan. Modular control-flow integrity.
tion, 2005. In ACM Conf. on Programming Language Design
and Implementation, 2014.
[28] C. Lattner, A. Lenharth, and V. Adve. Mak-
ing Context-Sensitive Points-to Analysis with Heap [40] PaX-Team. PaX ASLR (Address Space Lay-
Cloning Practical For The Real World. In ACM out Randomization). http://pax.grsecurity.
Conf. on Programming Language Design and Im- net/docs/aslr.txt, 2003.
plementation, 2007.
[41] Phoronix. Phoronix test suite. http://www.
[29] J. Li, Z. Wang, T. K. Bletsch, D. Srinivasan, M. C. phoronix-test-suite.com/.
Grace, and X. Jiang. Comprehensive and ef- [42] E. J. Schwartz, T. Avgerinos, and D. Brumley. All
ficient protection of kernel control data. IEEE you ever wanted to know about dynamic taint anal-
Transactions on Information Forensics and Secu- ysis and forward symbolic execution (but might
rity, 6(4):1404–1417, Dec. 2011. have been afraid to ask). In IEEE Symp. on Security
and Privacy, 2010.
[30] The LLVM compiler infrastructure. http://
llvm.org/. [43] K. Serebryany, D. Bruening, A. Potapenko, and
D. Vyukov. AddressSanitizer: A Fast Address San-
[31] A. J. Mashtizadeh, A. Bittau, D. Mazieres, and ity Checker. In USENIX Annual Technical Conf.,
D. Boneh. Cryptographically enforced control 2012.
flow integrity. http://arxiv.org/abs/1408.1451, Aug.
2014. [44] H. Shacham. The geometry of innocent flesh on the
bone: Return-into-libc without function calls (on
[32] S. McCamant and G. Morrisett. Evaluating sfi for a the x86). In ACM Conf. on Computer and Commu-
cisc architecture. In USENIX Security Symposium, nication Security, 2007.
2006.
[45] K. Z. Snow, F. Monrose, L. Davi, A. Dmitrienko,
[33] S. Nagarakatte, M. M. K. Martin, and C. Liebchen, and A.-R. Sadeghi. Just-in-time code
S. Zdancewic. Watchdog: Hardware for safe reuse: On the effectiveness of fine-grained address
and secure manual memory management and space layout randomization. In IEEE Symp. on Se-
full memory safety. In Intl. Symp. on Computer curity and Privacy, pages 574–588, 2013.
Architecture, 2012.
[46] L. Szekeres, M. Payer, T. Wei, and D. Song. SoK:
[34] S. Nagarakatte, J. Zhao, M. M. Martin, and Eternal war in memory. IEEE Symp. on Security
S. Zdancewic. SoftBound: Highly Compatible and and Privacy, 2013.
Complete Spatial Safety for C. In ACM Conf. on [47] C. Tice, T. Roeder, P. Collingbourne, S. Check-
Programming Language Design and Implementa- oway, Ú. Erlingsson, L. Lozano, and G. Pike. En-
tion, 2009. forcing forward-edge control-flow integrity in gcc
& llvm. In USENIX Security Symposium, 2014.
[35] S. Nagarakatte, J. Zhao, M. M. Martin, and
S. Zdancewic. CETS: Compiler Enforced Tempo- [48] A. van de Ven and I. Molnar. Exec Shield.
ral Safety for C. In Intl. Symp. on Memory Man- https://www.redhat.com/f/pdf/rhel/
agement, 2010. WHP0006US_Execshield.pdf, 2004.
16
[49] J. Wilander, N. Nikiforakis, Y. Younan, M. Kamkar,
and W. Joosen. RIPE: Runtime intrusion prevention
evaluator. In Annual Computer Security Applica-
tions Conf., 2011.
[50] B. Yee, D. Sehr, G. Dardyk, J. B. Chen, R. Muth,
T. Ormandy, S. Okasaka, N. Narula, and N. Ful-
lagar. Native client: A sandbox for portable, un-
trusted x86 native code. In IEEE Symp. on Security
and Privacy, 2009.
[51] B. Zeng, G. Tan, and Ú. Erlingsson. Strato: A retar-
getable framework for low-level inlined-reference
monitors. In USENIX Security Symposium, 2013.
[52] B. Zeng, G. Tan, and G. Morrisett. Combining
control-flow integrity and static analysis for effi-
cient and validated data sandboxing. In ACM Conf.
on Computer and Communication Security, 2011.
[53] C. Zhang, T. Wei, Z. Chen, L. Duan, L. Szekeres,
S. McCamant, D. Song, and W. Zou. Practical Con-
trol Flow Integrity & Randomization for Binary Ex-
ecutables. In IEEE Symp. on Security and Privacy,
2013.
[54] M. Zhang and R. Sekar. Control flow integrity for
COTS binaries. In USENIX Security Symposium,
2013.
17