gvx: Att köra Linux Containers, Docker och Kubernetes i en enda Unprivileged Process

Inledning: Säkerhetshärdade containrar utan root eller värddaemoner
Att köra Linux-containrar, docker build eller Kubernetes på en utvecklingsdator, en CI-runner eller en multitenant-molnnod medför vanligtvis en obekväm säkerhetsavvägning:
- På Linux kräver standardmässiga OCI-runtimes (
runc,crun,runsc) antingenroot-behörighet, exponering av värdens/var/run/docker.sock,--privileged(CAP_SYS_ADMIN) eller användarnamnrymder (unshare(CLONE_NEWUSER)), vilka utökar värdkärnans attackyta. - På Windows och macOS startar containermotorer (
Docker Desktop,Podman Machine,Lima,OrbStack) en fullständig Linux-kärna i en virtuell maskin inom WSL2, Hyper-V ellerVirtualization.frameworkoch kör en beständig bakgrundsdaemon inuti den virtuella maskinen.
gvx (rungv-CLI:t och z.gfire.dev/gvx/pkg/gvx Go-SDK:t) erbjuder ett säkert alternativ med noll behörighetsbehov. Det är byggt som en uppströmsspårbar fork av Googles gVisor användarlägeskärna för Linux (Sentry) och användarlägesnätverksstacken (netstack), och gvx kör oförändrade linux/amd64- och linux/arm64-ELF64-binärer samt OCI-containrar direkt inuti en enda, obehörig och självlåst värdprocess på Windows, macOS och Linux.
Säkerhetsgaranti (Tre-ringsförsvar i djupet):
gvxförsvagar aldrig värd-OS:ets säkerhetsgränser, installerar aldrig kärndrivrutiner och kräver aldrigroot,sudo,--privilegedpå värden ellerCLONE_NEWUSER. Innan en enda gästinstruktion körs låsergvxsin egen värdprocess med Windows Restricted Tokens och Job Objects, macOS Seatbelt (deny default) eller LinuxPR_SET_NO_NEW_PRIVS+ BPFseccomp. Alla gästsystemanrop, virtuellaUID 0-privilegier, monteringar (VFS2) och nätverkstabeller (netstack) är strikt begränsade inuti den minnessäkra Go-kärnanSentry.
| Runtime | Värd-OS | Kallstart | Minnesfotavtryck | Admin eller sudo krävs | Bakgrunds-VM-daemon | Direkt körning av OCI-avbild |
|---|---|---|---|---|---|---|
gvx (whpx / hvf) | Windows (amd64, arm64) & macOS (arm64) | ~20 ms | ~30 MiB | Nej | Nej | Ja |
gvx (systrap) | Windows (amd64, arm64) & Linux (amd64, arm64) | ~25 till 65 ms | ~31 MiB | Nej | Nej | Ja |
| WSL2 / Docker Desktop | Windows & macOS | 800 till 2500 ms | > 512 MiB | Ja (initial installation) | Ja | Kräver daemon |
| QEMU (Full System) | Windows & macOS | 1500 till 4000 ms | > 256 MiB | Nej | Nej | Kräver diskavbild |
Ingen Linux-VM-kärna startas på Windows eller macOS. Ingen bakgrundsdaemon körs på värden. Inga root-, sudo-, användarnamnrymds- (CLONE_NEWUSER), mount(2)-, TUN/TAP-drivrutins- eller CAP_NET_ADMIN-privilegier krävs på värd-OS:et. Inuti den obehöriga processen kör gvx Docker-in-Docker (docker:dind), docker build, docker compose och högtillgänglig Kubernetes med flera noder (k3s med inbäddad etcd och Calico CNI).
Containerlivscykel och låsning av värd i tre ringar
Varje gvx-sandlåda körs inuti en obehörig värdprocess och genomdriver en livscykel i tre faser: föröppning av alla nödvändiga värdfilbeskrivare och uttag, låsning av värdprocessen via pkg/hostlockdown samt inträde i den interna Sentry-exekveringsslingan.

