GoSuda

gvx: Uruchamianie kontenerów Linux, Docker i Kubernetes w jednym nieuprzywilejowanym procesie

By Lemon Mint views ...

Wprowadzenie: kontenery o wzmocnionym zabezpieczeniu bez roota i demonów hosta

Uruchamianie kontenerów Linux, docker build lub Kubernetes na maszynie deweloperskiej, runnerze CI lub węźle chmury wielodostępowej zazwyczaj wymusza niewygodny kompromis w zakresie bezpieczeństwa:

  • W systemie Linux standardowe runtime'y OCI (runc, crun, runsc) wymagają albo uprawnień root, udostępnienia gniazda /var/run/docker.sock hosta, flagi --privileged (CAP_SYS_ADMIN), albo przestrzeni nazw użytkowników (unshare(CLONE_NEWUSER)), które poszerzają powierzchnię ataku jądra hosta.
  • W systemach Windows i macOS silniki kontenerów (Docker Desktop, Podman Machine, Lima, OrbStack) uruchamiają pełne jądro maszyny wirtualnej Linux w ramach WSL2, Hyper-V lub Virtualization.framework i utrzymują trwały demon działający w tej maszynie wirtualnej.

gvx (CLI rungv oraz Go SDK z.gfire.dev/gvx/pkg/gvx) stanowi bezpieczną alternatywę bez uprawnień. Zbudowany jako śledzący upstream fork przestrzeni użytkownika jądra Linux gVisor firmy Google (Sentry) oraz stosu sieciowego w przestrzeni użytkownika (netstack), gvx uruchamia niezmodyfikowane pliki binarne linux/amd64 i linux/arm64 w formacie ELF64 oraz kontenery OCI bezpośrednio w jednym, nieuprzywilejowanym, samozablokowanym procesie hosta w systemach Windows, macOS i Linux.

Gwarancja bezpieczeństwa (trzypierścieniowa obrona w głąb): gvx nigdy nie osłabia granic bezpieczeństwa systemu operacyjnego hosta, nigdy nie instaluje sterowników jądra i nigdy nie wymaga root, sudo, --privileged na hoście ani CLONE_NEWUSER. Zanim wykona choćby jedną instrukcję gościa, gvx blokuje własny proces hosta przy użyciu Windows Restricted Tokens i Job Objects, macOS Seatbelt (deny default) lub Linux PR_SET_NO_NEW_PRIVS oraz BPF seccomp. Wszystkie wywołania systemowe gościa, wirtualne uprawnienia UID 0, montowania (VFS2) i tablice sieciowe (netstack) są ściśle ograniczone wewnątrz bezpiecznej pod względem pamięci jądra Go Sentry.

RuntimeSystem operacyjny hostaZimny startZużycie pamięciWymagane uprawnienia administratora lub sudoDemon maszyny wirtualnej w tleBezpośrednie uruchamianie obrazu OCI
gvx (whpx / hvf)Windows (amd64, arm64) i macOS (arm64)~20 ms~30 MiBNieNieTak
gvx (systrap)Windows (amd64, arm64) i Linux (amd64, arm64)~25 do 65 ms~31 MiBNieNieTak
WSL2 / Docker DesktopWindows i macOS800 do 2500 ms> 512 MiBTak (konfiguracja początkowa)TakWymaga demona
QEMU (pełny system)Windows i macOS1500 do 4000 ms> 256 MiBNieNieWymaga obrazu dysku

Na systemach Windows i macOS nie uruchamia się żadne jądro maszyny wirtualnej Linux. Na hoście nie działa żaden demon w tle. Na systemie operacyjnym hosta nie są wymagane uprawnienia root, sudo, przestrzenie nazw użytkowników (CLONE_NEWUSER), mount(2), sterowniki TUN/TAP ani CAP_NET_ADMIN. Wewnątrz tego nieuprzywilejowanego procesu gvx uruchamia Docker-in-Docker (docker:dind), docker build, docker compose oraz wysoko dostępny, wieloWęzłowyKubernetes (k3s z osadzonym etcd i wtyczką CNI Calico).


