Skip to content

wasi/fs: per-descriptor directory mutation requires mutate-directory, refuses read-only - #225

Merged
lannbot merged 1 commit into
mainfrom
fs-mutate-directory-flag
Aug 22, 2026
Merged

wasi/fs: per-descriptor directory mutation requires mutate-directory, refuses read-only#225
lannbot merged 1 commit into
mainfrom
fs-mutate-directory-flag

Conversation

@lannbot

@lannbot lannbot commented Aug 22, 2026

Copy link
Copy Markdown
Collaborator

Resolves the residual left open in #194: which flag authorizes directory mutation on a descriptor, and with what refusal code. Decided by the wasi-filesystem WIT text (identical in the 0.2.x and 0.3 WITs), which turned out to mandate more than the residual asked.

The WIT authority

descriptor-flags.mutate-directory:

When this flag is unset on a descriptor, operations using the descriptor which would create, rename, delete, modify the data or metadata of filesystem objects, or obtain another handle which would permit any of those, shall fail with error-code::read-only if they would otherwise succeed. This may only be set on directories.

plus the open-at doc paragraph making the handle-escalation rule explicit.

Changes (both tracks, shared provider)

  • Directory-mutating ops require mutate-directory specifically — the wasi/filesystem: directory-mutating ops never checked descriptor flags #194 residual. write no longer passes: create-directory-at, remove-directory-at, unlink-file-at, symlink-at, set-times-at, and both ends of rename-at/link-at.
  • Refusal code is read-only, not bad-descriptor — WIT-mandated (above). The landed choice in the read-only PR predates reading that sentence.
  • open-at enforces the escalation clause: through a base directory lacking mutate-directory, requests for write/mutate-directory descriptor-flags or create/truncate/exclusive open-flags refuse read-only. Previously a {read} directory descriptor could mint a {write} child, making the per-descriptor layer decorative. (exclusive goes beyond the WIT's literal create/truncate, mirroring the package-level enumeration; noted in a comment.)
  • File-write leaves require write alone and keep bad-descriptor (WIT-silent; POSIX EBADF). mutate-directory no longer launders file writes ("may only be set on directories"). set-times type-dispatches: directory → mutate-directory/read-only, file → write/bad-descriptor.
  • Kind before permission: path-ops through a file descriptor report not-directory (wasmtime parity: Descriptor::dir()NotDirectory), never a bogus read-only.

The package-level grant (writable, refusing read-only first on every mutating leaf) is untouched.

Interop notes

  • wasi-libc (p2) always requests write|mutate-directory together (openat.c), so the tightening costs p2 guests nothing.
  • The p1 adapter never requests mutate-directory — but POSIX directory opens are O_RDONLY, so those descriptors carry neither bit and were already refused by the landed check.
  • wasmtime is no authority here: it does not enforce guest-requested flags per-descriptor at all (host-config perms only, refusing not-permitted), and its get-flags derives mutate-directory from the open mode.

Versioning

No breaking label: the published API surface is unchanged; the observable error-code change (bad-descriptorread-only on per-descriptor directory refusals) is a spec-conformance fix within 0.x. Relabel at cut time if judged otherwise.

Gates

Full just gates pass (build, test-rust, test-protocol, test-runtime, test-wasi, test-sockets-node, test-ct-runner, test-bundle, test-version-guard, publish-check, test-npm, examples, test-translate, conformance, sched-seeds, shells, browsers, smoke-tls, smoke-c0).

Closes #194.

… refuses read-only

The #194 residual, resolved on the WIT's side: directory-mutating ops
(create/remove-directory-at, unlink-file-at, rename-at and link-at on
both ends, symlink-at, set-times-at) now require the mutate-directory
flag specifically — write no longer suffices — and refuse with the
WIT-mandated read-only code, not bad-descriptor. File-write leaves keep
bad-descriptor (WIT-silent; POSIX EBADF) and stop accepting
mutate-directory. set-times splits by descriptor kind.

open-at now enforces the same doc's escalation clause: a base directory
without mutate-directory cannot mint a handle carrying
write/mutate-directory or create/truncate/exclusive — previously a
{read} directory descriptor could openAt a {write} child, making the
per-descriptor layer decorative.

Path-ops check kind before permission (not-directory on a file
descriptor, wasmtime's Descriptor::dir() ordering).

Interop cost is nil in practice: wasi-libc (p2) always requests
write|mutate-directory together, and p1-adapter directory descriptors
carry neither bit (POSIX dirs open O_RDONLY).

Closes #194.
@lannbot
lannbot enabled auto-merge August 22, 2026 23:29
@lannbot
lannbot merged commit 58f2b7a into main Aug 22, 2026
4 checks passed
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.

wasi/filesystem: directory-mutating ops never checked descriptor flags

2 participants