Skip to content

Host-side coverage of the whole codebase (found a storage buffer overflow)#6

Merged
Tomunek merged 8 commits into
hakierspejs:masterfrom
d33mobile:chore/whole-coverage
Jul 15, 2026
Merged

Host-side coverage of the whole codebase (found a storage buffer overflow)#6
Tomunek merged 8 commits into
hakierspejs:masterfrom
d33mobile:chore/whole-coverage

Conversation

@d33mobile

Copy link
Copy Markdown

Host-side coverage of the whole codebase

The firmware is cross-compiled for the RP2040 and can't run natively, but almost
all of its logic is hardware-independent. This PR builds every first-party
.c as a native host binary
and produces an lcov/genhtml coverage report that
spans the whole codebase: the logic modules at their real percentage, and the
hardware-I/O wrappers at an honest 0%. The report is generated by compiling the
whole first-party set instrumented (a .gcno baseline at 0%) and merging in the
run data from the real harnesses.

Real tests (ASan + UBSan, and Valgrind)

Three small, table-driven harnesses exercise the non-trivial logic against the
real implementations (no mock reimplementations):

  • TOTPshared/totp.c against the system libmbedtls (real HMAC-SHA1),
    checked with the RFC 6238 test vectors. 100%.
  • storage + backupstorage.c + backup.c on top of the real
    littlefs
    , run against a RAM-backed XIP flash window that the harness maps in
    with mmap(MAP_FIXED) over the flash address the firmware uses. ~92%.
  • commands dispatcherserial/commands.c driven through
    commands_dispatch(), asserting routing, admin gating, argc validation and
    unknown-command handling. 100%.

Every harness runs clean under both ASan/UBSan (make -C test asan) and
Valgrind (make -C test valgrind).

The key find: a real buffer overflow in storage.c

ASan caught a global-buffer-overflow in storage.c: littlefs's
.lookahead_size was set to 64, but the lookahead buffer is only 8 bytes — a
56-byte out-of-bounds write on every mount and every format, on real
hardware too (it just happened to clobber adjacent globals silently there).

Fixed by tying the size to the buffer: .lookahead_size = sizeof(lfs_lookahead_buf).
The storage harness is the regression test.

Footprint

The firmware change is a single line (the storage fix). Everything else is test
scaffolding: mostly declaration-only host stubs for the pico-sdk / cyw43 / lwip /
mbedtls headers, plus the three harnesses and the test/Makefile. The CI test
job installs libmbedtls-dev (needed for the real TOTP test).

Note on CI

master carries the CI workflows, so the checks run and appear on this PR.

Claude and others added 8 commits July 9, 2026 15:22
… host stubs

Add a whole-codebase coverage path to test/Makefile: every first-party .c
(hardware/, network/, serial/, storage/, shared/, main.c) is compiled
instrumented but unlinked, so a .gcno alone lands it in the report. A
--initial baseline (all .gcno at 0%) merged with the harness run data yields
a report spanning the whole firmware -- base32/base64/random at their real %,
the 15 HW/IO/logic modules at an honest 0%.

Add declaration-only host stubs under test/stub/ for the pico-sdk, cyw43,
lwip and mbedtls headers those sources include; storage.c/backup.c build
against the real littlefs with the same LFS_NO_MALLOC/LFS_NO_DEBUG defines
the Pico CMake uses.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Build harness_totp.c + shared/totp.c against the system libmbedtls-dev
(real HMAC-SHA1) instead of the compile-only stub, using `-idirafter stub`
so the real <mbedtls/md.h> wins while hardware/rtc.h stays stubbed.
clock_get_unix_time() is injected in the harness for deterministic time.

Asserts the standard RFC 6238 SHA1 test vectors (seed "12345678901234567890")
and exercises totp_verify: RTC-not-set, correct code, the T-1/T/T+1 window,
and out-of-window/wrong code rejection. shared/totp.c goes 0% -> 100% lines
(25/25), 100% funcs (2/2), 100% branches (6/6). Wired into asan, valgrind,
and coverage targets.

CI note: the test job must apt-install libmbedtls-dev.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…c + backup.c

Exercise storage/storage.c and storage/backup.c host-side against the real
littlefs, backed by a RAM XIP window (MAP_FIXED at XIP_BASE+STORAGE_FLASH_OFFSET
so storage.c's absolute-address flash_read hits host RAM; program/erase shims
give NOR semantics). Covers key CRUD, list, update, wifi cfg, backup
export/import roundtrip and every rejection path. storage.c 0 -> 92.5% lines,
backup.c 0 -> 91.8% lines.

Fixes a genuine buffer overflow ASan caught: LFS_CFG.lookahead_size was set to
LFS_LOOKAHEAD (64) while lfs_lookahead_buf is only LFS_LOOKAHEAD/8 (8) bytes;
littlefs memsets lookahead_size bytes into that buffer, a 56-byte global
overflow on every mount/format (on real hardware too). Derive lookahead_size
from sizeof(buffer) so the two can never diverge.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…t job

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The github-pages environment is branch-protected to the default branch, so a
deploy from this branch is rejected regardless of the workflow. The coverage
build itself is already exercised by the test job; keep libmbedtls-dev in
pages.yml for when it runs on master.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… pico/unique_id stub)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Tomunek Tomunek merged commit d6fba27 into hakierspejs:master Jul 15, 2026
2 checks passed
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.

2 participants