Cykl życia kontenera i trzypierścieniowa blokada hosta

Każda piaskownica gvx działa w jednym nieuprzywilejowanym procesie hosta i realizuje trzyfazowy cykl życia: wstępne otwarcie wszystkich wymaganych deskryptorów plików i gniazd hosta, zablokowanie procesu systemu operacyjnego hosta za pomocą pkg/hostlockdown oraz przejście do pętli wykonawczej Sentry działającej w procesie.

Cykl życia kontenera gvx i samoblokada hosta

Przed przekazaniem sterowania do pierwszej instrukcji gościa gvx wstępnie otwiera buforowane deskryptory warstw obrazów .erofs, deskryptory plików woluminów .ext4, uchwyt *os.Root katalogu /workspace oraz gniazda sieciowe hosta, a następnie wywołuje hostlockdown.Apply():

  1. Windows NT (lockdown_windows.go): wywołuje AdjustTokenPrivileges z SE_PRIVILEGE_REMOVED, aby usunąć uprawnienia w miejscu, wiąże token ograniczonego podszywania się DISABLE_MAX_PRIVILEGE na niskim poziomie integralności (S-1-16-4096), przypisuje proces do anonimowego Job Object z JOB_OBJECT_LIMIT_ACTIVE_PROCESS = 1 (blokując tworzenie procesów potomnych) oraz włącza DisallowWin32kSystemCalls = 1 przez SetProcessMitigationPolicy.
  2. macOS XNU (lockdown_darwin.go): ustawia RLIMIT_NPROC = 0 i stosuje profil Seatbelt (sandbox_init_with_parameters) rozpoczynający się od (version 1)(deny default), który blokuje process-fork, process-exec* oraz wszystkie nowe otwarcia plików hosta.
  3. Linux (lockdown_linux.go + pkg/gvx/syscallfilter_linux.go): ustawia PR_SET_NO_NEW_PRIVS i instaluje listę dozwolonych wywołań BPF seccomp Sentry dla wszystkich wątków (SECCOMP_FILTER_FLAG_TSYNC) wraz z jawnymi regułami EPERM dla execve, execveat, clone, openat i socket.

Przechwytywanie wywołań systemowych między platformami i stronicowanie 4 KB (pkg/sentry/platform)

Pliki binarne Linux ELF64 zakładają ziarnistość stron pamięci 4 KB (0x1000) przy mapowaniu segmentów przez mmap(MAP_FIXED). Tymczasem VirtualAlloc i MapViewOfFile3 w systemie Windows wymuszają wyrównanie do 64 KB (0x10000), a macOS na Apple Silicon (darwin/arm64) używa stron hosta o rozmiarze 16 KB (0x4000). Ponadto każdy system operacyjny wymaga innego mechanizmu, aby przechwycić instrukcje Linux SYSCALL (amd64) i SVC #0 (arm64) bez uruchamiania maszyny wirtualnej Linux.

gvx rozwiązuje oba problemy w czterech backendach wykonawczych, które implementują pkg/sentry/platform.Context:

Mechanizmy przechwytywania wywołań systemowych i stronicowania 4KB w gvx

1. Sprzętowy silnik Windows: platform/whpx (WinHvPlatform.dll)

