Skip to content

fix(fspy): tolerate torn shared-memory frames when collecting traced accesses#545

Open
wan9chi wants to merge 1 commit into
mainfrom
claude/issue-544-e2e-snapshot-l0oi6v
Open

fix(fspy): tolerate torn shared-memory frames when collecting traced accesses#545
wan9chi wants to merge 1 commit into
mainfrom
claude/issue-544-e2e-snapshot-l0oi6v

Conversation

@wan9chi

@wan9chi wan9chi commented Jul 14, 2026

Copy link
Copy Markdown
Member

Motivation

Closes #544.

vp run can crash with a slice-bounds panic while collecting fspy path accesses after the task itself completed successfully:

thread 'main' panicked at crates/fspy_shared/src/ipc/channel/shm_io.rs:328:47:
range start index 1919954688 out of range for slice of length 102756

ShmReader::iter_frames assumes writer quiescence. When a tracked writer dies — or is still writing — between claiming a frame and completing it, the buffer is left with a torn frame: the end offset already covers the frame's extent, the frame header is still zero, but content bytes are present. The reader's zero-header arm then steps 4 bytes at a time into that extent and misreads frame content (path bytes) as a frame header: 1919954688 = 0x72702F00 is exactly "\0/pr" from an absolute path. The resulting bogus skip length overruns the used region and panics — or, worse, a misparse that stays in bounds could silently corrupt the inferred inputs/outputs that feed the cache.

The live race window is nanoseconds wide (see the issue's fresh-relink correlation), so the reproduction crafts the torn byte-state deterministically instead of racing.

Approach

Two commits, meant to be read in order:

  1. test(e2e): reproduce fspy shm reader panic on torn frame (#544) — adds a vtt corrupt-fspy-shm test helper that opens the task's own fspy channel (via the payload fspy hands every traced process) and appends the torn-frame state through raw shared-memory writes — the writer API can never produce it on its own. The new fspy_shm_torn_frame e2e fixture runs such a task twice; the snapshot at this commit captures the crash, byte-for-byte matching the issue (same bogus header value, same panic site). CI on this commit is green with the panic snapshot, demonstrating the bug is reproduced stably on every platform with the shared-memory channel (the fixture is skipped on musl, where the channel doesn't exist and fspy uses seccomp-unotify arenas instead).

  2. fix(fspy): treat torn shm frames as truncated tail instead of panicking — makes the reader defensive, following the issue's suggestion:

    • ShmReader::iter_frames stops (instead of panicking) when a frame's recorded extent overruns the used region, and reports it via is_tail_truncated();
    • a frame that fails to deserialize ends the iteration the same way instead of unwrap()-panicking;
    • the truncation propagates to update_cache, which skips caching with a new TrackingIncomplete reason — mirroring how FspyUnsupported degrades — because the inferred inputs/outputs may be missing entries and a cache entry built from them could produce stale hits;
    • the summary shows → Not cached: file access tracking data was incomplete.

    The e2e snapshot is updated to prove it: the run now exits 0 with the not-cached notice, and the second run stays a cache miss (nothing bogus was cached).

The root-cause audit of receiver-lock coverage (which descendants can write without holding the shared flock) and acquire-ordering for a non-quiescent reader remain follow-up work per the issue; this PR removes the crash and the unsound caching path regardless of which hypothesis produces the torn state.

🤖 Generated with Claude Code

https://claude.ai/code/session_017ps7hxpYTqwk3t9qnPgyK8


Generated by Claude Code

Add a `vtt corrupt-fspy-shm` helper that appends a torn frame to the
task's fspy shared-memory channel: the end offset covers the frame's
extent, the frame header is still zero, and path-like content bytes are
present. This is the exact byte-level state a tracked writer leaves
behind when it dies or races the reader between claiming a frame and
completing it, and the writer API can never produce it on its own, so
the helper reconstructs it through raw shared-memory writes (via a new
`ChannelConf::shm_id` accessor and the payload fspy hands every traced
process).

The new fspy_shm_torn_frame e2e fixture runs such a task twice: today
`vp run` panics in `ShmReader::iter_frames` after the task itself
succeeded, with the exact bogus skip length from the issue
(1919954688 = 0x72702F00 = "\0/pr" path bytes read as a frame header).
The snapshot captures the crash to demonstrate the bug; the follow-up
commit makes the reader degrade gracefully and updates the snapshot.

The fixture is skipped on musl because the shared-memory channel does
not exist there (fspy uses seccomp-unotify arenas instead).

Issue: #544

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ps7hxpYTqwk3t9qnPgyK8

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 43da79ec8d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


## `vt run -v torn-frame`

**Exit code:** 101

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Assert the non-panicking torn-frame behavior

The new regression fixture currently passes only when vt run exits 101 and prints the same fspy slice-bounds panic. That locks in the #544 failure mode instead of proving the reader tolerates the torn frame; the fixture comment says this must not crash and must avoid caching incomplete data, so the expected snapshot should be the graceful exit-0/not-cached result for both runs.

Useful? React with 👍 / 👎.

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.

fspy: shm frame reader panics parsing traced accesses (shm_io.rs:328)

2 participants