Skip to content

Ucsan#47

Merged
ChengyuSong merged 139 commits into
mainfrom
ucsan
Jul 1, 2026
Merged

Ucsan#47
ChengyuSong merged 139 commits into
mainfrom
ucsan

Conversation

@ChengyuSong

Copy link
Copy Markdown
Collaborator

Merge UCSan as a separate Pass, enabling running SymSan and UCSan individually or combined.

ChengyuSong and others added 30 commits January 15, 2026 15:01
…nary-based API

Updated the Python binding to handle the new solution_t structure with three
operation types (SET, INSERT, DELETE). Solutions are now returned as
dictionaries instead of tuples for better clarity and extensibility.

Changes:
- Added OpType IntEnum with SET(0), INSERT(1), DELETE(2) to module
- Updated SolveTask to return dictionaries with operation-specific fields:
  * SET: {op, id, offset, val}
  * INSERT: {op, id, offset, data}
  * DELETE: {op, id, offset, len}
- Updated test.py to demonstrate new dictionary-based API
- Fixed CMakeLists.txt to use ${Z3_LIBRARY} for correct Z3 linking
- Added comprehensive API documentation with examples to README.md

The new format supports string constraint solving operations (INSERT/DELETE
for strchr/strstr) while maintaining backward compatibility for byte-level
SET operations.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…red offsets

- Add label-level tracking (is_label_bv_, is_label_seq_) to track whether
  expressions involve bitvector or string/sequence variables
- Extend branch_dependency_t to cache input expressions and track whether
  offsets are used in bitvector and/or string contexts
- Add mark_expr_type() to mark offsets with expression types before linking
- Add add_string_bitvec_link() to create constraints linking str-X-Y-Z
  characters with input-X-Y bitvector bytes at the same offsets
- Modify add_nested_constraints() to detect overlapping offsets and add
  linking constraints when the same offset is used in both contexts
- Cache string ranges with their z3::expr for efficient linking lookup

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…cking

- ObjectOrigin and ObjectMapKey now use uint32_t/int32_t instead of uint64_t
- Add range validation before casting to narrower types in lookup_object
- Allows negative offsets (e.g., from OP_DUMMY counter) to work correctly
- Fix printf format specifiers for the new types

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- buildDangleFunction: call ucsan_wrap_retval for each primitive element
  in struct returns, load from returned pointer and assemble the struct
- Change UCWrapRetvalFn return type from i16* to void* to match runtime
- visitReturnInst: use getShadowTy(RT) instead of PrimitiveShadowTy
- __taint_set_retval_tls: add index parameter to support struct elements,
  calculate index from pointer offset (ret_label - __ucsan_retval_tls)
- loadShadowRecursive: fix InsertValue chaining in both TaintPass and
  UCSanPass - return accumulated shadow value instead of discarding it

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
ChengyuSong and others added 28 commits May 19, 2026 22:56
…ro-length under-constrained strings

TaintPass: when hoisting strlen bounds checks with UCSan, find and hoist
the ucsan_check_pointer call for the string base pointer so that
under-constrained objects are materialized before __taint_solve_str_bounds
dereferences them. Search all loop blocks (not just header) to find the
matching ucsan_check_pointer.

dfsan: in __taint_solve_str_bounds, scan shadow memory when strlen returns 0
(under-constrained globals are zero-initialized) to determine tainted extent,
and treat tainted zero terminators as input-derived.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wraps open/openat/close/fopen/freopen/fclose, fseek/fseeko/lseek/ftell/rewind,
fgetc/getc/getchar in ucsan_custom.cpp with a fake-file table backed by the
existing input objects, so symbolic reads work without touching real libc I/O.
Guards the same wrappers in dfsan_custom.cpp behind USE_UCSAN_CUSTOM to avoid
duplicate symbol conflicts.

