Commit graph

29 commits

Author SHA1 Message Date
trvon
fcf474e4f0
refactor(cli): one TU path normalizer for coverage, fan-out and quarantine
Some checks failed
build / build-linux (pull_request) Failing after 7m32s
build / build-macos (pull_request) Has been cancelled
build / release (pull_request) Has been cancelled
Problem: normalizeClangTuPath, normalizeQuarantinePath and fan-out's
normalizePath were three copies of the same absolute + weakly_canonical
routine. Coverage, worker records and the quarantine file must agree on a
TU's spelling, and three copies can drift apart.

Fix: move the routine to fold/cli/tu_path.hpp as normalizeTuPath and use it
at every site. No behaviour change.

Test: existing test_scan_coverage (path spellings), test_tu_quarantine and
test_subprocess_fanout, updated to the new name. Full suite: 140/140.
2026-09-24 21:46:12 -06:00
trvon
86b2f14a43
feat(analysis): harden project-wide scans
- persist TU-owned graphs and guarded configuration evidence
- add closed-world production reachability classification
- calibrate detector precision and reviewed self-scan policy
- terminate poisoned workers instead of longjmp recovery
2026-07-30 18:18:04 -04:00
trvon
bcdcb3036b
feat(analysis): harden quality and solver pipelines 2026-07-29 20:46:51 -04:00
trvon
b0b51f1e71
feat: converge quality and scan interfaces
- fail closed on MCP input and compile database policy
- add shared scan catalogs, finding queries, and tiered gates
2026-07-29 00:57:33 -04:00
trvon
7032334b25
perf(detect): accelerate clone scoring 2026-07-23 14:54:38 -04:00
trvon
2dcbdbc75d
feat: harden Fold quality guard
Preserve source, binary, and runtime evidence boundaries while making precision and performance regressions reproducible.

- decompose analysis orchestration into bounded modules
- add differential, metamorphic, organic, and Yams change-time gates
- fix detector, P-Code, fuzz sanitizer, and report identity precision
2026-07-13 15:05:35 -04:00
trvon
661157e648
feat(cli): add scan scopes + complexity detector 2026-06-22 09:09:43 -04:00
trvon
1914d8284e
feat: many improvements and bug fixes 2026-06-12 13:25:11 -04:00
trvon
50230c809b
checkpoint 2026-06-04 20:20:29 -04:00
trvon
374aa7475d
many good updates for binary and SAST / DAST scanning 2026-06-02 12:39:03 -04:00
trvon
c10b99961f
fix(binary): bound r2 security scans
- add r2 CLI timeouts and cap structural sweeps
- fix SQL return-source taint and base64 blob filtering
- document vendor lanes and Ghidra .sla runtime policy
2026-05-31 22:49:56 -04:00
trvon
d742a49d28
build: finish dead-encoder removal refs + meson build fixes
Completes the binary_bb_encoder deletion (3fa6d0f56) whose scan_runner include
and src/symbolic/meson.build entry were not captured in that commit — HEAD
referenced the deleted source. Also records build-config changes already in use:
- third_party/ghidra-decompiler/meson.build: link-only zlib (cpp.find_library)
  to avoid the macOS SDK include path shadowing libc++ <cstdio>/<cstdlib>.
- meson.build/meson_options.txt: reserve optional analysis-lane feature names
  (retdec/remill/souffle/phasar/crab) with vendor-manifest-only stubs; only
  ghidra is wired.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 21:22:20 -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
79bec72091
build(binary): meson wiring for vendored Ghidra decompiler (Phase 1)
Adds a -Dghidra feature option (default disabled while the encoder is under
construction; enable with -Dghidra=enabled) and third_party/ghidra-decompiler/
meson.build that:
  - builds libghidra_decompiler.a from the CORE+DECCORE+SLEIGH object sets
    (80 files; cpp_std=c++11, warnings/werror disabled — vendored third-party);
  - builds the ghidra_sleigh spec compiler and runs it per-arch to emit .sla
    into the build dir (single-file form; source tree stays clean, .sla are
    build artifacts);
  - builds tools/pcode_dump (custom LoadImage, no BFD).
