Repository navigation
v0.5.50
cfusa v0.5.50
Renumbered from v0.5.49: the tag/release v0.5.49 had already been
published (2026-07-29, PR #93) against a commit whose version.h still
read "0.5.48" — a pre-existing off-by-one in that release, unrelated to
this change. To avoid re-using an already-shipped version string, this
release is v0.5.50 instead.
External third-party audit remediation. Two of the findings are
live-exploitable command/argument-injection vulnerabilities, independently
reproduced against the shipped tool before being fixed here; the rest are
correctness/robustness defects in the ASIL derivation table, its test
coverage, and several check/trace/report code paths.
Security
impactgit-ref argument injection.cmd_impact.c's--from/--to
validator accepted a value beginning with-and built the underlying
git diffinvocation with no--separator between refs and paths. A
crafted--fromvalue could smuggle an arbitrarygit diffflag —
reproduced concretely withcfusa impact --from '--output=victim.txt' --to HEAD, which truncatesvictim.txtvia git's own--outputflag. Refs
beginning with-are now rejected, and a--separator is always
inserted before the ref arguments. While hardening this path,run_git_diff()
was also moved offpopen()/a shell command string ontofork+execvp
(argv passed directly, no shell), removing the shell entirely rather than
just refusing to abuse it.audit-packshell command injection.cmd_audit_pack.cinterpolated
unsanitized--output/--dirvalues into a double-quoted
system("cd ... && zip ...")string;$(...)command substitution is
still expanded inside double quotes by the shell, so a crafted--output
executed arbitrary commands — reproduced concretely withcfusa audit-pack --output 'x.zip$(touch /tmp/pwned)'. Thesystem()/zip/rm -rfshell
pipeline has been replaced withfork/execvp(argv passed directly, no
shell interpretation) and a POSIXnftw()-based recursive remove for
staging cleanup.
Fixed
- ISO 26262-3:2018 Table 4 ASIL derivation corrected (Critical). The
sharedcfusa_compute_asil()table (src/asil.c) over-assigned ASIL in 19
of 36 S×E×C cells (all S2 except the E1 row, and every S3 row). It now
implements the additive S+E+C mapping (≤6 → QM, 7 → A, 8 → B, 9 → C, 10 →
D).tests/test_asil_table.c's "exhaustive" 36-cell test previously only
assertedexit == 0and never the returned ASIL string, so it passed
against the wrong table; it now asserts the exact ASIL for every cell. The
dogfooded.fusa-hara.jsonshipped over-classified hazard ASILs (H-001
through H-005) as a direct consequence of the table bug and has been
regenerated to match. NOTE: the v0.5.47 entry below described the table as
making both call sites "provably consistent" — they were consistent with
each other but consistently wrong until this fix. - C0 controllability now maps to QM. A non-standard
C0("controllable
in general") value previously fell through to a non-QM result; per ISO
26262-3:2018 §4.3.5 it now short-circuitscfusa_compute_asil()to QM
regardless of S/E. - Out-of-range S/E/C now rejected.
harapreviously coerced an
out-of-range severity/exposure/controllability value to QM silently; it
now exits 2 with a diagnostic instead of masking a malformed hazard entry. - Duplicate requirement ids now fail
check. A duplicateidin
.fusa-reqs.json/.cfusa-reqs.jsonwas previously only reported as a
cfusa trace: ERROR: ...line on stderr, never as a machine-readable
Finding, and never affected any command's exit code. A newcheck
engine rule,DUPREQ001, re-parses the requirements registry and emits a
real, fingerprinted §4 Finding (SEV_ERROR) for each duplicated id, so
cfusa checknow fails on it. tracereads the canonicalparentkey.cmd_trace.cread only the
legacyparentIdfield for LLR→HLR links; it now reads the spec-canonical
parentkey first, falling back toparentIdfor backward compatibility.- Report envelope no longer hardcodes an always-empty
errorsarray.
src/report.cemitted a permanently-empty"errors": []array in every
report; per the x-FuSa spec this MUST be a singularerror{code,message}
object present only when a runtime error occurred (and omitted
otherwise), which is now what's emitted. ftell()return value now bounds-checked.cfusa_read_file()
(src/utils.c) used an uncheckedftell()result as an allocation size;
on a crafted or unseekable file this could wrap to a huge or negative
value and cause a heap-overflow/DoS. The result is now validated before
use.- Requirement objects over 1KB no longer truncated.
cmd_trace.c
parsed each requirement object into a fixed 1024-byte stack buffer,
silently droppingid/title/parentfields past that size; it now
heap-allocates to the exact object length. .fusa.jsonversion string reconciled.project.versionhad drifted
to a stale"0.5.1"while the shipped tool moved well past it; it now
matches the real released version. (The README's config-name guidance was
also updated to point at the canonical.fusa-*names rather than the
deprecated.cfusa-*ones, with the legacy names kept as a documented
fallback; a version badge was added to README so the new
version-consistency CI check has something real to verify against.)cfusa checknow passes cleanly on c-FuSa's own source (previously
masked by|| truein CI — see Changed, below — so this had silently
regressed):cmd_qualify.c's pre-existingqt_rmdir_recursive()and the
newcmd_audit_pack.cap_rmdir_recursive()both used genuine user-code
recursion (MISRA-C 2012 Rule 17.2,CFUSA-L004); both are now iterative,
built on POSIXnftw(FTW_DEPTH|FTW_PHYS).cmd_comp.chad two lines each
freeing two distinct pointers, which a same-line text scan mistook for a
double-free (CFUSA-CY007); the frees are now on separate lines. Two test
function names —..._includes_end_lineand..._no_build_system—
coincidentally contained the substringsdes_andsystem(, false-firing
the weak-crypto and unchecked-system-call rules (CFUSA-CY009/CY003);
both were renamed.cmd_req.c's ALM-import entry builder hardened against unbounded
write.append_entry()(used byreq importfor CSV/ReqIF/XML sources)
tracked the destination buffer's fill level via a caller-passed running
total rather than the buffer's actual content length, and appended with
strcat(). GitHub Advanced Security's CodeQL flagged this as a possible
unbounded write fromfgets/fread-sourced input (critical). It's
rewritten to measure the buffer's real length directly and append with an
exact-lengthmemcpy()bounded against that, removing the dependency on
the caller's bookkeeping entirely.
Changed
qualify's qualification timestamp now honoursSOURCE_DATE_EPOCHfor
reproducible builds.- CI (
ci.yml) no longer masks 5 meaningful steps behind|| true,
including the version-consistency check, which now actually fails the
build on drift. Theiso26262gap-report step is intentionally left
informational (|| true): it exits 1 whenever any §9.3 gap remains by
design, and closing every long-standing documentation/process gap (e.g.
"functional safety concept", "no multiple exit points") is a separate,
much larger effort than this release's scope. The Docker self-check step
now checks the whole mounted repo (--dir /workspace) instead of just
/workspace/src, matching the native self-check step —src/alone can
never contain the root-level.fusa.json/.fusa-hara.json, so scoping to
it madeFUSA00x/HARA001fail unconditionally the moment this step's
exit code started being enforced. docker-publish.yml's runner is pinned toubuntu-22.04for
build-environment parity/reproducibility.
Removed
- The stale committed
.cfusa_qualification.json(wrong version, obsolete
schema) has been deleted; CI regenerates it as a build artifact instead of
it being tracked in source control.
Installation
# Linux (amd64)
curl -Lo cfusa https://github.com/SoundMatt/c-FuSa/releases/download/v0.5.50/cfusa-linux-amd64
chmod +x cfusa && sudo mv cfusa /usr/local/bin/
# macOS (arm64)
curl -Lo cfusa https://github.com/SoundMatt/c-FuSa/releases/download/v0.5.50/cfusa-darwin-arm64
chmod +x cfusa && sudo mv cfusa /usr/local/bin/
# From source
cmake -B build -DCMAKE_BUILD_TYPE=Release && cmake --install buildVerify
cfusa version
cfusa qualifyWhat's Changed
- fix: v0.5.49 — ASIL Table 4 correction + two live-exploitable injection fixes by @SoundMatt in #95
- chore: renumber v0.5.49 -> v0.5.50 (version string collision) by @SoundMatt in #96
Full Changelog: v0.5.49...v0.5.50