Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 0 additions & 12 deletions .cfusa_qualification.json

This file was deleted.

20 changes: 10 additions & 10 deletions .fusa-hara.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"severity": "S3",
"exposure": "E4",
"controllability": "C2",
"asil": "ASIL-D"
"asil": "ASIL-C"
},
"safetyGoals": ["SG-001"]
},
Expand All @@ -43,7 +43,7 @@
"severity": "S2",
"exposure": "E4",
"controllability": "C3",
"asil": "ASIL-D"
"asil": "ASIL-C"
},
"safetyGoals": ["SG-002"]
},
Expand All @@ -56,7 +56,7 @@
"severity": "S3",
"exposure": "E2",
"controllability": "C1",
"asil": "ASIL-B"
"asil": "QM"
},
"safetyGoals": ["SG-003"]
},
Expand All @@ -69,7 +69,7 @@
"severity": "S2",
"exposure": "E3",
"controllability": "C2",
"asil": "ASIL-B"
"asil": "ASIL-A"
},
"safetyGoals": ["SG-004"]
},
Expand All @@ -82,7 +82,7 @@
"severity": "S3",
"exposure": "E3",
"controllability": "C2",
"asil": "ASIL-D"
"asil": "ASIL-B"
},
"safetyGoals": ["SG-005"]
}
Expand All @@ -92,39 +92,39 @@
"id": "SG-001",
"description": "cfusa shall not suppress or omit any finding for which the rule engine is configured to detect",
"hazards": ["H-001"],
"asil": "ASIL-D",
"asil": "ASIL-C",
"safeState": "Developer independently reviews code and uses additional static analysis tools",
"fssrRefs": ["REQ-FSR-CHECK001"]
},
{
"id": "SG-002",
"description": "cfusa shall limit its false-positive rate to a level that does not cause systematic check-disabling",
"hazards": ["H-002"],
"asil": "ASIL-D",
"asil": "ASIL-C",
"safeState": "Developer uses the disposition system to explicitly accept findings with rationale",
"fssrRefs": ["REQ-FSR-CHECK002"]
},
{
"id": "SG-003",
"description": "cfusa impact shall validate all git ref arguments before constructing shell commands",
"hazards": ["H-003"],
"asil": "ASIL-B",
"asil": "QM",
"safeState": "validate_git_ref() whitelist prevents injection; the tool exits 1 with an error message",
"fssrRefs": ["REQ-IMP001"]
},
{
"id": "SG-004",
"description": "cfusa release/qualify shall compute SHA-256 checksums deterministically and embed the correct project/version from .fusa.json",
"hazards": ["H-004"],
"asil": "ASIL-B",
"asil": "ASIL-A",
"safeState": "Reviewer independently verifies SBOM checksums against source",
"fssrRefs": ["REQ-REL002", "REQ-UTIL015"]
},
{
"id": "SG-005",
"description": "cfusa hara asil shall implement ISO 26262-3:2018 Table 4 correctly for all S/E/C combinations",
"hazards": ["H-005"],
"asil": "ASIL-D",
"asil": "ASIL-B",
"safeState": "Developer cross-checks the ASIL result with ISO 26262-3:2018 Table 4 directly",
"fssrRefs": ["REQ-HARA009"]
}
Expand Down
1 change: 1 addition & 0 deletions .fusa-reqs.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
{"id":"REQ-COUPLING003","title":"COUP003 warns when coupling-report.json absent","standard":"DO-178C","level":"DAL-A"},
{"id":"REQ-DISP001","title":"DISP001 warns on undispositioned ERROR findings","standard":"ISO 26262","level":"ASIL-B"},
{"id":"REQ-COMP001","title":"COMP001 detects cyclomatic complexity violations","standard":"DO-178C","level":"DAL-A"},
{"id":"REQ-DUPREQ001","title":"DUPREQ001 fails check on duplicate requirement ids in .fusa-reqs.json","standard":"ISO 26262","level":"ASIL-B"},
{"id":"REQ-RUNTIME001","title":"Watchdog provides kick-based timeout monitoring","standard":"IEC 61508","level":"SIL-4"},
{"id":"REQ-RUNTIME002","title":"Heartbeat provides periodic beat health checking","standard":"IEC 61508","level":"SIL-4"},
{"id":"REQ-RUNTIME003","title":"SafeState provides formal state machine transitions","standard":"ISO 26262","level":"ASIL-D"},
Expand Down
2 changes: 1 addition & 1 deletion .fusa.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"configVersion": "1.0",
"project": {"name": "c-FuSa", "version": "0.5.1"},
"project": {"name": "c-FuSa", "version": "0.5.49"},
"standard": "iso26262",
"strict": false,
"max_function_lines": 75,
Expand Down
26 changes: 19 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ jobs:

- name: Self-check (cfusa check on cfusa source)
run: |
./build/cfusa check --dir . --format json --output cfusa-self-check.json || true
./build/cfusa check --dir . --format json --output cfusa-self-check.json
./build/cfusa version

