gvx: Kørsel af Linux-containere, Docker og Kubernetes i én uprivilegeret proces

Introduktion: Sikkerhedshærdede containere uden root eller værtsdaemoner
At køre Linux-containere, docker build eller Kubernetes inde i en udviklingsmaskine, CI-runner eller multi-tenant cloud-node tvinger som regel en ubehagelig sikkerhedsafvejning:
- På Linux kræver standard OCI-runtimes (
runc,crun,runsc) entenroot-rettigheder, eksponering af host-/var/run/docker.sock,--privileged(CAP_SYS_ADMIN) eller user namespaces (unshare(CLONE_NEWUSER)), som udvider angrebsfladen i værtskernen. - På Windows og macOS starter containermotorer (
Docker Desktop,Podman Machine,Lima,OrbStack) en fuld Linux-kerne i en virtuel maskine inde i WSL2, Hyper-V ellerVirtualization.frameworkog kører en vedvarende baggrundsdaemon inde i denne VM.
gvx (rungv-CLI'en og z.gfire.dev/gvx/pkg/gvx Go SDK'en) udgør et sikkert alternativ uden privilegier. Bygget som en fork af Googles gVisor brugerrums-Linux-kerne (Sentry) og brugerrums-netværksstak (netstack), som kan spores opstrøms, eksekverer gvx uændrede linux/amd64- og linux/arm64-ELF64-binærfiler og OCI-containere direkte inde i en enkelt proces på værten uden privilegier og med selvlåsning på Windows, macOS og Linux.
Sikkerhedsgaranti (Tre-ring forsvar i dybden):
gvxsvækker aldrig sikkerhedsgrænserne i værtens OS, installerer aldrig kerne-drivere og kræver aldrigroot,sudo,--privilegedpå værten ellerCLONE_NEWUSER. Før en enkelt gæsteinstruktion eksekveres, låsergvxsin egen værtsproces ned ved hjælp af Windows Restricted Tokens og Job Objects, macOS Seatbelt (deny default) eller LinuxPR_SET_NO_NEW_PRIVS+ BPFseccomp. Alle gæstesystemkald, virtuelleUID 0-privilegier, monteringer (VFS2) og netværkstabeller (netstack) er strengt afgrænset inde i den hukommelsessikre GoSentry-kerne.
| Runtime | Værts-OS | Cold Start | Memory Footprint | Admin eller sudo påkrævet | Baggrunds-VM-daemon | Direkte OCI-billedkørsel |
|---|---|---|---|---|---|---|
gvx (whpx / hvf) | Windows (amd64, arm64) & macOS (arm64) | ~20 ms | ~30 MiB | Nej | Nej | Ja |
gvx (systrap) | Windows (amd64, arm64) & Linux (amd64, arm64) | ~25 to 65 ms | ~31 MiB | Nej | Nej | Ja |
| WSL2 / Docker Desktop | Windows & macOS | 800 to 2500 ms | > 512 MiB | Ja (initial opsætning) | Ja | Kræver daemon |
| QEMU (Full System) | Windows & macOS | 1500 to 4000 ms | > 256 MiB | Nej | Nej | Kræver diskbillede |
Der startes ingen Linux-VM-kerne på Windows eller macOS. Der kører ingen baggrundsdaemon på værten. Der kræves ingen root, sudo, user namespaces (CLONE_NEWUSER), mount(2), TUN/TAP-drivere eller CAP_NET_ADMIN-privilegier på værts-OS'et. Inde i denne proces uden privilegier kører gvx Docker-in-Docker (docker:dind), docker build, docker compose og multi-node højtilgængelig Kubernetes (k3s med indlejret etcd og Calico CNI).
Containerens livscyklus og tre-ring-låsning af værten
Hver gvx-sandkasse kører inde i en enkelt proces på værten uden privilegier og håndhæver en livscyklus i tre faser: præ-åbning af alle nødvendige fildeskriptorer og sockets på værten, låsning af værts-OS-processen via pkg/hostlockdown og indtræden i den in-process Sentry-eksekveringsløkke.

