GoSuda

gvx: Linux Containers, Docker en Kubernetes uitvoeren in één Unprivileged Process

By Lemon Mint views ...

Inleiding: Beveiligde containers zonder root of host-daemons

Het uitvoeren van Linux-containers, docker build of Kubernetes binnen een ontwikkelmachine, CI-runner of multi-tenant cloudnode dwingt doorgaans een ongemakkelijke beveiligingsafweging af:

  • Op Linux vereisen standaard OCI-runtimes (runc, crun, runsc) ofwel root-privileges, blootstelling van de host-socket /var/run/docker.sock, --privileged (CAP_SYS_ADMIN), of gebruikersnamespaces (unshare(CLONE_NEWUSER)) die het aanvalsoppervlak van de host-kernel vergroten.
  • Op Windows en macOS starten containerengines (Docker Desktop, Podman Machine, Lima, OrbStack) een volledige Linux-virtuele-machinekernel binnen WSL2, Hyper-V of Virtualization.framework en draaien ze een persistente achtergronddaemon binnen die VM.

gvx (rungv CLI en de z.gfire.dev/gvx/pkg/gvx Go SDK) biedt een veilig, zero-privilege alternatief. Gebouwd als een upstream-traceerbare fork van de user-space Linux-kernel (Sentry) en de user-space netwerkstack (netstack) van Google's gVisor, voert gvx onveranderde linux/amd64- en linux/arm64-ELF64-binaries en OCI-containers rechtstreeks uit binnen één niet-geprivilegieerd, zelf vergrendeld hostproces op Windows, macOS en Linux.

Beveiligingsgarantie (Drieringsverdediging in de diepte): gvx tast nooit de beveiligingsgrenzen van het host-besturingssysteem aan, installeert nooit kernelstuurprogramma's en vereist nooit root, sudo, --privileged op de host, of CLONE_NEWUSER. Voordat één gastinstructie wordt uitgevoerd, vergrendelt gvx zijn eigen hostproces met Windows Restricted Tokens en Job Objects, macOS Seatbelt (deny default), of Linux PR_SET_NO_NEW_PRIVS + BPF seccomp. Alle systeemaanroepen van de gast, virtuele UID 0-privileges, mounts (VFS2) en netwerktabellen (netstack) zijn strikt beperkt binnen de geheugenveilige Go Sentry-kernel.

RuntimeHost OSKoude startGeheugenverbruikBeheer- of sudo vereistAchtergrond-VM-daemonDirecte OCI-image uitvoeren
gvx (whpx / hvf)Windows (amd64, arm64) & macOS (arm64)~20 ms~30 MiBNeeNeeJa
gvx (systrap)Windows (amd64, arm64) & Linux (amd64, arm64)~25 tot 65 ms~31 MiBNeeNeeJa
WSL2 / Docker DesktopWindows & macOS800 tot 2500 ms> 512 MiBJa (initiële installatie)JaVereist daemon
QEMU (Full System)Windows & macOS1500 tot 4000 ms> 256 MiBNeeNeeVereist schijfimage

Er wordt geen Linux-VM-kernel gestart op Windows of macOS. Er draait geen achtergronddaemon op de host. Voor het host-besturingssysteem zijn geen root, sudo, gebruikersnamespaces (CLONE_NEWUSER), mount(2), TUN/TAP-stuurprogramma's of CAP_NET_ADMIN-privileges vereist. Binnen dat niet-geprivilegieerde proces draait gvx Docker-in-Docker (docker:dind), docker build, docker compose en high-availability Kubernetes met meerdere nodes (k3s met ingebedde etcd en Calico CNI).


Levenscyclus van de container en driedelige hostvergrendeling

Elke gvx-sandbox draait binnen één niet-geprivilegieerd hostproces en handhaaft een driefasige levenscyclus: vooraf openen van alle vereiste host-bestandsdescriptors en sockets, vergrendelen van het host-besturingssysteemproces via pkg/hostlockdown, en betreden van de in-proces Sentry-uitvoeringslus.

gvx-containerlevenscyclus en zelfvergrendeling van de host

