Greg Kurtzer's Ginux is now a userspace kernel
Background
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.
Architecture
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.
Containment for AI Agents
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.
Comparison with Related Approaches
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.
Development Environment and Build
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
Syscall Control and Performance
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.
Host Workflow and Instance Management
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:
ginx create rocky
ginx list
ginx status rocky
ginx start rocky --attach
create starts from the documented default recipe. Use ginx import NAME FILE to create or atomically replace an instance from a self-contained recipe, ginx export NAME to print that recipe, and ginx 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 ginx unlock NAME, perform the required maintenance, stop it again, and run ginx lock NAME. ginx list and ginx 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 ginx 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.
Instance Specification and Contract
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:
ginx 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
ginx 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:
ginx validate /etc/ginux/examples/rocky-systemd.toml
ginx inspect /etc/ginux/examples/rocky-systemd.toml
ginx 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.
Console and Testing
The attached terminal becomes the Ginux instance console. Log in as root with password ginx, 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 ginx start rocky, ginx status rocky, and ginx 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 rema
Comments
No comments yet. Start the discussion.