I. Reference monitors A. problem to solve ------------------------------------------ PROBLEM TO SOLVE Prevent hijacking of a process by memory attacks Solution Approaches (so far): (a) eliminate code causing overflows: - rewrite program (not in C) - expensive - static analysis - imprecise for C (b) make running attacker's code harder - dynamic taint checking - too expensive - ASLR - not effective (fork) - stack canaries - defeated if use fork - W xor X - defeated by ROP (c) eliminate overflows (dynamically): - bounds checking - too expensive ------------------------------------------ Why are the approaches in (a) not practical? Why are the approaches in (b) not practical? Why is bounds checking for (c) not practical? So, what else could be done? B. another approach ------------------------------------------ ANOTHER APPROACH Goal: eliminate overflows at runtime. Constraints: - no recompilation - ensure attacks must fail - be simple to explain - be simple to enforce Approach: - Monitor program's execution: - prevent attacker from ------------------------------------------ Why should a technique be simple to explain and enforce? 1. enforcible security policies ------------------------------------------ WHAT POLICIES CAN BE ENFORCED? Def: a *safety policy* is Once a safety policy is violated, What is not a safety policy? - policies that concern future events e.g., - policies that concern all possible executions e.g., ------------------------------------------ ------------------------------------------ EXAMPLE SAFETY POLICIES Access control: - A process only accesses files in ways permitted by ACLs Type safety: - A program only calls functions with the declared number of arguments of the declared types Memory safety: - a process never writes outside the bounds of an array - a process never writes into another process's memory Control flow safety: - A program only makes jumps that were in its original code (CFG) - A program can only call library functions that are in its original code ------------------------------------------ What is an ACL? Does an Operating System enforce safety policies? 2. implementation architectures ------------------------------------------ IMPLEMENTATION ARCHITECTURES Kernalized Reference Monitor (RM) [ Application ] | ^ v | ==================== [ RM ] Kernel Inline Reference Monitor (IRM): Compiler produces: [ Application + checks (RM) ] | ^ v | ==================== Kernel ------------------------------------------ What are the pros and cons of the kernalized approach? Could the kernalized approach be generalized to apply to more APIs? Have we seen anything like an IRM before? What are the pros and cons of the IRM approach? C. related work: isolation of processes 1. policy ------------------------------------------ ISOLATION OF PROCESSES Example of safety policy and enforcement Policy: - Processes cannot write into memory of other processes Enforces access control and sharing of: - CPU - storage (disks) ------------------------------------------ 2. costs ------------------------------------------ COSTS OF ISOLATION OF PROCESSES Communication between processes: - mediated by kernel - Context switch to kernel is expensive Costs of kernel call: ------------------------------------------ Why is communication between processes mediated by the kernel? Is there a tradeoff shown here? 3. software fault isolation ------------------------------------------ SOFTWARE FAULT ISOLATION (SFI) See: Robert Wahbe, Steven Lucco, Thomas E. Anderson, and Susan L. Graham. Efficient software-based fault isolation. In SOSP '93. ACM, NY, Dec. 1993, pages 203--216. https://doi.org/10.1145/168619.168635 Fault domain: - memory controlled by a process - code and data in one memory segment Policy enforced: - process can only write to its own fault domain - process can only jump to its own fault domain Approach: - processes live in same address space - software reference monitor (injected code) - masks statically known addresses to be inside fault domain - jump targets must be in dedicated register - register only changed by inserted code to a stored value ------------------------------------------ Does SFI protect against buffer overflows? D. inline reference monitor ------------------------------------------ INLINE REFERENCE MONITOR Generalizes SFI to other safety policy enforcement Policy checks integrated into binary Rely on SFI to prevent circumvention of policy checks ------------------------------------------ 1. implementation ------------------------------------------ SASI: ENFORCEMENT OF SAFETY POLICIES Project from Cornell See Ulfar Erlingsson and Fred B. Schneider. SASI Enforcement of Security Policies: A Retrospective. In Proceedings New Security Paradigms Workshop 1999 https://ecommons.cornell.edu/bitstream/ handle/1813/7412/99-1758.pdf Efficiency by eliminating unneeded checks using partial evaluation Partial evalution: execute as much of program as possible at compile time example: define bool p(int i) {return i > 4;} so f(2+3) becomes becomes becomes Problem: semantic gap between Approach: ------------------------------------------ E. control-flow integrity ------------------------------------------ CONTROL-FLOW INTEGRITY See: Martin Abadi, Mihai Budiu, Ulfar Erlingsson, and Jay Ligatti. Control-flow integrity. In CCS '05, ACM, NY, pp. 340--353, 2005. https://doi.org/10.1145/1102120.1102165 Goal: prevent attacker from hijacking control of ip Attack model: attacker can make arbitrary changes to the program's memory Approach: - statically determine program's CFG - only allow control to follow the CFG - binary instrumentation of code ------------------------------------------ Is that attack model realistic? 1. Control-flow Graph Example ------------------------------------------ CONTROL-FLOW GRAPH EXAMPLE (Figure 1 in Abadi et al., 2005) bool lt(int x, int y) { return x <= y; } bool gt(int x, int y) { return x >= y; } void sort2(int a[], int b[], int len) { sort(a, len, lt); sort(b, len, gt); } lt() sort2() sort() label17 . . . . . . call sort call 17,R ret 23 gt() label55 label23 label17 . . . . . . call sort ret 55 ret 23 label55 . . ret ... ------------------------------------------ Can the CFG be computed statically in a precise way? 2. enforcement ------------------------------------------ CFI ENFORCEMENT (a) For each control transfer, statically determine its possible destination(s) (b) insert unique ID at each destination - use same ID for destinations that are targets of same source (e.g., labels 55, 23, and 17) (c) before transfer of control check that bit pattern of target matches that found in the CFG ------------------------------------------ Why is using the same ID for destinations that can receive control from the same source imprecise? ------------------------------------------ BINARY REWRITING rewrite jmp ecx ; computed jump to mov eax, 12345677h ; load ID-1 inc eax ; ID now in eax cmp [ecx+4], eax ; cmp ID with dest's jne error_label ; fail if not same jmp ecx rewrite mov eax, [esp+4] ; destination to prefetchnta ; label [12345678h] ; ID mov eax, [esp+4] ; destination ------------------------------------------ 3. assumptions (section 3.3) ------------------------------------------ ASSUMPTIONS NEEDED FOR SECURE CFI Unique IDs: After CFI instrumentation bit patterns used as IDs only present in label IDs and ID checks Non-writeable code: program cannot modify code at runtime Non-executable data: program cannot execute instructions from data area ------------------------------------------ Is the unique IDs assumption reasonable? How would the non-writeable code and non-executable data assumptions be checked? 4. fixing imprecision a. imprecision of equivalent targets ------------------------------------------ IMPRECISION FROM REUSE OF IDs Suppose in the code: A calls C and B calls C and D Would CFI allow A to call D? ------------------------------------------ How could the CFG be made more precise? b. imprecision of not matching calls and returns in time ------------------------------------------ IMPRECISION FROM CALLS AND RETURNS Suppose in the code: A calls F then B calls F Would CFI allow F to always return to A? ------------------------------------------ How could this be made more precise? ------------------------------------------ SHADOW CALL STACK Track calls on shadow call stack to check that return is to right place Does the shadow call stack need to protected from attacker? ------------------------------------------ 5. evaluation ------------------------------------------ EVALUATION CFI prevents: + smashing the stack (buffer overflow attacks) + return-to-libc exploits and ROP + changes to function pointers CFI does not protect against: attacks that do not violate the CFG: - changing arguments to system calls (e.g., changing file names) - other attacks that only change data ------------------------------------------ II. XFI A. 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 ------------------------------------------ Are some of these authors the same as in the CFI paper? B. 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 ------------------------------------------ Why would an OS need to load dynamic modules like drivers? Why does a driver need to be in the kernel's address space to be efficient? How could a driver corrupt an OS kernel? 1. 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 ------------------------------------------ Why would this build on the work on 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 ------------------------------------------ Does the proof need to be trusted or created by a trusted tool? 2. Trusted Computing Base ------------------------------------------ TRUSTED COMPUTING BASE Def: The *trusted computing base* of a system is Approach to minimizing size of the trusted computing base: ------------------------------------------ Do we want the trusted computing base to be larger or smaller? C. approach/solution 1. 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 ------------------------------------------ What part of this is like PCC? Could XFI be made to be more like PCC? What is a "module"? ------------------------------------------ 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 ------------------------------------------ Why are these claims important? a. main properties (external properties) ------------------------------------------ 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 ------------------------------------------ What does P1 prevent? What would be prescribed in P2? What does P2 prevent? Does P2 prevent changing order in which control flows? ------------------------------------------ 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 ------------------------------------------ Which stack would hold arrays or buffers in C? Why separate these 2 kinds of stacks? ------------------------------------------ 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? ------------------------------------------ How does having a separate scoped stack help with enforcing P3? b. 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..." ------------------------------------------ Is P5 stronger than P3? On an x86 why is a simplified set of instructions (P4) useful? What kinds of changes to the machine environment should be prohibited? c. 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 ------------------------------------------ Is P6 the same as in the CFI paper? Does P6 help enforce other properties? Does P6 prevent jumps to the middle of instructions? How does P7 help enforce other properties? Do properties P1-P7 prevent code injection? Do properties P1-P7 prevent return-oriented programming? 2. 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 ------------------------------------------ Is the verifier static or dynamic? What kinds of jumps are computed? What kinds of memory accesses are computed? What kinds of jumps don't need guards? What kinds of memory access don't need guards? ------------------------------------------ GUARDS FOR COMPUTED JUMPS How would computed jumps be checked? Are guards needed for function returns? ------------------------------------------ ------------------------------------------ COMPUTED MEMORY ACCESS GUARDS What can be accessed by a pointer? What kinds of bounds checking is done? ------------------------------------------ Is XFI more permissive than baggy bounds checking? D. practical usage 1. 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 ------------------------------------------ 2. 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 ------------------------------------------ Can the CFG be made completely precise? ------------------------------------------ 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) ------------------------------------------ With CPI can memory corruption cause pointer changes? ------------------------------------------ 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% ------------------------------------------