gVisor 安全模型深度解析:应用内核如何通过纵深防御遏制容器逃逸
2026/9/13 19:40:54 网站建设 项目流程

gVisor 安全模型深度解析:应用内核如何通过纵深防御遏制容器逃逸

【免费下载链接】gvisorApplication Kernel for Containers项目地址: https://gitcode.com/GitHub_Trending/gv/gvisor

gVisor 是面向容器的"应用内核"(Application Kernel for Containers),其安全模型的核心目标是为运行不可信用户态代码的场景提供额外防线,降低内核漏洞被利用的风险。本文以 g3doc/architecture_guide/security.md 为主线,结合仓库内 Sentry 源码(如 runsc/boot/filter/config/config.go、pkg/sentry/platform/systrap/README.md)展开,帮助你理解 gVisor 的威胁模型、攻击面缩减策略与纵深防御工程原则,并掌握其在代码层面的落地方式。

威胁模型:一次漏洞利用的解剖

要理解 gVisor 为什么这样设计,首先要理解它防御的是什么。一次漏洞利用(exploit)通过软件或硬件缺陷来提升权限、获取特权数据或破坏服务。恶意应用与系统其余部分之间所有可能的交互途径(攻击向量)共同构成了攻击面(attack surface)。gVisor 将攻击向量归纳为以下几个常见类别。

系统 API(System API)

操作系统或虚拟机监控器(hypervisor)以系统调用(system calls)和陷阱(traps)的形式暴露一个抽象的系统 API。它可以是像 Linux 那样有文档、稳定的接口,也可以像 Windows 那样被封装在库(如 win32.dll、ntdll.dll)之后。系统 API 包含应用程序与系统交互的全部标准接口,包括由底层系统调用衍生出的高层抽象:系统文件、socket 和命名空间等。

虽然系统 API 是设计上就要暴露给应用的,但内核或 hypervisor 内部的 bug 与竞态条件仍可能经由该 API 被利用。这很大程度上是因为大多数内核与 hypervisor 使用 C 语言编写——C 适合与硬件打交道,却往往容易引入安全问题。一个典型的攻击往往组合了以下手法:

  1. 打开或创建若干文件、socket 或其他描述符;
  2. 传入精心构造的恶意参数、结构体或数据包;
  3. 使用多线程竞争,命中特定的竞态代码路径。

文档中以Dirty COW提权漏洞为例:应用打开/proc下的某个特定文件,或使用特定的ptrace系统调用,然后借助多线程在触碰一页新内存时触发竞态条件,最终获得对系统某页内存的控制权。一旦攻击者在内核中取得额外权限或接触到特权数据,往往还能运用更多技巧获得对系统其余部分的完全控制。

系统 API 实现中的 bug 虽然容易被修复,却也是最常见的漏洞利用形式。这一攻击类别带来的暴露面,正是 gVisor 旨在最小化与控制的目标。

系统 ABI(System ABI)

有些硬件和软件漏洞存在于不属于预期系统 API 的执行路径中,例如硬件或特权系统代码对陷阱、中断等事件做出响应时的隐式动作。文档引用的POPSS缺陷即属此类:只需原生代码执行(无需特定系统调用或文件访问)即可触发,且 Xen hypervisor 同样受影响——说明 hypervisor 对此类向量并非免疫。

侧信道(Side Channels)

硬件侧信道可能被系统上运行的任何代码利用:无论是原生代码、沙箱内代码还是虚拟化代码。不过,许多宿主级缓解措施在沙箱场景下依然有效:例如基于 retpoline 构建的内核可防御部分投机执行攻击(Spectre),帧中毒(frame poisoning)可防御 L1 终端故障(L1TF)攻击。而 hypervisor 在此可能引入额外的复杂性:在一个功能正常的虚拟机(VM)中,没有任何缓解措施能阻止应用利用 L1TF 攻击兄弟超线程上的另一个 VM。

其他向量(Other Vectors)

上述类别远非穷尽列表——gVisor 只聚焦于在操作系统或 hypervisor 内部运行不可信代码这一场景,并不考虑更泛化的对抗者交互方式,例如插入带有恶意文件系统镜像的便携存储设备、构造键盘/触摸输入组合,或用畸形数据包饱和网络设备等。

此外,高层系统本身也可能包含可利用组件。如果宿主机上存在可利用的网络可达服务或其他 API 路径,攻击者甚至无需在容器内提权。沙箱不是安全架构的替代品(A sandbox is not a substitute for a secure architecture)。

设计目标:限制暴露面

gVisor 的首要设计目标是:通过多层防御最小化系统 API 攻击向量,同时仍然提供一个进程模型。这一设计由两条主要安全原则驱动:

  1. 应用与宿主系统 API 的直接交互被 Sentry 拦截——由 Sentry 自己实现系统 API;
  2. Sentry 自身可访问的系统 API 被缩减为更小、更受限的集合

第一条原则最小化了应用直接利用宿主系统 API 的可能性;第二条原则最小化了间接可利用性——即已被利用或有 bug 的 Sentry 被攻击者"链式利用"(chaining an exploit)继续攻击宿主内核的可能。

