try ai
科普
编辑
分享
反馈
  • 对象能力模型

对象能力模型

SciencePedia玻尔百科
核心要点
  • 对象能力模型基于持有不可伪造的令牌(即“能力”)来授予权限,这与依赖请求者环境身份的 ACL 模型有根本区别。
  • 通过将对象的指代与其访问权限融合在一起,该模型优雅地解决了长期存在的“困惑的代理人问题”。
  • 它通过“衰减”机制实现了最小权限原则的实际应用,允许程序从更强大的能力中创建和委托权限更小的能力。
  • 同样的“间接”机制被用来解决不同的挑战,为创建较弱权限(衰减)和收回权限(撤销)提供了优雅的解决方案。

引言

在对安全计算的持续探索中,系统如何决定授予访问权限是最根本的问题。传统的安全模型通常建立在访问控制列表(ACL)之上,其运作方式就像一个警卫根据列表检查身份证——权限基于谁在请求。这种被称为“环境权限”(ambient authority)的方法充满了细微但持久的缺陷。对象能力模型提供了一种截然不同且更为健壮的范式,其中权限不是环境性的,而是被持有的。它基于一个简单而强大的理念:如果你拥有正确的钥匙,你就能打开这把锁并获得访问权限。

本文深入探讨了这一优雅的安全哲学,解释了它如何构建出“设计即安全”(secure by design)而非“约定即安全”(secure by convention)的系统。它解决了传统模型中固有的弱点,最著名的是“困惑的代理人问题”(confused deputy problem),并为构建更安全的软件和硬件提供了一个连贯的框架。您将首先探索定义该模型的核心思想,了解一个不可伪造的令牌,即“能力”(capability),如何同时充当名称和权限。在此之后,您将踏上一段旅程,探索其广泛的应用,从桌面上的图形用户界面,一直到 CPU 的硅芯片,发现这一单一哲学如何统一并保护计算系统的不同部分。

这段旅程始于审视使该模型得以运作的基础原则和机制。

原则与机制

要真正掌握对象能力模型,我们必须回到一个根本性的安全问题:系统应该如何决定授予访问权限?想象一下,你试图进入一栋安保严密的大楼。存在两种经典的方法。第一种方法是,一名警卫站在门口,手持一份授权人员名单。你出示身份证,警卫核对名单,如果你的名字在上面,你就可以进入。你的权限与你是谁绑定。这就是​​访问控制列表(ACL)​​的世界。

在第二种方法中,没有警卫,也没有名单。门上只有一把锁。如果你持有正确的钥匙,你就能打开它。你的权限来自于你持有之物。这个简单而强大的思想正是​​对象能力模型​​的核心。

权限:两种模型的故事

在传统的操作系统中,例如基于 POSIX 的系统,安全性主要围绕第一种模型构建。当一个程序运行时,它像一面旗帜一样携带着其用户的身份——一个用户ID(uiduiduid)和一组用户组成员关系(GGG)。这被称为​​环境权限​​(ambient authority)。每当程序试图访问一个对象,比如打开一个文件时,操作系统(“警卫”)会查看对象的 ACL(“名单”),并检查该程序的环境身份是否赋予了它权限。权限由一个基于“谁在请求?”这个问题的中央决策确定。

对象能力模型则完全颠覆了这一点。在这里,权限不是环境性的,而是被持有的。​​能力​​(capability)是一个不可伪造的令牌,作用如同一把魔法钥匙。它同时做两件事:唯一地指代一个特定对象,并授予访问该对象的一组特定权利。要执行一个操作,程序必须出示相应的能力。操作系统的任务仅仅是验证这把钥匙对这把锁是否有效。持有即证明拥有权限。问题不再是“谁在请求?”,而是“你持有哪把钥匙?”。

这一转变带来了深刻的影响:它融合了​​命名​​和​​保护​​这两个概念。在 ACL 系统中,你用一个像 "/path/to/my/file" 这样的字符串来命名一个文件(命名),然后系统独立地查阅一个列表来决定你是否有权限(保护)。相比之下,能力是一个单一的实体,既是名称又是权限。你不是通过一个可以被操纵的可变字符串来引用对象,而是通过不可伪造的能力本身来引用它。

魔法钥匙:能力的剖析

是什么让这些“钥匙”不可伪造的呢?能力不是程序可以凭空创造出来的东西。它是由操作系统内核管理和保护的一种特殊数据结构。用户进程可以持有它并将其传递给其他进程,但不能篡改它或凭空创建一个新的。在实践中,这种不可伪效性通常是密码学或计算上的。一个能力可以由一个非常大的随机生成数来表示,比如一个 96 位或 128 位的字符串。其可能性的空间是如此之大,以至于攻击者猜中一个有效能力的概率极低——比在银河系中找到一个特定原子还要低。出于所有实际目的,它在计算上是不可能伪造的。

这种设计的美妙之处在于,操作系统的角色变得更简单、更健壮。它的主要工作不再是为每个对象管理复杂的列表和策略,而是转变为确保能力的完整性——即它们不被伪造,被正确传递,并且只用于它们所授予的权利。

摆脱“困惑的代理人”

没有环境权限并不仅仅是一个学术上的区别;它解决了计算领域中最持久、最微妙的安全缺陷之一:​​困惑的代理人问题​​(confused deputy problem)。

想象一个服务器程序,它就像一个强大但天真的“代理人”——比如,档案室的办事员。一个客户打电话来,要求办事员将“文件 #123”的所有者更改为另一个人。作为一名受信任的员工,办事员有权更改任何文件的所有权。然而,客户应该只能重新分配他们自己拥有的文件。办事员现在“困惑”了:他收到了客户的请求(一个文件名),但他是用自己更广泛的环境权限来执行操作。如果客户巧妙地指定了一个他们不拥有的文件名,他们就可以诱骗这个强大的办事员滥用其权限为客户服务。

这在有环境权限的系统中 постоянно发生。像 chown(path, new_uid, new_gid) 这样的 Unix 系统调用就是一个完美的例子。path 由一个可能不受信任的客户提供,但内核使用调用进程(例如,一个以超级用户身份运行的服务器)强大的环境权限来执行请求。

能力以一种优美而简洁的方式解决了这个问题。客户不只是传递文件的名称。要请求所有权变更,客户必须传递一个本身就授予了更改该特定文件所有权的能力。权限不再是办事员的环境权限,而是嵌入在请求本身之中。办事员只是使用它被递交的钥匙。如果客户提供了一把只允许重新分配“文件 #123”的钥匙,办事员在物理上就不可能被诱骗去修改“文件 #456”。混淆是不可能的,因为权限是具体的、明确的,而不是笼统的、环境的。

衰减的艺术:制造更弱的钥匙

当我们考虑​​最小权限原则​​——即任何程序都应以完成其工作所需的最低权限集运行——时,能力模型的真正威力才得以显现。能力通过一个称为​​衰减​​(attenuation)的过程使这一原则成为可触摸的现实。

假设你拥有一个对文件对象的强大能力——一个“写入”能力,允许你修改它的任何部分。你希望将一个任务委托给一个辅助程序,但你只想让它能够添加数据到文件末尾,而不是覆盖你现有的工作。你想将你强大的“写入”钥匙转变为一个较弱的“仅追加”钥匙。

在能力系统中,你不需要请求中央管理员来创建新规则。你可以自己通过​​对象间接​​(object indirection)来完成。你创建一个新的、简单的对象,称为​​代理​​(proxy)或​​包装器​​(wrapper)。这个包装器对象做两件事:

  1. 它秘密地持有着你强大的“写入”能力。
  2. 它只向外界暴露一个方法:append(data)。当这个方法被调用时,包装器的内部逻辑会使用其秘密持有的强大能力来对真实文件执行追加操作。

然后,你将一个指向包装器对象的能力,而不是指向原始文件的能力,交给辅助程序。辅助程序现在持有的钥匙只允许它进行追加操作。它无法访问隐藏在包装器内部的更强大的钥匙。你已经成功地衰减了权限,从一个更具特权的钥匙创建了一个权限更低的钥匙,完美地遵守了最小权限原则。 这与困扰现代计算的“以管理员身份运行”提示相去甚远,后者是一种粗糙且危险的环境权限形式。基于能力的方法允许从一开始就授予细粒度的、特定的权利。