W systemach windows/amd64 i windows/arm64 platform/whpx tworzy lekką partycję hipernadzorcy za pomocą WHvCreatePartition, bez uruchamiania jądra systemu gościa:

  • Dekoplер stronicowania 64 KB na 4 KB: whpx rejestruje każdą 64-megabajtową arenę pgalloc.MemoryFile w partycji jednorazowo, za pomocą WHvMapGpaRange. Kolejne wywołania MapFile i Unmap modyfikują wyłącznie tablice stron gościa wewnątrz partycji, egzekwując dokładne uprawnienia R/W/X na poziomie 4 KB, mimo ziarnistości alokacji 64 KB w systemie Windows.
  • Wymiana rejestrów bez wywołań API przez CommPage:
    • Na amd64 instrukcja SYSCALL wykonana w Ring-3 gościa wchodzi do stubu MSR_LSTAR działającego w Ring-0 wewnątrz partycji (stubgen_x86.go), który zapisuje rejestry GPR ścieżki szybkiej (RAX, RDI, RSI, RDX, R10, R8, R9, RIP, RSP) we wspólnej CommPage o rozmiarze 4 KB i wykonuje HLT, aby zakończyć działanie WHvRunVirtualProcessor.
    • Na arm64 instrukcja SVC #0 wykonana w EL0 gościa wchodzi do wektorów EL1 (stubgen_a64.go), zapisuje X0..X8, SP_EL0, ELR_EL1, PSTATE w CommPage i odczytuje z niezamapowanego adresu GPA zero, aby wywołać wyjście pamięciowe z powrotem do Sentry.

2. Sprzętowy silnik macOS: platform/hvf (Hypervisor.framework)

Na Apple Silicon (darwin/arm64) platform/hvf uruchamia wątki kontenerów wewnątrz vCPU Hypervisor.framework (hv_vm_create, hv_vcpu_run):

  • Dwuetapowa translacja MMU 4 KB nad 16 KB: Stage-2 (hv_vm_map) mapuje fragmenty pliku pamięci o rozmiarze 1 GiB do przestrzeni Intermediate Physical Address (IPA) jednorazowo, z ziarnistością stron hosta 16 KB, z uprawnieniami HV_MEMORY_READ | HV_MEMORY_WRITE | HV_MEMORY_EXEC. Wewnątrz vCPU stub EL1 bluepill (bluepill_arm64.go) konfiguruje TCR_EL1.TG0 = 0b00 (granula 4 KB) w TTBR0_EL1 i wypełnia wpisy tablic stron Stage-1 o rozmiarze 4 KB z dokładnymi uprawnieniami R-X lub RW- (UXN=1). Ponieważ MMU ARM64 wymusza przecięcie uprawnień Stage-1 i Stage-2, sprzęt przechwytuje błędy uprawnień na dokładnych granicach 4 KB.
  • Pułapka wywołania systemowego: instrukcja SVC #0 wykonana w EL0 gościa trafia do VBAR_EL1 w EL1, zapisuje rejestry we wspólnej CommPage o rozmiarze 4 KB i wykonuje HVC #0, aby powrócić z hv_vcpu_run do wątku Go Sentry.

3. Rezerwowy tryb użytkownika Windows: platform/systrap (winsystrap)

Gdy sprzętowa wirtualizacja jest niedostępna (na przykład wewnątrz zagnieżdżonych maszyn wirtualnych w chmurze), winsystrap uruchamia kod gościa w trybie użytkownika w systemach windows/amd64 i windows/arm64:

  • Izolacja sprzętowa CR3 / TTBR0_EL1: wątki gościa działają wewnątrz izolowanych podprocesów stubowych uruchamianych z wygenerowanego pliku wykonywalnego o rozmiarze 1 KB (stubimage_xplat.go), ograniczonych tokenem o niskim poziomie integralności i Job Object.
  • Łatanie binarne w miejscu i IPC winSysmsg: na amd64 moduł łatający (patcher_amd64_xplat.go) zastępuje 2-bajtowe instrukcje syscall (0f 05) 5-bajtowymi skokami jmp rel32 (lub pułapkami punktu przerwania CC 90, gdy 5-bajtowe okno jest niedostępne), kierującymi do wyspy ścieżki szybkiej. Na arm64 instrukcja svc #0 jest łatana gałęzią B do slotu wyspy. Wyspa zapisuje rejestry we wspólnej stronie winSysmsg i synchronizuje się z Sentry za pomocą WaitOnAddress oraz WakeByAddressSingle, podczas gdy niełatane pułapki i błędy stron zatrzymują działanie na obiekcie debugowania Windows (WaitForDebugEventEx).

4. Silnik trybu użytkownika Linux: platform/systrap

