gvx: Spouštění Linux Containers, Docker a Kubernetes v jediném neprivilegovaném procesu

Úvod: Bezpečnostně zpevněné kontejnery bez oprávnění root a hostitelských démonů
Spouštění Linux kontejnerů, docker build nebo Kubernetes uvnitř vývojářského počítače, CI runneru či cloudového uzlu s více nájemci obvykle vynucuje nepříjemný bezpečnostní kompromis:
- Na Linuxu standardní OCI runtimy (
runc,crun,runsc) vyžadují buď oprávněníroot, vystavení hostitelského/var/run/docker.sock,--privileged(CAP_SYS_ADMIN), nebo jmenné prostory uživatelů (unshare(CLONE_NEWUSER)), které rozšiřují útočnou plochu hostitelského jádra. - Na Windows a macOS kontejnerové enginy (
Docker Desktop,Podman Machine,Lima,OrbStack) spouštějí plné jádro virtuálního stroje Linuxu uvnitř WSL2, Hyper-V neboVirtualization.frameworka uvnitř tohoto VM provozují trvale běžící démon na pozadí.
gvx (CLI rungv a Go SDK z.gfire.dev/gvx/pkg/gvx) poskytuje bezpečnou alternativu bez nutnosti zvýšených oprávnění. Je vytvořen jako fork uživatelského jádra Linuxu gVisor od společnosti Google (Sentry) a uživatelského síťového zásobníku (netstack), který lze sledovat vůči upstreamu. gvx spouští nezměněné binární soubory linux/amd64 a linux/arm64 ve formátu ELF64 a kontejnery OCI přímo uvnitř jediného neprivilegovaného procesu, který se sám uzamyká, na systémech Windows, macOS a Linux.
Bezpečnostní záruka (obrana do hloubky ve třech kruzích):
gvxnikdy neoslabuje bezpečnostní hranice hostitelského operačního systému, nikdy neinstaluje ovladače jádra a nikdy nevyžadujeroot,sudo,--privilegedna hostiteli aniCLONE_NEWUSER. Dříve než provede jedinou instrukci hosta,gvxuzamkne svůj vlastní hostitelský proces pomocí omezených tokenů Windows (Restricted Tokens) a Job Objects, profilu macOS Seatbelt (deny default), nebo pomocíPR_SET_NO_NEW_PRIVSa BPFseccompna Linuxu. Všechna systémová volání hosta, virtuální oprávněníUID 0, připojení (VFS2) a síťové tabulky (netstack) jsou přísně omezena uvnitř paměťově bezpečného jádra GoSentry.
| Runtime | Hostitelský OS | Studený start | Paměťová náročnost | Vyžadováno správce nebo sudo | Démon VM na pozadí | Přímé spuštění OCI obrazu |
|---|---|---|---|---|---|---|
gvx (whpx / hvf) | Windows (amd64, arm64) & macOS (arm64) | ~20 ms | ~30 MiB | Ne | Ne | Ano |
gvx (systrap) | Windows (amd64, arm64) & Linux (amd64, arm64) | ~25 až 65 ms | ~31 MiB | Ne | Ne | Ano |
| WSL2 / Docker Desktop | Windows & macOS | 800 až 2500 ms | > 512 MiB | Ano (počáteční nastavení) | Ano | Vyžaduje démon |
| QEMU (Full System) | Windows & macOS | 1500 až 4000 ms | > 256 MiB | Ne | Ne | Vyžaduje diskový obraz |
Na Windows ani macOS se nespouští žádné jádro virtuálního stroje Linuxu. Na hostiteli neběží žádný démon na pozadí. Na hostitelském OS nejsou vyžadována oprávnění root, sudo, jmenné prostory uživatelů (CLONE_NEWUSER), mount(2), ovladače TUN/TAP ani CAP_NET_ADMIN. Uvnitř tohoto neprivilegovaného procesu gvx spouští Docker-in-Docker (docker:dind), docker build, docker compose a vysoce dostupný vícouzlový Kubernetes (k3s s vestavěným etcd a CNI Calico).
Životní cyklus kontejneru a uzamčení hostitele ve třech kruzích
Každá sandbox gvx běží uvnitř jediného neprivilegovaného hostitelského procesu a vynucuje třífázový životní cyklus: předotevření všech požadovaných deskriptorů souborů a soketů hostitele, uzamčení hostitelského procesu operačního systému přes pkg/hostlockdown a vstup do vnitřní vykonávací smyčky Sentry.

