Skip to content

fix: LatestFrameReader opens the segment read-only (cross-privilege consumer)#2

Merged
DanielKow merged 1 commit into
masterfrom
fix/latest-frame-reader-readonly-open
Jul 9, 2026
Merged

fix: LatestFrameReader opens the segment read-only (cross-privilege consumer)#2
DanielKow merged 1 commit into
masterfrom
fix/latest-frame-reader-readonly-open

Conversation

@DanielKow

Copy link
Copy Markdown
Contributor

What

Lets a lower-privilege consumer attach to a display segment created by a higher-privilege producer — found during live e2e of Epic 020 / feature-002.

LatestFrameReader is a pure consumer (write-free), but it attached via SharedMemory::open, which always opens O_RDWR and maps PROT_READ|PROT_WRITE. The displaysink producer runs as root in a container and creates the segment 0644 root-owned (the coded 0666 is reduced by the container umask). The native player runs as a host user, so its O_RDWR open of a 644 root segment gets EACCES → never attaches → blank preview.

This is the first consumer that crosses a root→user boundary. The AI/zerosink path never hit it because rw2 launches that consumer as a privileged (root) container (ipc=host, /dev/shm shared), so both ends are root.

Fix

LatestFrameReader opens the segment read-only (O_RDONLY / PROT_READ) — correct, since the reader only ever .load()s and hands the consumer a const uint8_t*.

  • platform.h — add SharedMemory::open_readonly(name) (additive).
  • platform_linux.cpp — ctor gains bool read_only = false; open flags create ? O_CREAT|O_EXCL|O_RDWR : (read_only ? O_RDONLY : O_RDWR), mmap read_only ? PROT_READ : PROT_READ|PROT_WRITE. open/create unchanged.
  • platform_windows.cpp — parity (FILE_MAP_READ), so it still builds. Not the deployment target.
  • latest_frame.cpp — the reader's try_attach uses open_readonly. The writer's reclaim_stale_or_throw stays O_RDWR (writer privilege; may remove()).

latest_frame.h is not touched — the shared on-segment contract (LATEST_FRAME_MAGIC, LATEST_FRAME_VERSION, header/slot layout) is byte-identical, so an older writer stays wire-compatible with this reader. streamer keeps its pinned zerobuffer; only native-player rebuilds.

Verification

  • cpp/build.sh green; test_latest_frame 18/18 incl. the new ReadOnlyReaderReadsPublishedFrame (writer publishes → read-only reader reads back byte-for-byte; a later publish is picked up too).
  • ASan + UBSan clean — confirms the reader/consume path issues no write to the now-PROT_READ mapping.
  • No /dev/shm leak.

Honest limit: this proves read-only reads work; the cross-UID boundary (root writer / non-root reader) can't be reproduced in same-user CI, so that exact case is validated by the live run, not CI. Stated in the test comment.

Note on pre-existing CI: the generated BDD/Gherkin harness has ~67 unimplemented-step failures on master, unrelated to this change (verified by stash+rebuild — identical on master, and none touch LatestFrame). The LatestFrame suite is fully green.

🤖 Generated with Claude Code

…umers can attach

The reader is a pure consumer but attached via SharedMemory::open, which maps O_RDWR/PROT_READ|PROT_WRITE. A non-root reader opening a root-owned 0644 segment got EACCES and never attached. Add SharedMemory::open_readonly (O_RDONLY/PROT_READ) and use it in the reader attach path; the writer's stale-reclaim open stays O_RDWR.
@DanielKow
DanielKow merged commit 7ce680e into master Jul 9, 2026
4 of 5 checks passed
@DanielKow
DanielKow deleted the fix/latest-frame-reader-readonly-open branch July 9, 2026 16:52
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