gvx: Изпълнение на Linux Containers, Docker и Kubernetes в един непривилегирован процес

Въведение: контейнери със засилена сигурност без root и без хост демони
Изпълнението на Linux контейнери, docker build или Kubernetes на компютър за разработка, CI рънър или мултитенантен облачен възел обикновено налага неудобен компромис със сигурността:
- На Linux стандартните OCI рантайми (
runc,crun,runsc) изискват или привилегииroot, или излагане на host/var/run/docker.sock, или--privileged(CAP_SYS_ADMIN), или user namespaces (unshare(CLONE_NEWUSER)), които разширяват повърхността на атака на хост ядрото. - На Windows и macOS контейнерните движещи (
Docker Desktop,Podman Machine,Lima,OrbStack) стартират пълно Linux ядро на виртуална машина в WSL2, Hyper-V илиVirtualization.frameworkи изпълняват постоянен фонов демон във вътрешността на тази VM.
gvx (CLI rungv и Go SDK z.gfire.dev/gvx/pkg/gvx) предоставя сигурна алтернатива без привилегии. Изградена като проследима спрямо горния поток форк на user-space Linux ядрото Sentry и user-space мрежовия стек netstack на Google gVisor, gvx изпълнява непроменени linux/amd64 и linux/arm64 ELF64 двоични файлове и OCI контейнери директно в един непривилегирован, самозаключващ се хост процес на Windows, macOS и Linux.
Гаранция за сигурност (триетажна защита в дълбочина):
gvxникога не отслабва границите на сигурност на хост операционната система, никога не инсталира драйвери на ядрото и никога не изискваroot,sudo,--privilegedна хоста илиCLONE_NEWUSER. Преди да изпълни и една инструкция на гост системата,gvxзаключва собствения си хост процес чрез Windows Restricted Tokens и Job Objects, macOS Seatbelt (deny default) или LinuxPR_SET_NO_NEW_PRIVSи BPFseccomp. Всички системни извиквания на гост системата, виртуалните привилегииUID 0, монтиранията (VFS2) и мрежовите таблици (netstack) са строго ограничени във вътрешността на безопасното по отношение на паметта Go ядроSentry.
| Рантайм | Хост ОС | Студен старт | Отпечатък в паметта | Нужни права на администратор или sudo | Фонов VM демон | Директно изпълнение на OCI образ |
|---|---|---|---|---|---|---|
gvx (whpx / hvf) | Windows (amd64, arm64) и macOS (arm64) | ~20 ms | ~30 MiB | Не | Не | Да |
gvx (systrap) | Windows (amd64, arm64) и Linux (amd64, arm64) | ~25 до 65 ms | ~31 MiB | Не | Не | Да |
| WSL2 / Docker Desktop | Windows и macOS | 800 до 2500 ms | > 512 MiB | Да (първоначална настройка) | Да | Изисква демон |
| QEMU (Full System) | Windows и macOS | 1500 до 4000 ms | > 256 MiB | Не | Не | Изисква дисков образ |
На Windows и macOS не се зарежда Linux ядро на VM. На хоста не работи фонов демон. На хостовата операционна система не се изискват root, sudo, user namespaces (CLONE_NEWUSER), mount(2), драйвери TUN/TAP или привилегии CAP_NET_ADMIN. Във вътрешността на този непривилегирован процес gvx изпълнява Docker-in-Docker (docker:dind), docker build, docker compose и многовъзлов High-Availability Kubernetes (k3s с вграден etcd и Calico CNI).
Жизнен цикъл на контейнера и триетажно хост заключване
Всяка sandbox среда на gvx работи във вътрешността на един непривилегирован хост процес и спазва тристепенен жизнен цикъл: предварително отваряне на всички необходими хост файлови дескриптори и сокети, заключване на хост процеса на операционната система чрез pkg/hostlockdown и влизане в цикъла за изпълнение на Sentry във вътрешността на процеса.