与虚拟机(VM)的类比与差异

第一条原则与虚拟机的安全基础相似。在 VM 场景中,应用与宿主的交互被替换为与客户操作系统及一组虚拟化硬件设备的交互;这些硬件设备再由虚拟机监控器(VMM)通过宿主系统 API 实现。Sentry 同样通过提供应用必须与之交互的、自己实现的系统 API 来阻止直接交互——应用无法直接为宿主系统 API 构造特定参数或标志,也无法直接操作宿主原语。

值得强调的是,对 Sentry 和 VMM 而言,直接交互虽不可能,间接交互仍然存在。例如,Sentry 中对宿主后备文件(host-backed file)的一次read最终可能触发一次宿主read系统调用(由 Sentry 自己发起,而非透传应用参数)——这与 VM 中读取块设备可能导致 VMM 对后备文件发起相应宿主read类似。

与 VM 的关键区别在于:Sentry 直接基于宿主系统 API 原语实现系统 API,而不是依赖虚拟化硬件和客户操作系统。这带来一组不同的权衡,主要集中在性能、效率与兼容性领域:

  • 由于进出沙箱的切换相对昂贵,客户操作系统通常会"占有"资源。例如在上述例子中,客户 OS 可能把块设备数据读入本地页缓存以避免后续读取,从而获得更好的性能,但可能浪费或复制内存,导致效率较低;
  • Sentry 则选择在运行期间将许多操作延迟委派给宿主,换取更高的效率,但在某些使用场景下性能较低。

沙箱能做什么?

gVisor 沙箱中的应用被允许执行标准容器能做的大多数事情:读写映射进容器的文件、发起网络连接等。即便如此,gVisor 仍会限制某些标准容器可能允许的操作。即使具备相应的 capability,gVisor 沙箱中的用户也只能操纵虚拟化的系统资源(如系统时间、内核设置或文件系统属性),而无法操纵底层宿主系统资源。

沙箱虽为应用虚拟化了大量操作,但其自身与宿主的高层交互被限制在以下集合内:

  1. 使用已连接的 socket 与 Gofer 进程建立通信。Gofer 进程管理容器的文件系统,并在请求时向沙箱提供文件描述符;沙箱可直接读写这些文件描述符。沙箱自身运行在空挂载命名空间中。沙箱还可以进一步调优为拒绝所有对文件系统的访问,此时所有操作都由 Gofer 代表沙箱执行;
  2. 发起一组最小化的宿主系统调用。这些调用不包含创建新 socket(除非启用宿主网络模式)或打开文件(除非启用 directfs);包含的是文件描述符的复制与关闭、同步、定时器与信号管理;
  3. 读写虚拟以太网设备的数据包。若启用了宿主网络(或网络被禁用),则此项非必需。

系统 ABI、侧信道与其他向量的防御边界

对于基于硬件的攻击(系统 ABI 与侧信道类别),gVisor依赖宿主操作系统与平台层进行防御。鉴于这类漏洞的性质,gVisor 本身几乎无法提供额外防御——即使采用硬件虚拟化加速也是如此,因为最终仍由宿主内核或 hypervisor 负责防御来自恶意客户机的攻击(无法保证虚拟化、内存加密等额外硬件措施真能减小攻击面)。

对于资源耗尽与拒绝服务攻击,gVisor 同样依赖宿主资源机制(cgroups)进行防御。网络策略控制应在容器层面应用,以确保正确的网络策略执行。注意:沙箱自身无法修改或配置这些机制,而沙箱本身应使攻击者更难以通过其他手段利用或绕过这些控制。

原则:纵深防御(Defense-in-Depth)

为确保系统达成设计目标,gVisor 开发中采用了几条工程原则:

  1. 没有任何系统调用被直接透传给宿主。每个受支持的调用在 Sentry 中都有独立实现,因此不太可能遭受与宿主相同的漏洞影响。其推论是:应用使用的所有内核特性都需要在 Sentry 内有一份实现
  2. 只实现通用的、普遍的功能。某些文件系统、网络设备或模块可能通过扩展属性、raw socket 或 ioctl 向用户态应用暴露专用功能。由于 Sentry 负责实现完整的系统调用面,这类专用 API 不会被实现或透传;
  3. 暴露给 Sentry 的宿主面被最小化。虽然系统调用面并不小,但它被显式枚举并受控。Sentry 不允许在宿主上打开新文件、创建新 socket 或做其他许多"有趣"的事情。

此外,项目还施加了若干实践性限制,以最小化 Sentry 被利用的风险:

  1. 不安全代码被严格管控。所有 unsafe 代码被隔离在文件名以unsafe.go结尾的文件中,便于验证与审计;没有 unsafe 后缀的文件不得导入 unsafe 包
  2. 禁止 CGo。Sentry 必须是纯 Go 二进制;
  3. 核心包内通常不允许外部导入。仅设置代码中允许少量外部导入。Sentry 内可用的代码被仔细管控,以确保上述规则有效。

