GoSuda

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

By Lemon Mint views ...

Ú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 nebo Virtualization.framework a 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): gvx nikdy neoslabuje bezpečnostní hranice hostitelského operačního systému, nikdy neinstaluje ovladače jádra a nikdy nevyžaduje root, sudo, --privileged na hostiteli ani CLONE_NEWUSER. Dříve než provede jedinou instrukci hosta, gvx uzamkne 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_PRIVS a BPF seccomp na 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 Go Sentry.

RuntimeHostitelský OSStudený startPaměťová náročnostVyžadováno správce nebo sudoDémon VM na pozadíPřímé spuštění OCI obrazu
gvx (whpx / hvf)Windows (amd64, arm64) & macOS (arm64)~20 ms~30 MiBNeNeAno
gvx (systrap)Windows (amd64, arm64) & Linux (amd64, arm64)~25 až 65 ms~31 MiBNeNeAno
WSL2 / Docker DesktopWindows & macOS800 až 2500 ms> 512 MiBAno (počáteční nastavení)AnoVyžaduje démon
QEMU (Full System)Windows & macOS1500 až 4000 ms> 256 MiBNeNeVyž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.

gvx Container Lifecycle and Host Self-Lockdown

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

  1. Windows NT (lockdown_windows.go): Volá AdjustTokenPrivileges s SE_PRIVILEGE_REMOVED, aby odebrala oprávnění přímo na místě, připojí omezený token pro impersonaci DISABLE_MAX_PRIVILEGE na úrovni integrity Low (S-1-16-4096), přiřadí proces k anonymnímu Job Object s JOB_OBJECT_LIMIT_ACTIVE_PROCESS = 1 (čímž blokuje vytváření jakýchkoli podřízených procesů) a přes SetProcessMitigationPolicy povolí DisallowWin32kSystemCalls = 1.
  2. macOS XNU (lockdown_darwin.go): Nastaví RLIMIT_NPROC = 0 a aplikuje profil Seatbelt (sandbox_init_with_parameters) začínající (version 1)(deny default), který blokuje process-fork, process-exec* a všechna nová otevírání souborů hostitele.
  3. Linux (lockdown_linux.go + pkg/gvx/syscallfilter_linux.go): Nastaví PR_SET_NO_NEW_PRIVS a napříč všemi vlákny nainstaluje allowlist BPF seccomp pro Sentry (SECCOMP_FILTER_FLAG_TSYNC) s explicitními pravidly EPERM pro execve, execveat, clone, openat a socket.

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:

gvx Platform Syscall Interception and 4KB Paging Mechanics

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: whpx registruje každou 64MiB oblast pgalloc.MemoryFile u oblasti hypervizoru jednou přes WHvMapGpaRange. Následná volání MapFile a Unmap mění pouze tabulky stránek hosta uvnitř oblasti a vynucují přesná oprávnění R/W/X na úrovni 4 KB navzdory alokační granularitě Windows 64 KB.
  • Výměna registrů bez API přes CommPage:
    • Na amd64 vstupuje SYSCALL hosta v režimu Ring 3 do stubu MSR_LSTAR v 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é CommPage o velikosti 4 KB a provede HLT, aby ukončil WHvRunVirtualProcessor.
    • Na arm64 vstupuje SVC #0 hosta v EL0 do vektorů EL1 (stubgen_a64.go), uloží X0..X8, SP_EL0, ELR_EL1, PSTATE do CommPage a načte z neprovázané GPA nulu, aby vyvolal paměťový návrat do Sentry.

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ími HV_MEMORY_READ | HV_MEMORY_WRITE | HV_MEMORY_EXEC. Uvnitř vCPU stub EL1 bluepill (bluepill_arm64.go) nastaví TCR_EL1.TG0 = 0b00 (granule 4 KB) v TTBR0_EL1 a naplní záznamy tabulky stránek Stage-1 po 4 KB přesnými oprávněními R-X nebo RW- (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 #0 hosta v EL0 přechází do VBAR_EL1 v EL1, uloží registry do sdílené CommPage o velikosti 4 KB a provede HVC #0, aby se vrátila z hv_vcpu_run do 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: Na amd64 patcher (patcher_amd64_xplat.go) nahrazuje dvoubajtové instrukce syscall (0f 05) pětibajtovými skoky jmp rel32 (nebo pasti CC 90 typu breakpoint, pokud není dostupné pětibajtové okno) do ostrova rychlé cesty. Na arm64 je svc #0 opraven větvením B do slotu ostrova. Ostrov ukládá registry do sdílené stránky winSysmsg a synchronizuje se se Sentry přes WaitOnAddress a WakeByAddressSingle, 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:

How gvx Maps Guest Linux Syscalls to Subsystems and Host OS

Doména systémových voláníReprezentativní systémová volání Linuxu hostaInterní subsystém gvx (pkg/sentry)Interakce s hranicí hostitelského OS (po hostlockdown)
A. Proces, jmenný prostor, připojení a synchronizaceclone, execve, unshare, setns, mount, umount2, pivot_root, setuid, capset, futex, epoll_wait, pipe2kernel.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_createmm.MemoryManager (strom VMA po 4 KB) a pgalloc.MemoryFileObousmě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_watchSmě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 Netfiltersocket, 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:

gvx VFS2 Storage Engines and Dual-Track Netstack

Úložný engineRole připojení v gvxPodkladový prostředek hostiteleVolání hostitelského openat / NtCreateFile za běhuSkenování Windows Defenderem (WdFilter.sys) na soubor
fsimpl/erofsObrazy OCI pouze pro čtení, spodní vrstvyJeden soubor <sha256>.erofs na vrstvu00
fsimpl/ext4litePojmenované svazky (-v name:/path)Jeden soubor <name>.ext4 na svazek00
fsimpl/hostfs v2Pracovní prostor hostitele (-workspace, -v /host:/guest)Připnutý adresář hostitele *os.RootO(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ů (fallocate na Linuxu, fcntl(F_PREALLOCATE) na macOS, FileAllocationInfo na 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é atributy trusted.overlay.* a security.capability, whiteouty znakových zařízení (0:0), hlášení záznamů adresářů d_type a RENAME_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ím com.apple.security.hypervisor (xattr -d com.apple.quarantine rungv odstraní karanténu prohlížeče). Pokud sestavujete lokálně pomocí go install nebo go build na macOS, podepište binární soubor příkazem codesign --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

  1. gvx: Spouštění kontejnerů Linuxu, Dockeru a Kubernetes v jediném neprivilegovaném procesu (tento příspěvek)
  2. Spouštění Dockeru, docker build a docker compose bez oprávnění root nebo jmenných prostorů uživatelů: Jak zavést docker:dind v uzamčených sandboxech, kde jsou blokovány sudo, unshare(CLONE_NEWUSER), mount(2) a /dev/fuse.
  3. Spouštění Docker-in-Docker uvnitř gVisoru a vnořených sandboxů s gvx: Proč apt-get install docker.io a vnořený runsc selhávají uvnitř gVisor přes pět bariér jádra a jak gvx spouští vnořený docker:dind pomocí smyčky futexu nad sdílenou pamětí.
  4. Rychlá izolovaná Docker prostředí pro rollouty RL agentů s gvx: Škálování SWE-bench a posilovaného učení agentů pomocí stovek paralelních neprivilegovaných instancí docker:dind a resetování svazků .ext4 s kopírováním při zápisu za 100 milisekund.