CIS Technical Report #MS-CIS-13-05 March 28, 2013 Ironclad C++ A Library-Augmented Type-Safe Subset of C++ Christian DeLozier Richard Eisenberg Santosh Nagarakatte Peter-Michael Osera Milo M. K. Martin Steve Zdancewic Computer and Information Science Department, University of Pennsylvania {delozier, eir, santoshn, posera, milom, stevez}@cis.upenn.edu
Abstract Refactor Code No C++ remains a widely used programming language, despite Source Is Valid? retaining many unsafe features from C. These unsafe fea- Code Validator tures often lead to violations of type and memory safety, Yes (Safe) which manifest as buffer overflows, use-after-free vulner- abilities, or abstraction violations. Malicious attackers are Ironclad C++ Compiler Library Executable able to exploit such violations to compromise application (unmodified) and system security. This paper introduces Ironclad C++, an approach to bring the benefits of type and memory safety Figure 1. Workflow for Coding with Ironclad C++ to C++. Ironclad C++ is, in essence, a library-augmented type-safe subset of C++. All Ironclad C++ programs are Recognizing this problem, many approaches have been valid C++ programs, and thus Ironclad C++ programs can be proposed to prevent memory safety violations or even en- compiled using standard, off-the-shelf C++ compilers. How- force full memory safety in C and C-like languages [2, 4, ever, not all valid C++ programs are valid Ironclad C++ pro- 9, 10, 12, 13, 16, 20–22, 27, 31]. Collectively, these prior grams. To determine whether or not a C++ program is a valid works identified several key principles for bringing safety ef- Ironclad C++ program, Ironclad C++ uses a syntactic source ficiently to C. However, one challenge in making C memory code validator that statically prevents the use of unsafe C++ safe is that C provides limited language support for creating features. For properties that are difficult to check statically type-safe programming abstractions. In contrast, although Ironclad C++ applies dynamic checking to enforce memory C++ includes many unsafe features, C++ does provide ad- safety using templated smart pointer classes. Drawing from vanced constructs that enable type-safe programming, such years of research on enforcing memory safety, Ironclad C++ as templates and dynamically checked type-casts. utilizes and improves upon prior techniques to significantly This paper presents Ironclad C++, an approach to bring reduce the overhead of enforcing memory safety in C++. comprehensive memory and type safety to C++. Ironclad To demonstrate the effectiveness of this approach, we C++ is, in essence, a library-augmented type-safe subset of translate (with the assistance of a semi-automatic refactor- C++. As such, an Ironclad C++ program is a valid C++ ing tool) and test a set of performance benchmarks, multiple program (but not all C++ programs are valid Ironclad C++ bug-detection suites, and the open-source database leveldb. programs). Dynamic checking is implemented in a “smart These benchmarks incur a performance overhead of 12% on pointer” library, so no additional language extensions or average as compared to the unsafe original C++ code, which compiler changes are required. As shown in Figure 1, Iron- is small compared to prior approaches for providing com- clad C++ code is compiled using an unmodified off-the-shelf prehensive memory safety in C and C++. C++ compiler but Ironclad C++ includes a syntactic valida- tion pass that checks whether the input is a legal program. In the following paragraphs, we describe some key prin- 1. Introduction ciples of efficient type safety for C, survey their implemen- C and C++ are widely used programming languages for im- tations in prior work, and describe how these principles can plementing web browsers, native mobile applications, com- be brought to C++ by leveraging existing language features. pilers, databases, and other infrastructure software [28]. C Differentiating array pointers from non-array point- and C++ provide efficiency and low-level control, but these ers. Several prior memory safety proposals have recog- advantages come at the well-known cost of lack of memory nized the performance benefit of distinguishing between a and type safety. This unsafety allows programming errors pointer to an array (which requires bounds information) ver- such as buffer overflows (accessing location beyond the ob- sus a pointer to non-array (a.k.a. singleton) object (which ject or array bounds), use-after-free errors (accessing mem- does not). Doing so typically requires whole-program anal- ory locations that have been freed), and erroneous type casts ysis at compile time or language extensions. For example, to cause arbitrary memory corruption and break program- CCured [22] uses a whole-program type inference at com- ming abstractions. More dangerously, malicious attackers pile time to distinguish between singleton and array pointers, exploit such bugs to compromise system security [25]. Cyclone [16] introduces different type decorators, and some
Ironclad C++: A Library-Augmented Type-Safe Subset of C++ 1 pool allocation approaches [9] create type-homogeneous alone fails to prevent dangling pointers to the stack ob- pools of singleton objects. Systems without such differen- jects. In recognition of this problem, CCured prevents use- tiation implicitly treat all pointers as array pointers, adding after-free errors to stack objects by selectively converting unnecessary space and time overheads for bounds checking. escaping stack-allocated objects into heap-allocated objects Ironclad C++ captures this differentiation between single- (a.k.a. heapification), which unfortunately introduces signif- ton and array pointers without language extension or whole- icant performance overheads in some programs [22]. To pre- program analysis during each compilation by using the well- vent use-after-free errors for stack allocated memory with- known C++ technique of smart pointers [1, 2, 8]. Smart out the performance penalties of heapification, this paper pointers leverage C++’s template and operator overloading introduces hybrid static-dynamic checking of stack pointer constructs, and they have previously been used to dynam- lifetimes. The hybrid checking in Ironclad C++ avoids the ically insert safety checks [2] or perform reference count- performance overheads of heapification with simple static ing [8] on pointer operations. Ironclad C++ requires that all checking and limited dynamic checking. bare C++ pointer types be replaced with one from a suite of Validating conformance statically. Statically validating smart pointers, some of which include bounds information that code conforms to the rules of Ironclad C++ is paramount and thus support pointer arithmetic and indexing (for array for ensuring safety. Without some form of static validation, pointers) and some that avoid the bounds checking overhead prior smart pointers schemes could not alone provide a guar- (for singleton pointers). The distinction between singleton antee of safety because unsafe constructs can still be used and array smart pointer types allows their overloaded oper- outside of the use of smart pointers and smart pointers can ators to perform the minimum dynamic checking necessary be used incorrectly. Our current prototype divides this re- to detect bounds violations based on the type of the pointer. sponsibility between two checkers. First, we created a static Enforcing strong typing. C’s use of void* and unchecked code validator that checks basic syntactic properties of the type casts results in either pessimistic typing assumptions program to ensure that it conforms to the Ironclad C++ sub- that can significantly increase the overhead of dynamic set (e.g. no raw pointers). Second, after carefully precluding checking [20, 24] and/or the failure to detect all memory unsafe constructs with the validator, we then leverage the safety violations. Disallowing unsafe casts in C reduces existing C++ type checker to complete the remaining check- checking overhead, but doing so has typically required aug- ing of type safety. This use of strong static validation distin- menting the C language in some way. For example, Cyclone guishes Ironclad C++’s approach from other non-validated found it necessary to support generics, CCured adds RTTI smart pointer approaches [2, 8] and mark methods for pre- (run-time type information) pointers, and both support struc- cise garbage collection [3, 26]. tural subtyping. However, C++ already provides alternatives Experimentally evaluating Ironclad C++. To evaluate to C’s unsafe constructs (for example, templates and class practicality and performance, we refactored several C/C++ hierarchies). Yet, to facilitate adoption, C++ inherited many programs—over 50k lines in total—into Ironclad C++. We of C’s unsafe constructs. Ironclad C++ takes a different ap- performed this translation with a semi-automatic refactoring proach and explicitly enforces strong typing by disallowing tool we created to assist in this conversion, and we report legacy type-unsafe constructs and requiring that all pointer our experiences converting these programs. We also con- type-cast operations are either known to be safe statically verted multiple test suites designed for evaluating memory or checked dynamically (by building upon C++’s existing safety bug detectors, which confirmed that Ironclad C++ dynamic_cast construct). does successfully detect memory access violations. Using Heap-safety through conservative garbage collection. performance-oriented benchmarks, we measured an average Ironclad C++’s smart pointers provide strong typing and performance overhead of 12% for enforcing complete type bounds safety, but they do not prevent use-after-free er- and memory safety. rors. To avoid the overhead of reference counting [12] or This paper describes Ironclad C++ and reports on our ex- use-after-free checking [2, 21, 31], Ironclad C++ facilitates perience programming in this type-safe C++, including: the use of conservative garbage collection [7] by target- • ing two challenges of using conservative GC to enforce a C++ smart pointer library that efficiently and compre- safety. Conservative collection can lead to non-deterministic hensively enforces strong typing and bounds safety, memory leaks (due to non-pointer data that “looks” like • a hybrid static–dynamic checking technique that prevents a pointer) [5, 26]. To reduce such memory leaks due to use-after-free errors for stack objects, without requiring conservative garbage collection, Ironclad C++ supports costly heap allocation, heap-precise garbage collection. Inspired by prior work • an opt-in, source-level heap-precise garbage collector de- on mostly-copying and more-precise conservative garbage signed to reduce memory leaks due to conservative GC, collection [3, 11, 14, 26], Ironclad’s collector treats the • roots conservatively but supports the precise identification of tools for semi-automated refactoring of existing C and pointers in heap objects by employing mark() class meth- C++ code into the Ironclad subset of C++ and a validator ods to precisely identify pointer fields and pointer containing that enforces compliance with that subset, and members. • experimental evaluation of the performance overheads of Facilitating stack-allocation safety. Garbage collection this approach to enforce memory safety.
Ironclad C++: A Library-Augmented Type-Safe Subset of C++ 2 Type Capabilities Safety Checks Supporting type casts safely. By disallowing raw point- ptr Dereference Null Check ers, Ironclad C++ also implicitly disallows both void* lptr Dereference Null Check pointers and unsafe pointer-to-pointer casts. To support safe Receive address-of object Lifetime Check pointer-to-pointer casts, Ironclad C++ provides a cast
Ironclad C++: A Library-Augmented Type-Safe Subset of C++ 3 Example: Strong Static Typing float radius(Shape * shape){ float radius(ptr
2.2 Bounds Checking with aptr
Ironclad C++: A Library-Augmented Type-Safe Subset of C++ 4 the smart pointer, any use of the this pointer as a func- with dynamic checking on each pointer dereference [2, 21] tion argument, and any method calls. The static validator or maintaining reference counts [12]. In most of the bench- enforces this requirement. marks we tested, conservative garbage collection is fast and incurs low memory overheads (see Section 8). However, one Rule (Init.). A ptr
3. Support for Heap-Precise Garbage 4. Stack Deallocation Safety via lptr Collection Although garbage collection prevents all dangling pointers Along with strong typing and bounds checking, dealloca- to objects on the heap, it does not protect against dangling tion safety (i.e., no dangling pointers) is the final require- pointers to stack-allocated objects. One way to prevent such ment for complete type and memory safety. To prevent dan- errors is to forgo some of the efficiency benefits of stack allo- gling pointers to heap allocated objects, Ironclad C++ uses cation by limiting the use of stack allocation to non-escaping a conservative garbage collector to delay deallocation until objects only (a.k.a. heapification). To avoid the performance a time at which it is known to be safe.1 Garbage collection penalties of heapification, Ironclad C++ provides additional prevents dangling pointers without the overheads associated templated smart pointers that, cooperatively with the static code validator and C++ type checker, uses dynamic lifetime 1 For domains in which garbage collection is not applicable, Iron- checking to prevent use-after-free errors for stack allocations clad C++ still provides type and bounds safety. while avoiding heapification in almost all cases.
Ironclad C++: A Library-Augmented Type-Safe Subset of C++ 5 4.1 The Perils of Heapification template
Ironclad C++: A Library-Augmented Type-Safe Subset of C++ 6 void Acquire(Logger * logger){ heap could be initialized to point to a stack location through obj->logger = logger; the use of a constructor initializer list. Reference class mem- } bers are rare in C++ code and generally discouraged because void Release(){ obj->logger = NULL; the fact that they cannot be reseated makes them inflexible. } For example, an assignment operator cannot properly assign void f(){ a new location to a reference. We encountered only a sin- Logger logger; gle case in astar in which we refactored a reference class Acquire(&logger); member to be a ptr instead. ... Release(); Rule (Reference Class Members). Reference class members } are disallowed. Figure 4. Case in leveldb under which dynamic lifetime Ironclad C++ allows references to be used as function checking could not avoid heapification return values, mainly to support common code idioms, in- cluding chaining function or method calls on an object (e.g. Rule (Stack Pointers). Any pointer to stack object must be std::cout) but restricts the expressions that can be re- held by an laptr or lptr. turned as references. In general, any value with a lifetime To ensure the correct use of local pointers, Ironclad C++ that will persist through the function or method call may be places a few restrictions on where local pointers may be returned safely. The result of the dereference of an aptr or a used. First, a function may not return a local pointer. Sec- ptr can be returned as a reference because the referred loca- ond, a local pointer may not be allocated on the heap. From tion must be in the heap or globals. Ironclad C++ limits the the second restriction, it follows that a local pointer may not expressions that may be returned by reference to reference be declared in a struct or class because Ironclad C++ does function parameters, the dereference of the this pointer, not restrict in which memory space an object may be allo- and class members (of the class that the method was called cated. on). Intuitively, these expressions are allowed because the location they point to must have a lifetime that is at least as Rule (Local Pointer Return). A local pointer may not be long as the lifetime of the return value. returned from a function. Rule (Reference Return Values). A reference return value Rule (Local Pointer Location). A local pointer may not exist may only be initialized from the dereference of an aptr or a on the heap. ptr, from a reference function parameter, from the derefer- With the dynamic lifetime checks described above and ence of the this pointer, or from a class member. these few restrictions placed on the static use of local point- Although we did not identify any such cases in our bench- ers, Ironclad C++ provides deallocation safety for stack ob- marks, it is possible that a valid program will not conform to jects without the need for heapification in most situations. the above static restrictions on reference return values. For For example, stack-allocated arrays can be passed to nested any such cases, Ironclad C++ provides the ref
Ironclad C++: A Library-Augmented Type-Safe Subset of C++ 7 and methods. The store Σ contains both the stack and the The first rule concerns ptrs and requires that the location heap which maps locations ` to values v. A value is tagged pointed to by the ptr is on the heap (at index 0). as being either a ptr or an lptr, along with a pointer-value, n0 pv, which is either a valid location or null. Locations have Σ(x n @π) = ptr (x 0 @π0) n0 = 0 0 n n 0n 0 the form x @y1 .. ym where x is a base location with name Ψ(x @π ) = τ x and store level n, and y .. y is a path in the style of n 1 m Ψ; Σ `st1 x @π : ptrhτi ok Rossie and Friedman [17]. The heap is located at store index 0 and the stack grows with increasing indices starting at in- The second rule concerns lptrs and requires that lptrs only dex 1. The store height index n disambiguates between two exist at base locations without paths (not embedded within a variables with the same name but existing in different stack class) and that the location pointed to by a particular lptr is frames. in the same stack frame or a lower one. Paths allow locations to refer to the inner member of an n0 Σ(x n @ ) = lptr (x 0 @π0) n0 ≤ n object. For example, consider the following definitions: 0 Ψ(x 0n @π0) = τ struct C { ptrhC i a; }; Ψ; Σ ` x n @: lptrhτi ok struct B { C c; }; st1 A declaration B x; in the main function creates a B object 5.3 Statement of Theorem that lives at base location x 1. The location [email protected] refers to Invariant (Pointer lifetime). For all bindings of the form the a field of the C sub-object within B. [x n1 @π 7→ ptr (pv)] and [x n1 @π 7→ lptr (pv)] in Σ, if To simplify the system and to support temporary objects, 1 1 1 1 pv = x n2 @π (i.e., is non-null) then n ≤ n . expressions evaluate to locations. The value denoted by the 2 2 2 1 expression is stored at the location the expression evaluates We now can present our theorem that states that the to. Thus, we can use the same evaluation relation for expres- pointer lifetime invariant is preserved by execution. We sions on either side of an assignment. make use of the summary judgment wf (∆, Ψ, Σ, k), which asserts that the class and method context ∆, the store typing 5.2 Pointer Semantics Ψ, and the store Σ are all consistent with one another. These With respect to the pointer lifetime invariant, the most inter- checks also include the pointer lifetime invariant. Therefore, esting rules concern the assignment of pointers as well as the wf (∆, Ψ, Σ, k) implies that the pointer lifetime invariant constraints we place on their values in the store. The simplest holds for Σ. case is when we assign between two ptr values, where we simply overwrite the left-hand pointer with the right-hand Theorem (Pointer lifetime invariant is preserved). If ∆;n n 0 0 pointer in the store. wf (∆, Ψ, Σ, k), Ψ `stmt s ok, and (Σ, s) −→ (Σ , s ), stmt ∆ Σ(`1) = ptr (pv1) Σ(`2) = ptr (pv2) where k is the maximum stack height used within the state- 0 Σ = Σ [`1 7→ ptr (pv2)] ment s, then the pointer lifetime invariant holds in the store n 0 0 (Σ, `1 = `2) −→ (Σ , skip) Σ . stmt ∆ When assigning a (non-null) lptr to a ptr, we verify that the This theorem is a straightforward corollary of type preser- lptr does indeed point to the heap by checking that the store vation. That is, the standard type preservation lemma tells us wf index of the location referred to by the lptr is 0. that the judgment is preserved by evaluation. 0 Σ(`1) = ptr (pv1) Σ(`2) = lptr (x @π) Theorem (Preservation). 0 0 Σ = Σ [`1 7→ ptr (x @π)] ∆;n 1. If wf (∆, Ψ, Σ, |s| + n), Ψ `stmt s ok, and (Σ, s) −→ n 0 stmt (Σ, `1 = `2) −→∆(Σ , skip) stmt n 0 0 0 0 0 ∆(Σ , s ), then there exists Ψ such that wf (∆, Ψ , Σ , |s|+ ∆;n 0 0 Finally, when assigning (non-null) lptrs, the dynamic check n), Ψ `stmt s ok, and Ψ ⊆n Ψ . ensures that the lptr being assigned to out-lives the location 2. If wf (∆, Ψ, Σ, |e| + n), Ψ `∆;n e : τ, and (Σ, e) −→ exp exp it receives by comparing the appropriate store indices. n 0 0 0 0 0 ∆(Σ , e ), then there exists Ψ such that wf (∆, Ψ , Σ , |e|+ n1 Σ(x1 @π1) = lptr (pv1) 0 ∆;n 0 0 n), Ψ ` e : τ, and Ψ ⊆n Ψ . n2 exp Σ(`2) = lptr (x2 @π2) 0 n1 n2 Σ = Σ [x1 @π1 7→ lptr (x2 @π2)] n2 ≤ n1 We prove preservation at stack height |s|+n (respectively n1 n 0 (Σ, x1 @π1 = `2) −→∆(Σ , skip) |e| + n) where n is the height of the stack up to statement stmt s (respectively e) and |s| (respectively |e|) is the number In addition to standard typing rules for statements and of additional stack frames embedded in the subject of the 0 expressions, Core Ironclad enforces a consistency judgment evaluation. The extra condition Ψ ⊆n Ψ says that new store over the store by way of a store typing Ψ which maps loca- typing Ψ0 has all the bindings of the old store typing Ψ up tions to types τ. In particular, two of these binding consis- to stack height n. The complete details of the proofs of these tency rules for pointers capture the pointer lifetime invariant. theorems can be found in our companion technical report.
Ironclad C++: A Library-Augmented Type-Safe Subset of C++ 8 6. Validator for Ironclad C++ void f(void*); Although many of the rules of Ironclad C++ are simple ⇓ (Manual) enough for a well-intentioned programmer to follow un- template
Ironclad C++: A Library-Augmented Type-Safe Subset of C++ 9 Manual Code Changes Automated Code Changes Benchmark Lang Class Ptrs Refs LoC C++ Alloc Pre Post Ptr Types SysFunc Alloc Casts blackscholes C 1 20% N/A 405 0 5 4 2 5 5 4 0 bzip2 C 2 47% N/A 5731 16 41 28 14 224 21 30 30 lbm C 1 57% N/A 904 1 3 20 6 49 7 2 4 sjeng C 2 46% N/A 10544 45 24 164 158 310 82 30 0 astar C++ 25 7% 35% 4280 0 60 2 7 72 15 76 2 canneal C++ 3 27% 29% 2817 0 0 2 9 72 3 2 1 fluidanimate C++ 4 7% 7% 2785 0 1 0 2 85 7 44 1 leveldb C++ 66 49% 24% 16188 0 0 160 149 1028 69 195 33 namd C++ 15 46% 7% 3886 0 0 0 44 265 11 69 10 streamcluster C++ 5 37% 0% 1767 0 25 2 2 63 17 9 21 swaptions C++ 1 39% 0% 1095 0 11 1 12 63 3 0 9 Table 2. Characterization of the evaluated programs. From left to right, benchmark name, source language, number of class- es/structs, % pointer declarations, % reference declarations, lines of code, manually refactored lines of code, and automatically refactored lines of code tool to the code. This refactoring tool performs simple au- Two notable outliers required more manual refactor- tomated code modifications, including modifying pointer ing than the rest of the refactored programs (sjeng and type declarations (T* to ptr
7.4 Step 4: Post-Refactoring Modifications leveldb leveldb required to a larger number of manual Considering that C++ is a large language with many cor- code modifications compared to the benchmarks with fewer ner cases, the refactoring tool does not automatically han- total lines of code. In particular, the Slice class in leveldb dle every possible code modification. We also performed a contains a constructor that accepts a const char *. Refac- few manual code changes following refactoring. Given that toring this constructor to Ironclad C++ converts the param- refactoring is intended to be performed only once, the num- eter to type aptr
Ironclad C++: A Library-Augmented Type-Safe Subset of C++ 10 ing that can reduce the performance penalty of providing key parts of the STL to emulate the checking that a fully memory safety. refactored version would perform. We performed four ma- bzip2 In bzip2, the generateMTFValues function cre- jor modifications on the containers used in our benchmarks. ates a temporary pointer to a stack allocated array in a doubly First, we changed the default allocator to be the gc allocator. nested loop. In the Ironclad C++ version of this code, the Second, we inserted bounds checks on all array operations, temporary pointer becomes an laptr, which must initial- including the indexing operators for string and vector. ize its lowerBound on each iteration of the outer loop. Fur- Third, we modified methods that accepted or returned raw ther, the temporary pointer was used in pointer arithmetic, pointers to instead use Ironclad C++ smart pointers. Finally, which is relatively expensive (compared to array indexing) we modified the iterators to each container to avoid access- for Ironclad C++ aptr and laptr types. To improve the ing invalid memory. For string and vector, this was as performance of this code, we replaced the temporary pointer simple as replacing the raw pointer iterator with an aptr. with an integer index and used array indexing off of the orig- For map and set, we modified the tree_iterator to avoid inal stack allocated array instead of pointer arithmetic on the iterating past the root or end nodes of the tree. temporary. This optimization reduced the normalized run- External Libraries Ideally, library source-code would also time from 1.53x to 1.35x. be refactored to Ironclad C++, but we acknowledge that it streamcluster The streamcluster benchmark spends may not always be feasible. In such cases, Ironclad C++ pro- the majority of its runtime in the distance function, which tects what it can, while begrudgingly allowing the program computes the pointwise distance between two vectors. Due to call unsafe library code through methods on the smart to the use of a tight for-loop with repeated indexing into pointer classes that allow the underlying raw pointer to be the input vectors, our initial Ironclad C++ version of this passed to a library call. This functionality is provided for benchmark suffered from unnecessarily high bounds check- the case where refactoring is not possible, similarly to how ing overheads. To reduce this overhead, we replaced the loop Java provides the JNI for access to unsafe C/C++ code. This with a call to a reduce function, provided by the Ironclad behavior is optional and can be disabled. C++ library, that simply bounds checks the start and end in- 7.7 Bug Detection Effectiveness dices of the reduction on both input arrays, and then runs the computation at unchecked speeds. With this optimization, As a coarse sanity check on the implementation of the Iron- the streamcluster benchmark executes with no measur- clad C++ library, we tested Ironclad C++ on multiple suites able overhead compared to the original. of known bugs, including selected programs from BugBench (gzip, man, ncompress, and polymorph) [19], thirty array- swaptions Swaptions spends most of its runtime per- out-of-bounds vulnerability test cases from the NIST Juliet forming operations on vectors and matrices of floating Suite [23], and the Wilander test suite [30]. As expected, pointer numbers. For matrix structures, swaptions uses an Ironclad C++ safely aborted on all buggy inputs. We note array-of-array-pointers to approximate a two-dimensional that these tests are not definitive proof of Ironclad C++’s i.e. array ( , aptr
Ironclad C++: A Library-Augmented Type-Safe Subset of C++ 11 1.5 Translated Bounds Bounds + Stack Bounds + Stack + GC Bounds + Stack + H-P GC
1.0
0.5
0.0 astar blackscholes bzip2 canneal fluidanimate lbm ldb-fseq ldb-rreverse ldb-rhot namd sjeng stcluster swaptions average Normalized Runtime Figure 6. Normalized runtimes for (adding checking from left to right) refactored Ironclad C++ code with no checking, bounds checked arrays, safe stack allocations, safe heap allocations, and heap-precise garbage collection.
2.0 Translated Bounds Bounds + Stack Bounds + Stack + GC Bounds + Stack + H-P GC 1.5 1.0 0.5
0.0 astar blackscholes bzip2 canneal fluidanimate lbm ldb-fseq ldb-rreverse ldb-rhot namd sjeng stcluster swaptions average Normalized Mem. Usage Figure 7. Normalized memory usage for Ironclad C++ code with (from left to right) bounds checking metadata, bounds and stack checking metadata, both metadata with conservative GC, and both metadata with heap-precise GC. sor. The Ironclad C++ library includes implementations of 8.3 Overheads of Garbage Collection the various smart pointer classes and safe versions of various Figure 6 shows that the runtime overhead of garbage col- C standard library functions. We modified the libcxx STL lection is negligible in our benchmarks. Although perhaps implementation to provide safe iterators, bounds-checked surprising, our benchmarks are not typical of programs used array access operations, and interfaces that accept and re- in garbage collection studies, which are generally selected turn smart pointers instead of raw pointers. To reduce for their frequent allocation behavior. For example several of overhead, the smart pointer implementation uses clang’s our benchmarks allocate memory only during initialization always_inline function attribute to ensure the compiler and never deallocate any memory—resulting in extremely inlines the dereference and indexing operators. We imple- rare collection invocation (less than once per second for mented the heap-precise collector by extending the Boehm- many benchmarks). The benchmark that collects most fre- Demers-Weiser conservative garbage collector [7] to use the quently (six hundred times per second), swaptions, incurs marking algorithm described in Section 3. We used Val- an additional 23% performance penalty due to garbage col- grind’s Massif tool to measure memory usage overheads. lection. The garbage collector increases memory usage by 14% on average and up to 85% for leveldb-fillseq (Figure 7) when compared to explicit deallocation with the same un- 8.2 Overall Performance derlying memory allocator. One caveat is that the allocator The overall performance overhead for bringing type and underlying the conservative collector uses more space on av- memory safety to the refactored programs is just 12% on erage than clang’s default memory allocator (by 29%) even average. Figure 6 shows these results, and it also includes re- when operating in explicit memory deallocation mode; if sults for multiple configurations to show the impact of each this overhead is included, the total memory overhead of GC aspect of Ironclad C++. The left-most bar in each group rises to 43% (not shown on Figure 7). (“Translated”) shows that the performance of the original codes is the same as that of the refactored, strongly typed 8.4 Benefits of Heap-Precise Collection benchmarks when dynamic bound checking, dynamic life- Figure 7 also shows the impact of Ironclad’s heap-precise time checking, and the garbage collector are all disabled. extension to the garbage collector. In most cases, the heap- These results indicate that there is negligible overhead from precise collector provides no appreciable reduction of mem- replacing raw pointers with smart pointers. The second bar ory usage, but in two cases — astar and leveldb-fillseq from the left in each group of Figure 6 (“Bounds”) shows — the pure-conservative collector suffers due to imprecise that the performance overhead of bounds checking is respon- identification of heap pointers. When applying heap-precise sible for almost all of the 12% overall performance over- collection, these programs’ memory usage is reduced by head. 28% and 66%, respectively, compared to the unmodified
Ironclad C++: A Library-Augmented Type-Safe Subset of C++ 12 conservative collector. The memory overheads of bzip2 and collection, Ironclad C++ provides an optional interface for namd do not improve under heap-precise collection due to precisely identifying heap pointers, which was shown to de- stack-allocated arrays (which are not tagged) with elements crease average memory usage of garbage collection. We in- that are misidentified as pointers. The overall average mem- vestigated both heapification and dynamic lifetime check- ory overhead of GC vs. explicit memory allocation drops ing for enforcing stack deallocation safety and found that from 14% to 9% with the addition of heap-precise collec- dynamic lifetime checking offered flexibility, memory con- tion. trol, and limited source code modifications as compared to We observe a 2% runtime overhead increase for heap- heapification. Overall, our experiences and experimental re- precise garbage collection due to data layout changes from sults indicate that Ironclad C++ has the potential to be an the inclusion of virtual pointers in each tagged allocation. effective, low-overhead, and pragmatic approach for bring- This hypothesis was confirmed by tagging allocations but ing comprehensive memory safety to C++. using the completely conservative collector, which yielded the same runtime overheads. References 8.5 Benefits of Dynamic Lifetime Checking [1] A. Alexandrescu. Modern C++ Design: Generic Program- ming and Design Patterns Applied. Addison-Wesley, Boston, Dynamic lifetime checking incurs less than 1% overhead MA, 2001. over the baseline of providing no safety checking for stack [2] T. M. Austin, S. E. Breach, and G. S. Sohi. Efficient Detection deallocation. We originally observed overheads of 17% in of All Pointer and Array Access Errors. In PLDI, June 1994. bzip2, but these overheads were due to the creation of an [3] J. Bartlett. Mostly-Copying Garbage Collection Picks Up laptr temporary in a tight loop. The optimization described Generations and C++. Technical report, 1989. in Section 7.5 eliminated the overhead from dynamic life- [4] E. D. Berger and B. G. Zorn. DieHard: Probabilistic Memory time checking in bzip2. Safety for Unsafe Languages. In PLDI, pages 158–168, June In Figure 8, we compare dynamic lifetime checking to 2006. the other notable alternative for stack allocation safety: [5] H.-J. Boehm. Space Efficient Conservative Garbage Collec- tion. In PLDI, pages 197–206, June 1993. heapification. On average, heapification is 2x slower than [6] H.-J. Boehm and M. Spertus. Garbage collection in the next dynamic lifetime checking. We observed two situations in C++ standard. In ISMM, pages 30–38, Jun 2009. which heapification lead to increased performance over- [7] H.-J. Boehm and M. Weiser. Garbage Collection in an Unco- heads: a stack-allocated array escaping to a function (oc- operative Environment. Software — Practice & Experience, curs in sjeng) and calling a method on a stack-allocated 18(9):807–820, Sept. 1988. object (occurs in leveldb). As a result, dynamic lifetime [8] D. Colvin, G. and Adler, D. Smart Pointers - Boost 1.48.0. checking is faster than heapification by 27.5x (sjeng), 6.2x Boost C++ Libraries, Jan. 2012. www.boost.org/docs/ (ldb-fseq), 9.1x (ldb-rreverse), and 6.4x (ldb-rhot). libs/1_48_0/libs/smart_ptr/smart_ptr.htm. In almost all cases, dynamic lifetime checking avoids the [9] D. Dhurjati and V. Adve. Backwards-Compatible Array Bounds Checking for C with Very Low Overhead. In ICSE, use of heapification for enforcing stack deallocation safety. pages 162–171, 2006. The runtime performance of dynamic lifetime checking is [10] D. Dhurjati, S. Kowshik, V. Adve, and C. Lattner. Memory nearly identical to the performance of code with no safety Safety Without Runtime Checks or Garbage Collection. In checking for stack deallocation. LCTES, pages 69–80, 2003. [11] D. Edelson and I. Pohl. A Copying Collector for C++. In 8.6 Summary POPL, pages 51–58, Jan. 1991. Ironclad C++ enforces comprehensive memory safety for [12] D. Gay, R. Ennals, and E. Brewer. Safe Manual Memory C++ at an average runtime overhead of 12%. Without heap- Management. In ISMM, Oct. 2007. precise garbage collection, Ironclad C++ incurs a memory [13] R. Hastings and B. Joyce. Purify: Fast Detection of Mem- overhead of 14%. Heap-precise garbage collection mitigates ory Leaks and Access Errors. In Proc. of the Winter Usenix a few of the worst-case benchmarks for conservative col- Conference, 1992. lection and further reduces the memory overhead to 9%. [14] M. Hirzel and A. Diwan. On the type accuracy of garbage collection. In ISMM, pages 1–11, Oct. 2000. By avoiding heapification, Ironclad C++’s dynamic lifetime [15] International Standard ISO/IEC 14882:2011. Programming checking provides a 2x speedup over heapified code. It may Languages – C++. International Organization for Standards, be possible to further reduce the overheads of memory safety 2011. in Ironclad C++ through additional source-level refactorings [16] T. Jim, G. Morrisett, D. Grossman, M. Hicks, J. Cheney, and similar to those demonstrated in Section 7.5, compiler opti- Y. Wang. Cyclone: A Safe Dialect of C. In USENIX, June mization, or static analysis. 2002. [17] J. Jonathan G. Rossie and D. P. Friedman. An Algebraic Se- 9. Conclusion mantics of Subobjects. In OOPSLA, Nov. 2002. [18] D. Lomet. Making Pointers Safe in System Programming Ironclad C++ brings type safety to C++ at a runtime over- Languages. IEEE Transactions on Software Engineering, head of 12%. We demonstrated the feasibility of refactor- pages 87 – 96, Jan. 1985. ing C and C++ code to Ironclad C++ with the help of a [19] S. Lu, Z. Li, F. Qin, L. Tan, P. Zhou, and Y. Zhou. Bug- semi-automatic refactoring tool. With heap-precise garbage bench: Benchmarks for Evaluating Bug Detection tools. In
Ironclad C++: A Library-Augmented Type-Safe Subset of C++ 13 Heapify (No GC) Dynamic Lifetime (No GC) Dynamic Lifetime (With GC) Unsafe 1.0 0.8 0.6 0.4 0.2
0.0 astar blackscholes bzip2 canneal fluidanimate lbm ldb-fseq ldb-rreverse ldb-rhot namd sjeng stcluster swaptions geom Normalized Runtime Figure 8. Runtime normalized to using heapification with GC. Bars from left to right are heapification with unsafe deallo- cation, dynamic lifetime checking (safe stack allocations), dynamic lifetime checking with GC (safe allocations), and unsafe deallocation.
In PLDI Workshop on the Evaluation of Software Defect De- tection Tools, June 2005. [20] S. Nagarakatte, J. Zhao, M. M. K. Martin, and S. Zdancewic. SoftBound: Highly Compatible and Complete Spatial Mem- ory Safety for C. In PLDI, June 2009. [21] S. Nagarakatte, J. Zhao, M. M. K. Martin, and S. Zdancewic. CETS: Compiler Enforced Temporal Safety for C. In ISMM, Jun 2010. [22] G. C. Necula, J. Condit, M. Harren, S. McPeak, and W. Weimer. CCured: Type-Safe Retrofitting of Legacy Soft- ware. ACM TOPLAS, 27(3), May 2005. [23] NIST Juliet Test Suite for C/C++. NIST, 2010. http://samate.nist.gov/SRD/testCases/suites/ Juliet-2010-12.c.cpp.zip. [24] Y. Oiwa. Implementation of the Memory-safe Full ANSI-C Compiler. In PLDI, pages 259–269, June 2009. [25] J. Pincus and B. Baker. Beyond Stack Smashing: Recent Ad- vances in Exploiting Buffer Overruns. IEEE Security & Pri- vacy, 2(4):20–27, 2004. [26] J. Rafkind, A. Wick, M. Flatt, and J. Regehr. Precise Garbage Collection for C. In ISMM, Jun 2009. [27] M. S. Simpson and R. K. Barua. MemSafe: Ensuring the Spatial and Temporal Memory Safety of C at Runtime. In IEEE International Workshop on Source Code Analysis and Manipulation, pages 199–208, 2010. [28] B. Stroustrup. Software Development for Infrastructure. Com- puter, 45:47–58, Jan. 2012. [29] E. Unger. Severe memory problems on 32-bit Linux, April 2012. https://groups.google.com/d/topic/ golang-nuts/qxlxu5RZAI0/discussion. [30] J. Wilander and M. Kamkar. A Comparison of Publicly Avail- able Tools for Dynamic Buffer Overflow Prevention. In NDSS, 2003. [31] W. Xu, D. C. DuVarney, and R. Sekar. An Efficient and Backwards-Compatible Transformation to Ensure Memory Safety of C Programs. In FSE, pages 117–126, 2004.
Ironclad C++: A Library-Augmented Type-Safe Subset of C++ 14