GoSuda

gvx: Kjøring av Linux Containers, Docker og Kubernetes i én uprivilegert prosess

By Lemon Mint views ...

Introduksjon: Sikkerhetsherdede containere uten root eller vertsdaemoner

Å kjøre Linux-containere, docker build eller Kubernetes på en utviklingsmaskin, en CI-runner eller en flerleietakersky-node krever vanligvis en ubehagelig sikkerhetsavveining:

  • På Linux krever standard OCI-runtimes (runc, crun, runsc) enten root-rettigheter, eksponering av vertens /var/run/docker.sock, --privileged (CAP_SYS_ADMIN) eller brukernavnerom (unshare(CLONE_NEWUSER)), som utvider angrepsflaten i vertens kjerne.
  • På Windows og macOS starter containermotorer (Docker Desktop, Podman Machine, Lima, OrbStack) en fullstendig Linux-kjerne i en virtuell maskin innenfor WSL2, Hyper-V eller Virtualization.framework, og kjører en vedvarende bakgrunnsdaemon inne i den virtuelle maskinen.

gvx (rungv-CLI-en og z.gfire.dev/gvx/pkg/gvx Go-SDK-en) tilbyr et sikkert alternativ uten privilegier. Det er bygget som en oppstrømssporbar gaffel av Googles gVisor brukerromsbaserte Linux-kjerne (Sentry) og brukerromsbasert nettverksstakk (netstack), og gvx kjører umodifiserte linux/amd64- og linux/arm64-ELF64-binærfiler og OCI-containere direkte inne i en enkelt, uprivilegert og selvlåst vertsprosess på Windows, macOS og Linux.

Sikkerhetsgaranti (tre-ringers forsvar i dybden): gvx svekker aldri sikkerhetsgrensene i vertsoperativsystemet, installerer aldri kjernedrivere og krever aldri root, sudo, --privileged på verten eller CLONE_NEWUSER. Før en eneste gjesteinstruksjon kjøres, låser gvx sin egen vertsprosess ved hjelp av Windows Restricted Tokens og Job Objects, macOS Seatbelt (deny default), eller Linux PR_SET_NO_NEW_PRIVS + BPF seccomp. Alle systemkall fra gjesten, virtuelle UID 0-rettigheter, monteringer (VFS2) og nettverkstabeller (netstack) er strengt begrenset innenfor den minnesikre Go-kjernen Sentry.

RuntimeVertsoperativsystemKaldstartMinneavtrykkKrever administrator eller sudoVM-daemon i bakgrunnenDirekte kjøring av OCI-image
gvx (whpx / hvf)Windows (amd64, arm64) og macOS (arm64)~20 ms~30 MiBNeiNeiJa
gvx (systrap)Windows (amd64, arm64) og Linux (amd64, arm64)~25 til 65 ms~31 MiBNeiNeiJa
WSL2 / Docker DesktopWindows og macOS800 til 2500 ms> 512 MiBJa (førstegangsoppsett)JaKrever daemon
QEMU (fullt system)Windows og macOS1500 til 4000 ms> 256 MiBNeiNeiKrever diskbilde

Ingen Linux-kjerne i en virtuell maskin starter på Windows eller macOS. Ingen bakgrunnsdaemon kjører på verten. Det kreves ingen root, sudo, brukernavnerom (CLONE_NEWUSER), mount(2), TUN/TAP-drivere eller CAP_NET_ADMIN-rettigheter på vertsoperativsystemet. Inne i denne uprivilegerte prosessen kjører gvx Docker-in-Docker (docker:dind), docker build, docker compose og høyt tilgjengelig Kubernetes med flere noder (k3s med innebygd etcd og Calico CNI).


Containerlivssyklus og tre-ringers vertslåsing

Hver gvx-sandkasse kjører innenfor én uprivilegert vertsprosess og følger en livssyklus i tre faser: forhåndsåpning av alle nødvendige vertens filbeskrivere og sockets, låsing av vertens operativsystemprosess via pkg/hostlockdown, og inngang til den interne Sentry-utførelsessløyfen.

gvx containerlivssyklus og selvlåsing av verten