Na linux/amd64 i linux/arm64 systrap uruchamia wątki gościa (sysmsg), które przechwytują wywołania systemowe gościa za pomocą SECCOMP_RET_TRAP (SIGSYS) i łatają binarnie miejsca syscall w trampolinę we wspólnej pamięci (sysmsg), synchronizowaną przez SYS_FUTEX. Jednocześnie wątek stubowy syscallThread (syscall_thread.go) wykonuje operacje na przestrzeni adresowej (mmap, munmap, clone) za pośrednictwem SECCOMP_RET_USER_NOTIF (_RUN_SECCOMP_LOOP). Gdy gvx działa wewnątrz zewnętrznej piaskownicy gVisor (runsc), w której pojedynczy slot SECCOMP_FILTER_FLAG_NEW_LISTENER zwraca EBUSY, gvx automatycznie przełącza syscallThread na pętlę futex we współdzielonej pamięci (_RUN_SYSCALL_LOOP), co zostało szczegółowo opisane w części 3 tej serii.


Jak gvx mapuje wywołania systemowe Linux gościa na wirtualne podsystemy i uchwyty hosta

Gdy silnik platformy przechwyci instrukcję SYSCALL (amd64) lub SVC #0 (arm64) gościa i zapisze rejestry gościa we wspólnej stronie 4 KB (CommPage, sysmsg lub winSysmsg), sterowanie wraca do goroutine'a zadania Go Sentry (pkg/sentry/kernel/task_syscall.go). Zamiast przekazywać wywołania systemowe Linux do jądra systemu operacyjnego hosta, Task.doSyscall() zapisuje oryginalny stan rejestrów (SyscallSaveOrig()), ocenia wszelkie filtry BPF seccomp gościa oraz zatrzymania ptrace, po czym przekazuje numer wywołania systemowego przez linux.AMD64 lub linux.ARM64 (pkg/sentry/syscalls/linux/linux64.go, obejmujące około 370 wywołań systemowych Linux) do czterech wewnętrznych domen:

Jak gvx mapuje wywołania systemowe Linux gościa na podsystemy i system operacyjny hosta

Domena wywołań systemowychReprezentatywne wywołania systemowe Linux gościaWewnętrzny podsystem gvx (pkg/sentry)Interakcja z granicą systemu operacyjnego hosta (po hostlockdown)
A. Procesy, przestrzenie nazw, montowania i synchronizacjaclone, execve, unshare, setns, mount, umount2, pivot_root, setuid, capset, futex, epoll_wait, pipe2kernel.Task, wirtualne poświadczenia, tablica futex oraz VFS2 w pamięci (procfs, sysfs, cgroupfs v2, tmpfs, overlayfs)Zero wywołań systemowych hosta (100% stanu Go w pamięci; całkowicie niewidoczne dla systemu operacyjnego hosta)
B. Pamięć wirtualna (MM)mmap, munmap, mremap, mprotect, brk, madvise, memfd_createmm.MemoryManager (drzewo VMA 4 KB) oraz pgalloc.MemoryFileDwukierunkowe aktualizacje tablic stron 4 KB przez WHvMapGpaRange, hv_vm_map + TTBR0_EL1 lub stubowe mmap/mprotect
C. Pliki i pamięć masowa (VFS2)openat, pread64, pwrite64, getdents64, statx, renameat2, setxattr, mknod, inotify_add_watchRouter tabeli montowań vfs.VirtualFilesystem (fsimpl/erofs, fsimpl/ext4lite, fsimpl/hostfs v2)Dwukierunkowe pread/pwrite na 1 przypiętym deskryptorze .erofs lub 1 deskryptorze .ext4, albo ograniczone względne przejścia po *os.Root
D. Sieć i Netfiltersocket, bind, listen, connect, sendmsg, recvmsg, netlink, setsockopt(IPT_SO_SET_REPLACE)pkg/tcpip w przestrzeni użytkownika (lo, eth0, docker0, veth, vxlan, wireguard, ipset, iptables-legacy)Ruch wewnątrz piaskownicy nie wykonuje żadnych wywołań systemowych hosta; ruch zewnętrzny mapuje się na net.Dial/net.Listen (-net=nat) lub 1 gniazdo UDP (-net=wg)

