ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

gVisor 安全架构入门:理解“应用内核”沙箱的隔离原理与验证方法

gVisor 安全架构入门:理解“应用内核”沙箱的隔离原理与验证方法 gVisor 安全架构入门理解“应用内核”沙箱的隔离原理与验证方法【免费下载链接】gvisorApplication Kernel for Containers项目地址: https://gitcode.com/GitHub_Trending/gv/gvisorgVisor 是面向容器场景的开源工作负载隔离方案它的独特之处在于既不是虚拟机监控器Hypervisor也不是系统调用过滤器而是一个运行在用户态、用内存安全语言 Go 从零实现的“应用内核”。本文面向具备操作系统与内核基础的安全研究者先对比 Linux 内核安全原语与虚拟机两种主流隔离思路再逐层剖析 gVisor 的 Sentry / Gofer 架构、Systrap 与 KVM 两大平台机制最后给出runsc do、rootless 与 Docker 三种实测沙箱的完整方法帮助你从原理到实操完整理解 gVisor 的威胁模型与防御边界。gVisor 是什么gVisor 是一个开源的工作负载隔离解决方案用于安全地运行不可信代码、容器和应用。它与其它隔离方案的根本区别在于gVisor 是一个应用内核application kernel而不是虚拟机监控器也不是系统调用过滤器。从运行位置看gVisor 同时具备两种身份对被沙箱化的工作负载而言gVisor 扮演内核的角色拦截并处理其发出的系统调用与页错误对宿主机内核而言gVisor 本身又是一个普通的用户态应用程序。这套“像内核一样工作、像普通进程一样被宿主调度”的设计决定了后续所有安全属性的推导方式。若需要先建立威胁模型层面的全局认识建议同时阅读 g3doc/architecture_guide/security.md该文档从攻击向量分类System API / System ABI / 侧信道出发说明了 gVisor 的设计目标尽量缩小 System API 这一最常见的攻击向量同时保持进程模型。主流隔离方案对比为什么 gVisor 与众不同沙箱化工作负载最常见的两种路径是虚拟化VM以及seccomp-bpf、Linux namespaces、AppArmor、Landlock 等 Linux 内核安全原语。gVisor 也会使用这些技术但用法与常规思路截然不同它们只作为纵深防御defense-in-depth的第二道防线而非主防线。Linux 内核安全原语的局限Isolation with Linux kernel security primitives使用seccomp-bpf、AppArmor、Landlock、namespaces 等原语时沙箱应用的攻击面被缩减了但执行强制策略的仍然是那一个单体 Linux 内核——沙箱应用依然可以直接与之对话。这意味着工作负载离宿主机沦陷只有“一个系统调用”的距离内核安全原语只帮助缩小攻击面但攻击面内部的任何漏洞或能让内核安全机制本身失效的漏洞依然可以被利用。并且这些原语必须针对特定工作负载精心配置才有意义例如系统调用过滤器必须裁剪到工作负载所需的最小集合。这带来两个现实问题如果应用依赖ioctl(2)、io_uring(2)这类“不安全”或过宽的系统调用几乎不可能为其构造出安全可靠的过滤器集合如果要做一个适用于所有或大多数工作负载的通用配置结果往往是必须放行内核表面的大部分能力。gVisor 同样借助seccomp-bpf与 namespaces 来压缩自身暴露给宿主内核的表面但只把它当作第二层防御不需要按工作负载定制也能产生安全意义。虚拟化的成本与启发Isolation with virtualization使用虚拟机时一个 Hypervisor可运行在用户态、内核态或两者兼有协调两个内核一个在宿主机正常运行另一个运行在硬件强制的虚拟机内部沙箱工作负载的活动被限制在其中。虚拟机构成了强安全边界应用访问宿主资源的唯一出路是“VM exit”事件——它只在特定情形下触发并由 Hypervisor 处理。虚拟机是工作负载隔离的黄金标准但代价高昂需要为每台宿主机上的每个虚拟机预分配机器资源还要启动一个完整的、独立的 Linux 内核资源开销与效率损失显著。gVisor 的设计从虚拟机中获得了核心启发——双内核架构但它可以只在必要时才使用虚拟化即 KVM也可以完全不依赖虚拟化而维持高安全等级。gVisor 如何提供隔离Sentry 应用内核Isolation with gVisorgVisor 作为应用内核运行在用户态从沙箱工作负载的视角它取代了内核从宿主内核的视角它只是一个普通用户程序。像内核一样gVisor 拦截并处理沙箱工作负载的系统调用和页错误处理逻辑全部发生在 gVisor 自己的代码里这段代码用内存安全语言 Go 编写被称为gVisor Sentry。像普通用户程序一样Sentry可能会向宿主 Linux 内核发出有限的系统调用——仅当它判断服务沙箱工作负载的请求确实需要宿主机信息且沙箱工作负载在初始配置中被允许这类访问时才会这样做。这意味着一项庞大的工程承诺Sentry 需要用 Go 从零重新实现 Linux。Sentry 内含基于 Go 的系统调用接口、内存管理、文件系统、网络协议栈、进程管理、信号处理、namespaces 等完整实现。gVisor 从不把任何系统调用直通给宿主机——如果一个内核特性没有在 gVisor 中重实现沙箱工作负载就用不了它。两个例子getpid 与 pipe例一沙箱内调用getpid(2)。gVisor 拦截该调用查询自己维护的 PID 表表示沙箱内的进程这些并不是真实的宿主机进程在宿主机上运行top(1)根本看不到它们找到沙箱进程的 PID 并返回。从沙箱进程的视角它只是执行了getpid(2)但宿主机上根本没有发生任何系统调用。例二沙箱内两个进程通过 unixpipe(2)通信。一个进程read(2)、另一个write(2)时Sentry更具体地说是它依赖的 Go runtime可能调用宿主机的futex(2)来实现阻塞与同步。因此 Sentry 确实需要执行真实系统调用但它们与沙箱进程发出的系统调用并不是一一对应的。Sentry 自身运行在受限环境中Sentry 运行在一个高度受限的环境里使用了所有可用的 Linux 内核安全原语系统调用过滤、namespacing、cgroups、pivot_root(2)等。它的系统调用过滤器禁止exec(2)、connect(2)及其相关变体具体视沙箱配置而定通过 mount namespaces 获得宿主机文件系统的隔离视图运行在隔离的 user namespace 中仅持有最小 capabilities。需要特别澄清的是这并不意味着沙箱工作负载不能用这些系统调用——它其实可以用只是这些调用的逻辑与实现完全由 Sentry 的内核逻辑处理而不是把任何一部分委托给宿主机内核。这一点在 g3doc/architecture_guide/security.md 中被提炼为工程原则“没有系统调用被直接透传给宿主机每个受支持的调用在 Sentry 中都有独立实现因此不太可能遭受与宿主机完全相同的漏洞。”Gofer沙箱的“侧车”文件系统代理对于无法在受限环境内部服务的请求gVisor 引入了一个名为Gofer的侧车sidecar进程一个信任度稍高、运行在权限略高上下文的伴生进程。按 g3doc/user_guide/filesystem.md 的描述gVisor 通过 Gofer 这个文件代理访问文件系统Gofer 作为独立进程与沙箱隔离并通过LISAFS 协议与各自的 Sentry 通信。Sentry 对宿主机的高层交互被限制在极少数操作上使用 connected socket 与 Gofer 进程建立通信Gofer 管理容器的文件系统按需向沙箱提供文件描述符沙箱可直接读写这些 FD。沙箱自身运行在空 mount namespace 中执行最小集合的宿主系统调用——不包括创建新 socket除非启用 host networking或打开文件除非启用 directfs只包括文件描述符的复制与关闭、同步、定时器与信号管理读写虚拟以太网设备的数据包禁用网络或使用 host networking 时则不需要。在默认启用 directfs 的模式下Gofer 进程把所有挂载点的文件描述符捐献给沙箱沙箱用基于 FD 的系统调用如openat(2)、fchownat(2)直接操作文件禁用 directfs 时沙箱运行更严格的 seccomp 过滤器通过 RPC 请求 Gofer 代为执行文件系统操作安全性更高但性能有折衷。与虚拟机的同与异双内核安全架构gVisor 的安全架构与虚拟机相似存在两个分离的内核最内层的内核专属于沙箱工作负载且对宿主内核的访问被严格限制。但与虚拟机不同的是gVisor 沙箱可以在运行时灵活地分配和释放宿主资源CPU、内存从而在保持类虚拟机双内核安全收益的同时获得更好的资源效率与利用率。另一个关键差异在代码层面gVisor 的所有组件都用内存安全的 Go 编写消除了典型 VM 方案Linux 作为 guest kernel中最大的漏洞类别。要逃逸 gVisor 沙箱攻击者必须同时攻破 gVisor Sentry 内核和宿主机 Linux 内核而这两者不共享任何代码。平台机制Systrap 与 KVMgVisor 通过多种机制拦截沙箱工作负载的系统调用与页错误这些机制统称为“gVisor platforms”。平台之间对管理员是透明可互换的但从安全角度看并不相同——它们依赖的 Linux 内核功能不同。Systrap默认平台Systrap 基于 Linux 的seccomp-bpf子系统实现系统调用拦截注意与seccomp-bpf常规的过滤用途相反不需要宿主支持虚拟化因此非常适合在虚拟机内部运行。其原理在 pkg/sentry/platform/systrap/README.md 中有详细说明Linux 允许设置SECCOMP_RET_TRAP动作的 seccomp 过滤器当线程调用被过滤器捕获的系统调用时该线程会收到SIGSYS信号systrap 平台利用这一内核特性让所有需要 Sentry 处理的线程事件都触发信号。一个新 stub 线程的初始化包括安装 seccomp 过滤器以捕获所有用户系统调用设置一个与 Sentry 共享的备用信号栈为SIGSYS、SIGSEGV、SIGBUS、SIGFPE、SIGTRAP、SIGILL安装 sysmsg 信号处理器。用户代码在 stub 线程上下文中执行一旦发生系统调用或页错误stub 信号处理器就会运行并通知 SentrySentry 处理后回调该线程继续执行。内核在准备执行信号处理器时生成的信号帧保存寄存器、FPU 状态等进程状态存放在与 Sentry 进程共享的内存区域上gVisor 由此得以在 Sentry 中读取和修改线程状态。核心实现位于 pkg/sentry/platform/systrap/systrap.go其中Switch的流程注释为在 stub 信号帧上设置好寄存器和 FPU 状态 → 通过修改sysmsg-stage并调用FUTEX_WAKE唤醒 stub 线程 → 轮询sysmsg-stage等待新的 stub 事件。KVMKVM 平台基于 Linux 的 KVM 子系统用虚拟化手段提供地址空间隔离与页错误拦截沙箱工作负载的代码运行在 guest ring 3。该平台需要宿主支持虚拟化嵌套虚拟化下也可工作但通常比 Systrap 慢。在裸机非 VM环境中运行时KVM 平台通常能提供最佳性能。平台层的抽象接口定义在 pkg/sentry/platform/platform.goPlatform接口提供NewAddressSpace()创建新的内存上下文、NewContext()创建新的执行上下文Context.Switch()恢复线程执行AddressSpace负责MapFile/Unmap等地址空间操作。从源码结构看这一接口正是“平台可透明互换”承诺的落点。gVisor 不防护什么总体而言gVisor 通过把沙箱工作负载与宿主内核直接隔离来防御 Linux 内核漏洞利用。但它的保护边界之外仍有三类明显场景沙箱或容器运行时介入之前的高层组件攻击例如 containerd 中的漏洞导致它启动了一个没有 gVisor 的容器Spectre 式 CPU 侧信道攻击gVisor 只拦截系统调用和页错误应用可以自由使用 CPU在宿主 cgroup 限制内这与 VM 情形类似侧信道需要由宿主内核或硬件层面缓解沙箱工作负载内部的漏洞利用例如沙箱里运行 nginx 与 PHPPHP 代码被利用。gVisor 确实能阻止攻击者向宿主机进一步提权但攻击者依然能访问沙箱被配置允许访问的一切。因此不同的客户工作负载应运行在不同沙箱中防止恶意客户泄露数据或攻击其它客户工作负载。此外 gVisor 提供 runtime monitoring 运行时监控 特性可作为入侵检测机制探测沙箱工作负载本身的失陷。如何测试 gVisorgVisor 以 OCI 兼容容器运行时 runsc 的形式提供可配合 Docker见 g3doc/user_guide/quick_start/docker.md或 Kubernetes 使用也可以直接用于一次性测试$ sudo runsc do echo Hello world Hello world为什么需要 sudosudo可能会让你迟疑——沙箱工具难道不应该以非特权用户运行吗实际上gVisor 沙箱的工作负载在宿主内核视角下确实以最小 capabilities 运行在隔离的 user namespace 中但沙箱的搭建过程需要特权特别是搭建用户态网络协议栈时。沙箱搭建完成后gVisor 会重新执行自身并在任何不可信代码运行前丢弃全部特权。对于不需要网络的沙箱可以无 sudo 的 rootless 模式运行$ runsc --rootless --networknone do echo Hello world Hello world用 dmesg 验证 gVisor 真的在工作如何确认 gVisor 在起作用做一个会触及宿主内核的操作例如调用dmesg(1)读取内核日志# Without gVisor (unsandboxed): $ dmesg dmesg: read kernel buffer failed: Operation not permitted # With gVisor (sandboxed): $ runsc --rootless --networknone do dmesg [ 0.000000] Starting gVisor... [ 0.498943] Waiting for children... [ 0.972223] Committing treasure map to memory... [ 1.192981] Segmenting fault lines... [ 1.591823] Verifying that no non-zero bytes made their way into /dev/zero... [ 1.787191] Consulting tar man page... [ 2.083245] Searching for needles in stacks... [ 2.534575] Forking spaghetti code... [ 2.742140] Digging up root... [ 2.921313] Gathering forks... [ 3.342436] Creating cloned children... [ 3.511124] Setting up VFS... [ 3.812459] Setting up FUSE... [ 4.233037] Ready!这说明dmesg(1)二进制在跟 gVisor 内核对话而不是宿主 Linux 内核。这些幽默的提示信息是 gVisor 内核代码在沙箱工作负载请求内核日志时生成的虚构日志由 gVisor 的系统调用处理器按需产出因此重复运行会得到不同内容。以 Docker 运行时方式做安全测试注意runsc do默认把宿主机整个文件系统以只读方式暴露给沙箱它只是快速体验 gVisor 的便利功能。真实场景下当 runsc 作为 OCI 容器运行时使用时宿主机文件系统映射由 OCI 运行时配置严格定义gVisor 只暴露 OCI 配置规定应暴露的路径并会执行pivot_root(2)切断访问其它宿主目录的能力以作纵深防御。因此从安全角度测试 gVisor更推荐把它安装为 Docker 运行时见 g3doc/user_guide/quick_start/docker.md 的sudo runsc install与sudo systemctl restart docker步骤然后这样使用$ sudo docker run --rm --runtimerunsc -it -v /tmp/vol:/vol ubuntu /bin/bash这会在 gVisor 沙箱内启动一个 Bash shell宿主机目录/tmp/vol被映射为沙箱内的/vol。你可以尝试在沙箱内四处试探看看能否逃逸到宿主机、或窥探到除/tmp/vol内容之外的宿主信息——这正是评估沙箱边界最直观的练习。延伸阅读深入理解威胁模型与安全设计原则见 gVisor 安全模型系统调用拦截的更多细节见 gVisor 平台指南文件系统代理与 Gofer 的配置overlay、directfs、dentry cache、EROFS见 文件系统指南上手实践见 Docker 快速开始。【免费下载链接】gvisorApplication Kernel for Containers项目地址: https://gitcode.com/GitHub_Trending/gv/gvisor创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表