rsandbox: RPython-style compile-time sandbox for pyre (#285) - #304
Merged
Conversation
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #285.
Implements an RPython/PyPy-style sandbox for pyre. pyre's interpreter is never
translated to a standalone binary (there is no genc backend), so the faithful
analog of RPython's
translate.py --sandboxisrustc+ cargo as the"translation", and
#[cfg(feature = "sandbox")]as the compile-time rewrite:the
sandboxfeature compiles out the real-syscall implementations andcompiles in marshalling trampolines, reproducing RPython's security property
— syscall code unreachable from untrusted Python — at compile time.
Untrusted Python runs as
pyre --features sandbox, driven by a trustedpyre interactcontroller over the rmarshal wire protocol and a virtualfilesystem. All OS access is mediated; the host is never touched directly.
Architecture
pyre-sandboxcrate — the single live home shared by the untrusted clientand the trusted controller: byte-exact
rmarshalwire codec,protocol,trait-based
vfs,clienttrampoline half, and thesandlib/controllerports of
sandlib.py+pypy_interact.py.pyre interact(inpyrex) drivesit. Deps:
libc+stdonly.host_seam(interpreter) — the single indirection to the OS. Two#[cfg]-selected impls:RealHost(today's libc/std bodies, off sandbox) andTrampolineHost(marshals to the controller, on sandbox). Builtins arenon-capturing
fnpointers, so the seam is reached by module path throughhost_seam::ops::*.Defense layers
posix/timeOS surface gothrough
host_seam::ops::*(marshalled to the controller).raise;
_socket/_ctypes/_posixsubprocess/_signal/fcntl/… modules arecompiled out entirely. Import resolution is routed through a seam-backed
SourceProvider(nostd::fs), soimportcannot read arbitrary host files.host_seam::sysfacade — under sandbox,sysre-exportsonly libc TYPES, CONSTANTS and curated PURE functions; the mediated modules
name libc as
crate::host_seam::sys, so any direct syscall call left outsidethe seam is a compile error. A green
cargo build --features sandboxisthe proof.
filter after startup and before untrusted code: a curated allowlist of
host-neutral runtime syscalls (memory, signals, time, I/O on the open
marshalling fds), everything else
SECCOMP_RET_KILL_PROCESS. This is theanalog of RPython's
os_level_sandboxingand covers what the source-levelseam cannot: the linked
host_envcrate, std, or a syscall reached by amemory-safety exploit.
A 2026-06-27 escape audit (workflow, adversarially verified) found 40 reachable
escapes in an earlier import-free state (import spawned
python3, read hostfiles via
HostFsProvider,sendfile/readlink/scandir/… unstubbed). All areclosed here and locked by the e2e regression guard.
Verification
pyre/check.py --backend dynasm: 160/160 ×2 (default feature set, sandboxOFF — all changes are sandbox-cfg or
not(sandbox), so the default build isbyte-identical).
cargo build --release -p pyrex --bin pyre --features sandbox: green (thefails-closed facade proof).
pyre-sandbox/tests/e2e_interact.rs, port oftest_pypy_interact.py):2/2 — virtual-FS read,
/etc/passwd+ write attempts blocked, and aregression guard asserting the audited escape surface stays closed.
sandbox-build(ubuntu): builds the sandbox binary and runs thee2e — and, on Linux, validates the seccomp allowlist (a SIGSYS-killed child
surfaces as a failed run).
Caveats
cargo check --target x86_64-unknown-linux-gnu) but not runtime-validated locally (dev ismacOS, where seccomp is
cfg-d out); the Linux CI e2e is the runtime check. Amissing runtime syscall fails safe (over-restrictive: the child is killed, not
escaped). It is default-on under
sandboxon Linux;PYRE_SANDBOX_NO_SECCOMPbypasses it for direct-run debugging (the controller
env_clears the child, soit cannot be disabled through the real path).
sandbox = ["host_env"]is deliberate: thehost_envcrate stays linked butis unreachable from untrusted Python (and killed by seccomp if reached). The
majit-translatesandbox shells +rtyper.rs:734hook stay as inertRPython-parity mirrors (there is no genc backend to attach to).
🤖 Generated with Claude Code
Summary by CodeRabbit
pyre interactto run an external program inside a controlled sandbox with optional virtual filesystem roots, optional library root, and a timeout.sys.executable) with a virtualized environment and console behavior.