Skip to content

linux: close four path- and fd-policy gaps in the syscall handlers - #108

Open
perbu wants to merge 1 commit into
masterfrom
syscall-policy-fixes
Open

linux: close four path- and fd-policy gaps in the syscall handlers#108
perbu wants to merge 1 commit into
masterfrom
syscall-policy-fixes

Conversation

@perbu

@perbu perbu commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator

Fixes #94, #95, #96 and #97 — the four policy gaps from the audit. Each is small and independent, but they are all in the same handler file and share one theme (a path or fd check that does not actually gate what follows), so they are batched.

One judgement call worth review, on #94. The obvious fix is to clear O_CREAT|O_TRUNC|O_APPEND before building open_how, but then open(path, O_RDONLY|O_CREAT) on a missing file "succeeds" without creating it, and O_TRUNC "succeeds" without truncating — the guest is told something that is not true. This instead denies the open with -EACCES. Mutation is what the write branch and is_writable_path() are for. Happy to switch to stripping if you would rather keep the call succeeding.

setsockopt grows a try/catch as part of the #97 fix: translate_writable_vfd() throws for a non-writable fd, and that handler had no catch, so switching to it without one would have turned a policy check into a foreign exception escaping system_call() — the bug in #101/#102.

Tests: the openat, statx and unlinkat cases already written for the audit, plus a new fcntl case (#97 had none) that checks both the return value and, via F_GETFL, that O_NONBLOCK did not stick on the host fd. Verified it goes red without the fix.

tests/unit/syscalls.cpp is 16/16 green on this branch. The pre-existing tegridy failures and the reset "crash recovery" failure are untouched and also fail on master.

🤖 Generated with Claude Code

openat (#94): the read-only branch passed the guest's O_CREAT, O_TRUNC and
O_APPEND straight into openat2's open_how, having consulted only the
readable-path policy. may_open() adds MAY_WRITE for O_TRUNC and honours
O_CREAT regardless of the O_RDONLY access mode, so a path approved for
reading could be truncated or created.

Deny the open with -EACCES rather than quietly clearing the bits: a guest
told the open succeeded would go on believing the file had been truncated
or created. Creating and truncating belong on the write branch, which
consults is_writable_path().

statx (#95): the -EPERM from the readable-path check sat in a bare `if`
with no `else` and no early return, so the host statx() below it ran
anyway and overwrote sysret(). Gate the fd translation and the host call
the way the sibling newfstatat handler already does, keeping the empty-path
(AT_EMPTY_PATH) case.

unlinkat (#96): the handler read flags from sysarg(1) and the path pointer
from sysarg(2), so the pathname pointer was OR'd into the flags word and
the flags word was dereferenced as a string. It also called
is_writable_path() before the path was populated -- the policy callback
only ever saw "" -- and OR'd in AT_SYMLINK_NOFOLLOW, which unlinkat does
not accept. Read the registers in the Linux order, mask flags to
AT_REMOVEDIR, resolve the path before consulting the policy, and adopt the
try/catch -> -EBADF idiom the neighbouring handlers use. An empty path now
returns -ENOENT instead of being handed to the policy layer.

fcntl(F_SETFL) and setsockopt (#97): both mutate the host fd but resolved
the vfd with the plain translate(). Use translate_writable_vfd(), as
F_SETLK64 in the same handler already does. setsockopt had no try/catch,
so it grows one -- translate_writable_vfd() throws for a non-writable fd,
and that must not escape system_call().

Tests: the openat, statx and unlinkat cases in tests/unit/syscalls.cpp,
plus a new fcntl case that checks both the return value and, via F_GETFL,
that O_NONBLOCK did not stick on the host fd.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.

openat: read-only branch passes O_CREAT/O_TRUNC/O_APPEND through to openat2

1 participant