Papers
Topics
Authors
Recent
Search
2000 character limit reached

Kani: A Model Checker for Rust

Published 1 Jul 2026 in cs.SE, cs.LO, and cs.PL | (2607.01504v1)

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.

Summary

  • The paper introduces Kani, a tool that bridges automated bug finding with unbounded correctness proofs for Rust using lightweight specifications at the MIR level.
  • It integrates seamlessly with the Rust toolchain and employs CBMC with various solver backends to perform exhaustive symbolic analysis of safety properties.
  • Empirical results demonstrate Kani’s effectiveness in uncovering subtle bugs and reducing verification times in industrial-scale, safety-critical Rust applications.

Kani: A Model Checker for Rust

Motivation and Context

The Rust programming language has achieved wide adoption for safety-critical systems programming, offering a robust ownership type system that eliminates a large class of memory errors in "safe" code. However, critical gaps persist: developers must still ensure the correctness of "unsafe" operations, check for functional correctness properties, and guarantee absence of runtime panics. Testing and fuzzing only sample possible behaviors; complete correctness demands exhaustive reasoning. While deductive verification tools for Rust exist (e.g., Prusti, Creusot, Verus), they impose significant annotation and proof engineering burdens, reducing their practical deployment in mainstream codebases.

Kani addresses this verification gap by introducing an open-source model checker for Rust that bridges the spectrum from automated bug-finding to unbounded correctness proofs. Operating at the Rust Mid-level Intermediate Representation (MIR) level, Kani leverages bounded model checking (BMC) for automated safety property proofs, then incrementally upgrades to unbounded verification through lightweight specification constructs: function contracts, loop contracts, quantifiers, and stubbing.

Architecture and Tooling

Kani integrates deeply with the Rust toolchain, invoked via cargo kani for seamless usability. The toolchain reuses the full frontend of rustc, extracting and transforming MIR for reachability analysis, contract insertion, stubbing, and safety instrumentation. The modified MIR is lowered to GOTO programs executed by CBMC, a bit-precise BMC engine. Kani compiles specification constructs into CBMC-compatible expressions, synthesizing program-specific logical formulas dispatched to a range of solver backends (SAT/SMT), including MiniSat, Kissat, Z3, cvc5, and Bitwuzla.

MIR-level operation is essential for Rust: it preserves monomorphized type information, enum discriminant layouts, and Rust validity invariants erased by LLVM IR. This enables Kani to enforce and check Rust-specific semantic properties, including correct use of dynamic trait objects and closures. The verification pipeline orchestrates contract instrumentation, GOTO codegen, and analysis, mapping verification results back to original Rust source constructs for actionable diagnostics.

Specification Language and Verification Methodology

Kani's contract language offers a smooth path from bounded to unbounded verification:

  • Bounded Model Checking: Supports fully automated checking for safety properties—arithmetic overflows, pointer misuse, panics—by symbolic execution, loop unrolling, and SAT/SMT solving. When unwinding bounds suffice, results are complete.
  • Function Contracts: Allow specification of preconditions and postconditions over arbitrary (including mutably aliased) parameters, as deterministic, side-effect-free Rust boolean expressions. Postconditions utilize a pseudo-function old for referencing pre-state values and support quantified constraints.
  • Loop Contracts: Provide invariants and decreasing measures to discharge unbounded properties over loops, replacing costly exhaustive unrolling. Loop invariants, written with quantifiers if necessary, enable inductive proofs in a single BMC query, with sound application of the Floyd-Hoare while-rule.
  • Quantifiers and Stubbing: First-order quantifiers (forall!, exists!) can be used in contracts or specifications, either expanded for bounded domains or natively handled by SMT solvers on demand. Stubbing mechanisms allow for selective replacement of function implementations with either verified contract abstractions or user-supplied models, with explicit soundness trade-offs.

Kani formalizes these constructs atop Hoare logic, supporting compositional, modular reasoning with user-controllable precision. Safe code benefits from borrow-based frame inference for write sets; uses of raw pointers or unsafe code paths require explicit user annotations to manage aliasing and mutability soundly.

Evaluation and Empirical Results

