Skip to content

Let the sandbox override uname fields - #335

Merged
congwang-mk merged 6 commits into
mainfrom
uname-override
Oct 30, 2026
Merged

congwang-mk merged 6 commits into
mainfrom
uname-override

Conversation

@congwang-mk

@congwang-mk congwang-mk commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Let a sandbox report its own kernel identity in place of the host's:

Sandbox(fs_readable=[...], uname={"release": "6.1.0", "version": "#1 SMP"})
sandlock run --uname release=6.1.0 --uname "version=#1 SMP" -- make
[uname]
release = "6.1.0"
version = "#1 SMP"
sb := &sandlock.Sandbox{Uname: map[string]string{"release": "6.1.0"}}

Fields are sysname, release, version, machine and domainname; unset fields pass the host's value through. nodename stays the sandbox name.

Use cases

Reproducible builds. Builds that record the build kernel produce different artifacts on CI runners with different kernels. Common examples are configure logs and generated headers that capture uname -a, Perl's Config.pm (osvers) and build-info stamps. reprotest, the Debian Reproducible Builds checker, treats this as a variation source: its kernel variation changes the reported uname (via setarch --uname-2.6) to catch builds that leak it. Pinning release and version removes that variation. Together with features sandlock already has, a sandbox now covers several of reprotest's variations:

reprotest variation sandlock
kernel uname (this PR)
domain_host sandbox name (hostname) + uname domainname
time time_start
fileordering deterministic_dirs
num_cpus num_cpus

Hiding host details. A multi-tenant or agent sandbox no longer reveals the host's exact kernel build string through uname.

This is not an exploit mitigation. Kernel behavior (which syscalls exist, the Landlock ABI, the vDSO) still reveals the real kernel, and reducing attack surface through seccomp remains the actual defense.

Design

One fact reaches a process through several channels, so each override also answers the procfs file that repeats it: /proc/sys/kernel/{ostype,osrelease,version,arch,domainname} and /proc/version. Otherwise a plain cat would contradict uname(). /proc/version is built from the fields alone, without the host's compiler banner: the banner names the build host and differs across machines, so keeping it would defeat reproducibility.

This is a policy field rather than something users build with run_with_handlers. Built-in handlers run first and cannot be overridden, and the built-in uname handler always answers (it supplies the nodename), so a user handler on uname never runs. Under chroot/COW the built-in path handler answers every open first, so a user openat handler could not serve the procfs files either. The field is implemented as built-in handlers in the same dispatch table, which also makes it available from the CLI, profiles and Go, none of which have the handler API.

Validation lives in the core builder: unknown fields, nodename, values over 64 bytes (utsname fields are char[65]) and NULs are rejected, and --no-supervisor refuses the option.

While probing this, /proc/sys/kernel/hostname turned out to leak the real host name even though uname -n and /etc/hostname show the sandbox name. The first commit fixes that on its own.

Test plan

  • Rust unit tests: Uname validation, /proc/version rendering, utsname field truncation, profile round trip and rejection
  • Integration: test_uname_override checks uname -r/-v/-m against /proc/sys/kernel/{osrelease,version,arch} and /proc/version; test_hostname_virtualization covers /proc/sys/kernel/hostname
  • Python: TestUname (override plus unknown field rejection)
  • Go: TestUnameOverride, TestParseProfileMapsFields with [uname]
  • CLI by hand: profile [uname] merged with a field-wise --uname override; --no-supervisor rejection

🤖 Generated with Claude Code

The sandbox name replaces the host name in uname() and /etc/hostname,
but /proc/sys/kernel/hostname still returned the real host name, so a
plain cat undid the virtualization. Serve it from the same virtual
file as /etc/hostname so every channel reports one name.

Signed-off-by: Cong Wang <cwang@multikernel.io>
Uname lets a sandbox report its own kernel identity (release, version,
sysname, machine, domainname) in place of the host's, for hiding host
details and for reproducible builds. One fact reaches a process through
several channels, so each override also answers the procfs file that
repeats it (/proc/sys/kernel/*, /proc/version); patching uname() alone
would be contradicted by a plain cat. /proc/version is built from the
fields alone, without the host's compiler banner, which names the build
host and would keep the file differing across machines.

Signed-off-by: Cong Wang <cwang@multikernel.io>
Expose the uname override on the command line as a repeatable
FIELD=VALUE flag, matching --env. It needs the supervisor to answer
uname() and the procfs files, so --no-supervisor rejects it like the
other supervisor-mediated options.

Signed-off-by: Cong Wang <cwang@multikernel.io>
Python callers declare the override as a field-to-value mapping,
uname={"release": "8.0.0"}, the same shape as env. Each entry crosses
the FFI through a new sandlock_sandbox_builder_uname setter, so field
names and lengths are checked once, in the core builder.

Signed-off-by: Cong Wang <cwang@multikernel.io>
Profiles describe the same policy as the builder, so the uname
override gets a [uname] table keyed by utsname field. Unknown keys are
rejected by serde and lengths by the builder, which now checks Uname
on every build so a profile cannot bypass the per-field setter. A
CLI --uname still overrides a profile value field by field.

Signed-off-by: Cong Wang <cwang@multikernel.io>
Go callers get the uname override as a field-to-value map, the shape
Env and the Python SDK already use, and ParseProfile carries a
profile's [uname] table through instead of refusing it as a field Go
cannot express. Field names and lengths stay checked in the core.

Signed-off-by: Cong Wang <cwang@multikernel.io>
@congwang-mk
congwang-mk merged commit 6f0ed51 into main Oct 30, 2026
38 checks passed
@congwang-mk
congwang-mk deleted the uname-override branch October 30, 2026 17:58
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.

1 participant