home | articles | links | fun | about
Up to: CS432 Information Security

Memory Safety - 10/22/02

            ()
           / |\
          /  | \
        ()   |  ()
          \  |  
           \ |
           [  OS  ]

Components interact via interfaces.

Memory safety: keep each component "in a box."

High-level security: interfaces, etc.

Goals

  1. Components can't read/write memory except at approved addresses
  2. Components can't jump to unapproved addresses (interact with others through approved interfaces)

Common threads

Hardware-based memory safety

Examples: most OS's

Hardware: priviledge modes, generally "user" and "system" (can do anything "user" can't)

Use virtual memory to limit who can access what

Effect: each component can only touch memory that the enforcement code lets it tough

Moving between priviledge modes

Software-based memory safety

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

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

Approach 1: interpreter

Example: javascript/DHTML, Java (originally), SafeTCL. Require program to be written in "safe" language (no dangerous instructions)

interpreter checks every step, doesn't do unapproved stuff

Approach 2: Software Fault Isolation (SFI)

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

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