Skip to content

Commit bda4a3c

Browse files
committed
fix: v0.4.2 — RELAY ecosystem conformance fixes, pass 3
- 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>
1 parent f7006b2 commit bda4a3c

15 files changed

Lines changed: 213 additions & 20 deletions

File tree

.fusa-reqs.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -507,18 +507,18 @@
507507
},
508508
{
509509
"id": "REQ-MASTER-009",
510-
"title": "Run returns error for empty schedule",
511-
"description": "Run shall return an error immediately when the schedule table is empty.",
510+
"title": "Run treats an empty schedule as a no-op success",
511+
"description": "Run shall return success immediately (without invoking the bus) when the schedule table is empty, rather than returning an error.",
512512
"asil": "ASIL-B",
513-
"rationale": "An empty schedule is a programming error; running it would loop forever.",
513+
"rationale": "RELAY §8.3: an empty schedule table is valid and simply disables scheduled transmission; it is not a programming error, so Run must not fail callers that have not yet configured a schedule.",
514514
"tags": ["master", "validation"]
515515
},
516516
{
517517
"id": "REQ-MASTER-010",
518-
"title": "SetSchedule rejects empty schedule",
519-
"description": "SetSchedule shall return an error when called with nil or a zero-length slice.",
518+
"title": "SetSchedule accepts an empty schedule",
519+
"description": "SetSchedule shall accept a nil or zero-length slice and succeed, disabling scheduled transmission (REQ-MASTER-009) until a non-empty schedule is set.",
520520
"asil": "ASIL-B",
521-
"rationale": "REQ-MASTER-009 precondition enforced at configuration time.",
521+
"rationale": "RELAY §8.3: an empty schedule table is valid; SetSchedule must not reject it.",
522522
"tags": ["master", "validation"]
523523
},
524524
{

.github/workflows/ci.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ jobs:
5555
5656
- name: Set up MSVC environment (Windows)
5757
if: runner.os == 'Windows'
58-
uses: ilammy/msvc-dev-cmd@v1
58+
uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # v1.13.0
5959

6060
- name: Configure
6161
shell: bash
@@ -249,7 +249,7 @@ jobs:
249249
250250
- name: cpfusa init
251251
working-directory: cpp-LIN
252-
run: ../cpp-FuSa/build/cpfusa init --name cpp-LIN --standard iso26262 --asil ASIL-B --project-version 0.4.0 --force || true
252+
run: ../cpp-FuSa/build/cpfusa init --name cpp-LIN --standard iso26262 --asil ASIL-B --project-version 0.4.0 --force
253253

254254
- name: cpfusa check
255255
working-directory: cpp-LIN
@@ -273,7 +273,7 @@ jobs:
273273

274274
- name: cpfusa hara init
275275
working-directory: cpp-LIN
276-
run: ../cpp-FuSa/build/cpfusa hara init --project cpp-LIN --dir . || true
276+
run: ../cpp-FuSa/build/cpfusa hara init --project cpp-LIN --dir .
277277

278278
- name: cpfusa iso26262 (ASIL-B)
279279
working-directory: cpp-LIN
@@ -450,7 +450,7 @@ jobs:
450450
451451
- name: cpfusa init
452452
working-directory: cpp-LIN
453-
run: ../cpp-FuSa/build/cpfusa init --name cpp-LIN --standard iso26262 --asil ASIL-B --project-version 0.4.0 --force || true
453+
run: ../cpp-FuSa/build/cpfusa init --name cpp-LIN --standard iso26262 --asil ASIL-B --project-version 0.4.0 --force
454454

455455
- name: Generate SARIF report
456456
working-directory: cpp-LIN

.github/workflows/release.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ jobs:
3131

3232
- name: Set up MSVC (Windows)
3333
if: runner.os == 'Windows'
34-
uses: ilammy/msvc-dev-cmd@v1
34+
uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # v1.13.0
3535

3636
- name: Configure
3737
shell: bash
@@ -88,7 +88,7 @@ jobs:
8888
cat SHA256SUMS.txt
8989
9090
- name: Create GitHub Release
91-
uses: softprops/action-gh-release@v2
91+
uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2.6.2
9292
with:
9393
name: "cpp-LIN ${{ github.ref_name }}"
9494
tag_name: ${{ github.ref_name }}

CHANGELOG.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [0.4.2]
11+
### Fixed
12+
- `virtual::Bus::publish()` (and the default `IBus::publish()` entry point)
13+
now forces the classic checksum for diagnostic frame IDs 0x3C/0x3D
14+
regardless of the caller's requested checksum type, matching LIN 2.2A
15+
§2.3.1.5 and RELAY §15.3, and matching `validate_frame()`'s existing
16+
rejection of enhanced-checksum diagnostic frames.
17+
- RELAY adapter `LinAdapter::send()` now honours the bridged message's
18+
`lin.checksum_type` (and forces classic for 0x3C/0x3D), routing to
19+
`publish_classic()` instead of unconditionally upgrading every bridged
20+
frame to the enhanced checksum via `publish()`.
21+
- `master::Node::run()` now returns success (a no-op) for an empty schedule
22+
table instead of `lin::Errc::invalid_frame`, matching RELAY §8.3 ("an
23+
empty table is valid and disables scheduled transmission").
24+
- LDF parser now clamps `Signal::bit_width` to `[0, 64]` at parse time, so
25+
`DB::decode()`'s bit-extraction loop can no longer be driven into a
26+
shift-by->=64 (undefined behaviour) by a malformed/adversarial LDF file
27+
combined with an oversized payload.
28+
### Changed
29+
- CI: `ilammy/msvc-dev-cmd` and `softprops/action-gh-release` pinned to
30+
commit SHA instead of a floating major-version tag.
31+
- CI: `cpfusa init` / `cpfusa hara init` steps no longer swallow failures
32+
with `|| true` — a failed init step now hard-fails the job instead of
33+
letting every downstream FuSa/HARA step silently run against a
34+
missing/stale config.
35+
1036
## [0.4.1]
1137
### Added
1238
- `verify_checksum()`: validates a received checksum byte against the

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
cmake_minimum_required(VERSION 3.21)
22

33
project(cpplin
4-
VERSION 0.4.1
4+
VERSION 0.4.2
55
DESCRIPTION "C++ LIN bus library — RELAY-conformant, ISO 26262 ASIL-B"
66
HOMEPAGE_URL "https://github.com/SoundMatt/cpp-LIN"
77
LANGUAGES CXX

ROADMAP.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
convention), matching peers like `go-can`
5151
- [x] `lin::mock` canonical module (spec §13.7.1), aliasing `lin::virt::Bus`
5252

53-
## v0.4.1 — RELAY ecosystem audit fixes, pass 2 (current)
53+
## v0.4.1 — RELAY ecosystem audit fixes, pass 2
5454

5555
- [x] Declared RELAY spec version bumped 1.11 → 2.0
5656
- [x] `verify_checksum()` added; HARA SG-03 downgraded from "Implemented" to
@@ -70,6 +70,23 @@
7070
fixed, HARA §15→§5 citation fixed
7171
- [x] `CHANGELOG.md` added
7272

73+
## v0.4.2 — RELAY ecosystem audit fixes, pass 3 (current)
74+
75+
- [x] `virtual::Bus::publish()` forces classic checksum for diagnostic
76+
frames 0x3C/0x3D (LIN 2.2A §2.3.1.5 / RELAY §15.3)
77+
- [x] RELAY adapter `LinAdapter::send()` honours `lin.checksum_type` /
78+
diagnostic-ID classic-checksum override instead of always upgrading
79+
bridged frames to enhanced
80+
- [x] `master::Node::run()` treats an empty schedule as a no-op success
81+
(spec §8.3), not `invalid_frame`
82+
- [x] LDF parser clamps `Signal::bit_width` to `[0, 64]` at parse time
83+
(defence-in-depth against a malformed/adversarial LDF driving
84+
`DB::decode()`'s bit-extraction loop into UB)
85+
- [x] CI: third-party actions (`ilammy/msvc-dev-cmd`,
86+
`softprops/action-gh-release`) pinned to commit SHA
87+
- [x] CI: `cpfusa init` / `cpfusa hara init` steps hard-fail instead of
88+
being masked with `|| true`
89+
7390
## v0.5.0 — Enhanced Bus Features
7491

7592
- [ ] Sleep/wake frame support (LIN 2.x go-to-sleep command)

requirements/requirements.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,8 @@
7171
{"id": "REQ-MASTER-006", "title": "run invokes on_frame on success", "asil": "ASIL-B", "tags": ["master", "callback"]},
7272
{"id": "REQ-MASTER-007", "title": "run invokes on_error on failure", "asil": "ASIL-B", "tags": ["master", "callback"]},
7373
{"id": "REQ-MASTER-008", "title": "run returns on stop token", "asil": "ASIL-B", "tags": ["master", "lifecycle"]},
74-
{"id": "REQ-MASTER-009", "title": "run returns error for empty schedule", "asil": "ASIL-B", "tags": ["master", "validation"]},
75-
{"id": "REQ-MASTER-010", "title": "set_schedule rejects empty schedule", "asil": "ASIL-B", "tags": ["master", "validation"]},
74+
{"id": "REQ-MASTER-009", "title": "run is a no-op success for empty schedule", "asil": "ASIL-B", "tags": ["master", "validation"]},
75+
{"id": "REQ-MASTER-010", "title": "set_schedule accepts empty schedule", "asil": "ASIL-B", "tags": ["master", "validation"]},
7676
{"id": "REQ-MASTER-011", "title": "set_schedule rejects invalid frame ID", "asil": "ASIL-B", "tags": ["master", "validation"]},
7777
{"id": "REQ-MASTER-012", "title": "set_schedule stores a defensive copy", "asil": "ASIL-B", "tags": ["master", "copy"]},
7878
{"id": "REQ-MASTER-013", "title": "run continues after per-slot errors", "asil": "ASIL-B", "tags": ["master", "resilience"]},

src/ldf/parser.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,16 @@ struct Parser {
193193
Signal sig;
194194
sig.name = name;
195195
try { sig.bit_width = static_cast<int>(parse_int(parts[0])); } catch (...) {}
196+
// Clamp bit_width to [0, 64] at parse time so DB::decode()'s
197+
// extraction loop (`val |= uint64_t(1) << i` for i in
198+
// [0, bit_width)) can never shift by >= 64, which is undefined
199+
// behaviour, regardless of how large a malformed/adversarial LDF
200+
// declares the signal width or how large the payload is. This is
201+
// defence-in-depth: the loop's own byte_idx >= data.size() break
202+
// happens to bound i for realistic (<=8-byte) frames today, but
203+
// that is incidental, not a guarantee.
204+
if (sig.bit_width < 0) sig.bit_width = 0;
205+
else if (sig.bit_width > 64) sig.bit_width = 64;
196206
try { sig.init_value = parse_uint(parts[1]); } catch (...) {}
197207
sig.publisher = trim(parts[2]);
198208
for (std::size_t i = 3; i < parts.size(); ++i) {

src/lin.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,14 @@ class LinAdapter : public relay::INode {
178178
} catch (const ErrInvalidFrame&) {
179179
return relay::make_error_code(relay::Errc::payload_too_large);
180180
}
181+
// Honour the frame's checksum type when registering the response:
182+
// classic-configured frames (and all diagnostic frames 0x3C/0x3D, which
183+
// MUST use the classic checksum per LIN 2.x §2.3.1.5 / RELAY §15.3) go
184+
// through publish_classic(); otherwise the default publish() would
185+
// silently upgrade every bridged frame to the enhanced checksum.
186+
if (f.checksum_type == ChecksumType::Classic ||
187+
f.id == kLINDiagRequestID || f.id == kLINDiagResponseID)
188+
return bus_->publish_classic(f.id, std::move(f.data));
181189
return bus_->publish(f.id, std::move(f.data));
182190
}
183191

src/master/node.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,11 @@ std::pair<Frame, std::error_code> Node::send_header(uint8_t id) {
5151
// fusa:req REQ-MASTER-003 REQ-MASTER-004 REQ-MASTER-005 REQ-MASTER-006
5252
// fusa:req REQ-MASTER-007 REQ-MASTER-008 REQ-MASTER-009 REQ-MASTER-013
5353
std::error_code Node::run(const std::atomic<bool>& stop) {
54+
// RELAY §8.3: an empty schedule table is valid and disables scheduled
55+
// transmission — treat it as a no-op success, not an error, so callers
56+
// don't have to special-case "no schedule configured" as a failure.
5457
if (schedule_.empty())
55-
return lin::make_error_code(lin::Errc::invalid_frame);
58+
return {};
5659

5760
while (!stop.load()) {
5861
for (const auto& slot : schedule_) {

0 commit comments

Comments
 (0)