This crate uses Kani, Amazon Web Services's Rust verification tool based on bounded model checking (CBMC), to formally verify core invariants of the runtime library.
The Kani proof harnesses exhaustively verify that:
- No panics: All load/store operations return
OkorErr(WasmTrap), never panic - Bounds checking: Successful loads/stores are always within
[0, active_size) - Overflow handling: Offset overflow is detected and returns
OutOfBounds - Grow semantics:
active_pagesnever exceedsMAX_PAGES- Failed grow returns -1 and leaves state unchanged
- Successful grow zero-initializes new pages
- Store/load roundtrips: Storing then loading returns the original value
- Active region: Accesses beyond
active_pages(but withinMAX_PAGES) are rejected
- No panics: All get/set/grow operations return
OkorErr(WasmTrap), never panic - Bounds checking: Operations only succeed when
index < active_size - Grow semantics:
active_sizenever exceedsMAX_SIZE- Failed grow returns -1 and leaves state unchanged
- Successful grow initializes new slots with the init value
- Set/get roundtrips: Setting then getting returns the same entry
- Empty slots: Getting an empty slot returns
UndefinedElement
Install Kani following the official instructions:
cargo install --locked kani-verifier
cargo kani setupVerify all proofs in this crate:
cargo kani -p herkos-runtimeVerify a specific proof harness:
cargo kani -p herkos-runtime --harness load_i32_never_panicsRun with verbose output:
cargo kani -p herkos-runtime --verboseTo see verification coverage and proof counts:
cargo kani -p herkos-runtime --verbose | grep "VERIFICATION"The Kani proofs should be run in CI on every commit. Add to .github/workflows/ci.yml:
- name: Install Kani
run: |
cargo install --locked kani-verifier
cargo kani setup
- name: Run Kani proofs
run: cargo kani -p herkos-runtimeKani will report:
- VERIFICATION SUCCESSFUL: All assertions passed for all possible inputs
- VERIFICATION FAILED: A counterexample was found
- UNREACHABLE: Some code paths are proven unreachable (good for panic paths)
If a proof fails, Kani provides:
- The failing assertion
- A concrete counterexample (input values that trigger the failure)
- A trace showing the execution path to the failure
- Verification time scales with the size of
MAX_PAGES/MAX_SIZEconst generics - The proofs use small values (1-4 pages, 4-8 table slots) to keep verification tractable
- This is sound: if the code is correct for small values, const generics ensure it's correct for all values
When adding new runtime functionality:
- Add a
#[kani::proof]harness in the#[cfg(kani)] mod proofssection - Use
kani::any()to generate symbolic inputs covering all possible values - Use
kani::assert()to state invariants that must hold - Add
#[kani::unwind(N)]if the proof involves loops (set N to loop bound + 1)
Example:
#[cfg(kani)]
mod proofs {
use super::*;
#[kani::proof]
#[kani::unwind(1)]
fn my_operation_never_panics() {
let mut mem = IsolatedMemory::<4>::new(1);
let input: i32 = kani::any();
let _ = mem.my_operation(input);
// Kani verifies no panic occurs for all possible i32 values
}
}- Kani Tutorial
- Kani Rust Book
- FUTURE.md §4 — Contract-based verification design
- SPECIFICATION.md §3.2 — Runtime architecture