Skip to main content

Control-Flow Integrity (CFI) for GCC & LLVM

In summer of 2016, just a few months before finishing my bachelor’s thesis, I wrote a short report on an interesting paper concerning Control-Flow Integrity for common C++ compilers as part of a seminar. Since the topic is still of importance, I’m sharing it here.

Abstract

Since the widespread adoption of stack protection mechanisms, attacks on software’s control flow focused more and more on the forward edges. To cope with this issue, the authors present three tools, two of which harden the output binary and the third one is a tool for developers to prevent vulnerabilities. They implemented a C++ vtable verification for GCC, an indirect function call checker for LLVM and an addition to LLVM’s UBSan. Their new mechanisms improved on the performance overhead that prior work and put great effort into creating usable tools for the real world. Providing a realistic attack model they were able to bring a certain level of security to every day software. More recent work developed their ideas further which makes these tools a starting point for current approaches and improvements on control flow integrity.

Get the full PDF.

Report on: Enforcing Forward-Edge Control-Flow Integrity in GCC & LLVM [1]

Introduction

Securing the control-flow integrity (CFI) is a rather new approach to further improving the safety of software, especially of programs like webbrowsers which are complex, widespread and often running with high privileges. No software is free of errors and certain bugs (e. g. use-after-free, buffer-overflows) can be abused in such a way that the program’s control-flow is altered. Attackers use these possibilities as an entry point for further taking over an application. Thus, different approaches to hardening that kind of attacks emerged over time.

There are three types of instructions that alter the control-flow and therefore are in the focus of research. The first are the return addresses, stored on the stack. The other two are virtual and indirect function calls, which both operate on the heap. They are also referred to as forward-edges in the control flow, as they are a forward jump into another function while the return after executing a piece of code is analogously called a backward-edge.

Protection of stack data and return addresses is a widespread method for increasing computer security – in scientific contexts as well as in production environments, because most mainstream compilers offer such tools. As a result, recent attacks exploit heap-based memory corruption bugs and compromise the control-flow integrity (CFI). Protecting the program-control data on the heap however appears to be a complex problem and is a current research subject. The authors recognize that this kind of CFI enforcement has thus not been adopted in mainstream compilers. Most approaches are either ad-hoc mechanisms, binary rewriting frameworks or even only experimental, also incremental compilation and dynamic loading are often impossible. As a result, using this kind of tools in production compilers is not practicable.

The authors present two mechanisms they implemented to protect the CFI while not restricting compiler optimizations, operation modes, or features like Position-Independent Code (PIC) and C++ exceptions. Also they do not restrict the execuction environment of the final binary such as dynamically-loaded libraries or Address Space Layout Randomization (ASLR). The result are fully functional tools for the respective production compilers that are shown to be practical and at the time of the publication very efficient. Also, they describe and resolve the challenges that arise when developing a CFI solution usable in a real-world environment.

In their paper, the authors focus on verifying the targets of forward-edges of indirect control transfers at different levels of precision, which depends on target’s type and applied analysis. In C++, virtual calls make up the largest share of indirect control transfers, so the first approach protects these virtual calls by verifying the vtables. Evaluation of the binary compiled with the CFI tools showed that 95% to 99.8% of all indirect function calls could be protected, depending on the tool. The performance penalty ranges from 1\% to 8.7\%, evaluated with the SPEC CPU2006 benchmark suite and web browser benchmarks, all performed on the recompiled version of the Chromium browser.

At the time, the presented tools compared well to other research prototypes in terms of performance. Although the provided level of security is not as good as from these other prototypes, they still provide strong guarantees for the defined realistic attack model. Concurrent other tools like MIP [2] and CCFIR [3] have a significantly higher overhead or must sacrifice practicability to achieve a better performance, like SafeDispatch [4].

References

  1. Tice, C., Roeder, T., Collingbourne, P., Checkoway, S., Erlingsson, Ú., Lozano, L., & Pike, G. Enforcing Forward-Edge Control-Flow Integrity in GCC & LLVM. In: USENIX Security Symposium. pp. 941-955. USENIX Association (2014)
  2. Niu, B., Tan, G.: Monitor integrity protection with space efficiency and separate compilation. In: Proceedings of the 2013 ACM SIGSAC conference on Computer & communications security. pp. 199-210. ACM (2013)
  3. Zhang, C., Wei, T., Chen, Z., Duan, L., Szekeres, L., McCamant, S., Song, D., Zou, W.: Practical control flow integrity and randomization for binary executables. In: Security and Privacy (SP), 2013 IEEE Symposium on. pp. 559-573. IEEE (2013)
  4. Erlingsson, U., Abadi, M., Vrable, M., Budiu, M., Necula, G.C.: Xfi: Software guards for system address spaces. In: Proceedings of the 7th symposium on Operating systems design and implementation. pp. 75-88. USENIX Association (2006)

IT Security