Repository navigation
Add Linux NativeJail hard resource enforcement - #103
Conversation
Adds a Linux-only test asserting the native jail refuses a config with an explicit disk_bytes limit it cannot enforce. This guards against older serde behavior silently dropping the unknown field, which would otherwise turn an unsupported hard limit into an unrestricted native launch. Auto-committed-on: dragonfly
Introduce NativeJailResourceRequest with optional CPU, memory, pids and disk limits, and NativeJailRequest extending NativeJailConfig for contract 2.1 members. Disk is modelled explicitly so backends that cannot enforce a hard quota must refuse rather than silently ignore it. Auto-committed-on: dragonfly
Bumps the contract version to 2.1 and exports the new native jail request types along with the wire name for the resource-aware jailed exec member. The 2.0 request shapes are left unchanged so existing modules keep binding. Auto-committed-on: dragonfly
The wire snapshot tests still expected contract version 2.0, so they now assert 2.1 to match the current protocol version. Auto-committed-on: dragonfly
Add a test asserting that the 2.1 resource request stays additive to the jail config and that disk_bytes survives serialization explicitly. It also pins the resource version constant so future bumps are caught. Auto-committed-on: dragonfly
Introduce supports_native_jail_resources so hosts can verify a module advertises the resource-aware native jail members before sending a 2.1 request. Tests were updated to cover the new capability check and to keep the legacy all-resource request failing closed on Linux. Auto-committed-on: dragonfly
CPU millis were divided by ten as integers, so any quota below ten millis rounded down to zero and systemd saw no limit at all. The quota is now formatted with its tenth-percent digit retained. Auto-committed-on: dragonfly
Introduce NativeJailCommand and native_jail_argv to construct the systemd-run and bubblewrap launcher for the namespace-backed native directory jail. The builder validates request shapes and rejects unsupported dimensions such as subprocess denial rather than silently rounding or dropping limits. Auto-committed-on: dragonfly
Re-export NativeJailCommand and native_jail_argv from the sandbox module so callers can build and inspect native jail invocations without reaching into private module paths. Auto-committed-on: dragonfly
Reformatted long expressions, argument lists, and import ordering to satisfy rustfmt, with no behavioural change. Auto-committed-on: dragonfly
The root directory check now uses a match on the canonicalize result so a failed canonicalization is treated as invalid instead of relying on the `is_err_or` helper. A test covers native jail argv generation to confirm limits, namespace flags, and the target environment are preserved. Auto-committed-on: dragonfly
Reformatted the native jail command test to satisfy rustfmt, wrapping long assertions and the systemd delimiter lookup across multiple lines. No test behaviour changed. Auto-committed-on: dragonfly
Add an optional native_resources field carrying the Contract 2.1 resource subset for the namespace-backed native jail, leaving the legacy all-four-dimensions resources field untouched. Preflight now derives constraints from whichever of the two fields is set. Auto-committed-on: dragonfly
The validation path previously skipped checks when a jail requested native resources instead of portable ones, allowing zero or empty resource requests to reach the backend. Added checks that reject zero values for cpu, memory, pids, and disk limits, and reject requests where no native resource is set at all. Auto-committed-on: dragonfly
…quests Return ConstraintNotEnforced instead of InvalidResourceLimit when a native backend receives a request with no resource limits set. The empty request is valid input, so the error should describe the backend's inability to enforce the constraint rather than blame the caller. Auto-committed-on: dragonfly
Auto-committed-on: dragonfly
Add a distinct 2.1 subset request path that applies CPU, memory, pids and disk limits through the trusted namespace launcher while preserving the existing 2.0 config. The jail now mounts tmpfs over /run and /sys so a same-UID workload cannot reach a user-scope manager socket or host cgroup filesystem to escape its transient resource scope. Auto-committed-on: dragonfly
Introduce a start_jailed_exec_with_resources entry point that wraps the native jail request in a Resources variant, and thread a NativeJailRequest enum through the internal start_with_jail and start_with_preparation helpers so existing callers pass the Legacy variant. This prepares the spawn path to distinguish resource-backed jails from legacy configs. Auto-committed-on: dragonfly
Introduce a collector variant that prepares the jail with resource limits and wires it to the resource backend. The execution stream now passes the resolved request mutably so the jail can adjust it, and jail handling uses NativeJailRequest throughout. Auto-committed-on: dragonfly
Adds the contract 2.1 resource-aware jailed exec member to the bus contract and module method list, and routes it through the resource backend. The native jail now binds denied non-directory paths to /dev/null instead of masking them with a tmpfs, and only applies MemorySwapMax when a memory limit is set. Auto-committed-on: dragonfly
Auto-committed-on: dragonfly
The native jail now rejects roots, working directories, and grants whose canonical paths differ from the requested paths, and refuses to bind over /run, /sys, or /proc. Network access is always denied via a private network namespace, which also isolates AF_UNIX abstract sockets and closes the same-UID user-manager escape path. Tests were updated to use the real workspace paths instead of the removed /workspace bind. Auto-committed-on: dragonfly
# Conflicts: # crates/tinybox-bus/src/lib.rs # crates/tinybox-bus/src/shell.rs # crates/tinybox-module/src/tinybus_module/README.md # crates/tinybox-module/src/tinybus_module/native_jail.rs # crates/tinybox-module/src/tinybus_module/resources_stream.rs
# Conflicts: # crates/tinybox-linux/src/sandbox/args.rs
Add a per-execution jail backend that validates the negotiated 2.1 resource request and binds the launcher argv produced from it, so a spawn is only allowed when the command matches the validated plan. Resource limits are enforced through the native launcher while the target environment is carried inside bubblewrap. Auto-committed-on: dragonfly
Auto-committed-on: dragonfly
Dropped a redundant `std::path::PathBuf` import that was already covered by the combined `std::path::{Path, PathBuf}` import on the next line.
Auto-committed-on: dragonfly
The native jail backend now clones program and argument OsStrings directly instead of converting through raw bytes. The legacy collector call also passes the default jail backend explicitly to match the updated signature. Auto-committed-on: dragonfly
The expected argument list for the namespace jail test no longer includes RestrictAddressFamilies, matching the current set of properties passed to systemd-run. Auto-committed-on: dragonfly
The resource-aware namespace jail test now reads the launcher's cgroup-v2 membership and asserts the configured memory, swap, pids and CPU limits are actually applied. A short sleep was added to the jailed script so the cgroup can be inspected while the process is still alive. Auto-committed-on: dragonfly
The Unix ownership and mode checks in validate_trusted_program are now compiled only on Unix targets, with a non-Unix stub returning an Unsupported error. This keeps the crate building on platforms that lack the Unix metadata extensions while making the failure explicit. Auto-committed-on: dragonfly
The sandbox test module is now compiled only on Linux targets, and the native jail tests call native_jail_argv_with_programs with explicit systemd-run and bubblewrap paths instead of resolving them internally. This keeps the tests hermetic and lets them run on hosts where the trusted launchers are not installed at the usual locations. Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
…c/sandbox/mod_tests.rs Auto-committed-on: cuttlefish Co-authored-by: Medulla <medulla@tinyhumans.ai>
Auto-committed-on: cuttlefish Co-authored-by: Medulla <medulla@tinyhumans.ai>
Auto-committed-on: cuttlefish Co-authored-by: Medulla <medulla@tinyhumans.ai>
Auto-committed-on: cuttlefish Co-authored-by: Medulla <medulla@tinyhumans.ai>
Auto-committed-on: cuttlefish Co-authored-by: Medulla <medulla@tinyhumans.ai>
Auto-committed-on: cuttlefish Co-authored-by: Medulla <medulla@tinyhumans.ai>
# Conflicts: # Cargo.lock # Cargo.toml # crates/tinybox-module/tests/grants_manifest_e2e.rs
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Tiny Sweeper review
|
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
Co-authored-by: Medulla <medulla@tinyhumans.ai>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d344acd789
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| "--noprofile".to_owned(), | ||
| "--norc".to_owned(), | ||
| "-c".to_owned(), | ||
| "[ -d /proc/self/fd ] || exit 125; shopt -s nullglob; fdpaths=(/proc/self/fd/*); ((${#fdpaths[@]} > 0)) || exit 125; for fdpath in \"${fdpaths[@]}\"; do fd=${fdpath##*/}; case $fd in 0|1|2|*[!0-9]*) continue;; esac; eval \"exec ${fd}>&-\" || exit 125; done; unit=; for arg in \"$@\"; do case $arg in --unit=*) unit=${arg#--unit=};; esac; done; [ -n \"$unit\" ] || exit 125; \"$@\"; status=$?; if ! /usr/bin/systemctl --user stop \"$unit\"; then /usr/bin/systemctl --user is-active --quiet \"$unit\" && exit 125; fi; exit \"$status\"".to_owned(), |
There was a problem hiding this comment.
Keep target arguments out of cleanup-unit selection
For resource-aware executions, the supervisor scans every launcher argument for --unit=, including target argv and environment values after Bubblewrap's separator. A target argument such as --unit=unrelated.service therefore replaces the generated scope name, and the trusted host-side supervisor subsequently stops that user service. Executing the unit-selection prefix confirms this substitution; systemctl --help describes stop UNIT... as “Stop (deactivate) one or more units”. This gives target-controlled data access to a host-manager operation despite the namespace restrictions. Pass the generated cleanup unit separately instead of rediscovering it in untrusted arguments.
AGENTS.md reference: AGENTS.md:L48-L52
Useful? React with 👍 / 👎.
| .any(|path| { | ||
| ["/run", "/sys", "/proc"] | ||
| .iter() | ||
| .any(|protected| path.starts_with(protected)) |
There was a problem hiding this comment.
Reject grants that replace the protected filesystem root
A resource-aware request with read_only: ["/"] passes this validation because / does not start with /run, /sys, or /proc. Its bind is emitted after the private mounts, restoring access to the host's user-manager socket and defeating the resource-scope escape protection. The Bubblewrap documentation confirms filesystem operations are “applied in the order they are given”. Unlike the existing jail builder's replaces_private_mount check, this validator does not reject /. Reject grants that cover protected mounts, not only paths underneath them.
AGENTS.md reference: AGENTS.md:L48-L52
Useful? React with 👍 / 👎.
| for (source, destination, mode) in request | ||
| .read_only | ||
| .iter() | ||
| .map(|path| (path, path, "--ro-bind")) | ||
| .chain(request.read_write.iter().map(|path| (path, path, "--bind"))) |
There was a problem hiding this comment.
Apply nested read-only grants after their writable ancestors
When a read-only grant is nested inside an additional writable grant, this loop mounts the read-only child first and then overlays its writable parent, making the supposedly protected child writable. For example, read_only=[/data/vendor] and read_write=[/data] permit modifications to /data/vendor. The Bubblewrap documentation states filesystem operations are “applied in the order they are given as arguments”. The existing jail builder sorts mounts shallowest-first and already has a regression test for this exact grant combination; preserve that ordering in the resource-aware builder too.
AGENTS.md reference: AGENTS.md:L48-L52
Useful? React with 👍 / 👎.
| for (key, value) in &request.env { | ||
| argv.extend(["--setenv".into(), key.clone(), value.clone()]); |
There was a problem hiding this comment.
Transport target environment values outside launcher argv
For resource-aware requests containing credentials in the target environment, these --setenv arguments place the credential values directly into the host-side Bash, systemd-run, and Bubblewrap command lines. Other host users can read those values through /proc/<pid>/cmdline on ordinary Linux procfs configurations, even though the variables are installed only inside the sandbox. The existing Bubblewrap launcher explicitly avoids this exposure by transporting arguments through a private file and --args descriptor (crates/tinybox-jail/src/bwrap/mod.rs, “Environment and credentials”). Reuse an equivalent protected argument transport for this launcher.
Useful? React with 👍 / 👎.
Summary
Adds a TinyBox bus 2.1 native resource request for Linux CPU, memory, and pids limits while preserving the 1.22 and 2.0 request shapes. Linux runs the target inside a systemd transient scope and Bubblewrap namespaces, applies the requested cgroup limits, and refuses unsupported disk, networking, and subprocess-denial requests. The older all-four-dimensions request remains fail-closed.
The trusted supervisor closes inherited non-stdio descriptors, prevents target environment values from reaching the launcher, uses exact CPU quota conversion, and reaps the complete workload scope on normal exit, cancellation, and resource close. Live tests check cgroup values and pressure events, allowed and denied files, manager and inherited-FD escape paths, fractional CPU throttling, output/status events, and detached descendants.
This is Linux NativeJail phase-two progress for tinybox#27; it does not complete the wider epic or claim resource support on macOS/Windows. PR #100's contract and workspace changes are now on main. Keep this PR in draft until TinyBox publishes the compatible native resource contract and the OpenHuman host adapter has been updated for that released contract.
Validation
cargo fmt --all -- --checkcargo clippy --workspace --all-targets --all-features -- -D warningsTINYBOX_REQUIRE_NATIVE_RESOURCE_TESTS=1 cargo test -p tinybox-module native_ -- --nocapture(49 passed)TINYBOX_REQUIRE_NATIVE_RESOURCE_TESTS=1 cargo test -p tinybox-module resource_aware_bus_exec_cancellation_reports_events_and_reaps_descendants(1 passed)cargo test --workspace --all-features(passed)scripts/check-coverage.sh(all source files at least 90%)actionlint .github/workflows/ci.ymlA dedicated Ubuntu CI job installs Bubblewrap and starts the runner user's systemd manager, verifies transient scopes, then requires the live native proof tests instead of allowing them to skip.