Start · the one idea

How sysk is proven

Before sysk is built, its compiler proves that no index leaves its buffer, no number overflows, every call meets its callee's contract, and no task breaks the rules the others rely on. If any proof fails, there is no build. This page shows what that means in practice.

sysp in one paragraph

sysp is the language sysk is written in. It looks like Lisp and compiles to C. Every function states a contract: requires (what must hold when it is called), ensures (what holds when it returns) and effects (whether it may allocate or do IO). Buffers are linear: each has exactly one owner, and using one after giving it away is an error. sysp check turns every place the program could go wrong into an obligation, a logical formula, and asks the Z3 solver to prove it from what is known at that point. On the whole system that is obligations, proven in about seconds, with loop invariants sysp found by itself. sysp also keeps proofs: a function whose text, and whose callees' contracts, have not changed is not proven again, so after a small edit a check takes about a second.

Readthe sources, as one program; file-private names stay in their file
Typesevery value has a type; never for what never returns
Effects and ownershipno allocation or IO undeclared; each buffer and handle used once
Symbolic executionwalk each function, collecting what is known and what must hold
Invariantsguess loop invariants and keep the ones that hold (Houdini)
Z3prove each obligation, or show a counterexample
What sysp check does with the kernel and the tasks. sysp build runs the same check and writes C only if every obligation is proven.

An obligation, up close

Pick an example. The goal is what must hold at that point in the code; each part of it is proven from facts sysp knows there. Select a part to see where its facts come from.

When a proof fails

Here is a task that was handed a channel end for 5 lines and sends 6. It is one of the negative tests (tests/negative/send-too-many.sysp): sysp must reject it.

; A task handed a channel end for 5 lines sends a sixth.
(fn chatty ((k (shared Kernel kernel-rely)) (out Tx) (line (ref (buf u8)))) unit
  (effects io)
  (requires
    (and (running k)
      (tx-for k out)
      (tx-left out 5)
      (tx-bytes out line-bytes)
      (== (len line) line-bytes)))
  (var i 0)
  (while (< i 6)
    (send k out line)
    (set i (+ i 1)))
  (close-tx out))
tests/negative/send-too-many.sysp:14:5: error[V001]: precondition `(and (running k) (tx-for k out) (> (. out left) 0) (== (len msg) (. out bytes)))` of `send` may not hold for this call
    obligation: (and (running k) (tx-for k out) (> (. out left) 0) (== (len msg) (. out bytes)))
    counterexample: i = 5, out.left = 5, out.bytes = 64, len(line) = 64, ...
    fix: inside a loop the verifier knows only the invariants about variables the loop changes: add an invariant that implies this obligation, or rewrite the expression so the invariants suffice

sysp names the call and the contract it could not prove. The counterexample is a state it could not rule out. Here the useful invariant, "lines left = 5 − i", is not one it can keep: by the sixth round it would have to be −1. Without it, sysp knows nothing about the count inside the loop, so it cannot show one is left. The fix is in the code, never in the checker. Every rule sysk depends on has a test like this one; the table at the end of the page lists them.

Running while others run: rely and guarantee

Tasks are preempted at any instruction, and all of them share the kernel's state. A task cannot know what happened while it was paused. sysp's answer is a rely: the kernel is a shared parameter, (k (shared Kernel kernel-rely)), and between its atomic blocks a task may assume only what kernel-rely says: the kernel's invariant still holds and the task is still the current one. In return, every atomic block anywhere is proven to keep that promise (its guarantee), and so is the timer handler. Step through a stretch of the producer's run:

What is not proven

Proofs rest on things sysp takes on trust. In sysk those are kept few and named:

  • Extern contracts. A function written in assembly is declared extern with a contract sysp assumes. There are of them; each is listed on The trusted base.
  • The assembly itself, lines, and the C compiler that turns sysp's output into machine code. User mode with PMP catches what these get wrong in code that runs in a task; for the kernel, which runs in machine mode, nothing does.
  • How tasks interleave. sysp proves each function alone. Whether two tasks can wait for each other forever is checked by a different tool, which reads each task's waits (Deadlock freedom).

A proof can be vacuous. If the facts at some point contradict each other, every obligation there is "proven". A bad extern contract once did exactly that in sysk. sysp now reports contradictory facts itself (V012), and the tests also plant (assert false) at points that must be reachable: each must be rejected.

The rules, and the tests that hold sysp to them

CodeMeaningA test in sysk that must trigger it
V001a precondition may not hold at a callsending too often, closing early, a message of the wrong size, a manifest that disagrees with a task; a driver that submits while a request is in flight, or takes a buffer back before the device is done (sysk 023)
V003 · V004an index may be out of bounds · arithmetic may overflowchecked everywhere; the kernel has none left
V008an assertion may not holdthe vacuity probes: (assert false) at reachable points
V012the facts on a path contradict each otheran unsatisfiable ctx-switch contract, re-introduced on purpose
O001 · O002a value used after it was given away · a resource never consumedreading a freed block, copying a handle, walking away from a handle; a driver that writes to, frees or lends twice a buffer it lent the device (sysk 023, Storage)
C001 · C002shared state touched outside an atomic block · an atomic block that may break the relyreading kernel state outside atomic; a kernel function that changes current
N003a name private to another fileforging a handle, a Console, a Root, a Device; calling the scheduler; stopping the timer; a driver that makes an address to hand its device, or reads the ring the device writes as ordinary memory (sysk 023)
E001a missing effecta task that allocates from the boot arena
T001a type mismatchsending with a receive handle
W002an extern is trusted without proof (a warning)listed in full by the kernel's own test