最后,gVisor 承认安全是一个过程,保持警觉至关重要。除了安全披露流程外,Sentry 被持续模糊测试(fuzzed)以主动发现潜在的 bug 与竞态,生产崩溃会被记录与分诊,以同样识别出实质性问题。

源码佐证:Sentry 的宿主系统调用面如何被强制执行

上述"最小化 Sentry 宿主面"的承诺在代码中有直接体现。Sentry 进程通过seccomp-bpf 过滤器约束自己对宿主内核的系统调用,相关实现在 runsc/boot/filter 目录:

  • config.go 的包注释明确写道:"Package config defines all syscalls the sandbox is allowed to make to the host"(定义沙箱允许向宿主发起的全部系统调用),这正是"显式枚举并受控"的落地;
  • Options结构体(config.go#L37-L50)记录了影响过滤规则组合的开关:HostNetwork(宿主网络)、HostNetworkRawSockets(宿主 raw socket)、HostFilesystem(宿主文件系统/directfs)、ProfileEnableNVProxy/TPUProxy/RDMAProxy(GPU/TPU/RDMA 设备代理)、CgoEnabledPluginNetwork等;
  • rules()函数(config.go#L140-L176)以allowedSyscalls基础白名单为起点,按需合并hostInetFilters(宿主网络)、hostFilesystemFilters(directfs)、平台自身的SyscallFilters等,最终生成允许规则与DenyNewExecMappings拒绝规则。也就是说,默认配置下 Sentry 在宿主上不能新建 socket、不能打开文件,只有开启对应模式才会放行,与文档描述完全一致;
  • 注意Warnings()(config.go#L84-L118)会为每一项放宽过滤器的选项输出syscall filters less restrictive!警告,提醒运维人员安全边界正在变宽;
  • filter.go#L41 的Install()负责实际安装过滤器,并在debugFilter打开时把违规动作改为Trap以便输出 panic 堆栈(filter.go#L28-L32 的 DEBUG TIP 注释提示:怀疑 Sentry 因 seccomp 违规被杀时,可将其改为true获取崩溃现场)。

系统调用拦截(而非过滤)则由平台层负责。以默认平台 Systrap 为例,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 信号处理器通知 Sentry,Sentry 处理后回调系统线程继续执行——信号帧保存在共享内存区域,使 Sentry 能读写线程状态。注意,这与 ptrace 沙箱的"审核并放行"完全不同:被捕获的调用永远不会继续进入宿主内核完成,而是由 Sentry 解释并处理。

FAQ

这比虚拟机更安全还是更不安全?

VM 的安全性很大程度上取决于宿主内核与用户态支撑代码暴露了什么。例如,宿主内核中的设备模拟代码(如 APIC)或优化(如 vhost)可能比一次简单系统调用更复杂,利用它们同样带来风险;而用户态支撑代码往往未被沙箱化,一旦被利用(虽然罕见)可能获得对系统的无限制访问。

gVisor 的部分平台复用了与 VM 相同的虚拟化硬件以获得更好的系统调用拦截性能,但 gVisor不实现任何设备模拟,而是选择直接使用被沙箱化的宿主系统 API。两种方案都显著缩减了原始攻击面。归根结底,既然 gVisor 也能使用相同的硬件机制,就不应假设"仅仅使用了虚拟化硬件"就让系统更安全或更不安全——就像不能因为某辆车用了整体式车身就断言它安全一样。

这能阻止硬件侧信道吗?

一般来说,gVisor 不提供针对硬件侧信道的保护,尽管它可能使依赖直接访问宿主系统 API 的利用手段更难奏效。要最小化暴露面,应遵循厂商的相关指引,并保持宿主内核与固件及时更新。

这只是个 ptrace 沙箱吗?

不是。"ptrace 沙箱"通常指使用 Linux ptrace 机制检查并授权应用发起的系统调用、强制执行特定策略的软件。这类方案常见两个问题:其一,易受攻击的系统调用可能被沙箱授权——因为应用仍然直接接触部分系统 API;其二,在不禁用多线程的前提下无法避免 TOCTOU(检查时间/使用时间)竞态

在 gVisor 中,使用 ptrace 的平台运作方式不同:被跟踪的 stub 永远不会被允许继续执行进入宿主内核并直接完成一次调用。所有系统调用都由 Sentry 解释并处理,Sentry 将结果寄存器状态回写进被跟踪进程(tracee),然后继续在用户态执行。这与 User-Mode Linux(UML)使用的机制非常相似。

延伸阅读

  • gVisor 安全入门(面向安全研究者的高层介绍):对比 Linux 内核安全原语、虚拟化与 gVisor 三种隔离方案,并给出runsc do的快速验证示例;
  • 平台架构指南:深入了解 Systrap 与 KVM 两种系统调用/缺页拦截机制;
  • 文件系统指南(directfs 章节):了解如何进一步收紧沙箱对文件系统的访问;
  • 若需在本地复现文中所述行为,可参考 Docker 快速开始 将 runsc 注册为容器运行时后自行验证。

【免费下载链接】gvisorApplication Kernel for Containers项目地址: https://gitcode.com/GitHub_Trending/gv/gvisor

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询