Innan kontrollen överförs till den första gästinstruktionen föröppnar gvx sina cachade .erofs-lagerbeskrivare, .ext4-volymfilbeskrivare, /workspace-handtaget *os.Root och värdens nätverksuttag, varefter den anropar hostlockdown.Apply():
- Windows NT (
lockdown_windows.go): AnroparAdjustTokenPrivilegesmedSE_PRIVILEGE_REMOVEDför att på plats ta bort privilegier, binder enDISABLE_MAX_PRIVILEGE-begränsad personifieringstoken med låg integritetsnivå (S-1-16-4096), tilldelar processen till ett anonymt Job Object medJOB_OBJECT_LIMIT_ACTIVE_PROCESS = 1(vilket blockerar skapande av underordnade processer) och aktiverarDisallowWin32kSystemCalls = 1viaSetProcessMitigationPolicy. - macOS XNU (
lockdown_darwin.go): SätterRLIMIT_NPROC = 0och tillämpar en Seatbelt-profil (sandbox_init_with_parameters) som börjar med(version 1)(deny default)och blockerarprocess-fork,process-exec*samt alla nya öppningar av värdfiler. - Linux (
lockdown_linux.go+pkg/gvx/syscallfilter_linux.go): SätterPR_SET_NO_NEW_PRIVSoch installerar Sentrys BPFseccomp-tillåtelselista över alla trådar (SECCOMP_FILTER_FLAG_TSYNC) med explicitaEPERM-regler förexecve,execveat,clone,openatochsocket.
Plattformsoberoende avlyssning av systemanrop och 4 KB-paging (pkg/sentry/platform)
Linux-ELF64-binärer förutsätter en minnessidgranularitet på 4 KB (0x1000) för segmentmappningar via mmap(MAP_FIXED). Windows VirtualAlloc och MapViewOfFile3 tvingar dock fram 64 KB-justering (0x10000), och Apple Silicon macOS (darwin/arm64) använder 16 KB-sidor (0x4000) på värden. Dessutom kräver varje OS en annan mekanism för att avlyssna Linux-instruktionerna SYSCALL (amd64) och SVC #0 (arm64) utan att starta en Linux-VM.
gvx löser båda problemen över fyra exekveringsbackends som implementerar pkg/sentry/platform.Context:

1. Windows hårdvarumotor: platform/whpx (WinHvPlatform.dll)
På windows/amd64 och windows/arm64 skapar platform/whpx en lättviktig hypervisor-partition via WHvCreatePartition utan att starta en gästoperativsystemkärna:
- Frikoppling av 64 KB till 4 KB-paging:
whpxregistrerar varje 64 MiB-arena ipgalloc.MemoryFilehos partitionen en gång viaWHvMapGpaRange. Efterföljande anrop tillMapFileochUnmapändrar endast gästens sidtabeller inuti partitionen, vilket upprätthåller exakta 4 KB-behörigheterR/W/Xtrots Windows allokeringsgranularitet på 64 KB. - Registerutbyte utan API-anrop via
CommPage:- På
amd64går gästens Ring-3-SYSCALLin i en Ring-0-stubMSR_LSTARinuti partitionen (stubgen_x86.go), som skriver de snabba systemanropens GPR-register (RAX, RDI, RSI, RDX, R10, R8, R9, RIP, RSP) till en delad 4 KB-CommPageoch körHLTför att avslutaWHvRunVirtualProcessor. - På
arm64går gästens EL0-SVC #0in i EL1-vektorerna (stubgen_a64.go), skriverX0..X8, SP_EL0, ELR_EL1, PSTATEtillCommPageoch läser från en ej backad GPA-nolla för att utlösa ett minnesavbrott tillbaka till Sentry.
- På
2. macOS hårdvarumotor: platform/hvf (Hypervisor.framework)
På Apple Silicon (darwin/arm64) kör platform/hvf containertrådar inuti Hypervisor.framework-vCPU:er (hv_vm_create, hv_vcpu_run):
- Tvåstegs-MMU-översättning med 4 KB över 16 KB: Steg 2 (
hv_vm_map) mappar 1 GiB-delar av minnesfiler till det mellanliggande fysiska adressutrymmet (IPA) en gång, med 16 KB-granularitet för värdsidor och medHV_MEMORY_READ | HV_MEMORY_WRITE | HV_MEMORY_EXEC. Inuti vCPU:n konfigurerar EL1-stubbenbluepill(bluepill_arm64.go)TCR_EL1.TG0 = 0b00(4 KB-granulat) iTTBR0_EL1och fyller i 4 KB-sidtabellposter för steg 1 med exakta behörigheterR-XellerRW-(UXN=1). Eftersom ARM64-MMU:n upprätthåller skärningspunkten mellan behörigheterna för steg 1 och steg 2 fångar hårdvaran behörighetsfel vid exakta 4 KB-gränser. - Avlyssning av systemanrop: Gästens EL0-
SVC #0går till EL1VBAR_EL1, skriver register till den delade 4 KB-CommPageoch körHVC #0för att återvända frånhv_vcpu_runtill Go-trådens Sentry.
3. Windows användarlägesreserv: platform/systrap (winsystrap)
När hårdvaruppvisning inte är tillgänglig (till exempel inuti kapslade molnvirtuella maskiner) kör winsystrap gästkod i användarläge på windows/amd64 och windows/arm64:
- Isolering via hårdvarans
CR3/TTBR0_EL1: Gästtrådar körs inuti isolerade stubbunderprocesser som startas från en genererad körbar fil på 1 KB (stubimage_xplat.go), begränsade av en token med låg integritetsnivå och ett Job Object. - Binär patchning på plats och
winSysmsg-IPC: Påamd64ersätter patcharen (patcher_amd64_xplat.go) 2-byte-instruktionernasyscall(0f 05) med 5-byte-hoppjmp rel32(ellerCC 90-brytpunktsfällor när ett 5-byte-fönster inte är tillgängligt) till en snabbvägsö. Påarm64patchassvc #0med enB-förgrening till en ö-plats. Ön skriver register till den deladewinSysmsg-sidan och synkroniseras med Sentry viaWaitOnAddressochWakeByAddressSingle, medan opatchade fällor och sidfel stannar vid ett Windows Debug Object (WaitForDebugEventEx).
4. Linux användarlägesmotor: platform/systrap
På linux/amd64 och linux/arm64 kör systrap gästtrådar (sysmsg) som fångar gästens systemanrop via SECCOMP_RET_TRAP (SIGSYS) och binärpatchar syscall-platser till en trampolin i delat minne (sysmsg) som synkroniseras via SYS_FUTEX. Samtidigt utför en stubbad syscallThread (syscall_thread.go) operationer på adressutrymmet (mmap, munmap, clone) via SECCOMP_RET_USER_NOTIF (_RUN_SECCOMP_LOOP). När gvx körs inuti en yttre gVisor-sandlåda (runsc) där den enda SECCOMP_FILTER_FLAG_NEW_LISTENER-platsen returnerar EBUSY, växlar gvx automatiskt syscallThread till en futex-slinga i delat minne (_RUN_SYSCALL_LOOP), vilket beskrivs i detalj i del 3 i denna serie.
Hur gvx mappar gästens Linux-systemanrop till virtuella delsystem och värdhandtag
När en plattformsmotor har avlyssnat en gästs SYSCALL (amd64) eller SVC #0 (arm64) och skrivit gästregistren till den delade 4 KB-sidan (CommPage, sysmsg eller winSysmsg), återgår kontrollen till Go-goroutinen för Sentry-uppgiften (pkg/sentry/kernel/task_syscall.go). I stället för att vidarebefordra Linux-systemanrop till värd-OS:ets underliggande kärna sparar Task.doSyscall() det ursprungliga registertillståndet (SyscallSaveOrig()), utvärderar eventuella BPF seccomp-filter och ptrace-stopp i gästen och skickar systemanropsnumret vidare via linux.AMD64 eller linux.ARM64 (pkg/sentry/syscalls/linux/linux64.go, som omfattar cirka 370 Linux-systemanrop) till fyra interna domäner:

| Systemanropsdomän | Representativa Linux-systemanrop i gästen | Internt gvx-delsystem (pkg/sentry) | Interaktion med värd-OS (efter hostlockdown) |
|---|---|---|---|
| A. Process, namnrymd, montering & synkronisering | clone, execve, unshare, setns, mount, umount2, pivot_root, setuid, capset, futex, epoll_wait, pipe2 | kernel.Task, virtuella credentials, futex-tabell och VFS2 i minnet (procfs, sysfs, cgroupfs v2, tmpfs, overlayfs) | Noll systemanrop till värden (100 % Go-tillstånd i minnet; helt osynligt för värd-OS) |
B. Virtuellt minne (MM) | mmap, munmap, mremap, mprotect, brk, madvise, memfd_create | mm.MemoryManager (VMA-träd med 4 KB) och pgalloc.MemoryFile | Dubbelriktade uppdateringar av 4 KB-sidtabeller via WHvMapGpaRange, hv_vm_map + TTBR0_EL1 eller stubbens mmap/mprotect |
C. Filer och lagring (VFS2) | openat, pread64, pwrite64, getdents64, statx, renameat2, setxattr, mknod, inotify_add_watch | vfs.VirtualFilesystem-monteringstabellens router (fsimpl/erofs, fsimpl/ext4lite, fsimpl/hostfs v2) | Dubbelriktade pread/pwrite mot 1 fixerad .erofs-FD eller 1 .ext4-FD, eller avgränsade relativa sökningar på *os.Root |
| D. Nätverk och Netfilter | socket, bind, listen, connect, sendmsg, recvmsg, netlink, setsockopt(IPT_SO_SET_REPLACE) | Användarlägets pkg/tcpip (lo, eth0, docker0, veth, vxlan, wireguard, ipset, iptables-legacy) | Trafik inom sandlådan använder noll systemanrop till värden; extern trafik mappas till net.Dial/net.Listen (-net=nat) eller 1 UDP-uttag (-net=wg) |
När hanteraren returnerar (rval, ctrl, err) omvandlar Task.doSyscallInvoke() eventuella fel till ett negativt Linux-errno (-ExtractErrno(err, sysno)), skriver returvärdet tillbaka till den delade registersidan (RAX på amd64, X0 på arm64) och återupptar gästens exekvering.
Lagring (VFS2) och tvåspårig nätverkning i användarläge (netstack)
All gästens fil- och nätverksverksamhet hanteras inuti Go-kärnan Sentry via tre VFS2-filsystem och två pkg/netbackend-spår:

| Lagringsmotor | Monteringsroll i gvx | Underliggande värdresurs | Värdens openat- / NtCreateFile-anrop vid körning | Windows Defender (WdFilter.sys) skanningar per fil |
|---|---|---|---|---|
fsimpl/erofs | Skrivskyddade nedre lager för OCI-avbilder | En enda <sha256>.erofs-fil per lager | 0 | 0 |
fsimpl/ext4lite | Namngivna volymer (-v name:/path) | En enda <name>.ext4-fil per volym | 0 | 0 |
fsimpl/hostfs v2 | Värdarbetsyta (-workspace, -v /host:/guest) | Fixerad värdkatalog som *os.Root | O(djup) per ej cachad öppning | 1 per värdfil som stängs |
Varför ext4lite är viktigt för Docker, databaser och paketdomare
Att extrahera eller skriva tiotusentals containerfiler direkt till Windows NTFS eller macOS APFS utlöser skanningar av antivirusfilterdrivrutinen (WdFilter.sys), kollisioner mellan versaler och gemener samt saknade utökade POSIX-attribut.
pkg/sentry/fsimpl/ext4lite implementerar ett rent Go-baserat ext4-filsystem inuti en enda förtöppnad värdfil (%LOCALAPPDATA%\gvx\cache\volumes\<name>.ext4 på Windows, ~/Library/Caches/gvx/volumes/<name>.ext4 på macOS eller ~/.cache/gvx/volumes/<name>.ext4 på Linux) med:
- Block om 4 KiB och dynamiska blockgrupper om 16 MiB (
meta_bg): Växer dynamiskt utan stopp orsakade av allokering av glesa filer (fallocatepå Linux,fcntl(F_PREALLOCATE)på macOS,FileAllocationInfopå Windows) och packar ihop oanvända bakre grupper vid ren avmontering. - JBD2-journal i ordnat läge (inod 8): Atomära transaktionsbekräftelser med CRC-32-kontrollsummor och återkallningsblock, samt återställning via
fsckLite. - Stöd för
overlayfsövre lager: Implementerar utökade attributtrusted.overlay.*ochsecurity.capability, teckenenhets-whiteouts (0:0), rapportering avd_typeför katalogposter samtRENAME_EXCHANGE.
Praktiska arbetslaster: Docker-motor, Kubernetes (k3s + Calico) och Go-SDK
Installera rungv: SLSA-attesterade förbyggda utgåvor eller go install
Du kan ladda ner ett förbyggt rungv-arkiv för linux/amd64, linux/arm64, darwin/arm64, windows/amd64 eller windows/arm64 från GitHub Releases (vart och ett byggt av SLSA-release-pipelinen med ett Sigstore-provenansbuntel och SHA256SUMS), eller installera direkt från källkoden med go install:
1# Alternativ A: Ladda ner en förbyggd utgåva och verifiera SLSA-byggprovenans
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# Alternativ B: Installera direkt via Go (Linux / Windows)
9CGO_ENABLED=0 go install z.gfire.dev/gvx/cmd/rungv@latest
På macOS (
darwin/arm64) är det förbyggda utgåvearkivet redan signerat med rättighetencom.apple.security.hypervisor(xattr -d com.apple.quarantine rungvtar bort webbläsarens karantän). Om du bygger lokalt medgo installellergo buildpå macOS signerar du binären medcodesign --sign - --entitlements entitlements.plist --force $(which rungv).
1. Köra en obehörig Docker-motor (docker:dind)
Enligt docs/docker-engine.md kan du köra en oförändrad docker:dind-container inuti rungv på Windows, macOS eller Linux och styra den med din värds docker-CLI:
1# 1. Skapa värdkatalogen för det vidarebefordrade Unix-uttaget
2mkdir -p ~/.gvx
3
4# 2. Starta docker:dind inuti en gvx-sandlåda med en ext4lite-volym
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
I en andra terminal pekar du din värds docker-CLI mot sandlådans uttag:
1docker context create gvx --docker host=unix://$HOME/.gvx/docker.sock
2docker context use gvx
3
4docker info --format '{{.Driver}} {{.OperatingSystem}}'
5# Utdata: overlayfs Alpine Linux ... (containerized)
6
7docker build -t myapp ./app
8docker compose up -d
2. Köra Kubernetes (k3s + Calico) i obehöriga sandlådor
Eftersom gvx implementerar /dev/kmsg, cgroup v2 (OOM-dödaren memory.max och bandbreddsbegränsningen cpu.max), /proc/diskstats, netlink-enheterna wireguard och vxlan, ipset samt iptables-legacy (addrtype, conntrack, statistic, recent), når en oförändrad rancher/k3s-avbild tillståndet Ready inuti en enda rungv-process:
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
Du kan även ansluta flera gvx-noder över WireGuard (flannel-backend=wireguard-native eller ett wg0-nät) med inbäddad etcd för hög tillgänglighet, eller köra Calico (calico-vxlan.yaml med FELIX_IPTABLESBACKEND=Legacy) för att helt i användarläge upprätthålla Kubernetes NetworkPolicy.
3. Bädda in Linux-containrar i Go via z.gfire.dev/gvx/pkg/gvx
Go-applikationer kan bädda in Linux-containrar direkt utan en extern 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}
Vidare läsning i denna serie
- gvx: Köra Linux-containrar, Docker och Kubernetes i en enda obehörig process (detta inlägg)
- Köra Docker, docker build och docker compose utan root eller användarnamnrymder: Hur man startar
docker:dindi låsta sandlådor därsudo,unshare(CLONE_NEWUSER),mount(2)och/dev/fuseär blockerade. - Köra Docker-in-Docker inuti gVisor och kapslade sandlådor med gvx: Varför
apt-get install docker.iooch kapsladrunscmisslyckas inutigVisoröver fem kärnbarriärer, och hurgvxkör kapsladdocker:dindmed en futex-slinga i delat minne. - Snabba isolerade Docker-miljöer för RL-utrullningar av AI-agenter med gvx: Skalning av
SWE-benchoch förstärkningsinlärning för agenter med hundratals parallella obehörigadocker:dind-instanser och kopieringsvid-skrivning-återställningar av.ext4-volymer på 100 millisekunder.