try ai
科普
编辑
分享
反馈
  • seccomp

seccomp

SciencePedia玻尔百科
关键要点
  • seccomp 是一项 Linux 内核安全特性,它限制进程可以进行的系统调用,强制执行最小权限原则。
  • 它是现代容器和云安全的基础,为共享内核创建了系统调用防火墙,以缩小其攻击面。
  • seccomp 策略是持久的,在 fork 和 execve 调用后依然存在,并能启用如权限分离等安全架构模式。
  • 除了防止未经授权的操作,seccomp 还可以帮助缓解侧信道攻击等复杂威胁,并强制执行像 W^X 这样的内存安全策略。

引言

在现代操作系统的复杂世界中,用户应用程序与内核之间的边界是一条至关重要的安全前线。应用程序执行的每一个动作,从打开文件到通过网络发送数据,都由“系统调用”——一个向强大、特权的内核发出的请求——来中介。虽然这数百个可用的系统调用对功能至关重要,但它们也构成了一个巨大的攻击面,其中任何一个缺陷都可能危及整个系统。本文通过探讨 ​​seccomp​​ (安全计算模式) 来应对这一根本性的安全挑战,这是一种强大的 Linux 内核机制,旨在围绕这些系统调用构建防火墙。

本次探索的结构旨在全面理解 seccomp 在现代安全中的作用。在第一章 ​​“原理与机制”​​ 中,我们将深入剖析 seccomp 在底层的运作方式,审视其过滤逻辑、白名单概念及其精妙但重要的局限性。随后,​​“应用与跨学科联系”​​ 章节将展示这个底层工具如何成为高级安全架构的基石,从保护云中的容器到启用防御性编程模式,乃至缓解微妙的侧信道攻击。

原理与机制

要真正领会 ​​seccomp​​ (安全计算模式) 的精妙与强大,我们必须首先深入到现代操作系统的核心,并提出一个基本问题:一个在其隔离世界中运行的简单程序,究竟是如何做任何事情的?它如何打开一个文件,在屏幕上显示一个词,或者通过互联网发送一条消息?答案,简而言之,就是​​系统调用​​。

内核的网关:系统调用

想象一下,操作系统的内核是一个全能、重兵把守的实体,一个仁慈的守护者,管理着计算机所有宝贵的资源——文件、网络连接、内存。你的程序以及所有其他程序,都生活在这个堡垒之外,一个被称为​​用户空间​​的低权限领域。一个程序若要执行任何影响自身之外世界的有意义的操作,它不能简单地伸手去拿它需要的东西。相反,它必须礼貌地向内核请求许可。这个正式的请求就是一个​​系统调用​​,或称​​syscall​​。

一个想要写入文件的程序不会直接命令硬盘;它会发出一个 write 系统调用,并附上传递的数据和目标文件的句柄。内核接收这个请求,验证它,代表程序执行操作,然后报告结果。这个机制之所以优美,是因为它在不可信的用户程序世界和可信的内核圣地之间,创建了一个单一、狭窄且定义明确的网关。

然而,这个网关也是一把双刃剑。像 Linux 这样的现代内核提供了数百种不同的系统调用,每一种都有自己的参数集和复杂的行为。这个庞大的入口点集合构成了内核的​​攻击面​​。如果内核处理其中任何一个 syscall 的代码中存在一个 bug,那么诱使程序发出恶意请求的攻击者就可能导致系统崩溃或夺取控制权。在云计算和容器时代,这一点尤其危险,因为一个宿主内核可能由数十个隔离的工作负载共享。共享内核中的一个漏洞会威胁到每一个人。那么,我们如何才能缩小这个攻击面呢?

为系统调用构建防火墙

这就是 seccomp 登场的时刻。它是一种安全机制,允许一个进程为其自身的系统调用构建个人防火墙。在一个程序开始其主要工作之前,它可以请求内核安装一个​​过滤器​​。从那一刻起,每当该程序尝试进行系统调用时,内核都会首先停下来,并查阅这个过滤器。