Før kontrollen overdrages til den første gæsteinstruktion, præ-åbner gvx sine cachede .erofs-billedlagdeskriptorer, .ext4-volumen-fildeskriptorer, /workspace-*os.Root-håndtaget og værtens netværkssockets og kalder derefter hostlockdown.Apply():
- Windows NT (
lockdown_windows.go): KalderAdjustTokenPrivilegesmedSE_PRIVILEGE_REMOVEDfor at fjerne privilegier på stedet, binder etDISABLE_MAX_PRIVILEGE-begrænset impersonation-token med Low-integritet (S-1-16-4096), tildeler processen til et anonymt Job Object medJOB_OBJECT_LIMIT_ACTIVE_PROCESS = 1(blokerer oprettelse af child-processer) og aktivererDisallowWin32kSystemCalls = 1viaSetProcessMitigationPolicy. - macOS XNU (
lockdown_darwin.go): SætterRLIMIT_NPROC = 0og anvender en Seatbelt-profil (sandbox_init_with_parameters), der starter fra(version 1)(deny default)og blokererprocess-fork,process-exec*og alle nye filåbninger på værten. - Linux (
lockdown_linux.go+pkg/gvx/syscallfilter_linux.go): SætterPR_SET_NO_NEW_PRIVSog installerer Sentry BPFseccomp-tilladelsesliste på tværs af alle tråde (SECCOMP_FILTER_FLAG_TSYNC) med eksplicitteEPERM-regler forexecve,execveat,clone,openatogsocket.
Krydsplatforms-interception af systemkald og 4 KB-paging (pkg/sentry/platform)
Linux ELF64-binærfiler antager en hukommelsessidegranularitet på 4 KB (0x1000) for mmap(MAP_FIXED)-segmentafbildninger. Windows VirtualAlloc og MapViewOfFile3 håndhæver dog 64 KB (0x10000)-justering, og Apple Silicon macOS (darwin/arm64) bruger 16 KB (0x4000)-sider på værten. Desuden kræver hvert OS en forskellig mekanisme for at opsnappe Linux-instruktionerne SYSCALL (amd64) og SVC #0 (arm64) uden at starte en Linux-VM.
gvx løser begge problemer på tværs af fire eksekveringsbackends, der implementerer pkg/sentry/platform.Context:

1. Windows-hardwaremotor: platform/whpx (WinHvPlatform.dll)
På windows/amd64 og windows/arm64 opretter platform/whpx en letvægts-hypervisor-partition via WHvCreatePartition uden at starte en gæste-OS-kerne:
- 64 KB-til-4 KB Paging Decoupler:
whpxregistrerer hver 64 MiB-arena afpgalloc.MemoryFilehos partitionen én gang viaWHvMapGpaRange. Efterfølgende kald afMapFileogUnmapændrer kun gæstens sidetabeller inde i partitionen og håndhæver præcise 4 KBR/W/X-rettigheder på trods af Windows' 64 KB-allokeringsgranularitet. - Register-udveksling uden API via
CommPage:- På
amd64går gæstens Ring-3SYSCALLind i en in-partition Ring-0MSR_LSTAR-stub (stubgen_x86.go), som skriver fast-path-systemkaldets GPR'er (RAX, RDI, RSI, RDX, R10, R8, R9, RIP, RSP) ud i en delt 4 KBCommPageog udførerHLTfor at afslutteWHvRunVirtualProcessor. - På
arm64går gæstens EL0SVC #0ind i EL1-vektorer (stubgen_a64.go), skriverX0..X8, SP_EL0, ELR_EL1, PSTATEtilCommPageog læser fra ubacket GPA nul for at udløse en hukommelsesafslutning tilbage til Sentry.
- På
2. macOS-hardwaremotor: platform/hvf (Hypervisor.framework)
På Apple Silicon (darwin/arm64) kører platform/hvf containertråde inde i Hypervisor.framework-vCPU'er (hv_vm_create, hv_vcpu_run):
- To-trins 4 KB over 16 KB MMU-oversættelse: Stage-2 (
hv_vm_map) afbilder 1 GiB hukommelsesfil-bidder ind i Intermediate Physical Address (IPA)-rummet én gang med 16 KB sidegranularitet på værten medHV_MEMORY_READ | HV_MEMORY_WRITE | HV_MEMORY_EXEC. Inde i vCPU'en konfigurerer EL1-stubbenbluepill(bluepill_arm64.go)TCR_EL1.TG0 = 0b00(4 KB-granule) iTTBR0_EL1og udfylder 4 KB Stage-1-sidetabelindgange med præciseR-X- ellerRW-(UXN=1)-rettigheder. Fordi ARM64 MMU'en håndhæver skæringspunktet af Stage-1- og Stage-2-rettigheder, fanger hardwaren rettighedsfejl præcist på 4 KB-grænser. - Systemkaldsfælde: Gæstens EL0
SVC #0fanges af EL1VBAR_EL1, skriver registre til den delte 4 KBCommPageog udførerHVC #0for at vende tilbage frahv_vcpu_runtil Go Sentry-tråden.
3. Windows-fallback i brugertilstand: platform/systrap (winsystrap)
Når hardwarevirtualisering ikke er tilgængelig (f.eks. inde i indlejrede cloud-VM'er), kører winsystrap gæstekode i brugertilstand på windows/amd64 og windows/arm64:
- Hardware-isolation via
CR3/TTBR0_EL1: Gæstetråde kører inde i isolerede stub-underprocesser, startet fra en genereret 1 KB-eksekverbar fil (stubimage_xplat.go), afgrænset af et Low-Integrity-token og et Job Object. - Patching på stedet i binærfiler &
winSysmsgIPC: Påamd64erstatter patcheren (patcher_amd64_xplat.go) 2-bytesyscall-instruktioner (0f 05) med 5-bytejmp rel32-hop (ellerCC 90-breakpoint-fælder, når et 5-byte-vindue ikke er tilgængeligt) ind i en fast-path-ø. Påarm64patchessvc #0med enB-forgrening til en øplads. Øen skriver registre til den deltewinSysmsg-side og synkroniseres med Sentry viaWaitOnAddressogWakeByAddressSingle, mens upatchede fælder og sidefejl standser på et Windows Debug Object (WaitForDebugEventEx).
4. Linux-motor i brugertilstand: platform/systrap
På linux/amd64 og linux/arm64 kører systrap gæstetråde (sysmsg), der fanger gæstesystemkald via SECCOMP_RET_TRAP (SIGSYS) og binært patcher syscall-steder ind i en delt-hukommelses-trampolin (sysmsg), synkroniseret via SYS_FUTEX, mens en stub-syscallThread (syscall_thread.go) udfører adresserumsoperationer (mmap, munmap, clone) via SECCOMP_RET_USER_NOTIF (_RUN_SECCOMP_LOOP). Når gvx kører inde i en ydre gVisor (runsc)-sandkasse, hvor den enkelte SECCOMP_FILTER_FLAG_NEW_LISTENER-plads returnerer EBUSY, skifter gvx automatisk syscallThread til en futex-løkke i delt hukommelse (_RUN_SYSCALL_LOOP), som beskrevet i detaljer i Del 3 af denne serie.
Hvordan gvx afbilder gæste-Linux-systemkald til virtuelle delsystemer og værtshåndtag
Når en platformmotor har fanget en gæstes SYSCALL (amd64) eller SVC #0 (arm64)-instruktion og skrevet gæsteregistrene til den delte 4 KB-side (CommPage, sysmsg eller winSysmsg), vender kontrollen tilbage til Go Sentry-opgavegoroutinen (pkg/sentry/kernel/task_syscall.go). I stedet for at videresende Linux-systemkald til den underliggende værts-OS-kerne, gemmer Task.doSyscall() den oprindelige registertilstand (SyscallSaveOrig()), evaluerer eventuelle BPF seccomp-filtre og ptrace-stop i gæsten og sender systemkaldsnummeret gennem linux.AMD64 eller linux.ARM64 (pkg/sentry/syscalls/linux/linux64.go, der dækker ~370 Linux-systemkald) ind i fire interne domæner:

| Systemkaldsdomæne | Repræsentative gæste-Linux-systemkald | Internt gvx-delsystem (pkg/sentry) | Interaktion med værts-OS-grænsen (efter hostlockdown) |
|---|---|---|---|
| A. Proces, namespace, montering & synkronisering | clone, execve, unshare, setns, mount, umount2, pivot_root, setuid, capset, futex, epoll_wait, pipe2 | kernel.Task, virtuelle legitimationsoplysninger, futex-tabel og in-memory VFS2 (procfs, sysfs, cgroupfs v2, tmpfs, overlayfs) | Nul værtssystemkald (100% in-memory Go-tilstand; fuldstændigt usynlig for værts-OS'et) |
B. Virtuel hukommelse (MM) | mmap, munmap, mremap, mprotect, brk, madvise, memfd_create | mm.MemoryManager (4 KB VMA-træ) & pgalloc.MemoryFile | Tovejsopdateringer af 4 KB-sidetabeller via WHvMapGpaRange, hv_vm_map + TTBR0_EL1 eller stub-mmap/mprotect |
C. Fil og lager (VFS2) | openat, pread64, pwrite64, getdents64, statx, renameat2, setxattr, mknod, inotify_add_watch | vfs.VirtualFilesystem-monteringstabelrouter (fsimpl/erofs, fsimpl/ext4lite, fsimpl/hostfs v2) | Tovejs pread/pwrite på 1 fastgjort .erofs-FD eller 1 .ext4-FD, eller afgrænsede relative gennemgange af *os.Root |
| D. Netværk & Netfilter | socket, bind, listen, connect, sendmsg, recvmsg, netlink, setsockopt(IPT_SO_SET_REPLACE) | pkg/tcpip i brugertilstand (lo, eth0, docker0, veth, vxlan, wireguard, ipset, iptables-legacy) | Trafik internt i sandkassen bruger nul værtssystemkald; ekstern trafik afbildes til net.Dial/net.Listen (-net=nat) eller 1 UDP-socket (-net=wg) |
Når handleren returnerer (rval, ctrl, err), konverterer Task.doSyscallInvoke() enhver fejl til en negativ Linux-errno (-ExtractErrno(err, sysno)), skriver returværdien tilbage til den delte registerside (RAX på amd64, X0 på arm64) og genoptager gæsteeksekveringen.
Lager (VFS2) og netværk i brugertilstand med to spor (netstack)
Alle gæstens fil- og netværksoperationer betjenes inde i Go Sentry gennem tre VFS2-filsystemer og to pkg/netbackend-spor:

| Lagermotor | Monteringsrolle i gvx | Bagvedliggende værtsressource | Værtens openat- / NtCreateFile-kald under kørsel | Windows Defender (WdFilter.sys) scanninger pr. fil |
|---|---|---|---|---|
fsimpl/erofs | Skrivebeskyttede nedre OCI-billedlag | Enkelt <sha256>.erofs-fil pr. lag | 0 | 0 |
fsimpl/ext4lite | Navngivne volumener (-v name:/path) | Enkelt <name>.ext4-fil pr. volumen | 0 | 0 |
fsimpl/hostfs v2 | Arbejdsområde på værten (-workspace, -v /host:/guest) | Fastgjort *os.Root-værtsmappe | O(depth) pr. ukached åbning | 1 pr. lukning af værtsfil |
Hvorfor ext4lite betyder noget for Docker, databaser og pakkehåndteringer
Udpakning eller skrivning af titusindvis af containerfiler direkte til Windows NTFS eller macOS APFS udløser scanninger af antivirus-minifiltre (WdFilter.sys), kollisioner pga. versalfølsomhed og manglende POSIX-udvidede attributter.
pkg/sentry/fsimpl/ext4lite implementerer et rent Go-ext4-filsystem inde i en enkelt præ-åbnet værtsfil (%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:
- 4 KiB-blokke og 16 MiB dynamiske blokgrupper (
meta_bg): Vokser dynamisk uden stop i forbindelse med allokering af sparse-filer (fallocatepå Linux,fcntl(F_PREALLOCATE)på macOS,FileAllocationInfopå Windows) og komprimerer ubrugte efterfølgende grupper ved ren afmontering. - JBD2-journal i ordered-tilstand (inode 8): Atomare transaktionsbekræftelser med CRC-32-checksummer og revoke-blokke samt
fsckLite-gendannelse. - Understøttelse af øvre lag i
overlayfs: Implementerertrusted.overlay.*- ogsecurity.capability-udvidede attributter, whiteouts for tegnenheder (0:0),d_type-rapportering af mappeindgange ogRENAME_EXCHANGE.
Praktiske arbejdsbelastninger: Docker Engine, Kubernetes (k3s + Calico) og Go SDK
Installation af rungv: SLSA-attesterede færdigbyggede udgivelser eller go install
Du kan downloade et færdigbygget rungv-arkiv til linux/amd64, linux/arm64, darwin/arm64, windows/amd64 eller windows/arm64 fra GitHub Releases (hvert bygget af SLSA-udgivelsespipelinen med en Sigstore-provenansbundle og SHA256SUMS), eller installere direkte fra kildekoden med go install:
1# Mulighed A: Download en færdigbygget udgivelse og verificér SLSA-build-provenans
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# Mulighed B: Installér direkte via Go (Linux / Windows)
9CGO_ENABLED=0 go install z.gfire.dev/gvx/cmd/rungv@latest
På macOS (
darwin/arm64) er det færdigbyggede udgivelsesarkiv allerede signeret med rettighedencom.apple.security.hypervisor(xattr -d com.apple.quarantine rungvfjerner browserens karantæne). Hvis du bygger lokalt medgo installellergo buildpå macOS, signerer du binærfilen medcodesign --sign - --entitlements entitlements.plist --force $(which rungv).
1. Kørsel af en Docker Engine uden privilegier (docker:dind)
Ved at følge docs/docker-engine.md kan du køre en uændret docker:dind-container inde i rungv på Windows, macOS eller Linux og styre den med din værts docker-CLI:
1# 1. Opret værtsmappen til den videresendte Unix-socket
2mkdir -p ~/.gvx
3
4# 2. Start docker:dind inde i en gvx-sandkasse med et ext4lite-volumen
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 anden terminal peger du din værts docker-CLI mod sandkassens socket:
1docker context create gvx --docker host=unix://$HOME/.gvx/docker.sock
2docker context use gvx
3
4docker info --format '{{.Driver}} {{.OperatingSystem}}'
5# Resultat: overlayfs Alpine Linux ... (containerized)
6
7docker build -t myapp ./app
8docker compose up -d
2. Kørsel af Kubernetes (k3s + Calico) i sandkasser uden privilegier
Da gvx implementerer /dev/kmsg, cgroup v2 (memory.max OOM-killer og cpu.max båndbreddebegrænsning), /proc/diskstats, wireguard- og vxlan-netlink-enheder, ipset og iptables-legacy (addrtype, conntrack, statistic, recent), når et uændret rancher/k3s-image status Ready inde i en enkelt 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
Du kan også forbinde flere gvx-noder over WireGuard (flannel-backend=wireguard-native eller et wg0-mesh) med indlejret etcd-højtilgængelighed, eller køre Calico (calico-vxlan.yaml med FELIX_IPTABLESBACKEND=Legacy) for at håndhæve Kubernetes NetworkPolicy fuldstændigt inde i brugerrummet.
3. Indlejring af Linux-containere i Go via z.gfire.dev/gvx/pkg/gvx
Go-applikationer kan indlejre Linux-containere direkte uden en ekstern 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}
Yderligere læsning i denne serie
- gvx: Kørsel af Linux-containere, Docker og Kubernetes i én proces uden privilegier (dette indlæg)
- Kørsel af Docker, docker build og docker compose uden root eller user namespaces: Sådan bootstrapper du
docker:dindi låste sandkasser, hvorsudo,unshare(CLONE_NEWUSER),mount(2)og/dev/fuseer blokeret. - Kørsel af Docker-in-Docker inde i gVisor og indlejrede sandkasser med gvx: Hvorfor
apt-get install docker.ioog indlejretrunscfejler inde igVisorpå tværs af fem kernebarrierer, og hvordangvxkører indlejretdocker:dindved hjælp af en futex-løkke i delt hukommelse. - Hurtige isolerede Docker-miljøer til AI-agent RL-rollouts med gvx: Skalering af
SWE-benchog rollouts med forstærkningslæring for agenter med hundredvis af parallelledocker:dind-instanser uden privilegier og 100 millisekunders copy-on-write-nulstillinger af.ext4-volumener.