Преди да предаде управлението на първата инструкция на гост системата, gvx предварително отваря своите кеширани дескриптори на .erofs слоеве на образа, дескриптори на .ext4 томове, манипулатора *os.Root за /workspace и хост мрежовите сокети, след което извиква hostlockdown.Apply():
- Windows NT (
lockdown_windows.go): ИзвикваAdjustTokenPrivilegesсSE_PRIVILEGE_REMOVED, за да премахне привилегиите на място, свързва ограничен токен за имперсонацияDISABLE_MAX_PRIVILEGEс Low интегритет (S-1-16-4096), присвоява процеса към анонимен Job Object сJOB_OBJECT_LIMIT_ACTIVE_PROCESS = 1(блокиращ създаването на всякакви дъщерни процеси) и активираDisallowWin32kSystemCalls = 1чрезSetProcessMitigationPolicy. - macOS XNU (
lockdown_darwin.go): ЗадаваRLIMIT_NPROC = 0и прилага профил на Seatbelt (sandbox_init_with_parameters), започващ от(version 1)(deny default), който блокираprocess-fork,process-exec*и всички нови отваряния на хост файлове. - Linux (
lockdown_linux.go+pkg/gvx/syscallfilter_linux.go): ЗадаваPR_SET_NO_NEW_PRIVSи инсталира позволения списък на BPFseccompна Sentry за всички нишки (SECCOMP_FILTER_FLAG_TSYNC), с изрични правилаEPERMзаexecve,execveat,clone,openatиsocket.
Прихващане на системни извиквания между платформи и странициране от 4 KB (pkg/sentry/platform)
Linux ELF64 двоичните файлове предполагат гранулярност на страниците в паметта от 4 KB (0x1000) за сегментни съпоставяния с mmap(MAP_FIXED). Въпреки това Windows VirtualAlloc и MapViewOfFile3 налагат подравняване от 64 KB (0x10000), а Apple Silicon macOS (darwin/arm64) използва хостови страници от 16 KB (0x4000). Освен това всяка операционна система изисква различен механизъм за прихващане на инструкциите Linux SYSCALL (amd64) и SVC #0 (arm64) без стартиране на Linux VM.
gvx решава и двата проблема чрез четири бекенда за изпълнение, които реализират pkg/sentry/platform.Context:

1. Хардуерен двигател за Windows: platform/whpx (WinHvPlatform.dll)
На windows/amd64 и windows/arm64 platform/whpx създава лек хипервайзър дял чрез WHvCreatePartition, без да стартира ядро на гост операционна система:
- Разделител 64 KB към 4 KB страници:
whpxрегистрира всяка 64 MiB арена наpgalloc.MemoryFileпри дяла еднократно чрезWHvMapGpaRange. Последващите извиквания наMapFileиUnmapпроменят само таблиците със страници на гост системата във вътрешността на дяла, налагайки точни праваR/W/Xвърху страници от 4 KB, въпреки гранулярността на разпределение от 64 KB на Windows. - Обмен на регистри без API чрез
CommPage:- На
amd64SYSCALLот гост Ring-3 влиза във вътрешен Ring-0 стъбMSR_LSTAR(stubgen_x86.go), който записва GPR регистрите на бързия път за системни извиквания (RAX, RDI, RSI, RDX, R10, R8, R9, RIP, RSP) в споделенатаCommPageот 4 KB и изпълняваHLT, за да излезе отWHvRunVirtualProcessor. - На
arm64SVC #0от гост EL0 влиза във векторите на EL1 (stubgen_a64.go), записваX0..X8, SP_EL0, ELR_EL1, PSTATEвCommPageи зарежда от незаделен GPA нула, за да предизвика паметно излизане обратно към Sentry.
- На
2. Хардуерен двигател за macOS: platform/hvf (Hypervisor.framework)
На Apple Silicon (darwin/arm64) platform/hvf изпълнява нишките на контейнерите във vCPU на Hypervisor.framework (hv_vm_create, hv_vcpu_run):
- Двустепенен MMU превод от 4 KB върху 16 KB: Stage-2 (
hv_vm_map) съпоставя фрагменти от памет-файла по 1 GiB в пространството на междинните физически адреси (IPA) еднократно с гранулярност на хостовите страници от 16 KB, сHV_MEMORY_READ | HV_MEMORY_WRITE | HV_MEMORY_EXEC. Във вътрешността на vCPU стъбът EL1bluepill(bluepill_arm64.go) конфигурираTCR_EL1.TG0 = 0b00(гранула от 4 KB) вTTBR0_EL1и попълва записите на таблиците със страници Stage-1 от 4 KB с точни праваR-XилиRW-(UXN=1). Тъй като ARM64 MMU налага сечението на правата на Stage-1 и Stage-2, хардуерът улавя грешките при права на точни граници от 4 KB. - Прихващане на системни извиквания:
SVC #0от гост EL0 преминава към EL1VBAR_EL1, записва регистрите в споделенатаCommPageот 4 KB и изпълняваHVC #0, за да се върне отhv_vcpu_runкъм нишката на Go Sentry.
3. Резервен потребителски режим за Windows: platform/systrap (winsystrap)
Когато хардуерната виртуализация не е налице (например във вложени облачни VM), winsystrap изпълнява гост код в потребителски режим на windows/amd64 и windows/arm64:
- Хардуерна изолация чрез
CR3/TTBR0_EL1: Нишките на гост системата работят във вътрешността на изолирани подпроцеси стъбове, стартирани от генерирано изпълнимо изображение от 1 KB (stubimage_xplat.go), ограничено от токен с Low интегритет и Job Object. - Патчване на двоичния код на място и IPC
winSysmsg: Наamd64патчерът (patcher_amd64_xplat.go) заменя 2-байтовите инструкцииsyscall(0f 05) с 5-байтови скоковеjmp rel32(или с капаниCC 90, когато 5-байтов прозорец не е наличен) към остров за бърз път. Наarm64svc #0се патчва с клонBкъм слот в острова. Островът записва регистрите в споделената страницаwinSysmsgи се синхронизира със Sentry чрезWaitOnAddressиWakeByAddressSingle, докато непатчнатите капани и грешките на страници спират при Windows Debug Object (WaitForDebugEventEx).
4. Двигател за Linux в потребителски режим: platform/systrap
На linux/amd64 и linux/arm64 systrap изпълнява нишки на гост системата (sysmsg), които прихващат системните извиквания на гост системата чрез SECCOMP_RET_TRAP (SIGSYS) и двоично патчват местата syscall в трамплин в споделена памет (sysmsg), синхронизиран чрез SYS_FUTEX. Междувременно стъбовата нишка syscallThread (syscall_thread.go) изпълнява операции по адресното пространство (mmap, munmap, clone) чрез SECCOMP_RET_USER_NOTIF (_RUN_SECCOMP_LOOP). Когато gvx работи във вътрешността на външна gVisor (runsc) среда, в която единственият слот SECCOMP_FILTER_FLAG_NEW_LISTENER връща EBUSY, gvx автоматично превключва syscallThread към цикъл с futex в споделена памет (_RUN_SYSCALL_LOOP), както е описано в част 3 от тази серия.
Как gvx съпоставя системните извиквания на гост Linux с виртуални подсистеми и хостови манипулатори
След като платформеният двигател прихване инструкцията SYSCALL (amd64) или SVC #0 (arm64) на гост системата и запише регистрите на гост системата в страницата от 4 KB (CommPage, sysmsg или winSysmsg), управлението се връща към горутината на задачата на Go Sentry (pkg/sentry/kernel/task_syscall.go). Вместо да препраща системните извиквания на Linux към ядрото на основната хостова операционна система, Task.doSyscall() запазва първоначалното състояние на регистрите (SyscallSaveOrig()), оценява всички вътрешни за гост системата BPF филтри seccomp и спирания ptrace и изпраща номера на системното извикване през linux.AMD64 или linux.ARM64 (pkg/sentry/syscalls/linux/linux64.go, обхващащ ~370 системни извиквания на Linux) към четири вътрешни домейна:

| Домейн на системни извиквания | Представителни системни извиквания на гост Linux | Вътрешна подсистема на gvx (pkg/sentry) | Взаимодействие с границата на хостовата ОС (след hostlockdown) |
|---|---|---|---|
| A. Процеси, namespace, монтиране и синхронизация | clone, execve, unshare, setns, mount, umount2, pivot_root, setuid, capset, futex, epoll_wait, pipe2 | kernel.Task, виртуални идентификационни данни, таблица futex и VFS2 в паметта (procfs, sysfs, cgroupfs v2, tmpfs, overlayfs) | Нула хостови системни извиквания (100% състояние в паметта на Go; напълно невидимо за хостовата ОС) |
B. Виртуална памет (MM) | mmap, munmap, mremap, mprotect, brk, madvise, memfd_create | mm.MemoryManager (дърво на VMA от 4 KB) и pgalloc.MemoryFile | Двупосочни обновявания на таблиците със страници от 4 KB чрез WHvMapGpaRange, hv_vm_map + TTBR0_EL1 или стъбови mmap/mprotect |
C. Файлове и съхранение (VFS2) | openat, pread64, pwrite64, getdents64, statx, renameat2, setxattr, mknod, inotify_add_watch | Маршрутизатор на таблицата с монтирания vfs.VirtualFilesystem (fsimpl/erofs, fsimpl/ext4lite, fsimpl/hostfs v2) | Двупосочни pread/pwrite към 1 закачен FD на .erofs или 1 FD на .ext4, или ограничени относителни обхождания върху *os.Root |
| D. Мрежа и Netfilter | socket, bind, listen, connect, sendmsg, recvmsg, netlink, setsockopt(IPT_SO_SET_REPLACE) | Потребителско пространство pkg/tcpip (lo, eth0, docker0, veth, vxlan, wireguard, ipset, iptables-legacy) | Трафикът във вътрешността на sandbox среда използва нула хостови системни извиквания; външният трафик се съпоставя с net.Dial/net.Listen (-net=nat) или с 1 UDP сокет (-net=wg) |
Когато обработчикът върне (rval, ctrl, err), Task.doSyscallInvoke() превръща всяка грешка в отрицателен Linux errno (-ExtractErrno(err, sysno)), записва върнатата стойност обратно в споделената страница с регистри (RAX на amd64, X0 на arm64) и възобновява изпълнението на гост системата.
Съхранение (VFS2) и двупътна мрежа в потребителско пространство (netstack)
Всички файлови и мрежови операции на гост системата се обслужват във вътрешността на Go Sentry чрез три файлови системи VFS2 и два трака pkg/netbackend:

| Двигател за съхранение | Роля на монтиране в gvx | Хостов ресурс зад него | Извиквания openat / NtCreateFile към хоста при изпълнение | Сканирания на файлове от Windows Defender (WdFilter.sys) |
|---|---|---|---|---|
fsimpl/erofs | Долни слоеве на OCI образ, само за четене | Един файл <sha256>.erofs на слой | 0 | 0 |
fsimpl/ext4lite | Именувани томове (-v name:/path) | Един файл <name>.ext4 на том | 0 | 0 |
fsimpl/hostfs v2 | Работно пространство на хоста (-workspace, -v /host:/guest) | Закачена хостова директория *os.Root | O(depth) при всяко некеширано отваряне | 1 при всяко затваряне на хостов файл |
Защо ext4lite е важен за Docker, бази данни и мениджъри на пакети
Извличането или записването на десетки хиляди файлове на контейнери директно върху Windows NTFS или macOS APFS предизвиква сканиране от антивирусния minifilter (WdFilter.sys), конфликти при чувствителност към регистър на имената и липсващи разширени атрибути на POSIX.
pkg/sentry/fsimpl/ext4lite реализира чиста Go файлова система ext4 във вътрешността на един предварително отворен хостов файл (%LOCALAPPDATA%\gvx\cache\volumes\<name>.ext4 на Windows, ~/Library/Caches/gvx/volumes/<name>.ext4 на macOS или ~/.cache/gvx/volumes/<name>.ext4 на Linux) със следните характеристики:
- Блокове от 4 KiB и динамични блокови групи от 16 MiB (
meta_bg): Расте динамично, без забавяния при разпределяне на разредени файлове (fallocateна Linux,fcntl(F_PREALLOCATE)на macOS,FileAllocationInfoна Windows), и уплътнява неизползваните крайни групи при чисто размонтиране. - Журнал JBD2 в ordered режим (inode 8): Атомарно потвърждаване на транзакции с контролни суми CRC-32 и revoke блокове, плюс възстановяване чрез
fsckLite. - Поддръжка на горния слой на
overlayfs: Реализира разширените атрибутиtrusted.overlay.*иsecurity.capability, символни whiteout устройства (0:0), отчитане на записи в директорииd_typeиRENAME_EXCHANGE.
Практически натоварвания: Docker Engine, Kubernetes (k3s + Calico) и Go SDK
Инсталиране на rungv: предварително компилирани издания с SLSA атестация или go install
Можете да изтеглите предварително компилиран архив rungv за linux/amd64, linux/arm64, darwin/arm64, windows/amd64 или windows/arm64 от GitHub Releases (всеки от които е изграден от конвейера за издания SLSA с пакет за произход Sigstore и SHA256SUMS), или да инсталирате директно от изходния код с go install:
1# Вариант A: Изтеглете предварително компилирано издание и проверете 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# Вариант B: Инсталирайте директно чрез Go (Linux / Windows)
9CGO_ENABLED=0 go install z.gfire.dev/gvx/cmd/rungv@latest
На macOS (
darwin/arm64) предварително компилираният архив на изданието вече е подписан с правотоcom.apple.security.hypervisor(xattr -d com.apple.quarantine rungvпремахва карантината на браузъра). Ако компилирате локално сgo installилиgo buildна macOS, подпишете двоичния файл сcodesign --sign - --entitlements entitlements.plist --force $(which rungv).
1. Стартиране на непривилегирован Docker Engine (docker:dind)
Според docs/docker-engine.md можете да стартирате непроменен контейнер docker:dind в rungv на Windows, macOS или Linux и да го управлявате със своя хостов CLI docker:
1# 1. Създайте хостовата директория за пренасочения Unix сокет
2mkdir -p ~/.gvx
3
4# 2. Стартирайте docker:dind в sandbox среда на gvx с том 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
Във втори терминал насочете своя хостов CLI docker към сокета на sandbox средата:
1docker context create gvx --docker host=unix://$HOME/.gvx/docker.sock
2docker context use gvx
3
4docker info --format '{{.Driver}} {{.OperatingSystem}}'
5# Изход: overlayfs Alpine Linux ... (containerized)
6
7docker build -t myapp ./app
8docker compose up -d
2. Стартиране на Kubernetes (k3s + Calico) в непривилегировани sandbox среди
Тъй като gvx реализира /dev/kmsg, cgroup v2 (убиецът на OOM memory.max и ограничаването на честотната лента cpu.max), /proc/diskstats, мрежовите устройства wireguard и vxlan netlink, ipset и iptables-legacy (addrtype, conntrack, statistic, recent), непроменен образ rancher/k3s достига състояние Ready във вътрешността на един процес 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
Можете също да свържете няколко възела gvx през WireGuard (flannel-backend=wireguard-native или мрежа wg0) с вграден etcd и висока наличност, или да стартирате Calico (calico-vxlan.yaml с FELIX_IPTABLESBACKEND=Legacy), за да наложите Kubernetes NetworkPolicy изцяло в потребителското пространство.
3. Вграждане на Linux контейнери в Go чрез z.gfire.dev/gvx/pkg/gvx
Go приложенията могат да вграждат Linux контейнери директно, без външен демон за контейнери:
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}
Допълнително четиво в тази серия
- gvx: Изпълнение на Linux контейнери, Docker и Kubernetes в един непривилегирован процес (тази публикация)
- Изпълнение на Docker, docker build и docker compose без root или User Namespaces: Как да стартирате
docker:dindв заключени sandbox среди, къдетоsudo,unshare(CLONE_NEWUSER),mount(2)и/dev/fuseса блокирани. - Изпълнение на Docker-in-Docker във вътрешността на gVisor и вложени sandbox среди с gvx: Защо
apt-get install docker.ioи вложениятrunscсе провалят във вътрешността наgVisorпрез пет бариери на ядрото и какgvxизпълнява вложенdocker:dindчрез цикъл с futex в споделена памет. - Бързи изолирани Docker среди за RL rollouts на AI агенти с gvx: Мащабиране на
SWE-benchи rollouts за обучение на агенти с подкрепление със стотици паралелни непривилегировани инстанции наdocker:dindи 100-милисекундни копия при запис (copy-on-write) на.ext4томове.