Før kontrollen overføres til den første gjesteinstruksjonen, forhåndsåpner gvx sine cachede .erofs-bildelagsdeskriptorer, .ext4-volumfilbeskrivere, /workspace-håndtaket *os.Root og vertens nettverkssockets, og kaller deretter hostlockdown.Apply():

  1. Windows NT (lockdown_windows.go): Kaller AdjustTokenPrivileges med SE_PRIVILEGE_REMOVED for å fjerne privilegier på stedet, binder et DISABLE_MAX_PRIVILEGE-restriktivt impersonation-token med lav integritet (S-1-16-4096), tilordner prosessen til et anonymt Job Object med JOB_OBJECT_LIMIT_ACTIVE_PROCESS = 1 (blokkerer opprettelse av underordnede prosesser), og aktiverer DisallowWin32kSystemCalls = 1 via SetProcessMitigationPolicy.
  2. macOS XNU (lockdown_darwin.go): Setter RLIMIT_NPROC = 0 og bruker en Seatbelt-profil (sandbox_init_with_parameters) som starter fra (version 1)(deny default) og blokkerer process-fork, process-exec* og alle nye filåpninger på verten.
  3. Linux (lockdown_linux.go + pkg/gvx/syscallfilter_linux.go): Setter PR_SET_NO_NEW_PRIVS og installerer Sentrys BPF seccomp-tillatelsesliste på tvers av alle tråder (SECCOMP_FILTER_FLAG_TSYNC), med eksplisitte EPERM-regler for execve, execveat, clone, openat og socket.

Krysplattform-avskjæring av systemkall og 4 KB-paging (pkg/sentry/platform)

Linux ELF64-binærfiler forutsetter en minnesidekornethet på 4 KB (0x1000) for mmap(MAP_FIXED)-segmentmappinger. Windows VirtualAlloc og MapViewOfFile3 håndhever imidlertid 64 KB-justering (0x10000), og Apple Silicon macOS (darwin/arm64) bruker 16 KB-sider (0x4000) på verten. Dessuten krever hvert operativsystem en ulik mekanisme for å avskjære Linux-instruksjonene SYSCALL (amd64) og SVC #0 (arm64) uten å starte en Linux-virtuell maskin.

gvx løser begge problemene på tvers av fire utførelsesbackends som implementerer pkg/sentry/platform.Context:

gvx-mekanismer for avskjæring av plattformsystemkall og 4KB-paging

1. Windows maskinvaremotor: platform/whpx (WinHvPlatform.dll)

På windows/amd64 og windows/arm64 oppretter platform/whpx en lettvekts hypervisor-partisjon via WHvCreatePartition uten å starte en gjestekjerne for operativsystemet:

  • Frikobler for 64 KB-til-4 KB-paging: whpx registrerer hver 64 MiB-arena av pgalloc.MemoryFile hos partisjonen én gang via WHvMapGpaRange. Påfølgende kall til MapFile og Unmap endrer kun gjestens sidetabeller i partisjonen, og håndhever nøyaktige 4 KB-tillatelser R/W/X til tross for Windows' 64 KB-allokeringskornethet.
  • Registerutveksling uten API via CommPage:
    • På amd64 går gjestens Ring-3 SYSCALL inn i en Ring-0 MSR_LSTAR-stub i partisjonen (stubgen_x86.go), som skriver de raske systemkall-GPR-ene (RAX, RDI, RSI, RDX, R10, R8, R9, RIP, RSP) til en delt 4 KB CommPage og kjører HLT for å avslutte WHvRunVirtualProcessor.
    • På arm64 går gjestens EL0 SVC #0 inn i EL1-vektorer (stubgen_a64.go), skriver X0..X8, SP_EL0, ELR_EL1, PSTATE til CommPage, og laster fra ubakket GPA-null for å utløse et minneavbrudd tilbake til Sentry.

2. macOS maskinvaremotor: platform/hvf (Hypervisor.framework)