一个由相连对象构成的宇宙

如果我们把视野拉远,我们可以将整个能力系统想象成一个巨大而动态的图。每个对象和每个进程都是一个节点。能力是从持有它的进程指向它所指代对象的一条有向边。一个进程的世界——它可能与之交互的一切——由其​​能力邻域​​(capability neighborhood)定义,即它通过跟随从其出发的边所能到达的所有节点的集合。

在这种视角下,​​保护​​和​​进程间通信(IPC)​​之间的区别也消融了。要与另一个进程通信(IPC),你必须首先持有指向它的能力(保护)。发送消息这一行为本身就是一种权利的行使。此外,授权(delegation)——即授予权利——仅仅是在消息中传递一个能力的行为,这会动态地向系统图中添加一条新的边。保护不再是表格中一组静态的规则;它是这个连接图的活的、不断演化的拓扑结构。

变革的挑战:时间与撤销

这个优雅的模型有一个出了名的棘手问题:如果你给出了一个钥匙,后来又想把它收回怎么办?这就是​​撤销​​(revocation)问题。

在 ACL 系统中,撤销是微不足道的:你只需从对象的访问列表中删除用户的名字即可。更改是即时的。 但对于我们讨论过的简单“魔法钥匙”,一旦钥匙被分发出去,它就可以被复制并沿着一连串的进程传递下去。原始所有者失去了控制。试图追查每一个副本,在一般情况下是不可能的。这意味着强撤销(strong revocation)——使一个能力及其所有派生副本失效——在这种简单的模型下是不可能的。

这会带来现实世界的后果。如果一个用户的角色从“讲师”变为“学生”,他们之前拥有的用于研究资料库的强大能力可能在过期前一直有效,从而产生一个“残留窗口”,在此期间他们的访问权限与其真实角色不同步。

但同样,该模型自身就蕴含了解决方案的种子,而且它正是我们之前看到的那个优雅模式:​​间接​​(indirection)。

你不是直接分发一把通往宝箱的钥匙,而是分发一把通往一个特殊的​​撤销者对象​​(revoker object)的钥匙。这个撤销者对象则持有通往宝藏的钥匙。要访问宝藏,进程必须先向撤销者出示它的钥匙,然后撤销者再使用其内部的钥匙。要为所有人撤销访问权限,你只需命令撤销者对象丢弃其内部钥匙,或翻转一个内部的“无效”位。瞬间,所有指向该撤销者的钥匙都变得无用。尽管在高度并发的系统中,要使这一机制正确工作的工程细节很复杂——需要仔细处理竞争条件和内存管理——但其核心原则却异常简单。

因此,从一把钥匙的简单理念出发,一个丰富而统一的安全理论应运而生。对象间接这一相同的基本机制,为权限衰减和权限撤销这两个问题都提供了优雅的解决方案,展示了对象能力模型深刻的连贯性和力量。

应用与跨学科联系

一旦你真正掌握了对象能力的哲学——即那个简单而深刻的,将对象的指代与使用它的权限融合在一起的思想——你就会开始在各处看到它的影子。它不仅仅是局限于学术论文的理论奇观,而是一个强大而实用的透镜,通过它我们可以理解、设计和构建更安全、更健壮的系统,贯穿计算的每一层。它提供了一条道路,帮助我们摆脱那些仅靠约定或运气来保障安全的系统,走向那些通过设计来保证健壮性的系统。

让我们踏上一段旅程,从我们熟悉的桌面文件和窗口世界,深入到操作系统的核心,再到底层硬件的硅基,甚至延伸到庞大的云基础设施和我们用来构建软件的工具。在每一步,我们都将看到,能力的这种宁静的纪律如何为原本复杂而危险的问题带来清晰、安全和某种优雅。

驯服用户世界的“狂野西部”

也许,能力力量最经典的例证在于解决“困惑的代理人”问题。想象一个安全的日志记录服务,它是一个勤勉的程序,唯一的工作就是向日志文件(例如路径为 /var/log/security.log)追加记录。在采用访问控制列表(ACL)的传统系统中,日志记录器进程被授予写入该路径的权限。但如果一个攻击者重命名了真实的日志文件,并在其位置创建了一个新的恶意文件呢?日志记录器在下次醒来准备写入时,会愉快地解析路径 /var/log/security.log,找到攻击者的文件,并尽职地将敏感信息追加到其中。日志记录器成了一个“困惑的代理人”,被诱骗滥用了其合法权限。

