Neither scales precise analysis to the individual components of a large system.
Under-constrained execution (UC-KLEE) automates this via lazy initialization — allocate & initialize memory objects on first dereference. (Manual extraction — TCP stack, LKL — is labor-intensive and unmaintainable.)
Same workload — interpreters re-decode every instruction. File systems, the TCP stack, and drivers are out of reach.
Goal: analyze cal() in isolation — cover every path, including the assert(false) on line 12.
No tool can start here. Fuzzer input bounces off: there is no main(), and nobody built a list in memory.
head dangles into nothing. UCSan must answer on the fly: what does it point to · how big is it · what's inside?
A shadow pointer per pointer argument, deserialize the seed into head, then call cal() → a self-contained binary.
Every memory access is translated before use. Aliased pointers (curr, curr->v, head->next) share one shadow pointer — like a segment register. Translated pointers are ephemeral: the object may be reallocated or resized later.
Map to a wrapper (kmalloc → malloc) · assume pure · or havoc pointer args. (None in this example.)
All on LLVM IR — zero source changes; any code that emits IR works.
Watch for these in the next three runs: every pointer the program touches is a pseudo-pointer · check_ptr turns it into a real pointer just in time · JITI materializes memory the first time it's needed.
= UCSan† — a complete under-constrained concolic engine; this loop drives the exploration you’re about to watch.
①get_concrete(head): not in seed → phantom object, zero-filled → head = 0x0
②while(head): 0 → loop skipped, sum = 0 → assert NOT triggered
③Solver negates head ≠ null → assign head = 0x1 (a pseudo-pointer — any non-null value works) ⟹ Seed 1
⑤check_ptr → JITI allocates obj1 (phantom): size from container_of → node_t, not list_t · pointed-by SO@0 recorded
⑥pseudo-pointer head->next (0x1) → real obj1.list.next; shadow ptr stores logical base −3 (1−4), so any pointer arithmetic keeps working
⑦obj1.next = 0 → loop exits · sum = 0+obj1.v is symbolic → solve sum>100 ∧ obj1.next≠0 ⟹ Seed 2
⑨obj1 loaded from seed (via pointed-by recorded at ⑤) → obj1.next = 0x1 → re-enter loop
⑩check_ptr → JITI obj2 (phantom); record point-to (obj1 off 4 → obj2); obj2.next = 0 ends the loop
sum = 101 → assert(false) FIRES ✓ all paths covered — no harness, no manual setup
; cal.bcdefine i32 @cal(ptr %head)
entry: calscope: [foo, bar]kmalloc: → malloc
*p ⇒ *check_ptr(p, p_s)kmalloc() ⇒ malloc()✂ out-of-scope functions
SymSan: sum>100 ⇒ SMTor fuzzer · model checker
$ ./cal_uc seed2assert(false) hit!
Config is the harness-killer: name any function as entry, any set of functions as scope. Write it by hand — or generate it with static analysis or an LLM.
Output runs anywhere: no VM, no disk image, no device. Kernel code becomes a normal user-space process.
Analysis scopes generated automatically: function pointers in static initializers as entries + call-graph analysis.
Remaining failures: unhandled inline assembly — the same limitation KLEE has. Unlike KLEE, UCSan offers a path forward: complex blocks can be modeled with a custom wrapper function (the same mechanism as external functions; LLMs can help generate them).
Confirm/refute UBITect's use-before-init warnings — 2-minute, 2 GB budget per warning. UCSan† vs the KLEE-based engine built for this exact task (IncreLux).
6.36× faster average time-to-finish · 15.06× faster per warning (median 4.62s vs timeout)
| 25th% | Median | 75th% | Mean | |
| UCSan† | 3.98s | 4.62s | 6.21s | 14.37s |
| KLEE | 92.13s | T.O. | T.O. | 105.71s |
Reproduce known bugs with only a scope + entry function (most configs generated automatically, incl. by a coding agent).
All without hand-crafted test harnesses; most misses are path explosion or incomplete scope, with a few from unsupported features (e.g. concurrency).
▸ UCSan: the first compilation-based under-constrained execution engine — native speed, general, decoupled from the analysis.
▸ Enables under-constrained concolic execution at scale — finishing far more analysis tasks and finding real bugs without harnesses.
▸ Validated on the Linux kernel and 150+ real CVEs & kernel bugs across three datasets.
Questions? · [email protected] · github.com/R-Fuzz/UCSan
ACKNOWLEDGMENTS
We thank the anonymous reviewers for their insightful comments, and our shepherd for the valuable suggestions. This work is supported, in part, by the National Science Foundation (Grant No. 2046026), the Google ASPIRE Fund (2023 Award), and the United States Air Force and DARPA (Agreement No. FA8750-24-2-0002). Any opinions, findings, conclusions, or recommendations expressed are those of the authors and do not necessarily reflect the views of the funding agencies.