På Apple Silicon (darwin/arm64) kjører platform/hvf containertråder inne i Hypervisor.framework-vCPU-er (hv_vm_create, hv_vcpu_run):

  • To-trinns 4 KB over 16 KB MMU-oversettelse: Stage-2 (hv_vm_map) mapper 1 GiB store minnefilbiter inn i Intermediate Physical Address (IPA)-rommet én gang, med 16 KB vertssidekornethet og HV_MEMORY_READ | HV_MEMORY_WRITE | HV_MEMORY_EXEC. Inne i vCPU-en konfigurerer EL1-stubben bluepill (bluepill_arm64.go) TCR_EL1.TG0 = 0b00 (4 KB-granule) i TTBR0_EL1 og fyller Stage-1-sidetabelloppføringer på 4 KB med nøyaktige R-X- eller RW--tillatelser (UXN=1). Fordi ARM64-MMU-en håndhever snittet av Stage-1- og Stage-2-tillatelser, fanger maskinvaren tillatelsesfeil på nøyaktige 4 KB-grenser.
  • Systemkallfelle: Gjestens EL0 SVC #0 fanges opp til EL1 VBAR_EL1, skriver registrene til den delte 4 KB CommPage og kjører HVC #0 for å returnere fra hv_vcpu_run til Go-tråden for Sentry.

3. Windows brukermodus-reserve: platform/systrap (winsystrap)

Når maskinvarevirtualisering ikke er tilgjengelig (for eksempel i nestede skytilfeller), kjører winsystrap gjestekode i brukermodus på windows/amd64 og windows/arm64:

  • Maskinvarebasert isolasjon via CR3 / TTBR0_EL1: Gjestetråder kjører i isolerte stub-underprosesser startet fra en generert 1 KB kjørbar fil (stubimage_xplat.go), begrenset av et token med lav integritet og et Job Object.
  • Patching av binærfil på stedet og winSysmsg IPC: På amd64 erstatter patcheren (patcher_amd64_xplat.go) 2-byte syscall-instruksjoner (0f 05) med 5-byte jmp rel32-hopp (eller CC 90-breakpoint-feller når et 5-byte-vindu ikke er tilgjengelig) inn i en øy for hurtig bane. På arm64 patches svc #0 med en B-grenhopp til en øyplass. Øya skriver registrene til den delte winSysmsg-siden og synkroniseres med Sentry via WaitOnAddress og WakeByAddressSingle, mens upatchede feller og sidefeil stopper på et Windows Debug Object (WaitForDebugEventEx).

4. Linux brukermodusmotor: platform/systrap

På linux/amd64 og linux/arm64 kjører systrap gjestetråder (sysmsg) som fanger gjestens systemkall via SECCOMP_RET_TRAP (SIGSYS) og binærpatcher syscall-steder til en trampoline i delt minne (sysmsg) synkronisert via SYS_FUTEX. Samtidig utfører en stub-syscallThread (syscall_thread.go) operasjoner på adresserom (mmap, munmap, clone) via SECCOMP_RET_USER_NOTIF (_RUN_SECCOMP_LOOP). Når gvx kjører inne i en ytre gVisor (runsc)-sandkasse der den ene SECCOMP_FILTER_FLAG_NEW_LISTENER-plassen returnerer EBUSY, bytter gvx automatisk syscallThread til en futex-løkke i delt minne (_RUN_SYSCALL_LOOP), som beskrevet i del 3 av denne serien.


Hvordan gvx kobler gjestens Linux-systemkall til virtuelle delsystemer og vertshåndtak

Når en plattformmotor har fanget en gjeste-SYSCALL (amd64) eller SVC #0-instruksjon (arm64) og skrevet gjestens registre til den delte 4 KB-siden (CommPage, sysmsg eller winSysmsg), returnerer kontrollen til Go-gorutinen for Sentry-oppgaven (pkg/sentry/kernel/task_syscall.go). I stedet for å videresende Linux-systemkall til den underliggende kjernen i vertens operativsystem, lagrer Task.doSyscall() den opprinnelige registertilstanden (SyscallSaveOrig()), evaluerer eventuelle BPF seccomp-filtre og ptrace-stopp i gjesten, og sender systemkallnummeret gjennom linux.AMD64 eller linux.ARM64 (pkg/sentry/syscalls/linux/linux64.go, som dekker ca. 370 Linux-systemkall) til fire interne domener:

Hvordan gvx kobler gjestens Linux-systemkall til delsystemer og vertsoperativsystemet

SystemkalldomeneRepresentative Linux-systemkall i gjestenIntern gvx-delkomponent (pkg/sentry)Interaksjon med vertens operativsystemgrense (etter hostlockdown)
A. Prosess, navnerom, montering og synkroniseringclone, execve, unshare, setns, mount, umount2, pivot_root, setuid, capset, futex, epoll_wait, pipe2kernel.Task, virtuelle legitimasjoner, futex-tabell og VFS2 i minnet (procfs, sysfs, cgroupfs v2, tmpfs, overlayfs)Null systemkall på verten (100 % Go-tilstand i minnet; fullstendig usynlig for vertens operativsystem)
B. Virtuelt minne (MM)mmap, munmap, mremap, mprotect, brk, madvise, memfd_createmm.MemoryManager (VMA-tre med 4 KB) og pgalloc.MemoryFileToveis oppdateringer av 4 KB-sidetabeller via WHvMapGpaRange, hv_vm_map + TTBR0_EL1, eller stub-mmap/mprotect
C. Filer og lagring (VFS2)openat, pread64, pwrite64, getdents64, statx, renameat2, setxattr, mknod, inotify_add_watchvfs.VirtualFilesystem-monteringstabellens ruter (fsimpl/erofs, fsimpl/ext4lite, fsimpl/hostfs v2)Toveis pread/pwrite på 1 festet .erofs-FD eller 1 .ext4-FD, eller avgrensede relative gjennomgåinger av *os.Root
D. Nettverk og Netfiltersocket, bind, listen, connect, sendmsg, recvmsg, netlink, setsockopt(IPT_SO_SET_REPLACE)Brukerromsbasert pkg/tcpip (lo, eth0, docker0, veth, vxlan, wireguard, ipset, iptables-legacy)Trafikk internt i sandkassen bruker null systemkall på verten; ekstern trafikk mappes til net.Dial/net.Listen (-net=nat) eller 1 UDP-socket (-net=wg)

Når behandleren returnerer (rval, ctrl, err), konverterer Task.doSyscallInvoke() eventuelle feil til en negativ Linux-errno (-ExtractErrno(err, sysno)), skriver returverdien tilbake til den delte registersiden (RAX på amd64, X0 på arm64) og gjenopptar kjøringen i gjesten.


Lagring (VFS2) og tospors brukerromsnettverk (netstack)

All filhåndtering og nettverkstrafikk i gjesten betjenes inne i Go-Sentry gjennom tre VFS2-filsystemer og to spor i pkg/netbackend:

gvx VFS2-lagringsmotorer og tospors netstack

LagringsmotorMonteringsrolle i gvxUnderliggende vertsressursKall til vertens openat / NtCreateFile ved kjøringWindows Defender (WdFilter.sys) skanninger per fil
fsimpl/erofsSkrivebeskyttede nedre lag i OCI-imageÉn <sha256>.erofs-fil per lag00
fsimpl/ext4liteNavngitte volumer (-v name:/path)Én <name>.ext4-fil per volum00
fsimpl/hostfs v2Vertens arbeidsområde (-workspace, -v /host:/guest)Festet *os.Root-vertskatalogO(dybde) per ukachet åpning1 per lukking av vertsfil

Hvorfor ext4lite er viktig for Docker, databaser og pakkehåndterere

Å pakke ut eller skrive titusenvis av containerfiler direkte til Windows NTFS eller macOS APFS utløser skanninger fra antivirusfilterdrivere (WdFilter.sys), kollisjoner i forskjell mellom store og små bokstaver, og manglende utvidede POSIX-attributter.

