fix: LatestFrameReader opens the segment read-only (cross-privilege consumer)#2
Merged
Merged
Conversation
…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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.
LatestFrameReaderis a pure consumer (write-free), but it attached viaSharedMemory::open, which always opensO_RDWRand mapsPROT_READ|PROT_WRITE. Thedisplaysinkproducer runs as root in a container and creates the segment0644root-owned (the coded0666is reduced by the container umask). The native player runs as a host user, so itsO_RDWRopen of a644root segment gets EACCES → never attaches → blank preview.This is the first consumer that crosses a root→user boundary. The AI/
zerosinkpath never hit it because rw2 launches that consumer as a privileged (root) container (ipc=host,/dev/shmshared), so both ends are root.Fix
LatestFrameReaderopens the segment read-only (O_RDONLY/PROT_READ) — correct, since the reader only ever.load()s and hands the consumer aconst uint8_t*.platform.h— addSharedMemory::open_readonly(name)(additive).platform_linux.cpp— ctor gainsbool read_only = false; open flagscreate ? O_CREAT|O_EXCL|O_RDWR : (read_only ? O_RDONLY : O_RDWR), mmapread_only ? PROT_READ : PROT_READ|PROT_WRITE.open/createunchanged.platform_windows.cpp— parity (FILE_MAP_READ), so it still builds. Not the deployment target.latest_frame.cpp— the reader'stry_attachusesopen_readonly. The writer'sreclaim_stale_or_throwstaysO_RDWR(writer privilege; mayremove()).latest_frame.his 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.shgreen;test_latest_frame18/18 incl. the newReadOnlyReaderReadsPublishedFrame(writer publishes → read-only reader reads back byte-for-byte; a later publish is picked up too).PROT_READmapping./dev/shmleak.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