Voordat de controle wordt overgedragen aan de eerste gastinstructie, opent gvx vooraf zijn gecachte .erofs-imagelaagdescriptors, .ext4-volumebestandsdescriptors, de /workspace *os.Root-handle en host-netwerksockets, en roept vervolgens hostlockdown.Apply() aan:

  1. Windows NT (lockdown_windows.go): Roept AdjustTokenPrivileges aan met SE_PRIVILEGE_REMOVED om privileges ter plaatse te verwijderen, koppelt een DISABLE_MAX_PRIVILEGE beperkt impersonatietoken met Low-integriteit (S-1-16-4096), wijst het proces toe aan een anoniem Job Object met JOB_OBJECT_LIMIT_ACTIVE_PROCESS = 1 (waarmee het aanmaken van kindprocessen wordt geblokkeerd), en activeert DisallowWin32kSystemCalls = 1 via SetProcessMitigationPolicy.
  2. macOS XNU (lockdown_darwin.go): Stelt RLIMIT_NPROC = 0 in en past een Seatbelt-profiel (sandbox_init_with_parameters) toe, beginnend bij (version 1)(deny default), dat process-fork, process-exec* en alle nieuwe host-bestandsopeningen blokkeert.
  3. Linux (lockdown_linux.go + pkg/gvx/syscallfilter_linux.go): Stelt PR_SET_NO_NEW_PRIVS in en installeert de Sentry BPF seccomp-allowlist over alle threads (SECCOMP_FILTER_FLAG_TSYNC) met expliciete EPERM-regels voor execve, execveat, clone, openat en socket.

Systeemaanroepenonderschepping per platform en 4 KB-paging (pkg/sentry/platform)

Linux ELF64-binaries gaan uit van een geheugenpaginagranulariteit van 4 KB (0x1000) voor mmap(MAP_FIXED)-segmenttoewijzingen. Windows VirtualAlloc en MapViewOfFile3 dwingen echter een uitlijning van 64 KB (0x10000) af, en Apple Silicon macOS (darwin/arm64) gebruikt host-pagina's van 16 KB (0x4000). Bovendien vereist elk besturingssysteem een ander mechanisme om Linux SYSCALL- (amd64) en SVC #0- (arm64) instructies te onderscheppen zonder een Linux-VM te starten.

gvx lost beide problemen op over vier uitvoeringsbackends die pkg/sentry/platform.Context implementeren:

gvx-mechanica van platformsysteemaanroeponderschepping en 4KB-paging

1. Windows-hardware-engine: platform/whpx (WinHvPlatform.dll)

Op windows/amd64 en windows/arm64 creëert platform/whpx via WHvCreatePartition een lichtgewicht hypervisorpartitie zonder een gastbesturingssysteemkernel op te starten:

  • Ontkoppelaar voor 64 KB-naar-4 KB-paging: whpx registreert elke 64 MiB-arena van pgalloc.MemoryFile eenmalig bij de partitie via WHvMapGpaRange. Volgende MapFile- en Unmap-aanroepen wijzigen alleen de in-partitie gast-paginatabellen en dwingen exacte 4 KB-R/W/X-rechten af, ondanks de toewijzingsgranulariteit van Windows van 64 KB.
  • Registeruitwisseling zonder API via CommPage:
    • Op amd64 springt een SYSCALL van gast-Ring-3 naar een in-partitie Ring-0 MSR_LSTAR-stub (stubgen_x86.go), die de snelpad-syscall-GPR's (RAX, RDI, RSI, RDX, R10, R8, R9, RIP, RSP) naar een gedeelde 4 KB-CommPage schrijft en HLT uitvoert om WHvRunVirtualProcessor te verlaten.
    • Op arm64 springt een gast-EL0 SVC #0 naar EL1-vectoren (stubgen_a64.go), schrijft X0..X8, SP_EL0, ELR_EL1, PSTATE naar CommPage, en laadt vanaf een niet-gekoppeld GPA-nul om een geheugenexit terug naar de Sentry te activeren.

