Commit graph

35 commits

Author SHA1 Message Date
trvon
b33d3357b5
fix(rust): refine path join traversal provenance
Some checks failed
build / build-linux (push) Failing after 7m51s
build / build-macos (push) Has been cancelled
build / release (push) Has been cancelled
2026-09-02 05:58:14 -06:00
trvon
bcdcb3036b
feat(analysis): harden quality and solver pipelines 2026-07-29 20:46:51 -04:00
trvon
f37222d4fb
fix(scan): harden frontend analysis 2026-07-28 11:57:38 -04:00
trvon
75e3e09cbf
fix(rust): dedupe cargo wrapper output 2026-07-28 00:38:56 -04:00
trvon
f0ece0794a
feat(eval): add manifest project runner 2026-07-27 23:18:03 -04:00
trvon
45b31c6b5a
feat(swift): harden SIL scanner 2026-07-20 14:52:50 -04:00
trvon
f749f44d92
feat(rust): improve security provenance 2026-07-20 14:52:42 -04:00
trvon
374aa7475d
many good updates for binary and SAST / DAST scanning 2026-06-02 12:39:03 -04:00
trvon
2ffa58c50f
feat: ROP chain synthesis attached to binary buffer-overflow findings (Part B)
Turn a detected overflow + gadget inventory into a candidate ROP chain
scaffold (auto target) attached to the finding.

- rop_chain (pure, fold::binary, unit-tested): synthesizeChain auto-selects a
  target — a recovered "win" function (system/execve/puts/printf), else an
  execve("/bin/sh") syscall chain assembled from syscall + register-control
  gadgets (x0/x1/x2/x8 on AArch64, rdi/rsi/rdx/rax on x86-64). Emits ordered
  pad/gadget/value/target steps + explicit caveats (PIE/ASLR base, canary, and
  that padding_len is the buffer capacity — the exact saved-return-address
  offset is target-frame-dependent and must be verified).
- ghidra_lifter: expose each pointer arg's recovered stack-frame offset
  (in_buffer_offsets) alongside its size; signed-offset helper. (Exact RA-slot
  recovery is left for future work — the saved return address for these
  overflows lives in the caller's frame, which a single-function lift can't see;
  honest caveats cover this rather than emit a wrong padding.)
- r2_backend: findStringAddress ("/bin/sh" via r2 /j) to seed execve chains.
- report: ExploitContext gains an optional RopChainInfo (padding_len, target,
  steps, caveats); serialized in JSON (write + read round-trip) and SARIF.
- binary_pcode_scan: attachExploitPrimitives now also synthesizes a chain per
  buffer-overflow finding from the gadget inventory + recovered capacity.

Validated: baby_bof's decode_base64 overflow carries a chain (target win:printf,
padding 256, steps + caveats). Unit: test_rop_chain (17 assertions). Corpus BOF
counts unchanged; meson test 77/77.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 09:34:37 -04:00
trvon
5b5fd7dbc6
feat: binary stack-buffer-overflow detection via SSA P-Code
Phase 5: detect stack buffer overflows in binaries through the P-Code
pipeline, value-aware against recovered buffer capacities.

- ir::pcode::PcodeOp gains in_buffer_sizes (parallel to in): per-pointer-arg
  recovered stack-buffer capacity, SSA-only (-1 = unknown).
- ghidra_lifter: inject locked prototypes for known copy/input sinks at their
  call-target addresses BEFORE the SSA action pipeline, so parameter recovery
  attaches the destination pointer; resolveStackBufferBytes traces the
  PTRSUB/PTRADD/INT_ADD spacebase+const def chain to a stack address and reads
  the recovered local's type size via getScopeLocal()->findOverlap().
- pcode_encoder: recognize copy/input sinks (unbounded: gets/strcpy/strcat/
  sprintf/...; bounded: fgets/read/memcpy/strncpy/snprintf/...) and fire
  onBufferCopy with the write length when the dst capacity was recovered.
