nfs: don't reject WRITE on read-only-mode files (fixes git fsync EPERM on macOS)#339
nfs: don't reject WRITE on read-only-mode files (fixes git fsync EPERM on macOS)#339connorblack wants to merge 2 commits into
Conversation
…M on macOS) The macOS `agentfs run` overlay is exposed over a localhost NFSv3 server. `nfsproc3_write` gated every WRITE on the file's current mode bits via `permissions::can_write`, returning NFS3ERR_ACCES when the owner-write bit was clear. That is wrong for NFSv3: the protocol is stateless and has no OPEN procedure, so the server never sees the open(2) that authorized the descriptor. POSIX checks write permission once, at open time; later write(2)/fsync(2) on an already-open writable fd must succeed regardless of the file's mode bits. git relies on exactly this pattern: it creates loose objects with `git_mkstemp_mode(tmp, 0444)` (read-only mode, writable fd), writes, then fsyncs in `close_loose_object()`. On macOS the NFS client buffers the write and only flushes it as a synchronous WRITE RPC at fsync/close time, which hit the can_write check and failed with "fatal: error when closing loose object file: Permission denied" — making `git add`/`git commit` unusable inside `agentfs run`. Remove the mode-bit gate from the WRITE handler. Write authorization is enforced where NFSv3 expects it: the client kernel checks permission at open time via the ACCESS procedure (`nfsproc3_access` -> `permissions::compute_access`), which is unchanged. This matches upstream nfsserve, whose WRITE handler performs no mode-bit check. Adds nfs_handlers_write_perm_test.rs driving nfsproc3_write end-to-end over a real AgentNFS VFS: asserts WRITE on a 0444-mode file owned by the caller returns NFS3_OK (regression: was NFS3ERR_ACCES) and that an ordinary 0644 file still writes. Verified with a freshly built binary: the documented fsync repro and a real `git add`/`git commit` now succeed inside `agentfs run`; cli (32) and sdk (101) test suites pass; clippy clean.
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds regression coverage and adjusts NFSv3 WRITE handling to avoid rejecting writes based on file mode bits, fixing a macOS agentfs run failure when git writes to loose objects with mode 0444.
Changes:
- Remove the mode-bit write permission gate in
nfsproc3_write. - Add regression tests to ensure WRITE succeeds on
0444and0644files. - Wire the new test module into
nfs_handlers.rsundercfg(test).
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| cli/src/nfsserve/nfs_handlers.rs | Removes server-side mode-bit gating for WRITE and registers new regression tests. |
| cli/src/nfsserve/nfs_handlers_write_perm_test.rs | Adds tokio-based regression tests validating WRITE behavior for readonly/writable modes. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Follow-up on this branch: while addressing the review nits I found a second real bug on the same NFSv3 permission path and folded it into head In Local verification on the pushed head:
|
Summary
On macOS,
agentfs runexposes the copy-on-write overlay via a localhost NFSv3 server.nfsproc3_writegated every WRITE on the file's current mode bits (permissions::can_write), returningNFS3ERR_ACCESwhen the owner-write bit was clear.That's incorrect for NFSv3: the protocol is stateless and has no OPEN procedure, so the server never sees the
open(2)that authorized the descriptor. POSIX checks write permission once, at open time — laterwrite(2)/fsync(2)on an already-open writable fd must succeed regardless of the file's mode bits.Impact:
gitis unusable insideagentfs runon macOSgit creates loose objects with
git_mkstemp_mode(tmp, 0444)(read-only mode, writable fd), writes, then fsyncs inclose_loose_object(). On macOS the NFS client buffers the write and flushes it as a synchronous WRITE RPC at fsync/close — which hit thecan_writecheck:So
git add/git commitfail in the sandbox, which breaks the documented agentic-coding "commit + push" workflow on macOS.Minimal reproduction
Run it from inside the overlaid cwd — on macOS
/tmpis a host passthrough, so a path there does not exercise the NFS server.Fix
Remove the mode-bit gate from the WRITE handler. Write authorization is enforced where NFSv3 expects it: the client kernel checks permission at open time via the ACCESS procedure (
nfsproc3_access->permissions::compute_access), which is unchanged — unauthorized opens are still rejected. This also matches upstreamnfsserve, whose WRITE handler performs no mode-bit check.Tests
cli/src/nfsserve/nfs_handlers_write_perm_test.rsdrivesnfsproc3_writeend-to-end over a realAgentNFSVFS: asserts WRITE on a0444-mode file owned by the caller returnsNFS3_OK(regression: wasNFS3ERR_ACCES), and that a0644file still writes; reads the bytes back to avoid a vacuous pass.cargo testpasses (cli 32, sdk/rust 101);cargo clippyclean;git add/git commitverified working insideagentfs runwith a freshly built binary.Note
nfsproc3_setattrcarries the same-class gate on truncate (acan_writecheck beforeftruncate). It is not exercised by this bug (git never truncates a read-only loose object), so it is left as a follow-up to keep this change focused.