Kani: A Model Checker for Rust
Abstract: Rust's ownership type system prevents memory errors in safe code, but certain desirable properties remain orthogonal to compilation: the soundness of unsafe operations (e.g., raw pointer dereferences), functional correctness, and absence of runtime panics. We present Kani, an open-source model checker for Rust that pushes bounded model checking beyond bug-finding to provide correctness guarantees for these properties. Kani compiles proof harnesses from Rust's Mid-level Intermediate Representation (MIR) into CBMC's bit-precise verification engine, automatically checking a comprehensive set of safety properties with no user annotation. To extend verification from bounded to unbounded, Kani provides a specification language comprising function contracts, loop contracts, quantifiers, and function stubbing. We demonstrate feasibility through case studies on industrial Rust projects, where contracts upgraded verification from panic-freedom to functional correctness, uncovering six previously unknown bugs. Kani operates at scale in production CI, with over 16,000 harnesses verified per code change in the Rust standard library verification campaign.
Paper Prompts
Sign up for free to create and run prompts on this paper using GPT-5.
Top Community Prompts
Explain it Like I'm 14
What this paper is about
This paper introduces Kani, a tool that helps prove Rust programs are correct. Rust already prevents many memory mistakes, but it can’t guarantee everything. Kani steps in to check deeper properties like “does this function always do the right thing?” or “will this code ever crash at runtime?” It can find bugs, and it can also give strong, math-like guarantees that certain errors are impossible.
Think of Kani like a super-strict grader for your code. It tries all the cases within smart limits, and, with a few extra hints, it can prove your code is correct for all cases, not just the ones you tested.
The paper’s main questions, in simple terms
- Can Kani catch real bugs that normal tests and fuzzing (randomized testing) miss?
- Can developers start with easy, automatic checks and then add a few simple “rules” to get full, unbounded proofs of correctness?
- Can this work at large scale on real projects, every time code changes?
How Kani works (explained with everyday ideas)
Kani uses a technique called model checking. Imagine a robot that tries every path through a maze to see if you can ever crash into a wall. That’s “bounded” checking: it explores all paths up to a certain depth. Kani does this for code, checking for things like overflows, out-of-bounds access, and panics.
But what if the maze is huge or has loops? You can’t just try every step forever. Kani solves this by letting you write small “contracts” that explain the important truth that stays the same each time you go around the loop, called a loop invariant. With those, Kani can reason about all loop iterations at once, like proving a pattern works for every step, not just the first few.
Here are the key pieces, translated into plain language:
- Proof harnesses: Tiny, test-like entry points that tell Kani, “Check this function for all possible inputs.” Instead of picking random inputs, Kani symbolically explores them all within limits.
- Bounded model checking: Kani “unrolls” loops a set number of times and checks that within that limit, no bad thing happens. This is fast to start with and needs little setup.
- Contracts: Short rules you add to your code:
- Requires: what must be true before a function runs (its allowed inputs).
- Ensures: what must be true when it finishes (the promised result).
- Loop invariants: the fact that stays true every time the loop runs.
- Decreases: a number that goes down each loop step, proving the loop ends.
- Quantifiers (forall, exists): “for every d” or “there exists an x,” useful for precise math-like properties.
- Stubbing: Temporarily replace a complex or system-dependent function (like “what time is it?”) with a simpler stand-in that returns any allowed value. This lets Kani reason about the code without getting stuck on OS details.
- Under the hood: Kani compiles Rust’s mid-level code form (called MIR) into a verification engine (CBMC) that uses powerful SAT/SMT solvers. MIR keeps Rust-specific safety rules intact, so Kani can automatically check many errors without you writing annotations.
A simple example the paper uses is Euclid’s algorithm for greatest common divisor (GCD). Bounded checking needs a large loop limit and can become slow. With a well-chosen loop invariant (“the set of common divisors is preserved each step”), Kani proves GCD is correct for all 64-bit inputs without unrolling the loop many times.
What the researchers found and why it matters
Kani was tried on several real, high-stakes Rust projects. It did more than just catch bugs—it provided strong guarantees and fit into everyday development.
Highlights:
- It found 11 real bugs missed by tests and fuzzing:
- In s2n-quic (a QUIC networking library), Kani found edge-case errors at tricky number-encoding boundaries in seconds, even after millions of fuzzing attempts found nothing.
- In Firecracker (the virtual machine behind AWS Lambda and Fargate), Kani caught a rounding bug in a rate limiter that could let a guest exceed its I/O budget in rare timing cases, plus a panic a guest could trigger by crafting memory in a specific way.
- In Cedar (an authorization policy engine), Kani uncovered a crash caused by slicing strings at invalid character boundaries.
- In Hifitime (a time library used in aerospace), Kani found multiple math and ordering mistakes around dates, durations, and overflows.
- Contracts upgraded “no panics” checks to “full functional correctness” for important parts of code. In other words, with a few short rules, Kani could prove a function always behaves as intended, not just that it doesn’t crash.
- It scales: in the Rust standard library verification effort, more than 16,000 proof harnesses run per code change in continuous integration, keeping quality high as code evolves.
Why this matters:
- Kani complements tests and fuzzing. Tests sample some inputs; fuzzing tries many random ones; Kani symbolically considers all of them within its model. This helps hit rare edge cases and gives stronger guarantees.
- Developers can start with almost no annotations and get value right away, then add small, focused contracts only where stronger proof is needed.
What this could change going forward
- Stronger safety for critical Rust code: Kani helps ensure no panics, no overflows, and correct behavior in security-sensitive systems.
- Practical path from “bug finding” to “proof of correctness”: Teams don’t need to dive into heavy math. They can begin with automatic checks, then layer simple contracts to reach unbounded proofs where it matters most.
- Better CI quality gates: Because it runs on every change, Kani can stop regressions before they land.
- Complements, not replaces, other tools: It works alongside testing, fuzzing, and runtime tools like Miri to cover different kinds of mistakes.
A few current limits to keep in mind:
- Some properties are hard for solvers (especially with complex math), and some features (like certain forms of recursion, concurrency, or external code) may need stubs or extra care.
- Writing the right loop invariant or contract sometimes takes thought—but once written, it pays off with strong, reusable guarantees.
In short, Kani makes it realistic for everyday Rust developers to get formal, trustworthy proofs about their code, starting simple and growing stronger where needed.
Knowledge Gaps
Knowledge gaps, limitations, and open questions
Below is a consolidated list of what remains missing, uncertain, or unexplored, framed to guide actionable future work:
- Concurrency support is absent (threads, async/await, atomics); open path: define and implement a Rust-specific concurrent memory model for verification, including Send/Sync rules and interleavings.
- Unsafe aliasing semantics (Stacked/Tree Borrows) are not modeled; open path: integrate a static/dynamic alias model consistent with Rust’s UB rules to soundly check unsafe code.
- FFI and inline assembly are only handled by unverified stubs; open path: introduce contract schemas for foreign functions and lightweight checks that stubs over-approximate real behavior.
- Recursive functions lack termination checking and mutual recursion is unsupported; open path: add well-founded termination proofs (ranking functions, size-change) and modular summaries for mutually recursive cycles.
- Loop termination is limited to simple non-negative integer measures; open path: support general well-founded orders (lexicographic tuples, structural measures) and richer decreases expressions (e.g., field projections).
- Quantifier reasoning is constrained by solver limits (e.g., nonlinear bitvector arithmetic, lack of triggers); open path: user-facing trigger annotations, specialized instantiation strategies, and hybrid bitvector/integer abstractions.
- Bounded quantifiers on SAT backends require compile-time constants and expand eagerly; open path: scalable bounded quantification (symbolic ranges, slicing) and automatic backend routing.
- Vacuity and assumption misuse (e.g., assume(false)) are not automatically detected; open path: add vacuity checks, precondition satisfiability analysis, and assumption coverage metrics in harnesses.
- Write-set inference and alias-closure are manual for unsafe code and raw pointers; open path: ownership-aware alias analysis to infer/validate modifies sets and detect missing alias-closure.
- For-loop support is exact only for range-based loops; custom iterators require manual abstraction; open path: iterator-level contracts/specs and verified desugaring strategies for common iterator patterns.
- Environment/time modeling relies on ad hoc nondeterministic stubs (e.g., clocks); open path: provide reusable, verified environment models (monotonic clocks, timers, randomness) with conformance checks.
- Coverage depends on manually authored proof harnesses; open path: harness coverage metrics, automatic harness synthesis/guidance, and integration with code/property coverage tools.
- Deeper safety checks (valid-value, uninitialized memory) are opt-in due to cost; open path: selective/incremental enabling, path pruning, and caching to reduce overhead without losing defects.
- Panic semantics are fixed to “panic-free” proofs; open path: allow specifying expected panics, differentiate panic=unwind vs panic=abort, and verify panic-safety properties.
- Cross-crate modular verification and contract reuse are under-specified; open path: stable, versioned contract artifacts, visibility rules, and compositional verification across crate boundaries and generics.
- Drop (destructor) and unwinding behavior are not discussed; open path: model Drop execution on normal and panic paths to verify resource invariants and cleanup correctness.
- Floating-point reasoning (e.g., NaN behavior, FP/nonlinear arithmetic) is challenging; open path: FP-aware invariants, domain-specific rewriting/lemmas, and mixed-precision abstractions.
- Solver selection/performance is manual (e.g., picking Z3 for quantifiers); open path: automated solver portfolios, per-assertion routing, and incremental/interprocedural summaries to scale proofs.
- MIR-to-GOTO translation and DFCC instrumentation lack a formal soundness proof; open path: translation validation or mechanized proofs for Rust-specific constructs (enums, trait objects, closures).
- Unsafe trait implementations and union invariants are flagged risks without dedicated methods; open path: contract patterns and automated checks tailored to unsafe trait code and union field access.
- Quantifier-bound overflow is mitigated manually (e.g., saturating_add); open path: automatic bound safety transformations and diagnostic tooling for specification-induced overflows.
- Plain stubs can silently invalidate proofs; open path: introduce “assume-guarantee stubs” with machine-checked over-approximation conditions or refinement checks.
- Evaluation gaps: no data on false positives/negatives, developer effort, or head-to-head comparisons with Prusti/Creusot/Verus; open path: controlled studies and standardized Rust verification benchmarks.
- AI-assisted specification drafting is mentioned but not evaluated; open path: measure correctness, effort reduction, and failure modes of AI-generated contracts/invariants.
- BMC completeness remains challenging (e.g., GCD unrolling blows up); open path: automatic bound inference, completeness diagnostics, and fallback strategies (e.g., automatic suggestion of candidate invariants).
- Release vs debug integer overflow semantics are not addressed; open path: configurable integer semantics aligned to deployment profiles and cross-profile proof portability.
- Assumptions about non-aliasing between modified and unmodified locations in unsafe code are unchecked; open path: diagnostics or proof obligations to validate non-aliasing, with counterexample generation.
- Trait objects/dynamic dispatch are claimed supported but not evaluated; open path: targeted benchmarks on dynamic dispatch-heavy code to surface remaining limitations.
- Limited invariant/contract inference: specifications are mostly manual; open path: synthesis from code/tests/executions (IC3/PDR-style, template-based loop invariant and contract inference).
Practical Applications
Immediate Applications
Below are practical use cases you can deploy now using Kani’s current capabilities, with suggested sectors, tools/workflows, and key assumptions/dependencies to watch.
- Software (cloud infrastructure, virtualization, systems)
- Use case: Continuous verification of safety and correctness in critical Rust components (e.g., Firecracker’s rate limiter and VirtIO device emulation).
- What to do now:
- Add Kani proof harnesses to CI via cargo kani to assert absence of panics, arithmetic overflow, out-of-bounds, and UB in unsafe blocks.
- Model time and OS/environment calls with stubs (e.g., stub libc::clock_gettime/Instant::now) to verify time-dependent properties symbolically.
- Turn expensive algorithm bodies into verified contracts (e.g., gcd) and replace at call sites with stub_verified to speed up caller proofs.
- Gate merges on Kani results; use concrete playback to reproduce counterexamples as unit tests.
- Tools/workflows: cargo-kani, #[kani::proof], #[kani::unwind], #[kani::requires]/#[kani::ensures], #[kani::loop_invariant]/#[kani::loop_decreases], #[kani::stub]/#[kani::stub_verified], kani::any, concrete playback.
- Dependencies/assumptions: Single-threaded semantics only (no threads/async); model FFI via stubs; validate harness assumptions to avoid vacuity; for complex arithmetic/quantifiers prefer SMT backends (e.g., Z3); unsafe code may require explicit modifies and aliasing discipline.
- Software (networking stacks and protocols)
- Use case: Prove corner cases at encoding/decoding boundaries (e.g., s2n-quic variable-length integers, packet number decoding) that fuzzing often misses.
- What to do now:
- Combine property-based tests with Kani harnesses (Bolero + Kani), running the same property both as a fuzzer and a proof.
- Express protocol invariants and boundary conditions as assertions/contracts; let Kani explore the full input domain within bounds or via loop contracts.
- Tools/workflows: Bolero integration; dual-mode harnesses (fuzz/Kani); concrete playback for actionable counterexamples.
- Dependencies/assumptions: Keep preconditions precise (not overly strong); solver scalability for large bit-vector encodings; sequential execution only.
- Library and SDK maintainers (standard library, serialization, numerics)
- Use case: Guard public APIs against panics, UB, and semantic regressions; publish machine-checked specifications (contracts) for clients.
- What to do now:
- Add Kani harnesses across public APIs; use contracts for pre/post conditions; enforce frame conditions with modifies.
- Run Kani in CI at scale (as in the Rust standard library campaign verifying ~16k harnesses per change).
- Tools/workflows: Contract libraries per crate; CI dashboards aggregating results.
- Dependencies/assumptions: Solver time for large projects; align contracts with documented API semantics; maintain verified stubs for FFI.
- Security engineering and hardening
- Use case: Verify that adversarial inputs (e.g., untrusted guest memory in VirtIO) cannot cause panics or violate invariants.
- What to do now:
- Model attacker-controlled memory/state with kani::any and assumes consistent with hardware/protocol specs.
- Assert protocol conformance and safety properties; combine with dynamic tools (Miri) for aliasing checks.
- Tools/workflows: Adversarial harnesses with nondeterministic inputs; contracts + stubs for environment boundaries.
- Dependencies/assumptions: Kani doesn’t model Stacked/Tree Borrows; rely on safe Rust aliasing guarantees and complement with Miri for unsafe aliasing.
- Embedded/IoT/Robotics/Energy firmware (sequential)
- Use case: Prove absence of panics/overflows and functional correctness of embedded algorithms (e.g., fixed-point math, control routines) where Rust is used without threads.
- What to do now:
- Write loop contracts for key algorithms to avoid deep unwinding; stub hardware/RTOS calls.
- Enforce no-panic policies for release builds via Kani harnesses.
- Tools/workflows: Loop invariants and decreases; FFI/hardware stubs.
- Dependencies/assumptions: No concurrency model; model peripherals via stubs; ensure decreases are simple integers.
- Finance and fintech libraries/services
- Use case: Ensure arithmetic safety (no overflows), invariant preservation (e.g., non-negative balances), and panic-freedom in accounting/ledger code.
- What to do now:
- Add contracts to encode business invariants; use quantifiers with bounded ranges for integrity checks over small domains.
- Prefer SMT solver backends for quantified constraints.
- Tools/workflows: #[kani::requires]/#[kani::ensures], kani::forall!/exists! with bounded ranges; CI gating.
- Dependencies/assumptions: Nonlinear arithmetic/quantifiers may be hard for solvers; use bounded checks or refactor arithmetic where needed.
- Healthcare and medical software (Rust components)
- Use case: Verify safety-critical calculations and data normalization code for panic/overflow freedom and simple correctness properties.
- What to do now:
- Codify safety assumptions as preconditions; assert postconditions for calculators/transforms; use stubs for device/OS boundaries.
- Tools/workflows: Contracts; environment stubs; CI artifacts as evidence.
- Dependencies/assumptions: Regulatory acceptance requires process artifacts; tool qualification may be needed; model clinical environment precisely.
- Academia and education (courses and research)
- Use case: Teach formal verification in a mainstream systems language with low annotation overhead and immediate feedback.
- What to do now:
- Use GCD and similar algorithms to teach bounded vs. unbounded proofs (loop invariants, decreases); assign students to verify library functions.
- Compare dynamic tools (Miri) with static model checking (Kani) to highlight trade-offs.
- Tools/workflows: Z3 backend for quantified examples; contract and loop invariant exercises.
- Dependencies/assumptions: Provide guidance on non-linear arithmetic limits and vacuous assumptions.
- DevOps/QA “spec-as-code”
- Use case: Maintain executable specifications as harnesses and contracts co-located with code; review assumptions as part of code review.
- What to do now:
- Treat #[kani::assume] as audited artifacts; run Kani in PR checks; standardize stub policies (verified contract vs. plain stub).
- Tools/workflows: Templates for harnesses; CI checks; dashboards; failure triage with concrete playback.
- Dependencies/assumptions: Team discipline around assumption review; CI capacity for solver runs.
- Open-source maintainers/individual developers
- Use case: Quickly catch panics, UB, and boundary bugs beyond tests/fuzzing on personal or small-team Rust projects.
- What to do now:
- Start with panic/UB harnesses; add contracts incrementally for hot paths; use stubs to isolate external dependencies.
- Tools/workflows: Minimal harnesses; default SAT solver; upgrade to SMT as needed.
- Dependencies/assumptions: Keep quantifier ranges small for SAT backends (compile-time bounds ≤ ~1000), or switch to Z3/cvc5/Bitwuzla.
Long-Term Applications
These applications are promising but require additional research, engineering, or ecosystem adoption before broad deployment.
- Concurrency and async verification
- Vision: Prove correctness and panic/UB freedom for multi-threaded/async Rust (lock-free structures, actor systems, networking runtimes).
- Needed advances: Sound concurrency semantics in the backend, scheduling exploration, memory-model integration, and aliasing models (Stacked/Tree Borrows).
- Regulatory-grade assurance (safety/security certification)
- Vision: Use Kani artifacts (harnesses, verified contracts, solver logs) as evidence in audits (e.g., medical, automotive, aerospace, finance).
- Needed advances: Tool qualification and standards mapping, auditability and traceability dashboards, scalable verification pipelines.
- Verified contract ecosystem for crates
- Vision: Crates publish machine-checked contracts; downstream users rely on stub_verified abstractions to scale verification.
- Needed advances: Community norms, registry support for “verified” metadata, versioned contract compatibility, trust policies.
- Solver and backend capability growth
- Vision: Richer proofs over non-linear bit-vectors and quantifiers (e.g., proving full GCD divisibility postconditions), enabling stronger functional correctness in crypto, numerics, and geometry.
- Needed advances: SMT improvements (quantifier instantiation, non-linear BV), and tighter integration with Kani/CBMC.
- Automated contract and invariant synthesis
- Vision: AI-assisted discovery of preconditions, postconditions, and invariants; proof-repair loops that suggest minimal changes to meet specs.
- Needed advances: Reliable spec mining; safeguards against vacuous/over-strong assumptions; human-in-the-loop review workflows.
- Total correctness for recursion and richer decreases
- Vision: Verify termination and correctness of recursive and mutually recursive functions; support lexicographic and structural measures.
- Needed advances: DFCC/VCG support for complex well-founded orders; recursion termination checking; mutual recursion handling.
- Rich environment, hardware, and FFI models
- Vision: End-to-end verification for drivers, hypervisors, and embedded systems with realistic OS/hardware/network models.
- Needed advances: Library of audited environment models; co-simulation/abstraction techniques; automated stub generation with soundness guarantees.
- Sector-specific verified frameworks
- Vision:
- Cryptography: contract-verified constant-time and functional correctness for primitives/protocols.
- Robotics/Autonomous: safety envelopes for control loops and planners.
- Energy/ICS: real-time scheduling/resource invariants for PLC/edge devices.
- Finance: end-to-end invariants on transaction pipelines and risk calculations.
- Needed advances: Domain-specific models and time semantics; integration with real-time constraints; cross-component composition.
- Enterprise “continuous verification” platforms
- Vision: Organization-wide dashboards tracking property coverage, proof debt, and regression risk; supply-chain security gating on verified properties.
- Needed advances: Scalable orchestration, incremental verification, cross-repo dependency tracking, concise audit trails.
- Ecosystem-scale scanning and vulnerability discovery
- Vision: Periodic verification sweeps across crates.io to flag latent UB/panics and API contract violations.
- Needed advances: Compute capacity, prioritization heuristics, automated PRs with harnesses and fixes.
Each long-term item depends on ongoing research (e.g., concurrency, solvers), engineering (tooling and CI scalability), and ecosystem/process changes (standards, contracts as first-class artifacts).
Glossary
- Aliasing: The situation where two or more references point to the same memory location, requiring care in frame reasoning and write sets. "for unsafe code, the user must ensure is closed under aliasing."
- Bit-precise verification: Verification that models operations at the exact bit level (e.g., machine integers), avoiding abstraction gaps. "CBMC's bit-precise verification engine"
- Bitvector: A fixed-width sequence of bits used to model machine integers and operations in SMT/SAT encodings. "over nonlinear 64-bit bitvector remainder"
- Borrow checking: Rust’s static analysis ensuring references obey ownership and aliasing rules. "where the compiler still enforces borrow checking and type safety"
- Bounded model checking: A verification technique that explores program executions up to a finite bound to check properties. "Bounded model checking fills the automated end of this gap."
- CBMC: A bit-precise bounded model checker that symbolically executes GOTO programs and discharges properties via SAT/SMT. "CBMC returns a verdict per property (e.g., assertion, overflow check, memory safety check)"
- Code-level model checking: Applying model checking directly to program code rather than abstract models. "builds on the code-level model checking methodology"
- Compositional reasoning: Verifying parts in isolation using contracts to abstract callees, enabling scalable proofs. "serves as a sound abstraction for compositional reasoning."
- Contract abstraction rule: The reasoning rule that soundly replaces a function call with its verified contract at call sites. "applying a contract abstraction rule"
- Deductive verification: Proving program correctness using logical specifications (e.g., pre/postconditions) and proof rules. "Deductive verification tools for Rust"
- Differential testing: Testing by comparing outputs of two implementations (e.g., an implementation and a reference model) on the same inputs. "and differential testing against a formal reference model (Cedar)."
- Dynamic Frame Condition Checking (DFCC): A CBMC instrumentation that enforces declared write sets and frame conditions for contracts. "Dynamic Frame Condition Checking (DFCC) for function and loop contracts, instrumenting write-set enforcement."
- Dynamic trait objects: Runtime representations of trait-typed values enabling dynamic dispatch in Rust. "to reason about dynamic trait objects and closures"
- Enum discriminant: The tag identifying which variant of a Rust enum is active, with well-formedness enforced during verification. "invalid enum discriminants"
- FFI (Foreign Function Interface): Mechanism to call code written in other languages, often requiring stubs for verification. "FFI calls executing outside CBMC's memory model unless stubbed"
- Floyd–Hoare logic: A logic for reasoning about program correctness using Hoare triples and proof rules. "grounding them...in Floyd-Hoare logic"
- Floyd–Hoare While rule: The proof rule using loop invariants to establish correctness of while loops. "an application of the Floyd--Hoare While rule."
- Frame condition: A condition stating that memory outside the declared write set remains unchanged across a function or loop. "via CBMC's frame condition checker."
- Ghost state: Specification-only state used in proofs but not in the executable program. "proof engineering (e.g., separation logic or ghost state)"
- GOTO programs: A low-level intermediate representation used by CBMC for symbolic execution. "translates Rust programs into GOTO programs"
- Havoc: Replacing the contents of specified memory locations with arbitrary values to model unknown effects. "The havoc uses kani::any() (preserving )"
- Hoare triple: A specification of the form {Pre} S {Post} describing program behavior. "The contract establishes the Hoare triple:"
- IRep format: CBMC’s internal serialized representation for GOTO programs. "serialized to CBMC's binary IRep format."
- LLVM IR: A low-level intermediate representation used by the LLVM compiler infrastructure, less suited here than MIR for Rust-specific reasoning. "rather than LLVM IR."
- Loop invariant: A property that holds before and after every loop iteration, used to prove correctness. "The loop invariant is verified inductively"
- Memory model: The formal semantics of memory operations assumed by the verifier (e.g., allocations, aliasing). "outside CBMC's memory model unless stubbed"
- Mid-level Intermediate Representation (MIR): Rust’s typed, monomorphized IR used by Kani to preserve Rust semantics during verification. "Mid-level Intermediate Representation (MIR)"
- Monomorphization: Specializing generic code for concrete types before code generation or analysis. "monomorphization, producing typed MIR."
- Nondeterministic value: A symbolic value that can take any value of a type, used to explore all behaviors. "The call kani::any() produces a nondeterministic value of the given type"
- old(e): A history expression referencing the pre-state value of e inside a postcondition. "The old() construct captures the pre-state value of~"
- Partial correctness: A guarantee that if a program terminates without errors, the postcondition holds (termination may not be proved). "sound for partial correctness unconditionally"
- Propositional formula: A Boolean formula encoding program paths and assertions for SAT/SMT solving. "encodes all assertions as a propositional formula"
- Quantifier instantiation: Solver heuristic of choosing concrete values for quantified variables during SMT solving. "cannot automatically instantiate the quantifier"
- Quantifiers: First-order logical constructs (forall/exists) used in specifications. "Kani provides first-order quantifiers as procedural macros:"
- SAT solver: A solver for propositional satisfiability used to decide encoded verification conditions. "that is checked using a SAT solver."
- Safety invariant: A semantic property safe code may assume but the type system does not enforce by default. "A safety invariant is a semantic property"
- Shadow memory: An auxiliary memory used to track meta-properties (e.g., initialization) during analysis. "the uninit-memory pass uses shadow memory to verify that every dereference accesses initialized memory."
- SMT solver: A solver for satisfiability modulo theories (e.g., bitvectors, arithmetic) used for richer reasoning. "The #[kani::solver(z3)] attribute selects the Z3 SMT solver"
- Stacked Borrows: A formal model of Rust pointer aliasing rules relevant to unsafe code verification. "no Stacked Borrows / Tree Borrows pointer-aliasing modeling"
- Static Single Assignment (SSA) form: A representation where each variable is assigned exactly once, simplifying encoding. "converts the result to SSA form"
- Stubbing: Replacing a function with a mock implementation during verification to model the environment or simplify proofs. "Function stubbing replaces a function's implementation with a handwritten mock"
- Symbolic execution: Executing programs over symbolic inputs to explore many paths at once. "CBMC, which performs symbolic execution of the GOTO program"
- Total correctness: A guarantee of both correctness of results and termination. "upgrading the result to total correctness."
- Tree Borrows: An alternative formal model for Rust aliasing discipline. "no Stacked Borrows / Tree Borrows pointer-aliasing modeling"
- Undefined behavior: Program operations with no well-defined semantics (e.g., invalid dereferences), whose absence is verified. "detect undefined behavior at runtime but cannot prove its absence."
- Unwinding assertion: An assertion inserted after k unrollings to ensure a loop has been fully explored at the bound. "inserting an unwinding assertion after the last copy."
- Unwinding bound: The maximum number of loop iterations explored during bounded analysis. "The #[kani::unwind(94)] annotation sets the loop unwinding bound."
- Validity invariant: A type’s well-formedness constraint that must always hold (e.g., valid enum tag). "A validity invariant must hold at all times"
- Well-founded measure: A non-negative measure that strictly decreases each iteration/call to prove termination. "proving termination ... a well-founded decreasing measure"
- Write set: The set of memory locations a function or loop may modify, used for frame reasoning. "the write set is empty since gcd is a pure function."
Collections
Sign up for free to add this paper to one or more collections.