- BinaryBBObserver::onBufferCopy: ugt(size, cap) bounds check -> OOBAccess
  finding (binary-buffer-overflow). Unbounded sinks -> Heuristic; bounded with
  provable len>cap -> Proved (checkAndEmit gains a confidence parameter).
- router: SSA rewrites CALL targets to synthesized addresses, so resolve callee
  names from the raw stream by target address and carry them to the SSA stream
  by call-site instruction address (annotateByTarget / annotateBySite).
- r2_backend: extractCallTargetNames maps stub/func addresses -> normalized
  callee names; scan_runner activates the lane for oob/oob-access detectors.

Validated on bench/corpora/ctf-challenges: 3 TPs in libbof_overflow
(gets->32B, strcpy->64B, sprintf->128B), genuine TPs in libcmd_injection
(sprintf->256B) and libvuln_demo (strcpy->16B), zero false positives across
the corpus; safe_version and the bounded __strncpy_chk path are not flagged.
Unit tests cover unbounded/bounded/safe/no-size/non-sink cases.
meson test: 74/74. div-by-zero and null-deref P-Code findings unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 00:09:07 -04:00
trvon
3338dad392
feat(binary): whole-binary P-Code scan via r2 enumeration (Phase 4, part 1)
Drives the dual-stream router over a real binary end-to-end.

- r2_backend: detectBinaryArch() (iIj) + extractFunctionBytes() (aflj + p8,
  skipping import thunks) — r2 supplies arch + per-function bytes; the Ghidra
  lifter consumes them.
- binary_pcode_scan.cpp (fold-ghidra lib): scanBinaryWithPcode() maps the
  r2 arch to a vendored SLEIGH spec (arm64-LE -> AARCH64:LE:64:v8A today;
  unknown arch -> {} so the caller falls back to the r2-disasm passes),
  enumerates functions, routes each through both lift streams, and merges +
  dedups across functions. max_functions caps the sweep (robustness).
- meson: -DFOLD_GHIDRA_SPECS_DIR baked to the vendored specs path so the lifter
  locates .ldefs/.sla at runtime; binary_pcode_scan added to fold-ghidra; the
  pcode_scan driver gains a whole-binary `pcode_scan <binary>` mode.

Validated on the CTF corpus: libdiv_by_zero.dylib -> exactly 2 div-by-zero
(div_by_zero_direct + mod_by_zero, correctly attributed; div_by_zero_variable
abstains pending path-forking), and 0 on the other 9 dylibs. This reaches
parity with the shipped slice-4 div-by-zero TPs through the proper Z3/P-Code
pipeline. Next (Phase 4 part 2): wire into scan_runner additively (dedup the
div-by-zero overlap with the structural passes), then the encoder increments
(path-forking, null-deref, overflow) for the remaining corpus gains.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 20:10:02 -04:00
trvon
1681acbee6
feat(binary): dual-stream P-Code router + first end-to-end finding (Phase 3.5)
Wires the whole pipeline together: binary bytes -> raw+SSA lift -> P-Code
encoder -> Z3 -> BinaryBBObserver -> merged finding.

- include/fold/binary/pcode_router.{hpp} + src/binary/pcode_router.cpp:
  routeFunctionToFindings() lifts a function both ways (liftRaw + liftSSA),
  encodes each into a fresh Z3 context/observer, tags findings with the lift
  level (evidence.backend = binary-pcode-raw|ssa), and merges + dedups by
  (kind, line, detector) — raw wins on collision (more direct hazard evidence).
- meson: gated `fold-ghidra` static lib (ghidra_lifter + pcode_router) linking
  fold_core_dep + ghidra_decompiler_dep (first fold_core <-> Ghidra link), and
  the `pcode_scan` smoke driver. Defined after fold_core so both deps are in
  scope; built only when -Dghidra=enabled.