Kani is deployed across industrial and open-source Rust projects of varying scale and domain, including Firecracker (VMM behind AWS Lambda/Fargate), s2n-quic (QUIC transport protocol), Hifitime (aerospace time management), and the Rust standard library itself (2607.01504).

  • Bug Finding: Kani identified eleven bugs missed by extensive testing and fuzzing in widely used software, including subtle bugs at the boundaries of integer encoding in s2n-quic, time-dependent rounding errors in Firecracker, and logical errors in Cedar's string analysis. Fuzzers exploring millions of executions failed to cover these cases due to state space sparsity or lack of appropriate environmental modeling. Kani's symbolic analysis saturated all input spaces exhaustively and produced concrete counterexamples.
  • Automation and Unbounded Verification: In the Hifitime library, initial panic-freedom harnesses detected multiple defects but suffered from severe scaling limits—bounded BMC frequently timed out for complex loops. Augmenting with function and loop contracts both resolved scalability bottlenecks (by compositional abstraction) and enabled proof of critical functional properties, such as normalization invariants, algebraic laws, encode-decode identities, and conformance to the Rust standard library's Eq/Ord contracts. Over 150 harnesses verify both safety and correctness properties, with contract composition reducing verification times by orders of magnitude.
  • Sustainability and Integration: All major projects run Kani in CI workflows, with latency practical for industrial use (under 1–69 minutes per change for large codebases; over 16,000 harnesses per CI run in the Rust standard library). Kani's harnesses are standard Rust code, facilitating continuous evolution alongside source code and encouraging broader specification adoption. The Bolero property testing framework further enables dual-mode verification (as fuzz test or Kani proof) with minimal annotation.
  • AI-assisted Specification: Case studies demonstrate specification development assisted by AI coding agents: Kani's design (contracts as Rust code) allows general-purpose AI assistants to propose, test, and iterate on specifications, with Kani toolchain feedback enforcing correctness. All incorrect AI-generated annotations were caught and diagnosed by Kani.

Practical and Theoretical Implications

Kani's integration of model checking with Rust's MIR and its extensible specification language positions it uniquely among formal verification tools:

  • Low Annotation Overhead, High Coverage: Unlike deductive verifiers requiring manual loop invariants and ghost state constructs, Kani provides strong default safety property checking with zero-initial annotation. Users can then incrementally introduce contracts as needed, leveraging the existing SAT/SMT infrastructure for both bounded and unbounded results.
  • Compositional Soundness: Function and loop contract abstractions, once verified, permit modular reasoning and integration with legacy or environmental code through explicit stubbing. Contract-based modularity is critical for scaling verification to large, evolving codebases.
  • Support for Unsafe and Safe Code: Kani robustly supports safe Rust by leveraging Rust's type and ownership system invariants. For unsafe code, the tool exposes user responsibility for invariants and aliasing, but provides a path for incremental safety through stubbing, frame inference, and custom contracts.
  • Concurrency and Advanced Features: Presently, Kani restricts itself to sequential Rust and does not provide reasoning for advanced pointer-aliasing models (e.g., Stacked Borrows or Tree Borrows) or concurrency; such extensions will require further architectural enhancements.

Future Directions

Kani's roadmap suggests several avenues for advancing verification in Rust and beyond:

  • Solver-Aided Specification Synthesis: Improved integration with AI tools and synthesis of inductive invariants via counterexample-guided interaction could reduce or eliminate the remaining annotation overhead for functional correctness properties.
  • Concurrent and Async Rust: Extending the MIR-based architecture to model threads, async/await, and pointer-aliasing semantics (Stacked Borrows) will be necessary for full coverage of all Rust code patterns.
  • Interoperability and Composition: Combining Kani with deductive frameworks (e.g., Creusot, Verus) or integrating with semi-automated separation logic tools (e.g., Gillian-Rust) may further reduce the specialization wall between formal methods experts and general software engineers.

Conclusion

Kani exemplifies a practical, highly automated approach to exhaustive software model checking for Rust, providing both bounded safety and unbounded functional correctness through lightweight contracts. Its architecture, exploiting MIR-level knowledge and integration with industrial model checking backends, delivers verifiable, actionable guarantees at CI scale across safety-critical domains. The work demonstrates that model checking, once the domain of verification engineers, can become a routine part of mainstream software development for system-level languages.

Citation: "Kani: A Model Checker for Rust" (2607.01504).

Paper to Video (Beta)

No one has generated a video about this paper yet.

Whiteboard

No one has generated a whiteboard explanation for this paper yet.

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 Mod\mathit{Mod} 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 vInv(τ)vInv(\tau))"
  • 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(ee) construct captures the pre-state value of~ee"
  • 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 sInv(τ)sInv(\tau) 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 ¬G\neg G 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 vInv(τ)vInv(\tau) 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."

Open Problems

We haven't generated a list of open problems mentioned in this paper yet.

Collections

Sign up for free to add this paper to one or more collections.

Tweets

Sign up for free to view the 7 tweets with 0 likes about this paper.

HackerNews

  1. Kani: A Model Checker for Rust (54 points, 1 comment) 

Reddit

  1. Kani: A Model Checker for Rust (6 points, 0 comments) 
  2. Kani: A Model Checker for Rust (1 point, 0 comments)