Skip to content

fix: v0.4.2 — RELAY ecosystem conformance fixes, pass 3 - #52

Merged
SoundMatt merged 1 commit into
mainfrom
fix/audit-conformance
Jul 31, 2026
Merged

fix: v0.4.2 — RELAY ecosystem conformance fixes, pass 3#52
SoundMatt merged 1 commit into
mainfrom
fix/audit-conformance

Conversation

@SoundMatt

Copy link
Copy Markdown
Owner

Summary

Gap-audit fix pass against LIN 2.x (LIN Consortium 2.1/2.2A) and RELAY spec, closing four confirmed findings:

cpp-LIN-01 (medium) — virtual::Bus::publish() emits enhanced checksum for diagnostic frames

Spec: LIN 2.2A §2.3.1.5 — "Frame identifiers 60 (0x3C) to 61 (0x3D) shall always use classic checksum" (verified verbatim against the official LIN Consortium 2.2A PDF). RELAY §15.3: diagnostic frames MUST use ClassicChecksum.

do_publish() now forces ChecksumType::Classic whenever id == kLINDiagRequestID || id == kLINDiagResponseID, regardless of what the caller requested. This brings the production publish path in line with validate_frame(), which already rejected an enhanced-checksum diagnostic Frame — the two were previously disagreeing.

Applied from the pre-made audit diff (diffs/cpp-LIN/cpp-LIN-01.diff), which applied cleanly.

cpp-LIN-02 (medium) — RELAY adapter discards checksum_type, upgrading bridged frames to enhanced

Spec: LIN 2.2A §2.3.1.5 (as above); RELAY §8.3 (Bus.Publish vs Bus.PublishClassic) and §15.3 (lin.checksum_type meta must round-trip faithfully).

LinAdapter::send() now routes to bus_->publish_classic() when f.checksum_type == Classic or f.id is a diagnostic ID, and to bus_->publish() otherwise — preserving on-wire checksum fidelity across the RELAY bridge instead of silently re-emitting every bridged frame (including diagnostics) as enhanced.

Applied from the pre-made audit diff (diffs/cpp-LIN/cpp-LIN-02.diff), which applied cleanly.

cpp-LIN-A1 (low) — master::Node::run() returns invalid_frame for a spec-valid empty schedule

Spec: RELAY §8.3 (relay-spec.md:509-511): "SetSchedule installs a new LIN schedule table. An empty table is valid and disables scheduled transmission." (verified directly against RELAY/spec/relay-spec.md.)

set_schedule({}) already succeeded (fixed in v0.4.1), but run() still early-returned lin::Errc::invalid_frame on an empty schedule — an ERROR, not the no-op the spec requires. run() now returns success ({}) immediately for an empty schedule. Updated the pinned tests/test_master.cpp:72 test (previously asserting the wrong behaviour) and both .fusa-reqs.json / requirements/requirements.json entries for REQ-MASTER-009/010, which described the old (incorrect) contract.

cpp-LIN-A2 (low, hardening) — LDF DB::decode() bit-shift width not bounded to [0,64) at parse time

Not a wire-format conformance defect but a defence-in-depth fix: val |= 1ULL << i in the bit-extraction loop is UB if i >= 64. bit_width is parsed directly from untrusted LDF text with no clamp; it was only incidentally bounded by the loop's own byte_idx >= data.size() break for realistic (<=8-byte) frames. bit_width is now clamped to [0, 64] at parse time, independent of payload size.

Regression-test proof: added a malformed-LDF test (Signal bit_width = 999999999) decoded against an oversized (64-byte) payload. Built and ran this test under UBSan (-fsanitize=undefined -fno-sanitize-recover=undefined):

  • Pre-fix: runtime error: shift exponent 64 is too large for 64-bit type 'long long unsigned int', SIGABRT.
  • Post-fix: passes clean.