- pcode_encoder fix: value map now keys on (space, offset) without size and
  coerces on read, so a sub-register divisor read (w9, size 4) sees the
  full-register zero write (x9, size 8) the lifter emits for `mov w9,#0`
  (ARM64 zero-extend). New unit test locks this aliasing.

Validated end-to-end: pcode_scan on arm64 sym._div_by_zero_direct emits exactly
one finding — `[binary-pcode-raw] division by zero proven by Z3` — with the SSA
stream correctly contributing nothing (it folds x/0 -> 0) and the router
merging. Full suite 71 Ok / 0 Fail / 1 pre-existing binary_backend timeout.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 20:02:03 -04:00
trvon
bd1f289f06
feat(binary): dual-mode Ghidra lifter (raw + SSA) -> fold::ir::pcode (Phase 2)
Phase 2 of the Ghidra P-Code binary frontend. Adds the frontend-agnostic
P-Code IR and a dual-mode lifter, validated end-to-end.

- include/fold/ir/pcode.hpp: the ~25-op core P-Code IR (Op/Space/Varnode/
  PcodeOp/Function), no Ghidra/clang types (M30 invariant). Function carries a
  Source tag (Raw|SSA) so findings record provenance.
- include/fold/binary/ghidra_lifter.hpp + src/binary/ghidra_lifter.cpp
  (Ghidra types confined): a buffer LoadImage (no BFD) + two entry points
  producing the same ir::pcode::Function:
    * liftRaw  — Sleigh::oneInstruction, per-instruction RAW P-Code;
    * liftSSA  — SleighArchitecture + Funcdata + action pipeline, SSA high
      P-Code (needs CapabilityPoint::initializeAll + whole-archive link).
  Both serialized under one mutex (decompiler global state).
