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

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) ofwelroot-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 ofVirtualization.frameworken 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):
gvxtast nooit de beveiligingsgrenzen van het host-besturingssysteem aan, installeert nooit kernelstuurprogramma's en vereist nooitroot,sudo,--privilegedop de host, ofCLONE_NEWUSER. Voordat één gastinstructie wordt uitgevoerd, vergrendeltgvxzijn eigen hostproces met Windows Restricted Tokens en Job Objects, macOS Seatbelt (deny default), of LinuxPR_SET_NO_NEW_PRIVS+ BPFseccomp. Alle systeemaanroepen van de gast, virtueleUID 0-privileges, mounts (VFS2) en netwerktabellen (netstack) zijn strikt beperkt binnen de geheugenveilige GoSentry-kernel.
| Runtime | Host OS | Koude start | Geheugenverbruik | Beheer- of sudo vereist | Achtergrond-VM-daemon | Directe OCI-image uitvoeren |
|---|---|---|---|---|---|---|
gvx (whpx / hvf) | Windows (amd64, arm64) & macOS (arm64) | ~20 ms | ~30 MiB | Nee | Nee | Ja |
gvx (systrap) | Windows (amd64, arm64) & Linux (amd64, arm64) | ~25 tot 65 ms | ~31 MiB | Nee | Nee | Ja |
| WSL2 / Docker Desktop | Windows & macOS | 800 tot 2500 ms | > 512 MiB | Ja (initiële installatie) | Ja | Vereist daemon |
| QEMU (Full System) | Windows & macOS | 1500 tot 4000 ms | > 256 MiB | Nee | Nee | Vereist 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.

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:
- Windows NT (
lockdown_windows.go): RoeptAdjustTokenPrivilegesaan metSE_PRIVILEGE_REMOVEDom privileges ter plaatse te verwijderen, koppelt eenDISABLE_MAX_PRIVILEGEbeperkt impersonatietoken met Low-integriteit (S-1-16-4096), wijst het proces toe aan een anoniem Job Object metJOB_OBJECT_LIMIT_ACTIVE_PROCESS = 1(waarmee het aanmaken van kindprocessen wordt geblokkeerd), en activeertDisallowWin32kSystemCalls = 1viaSetProcessMitigationPolicy. - macOS XNU (
lockdown_darwin.go): SteltRLIMIT_NPROC = 0in en past een Seatbelt-profiel (sandbox_init_with_parameters) toe, beginnend bij(version 1)(deny default), datprocess-fork,process-exec*en alle nieuwe host-bestandsopeningen blokkeert. - Linux (
lockdown_linux.go+pkg/gvx/syscallfilter_linux.go): SteltPR_SET_NO_NEW_PRIVSin en installeert de Sentry BPFseccomp-allowlist over alle threads (SECCOMP_FILTER_FLAG_TSYNC) met explicieteEPERM-regels voorexecve,execveat,clone,openatensocket.
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:

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:
whpxregistreert elke 64 MiB-arena vanpgalloc.MemoryFileeenmalig bij de partitie viaWHvMapGpaRange. VolgendeMapFile- enUnmap-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
amd64springt eenSYSCALLvan gast-Ring-3 naar een in-partitie Ring-0MSR_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-CommPageschrijft enHLTuitvoert omWHvRunVirtualProcessorte verlaten. - Op
arm64springt een gast-EL0SVC #0naar EL1-vectoren (stubgen_a64.go), schrijftX0..X8, SP_EL0, ELR_EL1, PSTATEnaarCommPage, en laadt vanaf een niet-gekoppeld GPA-nul om een geheugenexit terug naar de Sentry te activeren.
- Op
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 metHV_MEMORY_READ | HV_MEMORY_WRITE | HV_MEMORY_EXEC. Binnen de vCPU configureert de EL1bluepill-stub (bluepill_arm64.go)TCR_EL1.TG0 = 0b00(4 KB-granule) inTTBR0_EL1en vult 4 KB Stage-1 paginatabelitems met exacteR-X- ofRW-(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 #0gaat naar EL1VBAR_EL1, schrijft registers naar de gedeelde 4 KB-CommPageen voertHVC #0uit om vanuithv_vcpu_runterug 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 &
winSysmsgIPC: Opamd64vervangt de patcher (patcher_amd64_xplat.go) 2-bytesyscall- (0f 05) instructies door 5-bytejmp rel32-sprongen (ofCC 90breekpunt-traps wanneer een 5-byte-venster niet beschikbaar is) naar een snelpad-eiland. Oparm64wordtsvc #0gepatcht met eenB-branch naar een eiland-slot. Het eiland schrijft registers naar de gedeeldewinSysmsg-pagina en synchroniseert met de Sentry viaWaitOnAddressenWakeByAddressSingle, 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:

| Systeemaanroepdomein | Representatieve Linux-systeemaanroepen van de gast | Intern gvx-subsysteem (pkg/sentry) | Interactie met de host-OS-grens (na hostlockdown) |
|---|---|---|---|
| A. Proces, namespace, mount en synchronisatie | clone, execve, unshare, setns, mount, umount2, pivot_root, setuid, capset, futex, epoll_wait, pipe2 | kernel.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_create | mm.MemoryManager (4 KB VMA-boom) & pgalloc.MemoryFile | Bidirectionele 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_watch | vfs.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 & Netfilter | socket, 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:

| Opslagengine | Mountrol in gvx | Onderliggende hostbron | Host-aanroepen van openat / NtCreateFile tijdens uitvoering | Windows Defender (WdFilter.sys) scans per bestand |
|---|---|---|---|---|
fsimpl/erofs | Alleen-lezen OCI-imagelagen (onderste lagen) | Eén <sha256>.erofs-bestand per laag | 0 | 0 |
fsimpl/ext4lite | Benoemde volumes (-v name:/path) | Eén <name>.ext4-bestand per volume | 0 | 0 |
fsimpl/hostfs v2 | Hostworkspace (-workspace, -v /host:/guest) | Vastgezette *os.Root-hostmap | O(depth) per niet-gecachte opening | 1 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 (fallocateop Linux,fcntl(F_PREALLOCATE)op macOS,FileAllocationInfoop 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: Implementeerttrusted.overlay.*- ensecurity.capability-uitgebreide attributen, whiteouts voor tekenapparaten (0:0),d_type-rapportage van directory-items enRENAME_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 decom.apple.security.hypervisorentitlement (xattr -d com.apple.quarantine rungvverwijdert de browserquarantaine). Als u lokaal bouwt metgo installofgo buildop macOS, onderteken de binary dan metcodesign --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
- gvx: Linux-containers, Docker en Kubernetes uitvoeren in één niet-geprivilegieerd proces (dit bericht)
- Docker, docker build en docker compose uitvoeren zonder root of gebruikersnamespaces: Hoe
docker:dindwordt gebootstrapt in afgeschermde sandboxes waarsudo,unshare(CLONE_NEWUSER),mount(2)en/dev/fusezijn geblokkeerd. - Docker-in-Docker uitvoeren binnen gVisor en geneste sandboxes met gvx: Waarom
apt-get install docker.ioen genesterunscbinnengVisorfalen over vijf kernelbarrières, en hoegvxgenestedocker:dinduitvoert met een futex-lus in gedeeld geheugen. - 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-geprivilegieerdedocker:dind-instanties en copy-on-write.ext4-volumeresets van 100 milliseconden.