CertiKOS:An extensible Architecture for Building Certified Concurrent OS Kernels
Gu, Shao, Chen, Wu, Kim, Sjoberg, Costanzo (2016)
Potentially useful notes
What kind of paper is this?
- Incremental advance (but an important one): They had already verified
their system once, but now they verified a concurrent version of it. The
concurrent thing is a big deal, so this is a bit tough to categorize.
The Story
- Once upon a time, realizing the importance of operating system code,
people began trying to verify operating systems.
Verification is challenging and still limited in its ability to verify
concurrent code.
The authors decided that strict layering and compositional verification might
be an approach to verifying concurrent (i.e., more realistic) operating
systems.
So they developed the CertiKOS architecture and the mC2 instance of that
architecture,
which was implemented in Coq, thereby producing the first certified concurrent
kernel.
Having a certified kernel allowed everyone to live happily ever after.
Big Idea
- Verify a concurrent kernel!
- Use Coq
- Use compositional approach (something of which Margo is a big fan).
Requires "meticulously" specifying layers and interdependencies.
- CertiKOS is an architecture (not a specific concurrent OS kernel); mC2 is
the specific kernel built in Coq.
- I really like this quote, "The ultimate goal of research on
building certified OS kernels is not just to verify the functional
correctness of a particular kernel, but rather to find the best OS
design and development methodologies that can be used to build
provably reliable, secure, and efficient computer systems in a
cost-effective way."
Their proposed eval metrics
- Ability to experiment with new divisions between supervisor mode and
non-supervisor mode.
- No significant performance overhead.
- Be able to verify global properties of things above the kernel.
- Efficiency of verification (leverage composition and modularity).
- Machine checkable proofs.
Definitions
- A certified abstraction layer consists of a language construct
(L1,M,L2) and a mechanized proof object showing that the layer
implementation M, built on top of the interface L1 (the underlay),
is a contextual refinement of the desirable interface L2 above (the
overlay).
- A deep specification (L2) of a module (M) captures everything
contextually observable about running the module over its underlay
(L1).
Context
- Designed for unmanned vehicle for DARPA
- OS kernel doubles as hypervisor
- Runs 3 Ubuntu systems (one on each core), each of which run several
Robot Architecture Definition Language nodes.
- What guarantee do we get: 1) all system calls and traps will strictly
follow the high level specification, 2) will run safely and terminate,
3) without data races, code injection attacks, buffer overflows, null
pointer access, overflow, etc.
What's missing?
- Proofs for TSO machines (assumes strong sequential consistency for
all atomic instructions)
- Certified storage system
- Assembler is uncertified.
- Assumes Coq proof checker is correct.
- Boot loader and ELF loader are unverified.
Concurrent/Layered Model
- Atomic object is an abstraction of well-synchronized shared memory plus
the operations that can be performed on it. A log records all operations on
the object (in order).
- Concurrent layer interface: includes both private and atomic objects and
invariants.
- Abstraction 1: Include CPU ID before each assembly instruction.
For proof, the lower layer must contextually refine the higher layer.
- Abstraction 2: Add pulls and pushes that copy shared state to local
and local to shared state respectively.
- Abstraction 3: Combine per-CPU logs. If the active sets of two logs
are disjoint, just union them. If not disjoint, use the overlapping switch
points to produce the correct execution.
Proof Approaches
- The beauty of the compositional approach is that you can verify small
blocks independently. They walk us through several examples.
- Key approach: the highest semantic layer is the trap handler (i.e.,
interface to applications) -- this is the whole kernel specification.
If you can prove global properties there, you can map them all the down
to the lowest semantic level of the hardware (via the layers sketched above).
- That lowest layer is the pre-initialization that connects to the CPU-local
model (that they derived throughout section 3 -- which was pretty hairy).
- Spinlock (both ticket locks and MCS locks)
- Create an atomic ticket object (ticket and timesamp)
- Do an atomic fetch and inc to obtain a ticket time; then spin waiting for
the lock to reach that time.
- Need to prove mutual exclusion: the fetch-an-add guarantees uniqueness of an
acquirer's ticket; the condition check guarantees that only the ticket holder gets
the lock.
- Need to prove eventual success: can bound the number of events possible thus
guaranteeding eventual success.
- This intuitive proof then gets coded up into Coq.
- Physical memory management
- Associate the page allocation table (AT) with a lock --
becomes an atomic object
- Prove that the interface is atomic (and disallow acquisitions outside object)
- Assign each thread a memory quota via its container. Disallow allocations
beyond the thread's quota.
- Virtual memory management
- Prove initialization correct: paging enabled only after page map initialized
- Prove that primitive manipulations are correct: pages with kernel data accessible
only with kernel permission; kernel page map is identity; unshared pages
are isolated.
- Shared memory management
- Logical owner can be a set
- Page can only be freed when logical owner is a singleton
- Shared Q
- Abstract queue states using Coq lists
- Enqueue and dequeue specified over abstract lists
- Associate a lock with each queue.
- Proof by log replay
- Thread state
- Like push button verification, prove that the stack is of limited size
- Scheduling based on shared queues
Evaluation
- How big are the proofs?
- How hard was it?
- How much can the kernel do?
- Started with a sequential version
- 6500 lines of C and assembly (mC2 kernel)
- Took about 2 person years for verification of the new concurrency framework
- 943 lines of code specify the hardware axiomization
- 450 lines of code for system call API specification (how many syscalls?)
- 5249 lines of specification outside the TCB
- Another 40K lines of code
- 50K lines of Coq (just for concurrency)!
- Th comparison to seL4 seems not terribly useful (I guess it says everyone
is in the right ball park?)