- name: Upload self-check report
Expand Down Expand Up @@ -124,7 +124,7 @@ jobs:

- name: Generate SARIF report
run: |
./build/cfusa check --dir . --format sarif --output results.sarif || true
./build/cfusa check --dir . --format sarif --output results.sarif

- name: Upload SARIF to GitHub Security tab
uses: github/codeql-action/upload-sarif@v3
Expand All @@ -147,8 +147,13 @@ jobs:

- name: Self-check via Docker
run: |
# Check the whole mounted repo (matching the native "Self-check"
# step's `--dir .`), not just src/ — src/ alone can never contain
# the root-level .fusa.json/.fusa-hara.json/.fusa-reqs.json, so
# scoping to it made FUSA00x/HARA001 fail unconditionally once
# this step's exit code started being enforced.
docker run --rm -v "$(pwd)":/workspace cfusa:ci check \
--dir /workspace/src --format json --output /workspace/cfusa-docker-check.json || true
--dir /workspace --format json --output /workspace/cfusa-docker-check.json

- name: Upload Docker self-check
uses: actions/upload-artifact@v4
Expand All @@ -175,23 +180,30 @@ jobs:
run: ./build/cfusa qualify --binary ./build/cfusa --format json --output .cfusa_qualification.json

- name: Run trace coverage check
run: ./build/cfusa trace --dir . --format json --output trace-report.json || true
run: ./build/cfusa trace --dir . --format json --output trace-report.json

- name: Run ISO 26262 gap report
run: |
# `iso26262` exits 1 whenever any §9.3 gap remains (by design — it's
# a strict evidence-completeness gate, not a check-engine Finding).
# Closing every long-standing documentation/process gap (e.g.
# "functional safety concept", "no multiple exit points") is a
# separate, much larger effort than this PR's security/ASIL-table
# remediation scope, so this step stays informational rather than
# build-breaking; see iso26262-gap-report.json for the current list.
./build/cfusa iso26262 --dir . --format json --output iso26262-gap-report.json || true

- name: Run HARA validation
run: |
./build/cfusa hara show --dir . || true
./build/cfusa hara show --dir .

- name: docs — version consistency check
run: |
# Verify README and CHANGELOG mention current version
VERSION=$(./build/cfusa version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)
echo "Checking version $VERSION in docs"
grep -q "$VERSION" CHANGELOG.md || echo "WARN: $VERSION not in CHANGELOG.md"
grep -q "$VERSION" README.md || echo "WARN: $VERSION not in README.md"
grep -q "$VERSION" CHANGELOG.md || { echo "ERROR: $VERSION not in CHANGELOG.md"; exit 1; }
grep -q "$VERSION" README.md || { echo "ERROR: $VERSION not in README.md"; exit 1; }

- name: Upload qualification record
uses: actions/upload-artifact@v4
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ on:
jobs:
push:
name: Build and push ghcr.io/soundmatt/c-fusa
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
permissions:
contents: read
packages: write
Expand Down
126 changes: 124 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,131 @@ All notable changes to c-FuSa are documented here.
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
and the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
## v0.5.49 — 2026-07-30

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
- **`impact` git-ref argument injection.** `cmd_impact.c`'s `--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` value could smuggle an arbitrary `git diff` flag —
reproduced concretely with `cfusa impact --from '--output=victim.txt' --to
HEAD`, which truncates `victim.txt` via git's own `--output` flag. 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 off `popen()`/a shell command string onto `fork`+`execvp`
(argv passed directly, no shell), removing the shell entirely rather than
just refusing to abuse it.
- **`audit-pack` shell command injection.** `cmd_audit_pack.c` interpolated
unsanitized `--output`/`--dir` values 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 with `cfusa audit-pack
--output 'x.zip$(touch /tmp/pwned)'`. The `system()`/`zip`/`rm -rf` shell
pipeline has been replaced with `fork`/`execvp` (argv passed directly, no
shell interpretation) and a POSIX `nftw()`-based recursive remove for
staging cleanup.

