Add real fault-injection crash-safety testing; investigate TSan for real
CI / build-and-test (push) Failing after 38s

Prompted by a direct question about data-integrity trustworthiness: the
crash-safety design (self-checking journal records, atomic-rename
compaction, Section 4.3/4.4) had never actually been tested against a real
crash -- only reasoned about statically and tested against an
already-corrupted file (test_pack_overlay.c, a different scenario).

Added tests/test_crash_consistency.c: real fork()+SIGKILL fault injection
against an actual child process, not a hand-truncated file standing in
for a crash, with empirically-calibrated kill-delay sampling (measured via
throwaway scripts, not guessed) so trials land genuine mid-operation
interruptions rather than always completing first:

- Journal-write crash injection (25 trials): kills a child mid-burst of
  individually-journaled, individually-fsynced writes; verifies a strict
  clean-prefix recovery (every completed write present and correct, every
  write after the kill cleanly absent, no gaps). 20-22/25 trials per run
  land a genuine interruption; zero corruption found.
- Compaction crash injection (40 trials): kills a child mid-vfs_sync;
  verifies every write durably journaled *before* vfs_sync was called
  survives regardless of whether compaction itself completed. 40/40
  trials per run land a genuine interruption; zero corruption found.

A real finding from building this test, not from the code under test: the
first draft reported 128 failures, every one a bug in the test itself --
it couldn't distinguish "the child was killed before ever attempting this
write" from "the write completed and was then lost," since SIGKILL can't
be caught to report progress. Fixed with an independent progress
side-channel (plain write()+fsync() on a separate file, entirely outside
packfs) as ground truth. Documented in CLAUDE.md's new "Known reliability
characteristics" section, including this finding, because a claim is only
as trustworthy as what's measuring it.

Also investigated whether this sandbox's TSan block is actually
unfixable, rather than re-asserting the known limitation: tried
personality(ADDR_NO_RANDOMIZE) directly (EPERM), setarch -R (fails
identically), searched TSAN_OPTIONS for a bypass (none exists), and
checked capabilities/seccomp/unshare --user (zero effective caps, active
seccomp filter, namespace escape also blocked). Confirmed categorical and
documented as such in CLAUDE.md/CONTRIBUTING.md, rather than left as an
unexamined "it doesn't work here."

Also closed a real documentation gap SECURITY.md had: the pack integrity
checksum's non-cryptographic nature (FNV-1a64, already documented in the
context of the compaction-dedup bug) had never been explicitly connected
to its *other* use, load-time pack integrity validation -- added, since
it's a real, relevant caveat for anyone relying on that checksum against
a deliberate adversary rather than accidental corruption.

Verified: clean make all + make test (all 7 binaries), full ASan/UBSan
sweep of all 7 binaries with zero real findings, and the new test run
standalone 4+ times (this session) with stable, consistent results.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UqJpkdJ6Njnt1pw3CbghzB
This commit is contained in:
2026-09-14 19:19:21 +00:00
co-authored by Claude Sonnet 5
parent 3308cf4eaf
commit e41bd2334a
3 changed files with 436 additions and 3 deletions
+22 -1
View File
@@ -38,10 +38,31 @@ rather than a generic "we take security seriously":
externally-sourced pack additionally needs a checksum verified at load.
Loading an attacker-supplied `.img` file that PackFS accepts without
detecting corruption, or that causes an out-of-bounds read/write, is a
security bug.
security bug. **That checksum (FNV-1a64, `src/hash.c`) is explicitly
non-cryptographic and not collision-resistant** — stated plainly here
because this is the one place in this document where it matters most:
it is a real, effective check against *accidental* corruption (a
truncated copy, a bit flip, a bad transfer), but it is not a tamper-
evident guarantee against a deliberate adversary who can choose the
bytes of a `.img` file — someone with that capability could in
principle construct a corrupted pack whose checksum still matches. Do
not treat "checksum verified at load" as a substitute for verifying the
*source* of an externally-supplied pack through some other channel if
the threat model includes a deliberate adversary, not just accidental
corruption.
- **Journal records are length/checksum self-describing** (Section 4.4),
so replay stops cleanly at the first torn or corrupted record rather
than reading past it.
- **The crash-safety design above is verified by actual fault injection,
not only by reasoning about the design.** `tests/test_crash_consistency.c`
forks a real child process and `SIGKILL`s it at randomized points during
journaled writes and during compaction, then checks what a fresh reopen
recovers, across dozens of trials per run — not a hand-truncated file
standing in for a crash. See `CLAUDE.md`'s "Known reliability
characteristics" for the methodology and results, including a real bug
the *test itself* had on its first draft (conflating "never attempted
before the kill" with "lost after completing") — fixed before treating
the crash-safety claim as verified.
## What PackFS explicitly does *not* claim