Science
Greg Kurtzer's Ginux is now a userspace kernel
Key Points
Ginux is a configurable userspace kernel for isolated Linux compatibility instances. It runs as an ordinary Linux process and places an explicit, versioned contract between managed workloads and the host kernel. Important Ginux began as an independent Rust kernel that booted directly through UEFI and ran a Rocky Linux 10 userspace.
Ginux is a configurable userspace kernel for isolated Linux compatibility instances. It runs as an ordinary Linux process and places an explicit, versioned contract between managed workloads and the host kernel.
Important
Ginux began as an independent Rust kernel that booted directly through UEFI
and ran a Rocky Linux 10 userspace. That research phase is complete. Its
final source is permanently preserved on the
bootable-kernel
branch and at the
bootable-kernel-final
tag. Active development now continues here as a userspace kernel.
The standalone-kernel experiment proved that an AI coding agent, directed by an experienced human through architecture, gates, and executable tests, could construct a new kernel capable of running meaningful Linux userspace. It also made the limits of that result obvious.
Linux is much more than source code. It is a stable interface, a development process, a global community, vendor and hardware alignment, and decades of compatibility and operational experience. Reimplementing kernel mechanisms does not reproduce that ecosystem, and competing with Linux was never the goal.
The more useful result is the boundary the experiment exposed. Linux must preserve a broad userspace ABI; as Linus Torvalds put it, “And we simply do not break user space.” The kernel regression documentation records that rule. Ginux can sit above Linux and deliberately offer a smaller contract to each managed instance. Before a managed syscall has a host effect, Ginux decides whether to emulate it, perform a validated mediated operation, or deny it.
Put bluntly: Linux must not break userspace; Ginux is designed to break the full Linux ABI guarantee deliberately and selectively. A instance receives only the capabilities and interface behavior its approved workload requires. Every departure is intended to be explicit, versioned, reviewable, and tested, rather than accidental incompatibility.
This pivot is also a result of the development method. AI made it practical to build disposable but executable prototype code, test an architectural idea, learn from the running system, and carry the valuable parts into a better design. The prototype was evidence, not an obligation to turn every experiment into a product.
Host Linux kernel
`- host systemd (real PID 1)
`- ginux (ordinary trusted host process)
`- instance contract
|- virtual processes and identities
|- virtual descriptors and kernel views
|- assigned devices and resources
`- managed Rocky Linux userspace
`- instance systemd (virtual PID 1)
Ginux supervises native x86-64 programs and intercepts managed syscalls before their host effect. Its central dispatcher resolves the instance contract and chooses one of three outcomes:
- Emulate: answer entirely from Ginux-owned state.
- Mediate: validate and translate the request, then perform a narrow Ginux-owned host operation.
- Deny: return a configured Linux-compatible error or terminate the task.
The current implementation includes:
- a security-focused x86-64 interpreter and a balanced native ptrace backend;
- strict, versioned instance configuration and lifecycle;
- a digest-pinned OCI root, immutable filesystem index, and bounded overlay;
- Ginux-owned process, credential, signal, synchronization, timer, and event models;
- virtual devices with explicit assignment, ownership, and revocation; and
- bounded virtual
/proc
,/sys
, cgroup v2, mount, namespace, machine, and boot-identity views; - a host-shared network backend that remains syscall-mediated; and
- a pinned Rocky Linux 10 systemd baseline that reaches
multi-user.target
, authenticates a managed console login, updates through Rocky's enabled repositories, and shuts the instance down cleanly.
This is a named compatibility baseline, not a claim of complete Linux or
systemd compatibility. Its exact image, readiness marker, tests, evidence, and
limitations are recorded in
hdae/gates-todo/9-systemd-baseline.md
.
Ginux is also being tested as a containment boundary for AI agents and other automated software that can execute commands, modify files, open network connections, and start additional processes. Instead of giving an agent the host's complete Linux ABI, an operator can place its process tree inside a instance whose kernel contract exposes only the operations and virtual resources required for the task.
The research goal is a reviewable boundary spanning syscalls, ioctls,
filesystems, devices, networking, process authority, and virtual /proc
and
/sys
state. For example, a contract may allow an agent to read time, inspect
its own processes, and modify one assigned workspace while denying clock
changes, host filesystem access, unapproved network listeners, and unrelated
devices. Ginux can then audit the contract decisions made while the agent and
its child processes run.
This is an active verification target, not a claim that Ginux is currently a production-grade agent sandbox. The contract inventory, configurable policy, adversarial tests, performance costs, and escape boundaries are tracked through the HDAE gates and must be demonstrated before stronger containment claims are made.
The userspace-kernel idea is established research and engineering territory. Ginux explores a particular point in that design space:
| Approach | Primary boundary | Difference in emphasis |
|---|---|---|
| Linux seccomp | Filters host syscalls | Restricts the existing ABI rather than defining a complete virtual contract |
| User-Mode Linux | Runs Linux as a host process | Reuses a complete Linux kernel over virtual hardware |
| gVisor | Implements an application kernel in userspace | Closely related model with different compatibility and performance goals |
| Gramine | Packages applications with a library OS | Uses manifests and a library-OS boundary |
| Proxos | Routes calls between private and commodity OSes | Makes trust placement configurable across the syscall interface |
| Ginux | Gives each instance a versioned kernel contract | Treats the declared contract, virtual state, devices, resources, and lifecycle as one reviewable boundary |
Ginux does not promise the complete Linux ABI. Compatibility claims apply only to named contracts, workloads, and executable evidence.
The reference development environment is macOS arm64. QEMU TCG boots a pinned Rocky Linux 10 x86-64 host where Ginux runs as an ordinary unprivileged process. The host systemd remains real PID 1.
Check prerequisites:
make doctor
Prepare the pinned assets with explicit network operations:
make host-image-fetch
make image-helper-fetch
make instance-image-fetch
make systemd-image-fetch
Build and run the deterministic suite offline after the caches are populated:
make build
make check
make test
Inspect the latest completed capability directly:
make test-systemd # cached image, external networking disabled
make bench-prepare # one-time networked setup for the pinned benchmark image
make bench # matched native/Ginux matrix plus mandatory budget checks
make bench-modes-prepare # one-time setup for the three-way dispatch comparison
make bench-modes # native/selective/paranoid tables and graphs
make demo-gate-15 # bounded reproducible three-way comparison
make test-systemd-live # shared network plus real upstream dnf -y update
make host-run # general interactive Rocky host with Ginux installed
make host-reset # discard that host's cache, instances, and writable state
make demo-gate-9 # Gate 9 checkpoint through the same host workflow
Instance recipes select syscall control under [contract.syscalls]
:
[contract.syscalls]
control = "selective" # default; use "paranoid" for all-syscall mediation
default = "deny"
allow = ["*"]
deny = []
selective
is the default and permits direct host execution only
when the instance contract allows the operation and Ginux's versioned catalog
independently classifies it as direct-safe. Denials, virtual state, inspection,
translation, emulation, pointer or descriptor validation, and modified results
remain Ginux-dispatched in both modes. paranoid
is an explicit opt-in that
sends every syscall through the Ginux userspace dispatcher before completion.
make bench-compare
checks every row against the 4% native budget and, after an
accepted baseline is published under benchmarks/gate11/
, against that Ginux
baseline as well. Environment mismatches are reported separately from
performance regressions. make demo-gate-11
runs a bounded human-inspectable
comparison and returns nonzero when a mandatory row misses its budget.
Gate 15 provides a separate characterization workflow across native Rocky,
selective Ginux, and paranoid Ginux. Native is the no-mediation control, not a
Ginux instance mode. The checked-in templates under
benchmarks/gate15/
fix the complete test protocol and
may be copied without editing scripts. make bench-modes
produces paired raw
samples, metadata, a Markdown table, and an SVG graph under build/gate15/
.
See Reproducible performance measurement for the exact
protocol, customization workflow, output schema, and interpretation limits.
make host-run
boots the QEMU Rocky host and logs you into its ordinary shell.
Ginux is installed in PATH
. The first create
or import
downloads and verifies
the pinned Rocky OCI image with live per-layer progress; later operations reuse the
cache. The writable Rocky host overlay persists across make host-run
, ordinary
cleanup, builds, and tests, so cached images, instance definitions, roots, and
stopped status remain available. make host-reset
is the explicit destructive
operation that stops the host and removes that overlay; the next make host-run
starts from the pinned clean Rocky image.
A complete first session is:
ginux create rocky
ginux list
ginux status rocky
ginux start rocky --attach
create
starts from the documented default recipe. Use
ginux import NAME FILE
to create or atomically replace an instance from a
self-contained recipe, ginux export NAME
to print that recipe, and
ginux edit NAME
to validate and atomically apply an editor session.
Instances are contract-locked by default. For an explicit maintenance window,
stop the instance, run ginux unlock NAME
, perform the required maintenance,
stop it again, and run ginux lock NAME
. ginux list
and ginux status NAME
show the current state, and starting an unlocked instance prints a warning.
Unlocking relaxes configurable recipe restrictions only; Ginux still owns the
virtual kernel boundary, rejects unsupported authority, reduces host identity,
and applies NoNewPrivs
. Lock state is host deployment state and is never
written into an exported portable recipe.
Syscall auditing is disabled by default with [audit] enabled = false
. Add
--audit
to ginux start
to enable it for that run, or set enabled = true
to enable it for every start. Enabled audit records always go to the instance's
audit.log
; they are kept separate from the attached console.
Existing recipes using mode = "stderr"
or mode = "disabled"
remain
read-compatible and normalize to the new boolean form when rewritten.
The instance specification supports typed recipe overrides, ingress and egress contract policy, volumes, virtual identity, and ordered pre-boot hooks. Host network attachment and translated ports are deployment settings, supplied as repeatable flags when the instance is created or changed:
ginux import web /etc/ginux/examples/rocky-systemd.toml \
--network eth0:1 \
--forward protocol=tcp,external_port=8080,instance_port=80 \
--forward protocol=tcp,external_port=8443,instance_port=443
ginux set web \
--network default \
--forward none
Omitting --network
uses the host's default network. Multiple flags are
validated and persisted atomically; an invalid combination changes nothing.
Hooks run only as
mediated instance processes; Ginux does not support arbitrary host-context hooks.
See Gate 9.75 instance specification
for the schema, precedence rules, examples, and verification targets.
The same instance file contains a self-contained hierarchical contract under
[contract]
and its syscalls
, filesystem
, network
, devices
,
privileges
, process
, and kernel
subsections. Syscall names accept bounded
globs compiled against the pinned catalog; explicit denies always win. The
contract is part of the instance rather than a separate operator object.
Validate and inspect the complete instance recipe before launch:
ginux validate /etc/ginux/examples/rocky-systemd.toml
ginux inspect /etc/ginux/examples/rocky-systemd.toml
ginux explain /etc/ginux/examples/rocky-systemd.toml filesystem.path.write
See Self-contained userspace contract for the syntax, precedence, migration behavior, and complete CLI workflow.
The attached terminal becomes the Ginux instance console. Log in as root
with
password ginux
, explore normally, press Ctrl-]
to detach while leaving the
instance running, and run kill -RTMIN+4 1
as instance root to
shut down systemd and return to the host. Detached operation uses
ginux start rocky
, ginux status rocky
, and
ginux stop rocky
. Exit the Rocky host shell to stop QEMU cleanly.
This stops the VM without deleting its persistent overlay; make clean
resets
that host state. make demo-gate-9
uses the same operator workflow;
make run-systemd
remains as a compatibility alias, and
make test-systemd-interactive
is the automated regression.
The live test and interactive demonstration enable shared external networking. An upstream DNF update can take many minutes under QEMU TCG because every managed syscall is intercepted and audited. The offline systemd test uses only the prepared, digest-pinned cache.
Individual component and gate targets are documented in the Makefile
and the
corresponding files under docs/
and hdae/
.
The primary adversary controls a process inside an instance. The host kernel, hardware, Ginux executable and approved configuration, boot chain, and operator are currently trusted. The balanced native backend still relies on the host for ELF loading, address spaces, page faults, signals, scheduling, and ptrace; those remain host-kernel attack surface.
Ginux is research software. It has not received independent security certification, does not provide complete Linux compatibility, and is not ready for production workloads. Unknown contract operations fail closed, but current coverage and guarantees are limited to the tested gates.
The precise supported claims, retained supervisor authority, trusted components, exclusions, compatibility gaps, performance limitation, and reproducible boundary tests are documented in Security boundary and limitations.
For agent containment, the protected object is the host and any authority not explicitly assigned to the instance. Ginux constrains the Linux processes in which an agent and its tools execute; it does not inspect model reasoning, secure an external model provider, or make untrusted code safe outside the enforced instance boundary.
The full intent, threat model, invariants, and gate plan live in:
Ginux is developed with
Human-Directed Agentic Engineering (HDAE):
human-owned product intent, architecture, constraints, and acceptance
boundaries combined with agent-executed implementation, diagnosis, and test
loops. Each approved gate records its scope, non-goals, acceptance criteria,
verification, evidence, and stopping point in hdae/
.
The independent bootable kernel remains available for research and historical reference:
It is no longer the active development line. The code remains available under the repository's existing terms for anyone who independently chooses to study or fork it.
The name remains a joke in the same family as Linux: Ginux combines Linux,
GPT, and Greg. To keep the author's ego within its configured resource limit,
the G
officially stands for GPT.
Ginux is available under the Apache License 2.0. See LICENSE
and
NOTICE
.
On x86-64 Linux, ginux launch [--result-file ]
executes [instance].init
with the configured arguments and environment inside
the supplied host root directory. The bootstrap requires chroot and credential
change privileges, and a kernel with close_range
(Linux 5.9 or later).
It drops to UID/GID 1000 before executing the workload. The configuration must
select profile = "balanced"
and backend = "ptrace-native"
; storage and device
assignments are not implemented by this command and are rejected.
[instance].workdir
is optional and defaults to /work
; /
is also supported.
The bootstrap creates the working directory if its parent exists, then enters
it after dropping privileges. Init and working-directory paths are limited to
128 bytes and must be absolute without ..
path components. Up to seven
arguments of 128 bytes each and eight NAME=value
environment entries of
256 bytes each are accepted. Invalid or oversized input fails before launch.
Workload stdout stays separate from supervisor diagnostics on stderr. Only
stdin, stdout, and stderr are inherited by the workload. The launcher returns
the workload exit code, or 128 + signal
for signal termination. Startup
failure returns 2; supervision or result-writing failure returns 1. Native
fork, clone, and subsequent exec are unsupported; fork returns ENOSYS
until
child supervision is implemented. Non-x86-64 syscall ABIs and job-control
group stops fail explicitly. Audit output is capped at 64 KiB plus a truncation
notice. This path does not implement resource quotas from the instance metadata.
When requested, the result file is reserved exclusively before launch, with mode 0600 and a maximum path length of 512 bytes. Existing files are never overwritten. After completion, it contains one synced JSON object:
{"version":1,"outcome":"exited","code":0,"signal":null,"generation":null}
outcome
is exited
, signaled
, or supervisor-error
; only the relevant
code
or signal
is populated. Storage generations are not implemented, so
generation
is null. A missing, empty, or malformed result is a protocol failure.
A failure to write, sync, or close the result returns 1 even if the workload
succeeded. Consumers should wait for launcher completion before reading it.
The focused Linux integration scenario consumes existing binaries:
make build
make build-launch-fixture
make build-gate1-fixture
sudo make test-gate1-setup
sudo make test-launch
sudo make test-native-supervision
The separate process-native
command exercises native
fork, clone, vfork, exec, threads, waits, termination signals, and pipes. It
executes a trusted host fixture as the caller, without chroot; it does not
require root on a host that permits tracing child processes. Configured
launch
remains a separate entry point and does not yet use this process loop.
Process creation returns virtual identities, including clone TID cells.
Untraced, reparenting, namespace, and unsupported clone flags are denied.
Thread slots are reclaimed on exit. CLONE_FILES
shares one descriptor table;
ordinary fork copies it, and exec unshares it before closing CLOEXEC entries.
Worker-thread exec preserves the group's process identity. Inherited host
files above stderr are closed before execution. PI futex operations and queries
for another task's robust list are unsupported.
Build the fixtures explicitly, then run their independent scenarios:
make build build-process-fixtures
make test-process-native
make test-process-native SCENARIO=shared_pipe
make test-process-native SCENARIO=thread_exec
The scenarios consume existing binaries under .artifacts/process
, including
checks for syscall ABI dispatch, clone restrictions, descriptor inheritance,
thread reuse, successful and failed worker exec, signals, wait-status writes,
nonblocking waits, and vfork. The existing native fork fixture also runs. These
checks do not claim complete libc, job-control, or systemd compatibility.