Skip to content

fix: v0.12.2 — conformance-audit fixes (DBC StartBit, CAN XL socketcan, FD DLC, ISO-TP) - #93

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

fix: v0.12.2 — conformance-audit fixes (DBC StartBit, CAN XL socketcan, FD DLC, ISO-TP)#93
SoundMatt merged 1 commit into
mainfrom
fix/audit-conformance

Conversation

@SoundMatt

Copy link
Copy Markdown
Owner

Summary

Closes the go-CAN findings from the x-Net gap-audit pass. Primary spec authority: ISO 11898-1 (CAN/CAN FD/CAN XL framing) and ISO 15765-2 (ISO-TP). Most-severe first:

go-CAN-01 (high) — DBC parser accepts negative StartBit → runtime panic

dbc.parseSignal validated length (1-64) but had no bound check on startBit. A DBC file with SG_ S : -1|8@1+ ... parsed successfully; the first Decode/Encode call computed a negative bit index and executed 1 << (-1), panicking with "negative shift amount". This is a malformed/adversarial-input crash — any consumer parsing untrusted DBC files could be crashed.

Fix: parseSignal now rejects startBit < 0 || startBit > 511 (512 bits = 64-byte CAN FD payload upper bound), matching the existing length-validation pattern.

Regression-test proof: TestParseRejectsNegativeStartBit and TestParseRejectsOversizeStartBit in dbc/parser_test.go assert Parse now returns an error for these inputs (previously would have parsed successfully and panicked downstream on Decode). Also added a dedicated FuzzParse seed corpus entry for the negative-start-bit case, and ran go test -fuzz=^FuzzParse$ -fuzztime=10s ./dbc/... locally — 1.5M+ executions, no panics.

go-CAN-02 (high) — socketcan silently mis-encodes CAN XL as corrupted classic CAN

can.ValidateFrame correctly accepts XL frames (1-2048 bytes), but socketcan/bus_linux.go's encodeFrame() only branched on f.FD, so XL frames fell through to the 16-byte classic can_frame path: the length byte truncated (e.g. 2048 → 0 mod 256) and at most 8 of up to 2048 data bytes were copied, with Send returning no error — a corrupt, silently mislabelled frame transmitted on the wire. Linux CAN_RAW sockets cannot represent CAN XL at all (per ISO 11898-1:2024 / CiA CAN XL), so the correct minimal fix is to fail loudly rather than attempt (out of scope) native XL wire encoding.

Fix: Bus.Send now rejects f.XL with ErrInvalidFrame before ever calling encodeFrame.