pkg/sentry/fsimpl/ext4lite implementerer et rent Go-ext4-filsystem inne i én forhåndsåpnet vertsfil (%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-blokker og 16 MiB dynamiske blokkgrupper (meta_bg): Vokser dynamisk uten stopp ved allokering av sparsomme filer (fallocate på Linux, fcntl(F_PREALLOCATE) på macOS, FileAllocationInfo på Windows) og komprimerer ubrukte grupper på slutten ved ren avmontering.
  • JBD2-journal i ordered-modus (inode 8): Atomiske transaksjonsforpliktelser med CRC-32-kontrollsummer og revoke-blokker, samt gjenoppretting med fsckLite.
  • Støtte for overlayfs øvre lag: Implementerer utvidede attributter trusted.overlay.* og security.capability, whiteouts for tegnenheter (0:0), rapportering av d_type for katalogoppføringer og RENAME_EXCHANGE.

Praktiske arbeidslaster: Docker-motor, Kubernetes (k3s + Calico) og Go-SDK

Installasjon av rungv: SLSA-attesterte forhåndsbygde utgivelser eller go install

Du kan laste ned et forhåndsbygd rungv-arkiv for linux/amd64, linux/arm64, darwin/arm64, windows/amd64 eller windows/arm64 fra GitHub Releases (hvert bygget av SLSA-utgivelsespipelinen med en Sigstore-proveniensbunt og SHA256SUMS), eller installere direkte fra kildekoden med go install:

1# Alternativ A: Last ned en forhåndsbygd utgivelse og verifiser SLSA-byggproveniens
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: Installer direkte via Go (Linux / Windows)
9CGO_ENABLED=0 go install z.gfire.dev/gvx/cmd/rungv@latest

På macOS (darwin/arm64) er det forhåndsbygde utgivelsesarkivet allerede signert med rettigheten com.apple.security.hypervisor (xattr -d com.apple.quarantine rungv fjerner karantene fra nettleseren). Hvis du bygger lokalt med go install eller go build på macOS, må du signere binærfilen med codesign --sign - --entitlements entitlements.plist --force $(which rungv).

1. Kjøre en uprivilegert Docker-motor (docker:dind)

Etter docs/docker-engine.md kan du kjøre en umodifisert docker:dind-container inne i rungv på Windows, macOS eller Linux og styre den med din lokale docker-CLI:

 1# 1. Opprett vertskatalogen for det videresendte Unix-socket
 2mkdir -p ~/.gvx
 3
 4# 2. Start docker:dind inne i en gvx-sandkasse med et ext4lite-volum
 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 andre terminal peker du din lokale docker-CLI mot sandkassens socket:

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. Kjøre Kubernetes (k3s + Calico) i uprivilegerte sandkasser

Fordi gvx implementerer /dev/kmsg, cgroup v2 (memory.max OOM-drepen og cpu.max-båndbreddebegrensning), /proc/diskstats, netlink-enheter for wireguard og vxlan, ipset og iptables-legacy (addrtype, conntrack, statistic, recent), når et umodifisert rancher/k3s-image status Ready inne i én enkelt rungv-prosess:

 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å koble flere gvx-noder sammen over WireGuard (flannel-backend=wireguard-native eller et wg0-nett) med innebygd etcd og høy tilgjengelighet, eller kjøre Calico (calico-vxlan.yaml med FELIX_IPTABLESBACKEND=Legacy) for å håndheve Kubernetes NetworkPolicy helt i brukerrommet.

3. Innbygging av Linux-containere i Go via z.gfire.dev/gvx/pkg/gvx

Go-applikasjoner kan bygge inn Linux-containere direkte uten 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}

Videre lesning i denne serien

  1. gvx: Kjøre Linux-containere, Docker og Kubernetes i én uprivilegert prosess (dette innlegget)
  2. Kjøre Docker, docker build og docker compose uten root eller brukernavnerom: Hvordan starte docker:dind i låste sandkasser der sudo, unshare(CLONE_NEWUSER), mount(2) og /dev/fuse er blokkert.
  3. Kjøre Docker-in-Docker inne i gVisor og nestede sandkasser med gvx: Hvorfor apt-get install docker.io og nestet runsc feiler inne i gVisor på tvers av fem kjernebarrierer, og hvordan gvx kjører nestet docker:dind ved hjelp av en futex-løkke i delt minne.
  4. Raske, isolerte Docker-miljøer for RL-utrullinger av AI-agenter med gvx: Skalering av SWE-bench og forsterkningslæringsutrullinger for agenter med hundrevis av parallelle uprivilegerte docker:dind-instanser og 100 millisekunders copy-on-write-tilbakestillinger av .ext4-volumer.