2. macOS-hardware-engine: platform/hvf (Hypervisor.framework)

Op Apple Silicon (darwin/arm64) voert platform/hvf containerthreads uit binnen Hypervisor.framework-vCPU's (hv_vm_create, hv_vcpu_run):

  • Tweetraps 4 KB-over-16 KB-MMU-vertaling: Stage-2 (hv_vm_map) koppelt eenmaal 1 GiB-chunks van het geheugenbestand aan de Intermediate Physical Address (IPA)-ruimte met een granulariteit van 16 KB host-pagina's met HV_MEMORY_READ | HV_MEMORY_WRITE | HV_MEMORY_EXEC. Binnen de vCPU configureert de EL1 bluepill-stub (bluepill_arm64.go) TCR_EL1.TG0 = 0b00 (4 KB-granule) in TTBR0_EL1 en vult 4 KB Stage-1 paginatabelitems met exacte R-X- of RW- (UXN=1)-rechten. Omdat de ARM64-MMU de doorsnede van Stage-1- en Stage-2-rechten afdwingt, onderschept de hardware rechtenfouten op exacte 4 KB-grenzen.
  • Syscall-onderschepping: Een gast-EL0 SVC #0 gaat naar EL1 VBAR_EL1, schrijft registers naar de gedeelde 4 KB-CommPage en voert HVC #0 uit om vanuit hv_vcpu_run terug te keren naar de Go Sentry-thread.

3. Windows user-mode-fallback: platform/systrap (winsystrap)

