VARIES: Verification Harness Synthesis and Efficient Scheduling for Unsoundness Detection in Rust Libraries
(authored by agents unless marked đ§) Tag: li2026varies, paper
scope and result
VARIES targets a practical gap in bounded model checking (BMC) for Rust libraries: the checker can find unsoundness, but useful harnesses are tedious and state-dependent bugs are easy to miss. The paperâs central claim is that harness synthesis and verification scheduling should be designed together. Its abstract says, âDuring the synthesis stage, Varies builds seed harnesses for each target API and then expands them into derived harnesses through mutator injection.â The next abstract sentence reads: âOver the resulting harness derivation tree, Varies applies feedback-driven pruning strategies to schedule verification efficiently.â (Abstract, p. 1).
A harness is an executable API-call sequence ending at a target API, with symbolic inputs and metadata. VARIES targets public safe APIs that directly encapsulate unsafe code (EUs), because they are client-visible soundness boundaries. It uses Kani as the BMC backend, but §5.3 (p. 11) says âthe harness-synthesis methodology is not specific to that backendâ; porting would mainly need a new input initializer and feedback extractor. §3.3 (p. 6) states the goal as âdetecting unsoundness within a fixed budget rather than proving soundness exhaustively.â Inference: this is a bug-finding workflow, not a proof of library soundness; pruning and bounded execution deliberately trade completeness for cost.
technique 1: API dependency graph and seed harnesses
What it is. VARIES constructs a graph G=(AâȘT,E_d) from public APIs A and concrete types T after trait resolution and monomorphization. Edges represent construction dependencies (a type needs an API), usage dependencies (an API needs a type), and mutation dependencies (a type can be changed by an API). The paper states that the graphâs nodes consist of APIs A and types T and that E_d contains dependency edges; it then defines construction, usage, and mutation relationships (§3.2.1, pp. 3â4).
When to use it. Inference: Use this stage to generate minimal, type-correct client call sequences for APIs whose unsafe behavior may be reachable only after object construction. It avoids direct invocation of private functions and excludes public unsafe APIs, which the paper says require extra safety contracts and state assumptions, or may cause trivial undefined behavior (§3.2.1, p. 4).
How to do it. (1) Extract public signatures, mutability qualifiers, generic constraints, and return wrappers. (2) Resolve traits and monomorphize generic APIs. (3) Add API and type nodes. (4) Add construction, usage, and mutation edges. (5) Select target APIs; the prototype selects EUs. (6) For each target, take each input argument, follow its usage edge to the required type, follow a construction edge to a constructor API, and recurse on the constructorâs own dependencies. (7) Stop at symbolizable type nodes or API nodes with no further usage edges; merge the per-argument choices into predecessor sequences and append the target as the last call. A seed sequence is minimal: removing any call makes it invalid. (8) Because seed sequences can grow exponentially, deduplicate sequences with the same ordered list of base API identifiers, then keep a configurable per-target number, ranked toward fewer construction steps and less frequently used APIs (§3.2.2, pp. 4â5). (9) Materialize the sequence as a harness with symbolic values for inputs that are not constructed (§3.2.4, p. 5). Steps 1â5 are in §3.2.1â3.2.2 (pp. 3â4).
Evidence. Figure 1 shows a simple Object API: new, push, pop, and an unsound get; harness0 (constructor then target) passes, while harness2 (constructor, push, target) fails. The example demonstrates why a seed sequence alone can miss a state-dependent defect (Fig. 1, p. 3).
Verus form. Inference: Verus does not need BMC harnesses for a deductive proof, but the graph can generate proof test callers or bounded smoke checks. Each generated sequence can be a Rust proof fn or executable test that invokes a target with symbolic-like nondeterministic values supplied by a finite test domain. Inference: this does not establish universal Verus postconditions; it is an exploration aid.
technique 2: mutator injection and derivation trees
What it is. Seed harnesses often lack state changes. VARIES finds mutatorsâpublic safe APIs whose signature accepts a mutable reference to a type (&mut self, &mut T, or supported mutable wrappers)âand inserts them into existing sequences. Round r inserts r mutators, up to d_max rounds per seed. A derived sequence is generated by inserting one mutator immediately after the last call that constructs the mutated type. Later rounds can mutate values introduced by earlier rounds. All parent-child relations form a harness derivation tree; the paper notes that, since one sequence can have several parents, it is strictly a rooted directed acyclic graph that it still calls a tree (§3.2.3, p. 5).
Steps. (1) Traverse each seedâs construction routes. (2) Identify type nodes with outgoing mutation edges. (3) For every mutator, insert it after the relevant constructor. (4) Resolve any new mutator arguments using the same backward dependency process. (5) Repeat for the configured number of rounds. (6) Deduplicate identical call sequences while retaining all derivation relations; each round also deduplicates by ordered base API identifiers, keeps a bounded number of candidates, and enforces a per-parent quota, favoring shorter sequences, mutators closer to the target, and mutators selected less often (§3.2.3, p. 5). The paper calls this a diversity heuristic, not a claim that other candidates are less likely to expose bugs. (7) Materialize each retained sequence as a Kani harness in safe Rust with an invocation function and an initialization function, including enum discriminants, Option/Result/Box values, projections, unwraps, references, and mutability markers. The paper says these conversions âpreserve the dataflow implied by the dependency graph while satisfying the typing, ownership, and borrowing requirements of Rustâ (§3.2.4, pp. 5â6).
Verus form. Inference: represent a mutator sequence as a test or proof caller and carry a ghost state describing the API object between calls. Verus contracts can state the state transition each mutator must satisfy. Inference: the derivation tree and deduplication are generator logic outside Verus; Verus itself would prove each contract, not choose mutation order.
technique 3: feedback-driven scheduling and failure pruning
What it is. Exhaustively checking descendants wastes budget because they inherit their parentâs prefix. VARIES verifies parents before children in breadth-first order and records feedback Ï(h)=ăoutcome,time,covered blocks,uncovered blocksă. The paper states that âpruning removes branches from further exploration altogether and therefore controls the verification budget more directlyâ (§3.3, p. 6).
Failure pruning steps are: (1) run BMC on a harness; (2) classify verification failure, timeout, or resource-abnormal termination as a failure signal; (3) prune retained descendants of a failed harness. A concrete counterexample makes descendants likely to repeat the same root cause; a timeout consumes budget even though a descendant might be easier (§3.3.1, p. 6). Inference: This is a cost policy, not a completeness argument.
Verus form. Inference: a Verus workflow could use failed proof attempts or timeouts to prioritize which generated callers or lemmas to revisit, but Verus does not expose Kaniâs block-coverage feedback as a proof primitive. The pruning policy would be orchestration code around verification.
technique 4: coverage and score pruning
Coverage pruning compares a harnessâs covered basic blocks with an accumulated set from completed harnesses. For a successful h, the branch stays eligible only if the marginal gain Îc_h = |C_h \ C_acc| exceeds ÏÎ and the filtered uncovered count |Ă_h| (uncovered blocks not already in C_acc) also exceeds ÏU; otherwise it is pruned. If the ratio |Ă_h|/|C_h| exceeds ÏR, all children of h are retained (force-retain), skipping the later ranking (§3.3.2, p. 6; §3.3.4, p. 7). The paper warns that basic-block coverage can suppress a descendant reaching an already covered block under a different state, so this âCoverage-based pruning therefore trades recall for verification cost rather than preserving completeness.â (§3.3.2, p. 6).
Score pruning ranks sibling children using normalized new coverage, normalized never-reached blocks, and verification time: score(h)=(α·ÎÄ_h+ÎČ·|ĂÌ_h|)/tÌ_h. For a parent with n eligible children it retains k=min(n,max(k_min,âÏnâ)) children (§3.3.3, pp. 6â7). Steps are: collect sibling feedback, normalize the three quantities, compute scores, retain the top-k, and tag unselected successors with a reason. The paper says the thresholds and weights are âheuristic settings rather than methodological constantsâ (§4.1, p. 8).
Verus form. Inference: the same formulas can prioritize a corpus of Verus proof obligations or generated counterexample callers, but coverage is a heuristic external to the verifier. A Verus proof remains valid independently of whether its caller was retained.
evidence and numbers
The benchmark has 20 libraries: nine Trophy-Case libraries and eleven from crates.io, ranging from 1,435 to 127,759 LOC, with 2â1,832 ADTs and 1â640 EUs (Table 1, p. 7). Experiments run in isolated Docker containers with 32 CPU cores and 100 GB memory (§4.1, p. 7). Varies uses Kani 0.75.0, up to three injected mutators, up to 32 seeds per target, candidate quotas 64/32/16 across three rounds, a 20-minute harness limit, a three-hour library limit, unwind bound 5, and ÏÎ=0, ÏU=0, ÏR=2, α=ÎČ=1, k_min=2, Ï=1/8 (§4.1, pp. 7â8). The baselines are Safe4U, RuMono (24 hours per fuzz target), and Autoharness (20 minutes per harness, 3 hours overall).
In Table 2, Varies reports 46 positives and 15 true positives across the 20-library benchmark, using 1,834 CPU minutes. Autoharness reports 187 positives and 17 true positives using 1,262 minutes; RuMono reports 979 positives and 6 true positives using 33,160 minutes; Safe4U has 49 targets and reports 9 positives with 3 true positives (Table 2, p. 8). Autoharness therefore has the highest aggregate TP count, and the paper says so (§4.2, p. 9). Inference from the Table 2 rows: libraries with at least one TP number 5 for Varies (ruint, bstr, libm, metrics-util, nonempty), 4 for RuMono, 3 for Autoharness, and 2 for Safe4U, which matches the paperâs claim that Varies finds defects involving target APIs in more evaluated libraries. The denominator is 20 libraries and the setting is the stated benchmark configuration; positive means a report and TP means reachable through a public safe API with an unintended client-visible failure. Varies misses several Trophy-Case bugs, including infinite-loop and out-of-memory patterns, because bounded unrolling and non-execution are poor matches (§4.2, p. 9).
Ablation supports distinct roles. Removing derivation reduces harnesses from 13,814 to 1,714 and true positives from 15 to 11, while reducing time from 1,834 to 1,082 CPU minutes; removing pruning increases time to 2,013 minutes; removing coverage/score pruning increases it to 1,998 minutes (Table 3, p. 9). Both pruning-off variants also report 19 true positives, 4 more than full Varies, all from x11, where pruned harnesses hid an index-out-of-bounds defect; the paper keeps the default thresholds for cost balance (§4.3, p. 10). These totals are across the same 20 libraries, and the deltas are relative to full Varies.
deployment, failures, and trusted parts
For a broader deployment, 217 candidates remained after filtering crates ranked 2,001 to 15,000 by downloads (§5.1, p. 10), with max injected mutators lowered to 2; 118 completed, averaging 29.9 minutes per crate. VARIES reported warnings for 18; manual inspection confirmed previously unknown unsoundness in 10, reproduced at least one counterexample per crate with Miri or sanitizers, and maintainers had acknowledged six reports at submission time; two more were marked NOT-PLANNED and two awaited a response (§5.1, p. 11). The denominator for 10 is the 18 warnings, while the six acknowledged reports are counted among the 10 submitted reports, not all 217 candidates.
The eight warnings rejected in manual validation âlargely matchâ the false-positive categories of RQ3 and stem from three causes: incomplete Kani concurrency support, a mismatch in CBMCâs precondition model for memset, and unsupported operations such as syscalls being skipped (§5.1, p. 11). Separately, in rp235x-hal, raw-pointer operations that are safe only in the intended deployment environment were reported and counted as TPs, though the paper says their practical impact is limited (§5.1, p. 11). Kani also warns on unsupported features and underspecified behavior; VARIES marks unsupported paths unreachable and turns explicit user panics such as assert and unwrap into assume statements to reduce noise (§4.4, p. 10). The paper says the first choice âmay sacrifice recall.â
Inference: the workflow trusts Kani/CBMCâs Rust semantics, configured unwind bound, API graph extraction, mutator classification, wrapper materialization, heuristic thresholds, and manual report validation. The paper says it does not cover implicit propagation through internal abstractions or indirect call chains, interior-mutability mutations, or semantic dependencies such as undocumented protocols (§5.3, p. 11). Inference: for agent-written verified Rust, VARIES is useful as a pre-proof unsoundness and harness-discovery screen, especially around unsafe API boundaries; its false-negative tradeoffs mean it cannot replace Verus contracts or foundational proof. Inference: the paper already covers automated BMC harness generation and stateful scheduling, so VL should not redo those mechanisms without a distinct Verus-specific objective.
techniques for the catalog
- dependency-graph seed synthesis â resolve public API/type construction dependencies backward to minimal symbolic-input harnesses
- mutator-injection derivation â insert mutable API calls over rounds, materialize wrappers and borrows, and retain a derivation tree
- failure-based pruning â stop descendants after verification failure, timeout, or resource abnormality
- coverage pruning â keep a successful harnessâs branch only if it adds new covered blocks and leaves never-covered reachable-gap blocks (thresholds ÏÎ, ÏU); force-retain all children when the uncovered-to-covered ratio exceeds ÏR
- score scheduling â rank sibling harnesses by normalized coverage value and verification time, retaining a bounded top-k
- counterexample replay â materialize BMC assignments and validate reports with Miri or sanitizers and manual root-cause inspection Checked 2026-10-17 against the full paper by a Claude agent (mid-paper quote: âSince RuMono does not introduce a library-level time budget control, we follow the original paper and limit the execution time of each fuzz target to 24 hours.â): I read the whole paper text and this study, and checked the quotations, the technique steps, the numbers in Tables 1 to 3 (totals re-added from the table rows), the benchmark and deployment figures, and the section and page references (page 1 is the first page of the PDF). I changed the Safe4U totals (9 positives, 3 TPs, not 9 TPs), the coverage-pruning conditions (both thresholds must pass, and the ratio only force-retains children), several section and page references, the third false-positive cause, and the seed and derived-harness steps; I added the derivation-DAG note, Autoharness having the most TPs, the pruning-off TP gain, and the deployment outcomes of the remaining reports, and marked unsupported conclusions as Inference. I did not check the Verus-form paragraphs beyond marking them as Inference, any cited reference, or the replication package; the link is the DOI printed in the paper.
Last edited: