Appears in the proceedings of the The Second International Workshop on Security Testing (SECTEST) 2011, affiliated with the International Conference on Software Testing, Verification and Validation (ICST). Practical Considerations in Control-Flow Integrity Monitoring Iavor Diatchki Galois, Inc. Email: diatchki@galois.com Lee Pike Galois, Inc. Email: leepike@galois.com Levent Erk¨ ok Galois, Inc. Email: levent.erkok@galois.com Abstract—Control-flow integrity (CFI) checks ensure that pro- grams respect their static call-graphs at runtime. A program might violate its call-graph due to malicious attacks such as shell- code injection or return-to-libc style exploits. CFI checking can also be beneficial during testing to discover properties of control- flow, as well as at deployment to detect malicious behavior. We present practical aspects of CFI checking, including advantages and disadvantages of the following: how to represent call-graphs, how to instrument CFI checks, and how to refine CFI checks to properties of control-flow. We discuss two implementations: one instrumenting the source code and the other instrumenting the compiler generated assembly, and we describe their performance. Our paper is meant to be a practical guide to CFI monitoring. I. I NTRODUCTION Most security attacks on software aim to modify their control-flow maliciously. If the control-flow of a program can be statically determined, then the program’s actual control- flow can be monitored at run-time to ensure conformance. This idea is known as control-flow integrity (CFI) [1], [2]. CFI approaches detect conventional control-flow modifications like the well-known jumping to shellcode [3] and return-to- libc [4] type of attacks, which cause the program’s control-flow to violate any possible call-graph of the program. However, carrying out a control-flow attack can be more subtle, such as modifying data values as well; CFI checks can also be used to test for these subtler attacks. Moreover, CFI checks can be used during testing to develop and check properties about the control-flow of a program: e.g., “Do the error handling routines always call the logging function?” From a security standpoint, CFI is a draconian solution, as compared with lower-overhead approaches such as the use of canaries [5], [6], [7], address-space layout randomiza- tion [8], or custom array out-of-bound checks [9]. However, recent work shows these latter approaches to be ineffective at stopping all return-to-libc attacks. In a 2010 paper by Checkoway et al., they demonstrate how to execute return-to- libc attacks that execute arbitrary programs without modifying return addresses [10]. The authors therefore write: What we show in this paper is that these defenses would not be worthwhile even if implemented in hardware. Resources would instead be better spent deploying a comprehensive solution, such as CFI. While a small number of researchers have explored specific CFI-like approaches [1], [2], there has been no discussion of some of the practical trade-offs between design choices of CFI implementations. We discuss design choices regarding the location of CFI checks, at what compiler phase to add the checks, the construction of static call-graphs, and how to check for conformance. Our hope is that this paper provides useful guidance for other CFI implementations for testing and security monitoring. The remainder of the paper is organized as follows. In Section II, we overview related work. In Section III, we describe design choices regarding various aspects of CFI, including computing the call-graphs, monitoring the control stack, and developing and testing control-flow properties. In Section IV, we discuss two implementations building on our ideas; one implementation instruments source code, and the other instruments assembly code generated by the compiler. We also provide initial performance benchmarks. Finally, we conclude in Section V, with a brief description of future work. II. BACKGROUND AND RELATED WORK A large body of literature exists describing approaches for detecting, preventing, and circumventing attacks to modify the control-flow of C programs. In this section, we briefly review the work most relevant to ours. An extensive recent bibliography for CFI checking can be found in Abadi et al. [1]. We call out specific similarities and differences with prior work in the remainder of the paper. Oh et al. [2] describe a CFI monitoring approach based on signature-based checks at the beginning of basic machine- code blocks. The motivation for this work is the presence of transient or permanent faults rather than security. Abadi et al. present an approach to CFI, where jumps in the program are instrumented with checks to ensure the targets are valid before jumping [11], [1]. Their call-graphs are built using static binary analysis, and instrumentation is done by machine-code rewriting, making it language-independent. Our implementa- tions (see Section IV) shows that reasonable efficiency can be achieved with an architecture-independent (but language- dependent) approach performed during compilation. Together, the work of Abadi et al. and our work explore much of the design space for CFI checking. Petroni and Hicks present an approach for monitoring control-flow attacks to detect Linux kernel rootkits [12]. Their work addresses environments in which some of the assump- tions made by Abadi et al. do not hold [11]. The monitoring 1