6
Original SFI Approach Wahbe et al. (1994) Rewrite MIPS assembly code so that it respects sandbox policy when executed. mask high bits of all effective addresses so they are forced to be in the proper segment. Mem[A] := r t := mask(A); Mem[t] := r But, code might jump over masking operation. we need the masking and deference to be atomic

7
Berkeley Solution Dedicate two registers: 1 for data (D), 1 for control (C). Invariant: dedicated registers always point into the proper segment. to store r at address A: D := dmask(A); Mem[D] := r to jump to address A: C := cmask(A); goto C If an attacker jumps over masking operations, the code still stays in the sandbox.

8
What about x86? Cant afford to burn 2 registers. need some other way to ensure atomicity of checks and uses. New problem: Variable length instructions. there are multiple parses we must consider.

10
SFI for CISC machines McCamant & Morrisett (2006) force a single parse of the code. All direct jumps must be to the beginning of an atomic instruction sequence in our parse. For computed jumps: dont allow atomic instruction sequences in our parse to cross a k-byte boundary insert no-ops until we are on a k-byte aligned boundary mask the destination address so it is k-byte aligned Overhead: ~20% on 32-bit machines, we can use the segment regs. to cut this to ~5%

12
The Checker A bug in the checker could result in a security breach. earlier implementations of SFI had bugs Google ran a contest and participants found bugs Our goal: Build a certified NaCl checker.

14
How RockSalt works Specify regexps for parsing legal x86 instructions preclude instructions that might change or override the segment registers. preclude doing a computed jump without first masking the effective address of the destination. Compile regexps to a table-based DFA interpret DFA tables & record start positions of instructions & check jump and alignment constraints All of this is proven correct.

15
What we proved… If we give the checker a string of bytes B, and the checker accepts, then if we load B into an appropriate x86 context and begin execution, the code will respect the sandbox policy as it runs. The real challenge is building a model of the x86. And to gain some confidence that it is correct! We have modeled about 300 different instructions including all the addressing modes, and all of the prefixes.

16
Were not the first of course… CompCerts x86 model (Coq) actually an abstract machine with a notion of stack code is not explicitly represented as bits Y86 model (ACL2) tens of instructions, monolothic interpreter but you can extract relatively efficient code for testing! Cambridge x86 work (HOL) inspired much of our design their focus was on modeling concurrency (TSO) semantics encoded with predicates (need symbolic computation)

22
How to give semantics? Usual approach is to use some form of structured operational semantics on the AST. c.f., CESK machine, or TAL-like machine encoded using an inductively defined predicate Worked in the 90s when we were doing languages on paper. But this doesnt scale…

27
Execution Summary Generic RTL functor abstracts machine state simple, core RISC instructions functional interpreter, easy to extract executable code non-determinism modeled with oracle relatively easy to reason about Translation into RTL can essentially follow the definitions in the manual type-classes, notation, and monads crucial but this is where most of the bugs lurk

32
Decoding For RISC architectures, decoding isnt that hard. can write a reasonable parser by hand. For x86, its essentially impossible. thousands of opcodes, many addressing modes, etc. prefix bytes override things like size of constants the number of bytes depends upon earlier bytes seen and can range from 1 to 15. Plus, we need to reason about parsing. need to relate regexps used in checker to models decoder

37
Typed Grammars as Specs The grammar language is very attractive for specification: typed semantic actions easy to build new combinators easy transliteration from the Intel manual Unlike Yacc/Flex/etc., has a good semantics: easy inversion principles good algebraic properties e.g., easy to refactor or optimize grammar

43
Table-Based Recognition The parser I showed you is calculating derivatives on-line. Brzozowski showed how to construct a DFA from a regular expression using derivatives. calculate (deriv c r) for each c in the alphabet. each unique (up to the optimizations) derivative corresponds to a state. continue by calculating all reachable states derivatives. guaranteed this process will terminate!

45
Bad News The derivatives for regular expressions are finite. But as defined, we can have an unbounded number of derivatives for our typed, regular grammars. This seems to preclude a table-based parser where we calculate all of the derivatives up front.

48
Regaining Finite Derivatives The solution is to split grammars into a map-free grammar and a single mapping function. split: grammar T -> {a : ast_gram & (ast_tipe a) -> T} As we calculate derivatives, we continue to split. the states correspond to AST grammars the edges are labeled with the maps the parser computes a composition of maps

61
Thats nice! We can construct a table-driven parser by just calculating derivatives, and then splitting. Calculating the table in this way leads to an easy, algebraic proof of correctness. We can also use the table to determine if the grammar is ambiguous. any terminal state (i.e., that accepts the empty string) shouldnt have alternatives.

62
Still Had Two Major Problems 1. The semantic actions were too expensive. for our table, each state corresponds to the 8 th derivative so each edge has the composition of 8 maps solution: reflect internal transforms as a typed, sequent calculus and perform cut elimination. Now the parser runs like a bat out of hell (100x faster than online derivatives.) 2. It literally took days to build the tables. and this problem is fundamental to Coq…

70
On Computational Irrelevance Coq will automatically erase its types and proofs in the extracted code, but not terms like my tipes. We cant use Coqs types or props because we lose injectivity for the type constructors and/or the ability to compile back to a Coq function. There are type theories (e.g., ICC*) that support a more principled approach to irrelevance.

72
Decoding Summary A nice declarative specification of the grammar. users can use arbitrary functions for semantic actions. can build nice notation/combinators. easy algebraic reasoning. We can extract a provably-correct, table-driven parser that can be used for testing. but we have to use a hack. buried within here is compiler, optimizer, and a lot of proofs (~ 5Kloc)

73
Future Directions for x86 Model Better validation the parsing technology is aimed at building a faster model so we can do more testing/validation Extending the execution model concurrency, system state, other architectures, … Extending the security policy CFI, XFI, TAL, … Beyond regular grammars e.g., VPDAs, CFGs, dependent grammars

75
Summary A big part of formalization is modeling our environments. Mechanization makes it possible to scale beyond the toy models we used to do. this is necessary to widen proofs to real code. But building models at scale is a big challenge. validation & re-use are crucial forces us to re-think how we do semantics even old topics, like parsing, need to be revisited