能力系统消除了这种模糊性。日志记录器得到的不是一个文件的普通名称,而是一个不可伪造的能力——一个指向唯一真实日志文件对象的直接、私有句柄。文件系统可能被攻击者搅得天翻地覆,但日志记录器的能力仍然与其指定的对象绑定。它不会被混淆,因为它的权限与事物本身绑定,而不是与一个易错的、环境性的事物名称绑定。

同样的原则也为我们日常交互的图形用户界面(GUI)带来了秩序。把屏幕上的一个窗口看作一个对象。在基于能力的 GUI 中,一个应用程序被授予其主窗口的一个能力。这个令牌是它进行绘制、调整大小和接收事件的权限。如果这个应用程序想要显示一个视频,它可以创建一个子进程——一个视频播放器——并向其委托一个新的、衰减过的能力。这个新能力可能授予在主窗口的一个矩形子区域内write_pixels的权利,但没有resize窗口或从旁边的密码字段读取用户输入的权利。撤销同样优雅。如果视频播放器行为不端,主应用程序可以立即撤销该窗口能力所有持有者的write_pixels权利,从而有效地将播放器区域清空,而不影响系统的任何其他部分。

即使是我们习以为常的简单功能——不起眼的剪贴板,在传统系统中也充满了危险。当你复制敏感数据,如密码或银行账号时,它被放置在一个全局空间中。任何恰好处于前台的应用程序都可能窥探到它。这是一个典型的“环境权限”例子——读取的权利是由环境上下文(处于前台)授予的,而不是由特定的用户意图授予的。基于能力的剪贴板将权限与意图对齐。当你复制时,系统会为数据创建一个能力,但将其隔离保存。只有当你明确在目标应用程序中执行“粘贴”手势时,系统才会将该能力交付给那一个应用程序,并且仅此一个。你可能点击过的中间应用程序,或在后台窥探的恶意应用程序,都一无所获。

机器中的幽灵:统一核心操作系统机制

能力模型的影响远不止于用户界面。它可以统一那些表面上看起来完全不相关的概念。思考一下 CPU 调度的基本任务。操作系统如何决定哪个进程运行,以及运行多长时间?我们通常认为这是一个资源管理问题。

但如果我们将其重新定义为一个安全问题呢?想象一下,“在接下来的 10 毫秒内执行的权利”本身就是一个对象。当调度器选择一个进程来运行时,它向该进程授予一个针对这个短暂时间片对象的能力。进程开始运行。当 10 毫秒后硬件定时器中断触发时,这个时间片对象在概念上不复存在,进程持有的能力变成了一个对已逝时代的无用令牌。抢占,即强制停止一个进程,不再是一个特殊动作;它仅仅是因过期而发生的撤销。这个惊人简单的模型确保了公平性和可用性,防止任何一个进程独占 CPU,其所用的逻辑与我们用来保护日志文件的逻辑完全相同。

这种思维方式也彻底改变了我们构建安全网络服务的方式。一类常见且破坏性巨大的漏洞源于远程过程调用(RPC)服务器反序列化来自客户端的不可信数据。攻击者可以精心构造一个恶意字节流,当其被反序列化时,会在服务器内存中创建一张对象网络,诱使服务器执行代码,即所谓的“小工具链”(gadget chain)。这种攻击之所以有效,是因为服务器进程通常以大量的环境权限运行——能够打开任何文件,或连接到任何网络地址。能力模型提供了双重防御。首先,反序列化器本身被限制为只能创建没有关联行为的惰性数据对象。其次,也是更重要的一点,服务器被剥夺了所有环境权限。如果客户希望服务器代其读取文件,客户必须在其 RPC 请求中传递一个针对该特定文件的能力。服务器本身没有任何权力;它仅仅是客户的代理,只挥舞着客户明确委托给它的权限。

从基岩到云端:硬件与现代基础设施