fold_core does not link it yet (the lifter/encoder glue is Phase 2-3); enabling
ghidra only adds these targets, leaving the normal build untouched.

Validated end-to-end through meson: ninja builds the lib + .sla + pcode_dump,
and the meson-built pcode_dump lifts arm64 sym._div_by_zero_direct to
INT_SDIV(w8,w9) with w9=COPY(const 0) — identical to the hand-build.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 17:39:44 -04:00
trvon
4bc62d721a
fix: scan namespaced/nested functions; timeout robustness; null-deref drift
Enumeration: replace the hand-rolled top-level `tu->decls()` walks (TU
security prefilter + every interprocedural pre-pass/cross-TU primer in
div-by-zero/int-overflow/null-deref/oob) with a shared RAV-based
`fold::detect::forEachFunctionWithBody`, so functions nested in namespaces
and classes are analyzed. Fixes 0-findings on namespaced C++ (dbench CTF).

TU prefilter: extracted to fold/cli/tu_security_prefilter; flags unsigned
-/* wraps so int-overflow candidates in size arithmetic are not skipped.

Timeout robustness: cooperative per-TU walk deadline (WalkOptions) checked
in the walk/unrollLoop/CFG-block checkpoints and the BatchSafetyObserver
flush loop, plus a non-progress unroll fixpoint — slow TUs degrade with
partial findings instead of being SIGALRM-quarantined.

OOB precheck: treat `*(p+i)` and indirect callees as candidates.

null-deref drift fix: remove the unsound `priorFunctionTextProvesNonNull`
source-text heuristic (added in 331a68467) that treated a textual `p!=0`
before a deref as proof of non-null, silently suppressing real null-derefs
(CWE-476 F1 0.485 -> 0.583, recall 0.379 -> 0.517).

Tests: test_function_enumeration, test_tu_security_prefilter, namespaced
cross-TU int-overflow regression. Full suite green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 18:04:15 -04:00
trvon
3e89a18ccd
fix: wire llvm-ar into meson native file so tests can rebuild
- setup.sh creates build/llvm_ar.ini with ar=llvm-ar path
- meson configure passes llvm_ar.ini as additional native file
- Fixes 'llvm-ar: command not found' when meson test rebuilds
- Also find llvm-ar in meson.build for direct ninja invocations
2026-05-30 00:13:10 -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
156dfa4642
build: add -march=native for ARM64 NEON / CPU-specific optimization
- Enables NEON SIMD on Apple Silicon, AVX on x86_64
- Speeds up Z3 queries, AST walking, and encoding
- ccache available: export CC='ccache clang' CXX='ccache clang++'
2026-05-28 21:12:41 -04:00
trvon
3beaef2958
build: fold-rust now build_by_default + install via custom_target
- Replaced run_target with custom_target (build_by_default: true)
- Single ninja builds all 3: fold + fold-rust + fold-swift
- meson install copies all 3 to prefix/bin automatically
- No more separate ninja fold-rust step needed
2026-05-28 13:03:02 -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
7c54d5d8a1
feat(cli): recovery hints on top error sites (errorWithRecovery)
Phase-1 (C3) of the CLI design philosophy refactor. Addresses
audit item #5 from docs/cli-philosophy.md.

Today scan_router throws std::invalid_argument /
std::runtime_error on bad inputs (missing --binary, unknown mode,
no compile DB, etc.). runScan catches them and logs "scan failed:
<msg>" — the user sees what failed but not how to recover.

After this change, those throw sites use the new
fold::cli::UserError (subclass of std::runtime_error carrying a
vector of recovery hints). runScan's catch dispatches on
UserError first and formats via emitUserError, which prints:

  <message>
  Try:
    <hint 1>
    <hint 2>

All hints are exact, copy-pasteable command lines — no
placeholders. Applied at six throw sites: unsupported mode,
no compile DB, empty compile DB, rust mode without --paths,
binary mode without --binary, unsupported hybrid strategy.

New files:

- include/fold/cli/error_format.hpp: UserError exception type +
  emitErrorWithRecovery / emitUserError helpers.
- src/cli/error_format.cpp: implementation.

Wired:

- src/cli/scan_router.cpp: throws now carry hints.
- src/cli/main.cpp::runScan: catches UserError first.
- meson.build: error_format.cpp in fold_core_sources.

44/44 tests still green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 22:52:53 -04:00
trvon
c657adfeeb
feat(cli): validate --detectors at parse time via central registry
Phase-1 (C2) of the CLI design philosophy refactor. Addresses
audit item #4 from docs/cli-philosophy.md.

Today --detectors / --source-detectors / --binary-detectors all
accept any string and silently drop unknown names downstream.
After this change CLI11 rejects unknown names at parse time with
exit code 105 (ValidationError) and a full list of valid names.

New files:

- include/fold/cli/detector_registry.hpp: canonical names +
  validator + family helpers (sourceLeafDetectors,
  binaryLeafDetectors).
- src/cli/detector_registry.cpp: the table (single source of
  truth across CLI11, scan_router family expansion, and any
  future MCP-tool routing per principle 9).

Wiring:

- main.cpp: new appendValidatedDetectors() helper validates
  every name via the registry before pushing into ScanOptions.
  Throws CLI::ValidationError with the registry's "did you
  mean" message on rejection so the user sees the full valid
  set inline. Applied to all three detector flags.
- meson.build: registry compiled into fold-core (used by both
  the CLI executable and downstream tests).

44/44 tests still green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 22:48:54 -04:00
trvon
93eb1f09b2
feat(fuzz): M31 Phase 2 — closed-loop fuzz-run promotes to ConcreteWitness
End-to-end: fold scan → fuzz-prep emits Z3-derived seed → fuzz-run
compiles + libFuzzer + ASan → crash ingested → predicted Finding
promoted from Proved to ConcreteWitness in ~1s wall-clock.

P2.1 — Confidence::ConcreteWitness tier
* Strictly stronger than Proved: SMT-feasibility + observed runtime
  failure. confidenceRank() gives explicit numeric ordering so the
  enum integer values stay incidental.
* FindingKind::FuzzCrash for surprise crashes the SymEx side didn't
  predict.
* FuzzWitness evidence sub-struct (seed_path, asan_kind, asan_file,
  asan_line) + JSON round-trip.

P2.2 — ASan stderr parser
* include/fold/fuzz/asan_parser.{hpp,cpp} (~150 LOC)
* Parses the canonical SUMMARY tail line — stable across LLVM 15–22
  for both ASan (heap-buffer-overflow, SEGV, …) and UBSan
  (signed-integer-overflow, …)
* test_asan_parser.cpp: 6 cases / 27 assertions

P2.3 — FuzzRunner
* include/fold/fuzz/runner.{hpp,cpp}
* Synchronous: compile + posix_spawn + waitpid-with-deadline +
  artifact-scan. SIGTERM + 1s grace + SIGKILL on parent timeout.
* Per-source `-x c` / `-x c++` pinning so C targets keep their
  C linkage and the harness's extern "C" decl resolves.
* Drops -std= entirely — generated harnesses are pre-C++17.

P2.4 + P2.5 — fuzz-run CLI + crash ingestion
* src/cli/fuzz_run_runner.cpp: load findings.predicted.json, group
  by function, runFuzzCampaign per harness, replay each crash under
  ASan, parse SUMMARY, match (file, line) to predicted Finding.id.
* Matched crashes → augmented Finding with Confidence::ConcreteWitness
  + evidence.fuzz_witness. Unmatched → FindingKind::FuzzCrash.
* Subcommand: --prep-dir, --target-source (rep), --time-budget,
  --compiler, --report, --clang-flag.

P2.6 — integration test
* tests/integration/test_fuzz_run.cpp: probes for a clang++ that can
  link -fsanitize=fuzzer, builds the null-deref-behind-guard fixture,
  chains runFuzzPrep + runFuzzRun, asserts at least one
  concrete-witness Finding whose id matches a predicted Finding.id.
* WARN+SUCCEED when no libFuzzer-capable compiler is found so CI
  on stripped clang installs stays green.

Tests: 39 → 40 green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 15:21:05 -04:00
trvon
075dd85669
feat(fuzz): R3-oob + P1.7 — full witness coverage + e2e test
R3-oob: oob_access now hoists z3::model out of both solver branches
and passes it to recordSinkWitnessWithModel. Predicted seeds for
`int buf[N]; return buf[idx];` now contain Z3's chosen boundary-
violating idx (e.g. `04 00 00 00` for buf[4]) instead of zeros.

P1.7: tests/integration/test_fuzz_prep.cpp materializes a
null-deref-behind-`flag==0xAB`-guard fixture, drives runFuzzPrep
in-process, and asserts the witness seed contains the exact magic
byte (0xAB at offset 0) plus a well-formed harness (extern "C",
size-gate at total_bytes, leaky(arg0, arg1) call).

Moves fuzz_prep_runner.cpp from the fold executable target into
fold_core_sources so the integration test can link runFuzzPrep
directly.

Tests: 37 → 38 green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 14:39:17 -04:00
trvon
470ed6f8b4
feat(fuzz): M31 — fuzz-prep emits Z3-derived harnesses + corpora
Build the static-analysis-driven fuzzing pipeline alongside the radare2
+ z3 work. fold's symbolic engine already proves sink feasibility; this
adds the path that retains the Z3 model, lowers it to libFuzzer
harnesses + seed corpora, and writes a findings.predicted.json bridge
so a future fold fuzz-run can promote crashes to ConcreteWitness
confidence.

* fold::fuzz value types (PathWitness, ParamWitness) + WitnessRecorder
  collector — process-local, lock-free hot-path check
* model_to_bytes — z3::model → little-endian std::byte at source width;
  pointer null-vs-nullable disambiguation
* harness_emitter — generates .harness.cc + .schema.json; honors
  is_c_linkage via `extern "C"` wrapper for C targets; nested-namespace
  emission for C++
* corpus_writer — packs witness bytes per harness layout, FNV-1a hash
  for filename, 3 canonical seeds (zeros / 0xFF / single byte)
* param_schema — schemaForFunction + recordSinkWitness +
  recordSinkWitnessWithModel; latter looks up each parameter's z3::expr
  via env->tryGet() and mines the model for byte payloads
* SafetyObserver: const SymbolicEnv* env added to onPointerDeref,
  onDivision, onTruncation, onSignedUnsignedConversion,
  onArraySubscript; encoder.cpp passes &env at every call site;
  CompositeSafetyObserver in scan_runner fans through
* null_deref, div_by_zero, int_overflow, int_truncation,
  sign_conversion now record witnesses with full model bytes;
  oob_access records schema-only (sat sites don't capture a model yet)
* fold fuzz-prep subcommand — runs scan in-process (workers can't
  ship recorder state), drains the recorder, emits one
  {harness,schema,corpus/} bundle per function

Smoke validation: 4 detectors on a 5-line C fixture produce
non-partial witnesses whose bytes are the exact magic values that
trigger each sink (flag=0xAB for null-deref; x=0 for div-by-zero;
a+b that overflows INT_MIN for int-overflow).

Tests: 34 → 37 (add test_model_to_bytes, test_harness_emitter,
test_corpus_writer). All green.

Also folds in a small worker-toolchain optimization that had been
sitting in the working tree: preloadHostToolchainEnv() avoids
repeated popen calls during worker init.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 14:21:43 -04:00
trvon
c4e42cec0a
Add scan router and MCP job queue
Split source and binary scan routing with merge-only hybrid support, binary provenance, and router-backed MCP scan contexts.

Add async MCP scan jobs for long-running scans, detector precision fixes, benchmark scripts, docs, and integration coverage.
2026-05-19 00:24:12 -04:00
trvon
879f60f52d
Add alloca/_alloca as known allocation function 2026-05-15 17:34:37 -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