第 6 章 TrustedBSD MAC 框架
6.1. MAC 文档版权
本文档由 Chris Costello 在 Safeport Network Services 和 Network Associates Laboratories(Network Associates, Inc. 的安全研究部)为 FreeBSD 项目开发,在 DARPA/SPAWAR 合同 N66001-01-C-8035(“CBOSS”)下,作为 DARPA CHATS 研究项目的一部分。
允许在源代码(SGML DocBook)和“已编译”形式(SGML、HTML、PDF、PostScript、RTF 等)中进行再分发和使用,无论是否修改,只要满足以下条件:
源代码(SGML DocBook)再分发必须保留上述版权声明、此条件列表以及以下免责声明,作为文件的前几行,并且不做修改。
已编译形式的再分发(转换为其他 DTD,转换为 PDF、PostScript、RTF 和其他格式)必须在文档和/或随附的其他材料中复制上述版权声明、此条件列表和以下免责声明。
重要
本文档由 NETWORKS ASSOCIATES TECHNOLOGY, INC “按原样”提供,且不作任何明示或暗示的担保,包括但不限于对适销性和特定用途适用性的默示担保。在任何情况下,NETWORKS ASSOCIATES TECHNOLOGY, INC 不对因使用本文档而导致的任何直接、间接、附带、特殊、惩罚性或后果性损害负责(包括但不限于:替代商品或服务的采购;使用、数据或利润的丧失;或业务中断),无论基于合同、严格责任或侵权(包括疏忽或其他)理论,即使已被告知可能发生此类损害。
6.2. 概述
FreeBSD 包括对几种强制访问控制(MAC)策略的实验性支持,并提供了一个内核安全扩展框架——TrustedBSD MAC 框架。MAC 框架是一个可插拔的访问控制框架,允许新的安全策略轻松地链接到内核中,可以在启动时加载,也可以在运行时动态加载。该框架提供了多种功能,使得实现新的安全策略变得更容易,包括能够轻松地将安全标签(如机密性信息)附加到系统对象上。
本章介绍了 MAC 策略框架,并提供了一个示例 MAC 策略模块的文档。
6.3. 介绍
TrustedBSD MAC 框架提供了一种机制,允许在编译时或运行时扩展内核访问控制模型。新的系统策略可以作为内核模块实现并链接到内核;如果存在多个策略模块,它们的结果将会组合。MAC 框架为策略编写者提供了多种访问控制基础设施服务,包括对瞬时和持久的、与策略无关的对象安全标签的支持。目前,这项支持被认为是实验性的。
本章提供了适用于策略模块开发人员以及潜在的 MAC 启用环境消费者的信息,帮助他们了解 MAC 框架如何支持对内核的访问控制扩展。
6.4. 策略背景
强制访问控制(MAC)指的是一组由操作系统对用户强制执行的访问控制策略。这些 MAC 策略可以与自主访问控制(DAC)保护进行对比,后者允许非管理员用户(自主决定)保护对象。在传统的 UNIX 系统中,DAC 保护包括文件权限和访问控制列表;而 MAC 保护包括防止用户间调试的进程控制和防火墙。操作系统设计师和安全研究人员已经提出了各种 MAC 策略,包括多级安全(MLS)机密性策略、Biba 完整性策略、基于角色的访问控制(RBAC)、域和类型强制(DTE)以及类型强制(TE)。每种模型的决策基于多种因素,包括用户身份、角色和安全许可,以及表示数据敏感性和完整性等概念的对象安全标签。
TrustedBSD MAC 框架能够支持实现所有这些策略的模块,以及一大类系统加固策略,这些策略可以利用现有的安全属性,如用户和组 ID,以及文件的扩展属性和其他系统属性。此外,尽管名字中带有“强制访问控制”,MAC 框架也可以用来实现纯粹的自主策略,因为策略模块在授权保护时具有相当大的灵活性。
6.5. MAC 框架内核架构
TrustedBSD MAC 框架允许内核模块扩展操作系统安全策略,同时提供许多访问控制模块所需的基础设施功能。如果多个策略同时加载,MAC 框架将有效地(在某些定义下有用地)组合这些策略的结果。
6.5.1. 内核元素
MAC 框架包含多个内核元素:
框架管理接口
并发和同步原语
策略注册
用于内核对象的可扩展安全标签
策略入口点组成运算符
标签管理原语
内核服务调用的入口点 API
策略模块的入口点 API
入口点实现(策略生命周期、对象生命周期/标签管理、访问控制检查)
与策略无关的标签管理系统调用
mac_syscall()多路复用系统调用作为 MAC 策略模块实现的各种安全策略
6.5.2. 框架管理接口
TrustedBSD MAC 框架可以通过 sysctl、加载器可调参数和系统调用进行直接管理。
在大多数情况下,具有相同名称的 sysctl 和加载器可调参数会修改相同的参数,并控制与各种内核子系统相关的保护执行。此外,如果内核中编译了 MAC 调试支持,将维护多个计数器,用于跟踪标签分配。通常建议在生产环境中不要使用每个子系统的执行控制来控制策略行为,因为它们广泛影响所有活动策略的操作。相反,应优先使用每个策略的控制,因为它们为策略模块提供了更大的粒度和更大的操作一致性。
策略模块的加载和卸载是通过系统模块管理系统调用和其他系统接口执行的,包括启动加载器变量;策略模块将有机会影响加载和卸载事件,包括防止不希望的策略卸载。
6.5.3. 策略列表并发性和同步
由于活动策略集可能在运行时发生变化,并且入口点的调用是非原子的,因此需要同步以防止在入口点调用过程中加载或卸载策略,从而在此期间冻结活动策略集。这是通过框架忙碌计数来完成的:每当进入一个入口点时,忙碌计数会增加;每当退出时,忙碌计数会减少。在忙碌计数为正时,不允许修改策略列表,试图修改策略列表的线程将进入睡眠,直到列表不再忙碌。忙碌计数由互斥锁保护,并且使用条件变量唤醒等待策略列表修改的线程。该同步模型的一个副作用是,允许从策略模块内部递归进入 MAC 框架,尽管通常不会使用。
为了减少忙碌计数的开销,使用了各种优化,包括避免在列表为空或仅包含静态条目的情况下完全增减计数(静态条目是指在系统启动之前加载的策略,并且不能卸载)。还提供了一个编译时选项,禁止在运行时更改已加载的策略集,这消除了与支持动态加载和卸载策略相关的互斥锁锁定开销,因为不再需要同步。
由于 MAC 框架在某些入口点中不允许阻塞,因此不能使用普通的睡眠锁;因此,加载或卸载尝试可能会因等待框架变为空闲而阻塞相当长的时间。
6.5.4. 标签同步
由于所关注的内核对象通常可能被多个线程同时访问,并且允许多个线程同时进入 MAC 框架,因此 MAC 框架维护的安全属性存储必须小心同步。通常,使用现有的内核同步机制来保护内核对象上的 MAC 框架安全标签:例如,套接字上的 MAC 标签使用现有的套接字互斥锁进行保护。同样,并发访问的语义通常与容器对象的语义相同:对于凭证,标签内容采用写时复制语义,类似于凭证结构的其余部分。当使用对象引用调用 MAC 框架时,它会断言对象上已持有必要的锁。策略作者必须了解这些同步语义,因为它们有时会限制对标签的访问类型:例如,当通过入口点将对凭证的只读引用传递给策略时,只允许对附加到凭证的标签状态进行读取操作。
6.5.5. 策略同步和并发性
策略模块必须假设由于 FreeBSD 内核的并行性和抢占性,多个内核线程可能会同时进入一个或多个策略入口点。如果策略模块使用可变状态,则可能需要在策略内使用同步原语,以防止在该状态上产生不一致的视图,从而导致策略操作错误。策略通常可以利用现有的 FreeBSD 同步原语来实现这一目的,包括互斥锁、睡眠锁、条件变量和计数信号量。然而,策略应该小心使用这些原语,遵守现有的内核锁定顺序,并认识到某些入口点不允许睡眠,因此在这些入口点中使用的原语仅限于互斥锁和唤醒操作。
当策略模块调用其他内核子系统时,它们通常需要释放任何策略内的锁,以避免违反内核锁定顺序或导致锁递归。这将使策略锁保持为全局锁顺序中的叶子锁,有助于避免死锁。
6.5.6. 策略注册
MAC 框架维护两种活动策略列表:静态列表和动态列表。这些列表仅在锁定语义上有所不同:使用静态列表时不需要提高引用计数。当包含 MAC 框架策略的内核模块被加载时,策略模块将使用 SYSINIT 调用注册函数;当策略模块被卸载时,SYSINIT 也会调用注销函数。如果策略模块被加载多次,或者在注册过程中没有足够的资源(例如,策略可能需要标签,而可用的标签状态不足),或者其他策略先决条件没有满足(某些策略可能仅能在启动前加载),注册可能会失败。同样,如果策略被标记为不可卸载,注销也可能失败。
6.5.7. 入口点
内核服务通过两种方式与 MAC 框架交互:它们调用一系列 API 通知框架相关事件,并提供安全相关对象中的与策略无关的标签结构指针。标签指针由 MAC 框架通过标签管理入口点维护,允许框架通过对维护对象的内核子系统进行相对非侵入式的修改,为策略模块提供标签服务。例如,标签指针已被添加到进程、进程凭证、套接字、管道、vnode、Mbuf、网络接口、IP 重组队列以及其他各种安全相关结构中。当内核服务执行重要的安全决策时,它们也会调用 MAC 框架,允许策略模块基于自己的标准(可能包括存储在安全标签中的数据)来增强这些决策。这些安全关键的决策大多是显式的访问控制检查;然而,某些决策影响更一般的决策函数,例如套接字的包匹配和程序执行时的标签过渡。
6.5.8. 策略组合
当多个策略模块同时加载到内核中时,框架将使用组合运算符组合策略模块的结果。该运算符当前是硬编码的,要求所有活动策略必须批准请求才能返回成功。由于策略可能返回多种错误条件(成功、访问被拒绝、对象不存在等),优先级运算符从策略返回的错误集选择最终的错误。通常,指示对象不存在的错误会优先于指示拒绝访问对象的错误。虽然无法保证结果组合将是有用或安全的,但我们发现,对于许多有用的策略选择而言,这一组合确实是有用的。例如,传统的可信系统通常使用两种或更多策略进行类似的组合。
6.5.9. 标签支持
由于许多值得关注的访问控制扩展依赖于对象上的安全标签,MAC 框架提供了一组与策略无关的标签管理系统调用,涵盖了各种用户可见的对象。常见的标签类型包括分区标识符、敏感度标签、完整性标签、隔离区、域、角色和类型。所谓与策略无关,意味着策略模块能够完全定义与对象相关的元数据的语义。策略模块参与用户应用程序提供的基于字符串的标签的内化和外化,并可以在需要时向应用程序暴露多个标签元素。
内存中的标签存储在 slab 分配的 struct label 中,该结构由一个固定长度的联合体数组组成,每个联合体包含一个 void * 指针和一个 long。注册标签存储的策略会获得一个“槽”标识符,该标识符可用于解引用标签存储。存储的语义完全由策略模块决定:模块提供与内核对象生命周期相关的多种入口点,包括初始化、关联/创建和销毁。通过这些接口,策略模块可以实现引用计数和其他存储模型。通常,策略模块不需要直接访问对象结构来检索标签,因为 MAC 框架通常会将对象的指针和对象标签的直接指针传递给入口点。主要的例外是进程凭证,必须手动解引用才能访问凭证标签。这可能会在 MAC 框架的未来版本中发生变化。
初始化入口点通常包括一个睡眠处置标志,指示是否允许初始化时进入睡眠状态;如果不允许睡眠,可能会返回失败,取消标签(及其对象)的分配。例如,在网络栈中的中断处理期间,不能进行睡眠,或者当调用者持有互斥锁时,可能会发生这种情况。由于在传输中的网络数据包(Mbuf)上维护标签的性能开销,策略必须明确声明需要分配 Mbuf 标签。使用标签的动态加载策略必须能够处理其初始化函数尚未在对象上调用的情况,因为在加载策略时,某些对象可能已经存在。MAC 框架保证未初始化的标签槽将保持 0 或 NULL 值,策略可以使用该值来检测未初始化的标签。然而,由于 Mbuf 标签的分配是有条件的,如果策略是动态加载的,策略也必须能够处理 Mbuf 的 NULL 标签指针。
在文件系统标签的情况下,提供了对安全标签在扩展属性中的持久化存储的特殊支持。在可用的情况下,使用扩展属性事务来进行安全标签的复合更新,以便在 vnode 上一致地更新安全标签——目前此支持仅在 UFS2 文件系统中存在。策略作者可以选择使用一个或多个扩展属性来实现多标签文件系统对象标签。出于效率考虑,vnode 标签(v_label)是任何磁盘标签的缓存;策略可以在 vnode 实例化时将值加载到缓存中,并根据需要更新缓存。因此,每次访问控制检查时不需要直接访问扩展属性。
注意
当前,如果一个标记策略允许动态卸载,则其状态槽不能被回收,这对标记策略的卸载-重新加载操作数量设置了严格(且相对较低)的上限。
6.5.10. 系统调用
MAC 框架实现了多个系统调用:这些调用中的大多数支持暴露给用户应用程序的与策略无关的标签检索和操作 API。
标签管理调用接受一个标签描述结构体 struct mac,该结构体包含一系列 MAC 标签元素。每个元素包含一个字符串名称和一个字符串值。每个策略都将有机会声明一个特定的元素名称,从而允许策略暴露多个独立的元素(如果需要的话)。策略模块通过入口点执行内核标签和用户提供的标签之间的内化和外化,从而支持各种语义。标签管理系统调用通常通过用户库函数进行包装,以执行内存分配和错误处理,从而简化需要管理标签的用户应用程序。
以下是 FreeBSD 内核中存在的 MAC 相关系统调用:
mac_get_proc()可用于检索当前进程的标签。mac_set_proc()可用于请求更改当前进程的标签。mac_get_fd()可用于检索由文件描述符引用的对象(文件、套接字、管道等)的标签。mac_get_file()可用于检索由文件系统路径引用的对象的标签。mac_set_fd()可用于请求更改由文件描述符引用的对象(文件、套接字、管道等)的标签。mac_set_file()可用于请求更改由文件系统路径引用的对象的标签。mac_syscall()允许策略模块在不修改系统调用表的情况下创建新的系统调用;它接受目标策略名称、操作编号和供策略使用的不透明参数。mac_get_pid()可用于通过进程 ID 请求获取另一个进程的标签。mac_get_link()与mac_get_file()相同,只是如果路径的最后一项是符号链接,它不会跟随符号链接,因此可以用于检索符号链接的标签。mac_set_link()与mac_set_file()相同,只是如果路径的最后一项是符号链接,它不会跟随符号链接,因此可以用于操作符号链接的标签。mac_execve()与execve()系统调用相同,只是它还接受一个请求的标签,用于在开始执行新程序时设置进程的标签。这种执行时的标签更改被称为“过渡”。mac_get_peer()实际上通过套接字选项实现,用于检索套接字上远程对等端的标签(如果可用)。
除了这些系统调用外,SIOCSIGMAC 和 SIOCSIFMAC 网络接口 ioctl 允许检索和设置网络接口上的标签。
6.6. MAC 策略架构
安全策略要么直接链接到内核中,要么编译成可加载的内核模块,可以在启动时或通过运行时的模块加载系统调用动态加载。策略模块通过一组声明的入口点与系统交互,提供对系统事件流的访问,并允许策略影响访问控制决策。每个策略包含以下几个元素:
策略的可选配置参数。
策略逻辑和参数的集中实现。
策略生命周期事件的可选实现,例如初始化和销毁。
可选的支持,用于在选定的内核对象上初始化、维护和销毁标签。
可选的支持,用于用户进程检查和修改选定对象的标签。
实现策略感兴趣的选定访问控制入口点。
声明策略身份、模块入口点和策略属性。
6.6.1. 策略声明
模块可以使用 MAC_POLICY_SET() 宏声明,该宏为策略命名,提供指向 MAC 入口点向量的引用,提供决定策略框架如何处理策略的加载时标志,并可选择性地请求框架分配标签状态。
MAC 策略入口点向量,在本例中为 mac_policy_ops,将模块中定义的函数与特定的入口点关联起来。可用入口点及其原型的完整列表可以在 MAC 入口点参考部分找到。在模块注册期间,.mpo_init 和 .mpo_destroy 入口点尤其重要。mpo_init 会在策略成功注册到模块框架后,但在其他任何入口点变为活跃之前调用。这样,策略就可以执行任何策略特定的分配和初始化,例如初始化数据或锁。mpo_destroy 会在卸载策略模块时调用,用于释放分配的内存并销毁锁。目前,这两个入口点是在持有 MAC 策略列表互斥锁的情况下调用的,以防止调用其他入口点:这将发生变化,但在此之前,策略应小心调用内核原语,以避免锁顺序或睡眠问题。
策略声明中的模块名称字段存在,以便为模块提供唯一标识,用于模块依赖关系。应选择一个适当的字符串。策略的完整字符串名称将在加载和卸载事件期间通过内核日志显示,并在提供用户空间进程的状态信息时导出。
6.6.2. 策略标志
策略声明的标志字段允许模块在加载时向框架提供有关其功能的信息。目前,定义了三个标志:
MPC_LOADTIME_FLAG_UNLOADOK:此标志指示策略模块可以卸载。如果未提供此标志,则策略框架将拒绝卸载模块的请求。此标志可能用于分配了标签状态且无法在运行时释放该状态的模块。MPC_LOADTIME_FLAG_NOTLATE:此标志指示策略模块必须在启动过程中尽早加载和初始化。如果指定了此标志,则启动后尝试注册模块会遭到拒绝。此标志可用于需要对所有系统对象进行普遍标记的策略,并且无法处理未由策略正确初始化的对象。MPC_LOADTIME_FLAG_LABELMBUFS:此标志指示策略模块需要对 Mbufs 进行标记,并且应始终为 Mbuf 标签分配内存。默认情况下,MAC 框架不会为 Mbuf 分配标签存储,除非至少一个已加载的策略设置了此标志。当策略不需要 Mbuf 标记时,这会显著提高网络性能。存在一个内核选项MAC_ALWAYS_LABEL_MBUF,该选项强制 MAC 框架分配 Mbuf 标签存储,无论此标志的设置如何,并且在某些环境中可能有用。
注意
使用
MPC_LOADTIME_FLAG_LABELMBUFS而未设置MPC_LOADTIME_FLAG_NOTLATE标志的策略必须能够正确处理传入入口点的NULLMbuf 标签指针。这是必要的,因为在启用 Mbuf 标记的策略加载后,未存储标签的传输中的 Mbuf 可能会持续存在。如果策略在网络子系统启用之前加载(即策略不是在后期加载的),那么所有 Mbuf 都保证有标签存储。
6.6.3. 策略入口点
框架提供了四类入口点供已注册策略使用:与策略注册和管理相关的入口点,表示内核对象的初始化、创建、销毁和其他生命周期事件的入口点,策略模块可能影响的访问控制决策相关的事件,以及与对象标签管理相关的调用。此外,提供了 mac_syscall() 入口点,以便策略可以扩展内核接口,而无需注册新的系统调用。
策略模块的编写者应注意内核锁策略,以及在不同入口点期间可用的对象锁。编写者应尽量避免通过在入口点内部获取非叶锁而导致死锁场景,并遵循对象访问和修改的锁定协议。特别地,编写者应注意,虽然访问对象及其标签所需的锁通常已经持有,但对于所有入口点,可能并不总是有足够的锁来修改对象或其标签。参数的锁定信息已在 MAC 框架入口点文档中进行记录。
策略入口点会传递对象标签的引用以及对象本身。这使得带标签的策略可以在不知道对象内部细节的情况下,仍然基于标签做出决策。例外情况是进程凭证,策略假定它作为内核中的一类安全对象是被理解的。
6.7. MAC 策略入口点参考
6.7.1. 通用模块入口点
6.7.1.1. mpo_init
mpo_initconf
MAC 策略定义
策略加载事件。持有策略列表互斥锁,因此不能执行睡眠操作,并且调用其他内核子系统时必须小心。如果策略初始化期间需要潜在的睡眠内存分配,则应通过单独的模块 SYSINIT() 来进行。
6.7.1.2. mpo_destroy
mpo_destroyconf
MAC 策略定义
策略卸载事件。持有策略列表互斥锁,因此应谨慎处理。
6.7.1.3. mpo_syscall
mpo_syscalltd
调用线程
call
策略特定的系统调用编号
arg
指向系统调用参数的指针
该入口点提供了一个策略多路复用的系统调用,允许策略为用户进程提供额外的服务,而无需注册特定的系统调用。在注册时提供的策略名称用于从用户空间解复用调用,并且参数会转发到此入口点。在实现新服务时,安全模块应确保根据需要调用 MAC 框架中的适当访问控制检查。例如,如果某个策略实现了扩展的信号功能,则应调用必要的信号访问控制检查以调用 MAC 框架和其他已注册策略。
注意
当前模块必须自行执行
copyin()来处理系统调用数据。
6.7.1.4. mpo_thread_userret
mpo_thread_userrettd
返回的线程
此入口点允许策略模块在线程返回到用户空间时,通过系统调用返回、陷阱返回或其他方式执行与 MAC 相关的事件。这对于具有浮动进程标签的策略是必需的,因为在系统调用处理过程中,通常无法在堆栈中的任意点获取进程锁;进程标签可能表示传统的认证数据、进程历史信息或其他数据。为了使用此机制,计划更改进程凭证标签的数据可以存储在 p_label 中,受每策略自旋锁保护,然后设置每线程的 TDF_ASTPENDING 标志和每进程的 PS_MACPENDM 标志,以便调度调用 userret 入口点。从此入口点,策略可以创建替代凭证,而无需过多担心锁定上下文。策略编写者需要注意,调度 AST 和 AST 执行之间的事件排序可能是复杂的,并且在多线程应用程序中可能是交错的。
6.7.2. 标签操作
6.7.2.1. mpo_init_bpfdesc_label
mpo_init_bpfdesc_labellabel
要应用的新标签
初始化新创建的 bpfdesc(BPF 描述符)上的标签。允许睡眠。
6.7.2.2. mpo_init_cred_label
mpo_init_cred_labellabel
要初始化的新标签
初始化新创建的用户凭证上的标签。允许睡眠。
6.7.2.3. mpo_init_devfsdirent_label
mpo_init_devfsdirent_labellabel
要应用的新标签
初始化新创建的 devfs 条目上的标签。允许睡眠。
6.7.2.4. mpo_init_ifnet_label
mpo_init_ifnet_labellabel
要应用的新标签
初始化新创建的网络接口上的标签。允许睡眠。
6.7.2.5. mpo_init_ipq_label
mpo_init_ipq_labellabel
要应用的新标签
初始化新创建的 IP 分片重组队列上的标签。flag 字段可以是 M_WAITOK 或 M_NOWAIT,用于避免在此初始化调用过程中执行睡眠的 malloc(9)。IP 分片重组队列的分配通常发生在性能敏感的环境中,因此实现时应小心避免睡眠或长时间操作。此入口点可能会失败,导致无法分配 IP 分片重组队列。
6.7.2.6. mpo_init_mbuf_label
mpo_init_mbuf_labellabel
要初始化的策略标签
初始化新创建的 mbuf 包头(mbuf)上的标签。flag 字段可以是 M_WAITOK 或 M_NOWAIT,用于避免在此初始化调用过程中执行睡眠的 malloc(9)。mbuf 的分配通常发生在性能敏感的环境中,因此实现时应小心避免睡眠或长时间操作。此入口点可能会失败,导致无法分配 mbuf 头部。
6.7.2.7. mpo_init_mount_label
mpo_init_mount_labelmntlabel
用于挂载点本身的策略标签
fslabel
用于文件系统的策略标签
初始化新创建的挂载点上的标签。允许睡眠。
6.7.2.8. mpo_init_mount_fs_label
mpo_init_mount_fs_labellabel
要初始化的标签
初始化新挂载的文件系统上的标签。允许睡眠。
6.7.2.9. mpo_init_pipe_label
mpo_init_pipe_labellabel
要填充的标签
初始化新创建的管道上的标签。允许睡眠。
6.7.2.10. mpo_init_socket_label
mpo_init_socket_labellabel
要初始化的新标签
初始化新创建的套接字上的标签。flag 字段可以是 M_WAITOK 或 M_NOWAIT,用于避免在此初始化调用过程中执行睡眠的 malloc(9)。
6.7.2.11. mpo_init_socket_peer_label
mpo_init_socket_peer_labellabel
要初始化的新标签
初始化新创建的套接字对等标签。flag 字段可以是 M_WAITOK 或 M_NOWAIT,用于避免在此初始化调用过程中执行睡眠的 malloc(9)。
6.7.2.12. mpo_init_proc_label
mpo_init_proc_labellabel
要初始化的进程标签
初始化新创建的进程标签。允许睡眠。
6.7.2.13. mpo_init_vnode_label
mpo_init_vnode_labellabel
要初始化的 vnode 标签
初始化新创建的 vnode 上的标签。允许睡眠。
6.7.2.14. mpo_destroy_bpfdesc_label
mpo_destroy_bpfdesc_labellabel
bpfdesc 标签
销毁 BPF 描述符上的标签。在此入口点,策略模块应释放与 label 相关的任何内部存储,以便将其销毁。
6.7.2.15. mpo_destroy_cred_label
mpo_destroy_cred_labellabel
正在销毁的标签
销毁凭证上的标签。在此入口点,策略模块应释放与 label 相关的任何内部存储,以便将其销毁。
6.7.2.16. mpo_destroy_devfsdirent_label
mpo_destroy_devfsdirent_labellabel
正在销毁的标签
销毁 devfs 条目上的标签。在此入口点,策略模块应释放与 label 相关的任何内部存储,以便将其销毁。
6.7.2.17. mpo_destroy_ifnet_label
mpo_destroy_ifnet_labellabel
正在销毁的标签
销毁移除的网络接口上的标签。在此入口点,策略模块应释放与 label 相关的任何内部存储,以便将其销毁。
6.7.2.18. mpo_destroy_ipq_label
mpo_destroy_ipq_labellabel
正在销毁的标签
销毁 IP 分片队列上的标签。在此入口点,策略模块应释放与 label 相关的任何内部存储,以便将其销毁。
6.7.2.19. mpo_destroy_mbuf_label
mpo_destroy_mbuf_labellabel
正在销毁的标签
销毁 mbuf 头部上的标签。在此入口点,策略模块应释放与 label 相关的任何内部存储,以便将其销毁。
6.7.2.20. mpo_destroy_mount_label
mpo_destroy_mount_labellabel
正在销毁的挂载点标签
销毁挂载点上的标签。在此入口点,策略模块应释放与 label 相关的任何内部存储,以便将其销毁。
6.7.2.21. mpo_destroy_mount_label
mpo_destroy_mount_labelmntlabel
正在销毁的挂载点标签
fslabel
正在销毁的文件系统标签
销毁挂载点上的标签。在此入口点,策略模块应释放与 mntlabel 和 fslabel 相关的任何内部存储,以便将其销毁。
6.7.2.22. mpo_destroy_socket_label
mpo_destroy_socket_labellabel
正在销毁的套接字标签
销毁套接字上的标签。在此入口点,策略模块应释放与 label 相关的任何内部存储,以便将其销毁。
6.7.2.23. mpo_destroy_socket_peer_label
mpo_destroy_socket_peer_labelpeerlabel
正在销毁的套接字对等标签
销毁套接字上的对等标签。在此入口点,策略模块应释放与 peerlabel 相关的任何内部存储,以便将其销毁。
6.7.2.24. mpo_destroy_pipe_label
mpo_destroy_pipe_labellabel
管道标签
销毁管道上的标签。在此入口点,策略模块应释放与 label 相关的任何内部存储,以便将其销毁。
6.7.2.25. mpo_destroy_proc_label
mpo_destroy_proc_labellabel
进程标签
销毁进程上的标签。在此入口点,策略模块应释放与 label 相关的任何内部存储,以便将其销毁。
6.7.2.26. mpo_destroy_vnode_label
mpo_destroy_vnode_labellabel
vnode 标签
销毁 vnode 上的标签。在此入口点,策略模块应释放与 label 相关的任何内部存储,以便将其销毁。
6.7.2.27. mpo_copy_mbuf_label
mpo_copy_mbuf_labelsrc
源标签
dest
目标标签
将 src 中的标签信息复制到 dest。
6.7.2.28. mpo_copy_pipe_label
mpo_copy_pipe_labelsrc
源标签
dest
目标标签
将 src 中的标签信息复制到 dest。
6.7.2.29. mpo_copy_vnode_label
mpo_copy_vnode_labelsrc
源标签
dest
目标标签
将 src 中的标签信息复制到 dest。
6.7.2.30. mpo_externalize_cred_label
mpo_externalize_cred_labellabel
要外部化的标签
element_name
需要外部化标签的策略名称
sb
用于填充标签文本表示的字符串缓冲区
claimed
当 element_data 可以填充时,应该递增该值
根据传递的标签结构生成外部化标签。外部化标签是标签内容的文本表示,可以供用户态应用程序使用,并由用户读取。目前,所有策略的 externalize 入口点都会被调用,因此实现应检查 element_name 的内容,在尝试填充 sb 之前。如果 element_name 与你的策略名称不匹配,直接返回 0。只有在外部化标签数据时发生错误时,才返回非零值。一旦策略填充了 element_data,*claimed 应递增。
6.7.2.31. mpo_externalize_ifnet_label
mpo_externalize_ifnet_labellabel
要外部化的标签
element_name
需要外部化标签的策略名称
sb
用于填充标签文本表示的字符串缓冲区
claimed
当 element_data 可以填充时,应该递增该值
根据传递的标签结构生成外部化标签。外部化标签是标签内容的文本表示,可以供用户态应用程序使用,并由用户读取。目前,所有策略的 externalize 入口点都会被调用,因此实现应检查 element_name 的内容,在尝试填充 sb 之前。如果 element_name 与你的策略名称不匹配,直接返回 0。只有在外部化标签数据时发生错误时,才返回非零值。一旦策略填充了 element_data,*claimed 应递增。
6.7.2.32. mpo_externalize_pipe_label
mpo_externalize_pipe_labellabel
要外部化的标签
element_name
需要外部化标签的策略名称
sb
用于填充标签文本表示的字符串缓冲区
claimed
当 element_data 可以填充时,应该递增该值
根据传递的标签结构生成外部化标签。外部化标签是标签内容的文本表示,可以供用户态应用程序使用,并由用户读取。目前,所有策略的 externalize 入口点都会被调用,因此实现应检查 element_name 的内容,在尝试填充 sb 之前。如果 element_name 与你的策略名称不匹配,直接返回 0。只有在外部化标签数据时发生错误时,才返回非零值。一旦策略填充了 element_data,*claimed 应递增。
6.7.2.33. mpo_externalize_socket_label
mpo_externalize_socket_labellabel
要外部化的标签
element_name
需要外部化标签的策略名称
sb
用于填充标签文本表示的字符串缓冲区
claimed
当 element_data 可以填充时,应该递增该值
根据传递的标签结构生成外部化标签。外部化标签是标签内容的文本表示,可以供用户态应用程序使用,并由用户读取。目前,所有策略的 externalize 入口点都会被调用,因此实现应检查 element_name 的内容,在尝试填充 sb 之前。如果 element_name 与你的策略名称不匹配,直接返回 0。只有在外部化标签数据时发生错误时,才返回非零值。一旦策略填充了 element_data,*claimed 应递增。
6.7.2.34. mpo_externalize_socket_peer_label
mpo_externalize_socket_peer_labellabel
要外部化的标签
element_name
需要外部化标签的策略名称
sb
用于填充标签文本表示的字符串缓冲区
claimed
当 element_data 可以填充时,应该递增该值
根据传递的标签结构生成外部化标签。外部化标签是标签内容的文本表示,可以供用户态应用程序使用,并由用户读取。目前,所有策略的 externalize 入口点都会被调用,因此实现应检查 element_name 的内容,在尝试填充 sb 之前。如果 element_name 与你的策略名称不匹配,直接返回 0。只有在外部化标签数据时发生错误时,才返回非零值。一旦策略填充了 element_data,*claimed 应递增。
6.7.2.35. mpo_externalize_vnode_label
mpo_externalize_vnode_labellabel
要外部化的标签
element_name
需要外部化标签的策略名称
sb
用于填充标签文本表示的字符串缓冲区
claimed
当 element_data 可以填充时,应该递增该值
根据传递的标签结构生成外部化标签。外部化标签是标签内容的文本表示,可以供用户态应用程序使用,并由用户读取。目前,所有策略的 externalize 入口点都会被调用,因此实现应检查 element_name 的内容,在尝试填充 sb 之前。如果 element_name 与你的策略名称不匹配,直接返回 0。只有在外部化标签数据时发生错误时,才返回非零值。一旦策略填充了 element_data,*claimed 应递增。
6.7.2.36. mpo_internalize_cred_label
mpo_internalize_cred_labellabel
要填充的标签
element_name
需要内部化标签的策略名称
element_data
需要内部化的文本数据
claimed
当数据能够成功内部化时,应该递增该值
根据外部化标签数据的文本格式生成内部标签结构。目前,所有策略的 internalize 入口点都会在请求内部化时被调用,因此实现应将 element_name 的内容与其策略名称进行比较,以确保应该内部化 element_data 中的数据。与 externalize 入口点一样,如果 element_name 与策略名称不匹配,则返回 0;如果数据能够成功内部化,则递增 *claimed。
6.7.2.37. mpo_internalize_ifnet_label
mpo_internalize_ifnet_labellabel
要填充的标签
element_name
需要内部化标签的策略名称
element_data
需要内部化的文本数据
claimed
当数据能够成功内部化时,应该递增该值
根据外部化标签数据的文本格式生成内部标签结构。目前,所有策略的 internalize 入口点都会在请求内部化时被调用,因此实现应将 element_name 的内容与其策略名称进行比较,以确保应该内部化 element_data 中的数据。与 externalize 入口点一样,如果 element_name 与策略名称不匹配,则返回 0;如果数据能够成功内部化,则递增 *claimed。
6.7.2.38. mpo_internalize_pipe_label
mpo_internalize_pipe_labellabel
要填充的标签
element_name
需要内部化标签的策略名称
element_data
需要内部化的文本数据
claimed
当数据能够成功内部化时,应该递增该值
根据外部化标签数据的文本格式生成内部标签结构。目前,所有策略的 internalize 入口点都会在请求内部化时被调用,因此实现应将 element_name 的内容与其策略名称进行比较,以确保应该内部化 element_data 中的数据。与 externalize 入口点一样,如果 element_name 与策略名称不匹配,则返回 0;如果数据能够成功内部化,则递增 *claimed。
6.7.2.39. mpo_internalize_socket_label
mpo_internalize_socket_labellabel
要填充的标签
element_name
需要内部化标签的策略名称
element_data
需要内部化的文本数据
claimed
当数据能够成功内部化时,应该递增该值
根据外部化标签数据的文本格式生成内部标签结构。目前,所有策略的 internalize 入口点都会在请求内部化时被调用,因此实现应将 element_name 的内容与其策略名称进行比较,以确保应该内部化 element_data 中的数据。与 externalize 入口点一样,如果 element_name 与策略名称不匹配,则返回 0;如果数据能够成功内部化,则递增 *claimed。
6.7.2.40. mpo_internalize_vnode_label
mpo_internalize_vnode_labellabel
要填充的标签
element_name
需要内部化标签的策略名称
element_data
需要内部化的文本数据
claimed
当数据能够成功内部化时,应该递增该值
根据外部化标签数据的文本格式生成内部标签结构。目前,所有策略的 internalize 入口点都会在请求内部化时被调用,因此实现应将 element_name 的内容与其策略名称进行比较,以确保应该内部化 element_data 中的数据。与 externalize 入口点一样,如果 element_name 与策略名称不匹配,则返回 0;如果数据能够成功内部化,则递增 *claimed。
6.7.3. 标签事件
这一类入口点由 MAC 框架使用,允许策略维护内核对象的标签信息。对于每个对 MAC 策略感兴趣的被标记内核对象,可以为相关生命周期事件注册入口点。所有对象都实现了初始化、创建和销毁挂钩。一些对象还实现了重新标记,允许用户进程更改对象上的标签。某些对象还实现了特定于对象的事件,例如与 IP 重组相关的标签事件。一个典型的标记对象会有以下生命周期的入口点:
标签初始化允许策略在没有对象使用上下文的情况下分配内存并设置标签的初始值。分配给策略的标签槽将默认被清零,因此某些策略可能不需要执行初始化。
标签创建发生在内核结构与实际内核对象关联时。例如,mbuf 可能会被分配并保持未使用状态,直到需要它们。mbuf 分配会导致 mbuf 上的标签初始化,但 mbuf 创建发生在 mbuf 与数据报关联时。通常,在创建事件中会提供上下文,包括创建的情况以及创建过程中其他相关对象的标签。例如,当一个 mbuf 从套接字创建时,套接字及其标签将与新创建的 mbuf 及其标签一起呈现给注册的策略。创建事件中不鼓励进行内存分配,因为它可能发生在内核性能敏感部分;此外,创建调用不允许失败,因此内存分配失败无法报告。
特定对象事件通常不属于标签事件的其他广泛类别,但通常会提供在额外上下文的基础上修改或更新对象标签的机会。例如,IP 分片重组队列上的标签可能会在 MAC_UPDATE_IPQ 入口点中更新,这是由于接受额外的 mbuf 到该队列中。
访问控制事件将在以下部分详细讨论。
标签销毁允许策略释放与标签相关的存储或状态,以便支持对象的内核数据结构可以被重用或释放。
除了与特定内核对象关联的标签之外,还有一种额外的标签类别:临时标签。这些标签用于存储由用户进程提交的更新信息。这些标签与其他标签类型一样进行初始化和销毁,但创建事件是 MAC_INTERNALIZE,接受用户标签并将其转换为内核表示。
6.7.3.1. 文件系统对象标记事件操作
6.7.3.1.1. mpo_associate_vnode_devfs
mp
Devfs 挂载点
fslabel
Devfs 文件系统标签(mp->mnt_fslabel)
de
Devfs 目录项
delabel
与 de 关联的策略标签
vp
与 de 关联的 vnode
vlabel
与 vp 关联的策略标签
根据传入的 Devfs 目录项 de 及其标签,填充新创建的 Devfs vnode 的标签 (vlabel)。
6.7.3.1.2. mpo_associate_vnode_extattr
mp
文件系统挂载点
fslabel
文件系统标签
vp
要标记的 vnode
vlabel
与 vp 关联的策略标签
尝试从文件系统扩展属性中检索 vp 的标签。成功时返回值为 0。如果不支持扩展属性检索,则可以接受的备用方案是将 fslabel 复制到 vlabel 中。如果发生错误,则应返回适当的 errno 值。
6.7.3.1.3. mpo_associate_vnode_singlelabel
mp
文件系统挂载点
fslabel
文件系统标签
vp
要标记的 vnode
vlabel
与 vp 关联的策略标签
在非多标签文件系统上,调用此入口点根据文件系统标签 fslabel 设置 vp 的策略标签。
6.7.3.1.4. mpo_create_devfs_device
dev
与 devfs_dirent 相关的设备
devfs_dirent
要标记的 Devfs 目录项
label
要填充的 devfs_dirent 标签
为传入的设备创建的 devfs_dirent 填充标签。此调用会在设备文件系统挂载、重新生成或新设备可用时触发。
6.7.3.1.5. mpo_create_devfs_directory
dirname
被创建目录的名称
namelen
字符串 dirname 的长度
devfs_dirent
被创建的目录的 Devfs 目录项
为传入的目录创建的 devfs_dirent 填充标签。此调用会在设备文件系统挂载、重新生成或新设备需要特定目录层次时触发。
6.7.3.1.6. mpo_create_devfs_symlink
cred
主体凭证
mp
Devfs 挂载点
dd
符号链接的目标
ddlabel
与目标 dd 关联的标签
de
符号链接条目
delabel
与符号链接条目 de 关联的标签
为新创建的 Devfs 符号链接条目填充标签 (delabel)。
6.7.3.1.7. mpo_create_vnode_extattr
cred
主体凭证
mp
文件系统挂载点
fslabel
文件系统标签
dvp
父目录 vnode
dlabel
与父目录 dvp 关联的标签
vp
新创建的 vnode
vlabel
与 vp 关联的策略标签
cnp
vp 的组件名称
将 vp 的标签写入适当的扩展属性。如果写入成功,填充 vlabel 并返回 0。如果失败,返回适当的错误值。
6.7.3.1.8. mpo_create_mount
cred
主体凭证
mp
对象;正在挂载的文件系统
mntlabel
要为 mp 填充的策略标签
fslabel
mp 所挂载文件系统的策略标签
根据传入的主体凭证填充挂载点的标签。此调用会在新文件系统挂载时触发。
6.7.3.1.9. mpo_create_root_mount
根据传入的主体凭证填充根文件系统挂载点的标签。此调用会在根文件系统挂载后、mpo_create_mount 调用之后触发。
6.7.3.1.10. mpo_relabel_vnode
cred
主体凭证
vp
要重新标记的 vnode
vnodelabel
vp 当前的策略标签
newlabel
替换 vnodelabel 的新标签,可能是部分标签
根据传入的更新 vnode 标签和主体凭证更新传入的 vnode 标签。
6.7.3.1.11. mpo_setlabel_vnode_extattr
cred
主体凭证
vp
要写入标签的 vnode
vlabel
与 vp 关联的策略标签
intlabel
要写入的标签
将 intlabel 中的策略标签写入扩展属性。该函数由 vop_stdcreatevnode_ea 调用。
6.7.3.1.12. mpo_update_devfsdirent
devfs_dirent
Devfs 目录项
direntlabel
需要更新的 devfs_dirent 的策略标签
vp
父 vnode
锁定
vnodelabel
与 vp 关联的策略标签
根据传入的 devfs vnode 标签更新 devfs_dirent 的标签。当 devfs vnode 成功重新标记后,会调用此函数以确保标签更改持久化,即使 vnode 被回收。同时,在 devfs 创建符号链接时,也会调用此函数,在 mac_vnode_create_from_vnode 调用之后初始化 vnode 标签。
6.7.3.2. IPC 对象标记事件操作
6.7.3.2.1. mpo_create_mbuf_from_socket
socket
套接字
套接字锁定开发中
socketlabel
与 socket 关联的策略标签
m
mbuf 对象
mbuflabel
为 m 填充的策略标签
根据传入的套接字标签设置新创建的 mbuf 标头的标签。此调用在套接字生成新数据报或消息并存储到传入的 mbuf 时触发。
6.7.3.2.2. mpo_create_pipe
cred
主体凭证
pipe
管道
pipelabel
与 pipe 关联的策略标签
根据传入的主体凭证设置新创建的管道的标签。此调用在新管道创建时触发。
6.7.3.2.3. mpo_create_socket
cred
主体凭证
不可变
so
要标记的套接字
socketlabel
为 so 填充的标签
根据传入的主体凭证为新创建的套接字设置标签。此调用在套接字创建时触发。
6.7.3.2.4. mpo_create_socket_from_socket
oldsocket
监听套接字
oldsocketlabel
与 oldsocket 关联的策略标签
newsocket
新的套接字
newsocketlabel
与 newsocket 关联的策略标签
6.7.3.2.5. mpo_relabel_pipe
cred
主体凭证
pipe
管道
oldlabel
当前与管道关联的策略标签
newlabel
要应用于管道的策略标签更新
将新的标签 newlabel 应用到管道 pipe 上。
6.7.3.2.6. mpo_relabel_socket
cred
主体凭证
不可变
so
套接字对象
oldlabel
当前套接字 so 的标签
newlabel
套接字 so 的标签更新
根据传入的标签更新,更新套接字 so 的标签。
6.7.3.2.7. mpo_set_socket_peer_from_mbuf
mbuf
通过套接字接收到的第一个数据报
mbuflabel
与 mbuf 关联的标签
oldlabel
套接字当前的标签
newlabel
要为套接字设置的对端标签
根据传入的 mbuf 标签为流式套接字设置对端标签。此调用在流式套接字接收到第一个数据报时触发,Unix 域套接字除外。
6.7.3.2.8. mpo_set_socket_peer_from_socket
oldsocket
本地套接字
oldsocketlabel
与 oldsocket 关联的策略标签
newsocket
对端套接字
newsocketpeerlabel
要为 newsocket 填充的对端策略标签
根据传入的远程套接字端点为流式 Unix 域套接字设置对端标签。此调用将在套接字对连接时触发,并将为两个端点进行调用。
6.7.3.3. 网络对象标记事件操作
6.7.3.3.1. mpo_create_bpfdesc
cred
主体凭证
不可变
bpf_d
对象;BPF 描述符
bpf
要为 bpf_d 填充的策略标签
根据传入的主体凭证为新创建的 BPF 描述符设置标签。此调用将在进程使用传入的主体凭证打开 BPF 设备节点时触发。
6.7.3.3.2. mpo_create_ifnet
ifnet
网络接口
ifnetlabel
要为 ifnet 填充的策略标签
为新创建的网络接口设置标签。此调用可能在新物理接口对系统可用时触发,或者在启动期间或由于用户操作而实例化伪接口时触发。
6.7.3.3.3. mpo_create_ipq
fragment
第一个接收到的 IP 分片
fragmentlabel
与 fragment 关联的策略标签
ipq
要标记的 IP 分片重组队列
ipqlabel
要为 ipq 填充的策略标签
根据第一个接收到的分片的 mbuf 标头设置新创建的 IP 分片重组队列的标签。
6.7.3.3.4. mpo_create_datagram_from_ipq
ipq
IP 分片重组队列
ipqlabel
与 ipq 关联的策略标签
datagram
要标记的数据报
datagramlabel
要为 datagram 填充的策略标签
根据生成该数据报的 IP 分片重组队列,为新重新组装的 IP 数据报设置标签。
6.7.3.3.5. mpo_create_fragment
datagram
数据报
datagramlabel
与 datagram 关联的策略标签
fragment
要标记的分片
fragmentlabel
要为 fragment 填充的策略标签
根据数据报的 mbuf 标头的标签为新创建的 IP 分片的 mbuf 标头设置标签。
6.7.3.3.6. mpo_create_mbuf_from_mbuf
oldmbuf
现有的(源)mbuf
oldmbuflabel
与 oldmbuf 关联的策略标签
newmbuf
要标记的新 mbuf
newmbuflabel
要为 newmbuf 填充的策略标签
根据现有数据报的 mbuf 标头为新创建的数据报的 mbuf 标头设置标签。此调用可能在多种情况下触发,包括当 mbuf 因对齐目的而重新分配时。
6.7.3.3.7. mpo_create_mbuf_linklayer
ifnet
网络接口
ifnetlabel
与 ifnet 关联的策略标签
mbuf
新数据报的 mbuf 标头
mbuflabel
要为 mbuf 填充的策略标签
根据传入的网络接口为链路层响应而生成的新数据报设置 mbuf 标头标签。此调用可能在多种情况下触发,包括 ARP 或 IPv4 和 IPv6 堆栈中的 ND6 响应。
6.7.3.3.8. mpo_create_mbuf_from_bpfdesc
bpf_d
BPF 描述符
bpflabel
与 bpf_d 关联的策略标签
mbuf
要标记的新 mbuf
mbuflabel
要为 mbuf 填充的策略标签
根据传入的 BPF 描述符为新创建的数据报设置 mbuf 标头标签。此调用在对与传入的 BPF 描述符关联的 BPF 设备执行写操作时触发。
6.7.3.3.9. mpo_create_mbuf_from_ifnet
ifnet
网络接口
ifnetlabel