GoSuda

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

By Lemon Mint views ...

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) enten root-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 eller Virtualization.framework og 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): gvx svækker aldrig sikkerhedsgrænserne i værtens OS, installerer aldrig kerne-drivere og kræver aldrig root, sudo, --privileged på værten eller CLONE_NEWUSER. Før en enkelt gæsteinstruktion eksekveres, låser gvx sin egen værtsproces ned ved hjælp af Windows Restricted Tokens og Job Objects, macOS Seatbelt (deny default) eller Linux PR_SET_NO_NEW_PRIVS + BPF seccomp. Alle gæstesystemkald, virtuelle UID 0-privilegier, monteringer (VFS2) og netværkstabeller (netstack) er strengt afgrænset inde i den hukommelsessikre Go Sentry-kerne.

RuntimeVærts-OSCold StartMemory FootprintAdmin eller sudo påkrævetBaggrunds-VM-daemonDirekte OCI-billedkørsel
gvx (whpx / hvf)Windows (amd64, arm64) & macOS (arm64)~20 ms~30 MiBNejNejJa
gvx (systrap)Windows (amd64, arm64) & Linux (amd64, arm64)~25 to 65 ms~31 MiBNejNejJa
WSL2 / Docker DesktopWindows & macOS800 to 2500 ms> 512 MiBJa (initial opsætning)JaKræver daemon
QEMU (Full System)Windows & macOS1500 to 4000 ms> 256 MiBNejNejKræ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.

gvx-containerens livscyklus og selvlåsning af værten

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():

  1. Windows NT (lockdown_windows.go): Kalder AdjustTokenPrivileges med SE_PRIVILEGE_REMOVED for at fjerne privilegier på stedet, binder et DISABLE_MAX_PRIVILEGE-begrænset impersonation-token med Low-integritet (S-1-16-4096), tildeler processen til et anonymt Job Object med JOB_OBJECT_LIMIT_ACTIVE_PROCESS = 1 (blokerer oprettelse af child-processer) og aktiverer DisallowWin32kSystemCalls = 1 via SetProcessMitigationPolicy.
  2. macOS XNU (lockdown_darwin.go): Sætter RLIMIT_NPROC = 0 og anvender en Seatbelt-profil (sandbox_init_with_parameters), der starter fra (version 1)(deny default) og blokerer process-fork, process-exec* og alle nye filåbninger på værten.
  3. Linux (lockdown_linux.go + pkg/gvx/syscallfilter_linux.go): Sætter PR_SET_NO_NEW_PRIVS og installerer Sentry BPF seccomp-tilladelsesliste på tværs af alle tråde (SECCOMP_FILTER_FLAG_TSYNC) med eksplicitte EPERM-regler for execve, execveat, clone, openat og socket.

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:

gvx-platformens interception af systemkald og 4KB-paging

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: whpx registrerer hver 64 MiB-arena af pgalloc.MemoryFile hos partitionen én gang via WHvMapGpaRange. Efterfølgende kald af MapFile og Unmap ændrer kun gæstens sidetabeller inde i partitionen og håndhæver præcise 4 KB R/W/X-rettigheder på trods af Windows' 64 KB-allokeringsgranularitet.
  • Register-udveksling uden API via CommPage:
    • På amd64 går gæstens Ring-3 SYSCALL ind i en in-partition Ring-0 MSR_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 KB CommPage og udfører HLT for at afslutte WHvRunVirtualProcessor.
    • På arm64 går gæstens EL0 SVC #0 ind i EL1-vektorer (stubgen_a64.go), skriver X0..X8, SP_EL0, ELR_EL1, PSTATE til CommPage og læser fra ubacket GPA nul for at udløse en hukommelsesafslutning tilbage til Sentry.

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 med HV_MEMORY_READ | HV_MEMORY_WRITE | HV_MEMORY_EXEC. Inde i vCPU'en konfigurerer EL1-stubben bluepill (bluepill_arm64.go) TCR_EL1.TG0 = 0b00 (4 KB-granule) i TTBR0_EL1 og udfylder 4 KB Stage-1-sidetabelindgange med præcise R-X- eller RW- (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 #0 fanges af EL1 VBAR_EL1, skriver registre til den delte 4 KB CommPage og udfører HVC #0 for at vende tilbage fra hv_vcpu_run til 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 & winSysmsg IPC: På amd64 erstatter patcheren (patcher_amd64_xplat.go) 2-byte syscall-instruktioner (0f 05) med 5-byte jmp rel32-hop (eller CC 90-breakpoint-fælder, når et 5-byte-vindue ikke er tilgængeligt) ind i en fast-path-ø. På arm64 patches svc #0 med en B-forgrening til en øplads. Øen skriver registre til den delte winSysmsg-side og synkroniseres med Sentry via WaitOnAddress og WakeByAddressSingle, 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:

Hvordan gvx afbilder gæste-Linux-systemkald til delsystemer og værts-OS

SystemkaldsdomæneRepræsentative gæste-Linux-systemkaldInternt gvx-delsystem (pkg/sentry)Interaktion med værts-OS-grænsen (efter hostlockdown)
A. Proces, namespace, montering & synkroniseringclone, execve, unshare, setns, mount, umount2, pivot_root, setuid, capset, futex, epoll_wait, pipe2kernel.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_createmm.MemoryManager (4 KB VMA-træ) & pgalloc.MemoryFileTovejsopdateringer 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_watchvfs.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 & Netfiltersocket, 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:

gvx VFS2-lagermotorer og netstack med to spor

LagermotorMonteringsrolle i gvxBagvedliggende værtsressourceVærtens openat- / NtCreateFile-kald under kørselWindows Defender (WdFilter.sys) scanninger pr. fil
fsimpl/erofsSkrivebeskyttede nedre OCI-billedlagEnkelt <sha256>.erofs-fil pr. lag00
fsimpl/ext4liteNavngivne volumener (-v name:/path)Enkelt <name>.ext4-fil pr. volumen00
fsimpl/hostfs v2Arbejdsområde på værten (-workspace, -v /host:/guest)Fastgjort *os.Root-værtsmappeO(depth) pr. ukached åbning1 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 (fallocate på Linux, fcntl(F_PREALLOCATE) på macOS, FileAllocationInfo på 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: Implementerer trusted.overlay.*- og security.capability-udvidede attributter, whiteouts for tegnenheder (0:0), d_type-rapportering af mappeindgange og RENAME_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 rettigheden com.apple.security.hypervisor (xattr -d com.apple.quarantine rungv fjerner browserens karantæne). Hvis du bygger lokalt med go install eller go build på macOS, signerer du binærfilen med codesign --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

  1. gvx: Kørsel af Linux-containere, Docker og Kubernetes i én proces uden privilegier (dette indlæg)
  2. Kørsel af Docker, docker build og docker compose uden root eller user namespaces: Sådan bootstrapper du docker:dind i låste sandkasser, hvor sudo, unshare(CLONE_NEWUSER), mount(2) og /dev/fuse er blokeret.
  3. Kørsel af Docker-in-Docker inde i gVisor og indlejrede sandkasser med gvx: Hvorfor apt-get install docker.io og indlejret runsc fejler inde i gVisor på tværs af fem kernebarrierer, og hvordan gvx kører indlejret docker:dind ved hjælp af en futex-løkke i delt hukommelse.
  4. Hurtige isolerede Docker-miljøer til AI-agent RL-rollouts med gvx: Skalering af SWE-bench og rollouts med forstærkningslæring for agenter med hundredvis af parallelle docker:dind-instanser uden privilegier og 100 millisekunders copy-on-write-nulstillinger af .ext4-volumener.