Před předáním řízení první instrukci hosta gvx předotevře své uložené deskriptory souborů vrstev obrazu .erofs, deskriptory souborů svazků .ext4, handle /workspace typu *os.Root a síťové sokety hostitele. Poté zavolá hostlockdown.Apply():
- Windows NT (
lockdown_windows.go): VoláAdjustTokenPrivilegessSE_PRIVILEGE_REMOVED, aby odebrala oprávnění přímo na místě, připojí omezený token pro impersonaciDISABLE_MAX_PRIVILEGEna úrovni integrity Low (S-1-16-4096), přiřadí proces k anonymnímu Job Object sJOB_OBJECT_LIMIT_ACTIVE_PROCESS = 1(čímž blokuje vytváření jakýchkoli podřízených procesů) a přesSetProcessMitigationPolicypovolíDisallowWin32kSystemCalls = 1. - macOS XNU (
lockdown_darwin.go): NastavíRLIMIT_NPROC = 0a aplikuje profil Seatbelt (sandbox_init_with_parameters) začínající(version 1)(deny default), který blokujeprocess-fork,process-exec*a všechna nová otevírání souborů hostitele. - Linux (
lockdown_linux.go+pkg/gvx/syscallfilter_linux.go): NastavíPR_SET_NO_NEW_PRIVSa napříč všemi vlákny nainstaluje allowlist BPFseccomppro Sentry (SECCOMP_FILTER_FLAG_TSYNC) s explicitními pravidlyEPERMproexecve,execveat,clone,openatasocket.
Meziplatformní zachytávání systémových volání a stránkování po 4 KB (pkg/sentry/platform)
Binární soubory Linux ve formátu ELF64 předpokládají granularitu paměťových stránek 4 KB (0x1000) pro mapování segmentů mmap(MAP_FIXED). Funkce Windows VirtualAlloc a MapViewOfFile3 však vynucují zarovnání na 64 KB (0x10000) a macOS na Apple Silicon (darwin/arm64) používá hostitelské stránky o velikosti 16 KB (0x4000). Navíc každý operační systém vyžaduje jiný mechanismus pro zachycení instrukcí Linuxu SYSCALL (amd64) a SVC #0 (arm64) bez spuštění virtuálního stroje s Linuxem.
gvx řeší oba problémy napříč čtyřmi exekučními backendy, které implementují pkg/sentry/platform.Context:

1. Hardwarový engine pro Windows: platform/whpx (WinHvPlatform.dll)
Na windows/amd64 a windows/arm64 vytváří platform/whpx lehkou oblast hypervizoru (partition) přes WHvCreatePartition, a to bez spuštění jádra hostovaného operačního systému:
- Dekuplér stránkování z 64 KB na 4 KB:
whpxregistruje každou 64MiB oblastpgalloc.MemoryFileu oblasti hypervizoru jednou přesWHvMapGpaRange. Následná voláníMapFileaUnmapmění pouze tabulky stránek hosta uvnitř oblasti a vynucují přesná oprávněníR/W/Xna úrovni 4 KB navzdory alokační granularitě Windows 64 KB. - Výměna registrů bez API přes
CommPage:- Na
amd64vstupujeSYSCALLhosta v režimu Ring 3 do stubuMSR_LSTARv režimu Ring 0 uvnitř oblasti hypervizoru (stubgen_x86.go). Ten uloží GPR rychlé cesty systémových volání (RAX, RDI, RSI, RDX, R10, R8, R9, RIP, RSP) do sdílenéCommPageo velikosti 4 KB a provedeHLT, aby ukončilWHvRunVirtualProcessor. - Na
arm64vstupujeSVC #0hosta v EL0 do vektorů EL1 (stubgen_a64.go), uložíX0..X8, SP_EL0, ELR_EL1, PSTATEdoCommPagea načte z neprovázané GPA nulu, aby vyvolal paměťový návrat do Sentry.
- Na
2. Hardwarový engine pro macOS: platform/hvf (Hypervisor.framework)
Na Apple Silicon (darwin/arm64) spouští platform/hvf vlákna kontejnerů uvnitř vCPU Hypervisor.framework (hv_vm_create, hv_vcpu_run):
- Dvoustupňový překlad MMU z 4 KB přes 16 KB: Stage-2 (
hv_vm_map) mapuje části paměťového souboru po 1 GiB do prostoru fyzických adres IPA (Intermediate Physical Address) jednou, s granularitou hostitelských stránek 16 KB, s oprávněnímiHV_MEMORY_READ | HV_MEMORY_WRITE | HV_MEMORY_EXEC. Uvnitř vCPU stub EL1bluepill(bluepill_arm64.go) nastavíTCR_EL1.TG0 = 0b00(granule 4 KB) vTTBR0_EL1a naplní záznamy tabulky stránek Stage-1 po 4 KB přesnými oprávněnímiR-XneboRW-(UXN=1). Protože MMU ARM64 vynucuje průnik oprávnění Stage-1 a Stage-2, hardware zachytává chyby oprávnění přesně na hranicích 4 KB. - Zachycení systémového volání: Instrukce
SVC #0hosta v EL0 přechází doVBAR_EL1v EL1, uloží registry do sdílenéCommPageo velikosti 4 KB a provedeHVC #0, aby se vrátila zhv_vcpu_rundo vlákna Go Sentry.
3. Uživatelský záložní režim pro Windows: platform/systrap (winsystrap)
Když hardwarová virtualizace není dostupná (například uvnitř vnořených cloudových virtuálních strojů), winsystrap spouští kód hosta v uživatelském režimu na windows/amd64 a windows/arm64:
- Hardwarová izolace
CR3/TTBR0_EL1: Vlákna hosta běží uvnitř izolovaných podprocesů stubů spuštěných z vygenerovaného spustitelného souboru o velikosti 1 KB (stubimage_xplat.go), omezených tokenem Low-Integrity a Job Object. - Binární patchování na místě a IPC
winSysmsg: Naamd64patcher (patcher_amd64_xplat.go) nahrazuje dvoubajtové instrukcesyscall(0f 05) pětibajtovými skokyjmp rel32(nebo pastiCC 90typu breakpoint, pokud není dostupné pětibajtové okno) do ostrova rychlé cesty. Naarm64jesvc #0opraven větvenímBdo slotu ostrova. Ostrov ukládá registry do sdílené stránkywinSysmsga synchronizuje se se Sentry přesWaitOnAddressaWakeByAddressSingle, zatímco neopravené pasti a chyby stránek se zastavují na Windows Debug Object (WaitForDebugEventEx).
4. Linuxový engine v uživatelském režimu: platform/systrap
Na linux/amd64 a linux/arm64 spouští systrap vlákna hosta (sysmsg), která zachytávají systémová volání hosta přes SECCOMP_RET_TRAP (SIGSYS) a binárně patchují místa syscall do sdíleného trampolinu v paměti (sysmsg) synchronizovaného přes SYS_FUTEX. Stub syscallThread (syscall_thread.go) provádí operace nad adresním prostorem (mmap, munmap, clone) přes SECCOMP_RET_USER_NOTIF (_RUN_SECCOMP_LOOP). Když gvx běží uvnitř vnější sandbox gVisor (runsc), kde jediný slot SECCOMP_FILTER_FLAG_NEW_LISTENER vrací EBUSY, gvx automaticky přepne syscallThread na smyčku futexu nad sdílenou pamětí (_RUN_SYSCALL_LOOP), jak je podrobně popsáno v části 3 této série.
Jak gvx mapuje systémová volání Linuxu hosta na virtuální subsystémy a handle hostitele
Jakmile engine platformy zachytí instrukci SYSCALL (amd64) nebo SVC #0 (arm64) hosta a zapíše registry hosta do sdílené stránky o velikosti 4 KB (CommPage, sysmsg nebo winSysmsg), řízení se vrací do goroutine úlohy Go Sentry (pkg/sentry/kernel/task_syscall.go). Namísto předávání systémových volání Linuxu podkladovému jádru hostitelského operačního systému Task.doSyscall() uloží původní stav registrů (SyscallSaveOrig()), vyhodnotí všechny filtry BPF seccomp a zastavení ptrace uvnitř hosta a odešle číslo systémového volání přes linux.AMD64 nebo linux.ARM64 (pkg/sentry/syscalls/linux/linux64.go, pokrývající přibližně 370 systémových volání Linuxu) do čtyř interních domén:

| Doména systémových volání | Reprezentativní systémová volání Linuxu hosta | Interní subsystém gvx (pkg/sentry) | Interakce s hranicí hostitelského OS (po hostlockdown) |
|---|---|---|---|
| A. Proces, jmenný prostor, připojení a synchronizace | clone, execve, unshare, setns, mount, umount2, pivot_root, setuid, capset, futex, epoll_wait, pipe2 | kernel.Task, virtuální pověření, tabulka futexů a VFS2 v paměti (procfs, sysfs, cgroupfs v2, tmpfs, overlayfs) | Nula systémových volání hostitele (100% stav Go v paměti; zcela neviditelné pro hostitelský OS) |
B. Virtuální paměť (MM) | mmap, munmap, mremap, mprotect, brk, madvise, memfd_create | mm.MemoryManager (strom VMA po 4 KB) a pgalloc.MemoryFile | Obousměrné aktualizace tabulek stránek po 4 KB přes WHvMapGpaRange, hv_vm_map + TTBR0_EL1, nebo stub mmap/mprotect |
C. Soubory a úložiště (VFS2) | openat, pread64, pwrite64, getdents64, statx, renameat2, setxattr, mknod, inotify_add_watch | Směrovač tabulky připojení vfs.VirtualFilesystem (fsimpl/erofs, fsimpl/ext4lite, fsimpl/hostfs v2) | Obousměrné pread/pwrite na 1 připnutý deskriptor .erofs nebo 1 deskriptor .ext4, nebo omezená relativní procházení přes *os.Root |
| D. Síť a Netfilter | socket, bind, listen, connect, sendmsg, recvmsg, netlink, setsockopt(IPT_SO_SET_REPLACE) | Uživatelský pkg/tcpip (lo, eth0, docker0, veth, vxlan, wireguard, ipset, iptables-legacy) | Provoz uvnitř sandboxu nepoužívá žádná systémová volání hostitele; externí provoz se mapuje na net.Dial/net.Listen (-net=nat) nebo na 1 UDP soket (-net=wg) |
Když handler vrátí (rval, ctrl, err), Task.doSyscallInvoke() převede každou chybu na záporný Linux errno (-ExtractErrno(err, sysno)), zapíše návratovou hodnotu zpět do sdílené stránky registrů (RAX na amd64, X0 na arm64) a obnoví vykonávání hosta.
Úložiště (VFS2) a dvoustopá uživatelská síť (netstack)
Všechny souborové a síťové operace hosta jsou obsluhovány uvnitř Go Sentry přes tři souborové systémy VFS2 a dvě stopy pkg/netbackend:

| Úložný engine | Role připojení v gvx | Podkladový prostředek hostitele | Volání hostitelského openat / NtCreateFile za běhu | Skenování Windows Defenderem (WdFilter.sys) na soubor |
|---|---|---|---|---|
fsimpl/erofs | Obrazy OCI pouze pro čtení, spodní vrstvy | Jeden soubor <sha256>.erofs na vrstvu | 0 | 0 |
fsimpl/ext4lite | Pojmenované svazky (-v name:/path) | Jeden soubor <name>.ext4 na svazek | 0 | 0 |
fsimpl/hostfs v2 | Pracovní prostor hostitele (-workspace, -v /host:/guest) | Připnutý adresář hostitele *os.Root | O(depth) na neuložené otevření | 1 na zavření souboru hostitele |
Proč ext4lite záleží na Dockeru, databázích a správcích balíčků
Rozbalování nebo zápis desítek tisíc souborů kontejneru přímo na Windows NTFS nebo macOS APFS spouští skenování antivirových minifiltrů (WdFilter.sys), kolize rozlišování velkých a malých písmen a chybějící rozšířené atributy POSIX.
pkg/sentry/fsimpl/ext4lite implementuje čistě v Go souborový systém ext4 uvnitř jednoho předem otevřeného hostitelského souboru (%LOCALAPPDATA%\gvx\cache\volumes\<name>.ext4 na Windows, ~/Library/Caches/gvx/volumes/<name>.ext4 na macOS nebo ~/.cache/gvx/volumes/<name>.ext4 na Linuxu) s těmito vlastnostmi:
- Bloky 4 KiB a dynamické skupiny bloků 16 MiB (
meta_bg): Roste dynamicky bez zdržení při alokaci řídkých souborů (fallocatena Linuxu,fcntl(F_PREALLOCATE)na macOS,FileAllocationInfona Windows) a při čistém odpojení zhušťuje nepoužívané koncové skupiny. - Žurnál JBD2 v režimu ordered (inode 8): Atomické potvrzování transakcí s kontrolními součty CRC-32 a bloky revoke, plus obnova pomocí
fsckLite. - Podpora horní vrstvy
overlayfs: Implementuje rozšířené atributytrusted.overlay.*asecurity.capability, whiteouty znakových zařízení (0:0), hlášení záznamů adresářůd_typeaRENAME_EXCHANGE.
Praktické úlohy: Docker Engine, Kubernetes (k3s + Calico) a Go SDK
Instalace rungv: předkompilovaná vydání s ověřeným SLSA nebo go install
Předkompilovaný archiv rungv pro linux/amd64, linux/arm64, darwin/arm64, windows/amd64 nebo windows/arm64 si můžete stáhnout z GitHub Releases (každý je sestaven pipeline vydání SLSA s provenienčním balíčkem Sigstore a souborem SHA256SUMS). Nebo můžete nainstalovat přímo ze zdrojového kódu pomocí go install:
1# Možnost A: Stáhněte předkompilované vydání a ověřte původ sestavení SLSA
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# Možnost B: Instalace přímo přes Go (Linux / Windows)
9CGO_ENABLED=0 go install z.gfire.dev/gvx/cmd/rungv@latest
Na macOS (
darwin/arm64) je předkompilovaný archiv vydání již podepsán oprávněnímcom.apple.security.hypervisor(xattr -d com.apple.quarantine rungvodstraní karanténu prohlížeče). Pokud sestavujete lokálně pomocígo installnebogo buildna macOS, podepište binární soubor příkazemcodesign --sign - --entitlements entitlements.plist --force $(which rungv).
1. Spuštění neprivilegovaného Docker Engine (docker:dind)
Podle docs/docker-engine.md můžete spustit nezměněný kontejner docker:dind uvnitř rungv na Windows, macOS nebo Linuxu a ovládat jej pomocí svého hostitelského CLI docker:
1# 1. Vytvořte hostitelský adresář pro přeposlaný soket Unix
2mkdir -p ~/.gvx
3
4# 2. Spusťte docker:dind uvnitř sandboxu gvx se svazkem ext4lite
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
V druhém terminálu nasměrujte své hostitelské CLI docker na soket sandboxu:
1docker context create gvx --docker host=unix://$HOME/.gvx/docker.sock
2docker context use gvx
3
4docker info --format '{{.Driver}} {{.OperatingSystem}}'
5# Výstup: overlayfs Alpine Linux ... (v kontejneru)
6
7docker build -t myapp ./app
8docker compose up -d
2. Spuštění Kubernetes (k3s + Calico) v neprivilegovaných sandboxech
Protože gvx implementuje /dev/kmsg, cgroup v2 (OOM killer přes memory.max a omezování šířky pásma přes cpu.max), /proc/diskstats, síťová zařízení netlink wireguard a vxlan, ipset a iptables-legacy (addrtype, conntrack, statistic, recent), nezměněný obraz rancher/k3s dosáhne stavu Ready uvnitř jediného procesu rungv:
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
Můžete také připojit více uzlů gvx přes WireGuard (flannel-backend=wireguard-native nebo síť wg0) s vestavěnou vysokou dostupností etcd, nebo spustit Calico (calico-vxlan.yaml s FELIX_IPTABLESBACKEND=Legacy), aby se Kubernetes NetworkPolicy vynucovala zcela v uživatelském prostoru.
3. Vložení kontejnerů Linuxu do Go přes z.gfire.dev/gvx/pkg/gvx
Aplikace v Go mohou vkládat kontejnery Linuxu přímo, bez externího démona kontejnerů:
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}
Další čtení v této sérii
- gvx: Spouštění kontejnerů Linuxu, Dockeru a Kubernetes v jediném neprivilegovaném procesu (tento příspěvek)
- Spouštění Dockeru, docker build a docker compose bez oprávnění root nebo jmenných prostorů uživatelů: Jak zavést
docker:dindv uzamčených sandboxech, kde jsou blokoványsudo,unshare(CLONE_NEWUSER),mount(2)a/dev/fuse. - Spouštění Docker-in-Docker uvnitř gVisoru a vnořených sandboxů s gvx: Proč
apt-get install docker.ioa vnořenýrunscselhávají uvnitřgVisorpřes pět bariér jádra a jakgvxspouští vnořenýdocker:dindpomocí smyčky futexu nad sdílenou pamětí. - Rychlá izolovaná Docker prostředí pro rollouty RL agentů s gvx: Škálování
SWE-bencha posilovaného učení agentů pomocí stovek paralelních neprivilegovaných instancídocker:dinda resetování svazků.ext4s kopírováním při zápisu za 100 milisekund.