- meson: libghidra_decompiler gains the arch/loader infra (sleigh_arch,
  inject_sleigh, loadimage_xml, xml_arch, raw_arch); dep switched to
  link_whole so capability self-registration constructors run; pcode_lift
  smoke driver added (built at fold's std, links the c++11 lib).

Empirical validation on arm64 sym._div_by_zero_direct confirms the dual-path
rationale: RAW preserves `(w9)=COPY const 0` + `INT_SDIV(w8,w9)` (the hazard),
while SSA folds it to `COPY const 0` (ARM64 sdiv-by-0 returns 0; Ghidra's
optimizer constant-folds x/0 -> 0). The two levels are complementary streams
into the Z3 core (next phases): raw for div/overflow/null-deref, SSA for
UAF/dataflow.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 19:39:21 -04:00
trvon
5152d0c9f7
feat(binary): vendor Ghidra 12.1 decompiler + validate P-Code lifting (Phase 0-1)
Foundation for replacing fold's dead/abstract binary encoder with a real
SSA P-Code frontend. Verified root-cause gap: serializeBinaryIR() emits only
data-discovery nodes (sections/strings/imports/exports); the control-flow
BinaryExprKind values (BasicBlock/CallSite/...) are never constructed, so the
Z3-BB encoder's encodable filter is always empty and encodeBinaryBBs never
runs — hence 0 binary Z3 findings. The P-Code pipeline supersedes that path.

Phase 0 — docs/internal/ghidra-pcode-integration-research.md: P-Code opcode
core, the decompiler C++ embedding API (LoadImage/SleighArchitecture/Funcdata),
the CORE+DECCORE+SLEIGH source sets, no-bison finding, and pypcode as build
reference.

Phase 1 — vendored Ghidra decompiler at pinned commit (Ghidra_12.1_build,
7e89d94e34...) under third_party/ghidra-decompiler/ (cpp + AArch64 specs +
Apache-2.0 LICENSE + VENDOR.md + .gitignore for build artifacts). Validated by
hand on this clang-21/macOS toolchain:
  - full SSA engine (CORE+DECCORE+SLEIGH, 80 files incl. heritage/funcdata/
    action) compiles clean, -std=c++11 -O2, NO BFD, NO bison (generated
    parsers are committed upstream; ruleparse isn't in the lib set);
  - sleigh compiler builds; AArch64 .slaspec -> .sla compiles;
  - tools/pcode_dump.cpp (custom LoadImage, no BFD) lifts the real arm64
    sym._div_by_zero_direct to correct P-Code: `mov w9,#0 -> COPY(const 0)`,
    `sdiv -> INT_SDIV(w8,w9)` — divisor zero-ness explicit in the data flow,
    so the Phase-3 encoder proves div-by-zero trivially, multi-arch.

Not yet meson-wired (next step) and no fold-side IR/encoder yet (Phase 2-3);
this commit is the validated vendor + smoke tool.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 17:32:47 -04:00
trvon
d2755e4d79
build: update nightly toolchain pin to 2026-05-28 (rustc 1.98.0)
- fold-rust builds with nightly rustc_private feature
- cargo-check now requires RUSTUP_TOOLCHAIN=nightly for target crates
- Ripgrep scan: 49 findings (42 path-traversal, 7 command-injection)
  with --suppress-local-paths taint filtering enabled
2026-05-29 09:48:22 -04:00
trvon
5d8b288719
feat: Rust interprocedural symbolic summary caching (Phase 2, Rust-CLang parity)
- symbolic_summary.rs: MIR-based summary computation per function
- Produces SymbolicSummaryData with SMT-LIB BV-64 expressions
- Matches C++ fold::symbolic::SymbolicSummary wire format
- Cross-function Z3 proofs now possible
2026-05-28 20:41:29 -04:00
trvon
192387b406
feat: wire MIR control flow into Rust Z3 encoder (Phase 2, Rust-CLang parity)
- MIR-to-IR tree converter: 12 functions walk MIR basic blocks
- Each block becomes Call(mir_block) with statements + terminator
- SwitchInt terminators → path predicate forking per case
- Return/Rvalue encoding threaded through RustExprNode JSON
- Brings Rust CFG precision to parity with Clang security walker
2026-05-28 20:40:12 -04:00
trvon
10b9b81ef9
feat: known-safe path prefix recognition for Rust detectors
- KnownSafePaths set: codex_home, config_path, cargo_bin, cache dirs
- classify_origin returns LiteralValue for calls to known-safe functions
- Eliminates ~200 path-traversal FPs from config/cache file reading
2026-05-28 20:09:36 -04:00
trvon
74b10224b1
fix: filter ts_rs codegen from path-traversal (eliminates 45% FPs)
- Skip path-traversal in ts_rs/__inline functions (compile-time codegen)
- TS::inline serializes types to .ts files — not runtime path handling
- Expected: ~500/1102 findings eliminated
2026-05-28 12:48:22 -04:00
trvon
ee49900f01
fix: filter augment_args from command-injection (eliminates 69% FPs)
- Add current_fn_name tracking in DerefHunter HIR visitor
- Skip command-injection when inside augment_args* functions
- clap derive macros are CLI registration, not command execution
- Expected: 150/217 findings eliminated
2026-05-28 12:47:19 -04:00
trvon
b79f964a59
feat: intra-procedural taint origin tracking for Rust detectors
- TaintOrigin enum: LiteralValue, EnvironmentSource, FileSource, UnknownProvenance
- classify_origin(): walks HIR to classify expression provenance
- Path-traversal: skip when path arg is LiteralValue (string literal, const)
- Command-injection: skip when .arg()/.args() arg is LiteralValue
- Expected: 80%+ FP reduction — most code uses fixed paths and commands
2026-05-28 12:20:07 -04:00
trvon
f45bce34ac
fix: objc_msgSend — lang filter + _OBJC_CLASS_ verification + Swift IR kinds + vtable fuzzing
- discoverObjcClasses: filter icj to lang='objc', verify _OBJC_CLASS_ symbol exists
- Add 4 Swift IR node kinds: OptionalBind, ProtocolWitness, ExistentialContainer, SwitchEnum
- Vtable fuzzing: parse _ZTV symbols, emit vtable-dispatch harnesses
- Clone scoring: --clone-min-score gate with hash-distance metric
- BorrowTracker wired into Rust null-deref findings
2026-05-28 10:36:25 -04:00
trvon
6f07fa25cc
fix: filter Swift symbols from binary fuzz + bump budget to 120-600s
- Swift-mangled symbols (//_T0) skipped: need valid objects, not raw uint64_t
- Cov:2 dead end explained: garbage args cause Swift runtime abort before real code
- Fuzz budget floor raised 30→120s, ceiling 120→600s (match AFL++/libFuzzer norms)
- Base per-CPU allocation doubled 45→90s
2026-05-28 09:08:32 -04:00
trvon
e61821e5b7
feat: return-checking harnesses + Rust unsafe-audit command
- --check-returns flag adds ASan sentinel for pointer-returning functions
- unsafe-audit subcommand: detects SAFETY comment gaps in unsafe blocks
- fold-rust: null-deref adt-safety filter for Option/Result/Vec paths
2026-05-27 23:12:43 -04:00
trvon
ee3392931c
feat: param type inference + internal fuzz targets for binary harnesses
- --binary-param-types: uint64 (default) or infer (map r2 types)
- mapR2Type: converts afij type strings → C types with sized memcpy
- discoverInternalFuzzTargets: ObjC methods + Swift witnesses + vtable
- --fuzz-internal flag gates internal function fuzzing
2026-05-27 22:59:13 -04:00
trvon
b9381a6ab3
feat: adaptive fuzz budget + corpus seeding for binary harnesses
- HW detect: sysconf CPU/memory → compute per-harness budget (30-120s)
- --per-harness-timeout-seconds flag for override
- 3-5 seed files per harness: zeros, sentinels, ASCII patterns
- --corpus-seeds N flag to control seed count
2026-05-27 21:04:00 -04:00
trvon
a36801cf8e
perf: Z3 LRU cache, batch observers, fold-rust body cache, ripgrep benchmark
- Z3 query LRU cache (4096 entries, O(1) promote/evict)
- Batch observer dispatch in security walker
- fold-rust: single-walk hash, dead code removed, timing added
- bench/corpora/rust-oss/: ripgrep benchmark harness
2026-05-27 19:06:32 -04:00
trvon
56550a93a1
feat: symex-fuzz landscape, SWAN SIL parser, Juliet expansion, FP scoring
- docs/internal/symex-fuzz-landscape.md: 11 tools across 6 categories
- tools/fold-swift/swan_sil_parser: 75 SIL operator kinds, JSON converter
- bench/juliet/expansion/: 118-CWE catalog, gold generator, runner
- bench/score_fp.py: precision/recall/F1 per detector
2026-05-27 18:00:06 -04:00
trvon
9bdd989b7e
fix: binary harness_file, -I path resolution, caller-context schema
- Store harness_file in binary fuzz predictions to fix prep↔run mismatch
- Resolve relative -I flags against compile DB directory
- Add caller_unsafe/is_param_sourced fields to fold-rust NDJSON
- Allow empty target_sources in fuzz-run for dlopen harnesses
- Recursive IR encoder descent for nested hazards
2026-05-27 17:34:52 -04:00
trvon
ffc7917957
feat: symbolic IR, swift backend, binary-to-fuzz pipeline, SARIF fixes
- Rust IR encoder (encodeRustIr): walks HIR expr trees, fires SafetyObserver hooks
- Swift SIL frontend (fold-swift): swiftc -emit-sil parser, demangling, NDJSON output
- SwiftBackend: --mode swift CLI routing, shells out to fold-swift
- Binary-to-fuzz: Mach-O export discovery, ARM64 ABI param-count inference, dlopen harness generation
- Language-agnostic IR: json_keys canonical constants, Call/Member/ArraySubscript/Cast/HeapNew nodes
- Dogfood fixes: SARIF kind field, cross-detector dedup, harness_file in predictions, Swift stoi safety
- Corpuses: swift-sil-z3-corpus.md, binary-fuzz-corpus.md
- AGENTS.md: cross-language wire format rule
2026-05-27 15:29:14 -04:00
trvon
95aac8209c
feat: expand rust security scanner coverage 2026-05-27 11:32:05 -04:00
trvon
ce1c353847
feat: harden security scanner routing 2026-05-27 10:36:57 -04:00
trvon
da4c848cbc
feat(rust): MVP Rust frontend — unsafe-deref detection + clone clustering
First non-clang frontend for fold. Scans Rust source for the
subset of issues that meaningfully exist there: raw-pointer
dereferences in `unsafe` blocks (Heuristic confidence) and
within-Rust Type-1/2/3 clones via the existing clone detector.
Reuses the M30 frontend-agnostic IR + AnalysisBackend
abstraction unchanged.

Architecture: a separate `tools/fold-rust` cargo binary that the
C++ `RustBackend` shells out to. Two subprocess invocation
modes:

- `extract --unit <file>`: standalone .rs file, no Cargo
  context (limited; external-crate imports won't resolve).
- `cargo-check --crate-dir <dir>`: invokes `cargo check` with
  RUSTC_WORKSPACE_WRAPPER set to ourselves so cargo supplies
  the full --extern/-L/--cfg per-TU. Mirrors the
  clippy/miri/kani pattern.

RustBackend::discoverUnits walks up from each `.rs` input to
find the nearest Cargo.toml and collapses sibling files to one
crate unit; orphan files fall back to single-file extract.
scan_router auto-detects --mode=rust when all paths are `.rs`;
mixed Rust + C/C++ warns and defaults to source mode.

Includes:

- tools/fold-rust/: cargo crate pinned to nightly-2026-05-22.
  HIR-driven DerefHunter (raw-pointer dereference detection
  via typeck `is_raw_ptr()`) and HashWalker (FNV-1a + mix64
  port of src/ir/normalize.cpp for FunctionSummary hashes).
- include/fold/ir/expr_json.{hpp,cpp}: RustExprNode +
  RustNodeArena + bidirectional JSON wire format for
  ir::Expr; round-trip tested across 8 Kinds.
- include/fold/frontend/rust_backend.{hpp,cpp}: AnalysisBackend
  impl. posix_spawnp + NDJSON parser. Caches summaries +
  findings across extractFunctions calls; polymorphic
  runDetectorsOnShard dispatch routes summaries to
  detectClones.
- src/cli/{main,scan_router,scan_runner}.cpp: --mode=rust
  branch + autoDetectRust(). Mode validation extended to
  accept rust.
- docs/rust-frontend.md: scope, wire format, rustc-private
  pin + bump procedure, verification snippets.

44/44 unit + integration tests still green. Verified
end-to-end on a 3-function rename-pair fixture: 1 clone
cluster + 1 raw-deref finding through both `extract` and
`cargo-check` paths.

Out of scope (tracked separately): equiv + class-dup
detectors (need encoder frontend-agnostification),
dead-statement (needs Rust CFG), cross-language clones
(needs unified kind-tag taxonomy), Proved/ConcreteWitness
promotion (needs encoder integration), `cargo fuzz`
harnesses.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 22:36:28 -04:00
trvon
0452353d2e
Implement walkSwitch: analyze case bodies in switch statements
Results: +86 TP across all CWEs (476:+3, 369:+18, 190:+51, 121:+14)
2026-05-15 18:32:45 -04:00
trvon
3f1234f7bb
fold: bounded symbolic SAST for C++
Clang LibTooling + Z3 BV-64 encoding.
11 detectors: clone, equiv, class-dup, dead, null-deref,
div-by-zero, int-overflow, oob, int-truncation,
sign-conversion, taint-track.

Interfaces: CLI, MCP (JSON-RPC 2.0), LSP.
Output: SARIF 2.1.0, JSON, Markdown.
2026-05-14 23:14:54 -04:00