基于能力的设计原则是如此基础,以至于它们在运行我们软件的硬件本身中找到了最终的表达。思考一下网卡或图形处理器设备驱动程序的巨大权力。这些驱动程序通常使用直接内存访问(DMA)将数据直接写入内存,为追求性能而绕过 CPU。驱动程序中的一个 bug 就可能使其覆写内核本身,导致整个系统被攻破。

在这里,输入输出内存管理单元(IOMMU),一种转换设备内存地址的硬件,可以充当强制执行能力的引用监视器。为了让驱动程序执行 DMA 操作,内核可以要求它出示两种能力:一种指代它控制的设备,另一种指代它希望访问的特定内存缓冲区,并附带读/写权限。然后,内核对 IOMMU 进行编程以强制执行这种绑定。驱动程序现在被关在了笼子里。它有权访问其指定的缓冲区,仅此而已,从而将一个极其强大的组件转变为一个安全、可管理的组件。

展望未来,计算机架构师们正在将这种模型直接构建到 CPU 中。通过硬件内存标记,内存中的每个字都伴随着一个小标签。一个能力不仅仅是一个软件结构,而是一种也包含标签的特殊指针。要访问内存,指针的标签必须与内存的标签匹配。撤销变得异常高效:要使指向某个内存区域的所有能力失效,操作系统只需更改该内存中的标签。该区域所有现存的软件能力都会立即失效,而无需操作系统去逐一寻找并删除它们。这种方法将能力模型的安全性与硅片的原始速度结合起来。

硬件和软件安全原则的这种紧密耦合使我们能够解决非常现代的问题。在云环境中,我们如何运行一个容器,并给予它恰好足够的权限来管理自己的网络,但又不多一分?传统方法可能会授予容器一个强大的环境特权,如 Linux 的 CAP_NET_ADMIN,这就像给了它一把所有网络配置的主钥匙。而能力方法则是锻造一把高度特定的钥匙。容器运行时可以创建一个特殊的、由内核过滤的通信通道,该通道只允许与“在接口 eth0 上设置 IP 地址”或“启动接口 eth0”相对应的消息通过。这个通道的文件描述符——一个能力——被传递给容器。容器没有被授予任何环境网络特权,但它可以使用这一个句柄来执行其特定的、被授权的任务。我们已经将一个宽泛、危险的特权映射到了一个细粒度、安全的对象能力上,从而实现了安全的多租户。

构建构建者:保障工具链安全

能力思维的影响并不止于操作系统或硬件。它延伸到我们用以创造软件的工具本身。例如,编译器是一个高度特权的程序。它读取源代码文件并写入可执行二进制文件。许多现代编译器支持插件或宏来扩展其功能,这些插件或宏在编译过程中执行。

这带来了一个微妙但严重的风险。一个有缺陷或恶意的宏可能利用“卫生违规”(hygiene violation)从编译器自身环境中捕获一个标识符,从而获得其在系统上任何地方读写文件的环境权限。解决方案再次是应用最小权限原则。一个安全的编译器会在各自独立的、零环境权限的沙箱中执行每个插件。如果一个插件需要读取文件,它必须在其清单(manifest)中声明这一需求。构建系统在获得用户批准后,授予该插件针对那一个文件的能力,仅此而已。通过设计,权限提升的可能性被消除了。

至此,我们回到了起点,回到了文件系统本身的结构。操作系统如何高效地保证其目录结构保持为有向无环图(DAG),防止用户创建一个形成循环的链接?在每次链接操作时遍历图以检查祖先关系,其成本高得令人望而却步。能力模型提供了一个极为优雅的解决方案。如果每个目录在创建时都被赋予一个数值“等级”,该等级被不可变地封存在其能力中,那么内核可以强制执行一个简单的局部规则:仅当父目录的等级严格小于子目录的等级时,才允许从父目录到子目录建立链接。这个单一的局部检查,由于能力的不可伪造性而成为可能,足以保证无环性这一全局属性。

从保护一个日志文件到组织其自身结构,从管理屏幕上的像素到管理 CPU 上的周期,从限制设备驱动程序到沙箱化编译器插件,对象能力模型提供了一个单一、统一的哲学。它呼吁我们明确地对待权限,审慎而节制地授予权力,并构建出安全不是事后诸葛,而是源于有原则且优美设计的自然结果的系统。