fgetc creates an 8-bit byte label and ZExt-extends it to 32 bits to match the
int return type. The extension goes through a new __taint_extend_label bridge
(weak stub in ucsan.cpp, strong override in dfsan.cpp wrapping do_taint_union)
so ucsan can be linked without symsan.

All wrappers that don't otherwise store a meaningful return-value label now
clear __dfsan_retval_tls explicitly; handleUCSanCall auto-loads that slot
after each __dfsw_* call and would otherwise read stale label bits left by an
earlier fgetc.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…so fixed return label of read-like functions
Malloc-size minimization could collapse a symbolic allocation size to
malloc(0), steering exploration into allocator/null corner cases instead
of the intended small heap object. Two complementary, non-conflicting
guards:

- Thread the concrete allocation size through __taint_minimize_label
  (label, size, bounds). When the concrete size is 0 and
  allow_zero_size_alloc is off, fastgen emits a synthetic "size != 0"
  condition (via __taint_send_cond) so the manager generates a new input
  with a non-zero size. This is the hard/UCSan path for real malloc(0).

- record_minimize gains an allow_zero parameter; when false the z3
  optimizer adds (size != 0) only inside the minimize pass (soft: it
  falls back to the bounded solver if unsat).

Adds the allow_zero_size_alloc dfsan flag (default false) and plumbs
allow_zero through parse.h / parse-z3.h / the Python binding.
Pass -taint-solve-ub=true in the ucsan,taint pipeline so the analysis of
target IR emits UB-solving instrumentation (ClSolveUB). Actual solving
remains gated at runtime by the solve_ub dfsan flag.
Add __dfsw_ wrappers for htons/ntohs/htonl/ntohl, __bswap_16/32/64 and
__bswapsi2/__bswapdi2 that byte-swap the label via Extract+Concat
(net*_label is a no-op on big-endian). Without these, byte-swapped
protocol fields (e.g. htons(ETHERTYPE_IP6)) lose their symbolic
relationship and dispatch constraints become unsolvable.

UCSanPass keeps __bswap_16/32/64 dangle wrappers semantic (emit
llvm.bswap) instead of returning an arbitrary symbolic value, so the
later TaintPass models them normally.

abilist: mark htonl/htons/ntohl/ntohs/__bswap_*/__bswap[sd]i2 as taint
(ucsan) / custom (dfsan). Keep them uninstrumented as well (the
malloc/memcpy convention) so isInstrumented() is false and TaintPass
wraps them as custom instead of renaming them to <name>.taint;
__bswap_16/32/64 have no libc entry so they get an explicit
uninstrumented mark in done_abilist.
Replace hard-coded UCSAN_OBJECT_SIZE_LIMIT comparisons with
ucsan_object_size_limit(), which honors the max_obj_size runtime flag and
falls back to UCSAN_OBJECT_SIZE_LIMIT when the flag is unset. The cap can
now be tuned per run instead of recompiling.

max_obj_size now defaults to 0 (meaning "use UCSAN_OBJECT_SIZE_LIMIT"), so
the default behavior is unchanged.
Add a cross-pass summary so loop-bounds reasoning can see writes that
happen behind a call (inter-procedural loops) and through UCSan-checked
pointers, and hoist the size/bounds *solving* out of the loop.

- UCSanSummary.h: shared "ptr_field_access" metadata schema — "callee
  loads a pointer from arg#N + field_offset and reads/writes access_size
  bytes through it" — with create/parse helpers.
- UCSanPass (producer): record these per function (recordPointerLoadSummary,
  recordMemoryAccessSummary) and attach as ucsan.mem_summary metadata.
- TaintPass (consumer, hoistBoundsChecks): use callee summaries,
  ucsan_check_pointer-guarded affine accesses, and __taint_solve_bounds to
  emit hoisted solve_size hints in the preheader.