过滤器本身是一个小程序,是一套规则,它检查传入的系统调用——其唯一的标识号及其参数——并做出决定。根据过滤器的逻辑,内核可以:

  • ​​ALLOW​​ (允许) 调用继续进行。
  • ​​DENY​​ (拒绝) 调用,立即向程序返回一个错误,而根本不运行 syscall 的代码。
  • ​​TRAP​​ (陷阱) 调用,向进程发送一个信号,让它知道一个有趣的事件已经发生。
  • ​​KILL​​ (终止) 进程。

使用 seccomp 最强大和最常见的方式是建立一个​​白名单​​。这体现了​​最小权限原则​​,这是安全设计的一个核心信条。我们不是试图列出所有危险的 syscall 来阻止(即黑名单),而是从默认拒绝一切开始。然后,我们 meticulously 地构建一个列表,其中只包含我们程序绝对需要运行的 syscall,并明确允许它们。对于一个简单的 Web 服务器,这个列表可能包括用于网络的 syscall(accept、read、write)和内存管理的 syscall(mmap),但肯定不会包括 reboot 或 mount。通过这样做,我们极大地将内核的可达攻击面缩小到我们所允许的少数几个 syscall。如果攻击者攻破了我们的 Web 服务器应用程序,他们会发现自己身处一个严密的数字牢笼中,无法进行任何不在我们预先批准列表上的系统调用。

过滤器能看到什么,不能看到什么

理解 seccomp 过滤器并非无所不知是至关重要的。它在系统调用的原始、底层边界上操作。它检查的参数仅仅是数字:syscall 的 ID 是一个数字,文件句柄是数字,内存地址也是数字。这导致了一些微妙但深刻的局限性。

考虑一个程序,它继承了一个位于文件描述符 3 上的网络连接。如果我们的 seccomp 过滤器允许 write syscall,那么夺取了程序控制权的攻击者可以简单地发出 write(3, "secret_data", ...) 来窃取信息。seccomp 过滤器看到数字 3,但它本身并不知道这个句柄指向一个网络套接字;它很可能只是一个无害的日志文件。这说明 seccomp 并非万能灵药;其有效性取决于​​环境加固​​——在激活过滤器之前,清理进程的环境,例如关闭不需要的文件描述符。

当我们考虑与其他内核子系统的交互时,这些局限性变得更加清晰。seccomp 过滤的是由沙箱化进程发出的系统调用。例如,它不控制进程可以在用户空间运行哪些 CPU 指令。如果一个硬件特性允许用户空间指令修改安全设置,seccomp 对此无能为力。

此外,另一个强大的内核特性 ​​ptrace​​ (进程跟踪),允许一个进程(跟踪者)观察和控制另一个进程(被跟踪者),在每次系统调用时将其停止。在某些内核版本中,一个特权跟踪者可以附加到一个受 seccomp 沙箱保护的进程上,并在 syscall 发生的那一刻,在 seccomp 过滤器有机会看到请求之前修改它。这实际上完全绕过了沙箱。这是一个优美而又可怕的例子,说明了为什么内核安全如此困难:系统自身的特性有时可以被用来相互破坏。解决方案要求内核本身来调解这种交互,对谁可以跟踪一个启用了 seccomp 的进程施加严格的限制。seccomp 不是一座孤岛;它是由相互关联的安全特性组成的复杂生态系统的一部分。

终身有效的策略

也许 seccomp 最优雅的特性之一是其持久性。一旦 seccomp 过滤器安装在一个进程上,它就是​​单调的​​:可以变得更严格,但绝不能放宽。这种限制会代代相传。

当一个进程使用 fork 系统调用创建子进程时,该子进程会继承其父进程 seccomp 过滤器的完美副本。更引人注目的是,该过滤器在 execve 系统调用后也会持续存在。execve 是一个进程用一个新程序完全替换其当前程序的机制。虽然在此转换过程中进程的许多属性会被重置为默认值,但 seccomp 沙箱仍然牢固地保留在原位。

这是一个极其重要的安全特性。一个启动程序可以设置一个限制性的 seccomp 沙箱,然后 execve 一个不受信任的应用程序。即使攻击者完全攻破了该应用程序,并利用漏洞 execve 了一个不同的程序——比如一个命令 shell——那个 shell 醒来后会发现自己被困在完全相同的 seccomp 沙箱中。安全策略一旦建立,就会伴随进程的整个生命周期,无论它在执行什么代码。