Test: TestSendRejectsXL in socketcan/bus_linux_test.go (vcan-gated, matching this file's existing test convention — see note on CI/local verification below).

go-CAN-03 (medium) — ValidateFrame accepts non-canonical CAN FD lengths

CAN FD (ISO 11898-1 / Bosch CAN FD) only represents the discrete lengths 0-8, 12, 16, 20, 24, 32, 48, 64 bytes on the wire (DLC codes 9-15 map to 12/16/20/24/32/48/64). ValidateFrame's FD branch only rejected len(Data) > 64, so e.g. a 9-byte FD frame passed validation and would be silently padded to 12 bytes by the controller — sender and receiver disagree on length.

Fix: added validFDDataLen helper and a ValidateFrame check rejecting FD lengths outside the canonical DLC-mapped set.

Test: table-driven cases in TestValidateFrame (can_test.go) for rejected lengths 9/10/11/13/15 and accepted canonical lengths 12/20/48.

go-CAN-05 (low) — ISO-TP empty-CF stall + reserved STmin mapped to 0

Two issues in isotp/transport.go: (a) a Consecutive Frame carrying only the PCI byte (len(Data)==1) yielded an empty chunk; buf never grew while sn kept incrementing, letting a peer with correctly-incrementing SNs but zero-length payloads stall reassembly indefinitely as long as frames kept arriving. (b) stminToDuration's default case mapped all ISO 15765-2 reserved STmin values (0x80-0xF0, 0xFA-0xFF) to 0 (no delay) instead of the conventional 127ms fail-safe used by reference implementations (e.g. Linux kernel isotp.c) — independently confirmed via WebSearch against the ISO 15765-2 STmin encoding table.

Fix: reject CFs with no payload bytes during reassembly (errors.New("isotp: empty consecutive frame payload")); reserved STmin values now map to 127ms instead of 0.

Tests: TestRecvRejectsEmptyConsecutiveFrame (external test, via Recv) and TestSTminToDurationReservedValuesMapToFailSafeMax / TestSTminToDurationStandardRanges (new internal test file, isotp/transport_internal_test.go, since stminToDuration is unexported). Updated a stale comment in the existing TestSendMultiFrameMicroSTmin that referenced the old "reserved → 0" behavior.

go-CAN-04 (low) — CI actions pinned to mutable tags

.github/workflows/ci.yml referenced actions/checkout@v5, actions/setup-go@v6, actions/upload-artifact@v4/v6 by mutable tag, and installed govulncheck@latest. Supply-chain hardening (SLSA / OSSF Scorecard Pinned-Dependencies) — not a protocol-conformance item, so not re-verified against ISO specs, accepted as standard practice.

Fix: pinned all four action references to their current release's commit SHA (with a # vX.Y.Z comment) and govulncheck to v1.6.0.

Verification

  • go build ./... / go vet ./... — clean
  • go test -race -count=1 $(go list ./... | grep -v socketcan) — all packages pass
  • GOOS=linux go vet ./socketcan/... / GOOS=linux go build ./socketcan/... — clean (dev machine is darwin; socketcan is Linux-only by filename-suffix build constraint, and its tests require vcan0, which — per this repo's own CI comments — GitHub-hosted runners don't provide either, so CI's test-socketcan job is the actual first execution of TestSendRejectsXL, same as all pre-existing tests in that file)
  • go test -fuzz=^FuzzParse$ -fuzztime=10s ./dbc/... — 1.5M+ execs, no panics, no new crashers
  • No local gofusa/relay CLI available in this environment — the gofusa and relay-conform CI jobs will be the first real check; no FuSa-gated safety artifact (coverage/traceability/ASIL) content needed manual editing, since all new tests live in files already covered by blanket //fusa:test tags for the same requirement IDs, matching this repo's existing convention.

ROADMAP.md updated with a new v0.12.2 patch row per existing convention (see e.g. the v0.12.1 row/commit for precedent); cmd/cantool toolVersion bumped to match.

Do not merge — leaving for review/CI per process.

…n, FD DLC, ISO-TP STmin/empty-CF)

Gap-audit pass against ISO 11898-1 / ISO 15765-2 found five conformance
issues, closed here:

- dbc: parseSignal now rejects StartBit < 0 or > 511. A negative start
  bit previously parsed successfully and panicked on the first
  Decode/Encode call ("negative shift amount", 1 << (-1)) — malformed
  or adversarial DBC input could crash any consumer. Regression tests
  added (TestParseRejectsNegativeStartBit, TestParseRejectsOversizeStartBit)
  plus a dedicated FuzzParse seed corpus entry.

- socketcan: Bus.Send now rejects CAN XL frames (ErrInvalidFrame)
  instead of silently emitting a corrupted classic CAN frame. Linux
  CAN_RAW sockets can only carry can_frame/canfd_frame; the previous
  encodeFrame fallback truncated the length byte (e.g. 2048 -> 0 mod
  256) and copied at most 8 of up to 2048 data bytes with no error
  returned. TestSendRejectsXL added (vcan-gated, matching existing
  test conventions in this file).

- can.go: ValidateFrame now rejects non-canonical CAN FD payload
  lengths. CAN FD only represents 0-8, 12, 16, 20, 24, 32, 48, 64
  bytes on the wire (Bosch CAN FD DLC-code mapping); lengths like 9,
  10, 11 previously passed validation and would be silently padded by
  the controller, desyncing sender/receiver length expectations.
  Table-driven cases added for both rejected and accepted lengths.

- isotp: Consecutive Frames carrying only the PCI byte (empty
  payload) are now rejected during reassembly instead of allowing buf
  to stall indefinitely while SN keeps incrementing.
  TestRecvRejectsEmptyConsecutiveFrame added. Reserved STmin values
  (0x80-0xF0, 0xFA-0xFF) now map to the conventional 127 ms fail-safe
  maximum instead of 0 (no delay), matching the interpretation used by
  reference ISO-TP implementations (e.g. Linux kernel isotp.c).
  TestSTminToDurationReservedValuesMapToFailSafeMax and
  TestSTminToDurationStandardRanges added as internal unit tests.

- .github/workflows/ci.yml: third-party actions (checkout, setup-go,
  upload-artifact) pinned to commit SHAs with version comments;
  govulncheck pinned to v1.6.0 instead of @latest (supply-chain
  hardening, SLSA/OSSF Scorecard Pinned-Dependencies practice).

cmd/cantool toolVersion bumped to 0.12.2; ROADMAP.md updated per
existing release-plan convention.

go build/vet/test -race all green (non-socketcan packages, run
locally on darwin); socketcan cross-compiled and vetted clean for
GOOS=linux (execution requires vcan0, unavailable on darwin/CI
hosted runners per this repo's own documented CI limitation).
go test -fuzz=FuzzParse run for 10s with no panics.

Signed-off-by: SoundMatt <47545907+SoundMatt@users.noreply.github.com>
@SoundMatt
SoundMatt merged commit 34c0f47 into main Jul 31, 2026
14 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