CIS 6614 meeting -*- Outline -*- * XFI ** Reference ------------------------------------------ PAPER ON XFI Ulfar Erlingsson, Martin Abadi, Michael Vrable, Mihai Budiu, and George C. Necula. XFI: Software guards for system address spaces. In OSDI '06, pp. 75-88, Usenix, Nov, 2006. https://www.usenix.org/legacy/event/ osdi06/tech/full_papers/ erlingsson/erlingsson.pdf ------------------------------------------ Q: Are some of these authors the same as in the CFI paper? Yes, Erlingsson, Abadi, and Budiu ** problem to solve ------------------------------------------ PROTECTING DYNAMICALLY-LOADED CODE Problems: - Dyanmically load drivers, modules - Communicate with them quickly from the kernel - Ensure they don't corrupt the kernel ------------------------------------------ Q: Why would an OS need to load dynamic modules like drivers? In general to support a complex, evolving ecosystem of peripherals To support new devices, file formats, etc. Some drivers, like codecs, are specific to particular devices and file formats used by them Q: Why does a driver need to be in the kernel's address space to be efficient? To avoid context-switching overhead, especially if a lot of back-and-forth communication Q: How could a driver corrupt an OS kernel? Buffer overflows, format string problems, etc... *** Related Work ------------------------------------------ WHY ISN'T CFI GOOD ENOUGH? Builds on CFI work, but also want to: - Protect more properties - Have a small trusted code base ------------------------------------------ Q: Why would this build on the work on CFI? To make sure that checks aren't bypassed in code (This work builds on the ideas in the SMAC part of CFI.) ------------------------------------------ PROOF-CARRYING CODE (PCC) George C. Necula. Proof-carrying code. In POPL '97. ACM, NY, pp. 106--119. https://doi.org/10.1145/263699.263712 Problem: - Safely execute untrusted code Approach: - State safety property code must follow - Code comes with a proof - Verifier checks that proof Advantages: - Checking the proof is (much) easier/faster than constructing it - Only need to trust the proof checker ------------------------------------------ Q: Does the proof need to be trusted or created by a trusted tool? No, just reject it if it's wrong! Note that Necula is one of the authors of the XFI paper *** Trusted Computing Base ------------------------------------------ TRUSTED COMPUTING BASE Def: The *trusted computing base* of a system is Approach to minimizing size of the trusted computing base: ------------------------------------------ ... the software that security guarantees rely on and thus needs to be correct and secure itself Q: Do we want the trusted computing base to be larger or smaller? Smaller, so can check it better Larger code bases give more opportunity for (security) bugs ... use a (small) verifier/checker (Often checking a property is easier than constructing it and it's ultimately what we want) ** approach/solution *** claims/guarantees ------------------------------------------ CLAIMS MADE BY XFI - "XFI offers a flexible, generalized form of software-based fault isolation (SFI) by building on control-flow integrity (CFI)" - p. 75 - [XFI] "helps protect the integrity of critical state, such as the x86 control registers" - p. 75 - "XFI does not restrict memory layout and is compatible with system aspects..." - p. 75 - "XFI applies at any privilege level, even to legacy code that is run natively in the most privileged ring of x86 systems." - p. 75 - "software that hosts XFI modules need trust only the verifier, not the means of module creation." - p. 75 ------------------------------------------ Q: What part of this is like PCC? The verifier Q: Could XFI be made to be more like PCC? Yes, include some "hints" to checking the safety properties, which is actually done in the paper, speeds up checking Q: What is a "module"? A software program, like a driver or codec Section 4.1 says: "XFI modules are dynamically loadable executables in an appropriate object format." like EXE or DLL files on Windows ------------------------------------------ CLAIMS ABOUT PRACTICALITY - "XFI modules are normal executable binaries (e.g.,DLLs), and can be loaded and used as such." - p. 76 - "We have used our implementation for creating independently verifiable dynamic libraries, device drivers, and multimedia codecs." - p. 76 - "overhead is modest: in our experiments it ranged from 5% to a factor of two, on current x86 hardware." p. 76 ------------------------------------------ Q: Why are these claims important? - want to be able to actually use the approach without making lots of other major changes - want to protect dynamically loaded modules (DLLs) - want to not slow down systems too much... **** main properties (external properties) These properties are "external" to a module in the sense that they are about the interface between a module and the host system (OS) ------------------------------------------ EXTERNAL PROPERTIES OF XFI Def: An *external property* is a property about "P1: Memory-access constraints: Memory accesses are either into: (a) the memory of the XFI module, ..., or (b) into contiguous memory regions to which the host system has explicitly granted access." - p. 76 "P2 Interface restrictions: Control can never flow outside the module's code, except via calls to a set of prescribed support routines, and via returns to external call-sites." - p. 76 ------------------------------------------ ... a module's interface with the host system (OS) Q: What does P1 prevent? access to memory of other processes, threads, or modules (This is similar to SMAC) Q: What would be prescribed in P2? The program's CFG Q: What does P2 prevent? ROP and return-to-libc attacks Q: Does P2 prevent changing order in which control flows? No! ------------------------------------------ SCOPED vs. ALLOCATION STACK Scoped stack: Stack for local variables of functions Never accessed by computed memory access Allocation stack: Stack for locals that could be accessed via pointers ------------------------------------------ Q: Which stack would hold arrays or buffers in C? The allocation stack Q: Why separate these 2 kinds of stacks? So that buffer overflows can't overwrite some parts of the stack (e.g., return addresses) ------------------------------------------ PROPERTY OF THE SCOPED STACK "P3 Scoped-stack integrity: The scoped stack is always well formed. In particular: (a) the stack register points to at least a fixed amount of writable stack memory; (b) the stack accurately reflects function calls, returns, and exceptions; (c) the Windows stack exception frames are well formed, and linked to each other." What is prohibited? ------------------------------------------ ... overwriting the return address ... changing SEH pointers and links Q: How does having a separate scoped stack help with enforcing P3? Easier to check direct accesses in code than computed ones Assuming code can't be written to (W xor X) allows one check at verification time **** additional properties (used in implementation) ------------------------------------------ ADDITIONAL PROPERTIES USED "P4 Simplified instruction semantics: Certain machine-code instructions can never be executed. These include: - dangerous, privileged instructions (e.g., modifying x86 task descriptors), as well as ... deprecated instructions (e.g., far jumps between segments)." "P5 System-environment integrity: ... aspects of the system environment, such as the machine model, are subject to invariants. For instance, the x86 segment registers cannot be modified, nor can the x86 flags register -- except for condition flags..." ------------------------------------------ Q: Is P5 stronger than P3? Yes, P3 is a special case, as P3 talks about invariants of the scoped stack. Q: On an x86 why is a simplified set of instructions (P4) useful? Because there are a great many instructions, and some of them: (a) aren't used in modern software and (b) can have dangerous effects, such as accessing other segments Q: What kinds of changes to the machine environment should be prohibited? - Changing privilege level (rings), - Direction of repeated instructions and others (like double faults) **** internal properties of a module ------------------------------------------ INTERNAL PROPERTIES WITHIN A MODULE "P6 Control-flow integrity: Execution must follow a static, expected control-flow graph.... In particular, function calls must target the start of functions, and those functions must return to their callers." - p. 77 "P7 Program-data integrity: Certain module-global and function-local variables can be accessed only via static references from the proper instructions in the XFI module. These variables are not subject to computed memory access." - p. 77 ------------------------------------------ Q: Is P6 the same as in the CFI paper? No, it's stronger, since it uses a protected stack (the scoped stack). Q: Does P6 help enforce other properties? Yes, because code can't jump around guards Q: Does P6 prevent jumps to the middle of instructions? Yes, and that would be dangerous, as some of those could be prohibited instructions! Q: How does P7 help enforce other properties? It allows auxiliary data to be protected (as in SMAC) e.g., authentication data Q: Do properties P1-P7 prevent code injection? Yes, code can't run in the data segment, not in the CFG! Q: Do properties P1-P7 prevent return-oriented programming? Yes, returns must be to the right place! *** implementation ------------------------------------------ VERIFIER Checks that: "each XFI module has the appropriate structure and the necessary guards." - p. 78 - "establishes constraints on control flow and memory accesses" guards must be in code before: - computed jumps/calls - computed memory accesses Their implementation: - is "3000 lines of straightforward, commented C++ code..." - works in one linear pass over code ------------------------------------------ Q: Is the verifier static or dynamic? It's static, but some of the checks are that appropriate dynamic checks are in the code. This is an interesting way to blend static and dynamic checks Q: What kinds of jumps are computed? switch statements, virtual function calls Q: What kinds of memory accesses are computed? array indexing, pointer arithmetic Q: What kinds of jumps don't need guards? direct jumps to static addresses (e.g., if-then-else and while) Q: What kinds of memory access don't need guards? direct use of simple variables (e.g., global int variables) The paper (p.81) says that most of the code in the verifier is "tables for x86 opcode decoding" ------------------------------------------ GUARDS FOR COMPUTED JUMPS How would computed jumps be checked? Are guards needed for function returns? ------------------------------------------ ... as in CFI assign a unique label to target check that target is allowed by CFG ... no, the scoped stack is protected, so use the addresses ------------------------------------------ COMPUTED MEMORY ACCESS GUARDS What can be accessed by a pointer? What kinds of bounds checking is done? ------------------------------------------ ... anything in the module itself (fast) or memory permitted by the host (slower) ... checks that accesses are to permitted memory, but not that array or pointer indexing is legal Q: Is XFI more permissive than baggy bounds checking? Yes, anything in a module's data can be overwritten! ** practical usage *** usage in practice ------------------------------------------ ARE CFI AND XFI USED IN PRACTICE? Yes! - XFI is used in Windows (since 8.1) - CFI is a feature of clang llvm supports a limited form of CFI ------------------------------------------ *** comparison ------------------------------------------ A COMPARISON Source: Mathias Payer, Volodymyr Kuznetsov. On differences between the CFI, CPS, and CPI properties. Oct. 7, 2014, accessed Oct. 15, 2022. In https://nebelwelt.net/blog/2014/ 1007-CFICPSCPIdiffs.html CFI - limitations: The CFG determined by static analysis, and is imprecise - attack: call-oriented programming (Oakland'14) call gadgets: call a function and end with an indirect call ------------------------------------------ Q: Can the CFG be made completely precise? No, it's not computable (by Rice's theorem) ------------------------------------------ CPS AND CPI CPS = Code-Pointer Separation - code cannot create new pointers, can only reuse those from previous run - uses a safe stack for returns CPI = Code-Pointer Integrity - integrity of: - all code pointers - other pointers marked as sensitive (e.g., containing code pointers) ------------------------------------------ Q: With CPI can memory corruption cause pointer changes? No, but can still change program data that could redirect flow... ------------------------------------------ COMPARING MECHANISMS FOR STOPPING ATTACKS: - Overwriting return pointers CFI - with shadow stack about 5% overhead CPS, CPI - safe stack about 0% overhead - Overwriting code pointer in memory CFI - must be to legal code pointer CPS, CPI - prevent this - Overwrite data that stores code pointer CFI - must be to legal code pointer CPS - must be to previously used ptr CPI - prevents this - Change data used in decisions (if stmts) CFI - no guarantees CPS - no guarantees CPI - no guarantees ------------------------------------------ ------------------------------------------ PERFORMANCE CFI with return address protection: 21% overhead WIT implementation of CFI: 10% overhead Median overhead for Safe Stack: 0.0% Median overhead for CPS: 0.4% Median overhead for CPI: 0.4% ------------------------------------------