过滤器的艺术

在现实世界中应用 seccomp 是一门艺术,需要在安全性与兼容性之间取得平衡。如果你阻止了一个程序合法需要的 syscall,程序就会崩溃。挑战在于现代软件极其复杂。一个程序甚至可能不知道它正在使用哪些 syscall,因为它们通常深藏在标准库中。

GNU C 库 (glibc),作为大多数 Linux 系统的标准库,是这种微妙平衡的一个绝佳例子。为了在不同的内核版本之间保持兼容性,glibc 有时会包含回退逻辑。例如,如果它尝试使用像 openat2 这样的新潮 syscall 并得到错误 ENOSYS(“功能未实现”),它会正确地推断出它正在一个较旧的内核上运行,并优雅地回退到使用较旧的 openat syscall。

现在,想象我们部署一个 seccomp 过滤器,为了安全起见,通过返回错误 EPERM(“操作不允许”)来拒绝 openat2。当 glibc 看到 EPERM 时,它不会假设内核是旧的;它会假设有一个安全策略在主动阻止该操作,所以它放弃并报告错误。应用程序崩溃了!巧妙的解决方案是设计 seccomp 过滤器,使其通过返回 ENOSYS 来拒绝 openat2。这会欺骗 glibc 触发其安全的内置回退路径,让应用程序正常工作,同时仍然阻止它使用那个更新的、未经审计的 syscall。

对于那些有时必要但允许又过于危险的操作,seccomp 提供了 TRAP 动作。这允许进行​​代理 (brokering)​​,一种在现代 Web 浏览器沙箱中大量使用的技术。当沙箱化的渲染进程需要打开一个文件时,其 seccomp 过滤器会捕获 open syscall。一个更特权的代理进程会收到通知,然后可以在完整的上下文中检查该请求——“这个文件是网页的一部分,还是用户的私人密码文件?”——之后再做出高层决策,并将一个安全的句柄传回给渲染器。

最终,seccomp 改变了安全监控的性质。在一个没有沙箱的系统中,成千上万的事件中任何一个都可能是可疑的。但在一个严格沙箱化的进程中,过滤器使得被禁止的操作不仅困难,而且在计算上不可能。仅仅是尝试执行一个被禁止的 syscall,就成了一个极高保真度的信号,表明进程已被攻破,从而实现了简单而有效的入侵检测。seccomp 不仅仅是防止攻击;它创造了一个环境,在这个环境中,恶意行为在预期的、被允许的操作的安静背景下,显得格外醒目。

应用与跨学科联系

在我们之前的讨论中,我们深入探究了 seccomp 的内部工作原理,将其理解为一种内核强制的机制,用于审查和过滤系统调用——这正是应用程序与操作系统核心对话所使用的语言。我们视其为一个精确的工具,一个守卫在计算机系统最关键边界的看门人。但一个工具的趣味性取决于我们能用它来建造什么。现在,我们踏上征程,去看看这个简单而优雅的原则如何发展成为现代计算的基石,使我们能够建造数字堡垒,设计本质上更安全的软件,甚至抵御我们系统中信息泄露的幽灵。

数字堡垒:保护容器与云

想象一下建造一座高安全性的公寓楼。你肯定会给每位住户一间带锁的房间(​​命名空间​​),这样他们就看不到邻居的物品。你还会给他们水电表和配给卡(​​控制组​​,即 cgroup),以确保没有单个住户能用完大楼所有的水或电。但大楼提供的服务呢?管道、电线、邮件服务?如果一个住户可以要求大楼管理员将管道改道至邻居的公寓,那么锁住的门就没多大意义了。

这正是 seccomp 在容器世界中发挥作用的地方。容器是这些隔离技术的集合。命名空间给容器化的应用程序一种拥有自己私有机器的错觉,而 cgroup 则阻止它垄断 CPU 和内存等资源。但正是 seccomp 提供了关键的规则手册,规定了应用程序被允许请求底层共享内核做什么。它确保了一个只需要处理网络连接和读取文件的 Web 服务器进程,不能突然发出一个系统调用来重新格式化磁盘驱动器或挂载新的文件系统。