Hard __taint_check_bounds (concrete-range check) is emitted ONLY for
proven affine recurrences (Groups / affine SolveGroups). The heuristic
paths — inter-procedural callee summaries, the unknown-trip-count guard
heuristic, and NonAffineSolveGroups — emit only the soft solve_size hint:
their ranges (trip*size, guard_count*size, ConstantIterations*counts) are
guesses, not proven extents, so asserting a concrete bound there would
risk spurious OOB reports. Per-iteration __taint_check_bounds inside the
loop (and the callee's own checks) are left untouched.

Also erase ptrtoint temporaries materialized only for SCEV queries.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extend the single-file simulator so programs that query their input file
behave under UCSan replay (useful for any user-mode app that stats/sizes
its input, not just afgen targets):

- ucsan_file_state tracks the backing-file size (concrete_file_size via
  stat()), the fake fd, and the fake FILE*; open/openat/fopen record them.
- __dfsw_fileno / fileno_unlocked return the synthetic fd.
- __dfsw_ucsan_fstat / fstat64 / fxstat / fxstat64 synthesize a regular-
  file stat whose st_size is the simulated file size.

abilist: fileno/fileno_unlocked=custom.

Note: fileno is wired directly via the abilist. The fstat wrappers use
ucsan_*-prefixed custom ref names and require a per-target metadata
mapping (fstat->ucsan_fstat) plus ucsan_-ref custom routing to activate;
they are inert in the default configuration.
Mark the EH unwind path uninstrumented in done_abilist.txt (unwinder,
personality, __cxa_throw/begin_catch, type-matching) so throws unwind;
keep the alloc/free path instrumented so malloc/free stay captured.
Rebuild instrumented libc++/libc++abi/libunwind; add tests/cpp_eh.cpp.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
UCSanPass: demangle scope/entry names to match C++ signatures; rebuild EH
dangle stubs as passthroughs to the real libc++abi and restore the
personality; use getFirstInsertionPt so BB tracing doesn't break landing
pads. ko_clang links a plain libc++abi/libunwind EH runtime for ucsan-only
C++; add rebuild_native.sh + CMake install to build it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Under UC exploration a strchr/memchr result is base+index, but the string
solver only models the haystack content, not the base pointer. This broke
delimiter planting on UC-materialized objects:

 - the search result pointed into the freshly-malloc'd materialization (np)
   while the program's pointer stayed a UC pseudo-pointer, so p - c was
   garbage on replay and the planted-delimiter branch was never taken;
 - a match at index 0 collapsed to a NULL pseudo-pointer when the pseudo
   base was 0, so p != NULL failed.

Fixes, for the single-char search ops (strchr/memchr/strrchr/memrchr):

 - ucsan_uncheck_pointer(): inverse of ucsan_check_pointer (phys_to_virt
   style), translating the materialized result back into the caller's UC
   pseudo space so p - c == index holds concretely. Driven by a new
   `retptrN` abilist category; UCSanPass emits the translation after the
   call and TaintPass propagates the (separate) symsan label through it.
 - the __dfsw_ wrappers stash the haystack pointer label in op2's high bits
   (low 8 bits remain the needle char); the solver constrains that base
   pointer non-null and extends the (PtrToInt(strchr) - PtrToInt(base))
   simplification to the tainted (UC) base, yielding index == k instead of
   index - base_value == k.

strstr/strpbrk/memmem keep a needle/accept pointer in op2 (used for
concrete content) so they have no room to carry the pointer label and are
not enabled here.

tests/strchr_plant_*.c, memchr_plant_offset.c: symsan-level coverage for
delimiter planting. strchr_sibling_byte.c: covers an indexof constraint
conjoined with an overlapping scalar byte constraint (the haystack str-var
and the per-byte scalar var are linked so the models agree).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The pipe_msg_type enum slot 3 was renamed from fsize_type to
add_constraint_type in the thoroupy backend import, but symsan.cpp
still referenced the old name, breaking the AFLPP mutator build.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ChengyuSong ChengyuSong merged commit 7915086 into main Jul 1, 2026
1 check passed
@ChengyuSong ChengyuSong deleted the ucsan branch July 1, 2026 19:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant