Skip to content

fix: v0.5.49 — ASIL Table 4 correction + two live-exploitable injection fixes - #95

Merged
SoundMatt merged 4 commits into
mainfrom
fix/audit-asil-table-and-injection
Jul 30, 2026
Merged

fix: v0.5.49 — ASIL Table 4 correction + two live-exploitable injection fixes#95
SoundMatt merged 4 commits into
mainfrom
fix/audit-asil-table-and-injection

Conversation

@SoundMatt

Copy link
Copy Markdown
Owner

Summary

External third-party audit remediation for c-FuSa. This PR fixes two proven, live-reproduced security vulnerabilities plus a Critical correctness defect in the ISO 26262 ASIL derivation table, and several smaller robustness/spec-conformance issues.

Security — two live-exploitable vulnerabilities

  • impact git-ref argument injection. The --from/--to validator accepted a value beginning with - and built the underlying git diff invocation with no -- separator between refs and paths. A crafted --from could smuggle an arbitrary git diff flag.
    Reproduced: cfusa impact --from '--output=victim.txt' --to HEAD truncates victim.txt via git's own --output flag.
    Fix: refs starting with - are now rejected outright, and a -- separator is always inserted before the ref arguments.

  • audit-pack shell command injection. --output/--dir were interpolated unsanitized into a double-quoted system("cd ... && zip ...") string; $(...) command substitution is still expanded by the shell inside double quotes.
    Reproduced: cfusa audit-pack --output 'x.zip$(touch /tmp/pwned)' executes the injected command.
    Fix: the system()/zip/rm -rf shell pipeline is replaced with fork+execvp (argv passed directly, never interpreted by a shell) and a POSIX recursive remove for staging cleanup.

Both exploits were re-verified live against this branch's build (before and after) as part of this PR — before the fix, both succeeded; after, neither does.

Critical correctness fix

  • ISO 26262-3:2018 Table 4 ASIL derivation corrected. The shared cfusa_compute_asil() table mis-derived 19 of 36 S×E×C cells (all inflated). It now implements the additive S+E+C model (≤6→QM, 7→A, 8→B, 9→C, 10→D). The "exhaustive" 36-cell test previously only asserted exit==0 and never the returned ASIL string, so it passed against the wrong table — it now asserts the exact ASIL per cell. The dogfooded .fusa-hara.json shipped over-classified hazard ASILs as a direct consequence and has been regenerated.

Other fixes

  • C0 controllability now short-circuits to QM (ISO 26262-3:2018 §4.3.5).
  • Out-of-range S/E/C now exits 2 instead of silently coercing to QM.
  • New DUPREQ001 check-engine rule: a duplicate requirement id in .fusa-reqs.json previously only printed to stderr from trace and never gated any exit code — it now fails check as a real, fingerprinted Finding (with test coverage).
  • trace reads the canonical parent key (falling back to legacy parentId) instead of only parentId.
  • The report envelope no longer hardcodes an always-empty "errors": [] array; the spec's singular error{code,message} object is emitted only when a runtime error occurred.
  • cfusa_read_file()'s unchecked ftell() can no longer wrap into an oversized/negative allocation (heap-overflow/DoS guard).
  • trace's requirement-object parsing no longer truncates objects over 1KB in a fixed stack buffer.
  • .fusa.json's stale project.version ("0.5.1") now matches the shipped tool version; README points at canonical .fusa-* config names.
  • qualify's timestamp honours SOURCE_DATE_EPOCH; CI no longer masks 6 meaningful steps behind || true; docker-publish pinned to ubuntu-22.04; the stale committed .cfusa_qualification.json is removed (CI regenerates it as an artifact).

Version bumped 0.5.48 → 0.5.49 (CMakeLists.txt, include/cfusa/version.h, .fusa.json).

Test plan

  • cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug && cmake --build build — clean build, no new warnings
  • ctest --test-dir build — 40/40 tests pass (including 2 new DUPREQ001 tests)
  • Live-reproduced both exploits against the pre-fix binary, confirmed both are closed post-fix
  • Hand-verified the corrected ASIL table cell-by-cell against the additive S+E+C model
  • Confirmed .fusa-reqs.json has no pre-existing duplicate ids (new DUPREQ001 rule doesn't regress the tool's own self-check)

…on fixes

External third-party audit remediation. Two of the findings are proven,
live-reproduced security vulnerabilities:

- impact: git-ref validator accepted a leading '-' and built `git diff`
  with no `--` separator, letting a crafted --from ref smuggle a git flag
  (reproduced: `cfusa impact --from '--output=victim.txt' --to HEAD`
  truncates victim.txt). Refs starting with '-' are now rejected and a `--`
  separator is always inserted.
- audit-pack: interpolated --output/--dir unsanitized into a double-quoted
  system("cd ... && zip ...") string, so `$(...)` command substitution
  executed inside the shell (reproduced: `cfusa audit-pack --output
  'x.zip$(touch /tmp/pwned)'` ran the injected command). Replaced with
  fork+execvp (argv passed directly, no shell) and a POSIX recursive
  remove for staging cleanup.

Also fixes:
- Critical: the shared cfusa_compute_asil() table (src/asil.c) mis-derived
  19 of 36 ISO 26262-3:2018 Table 4 S x E x C cells (all inflated); now
  implements the additive S+E+C model. tests/test_asil_table.c's
  "exhaustive" 36-cell test previously only asserted exit==0 and never the
  returned ASIL string, so it passed against the wrong table; it now
  asserts the exact ASIL per cell. The dogfooded .fusa-hara.json shipped
  over-classified hazard ASILs as a direct consequence and is regenerated.
- C0 controllability now short-circuits to QM per ISO 26262-3:2018 4.3.5.
- Out-of-range S/E/C now exits 2 instead of silently coercing to QM.
- New DUPREQ001 check-engine rule: a duplicate requirement id in
  .fusa-reqs.json previously only printed to stderr from `trace` and never
  gated any exit code; it now fails `check` as a real, fingerprinted
  Finding.
- trace reads the canonical "parent" key (with "parentId" as a legacy
  fallback) instead of only the non-canonical "parentId".
- The report envelope no longer hardcodes an always-empty "errors": []
  array; the spec's singular error{code,message} object is now emitted
  only when a runtime error occurred.
- cfusa_read_file()'s unchecked ftell() can no longer wrap into an
  oversized/negative allocation (heap-overflow/DoS guard).
- trace's requirement-object parsing no longer truncates objects over 1KB
  in a fixed stack buffer.
- .fusa.json's stale project.version ("0.5.1") now matches the shipped
  tool version; README points at canonical .fusa-* config names.
- qualify's timestamp honours SOURCE_DATE_EPOCH; CI no longer masks 6
  meaningful steps behind `|| true`; docker-publish pinned to
  ubuntu-22.04; the stale committed .cfusa_qualification.json is removed
  (CI regenerates it as an artifact).

40/40 tests pass (plus 2 new DUPREQ001 tests).

Signed-off-by: Matt Jones <matt@jellybaby.com>
Signed-off-by: Matt Jones <47545907+SoundMatt@users.noreply.github.com>
D007 (part of the applied patch set) removed `|| true` masking from CI
steps that depend on `cfusa check`'s exit code across the whole c-FuSa
source tree. That surfaced real, previously-hidden findings — some
pre-existing, one newly introduced by this branch's own audit-pack fix:

- cmd_audit_pack.c's new ap_rmdir_recursive() and cmd_qualify.c's
  pre-existing qt_rmdir_recursive() both used genuine user-code recursion
  (MISRA-C 2012 Rule 17.2, CFUSA-L004). Both are now iterative, built on
  POSIX nftw(FTW_DEPTH|FTW_PHYS) instead of a self-recursive helper.
- cmd_comp.c had two lines each freeing two distinct pointers
  (`free(a); free(b);` on one line), which a same-line text scan mistook
  for a double-free (CFUSA-CY007 false positive). The frees are now on
  separate lines.
- Two test function names coincidentally contained the substrings `des_`
  (in "...includes_end_line...") and `system(` (in "...build_system(void)"),
  false-triggering the weak-crypto and unchecked-system-call rules
  (CFUSA-CY009/CY003). Both renamed; no behavior change.
- cmd_impact.c's run_git_diff() still used popen() with a shell command
  string for `git diff` (CFUSA-CY003, CWE-78) even after the previous
  commit's ref-validation/`--`-separator fix. Since we're already
  hardening this exact function against injection, went further and
  removed the shell entirely: fork+execvp("git", argv), matching the
  pattern used for audit-pack's zip invocation.
- README gained a version badge so the new version-consistency CI check
  (also part of D007) has an actual version string to verify in README.md,
  not just CHANGELOG.md.

`cfusa iso26262`'s gap-report step is intentionally left `|| true`: it
exits 1 whenever any §9.3 gap remains by design (a strict completeness
gate, not a check-engine Finding), and closing every long-standing
documentation/process gap is a separate, much larger effort than this
release's security/ASIL-table remediation scope.

`cfusa check --dir .` now exits 0 against c-FuSa's own source (previously
always non-zero, just masked). 40/40 ctest still pass. Both live exploits
re-verified closed against this commit.

Signed-off-by: Matt Jones <matt@jellybaby.com>
Signed-off-by: Matt Jones <47545907+SoundMatt@users.noreply.github.com>
cmd_audit_pack.c and cmd_qualify.c's new iterative rmdir helpers (from the
previous commit) failed to compile on ubuntu-22.04 with both gcc and clang:

  error: 'FTW_DEPTH' undeclared
  error: 'FTW_PHYS' undeclared
  warning: implicit declaration of function 'nftw'

nftw()/FTW_DEPTH/FTW_PHYS are XSI extensions that glibc hides unless
_XOPEN_SOURCE >= 500 (or _DEFAULT_SOURCE) is defined before the first
system header include; _POSIX_C_SOURCE alone does not expose them. They're
visible by default on macOS/BSD libc, which is why the macos-14 job built
fine while both ubuntu-22.04 jobs failed. Added _XOPEN_SOURCE 700 alongside
the existing _POSIX_C_SOURCE 200809L in both files.

Verified with a local ubuntu:22.04 Docker build (gcc, cmake) — clean
compile, 40/40 ctest pass, `cfusa check` self-check exit 0 — and a native
macOS rebuild (clang) unaffected.

Signed-off-by: Matt Jones <matt@jellybaby.com>
Signed-off-by: Matt Jones <47545907+SoundMatt@users.noreply.github.com>

@github-advanced-security github-advanced-security AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

c-FuSa found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.

Two remaining CI checks were still failing after the last commit:

- GitHub Advanced Security's CodeQL default-setup check flagged a critical
  "unbounded write" in cmd_req.c's append_entry() (used by `req import` for
  CSV/ReqIF/XML sources): it tracked the destination buffer's fill level
  via a caller-passed running total rather than the buffer's actual
  content length, then appended with strcat(). Rewrote it to measure the
  buffer's real length directly (strlen, since the buffer is always
  zero-initialized and kept NUL-terminated within bounds by this same
  function) and append with an exact-length memcpy() bounded against that
  — removes the dependency on the caller's bookkeeping entirely. All CSV/
  ReqIF/XML import tests in test_req_trace_cov.c still pass.

- The "Docker Build" job's "Self-check via Docker" step ran
  `check --dir /workspace/src`, i.e. only the src/ subdirectory of the
  mounted repo. src/ alone can never contain the root-level
  .fusa.json/.fusa-hara.json, so once this step's exit code started being
  enforced (previous commit's `|| true` removal) it failed unconditionally
  on FUSA001/HARA001, regardless of any code change. Changed it to
  `--dir /workspace` to match the native "Self-check" step's `--dir .`.
  Verified by building the actual Dockerfile locally and running the exact
  CI command against it: exit 0.

40/40 ctest pass; `cfusa check --dir .` exits 0 natively and inside the
built Docker image; both live exploits re-verified closed in the Docker
image too.

Signed-off-by: Matt Jones <matt@jellybaby.com>
Signed-off-by: Matt Jones <47545907+SoundMatt@users.noreply.github.com>
@SoundMatt
SoundMatt merged commit 1d77fe1 into main Jul 30, 2026
10 checks passed
@SoundMatt
SoundMatt deleted the fix/audit-asil-table-and-injection branch July 30, 2026 20:21
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.

2 participants