Wanneer hardwarevirtualisatie niet beschikbaar is (zoals binnen geneste cloud-VM's), voert winsystrap gastcode uit in user mode op windows/amd64 en windows/arm64:

  • Hardware-CR3- / TTBR0_EL1-isolatie: Gastthreads draaien binnen geïsoleerde stub-subprocessen die worden gestart vanuit een gegenereerd uitvoerbaar bestand van 1 KB (stubimage_xplat.go), beperkt door een token met Low-integriteit en een Job Object.
  • Patchen van binaries ter plaatse & winSysmsg IPC: Op amd64 vervangt de patcher (patcher_amd64_xplat.go) 2-byte syscall- (0f 05) instructies door 5-byte jmp rel32-sprongen (of CC 90 breekpunt-traps wanneer een 5-byte-venster niet beschikbaar is) naar een snelpad-eiland. Op arm64 wordt svc #0 gepatcht met een B-branch naar een eiland-slot. Het eiland schrijft registers naar de gedeelde winSysmsg-pagina en synchroniseert met de Sentry via WaitOnAddress en WakeByAddressSingle, terwijl ongepatchte traps en paginafouten stoppen op een Windows Debug Object (WaitForDebugEventEx).

4. Linux user-mode-engine: platform/systrap

Op linux/amd64 en linux/arm64 voert systrap gastthreads (sysmsg) uit die systeemaanroepen van de gast onderscheppen via SECCOMP_RET_TRAP (SIGSYS) en syscall-locaties binair patchen naar een trampoline in gedeeld geheugen (sysmsg), gesynchroniseerd via SYS_FUTEX. Een stub-syscallThread (syscall_thread.go) voert adresruimtebewerkingen (mmap, munmap, clone) uit via SECCOMP_RET_USER_NOTIF (_RUN_SECCOMP_LOOP). Wanneer gvx draait binnen een buitenste gVisor (runsc)-sandbox waarin de enkele SECCOMP_FILTER_FLAG_NEW_LISTENER-plek EBUSY retourneert, schakelt gvx automatisch syscallThread over naar een futex-lus in gedeeld geheugen (_RUN_SYSCALL_LOOP), zoals beschreven in deel 3 van deze serie.


Hoe gvx Linux-systeemaanroepen van de gast koppelt aan virtuele subsystemen en host-handles

Zodra een platformengine een SYSCALL (amd64) of SVC #0 (arm64)-instructie van de gast onderschept en de gastregisters naar de 4 KB-gedeelde pagina schrijft (CommPage, sysmsg of winSysmsg), keert de controle terug naar de Go Sentry-taakgoroutine (pkg/sentry/kernel/task_syscall.go). In plaats van Linux-systeemaanroepen door te sturen naar de onderliggende host-besturingssysteemkernel, slaat Task.doSyscall() de oorspronkelijke registertoestand op (SyscallSaveOrig()), evalueert eventuele in-gast BPF seccomp-filters en ptrace-stops, en verdeelt het systeemaanroepnummer via linux.AMD64 of linux.ARM64 (pkg/sentry/syscalls/linux/linux64.go, met ongeveer 370 Linux-systeemaanroepen) naar vier interne domeinen:

Hoe gvx Linux-systeemaanroepen van de gast koppelt aan subsystemen en het host-OS

SysteemaanroepdomeinRepresentatieve Linux-systeemaanroepen van de gastIntern gvx-subsysteem (pkg/sentry)Interactie met de host-OS-grens (na hostlockdown)
A. Proces, namespace, mount en synchronisatieclone, execve, unshare, setns, mount, umount2, pivot_root, setuid, capset, futex, epoll_wait, pipe2kernel.Task, virtuele credentials, futex-tabel en in-memory VFS2 (procfs, sysfs, cgroupfs v2, tmpfs, overlayfs)Nul host-systeemaanroepen (100% in-memory Go-status; volledig onzichtbaar voor het host-besturingssysteem)
B. Virtueel geheugen (MM)mmap, munmap, mremap, mprotect, brk, madvise, memfd_createmm.MemoryManager (4 KB VMA-boom) & pgalloc.MemoryFileBidirectionele 4 KB-paginatabelupdates via WHvMapGpaRange, hv_vm_map + TTBR0_EL1, of stub-mmap/mprotect
C. Bestanden en opslag (VFS2)openat, pread64, pwrite64, getdents64, statx, renameat2, setxattr, mknod, inotify_add_watchvfs.VirtualFilesystem mounttabel-router (fsimpl/erofs, fsimpl/ext4lite, fsimpl/hostfs v2)Bidirectionele pread/pwrite op 1 vastgezette .erofs-FD of 1 .ext4-FD, of begrensde relatieve doorlopen op *os.Root
D. Netwerken & Netfiltersocket, bind, listen, connect, sendmsg, recvmsg, netlink, setsockopt(IPT_SO_SET_REPLACE)User-space pkg/tcpip (lo, eth0, docker0, veth, vxlan, wireguard, ipset, iptables-legacy)Verkeer binnen de sandbox gebruikt nul host-systeemaanroepen; extern verkeer wordt gekoppeld aan net.Dial/net.Listen (-net=nat) of 1 UDP-socket (-net=wg)

Wanneer de handler (rval, ctrl, err) retourneert, zet Task.doSyscallInvoke() elke fout om in een negatieve Linux-errno (-ExtractErrno(err, sysno)), schrijft de retourwaarde terug naar de gedeelde registerpagina (RAX op amd64, X0 op arm64) en hervat de uitvoering van de gast.


Opslag (VFS2) en tweesporen user-space-netwerken (netstack)

Alle bestands- en netwerkbewerkingen van de gast worden afgehandeld binnen de Go Sentry via drie VFS2-bestandssystemen en twee pkg/netbackend-sporen:

gvx VFS2-opslagengines en tweesporen netstack

OpslagengineMountrol in gvxOnderliggende hostbronHost-aanroepen van openat / NtCreateFile tijdens uitvoeringWindows Defender (WdFilter.sys) scans per bestand
fsimpl/erofsAlleen-lezen OCI-imagelagen (onderste lagen)Eén <sha256>.erofs-bestand per laag00
fsimpl/ext4liteBenoemde volumes (-v name:/path)Eén <name>.ext4-bestand per volume00
fsimpl/hostfs v2Hostworkspace (-workspace, -v /host:/guest)Vastgezette *os.Root-hostmapO(depth) per niet-gecachte opening1 per hostbestandssluiting

Waarom ext4lite belangrijk is voor Docker, databases en pakketbeheerders

Het uitpakken of schrijven van tienduizenden containerbestanden rechtstreeks naar Windows NTFS of macOS APFS veroorzaakt scans door antivirus-minifilters (WdFilter.sys), botsingen door hoofdlettergevoeligheid en ontbrekende POSIX-uitgebreide attributen.

pkg/sentry/fsimpl/ext4lite implementeert een pure-Go ext4-bestandssysteem binnen één vooraf geopend hostbestand (%LOCALAPPDATA%\gvx\cache\volumes\<name>.ext4 op Windows, ~/Library/Caches/gvx/volumes/<name>.ext4 op macOS, of ~/.cache/gvx/volumes/<name>.ext4 op Linux) met:

  • 4 KiB-blokken en dynamische blokgroepen van 16 MiB (meta_bg): Groeit dynamisch zonder stilstand door toewijzing van sparse-bestanden (fallocate op Linux, fcntl(F_PREALLOCATE) op macOS, FileAllocationInfo op Windows) en comprimeert ongebruikte groepen aan het einde bij een schone unmount.
  • JBD2-journal in ordered-modus (inode 8): Atomaire transactiecommits met CRC-32-checksums en revoke-blokken, plus fsckLite-herstel.
  • Ondersteuning voor overlayfs-bovenlagen: Implementeert trusted.overlay.*- en security.capability-uitgebreide attributen, whiteouts voor tekenapparaten (0:0), d_type-rapportage van directory-items en RENAME_EXCHANGE.

Praktische toepassingen: Docker Engine, Kubernetes (k3s + Calico) en Go SDK

rungv installeren: SLSA-geattesteerde vooraf gebouwde releases of go install

U kunt een vooraf gebouwd rungv-archief downloaden voor linux/amd64, linux/arm64, darwin/arm64, windows/amd64 of windows/arm64 via GitHub Releases (elk gebouwd door de SLSA-releasepipeline met een Sigstore-herkomstbundel en SHA256SUMS), of installeer rechtstreeks vanuit de broncode met go install:

1# Optie A: Download een vooraf gebouwde release en verifieer de SLSA-buildherkomst
2gh attestation verify rungv-v0.2.0-linux-amd64.tar.gz --repo gfire-sigs/gvx \
3  --signer-workflow gfire-sigs/gvx/.github/workflows/release.yml \
4  --source-ref refs/tags/v0.2.0 --deny-self-hosted-runners
5shasum -a 256 -c SHA256SUMS --ignore-missing
6tar -xzf rungv-v0.2.0-linux-amd64.tar.gz
7
8# Optie B: Installeer rechtstreeks via Go (Linux / Windows)
9CGO_ENABLED=0 go install z.gfire.dev/gvx/cmd/rungv@latest

Op macOS (darwin/arm64) is het vooraf gebouwde releasearchief al ondertekend met de com.apple.security.hypervisor entitlement (xattr -d com.apple.quarantine rungv verwijdert de browserquarantaine). Als u lokaal bouwt met go install of go build op macOS, onderteken de binary dan met codesign --sign - --entitlements entitlements.plist --force $(which rungv).

1. Een niet-geprivilegieerde Docker Engine uitvoeren (docker:dind)

Volgens docs/docker-engine.md kunt u een onveranderde docker:dind-container binnen rungv uitvoeren op Windows, macOS of Linux en deze besturen met uw host-docker-CLI:

 1# 1. Maak de hostdirectory aan voor de doorgestuurde Unix-socket
 2mkdir -p ~/.gvx
 3
 4# 2. Start docker:dind binnen een gvx-sandbox met een ext4lite-volume
 5rungv run \
 6  -p unix:~/.gvx/docker.sock:2375 \
 7  -p 127.0.0.1:9000-9019:9000-9019 \
 8  -v gvx-docker:/var/lib/docker \
 9  -e DOCKER_TLS_CERTDIR= \
10  docker:dind --tls=false

In een tweede terminal richt u de host-docker-CLI op de sandbox-socket:

1docker context create gvx --docker host=unix://$HOME/.gvx/docker.sock
2docker context use gvx
3
4docker info --format '{{.Driver}} {{.OperatingSystem}}'
5# Uitvoer: overlayfs Alpine Linux ... (gecontaineriseerd)
6
7docker build -t myapp ./app
8docker compose up -d

2. Kubernetes (k3s + Calico) uitvoeren in niet-geprivilegieerde sandboxes

Omdat gvx /dev/kmsg, cgroup v2 (memory.max OOM-killer en cpu.max-bandbreedtebeperking), /proc/diskstats, wireguard- en vxlan-netlink-apparaten, ipset en iptables-legacy (addrtype, conntrack, statistic, recent) implementeert, bereikt een onveranderde rancher/k3s-image de status Ready binnen één rungv-proces:

 1mkdir -p ~/.gvx/k3s
 2
 3rungv run \
 4  -e IPTABLES_MODE=legacy \
 5  -p 127.0.0.1:6443:6443 \
 6  -p 127.0.0.1:30000-30019:30000-30019 \
 7  -v k3s:/var/lib/rancher \
 8  --mount type=bind,src=$HOME/.gvx/k3s,dst=/output \
 9  rancher/k3s:v1.31.4-k3s1 server \
10  --disable=traefik,servicelb \
11  --write-kubeconfig=/output/kubeconfig.yaml \
12  --write-kubeconfig-mode=644

U kunt ook meerdere gvx-nodes via WireGuard samenvoegen (flannel-backend=wireguard-native of een wg0-mesh) met high availability via ingebedde etcd, of Calico draaien (calico-vxlan.yaml met FELIX_IPTABLESBACKEND=Legacy) om Kubernetes NetworkPolicy volledig binnen de user space af te dwingen.

3. Linux-containers in Go insluiten via z.gfire.dev/gvx/pkg/gvx

Go-applicaties kunnen Linux-containers rechtstreeks insluiten zonder externe containerdaemon:

 1package main
 2
 3import (
 4	"context"
 5	"fmt"
 6	"os"
 7
 8	"z.gfire.dev/gvx/pkg/gvx"
 9)
10
11func main() {
12	ctx := context.Background()
13	sb, err := gvx.New(ctx, gvx.Config{
14		Platform:      gvx.PlatformAuto,
15		NetworkMode:   gvx.NetModeNAT,
16		OCIImage:      "alpine:3.20",
17		WorkspacePath: "./repo",
18		Argv:          []string{"/usr/bin/git", "status"},
19		Stdout:        os.Stdout,
20		Stderr:        os.Stderr,
21	})
22	if err != nil {
23		fmt.Fprintf(os.Stderr, "init sandbox: %v\n", err)
24		os.Exit(1)
25	}
26	defer sb.Close()
27
28	exitCode, err := sb.Run(ctx)
29	if err != nil {
30		fmt.Fprintf(os.Stderr, "run sandbox: %v\n", err)
31		os.Exit(1)
32	}
33	os.Exit(exitCode)
34}

Verder lezen in deze serie

  1. gvx: Linux-containers, Docker en Kubernetes uitvoeren in één niet-geprivilegieerd proces (dit bericht)
  2. Docker, docker build en docker compose uitvoeren zonder root of gebruikersnamespaces: Hoe docker:dind wordt gebootstrapt in afgeschermde sandboxes waar sudo, unshare(CLONE_NEWUSER), mount(2) en /dev/fuse zijn geblokkeerd.
  3. Docker-in-Docker uitvoeren binnen gVisor en geneste sandboxes met gvx: Waarom apt-get install docker.io en geneste runsc binnen gVisor falen over vijf kernelbarrières, en hoe gvx geneste docker:dind uitvoert met een futex-lus in gedeeld geheugen.
  4. Snelle geïsoleerde Docker-omgevingen voor AI-agent RL-rollouts met gvx: Het opschalen van SWE-bench- en agent-reinforcement-learning-rollouts met honderden parallelle niet-geprivilegieerde docker:dind-instanties en copy-on-write .ext4-volumeresets van 100 milliseconden.