cpp-LIN-A3 (low, CI hygiene) — unpinned third-party actions + || true-masked FuSa/HARA steps

  • SHA-pinned the two genuinely third-party (non-actions/*, non-github/*) actions: ilammy/msvc-dev-cmd@v1@0b201ec7... (v1.13.0), softprops/action-gh-release@v2@3bb12739... (v2.6.2), resolved via git ls-remote.
  • Removed || true from the cpfusa init (x2) and cpfusa hara init steps — these are pure setup steps immediately followed by steps that already gate (cpfusa check), so a silently-failed init should hard-fail rather than let downstream FuSa/HARA reporting run against a missing/stale config.
  • Left the iso26262/iec61508 gap-report || true and the clang-tidy warning-masking alone: those tools' non-zero exit plausibly signals "gap/warning found" rather than "tool crashed" (gap reports are uploaded as informational artifacts), and tightening warning-gating would require auditing/fixing the existing warning baseline — out of scope for this low-severity, no-diff CI-hygiene item. Noted as a follow-up.

Verification

  • Clean CMake configure + build (Release, Ninja) from scratch.
  • Full local test suite: 175/175 passing (ctest --test-dir build --output-on-failure -j1), matching CI's exact command.
  • Added regression tests for all four code fixes; confirmed each fails without its corresponding fix (verified by temporarily reverting each fix and re-running):
    • publish() on diagnostic IDs forces classic checksum
    • adapt: send preserves classic checksum type across the bridge
    • adapt: send forces classic checksum for diagnostic frame IDs even when tagged enhanced
    • run is a no-op success for empty schedule
    • parse clamps malformed/adversarial signal bit_width to [0,64] (UBSan-verified, see above)
  • CHANGELOG.md / ROADMAP.md updated per existing convention; CMakeLists.txt version bumped 0.4.1 → 0.4.2 (self-reported by the CLI via PROJECT_VERSION, exercised by existing version_json/version_text tests).
  • No local cpfusa build available in this environment (its source repo cpp-FuSa isn't checked out locally) — the .fusa-reqs.json / requirements/requirements.json REQ-MASTER-009/010 text was updated by hand to match the corrected run()/set_schedule() contract; CI's cpfusa check/trace/qualify gates will be the first automated verification of the updated safety-requirements artifacts.

Not merging — leaving for review per repo policy.

- virtual::Bus::publish() (and the default IBus::publish() path) now forces
  the classic checksum for diagnostic frame IDs 0x3C/0x3D regardless of the
  caller's requested checksum type (LIN 2.2A §2.3.1.5, RELAY §15.3),
  matching validate_frame()'s existing rejection of enhanced-checksum
  diagnostic frames.
- RELAY adapter LinAdapter::send() now honours the bridged message's
  lin.checksum_type (and forces classic for 0x3C/0x3D), routing to
  publish_classic() instead of unconditionally upgrading every bridged
  frame to enhanced.
- master::Node::run() now returns success (no-op) for an empty schedule
  table instead of lin::Errc::invalid_frame, matching RELAY §8.3 ("an
  empty table is valid and disables scheduled transmission"). Updated
  REQ-MASTER-009/010 requirement text (both req JSON files) to match.
- LDF parser clamps Signal::bit_width to [0, 64] at parse time so
  DB::decode()'s bit-extraction loop can't be driven into a shift-by->=64
  (UB) by a malformed/adversarial LDF file with an oversized payload.
- CI: SHA-pin ilammy/msvc-dev-cmd and softprops/action-gh-release instead
  of floating major-version tags; stop masking cpfusa init / hara init
  failures with `|| true` so a broken init hard-fails instead of letting
  downstream FuSa/HARA steps run against a stale config.

Regression tests added for all four code fixes, including a malformed-LDF
test that reproduces the bit-width UB under UBSan pre-fix.

Signed-off-by: Matt Jones <47545907+SoundMatt@users.noreply.github.com>
@SoundMatt
SoundMatt merged commit a79291b into main Jul 31, 2026
15 checks passed
@SoundMatt
SoundMatt deleted the fix/audit-conformance branch July 31, 2026 13:26
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.

1 participant