这种分层防御是当今云计算基础设施的基石。每当你使用在云中运行代码的服务时,你几乎肯定处于一个由这三种协同机制保护的气泡中。Seccomp 在内核的前门充当“最小权限”的不可绕过的执行者,极大地缩小了暴露给每个应用程序的攻击面。即使攻击者在应用程序中发现了一个缺陷,他们造成危害的能力也受到严重限制,因为他们能说出的“作恶词汇”——可用的系统调用——已被削减到最低限度。

当然,这提出了一个非常实际的问题:谁来编写这本规则手册?过于严格的策略会导致合法的应用程序崩溃,而过于宽松的策略则提供了一种虚假的安全感。答案在于静态分析和动态分析的美妙结合。安全架构师可以像侦探一样,结合两种方法:阅读应用程序的蓝图(静态分析代码以预测它可能需要哪些 syscall)和在测试轨道上观察它的工作(动态分析或跟踪,以查看它在正常操作下实际使用哪些 syscall)。通过仔细结合这些信息来源,可以自动生成一个为每个应用程序的独特需求量身定制的 seccomp 配置文件,从而创建一个既安全又实用的策略。这种“学习”正确策略的过程是一个活跃的研究领域,旨在使强大的安全性成为我们软件管道中一个自动的、内置的特性。

防御性编程的艺术:从头构建安全的应用

虽然 seccomp 是隔离整个应用程序的强大工具,但其影响更深,延伸至软件本身的架构中。它鼓励并启用了一种称为​​权限分离​​的设计哲学。

考虑构建一个网络监控工具,即“网络嗅探器”的任务。此应用程序需要高权限来打开一个特殊的网络套接字,并监听接口上的所有流量。然而,其大部分工作是解析这些流量——一个复杂、易于出错的任务,其中一个格式错误的包就可能触发漏洞。单体设计将意味着整个应用程序,包括有风险的解析代码,都以高权限运行。这里的任何妥协都将是灾难性的。

一个更优雅的设计,得益于 seccomp 和其他操作系统原语,是将应用程序拆分为两个协作进程。首先,一个微小、简单且易于验证的“监督者”进程以必要的权限启动。它唯一的工作就是打开网络套接字。然后,它将这个打开的套接字的文件描述符——像一把钥匙——传递给第二个“工作者”进程。紧接着,工作者进程将自己锁定。它使用 seccomp 丢弃执行任何特权系统调用的能力,只保留其任务所需的最低限度:从它被给予的套接字中读取数据,并将其分析结果写入标准输出。这个包含所有复杂和有风险的解析逻辑的工作者,现在在一个严密的沙箱中运行。即使它被攻破,攻击者也无处可去;他们被困在一个无法提升权限或与更广泛系统交互的牢房里。这种分离权限和沙箱化不可信组件的模式是安全设计的标志,被用于从 Web 浏览器到安全外壳 (SSH) 守护进程等关键软件中。

同样这种“纵深防御”的心态可以应用于加固那些必须与不可信世界交互的单个程序。想象一个 DHCP 客户端,这是你计算机上的一个简单工具,其工作是从本地网络上的服务器获取网络配置。历史上,这些客户端一直是漏洞的来源,因为它们经常从网络获取字符串并用它们来运行配置脚本——如果处理不极其小心,这就是命令注入的温床。

一种现代、安全的方法是将这个脚本的执行包裹在多层保护中。客户端会避免调用 shell 解释器,而是通过 execve 系统调用直接调用脚本,这严格地将要运行的程序与其数据参数分开。在此之前,它会设置一个名为 PR_SET_NO_NEW_PRIVS 的特殊内核标志,该标志永久阻止子进程获得任何新权限。作为最后、决定性的一层,它会应用一个严格的 seccomp 过滤器,拒绝脚本访问任何非其工作绝对必需的系统调用,明确阻止像 fork 或 execve 这样的调用,以防止脚本衍生其他程序。Seccomp 成为最后的防线,确保即使其他防御措施失败,潜在的损害也能被控制住。

从堡垒到全景监狱:驯服一类新型威胁

