Skip to content

fix(tracking): close append/compaction TOCTOU lost-write - #92

Merged
fusengine merged 2 commits into
mainfrom
fix/track-toctou
Jul 24, 2026
Merged

fix(tracking): close append/compaction TOCTOU lost-write#92
fusengine merged 2 commits into
mainfrom
fix/track-toctou

Conversation

@fusengine

@fusengine fusengine commented Jul 24, 2026

Copy link
Copy Markdown
Owner

Summary

  • Closes a TOCTOU lost-write between the journal append path and the compactor's rename/fold/unlink: appendEvent now serialises on a blocking twin of the existing track lock (withTrackLockSyncBlocking), never skipped, so an append can no longer race compaction's rename/unlink window.
  • Adds deterministic, non-vacant probes (test/track-journal-toctou.test.ts): a manual-lock block test and a real append-loop-vs-real-compaction race test asserting zero event loss.
  • Bumps package.json to 0.1.83 (PATCH) and updates CHANGELOG.md.

Test plan

  • bunx tsc --noEmit clean
  • bun test — 900 pass / 1 skip / 0 fail
  • grep -rEn "bun:|Bun\.|from ['"'"'bun['"'"']" src/tracking — no runtime hits
  • New probe test proven non-vacant independently: red without the fix, green with it, non-flaky 5/5
  • CI green on this PR's head SHA (verified: 20f72154e8876e02180ba7f8dd8a018005968cb5)

…pend lock)

The journal append path used a bare appendFileSync (O_APPEND) with no
mutual exclusion against the compactor. Compaction is rename -> fold ->
unlink on a fresh inode; an append could straddle open->write between
compaction's rename and unlink, landing its write in the renamed inode
that then got unlinked -- a silent lost write, never surfaced by the
per-line HMAC (fail-closed per line has nothing to reject: the write
simply never reached the surviving file).

Fix: appendEvent now serialises on withTrackLockSyncBlocking, the same
track.lock the compaction takes, spinning (1ms step, stale-lock TTL as
the only anti-deadlock guard) until acquired -- never skipped, unlike
the existing non-blocking withTrackLockSync used elsewhere. An append
can no longer race compaction's rename/fold/unlink window.

Probe (test/track-journal-toctou.test.ts) is deterministic and proven
non-vacant independently: (A) a child process blocked on a manually
held track.lock must NOT complete its append until the lock is
released -- red without the fix (bare appendFileSync ignores the
lock), green with it. (B) a real appendEvent loop running during a
real maybeCompactJournal must serialise behind the compaction with
zero event loss -- red without the fix (loop finishes inside the
compaction window), green with it. Verified non-flaky 5/5, full suite
900/0.
@fusengine
fusengine merged commit 6933260 into main Jul 24, 2026
1 check passed
@fusengine
fusengine deleted the fix/track-toctou branch July 24, 2026 11:41
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