()
/ |\
/ | \
() | ()
\ |
\ |
[ OS ]
Components interact via interfaces.
Memory safety: keep each component "in a box."
High-level security: interfaces, etc.
- Components can't read/write memory except at approved addresses
- Components can't jump to unapproved addresses (interact with others through approved interfaces)
Common threads
- enforcement code must be more powerful than enforcee
- enforcement code must start running first. Need to build foundation
Examples: most OS's
Hardware: priviledge modes, generally "user" and "system" (can do anything "user" can't)
- "dangerous" instructions usable only in system mode (i.e. direct access to physical memory or devices, or priviledge change)
- if "user" tries to execute, violation traps (jumps) to a fixed address (in priviledged mode)
- system call: jump to priviledged code
Use virtual memory to limit who can access what
- memory divided into fixed-size pages
- page table maps vaddr to real page (phys addr)
- some vaddr are forbidden (no entry in table)
- separate vaddr space for each component
- page table lives in memory that only enforcement code can touch (program can't control it's own page table)
Effect: each component can only touch memory that the enforcement code lets it tough
Moving between priviledge modes
- priv to unpriv: usually w/single instruction (no security problem)
- unpriv to priv: special instruction (system call)
- like normal function call, but goes to a fixed addr or thorugh a fixed jump table AND raises priv level
- code at destination addr must be carefully written (fields request from users to do things). This code is priviledged, written by OS
No HW support needed (more flexible). Died out in 1970, reborn around 1995.
Wisdom: Simple security mechanisms are more reliable. Hardware is simpler. But... in '95 HW got complicated, wasn't as secure as thoguht. Engineered memory access to be fast.
Web-browsers: java virtual machines (3 levels), not 2. Java couldn't use HW protection for its programs.
SW: performance boost
- # clock cycles used to switch priviledged mode code is rising, expensive. Memory access... cache... page in/out.
- SW faster to crowss priv boundary. Flexible
4 approaches: interpreter, software fault isolation (SFI), just-in-time compilation (JIT), proof carrying code.
Hot area, make up for last 25 years.
Code producer ----------------------> Code consumer
Example: javascript/DHTML, Java (originally), SafeTCL. Require program to be written in "safe" language (no dangerous instructions)
- Software interprets program step-by-step (walk through, execute step by step, like a person walks through a program on paper)
- state variables in (say) a hash table
- limit procedures program can call
interpreter checks every step, doesn't do unapproved stuff
- pros: easy to explain, likely to be safe (simple in conception)
- con: slow (10x or 100x), requires use of single language (well, could compile to an intermediate language and interpret that)
idea: read in machine codes, rewrite it to make safe, run rewritten version
Example:
BEFORE AFTER
r5 <- Mem[r6 + 72] | r30 <- (r6 < [Base - 72] )
mem[r6 + 76] <- r5 | r31 <- ( r6 > [limit + 72] )
| r31 <- r30 / r31
if (r5 == 0), jump (PC - 4) |
| if (r31 != 0 ) jump (error)
assume: OK to access (R/W) | r5 <- mem[r6 + 72]
memory in [Base, limit] | r31 <- (r6 > [limit - 76] )
|
| if (r31 != 0) jump (error)
| mem[r6 + 76] <- r5
|
| if (r5 == 0) jump (PC - 14)
How to get r30 and r31? Push to memory if not free, then restore.
Rewriting: add checks before every load/store, some jumps. Optimize away some checks
Complexities
- self-modifying
- indirect jumps
- finding/respecting instruction boundaries (jump into middle of instruction), jump in between instructions
Status: invented 1991, prototype implementations exist, slowdown not bad (2%-50%). Out of order execution: extra stuff done in parallel. Modern CPUs execute in parallel, much of performance hit in program is from jumps. fills in stalls.
Pain in approach: complexity, not slowdown