到目前为止,我们看到的 seccomp 的应用都涉及防止直接、未经授权的操作。但系统调用中介的原则是如此强大,以至于它也可以用来对抗一类更虚无缥缈的威胁:侧信道攻击和隐蔽信道攻击。这些攻击中,信息不是被直接窃取,而是通过计算中微妙、可观察到的副作用泄露出去。

想象同一台主机上的两个隔离的容器。它们不能直接相互通信。然而,一个恶意的“发送者”容器可以尝试通过操纵一个共享的、隐藏的资源来向“接收者”容器发送信号。例如,发送者可以通过读取一个特定文件来编码一个“1”,确保它在共享的操作系统页面缓存中,并通过将其逐出缓存来编码一个“0”。接收者随后可以计时自己读取同一个文件的时间。快速读取意味着缓存命中(一个“1”),而慢速读取意味着缓存未命中(一个“0”)。一个隐蔽信道就此诞生。另一个巧妙的技术是,发送者使用 mprotect 系统调用在可写和只读之间切换一个共享内存页。当接收者尝试写入时,只读页会导致轻微的延迟,因为内核需要处理页面错误。这种时间差可以用来传输数据。

seccomp 如何提供帮助?它提供了两种绝妙的对策。首先,在 mprotect 信道的情况下,一个 seccomp 策略可以简单地完全拒绝使用 mprotect 系统调用,从而彻底切断通信信道。用于敲出代码的锤子被拿走了。其次,即使对于不依赖特定 syscall 的信道,seccomp 仍然可以成为一种威慑。为每个系统调用处理 seccomp 过滤器的行为本身就增加了一点计算开销和时间“抖动”。这种噪音可以扰乱信道所依赖的精细时序信号,有效地降低其带宽并使其更难可靠地使用。

除了侧信道,seccomp 的细粒度过滤可以在复杂操作上强制执行关键的安全策略。在现代机器学习中,工作负载经常使用巨大的共享内存区域进行高性能数据交换。一个关键的安全原则是​​写异或执行​​ (W^X),即内存区域不应同时既可写又可执行。如果可以,那么找到方法写入该内存的攻击者就可以简单地注入自己的代码然后执行它。Seccomp 可以在 syscall 边界强制执行此策略。通过过滤对 mmap 和 mprotect 的调用,它可以检查请求的内存权限,并拒绝任何会创建同时带有 PROT_WRITE 和 PROT_EXEC 标志的映射的请求,从而有效地防止攻击者将共享数据缓冲区变成攻击的发射台。

超越 Linux:中介访问的普适原则

也许 seccomp 重要性的最深刻证明是,其基本原则并不仅限于 Linux 内核。考虑一个 ​​unikernel​​,这是一种奇特的操作系统,其中应用程序和内核被编译成一个单一的程序,在单一地址空间和特权级别中运行。在这个世界里,没有传统的用户-内核边界,也没有硬件意义上的系统调用陷阱。

然而,安全地沙箱化第三方代码(如库)的需求依然存在。这如何实现?解决方案是在软件中重新创造 seccomp 的原则。可以设计“调用门”——定义明确的函数入口点——沙箱化的库必须使用它们来请求特权操作。在执行操作之前,这个门可以调用一个过滤器,很像 seccomp 的 BPF 程序,以对照白名单检查请求。其哲学是相同的:通过一个可以强制执行策略的关节点来中介对强大能力的访问。这表明,系统调用过滤不仅仅是一个 Linux 特性,而是安全系统设计中的一个基本模式。

最终,这些多样化的应用在现实世界的系统中汇集在一起。例如,一个为学生编程作业评分的在线平台,就是这些挑战的一个缩影。它必须安全地运行不受信任的代码,提供足够的功能以使其工作,同时不允许滥用。解决方案涉及我们讨论过的一系列技术的协同作用:在容器中运行每个提交,使用自动生成的 seccomp 配置文件强制执行最小权限,丢弃所有不需要的能力,并使用审计系统记录任何企图违规的行为以供取证分析。

从云到我们的编译器,从保护守护进程到挫败侧信道,seccomp 证明了自己不仅仅是一个过滤器。它是复杂软件世界中信任的基本构建块,一个简单而强大的理念,让我们能够在沙地上划出界线,并以内核的权威,确保它们永不被逾越。