Gdy handler zwraca (rval, ctrl, err), Task.doSyscallInvoke() przekształca każdy błąd w ujemny errno Linux (-ExtractErrno(err, sysno)), zapisuje wartość zwrotną z powrotem do współdzielonej strony rejestrów (RAX na amd64, X0 na arm64) i wznawia wykonywanie kodu gościa.


Pamięć masowa (VFS2) i dwutorowy stos sieciowy w przestrzeni użytkownika (netstack)

Wszystkie operacje plikowe i sieciowe gościa są obsługiwane wewnątrz Go Sentry przez trzy systemy plików VFS2 oraz dwa tory pkg/netbackend:

Silniki pamięci masowej VFS2 i dwutorowy netstack w gvx

Silnik pamięci masowejRola montowania w gvxZasób bazowy hostaWywołania hosta openat / NtCreateFile podczas działaniaSkanowania WdFilter.sys (Windows Defender) dla pojedynczych plików
fsimpl/erofsDolne warstwy obrazów OCI tylko do odczytuPojedynczy plik <sha256>.erofs na warstwę00
fsimpl/ext4liteNazwane woluminy (-v name:/path)Pojedynczy plik <name>.ext4 na wolumin00
fsimpl/hostfs v2Obszar roboczy hosta (-workspace, -v /host:/guest)Przypięty katalog hosta *os.RootO(depth) na każde nieobsłużone z pamięci podręcznej otwarcie1 na zamknięcie pliku hosta

Dlaczego ext4lite ma znaczenie dla Dockera, baz danych i menedżerów pakietów

Rozpakowywanie lub zapisywanie dziesiątek tysięcy plików kontenerów bezpośrednio na systemie plików NTFS w Windows lub APFS w macOS uruchamia skanowanie przez minifiltry antywirusowe (WdFilter.sys), kolizje wynikające z rozróżniania wielkości liter oraz brak rozszerzonych atrybutów POSIX.

pkg/sentry/fsimpl/ext4lite implementuje czysto w Go system plików ext4 wewnątrz jednego wstępnie otwartego pliku hosta (%LOCALAPPDATA%\gvx\cache\volumes\<name>.ext4 w systemie Windows, ~/Library/Caches/gvx/volumes/<name>.ext4 w macOS lub ~/.cache/gvx/volumes/<name>.ext4 w Linux), z następującymi cechami:

  • Bloki 4 KiB i dynamiczne grupy bloków po 16 MiB (meta_bg): rosną dynamicznie bez zacięć wynikających z alokacji plików rzadkich (fallocate w Linux, fcntl(F_PREALLOCATE) w macOS, FileAllocationInfo w Windows) i kompaktują nieużywane końcowe grupy przy czystym odmontowaniu.
  • Dziennik JBD2 w trybie uporządkowanym (inode 8): atomowe zatwierdzanie transakcji z sumami kontrolnymi CRC-32 i blokami revoke, a także odzyskiwanie za pomocą fsckLite.
  • Obsługa warstwy górnej overlayfs: implementuje rozszerzone atrybuty trusted.overlay.* i security.capability, wymazania za pomocą urządzeń znakowych (0:0), raportowanie d_type wpisów katalogów oraz RENAME_EXCHANGE.

Praktyczne obciążenia: Docker Engine, Kubernetes (k3s + Calico) i Go SDK

Instalacja rungv: gotowe wydania z atestacją SLSA lub go install

Gotowe archiwum rungv możesz pobrać dla linux/amd64, linux/arm64, darwin/arm64, windows/amd64 lub windows/arm64 z GitHub Releases (każde zbudowane przez potok wydań SLSA z pakietem pochodzenia Sigstore i plikiem SHA256SUMS) albo zainstalować bezpośrednio ze źródeł za pomocą go install:

1# Opcja A: Pobierz gotowe wydanie i zweryfikuj pochodzenie builda 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# Opcja B: Zainstaluj bezpośrednio przez Go (Linux / Windows)
9CGO_ENABLED=0 go install z.gfire.dev/gvx/cmd/rungv@latest

W systemie macOS (darwin/arm64) gotowe archiwum wydania jest już podpisane uprawnieniem com.apple.security.hypervisor (xattr -d com.apple.quarantine rungv usuwa kwarantannę przeglądarki). Jeśli budujesz lokalnie za pomocą go install lub go build na macOS, podpisz plik binarny poleceniem codesign --sign - --entitlements entitlements.plist --force $(which rungv).

1. Uruchamianie nieuprzywilejowanego Docker Engine (docker:dind)

Zgodnie z docs/docker-engine.md możesz uruchomić niezmodyfikowany kontener docker:dind wewnątrz rungv w systemie Windows, macOS lub Linux i sterować nim za pomocą lokalnego CLI docker:

 1# 1. Utwórz katalog hosta dla przekazywanego gniazda Unix
 2mkdir -p ~/.gvx
 3
 4# 2. Uruchom docker:dind w piaskownicy gvx z woluminem 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

W drugim terminalu skieruj lokalne CLI docker na gniazdo piaskownicy:

1docker context create gvx --docker host=unix://$HOME/.gvx/docker.sock
2docker context use gvx
3
4docker info --format '{{.Driver}} {{.OperatingSystem}}'
5# Wynik: overlayfs Alpine Linux ... (containerized)
6
7docker build -t myapp ./app
8docker compose up -d

2. Uruchamianie Kubernetes (k3s + Calico) w nieuprzywilejowanych piaskownicach

Ponieważ gvx implementuje /dev/kmsg, cgroup v2 (zabójca OOM oparty na memory.max oraz ograniczanie przepustowości cpu.max), /proc/diskstats, urządzenia netlink wireguard i vxlan, ipset oraz iptables-legacy (addrtype, conntrack, statistic, recent), niezmodyfikowany obraz rancher/k3s osiąga stan Ready w obrębie pojedynczego 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

Możesz także połączyć wiele węzłów gvx przez WireGuard (flannel-backend=wireguard-native lub siatka wg0) z osadzonym etcd w wysokiej dostępności albo uruchomić Calico (calico-vxlan.yaml z FELIX_IPTABLESBACKEND=Legacy), aby w pełni w przestrzeni użytkownika egzekwować NetworkPolicy Kubernetes.

3. Osadzanie kontenerów Linux w Go za pomocą z.gfire.dev/gvx/pkg/gvx

Aplikacje Go mogą osadzać kontenery Linux bezpośrednio, bez zewnętrznego demona kontenerów:

 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}

Dalsza lektura w tej serii

  1. gvx: Uruchamianie kontenerów Linux, Dockera i Kubernetes w jednym nieuprzywilejowanym procesie (ten wpis)
  2. Uruchamianie Dockera, docker build i docker compose bez roota i przestrzeni nazw użytkowników: jak uruchomić docker:dind w zablokowanych piaskownicach, w których zablokowane są sudo, unshare(CLONE_NEWUSER), mount(2) i /dev/fuse.
  3. Uruchamianie Docker-in-Docker wewnątrz gVisor i zagnieżdżonych piaskownic z gvx: dlaczego apt-get install docker.io oraz zagnieżdżone runsc kończą się niepowodzeniem wewnątrz gVisor na pięciu barierach jądra oraz jak gvx uruchamia zagnieżdżony docker:dind przy użyciu pętli futex we współdzielonej pamięci.
  4. Szybkie izolowane środowiska Docker dla rolloutów uczenia ze wzmocnieniem agentów AI z gvx: skalowanie SWE-bench i rolloutów uczenia ze wzmocnieniem agentów z setkami równoległych nieuprzywilejowanych instancji docker:dind oraz 100-milisekundowymi resetami woluminów .ext4 z kopiowaniem przy zapisie.