Expand description
Host-process resource bounds for every native subprocess this runtime spawns.
Sibling of crate::limits, and deliberately not a replacement for it: that module bounds
a WASM guest inside its wasmtime store (linear memory, tables, epoch deadline), this one
bounds the operating-system processes the runtime forks — shell::execute_shell and
runtime::dispatch_native_tool. A capsule that cannot escape its containment can still
wedge the host it runs on: fork bombs, unbounded open files, unbounded writes, unbounded
per-process CPU and address space, and unbounded workdir growth are all denial of service,
not boundary defeats — nothing outside the granted scope is read, written, or reached — but
a wedged host is still a broken host.
Three mechanisms, in descending order of portability:
setrlimit(2)ceilings ([apply_hard_rlimits]), applied inside the forked child’spre_execwindow on every Unix platform. Per-process, POSIX-portable, no configuration required of the host.- a cgroup v2 scope ([
crate::cgroup]) around the whole subprocess tree, Linux only. This is what rlimits structurally cannot do:RLIMIT_NPROCis a per-uid ceiling, checked against every thread the uid owns on the host, so it bounds neither this tree nor anything else in particular.pids.maxon a cgroup counts only the scope’s own tasks, and a spawn that joins a scope gets noRLIMIT_NPROCfrom this module at all ([NprocBound]). - a periodic workdir-size check ([
WorkdirGuard]), on every platform. A plain filesystem walk on a fixed cadence, so a breach is caught within one poll interval — not instantly.
§A silent manifest means defaults, never “unlimited”
HostResourceLimits::resolve follows crate::limits::ExecutionLimits::resolve exactly:
the manifest’s capabilities.resources block with every omitted field replaced by its
DEFAULT_* constant, and the whole block’s absence treated as “every field omitted”. There
is no “unset” state downstream — a capsule that declares nothing is bounded identically to
one that declares every default by hand.
§Hard limits, not soft ones
setrlimit(2) takes both a soft (rlim_cur) and a hard (rlim_max) value, and an
unprivileged process may raise its own soft limit up to the hard one at any time. Against a
hostile agent a soft-only cap is therefore advisory: one setrlimit call from inside the
capsule undoes it. Every limit this module sets writes rlim_cur == rlim_max, leaving no
ceiling to raise into, and lowering rlim_max is irreversible for an unprivileged process.
Structs§
- Host
Resource Limits - Fully-resolved host-process limits for one session: the manifest’s
capabilities.resourcesblock with every omitted field replaced by itsDEFAULT_*.
Constants§
- DEFAULT_
CGROUP_ CPU_ PERCENT - Default cgroup
cpu.maxquota as a percentage of one core: 200 = two cores’ worth. - DEFAULT_
CGROUP_ IO_ BYTES_ PER_ SEC - Default cgroup
io.maxread+write throughput on the workdir’s backing device — 100 MiB/s. - DEFAULT_
CGROUP_ MEMORY_ BYTES - Default cgroup
memory.max— 4 GiB aggregate across the whole subprocess tree. Above the per-process ceiling on purpose: the tree bound exists to catch what many processes do together, so setting it at or below the single-process bound would make it redundant. - DEFAULT_
CGROUP_ PIDS_ MAX - Default cgroup
pids.max— 256 tasks aggregate across the whole subprocess tree. - DEFAULT_
CPU_ SECONDS - Default
RLIMIT_CPUhard ceiling — one hour of CPU time per spawned subprocess. - DEFAULT_
MAX_ FILE_ SIZE_ BYTES - Default
RLIMIT_FSIZEhard ceiling — 4 GiB, the largest single file a subprocess may write. - DEFAULT_
MAX_ OPEN_ FILES - Default
RLIMIT_NOFILEhard ceiling — open descriptors per spawned subprocess. - DEFAULT_
MAX_ PROCESSES - Default
RLIMIT_NPROCheadroom for a spawn with no cgroup scope — how much past the uid’s count at that spawn a subprocess tree may add, in whichever unit this platform’sRLIMIT_NPROCis checked against. Unused for a spawn that joins a scope. See [NprocBound] for which spawns get it and [uid_task_count] for the per-platform unit. - DEFAULT_
MEMORY_ BYTES - Default
RLIMIT_AS(Linux) /RLIMIT_DATA(macOS) hard ceiling — 2 GiB per subprocess. - DEFAULT_
WORKDIR_ MAX_ BYTES - Default ceiling on total workdir size — 10 GiB, checked periodically.