Repository navigation
Fix filesystem streams and no-follow symlink checks - #2448
Open
landonxjames wants to merge 2 commits into
Open
landonxjames wants to merge 2 commits into
landonxjames wants to merge 2 commits into
Conversation
Keep a file open until stream operations started from its descriptor finish Give each read-via-stream chunk its own buffer so concurrent reads cannot corrupt each other End read-via-stream and read-directory in the worker when the guest drops the stream Skip the sandbox check on a final symlink for path operations that do not follow it
landonxjames
requested review from
andreiltd and
vados-cosmonic
as code owners
November 12, 2026 04:41
This branch has not been deployed
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.
Part of the fixes for #2447
This fixes the
wasm32-wasip3filesystem problems 2, 3, 4 and 6 from #2447, and the same stream-drop problem inread-directory. Problem 1 was already fixed in #2437. Problem 5 is not addressed here, because since #2412exitdoesn't drain output.Dropping a descriptor during a stream operation (problem 2)
Descriptor[Symbol.dispose]closed the descriptor'sFileHandlewhile awrite-via-stream,append-via-streamorread-via-streamoperation could still be running in the filesystem worker with the raw fd. The remaining writes then failed with anEBADFerror, or used a file that reused the fd number. Rust'sstd::fs::writedrops the descriptor this way once the stream has accepted the last bytes, which left files short.The new
SharedFileHandleowns theFileHandle. Stream operations hold it while they run, and closing it waits until the last one settles. This applies to explicit disposes and the finalizer. Under Wasmtime, the stream similarly holds its own reference to the file.Concurrent reads sharing one buffer (problem 3)
The worker read every
read-via-streamchunk into one module-level buffer. A written chunk is only copied to the reader's thread when the reader pulls it, so another read in flight could overwrite it first, and the guest received bytes from the wrong file or offset. Each chunk now gets its own buffer.Reads that outlive their stream (problem 6 and
read-directory)read-via-streamandread-directorycreated theirStreamReaderwith the defaultpreventCancel: true. A guest that dropped the stream early left the worker blocked on its next write, and the future didn't settle. With the change for problem 2 this would also have kept the file open. Both now pass{ preventCancel: false }(liketcp-socket.receive) and the worker ends the operation when the reader cancels. Then the future resolves successfully, matching Wasmtime'sOk(())when the reader drops the stream.No-follow path operations on a symlink that leaves the directory (problem 4)
#ensureSandboxedPathresolved a symlink in the final path component even for operations that do not follow it. On a link such assub/loop -> ..,stat-atandmetadata-hash-atwithoutsymlink-follow,readlink-atandunlink-file-atfailed withnot-permitted. Rust'sstd::fs::read_dirhits this throughmetadata-hash-at.#ensureSandboxedPathnow takes a requiredfollowFinalargument, and each caller states whether the operation follows the final component. Intermediate components are always checked.open-at, and the source path oflink-at, still check the final component, because Windows has noO_NOFOLLOWandlinkfollows symlinks on macOS.Two related behaviour changes:
readlink-atrejects an absolute target withnot-permittedlike cap-std. So the relaxed check does not expose host paths. Previously an absolute target inside the directory was returned as the absolute host pathset-times-atwithoutsymlink-followkeeps the symlink's timestamp forno-change, instead of copying the target'sTest
test/filesystem.jsadds 16 tests:readViaStreamreads of different filesreadViaStreamandreadDirectorystreams after one chunk or entry, both through the async iterator'sreturn(), as generated bindings do, and throughStreamReader.close()sub/loop -> .., an absolutereadlink-attarget, and operations that follow the link or pass through it, which must still be rejected