Skip to content

runtime: validate sandbox grant transport (#3433's later revision, by @adilalperenciftci) - #3466

Merged
paul-hammant merged 6 commits into
mainfrom
fix/sandbox-grant-transport
Oct 30, 2026
Merged

paul-hammant merged 6 commits into
mainfrom
fix/sandbox-grant-transport

Conversation

@paul-hammant

Copy link
Copy Markdown
Collaborator

Credit: this is @adilalperenciftci's work, the later revision of #3433 (ac2a506). It arrived after #3456 was built, so it missed that batch. Rebased onto main with his authorship kept; one test commit of ours on top. Thank you, Adil.

What it does

A single grant serialiser for both spawners, runtime/sandbox/aether_sandbox_grants.h (aether_sandbox_serialize_grants):

  • Grant-line injection is refused. Grants travel to the child as category:pattern lines. A pattern containing a newline could add a line of its own, e.g. *:* (allow everything). CR or LF in either field, or a : in the category, now fails the spawn with EINVAL. A : in a pattern stays allowed (host:port).
  • Nothing is silently dropped. The old loop stopped adding grants past 8000 bytes. A list that doesn't fit now fails with E2BIG. This supersedes the bound check Batch: seven sandbox, compiler and CI hardening PRs from @adilalperenciftci, with follow-ups #3456 added (e18798f); that guard is removed here.
  • A null list item fails the spawn instead of being skipped.

Rebase notes

  • Conflicts with e18798f in both spawners were resolved in favour of aether_sandbox_grants.h. aether_sandbox_shm.h ends up byte-identical to main's.
  • Our sandbox_grant_overflow test is kept; it passes, since E2BIG makes the spawn return -1.

Our commit

557e848 adds the injection cases the harness lacked: a newline in the category, CR in either field, and a trailing newline in the pattern.

Verification

  • macOS: make test 633 passed.
  • Real x86_64, Ubuntu 22.04: make ci with gcc -Werror exited 0 (runtime suite 634 passed). sandbox_grant_transport, sandbox_grant_overflow, sandbox_unique_shm, sandbox_source_fail_closed and sandbox_exec_env_inherit all pass under gcc 11 and clang 14.
  • The test catches the bug: against main's serialiser, sandbox_grant_transport fails on 8 cases, including every injection case. With this change it passes.
  • Not run: FreeBSD (lab box down). The BSD hunk is identical to the Linux one, and CI's FreeBSD job builds it.

For Nic: one contract change

An empty grant list used to make spawn_sandboxed return -1. Now the child runs with an empty grant source. The preload treats that as an active sandbox with zero grants (#3436's fail-closed logic), so nothing is allowed. That is safe, but "no grants = refuse" becomes "no grants = run with nothing allowed". Keep it, or restore the -1?

Also cosmetic: a validation failure still reports "cannot create shared memory for grants".

🤖 Generated with Claude Code

adilalperenciftci and others added 2 commits October 29, 2026 15:06
Signed-off-by: Adil Alperen Çiftci <adilalperenciftci@users.noreply.github.com>

Rebased onto main after #3456; conflicts with e18798f resolved in favour of aether_sandbox_grants.h.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@adilalperenciftci

Copy link
Copy Markdown
Contributor

@nicolas-maman For an empty grant list, should spawn_sandboxed return -1, or should it start the child with an active sandbox and zero permissions?

I will keep the implementation, regression test, and changelog aligned with that contract. If the child should start, I will add an end-to-end assertion that a forbidden operation is denied. I will also separate grant-validation diagnostics from shared-memory creation failures.

paul-hammant and others added 3 commits October 30, 2026 07:24
An invalid grant list (EINVAL) and an oversized one (E2BIG) were both
reported as a shared-memory failure. The caller now reports 'invalid grant
list', 'grant list too large' or the shm failure. Return value stays -1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The child tries a file read, a TCP connect and an exec and reports which were
not refused; an empty list must refuse all three, matching grants must allow
all three. With an inert preload the same test fails (child status 67).
sandbox_grant_transport also checks the two new diagnostics.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@paul-hammant

Copy link
Copy Markdown
Collaborator Author

@adilalperenciftci thanks for the offer and the question. To land this we've coded an empty grant list as "start the child, with an active sandbox and zero permissions". That matches docs/containment-sandbox.md, where sandbox.new("name") { } means deny all, and the no-grant worker example. @nicolas-maman can still revisit it later.

This push adds:

  • An end-to-end confinement test, tests/integration/sandbox_empty_grants. A child spawned with an empty list tries a file read, a TCP connect and an exec, and all three are refused. The same child with matching grants is allowed all three, so the test shows confinement and not a broken child. With an inert preload it fails (child status 67).
  • Separate diagnostics in the Linux and FreeBSD spawn paths: "invalid grant list" (EINVAL), "grant list too large" (E2BIG), or the shared-memory failure. The return value is still -1, and sandbox_grant_transport now checks both messages.
  • A line in docs/containment-sandbox.md, and an updated changelog fragment covering the empty-list contract and the injection/E2BIG checks.

Tested: macOS make test; on x86_64 Linux a gcc HARDEN=0 make ci run and the sandbox tests under gcc and clang.

🤖 Generated with Claude Code

The previous head (f49c475) carried [skip actions], which skipped CI for
the code commits under it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@paul-hammant
paul-hammant merged commit ec8b012 into main Oct 30, 2026
@paul-hammant
paul-hammant deleted the fix/sandbox-grant-transport branch October 30, 2026 07:43
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.

2 participants