### Fixed
- **ISO 26262-3:2018 Table 4 ASIL derivation corrected (Critical).** The
shared `cfusa_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
asserted `exit == 0` and 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.json` shipped 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-circuits `cfusa_compute_asil()` to QM
regardless of S/E.
- **Out-of-range S/E/C now rejected.** `hara` previously 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 duplicate `id` in
`.fusa-reqs.json`/`.cfusa-reqs.json` was 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 new `check`
engine rule, `DUPREQ001`, re-parses the requirements registry and emits a
real, fingerprinted §4 Finding (SEV_ERROR) for each duplicated id, so
`cfusa check` now fails on it.
- **`trace` reads the canonical `parent` key.** `cmd_trace.c` read only the
legacy `parentId` field for LLR→HLR links; it now reads the spec-canonical
`parent` key first, falling back to `parentId` for backward compatibility.
- **Report envelope no longer hardcodes an always-empty `errors` array.**
`src/report.c` emitted a permanently-empty `"errors": []` array in every
report; per the x-FuSa spec this MUST be a singular `error{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 unchecked `ftell()` 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 dropping `id`/`title`/`parent` fields past that size; it now
heap-allocates to the exact object length.
- **`.fusa.json` version string reconciled.** `project.version` had 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 check` now passes cleanly on c-FuSa's own source** (previously
masked by `|| true` in CI — see Changed, below — so this had silently
regressed): `cmd_qualify.c`'s pre-existing `qt_rmdir_recursive()` and the
new `cmd_audit_pack.c` `ap_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)`. `cmd_comp.c` had 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_line` and `..._no_build_system` —
coincidentally contained the substrings `des_` and `system(`, 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 by `req import` for 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 from `fgets`/`fread`-sourced input (critical). It's
rewritten to measure the buffer's real length directly and append with an
exact-length `memcpy()` bounded against that, removing the dependency on
the caller's bookkeeping entirely.

- Bump `CFUSA_SCHEMA_VERSION`/`CFUSA_SPEC_VERSION` to x-FuSa spec v1.15.2 — v1.15.1 and v1.15.2 are both pure documentation clarifications (schemaVersion/specVersion format, §1.6.1 Rule A false-positive example) with no required behavior or wire-format changes.
### Changed
- `qualify`'s qualification timestamp now honours `SOURCE_DATE_EPOCH` for
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. The `iso26262` gap-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 made `FUSA00x`/`HARA001` fail unconditionally the moment this step's
exit code started being enforced.
- `docker-publish.yml`'s runner is pinned to `ubuntu-22.04` for
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.

## v0.5.47 — 2026-07-28

Expand Down
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.16)
project(cfusa
VERSION 0.5.48
VERSION 0.5.49
DESCRIPTION "C functional safety toolkit"
LANGUAGES C
)
Expand Down
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
[![CI](https://github.com/SoundMatt/c-FuSa/actions/workflows/ci.yml/badge.svg)](https://github.com/SoundMatt/c-FuSa/actions/workflows/ci.yml)
[![CodeQL](https://github.com/SoundMatt/c-FuSa/actions/workflows/codeql.yml/badge.svg)](https://github.com/SoundMatt/c-FuSa/actions/workflows/codeql.yml)
[![License: MPL-2.0](https://img.shields.io/badge/License-MPL_2.0-brightgreen.svg)](LICENSE)
[![Version](https://img.shields.io/badge/version-0.5.49-blue.svg)](CHANGELOG.md)

> **c-FuSa is not a certification product.** It is an engineering accelerator — helping you produce and maintain safety evidence faster.

Expand Down Expand Up @@ -91,7 +92,7 @@ cfusa fix --dir src/

| Command | Description |
|---|---|
| `init` | Initialise `.cfusa.json` project config |
| `init` | Initialise `.fusa.json` project config |
| `check` | Run all checks (exits 1 on errors; `--strict` on warnings too) |
| `lint` | MISRA-C:2012 / CERT-C coding standard rules |
| `analyze` | Static analysis — overflows, unchecked returns, pointer issues |
Expand All @@ -101,7 +102,7 @@ cfusa fix --dir src/
| `fmea` | Design FMEA from function signatures → `fmea.json` + `fmea.csv` |
| `report` | Compliance report (text/json/sarif/html/md) |
| `template` | Safety doc templates (HARA, PSAC, safety-plan, test-evidence) |
| `trace` | Requirements traceability matrix from `.cfusa-reqs.json` |
| `trace` | Requirements traceability matrix from `.fusa-reqs.json` |
| `req` | Show requirements and their impl/test source locations — `export`/`import` CSV/XML (DOORS/Polarion/Codebeamer/Jama) |
| `verify` | Collect and bundle test evidence |
| `release` | SBOM (SPDX-3.0.1 JSON), SLSA v0.2 provenance, artifact manifest |
Expand Down Expand Up @@ -169,7 +170,7 @@ cfusa_watchdog_check(&wd); /* check from scheduler tick */

## Requirements Traceability

Create a requirements registry at `.cfusa-reqs.json` (copy from `.cfusa-reqs.json.template`):
Create a requirements registry at `.fusa-reqs.json` (copy from `.cfusa-reqs.json.template`, the legacy `.cfusa-reqs.json` name is still read as a fallback):

```json
{
Expand Down Expand Up @@ -265,7 +266,7 @@ cfusa hara show --dir . # list hazards with ASIL ratings
cfusa hara asil --severity 3 --exposure 3 --controllability 2 # compute ASIL
```

ASIL is computed per ISO 26262-3:2018 Table 4 from severity (S1–S4), exposure (E1–E4), and controllability (C1–C3).
ASIL is computed per ISO 26262-3:2018 Table 4 from severity (S0–S3), exposure (E1–E4), and controllability (C0–C3), using the additive S+E+C derivation (C0 → QM). The worked example above (S3/E3/C2 = 3+3+2 = 8 points) yields ASIL-B.

---

Expand Down
Loading
Loading