NVIDIA / NVIDIA/OpenShell

SandboxConfirmEvidence requires Linux mechanisms no non-stock guest kernel can provide

Open
#3,361 1 comment 0 reactions 1 assignee View on GitHub

@drew is already working on this.

Since Sep 16, 2026.

area:sandbox state:accepted
Dominant language
Rust
Stars
8.7k
Forks
1.3k
Avg merge
2d 11h
Merged PRs (30d)
253

Description

Following up on the conformance question from #2048, where @drew asked for a proposal and offered to prototype one. Opening it as an issue rather than a PR comment since #2942 has merged and the interface crate is on main now. Scoped to a single thing: SandboxConfirmEvidence.

What we hit

We're planning to implement RFC 12 for Agent Substrate as a delegated backend. Our boundary is a gVisor sandbox, so the workload's kernel is the sentry rather than the host kernel.

SandboxConfirmEvidence::validate requires, among others:

&& self.seccomp.new_listener
&& self.seccomp.notification_round_trip
&& self.seccomp.addfd_send
// ... six more seccomp notification facts
&& self.landlock_abi >= 3
&& self.landlock_allow_deny

and ConfirmedBoundary::try_new is the only route to ReadyBoundary. gVisor implements neither mechanism. We measured it rather than inferring it, with a small static probe that just makes the two syscalls, run under the gVisor build we deploy (runsc release-20260824.0-120-g727c8c389c36, nightly 2026-09-02):

landlock/abi:          UNAVAILABLE (Function not implemented)   # ENOSYS
seccomp/new_listener:  UNAVAILABLE (Invalid argument)           # EINVAL

The same binary on the host returns Landlock ABI v9 and a listener fd, so that is the sentry and not a build or permissions artifact. It also matches gVisor's own syscall table (pkg/sentry/syscalls/linux/linux64.go):

syscalls.PartiallySupported("seccomp", Seccomp, "Only SECCOMP_SET_MODE_FILTER is supported, with flags set to 0 or SECCOMP_FILTER_FLAG_TSYNC.", nil)

SECCOMP_FILTER_FLAG_NEW_LISTENER is not in that set, and there is no Landlock implementation in the sentry at all. So the lifecycle cannot advance past confirm, and no amount of work on our side changes that. Happy to share the probe if it's useful for your own backends.

This isn't specific to us. Any backend whose guest kernel is not stock Linux lands in the same place, which includes Kata and microVM topologies with a reduced guest.

What makes me think this is a mismatch rather than an intended constraint is that the RFC asks for none of it. "Evidence", "seccomp" and "landlock" have zero hits in the merged text. The six invariants are behavioral throughout, the risks table keeps capabilities mandatory rather than mechanisms, and the topology matrix explicitly contemplates a workload running on "a VM guest kernel or a userspace application kernel". The interface crate then requires two specific Linux mechanisms, which closes the door the RFC left open.

What we'd propose

The struct already does it both ways. tcp_deny_round_trip, tcp_allow_round_trip and the two DNS round trips are properties: you prove them by attempting the thing and observing the outcome, and any mechanism that produces the outcome passes. landlock_abi >= 3 and seccomp.new_listener are mechanisms: you prove them by naming the kernel feature.

So the proposal is to finish in the direction the struct already started.

/// A security property established before untrusted code runs.
pub struct EnforcedProperty {
    /// Measured, not configured.
    pub enforced: bool,
    /// Backend-declared mechanism. Diagnostic and audit only; never authorizes.
    pub mechanism: String,
}

Then the mechanism-specific fields become the properties they stand for:

  • seccomp: SeccompEvidence becomes egress_interception and request_attribution
  • landlock_abi and landlock_allow_deny become filesystem_confinement

validate keeps its shape and checks .enforced. The co-located backend declares mechanism: "seccomp-notify" and "landlock-v4" and nothing about its behavior changes. A gVisor-backed backend declares "gvisor-vfs" and "envoy-ext-proc". The isolation-strength difference between those is real, and it lands where the RFC already puts it: conformance is behavioral, not a security-strength rating.

We don't have a strong view on the shape. An enum of known mechanisms works as well as a string, and if you'd rather keep the existing fields and add a parallel property block, that's fine too. The part that matters is that reaching Ready depends on the property holding rather than on which kernel feature produced it.

Conformance should check the property, not the attestation

The obvious objection is that a backend can set enforced: true and lie, and a declared mechanism is only worth as much as the check behind it. That's the other half, and openshell-conformance is the right place for it, because it drives the openshell CLI against a real deployment rather than exercising the trait directly. The smoke scenario already walks create, get, exec and delete and asserts the sandbox reaches Ready, so a scenario that asserts a violation fails slots in the same way.

Scenarios we'd suggest, each of which attempts a violation and requires failure:

  • egress-confinement: from inside the sandbox, attempt outbound TCP to a denied destination, to a raw IP with no DNS involved, and via an alternate resolver. All three must fail.
  • filesystem-confinement: attempt read and write outside the admitted allowlist. Must fail.
  • privilege-floor: attempt to gain privilege through a setuid binary. Must fail.
  • attribution: on an allowed connection, the supervisor's decision record carries a resolved binary identity, or an explicitly unavailable one where the runtime advertises endpoint-only per RFC 0005.

These are backend-neutral by construction and would catch a broken co-located backend as readily as a delegated one.

Happy to write them as scenarios and open a PR against openshell-conformance if that's useful.

On the offer to prototype: we can supply a backend that fails today's validate for exactly the reasons above, which gives you a concrete case to test whichever shape you land on.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.