# 第 6 章 TrustedBSD MAC 框架

This translation may be out of date. To help with the translations please access the FreeBSD translations instance.

## 6.1. MAC 文档版权声明

本文档是作为 DARPA CHATS 研究计划的一部分，由供职于 Security Research Division of Network Associates 公司Safeport Network Services and Network Associates Laboratories 的Chris Costello依据 DARPA/SPAWAR 合同 N66001-01-C-8035 ("CBOSS")，为 FreeBSD 项目编写的。

Redistribution and use in source (SGML DocBook) and 'compiled' forms (SGML, HTML, PDF, PostScript, RTF and so forth) with or without modification, are permitted provided that the following conditions are met:

1. Redistributions of source code (SGML DocBook) must retain the above copyright notice, this list of conditions and the following disclaimer as the first lines of this file unmodified.

2. Redistributions in compiled form (transformed to other DTDs, converted to PDF, PostScript, RTF and other formats) must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

| THIS DOCUMENTATION IS PROVIDED BY THE NETWORKS ASSOCIATES TECHNOLOGY, INC "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL NETWORKS ASSOCIATES TECHNOLOGY, INC BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

| 本文中许可证的非官方中文翻译仅供参考， 不作为判定任何责任的依据。如与英文原文有出入，则以英文原文为准。

在满足下列许可条件的前提下，允许再分发或以源代码 (SGML DocBook) 或 "编译" (SGML, HTML, PDF, PostScript, RTF 等) 的经过修改或未修改的形式：

1. 再分发源代码 (SGML DocBook) 必须不加修改的保留上述版权告示、本条件清单和下述弃权书作为该文件的最先若干行。

2. 再分发编译的形式 (转换为其它DTD、 PDF、 PostScript、 RTF 或其它形式)，必须将上述版权告示、 本条件清单和下述弃权书复制到与分发品一同提供的文件，以及其它材料中。

| 本文档由 NETWORKS ASSOCIATES TECHNOLOGY, INC "`按现状条件`"提供，并在此明示不提供任何明示或暗示的保障， 包括但不限于对商业适销性、对特定目的的适用性的暗示保障。任何情况下， NETWORKS ASSOCIATES TECHNOLOGY, INC 均不对任何直接、 间接、 偶然、 特殊、 惩罚性的， 或必然的损失 (包括但不限于替代商品或服务的采购、 使用、 数据或利益的损失或营业中断) 负责， 无论是如何导致的并以任何有责任逻辑的， 无论是否是在本文档使用以外以任何方式产生的契约、严格责任或是民事侵权行为(包括疏忽或其它)中的， 即使已被告知发生该损失的可能性。

## 6.2. 术语解析

FreeBSD 以一个内核安全扩展性框架(TrustedBSD MAC 框架)的方式，为若干强制访问控制策略（也称"`集权式访问控制策略`"） 提供试验性支持。MAC 框架是一个插入式的访问控制框架，允许新的安全策略更方便地融入内核:安全策略可以静态链入内核,也可以 在引导时加载,甚至在运行时动态加载。该框架所提供的标准化接口,使得运行在其上的安全策略模块能对系统对象的安全属性进行诸如标记等一系列操作。 MAC 框架的存在，简化了这些操作在策略模块中的实现，从而显著降低了新安全策略模块的开发难度。

本章将介绍 MAC 策略框架，为读者提供一个示例性的 MAC 策略模块文档。

## 6.3. 概述

TrustedBSD MAC 框架提供的机制,允许在其上运行的内核模块在内核编译或者运行时，对内核的访问控制模型进行扩展。 新的系统安全策略作为一个内核模块实现,并被链接到内核中；如果系统中同时存在多个安全策略模块，则它们的决策结果将以某种确定的方式组合。 为了给简化新安全策略的开发，MAC 向上提供了大量用于访问控制的基础设施，特别是，对临时的或者持久的、策略无关的对象安全标记的支持。 该支持目前仍是试验性质的。

本章所提供的信息不仅将使在 MAC 使能环境下工作的潜在用户受益， 也可以为需要了解 MAC 框架是如何支持对内核访问控制进行扩展的策略模块开发人员所用。

## 6.4. 安全策略背景知识

强制访问控制（简称 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 子系统、通过 loader 配置, 或者使用系统调用。

多数情况下，与同一个内核内部变量相关联的 sysctl 变量和 loader 参数的名字是相同的， 通过设置它们，可以控制保护措施的实施细节，比如，某个策略在各个内核子系统中的实施与否等等。 另外，如果在内核编译时选择支持 MAC 调试选项，内核将维护若干计数器以跟踪标记的分配使用情况。 通常不建议在实用环境下通过在不同子系统上设置不同的变量或参数来实施控制，因为这种方法将会作用于系统中所有的活跃策略。 如果希望对具体策略实施管理而不相影响其他活跃策略,则应当使用策略级别的控制，因为这种方法的控制粒度更细， 并能更好地保证策略模块的功能一致性。

与其他内核模块一样，系统管理员可以通过系统的模块管理系统调用和其他系统接口，包括 boot loader 变量，对策略模块执行加载与卸载操作； 策略模块可以在加载时,设置加载标志,来指示系统对其加载、卸载操作进行相应控制，比如阻止非期望的卸载操作。

### 6.5.3. 策略链表的并发与同步

在运行时,系统中活跃的策略集合可能发生变化，然而对策略入口函数的使用操作并不是原子性的，因此，当某一个入口函数正被使用时， 系统需要提供额外的同步机制来阻止对该策略模块的加载与卸载，以确保当前活跃的策略集合不会在此过程中发生改变。 通过使用"框架忙"计数器,就可以做到这一点：一旦某个入口函数被调用，计数器的值被增加1；而每当一个入口函数调用结束时，计数器的值被减少1。 检查计数器的值，如果其值为正，框架将阻止对策略链表的修改操作，请求操作的线程将被迫进入睡眠，直到计数器的值重新减少到0为止。 计数器本身由一个互斥锁保护，同时结合一个条件变量(用于唤醒等待对策略链表进行修改操作的睡眠线程)。 采用这种同步模型的一个副作用是，在同一个策略模块内部，允许嵌套地调用框架，不过这种情况其实很少出现。

为了减少由于采用计数器引入的额外开销，设计者采用了各种优化措施。其中包括，当策略链表为空或者其中仅含有静态表项 （那些只能在系统运行之前加载而且不能动态卸载的策略）时，框架不对计数器进行操作，其值总是为0，从而将此时的同步开销减到0。 另一个极端的办法是，使用一个编译选项来禁止在运行时对加载的策略链表进行修改，此时不再需要对策略链表的使用进行同步保护。

因为 MAC 框架不允许在某些入口函数之内阻塞，所以不能使用普通的睡眠锁。 故而，加载或卸载操作可能会为等待框架空闲而被阻塞相当长的一段时间。

### 6.5.4. 标记同步

MAC 框架必须对其负责维护的安全属性标记的存储访问提供同步保护。下列两种情形，可能导致对安全属性标记的不一致访问： 第一，作为安全属性标记的持有者，内核对象本身可能同时被多个线程访问；第二，MAC 框架代码是可重入的， 即允许多个线程同时在框架内执行。通常，MAC 框架使用内核对象数据上已有的内核同步机制来保护该其上附加的 MAC 安全标记。 例如，套接字上的 MAC 标记由已有的套接字互斥锁保护。类似的，对于安全标记的并发访问的过程与对其所在对象进行的并发访问在语义上是一样的， 例如，信任状安全标记,将保持与该数据结构中其他内容一致的"写时复制"的更新过程。 MAC 框架在引用一个内核对象时，将首先对访问该对象上的标记需要用到的锁进行断言。 策略模块的编写者必须了解这些同步语义， 因为它们可能会限制对安全标记所能进行的访问类型。 举个例子，如果通过入口函数传给策略模块的是对某个信任状的只读引用，那么在策略内部，只能读该结构对应的标记状态。

### 6.5.5. 策略间的同步与并发

FreeBSD 内核是一个可抢占式的内核，因此，作为内核一部分的策略模块也必须是可重入的，也就是说， 在开发策略模块时必须假设多个内核线程可以同时通过不同的入口函数进入该模块。 如果策略模块使用可被修改的内核状态，那么还需要在策略内部使用恰当的同步原语，确保在策略内部的多个线程不会因此观察到不一致的内核状态， 从而避免由此产生的策略误操作。为此，策略可以使用 FreeBSD 现有的同步原语，包括互斥锁、睡眠锁、条件变量和计数信号量。 对这些同步原语的使用必须慎重，需要特别注意两点：第一，保持现有的内核上锁次序； 第二，在非睡眠的入口函数之内不要使用互斥锁和唤醒操作。

为避免违反内核上锁次序或造成递归上锁，策略模块在调用其他内核子系统之前，通常要释放所有在策略内部申请的锁。 这样做的结果是，在全局上锁次序形成的拓朴结构中，策略内部的锁总是作为叶子节点， 从而保证了这些锁的使用不会导致由于上锁次序混乱造成的死锁。

### 6.5.6. 策略注册

为了记录当前使用的策略模块集合，MAC 框架维护两个链表：一个静态链表和一个动态链表。 两个链表的数据结构和操作基本相同，只是动态链表还额外使用了一个"引用计数"以同步对其的访问操作。 当包含 MAC 框架策略的内核模块被加载时，该策略模块会通过 `SYSINIT` 调用一个注册函数； 相对应的，每当一个策略模块被卸载，`SYSINIT` 也会调用一个注销函数。 只有当遇到下列情况之一时，注册过程才会失败： 一个策略模块被加载多次，或者系统资源不足不能满足注册过程的需要（ 例如，策略模块需要对内核对象添加标记而可用资源不足），或者其他的策略加载前提条件不满足（有些策略要求只能在系统引导之前加载）。 类似的，如果一个策略被标记为不可卸载的，对其调用注销过程将会失败。

### 6.5.7. 入口函数

内核服务与 MAC 框架之间进行交互有两种途径： 一是，内核服务调用一系列 API 通知 MAC 框架安全事件的发生； 二是，内核服务向 MAC 框架提供一个指向安全对象的策略无关安全标记数据结构的指针。 标记指针由 MAC 框架经由标记管理入口函数进行维护， 并且，只要对管理相关对象的内核子系统稍作修改，就可以允许 MAC 框架向策略模块提供标记服务。 例如，在进程、进程信任状、套接字、管道、Mbuf、网络接口、IP 重组队列和其他各种安全相关的数据结构中均增加了指向安全标记的指针。 另外，当需要做出重要的安全决策时，内核服务也会调用 MAC 框架，以便各个策略模块根据其自己的标准（可以使用存储在安全标记中的数据）完善这些决策。 绝大多数安全相关的关键决策是显式的访问控制检查； 也有少数涉及更加一般的决策函数，比如，套接字的数据包匹配和程序执行时刻的标记转换。

### 6.5.8. 策略组合

如果内核中同时加载了多个策略模块，这些策略的决策结果将由框架使用一个合成运算子来进行组合汇总，得出最终的结果。 目前，该算子是硬编码的，并且只有当所有的活跃策略均对请求表示同意时才会返回成功。 由于各个策略返回的出错条件可能并不相同（成功、访问被拒绝、请求对象不存在等等）， 需要使用一个选择子先从各个策略返回的错误条件集合中选择出一个作为最终返回结果。 一般情况下，与"`访问被拒绝`"相比，将更倾向于选择"`请求对象不存在`"。 尽管不能从理论上保证合成结果的有效性与安全性，但试验结果表明，对于许多实用的策略集合来说，事实的确如此。 例如，传统的可信系统常常采用类似的方法对多个安全策略进行组合。

### 6.5.9. 标记支持

与许多需要给对象添加安全标记的访问控制扩展一样，MAC 框架为各种用户可见的对象提供了一组用于管理策略无关标记的系统调用。 常用的标记类型有，partition标识符、机密性标记、完整性标记、区间（非等级类别）、域、角色和型。 "`策略无关`"的意思是指，标记的语法与使用它的具体策略模块无关，而同时策略模块能够完全独立地定义和使用与对象相关联的元数据的语义。 用户应用程序提供统一格式的基于字符串的标记，由使用它的策略模块负责解析其内在含义并决定其外在表示。 如果需要，应用程序可以使用多重标记元素。

内存中的标记实例被存放在由 slab 分配的`struct label`数据结构中。 该结构是一个固定长度的数组，每个元素是由一个 `void *` 指针和一个 `long`组成的联合结构。 申请标记存储的策略模块在向 MAC 注册时，将被分配一个"`slot`"值，作为框架分配给其使用的策略标记元素在整个标记存储结构中的位置索引。 而所分配的存储空间的语义则完全由该策略模块来决定：MAC 框架向策略模块提供了一系列入口函数用于对内核对象生命周期的各种事件进行控制，包括， 对象的初始化、标记的关联/创建和对象的注销。使用这些接口，可以实现诸如访问计数等存储模型。 MAC 框架总是给入口函数传入一个指向相关对象的指针和一个指向该对象标记的指针，因此，策略模块能够直接访问标记而无需知悉该对象的内部结构。 唯一的例外是进程信任状结构，指向其标记的指针必须由策略模块手动解析计算。今后的 MAC 框架实现可能会对此进行改进。

初始化入口函数通常有一个睡眠标志位，用来表明一个初始化操作是否允许中途睡眠等待； 如果不允许，则可能会失败返回，并要求撤销此次标记分配操作（乃至对象分配操作）。 例如，如果在网络栈上处理中断时因为不允许睡眠或者调用者持有一个互斥锁，就可能出现这种情况。 考虑到在处理中的网络数据包（Mbufs）上维护标记的性能损失太大，策略必须就自己对 Mbuf 进行标记的要求向 MAC 框架做出特别声明。 动态加载到系统中而又使用标记的策略必须为处理未被其初始化函数处理过的对象作好准备， 这些对象在策略加载之前就已经存在,故而无法在初始化时调用策略的相关函数进行处理。 MAC 框架向策略保证，没有被初始化的标记 slot 的值必为0或者 NULL，策略可以借此检测到未初始化的标记。 需要注意的是，因为对 Mbuf 标记的存储分配是有条件的，因此需要使用其标记的动态加载策略还可能需要处理 Mbuf 中值为 NULL 的标记指针。

对于文件系统对象的标记，MAC 框架在文件的扩展属性中为其分配永久存储。 只要可能，扩展属性的原子化的事务操作就被用于保证对 vnode 上安全标记的复合更新操作的一致性－－目前，该特性只被 UFS2 文件系统支持。 为了实现细粒度的文件系统对象标记（即每个文件系统对象一个标记），策略编写者可能选择使用一个（或者若干）扩展属性块。 为了提高性能， vnode 数据结构中有一个标记 (`v_label`)字段，用作磁盘标记的缓冲； vnode 结构实例化时，策略可以将标记值装入该缓冲，并在需要时对其进行更新。 如此，不必在每次进行访问控制检查时，均无条件地访问磁盘上的扩展属性。

| 目前，如果一个使用标记的策略允许被动态卸载，则卸载该模块之后,其状态 slot 尚无法被系统回收重用， 由此导致了 MAC 框架对标记策略卸载－重载操作数目上的严格限制。

### 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()` 用于查询由进程号指定的另一个进程的安全标记。

- `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 框架将以策略所期望的方式对其进行操作； 另外，还可能请求框架为策略分配标记状态 slot 值。

    static struct mac_policy_ops mac_policy_ops =
    {
            .mpo_destroy = mac_policy_destroy,
            .mpo_init = mac_policy_init,
            .mpo_init_bpfdesc_label = mac_policy_init_bpfdesc_label,
            .mpo_init_cred_label = mac_policy_init_label,
    /* ... */
            .mpo_check_vnode_setutimes = mac_policy_check_vnode_setutimes,
            .mpo_check_vnode_stat = mac_policy_check_vnode_stat,
            .mpo_check_vnode_write = mac_policy_check_vnode_write,
    };

如上所示，MAC 策略入口函数向量，`macpolicyops`， 将策略模块中定义的功能函数挂接到特定的入口函数地址上。 在稍后的"`入口函数参考`"小节中，将提供可用入口函数功能描述和原型的完整列表。 与模块注册相关的入口函数有两个：.mpo_destroy和.mpo_init。 当某个策略向模块框架注册操作成功时，.mpo_init将被调用，此后其他的入口函数才能被使用。 这种特殊的设计使得策略有机会根据自己的需要，进行特定的分配和初始化操作，比如对特殊数据或锁的初始化。 卸载一个策略模块时，将调用 .mpo_destroy 用来释放策略分配的内存空间或注销其申请的锁。 目前，为了防止其他入口函数被同时调用，调用上述两个入口函数的进程必须持有 MAC 策略链表的互斥锁：这种限制将被放开， 但与此同时，将要求策略必须谨慎使用内核原语,以避免由于上锁次序或睡眠造成死锁。

之所以向策略声明提供模块名字域，是为了能够唯一标识该模块，以便解析模块依赖关系。选择使用恰当的字符串作为名字。 在策略加载和卸载时，策略的完整字符串名字将经由内核日志显示给用户。另外，当向用户进程报告状态信息时也会包含该字符串。

### 6.6.2. 策略标志

在声明时提供标志参数域的机制，允许策略模块在作为模块被加载时，就自身特性向 MAC 框架提供说明。 目前，已经定义的标志有三个：

MPC_LOADTIME_FLAG_UNLOADOK

  表示该策略模块可以被卸载。 如果未提供该标志，则表示该策略模块拒绝被卸载。 那些使用安全标记的状态，而又不能在运行时释放该状态的模块可能会设置该标志。

MPC_LOADTIME_FLAG_NOTLATE

  表示该策略模块必须在系统引导过程时进行加载和初始化。 如果该标志被设置，那么在系统引导之后注册该模块的请求将被 MAC 框架所拒绝。 那些需要为大范围的系统对象进行安全标记初始化工作，而又不能处理含有未被正确初始化安全标记的对象的策略模块可能会设置该标志。

MPC_LOADTIME_FLAG_LABELMBUFS

  表示该策略模块要求为 Mbuf 指定安全标记，并且为存储其标记所需的内存空间总是提前分配好的。 缺省情况下，MAC 框架并不会为 Mbuf 分配标记存储，除非系统中注册的策略模块中至少有一个设置了该标志。 这种做法在没有策略需要对 Mbuf 做标记时，显著地提升了系统网络性能。另外，在某些特殊环境下，可以通过设置内核选项， `MAC_ALWAYS_LABEL_MBUF`，强制 MAC 框架为 Mbuf 的安全标记分配存储，而不论上述标志如何设置。

| 那些使用了 `MPC_LOADTIME_FLAG_LABELMBUFS` 标志但没有设置 `MPC_LOADTIME_FLAG_NOTLATE` 标志的 策略模块必须能够正确地处理通过入口函数传入的值为 `NULL` 的 Mbuf 安全标记指针。 这是因为那些没有分配标记存储的处理中的 Mbuf 在一个需要 Mbuf 安全标记的策略模块加载之后， 其安全标记的指针将仍然为空。 如果策略在网络子系统活跃之前被加载（即，该策略不是被推迟加载的），那么所有的 Mbuf 的标记存储的分配就可以得到保证。

### 6.6.3. 策略入口函数

MAC 框架为注册的策略提供四种类型的入口函数： 策略注册和管理入口函数； 用于处理内核对象声明周期事件，如初始化、 创建和销毁，的入口函数； 处理该策略模块感兴趣的访问控制决策事件的入口函数； 以及用于管理对象安全标记的调用入口函数。 此外， 还有一个 `mac_syscall()` 入口函数， 被策略模块用于在不注册新的系统调用的前提下， 扩展内核接口。

策略模块的编写人员除了必须清楚在进入特定入口函数之后， 哪些对象锁是可用的之外， 还应该熟知内核所采用的加锁策略。 编程人员在入口函数之内应该避免使用非叶节点锁， 并且遵循访问和修改对象时的加锁规程， 以降低导致死锁的可能性。 特别地， 程序员应该清楚， 虽然在通常情况下， 进入入口函数之后， 已经上了一些锁， 可以安全地访问对象及其安全标记， 但是这并不能保证对它们进行修改（ 包括对象本身和其安全标记） 也是安全的。 相关的上锁信息，可以参考 MAC 框架入口函数的相关文档。

策略入口函数把两个分别指向对象本身和其安全标记的指针传递给策略模块。 这样一来，即使策略并不熟悉对象内部结构，也能基于标记作出正确决策。 只有进程信任状这个对象例外：MAC 框架总是假设所有的策略模块是理解其内部结构的。

## 6.7. MAC策略入口函数参考

### 6.7.1. 通用的模块入口函数

#### 6.7.1.1. `mpo_init`

    void
                  mpo_init (struct mac_policy_conf);

参数 | 说明 | 锁定
--- | --- | ---
conf | MAC 策略定义 |

策略加载事件。当前进程正持有策略链表上的互斥锁，因此是非睡眠的，对其他内核子系统的调用也须慎重。 如果需要在策略初始化阶段进行可能造成睡眠阻塞的存储分配操作，可以将它们放在一个单独的模块 SYSINIT() 过程中集中进行。

#### 6.7.1.2. `mpo_destroy`

    void
                  mpo_destroy (struct mac_policy_conf);

参数 | 说明 | 锁定
--- | --- | ---
conf | MAC 策略定义 |

策略加载事件。必须持有策略链表互斥锁，因此需要慎重行事。

#### 6.7.1.3. `mpo_syscall`

    int
                  mpo_syscall (struct thread,
                                   int call,
                                   void *arg);

参数 | 说明 | 锁定
--- | --- | ---
td | 调用线程 |
call | 策略特有的系统调用编号 |
arg | 系统调用参数的指针 |

该入口函数提供策略复用的系统调用，这样策略模块不需要为其向用户进程提供的每一个额外服务而注册专用的系统调用。 由应用程序提供的策略注册名字来确定提供其所申请服务的特定策略，所有参数将通过该入口函数传递给被调用的策略。 当实现新服务时，安全模块必须在必要时通过 MAC 框架调用相应的访问控制检查机制。 比方说，假如一个策略实现了某种额外的信号功能，那么它应该调用相关的信号访问控制检查，以接受 MAC 框架中注册的其他策略的检查。

| 不同的模块需要并发地手动进行`copyin()`拷贝系统调用数据。

#### 6.7.1.4. `mpo_thread_userret`

    void
                  mpo_thread_userret (struct thread);

参数 | 说明 | 锁定
--- | --- | ---
td | 返回线程 |

使用该入口函数，策略模块能够在线程返回用户空间时（系统调用返回、异常返回等等）进行 MAC 相关的处理工作。 使用动态进程标记的策略需要使用该入口函数，因为在处理系统调用的过程中，并不是在任意时刻都能申请到进程锁的； 进程的标记可能表示传统的认证信息、进程历史记录或者其他数据。为使用该入口函数，对进程信任状所作的修改 可能被存放在 `p_label` ,该域受一个进程级自旋锁的保护；接下来，设置线程级的`TDF_ASTPENDING` 标志位和进程级的`PS_MACPENDM`标志位，表明将调度一个对 userret 入口函数的调用。通过该入口函数， 策略可以在相对简单的同步上下文中创建信任状的替代品。策略编程人员必须清楚，需要保证与调度一个 AST 相关的事件执行次序， 同时所执行的 AST 可能很复杂，而且在处理多线程应用程序时可能被重入。

### 6.7.2. 操作标记

#### 6.7.2.1. `mpo_init_bpfdesc_label`

    void
                  mpo_init_bpfdesc_label (struct label);

参数 | 说明 | 锁定
--- | --- | ---
label | 将被应用的新标记 |

为一个新近实例化的 bpfdesc（BPF 描述子）初始化标记。可以睡眠。

#### 6.7.2.2. `mpo_init_cred_label`

    void
                  mpo_init_cred_label (struct label);

参数 | 说明 | 锁定
--- | --- | ---
label | 将被初始化的新标记 |

为一个新近实例化的用户信任状初始化标记。可以睡眠。

#### 6.7.2.3. `mpo_init_devfsdirent_label`

    void
                  mpo_init_devfsdirent_label (struct label);

参数 | 说明 | 锁定
--- | --- | ---
label | 将被应用的新标记 |

为一个新近实例化的 devfs　表项初始化标记。可以睡眠。

#### 6.7.2.4. `mpo_init_ifnet_label`

    void
                  mpo_init_ifnet_label (struct label);

参数 | 说明 | 锁定
--- | --- | ---
label | 将被应用的新标记 |

为一个新近实例化的网络接口初始化标记。可以睡眠。

#### 6.7.2.5. `mpo_init_ipq_label`

    void
                  mpo_init_ipq_label (struct label,
                                           int flag);

参数 | 说明 | 锁定
--- | --- | ---
label | 将被应用的新标记 |
flag | 睡眠/不睡眠 malloc(9); 参见下文 |

为一个新近实例化的 IP 分片重组队列初始化标记。其中的flag域可能取M_WAITOK 或M_NOWAIT之一，用来避免在该初始化调用中因为 malloc(9) 而进入睡眠。IP 分片重组队列的分配操作通常是在 对性能有严格要求的环境下进行的，因此实现代码必须小心地避免睡眠和长时间的操作。IP 分片重组队列分配操作失败时上述入口函数将失败返回。

#### 6.7.2.6. `mpo_init_mbuf_label`

    void
                  mpo_init_mbuf_label (int flag,
                                            struct label);

参数 | 说明 | 锁定
--- | --- | ---
flag | 睡眠/不睡眠 malloc(9); 参见下文 |
label | 将被初始化的策略标记 |

为一个新近实例化的 mbuf 数据包头部（mbuf）初始化标记。 其中的flag的值可能取M_WAITOK和M_NOWAIT之一， 用来避免在该初始化调用中因为 malloc(9) 而进入睡眠。Mbuf 头部的分配操作常常在对性能有严格要求的环境下被频繁执行， 因此实现代码必须小心地避免睡眠和长时间的操作。上述入口函数在 Mbuf 头部分配操作失败时将失败返回。

#### 6.7.2.7. `mpo_init_mount_label`

    void
                  mpo_init_mount_label (struct label,
                                             struct label);

参数 | 说明 | 锁定
--- | --- | ---
mntlabel | 将被初始化的mount 结构策略标记 |
fslabel | 将被初始化的文件系统策略标记 |

为一个新近实例化的 mount 点初始化标记。可以睡眠。

#### 6.7.2.8. `mpo_init_mount_fs_label`

    void
                  mpo_init_mount_fs_label (struct label);

参数 | 说明 | 锁定
--- | --- | ---
label | 将被初始化的标记 |

为一个新近加载的文件系统初始化标记。可以睡眠。

#### 6.7.2.9. `mpo_init_pipe_label`

    void
                  mpo_init_pipe_label (struct);

参数 | 说明 | 锁定
--- | --- | ---
label | 将被填写的标记 |

为一个刚刚实例化的管道初始化安全标记。可以睡眠。

#### 6.7.2.10. `mpo_init_socket_label`

    void
                  mpo_init_socket_label (struct label,
                                              int flag);

参数 | 说明 | 锁定
--- | --- | ---
label | 将被初始化的新标记 |
flag | malloc(9) flags |

为一个刚刚实例化的套接字初始化安全标记。其中的 flag 域的值必须被指定为 M_WAITOK和M_NOWAIT之一，以避免在该初始化程中使用可能睡眠的malloc(9) 。

#### 6.7.2.11. `mpo_init_socket_peer_label`

    void
                  mpo_init_socket_peer_label (struct label,
                                                   int flag);

参数 | 说明 | 锁定
--- | --- | ---
label | 将被初始化的新标记 |
flag | malloc(9) flags |

为刚刚实例化的套接字对等体进行标记的初始化。其中的 flag 域的值必须被指定为 M_WAITOK 和 M_NOWAIT 之一，以避免在该初始化程中使用可能睡眠的 malloc(9)。

#### 6.7.2.12. `mpo_init_proc_label`

    void
                  mpo_init_proc_label (struct label);

参数 | 说明 | 锁定
--- | --- | ---
label | 将被初始化的新标记 |

为一个刚刚实例化的进程初始化安全标记。可以睡眠。

#### 6.7.2.13. `mpo_init_vnode_label`

    void
                  mpo_init_vnode_label (struct label);

参数 | 说明 | 锁定
--- | --- | ---
label | 将被初始化的新标记 |

为一个刚刚实例化的 vnode 初始化安全标记。可以睡眠。

#### 6.7.2.14. `mpo_destroy_bpfdesc_label`

    void
                  mpo_destroy_bpfdesc_label (struct label);

参数 | 说明 | 锁定
--- | --- | ---
label | bpfdesc 标记 |

销毁一个 BPF 描述子上的标记。在该入口函数中，策略应当释放所有在内部分配与 label 相关联的存储空间，以便销毁该标记。

#### 6.7.2.15. `mpo_destroy_cred_label`

    void
                  mpo_destroy_cred_label (struct label);

参数 | 说明 | 锁定
--- | --- | ---
label | 将被销毁的标记 |

销毁一个信任状上的标记。在该入口函数中，策略应当释放所有在内部分配的与 label 相关联的存储空间，以便销毁该标记。

#### 6.7.2.16. `mpo_destroy_devfsdirent_label`

    void
                  mpo_destroy_devfsdirent_label (struct label);

参数 | 说明 | 锁定
--- | --- | ---
label | 将被销毁的标记 |

销毁一个 devfs 表项上的标记。在该入口函数中，策略应当释放所有在内部分配的与 label 相关联的存储空间，以便销毁该标记。

#### 6.7.2.17. `mpo_destroy_ifnet_label`

    void
                  mpo_destroy_ifnet_label (struct label);

参数 | 说明 | 锁定
--- | --- | ---
label | 将被销毁的标记 |

销毁与一个已删除接口相关联的标记。在该入口函数中，策略应当释放所有在内部分配的与 label 相关联的存储空间，以便销毁该标记。

#### 6.7.2.18. `mpo_destroy_ipq_label`

    void
                  mpo_destroy_ipq_label (struct label);

参数 | 说明 | 锁定
--- | --- | ---
label | 将被销毁的标记 |

销毁与一个 IP 分片队列相关联的标记。在该入口函数中，策略应当释放所有在内部分配的与 label 相关联的存储空间，以便销毁该标记。

#### 6.7.2.19. `mpo_destroy_mbuf_label`

    void
                  mpo_destroy_mbuf_label (struct label);

参数 | 说明 | 锁定
--- | --- | ---
label | 将被销毁的标记 |

销毁与一个 Mbuf 相关联的标记。在该入口函数中，策略应当释放所有在内部分配的与 label 相关联的存储空间，以便销毁该标记。

#### 6.7.2.20. `mpo_destroy_mount_label`

    void
                  mpo_destroy_mount_label (struct label);

参数 | 说明 | 锁定
--- | --- | ---
label | 将被销毁的 Mount 点标记 |

销毁与一个 mount 点相关联的标记。在该入口函数中，策略应当释放所有在内部分配的与 mntlabel 相关联的存储空间，以便销毁该标记。

#### 6.7.2.21. `mpo_destroy_mount_label`

    void
                  mpo_destroy_mount_label (struct label,
                                                struct label);

参数 | 说明 | 锁定
--- | --- | ---
mntlabel | 将被销毁的 Mount 点标记 |
fslabel | File system label being destroyed |

销毁与一个 mount 点相关联的标记。在该入口函数中，策略应当释放所有在内部分配的，与 mntlabel 和fslabel 相关联的存储空间，以便销毁该标记。

#### 6.7.2.22. `mpo_destroy_socket_label`

    void
                  mpo_destroy_socket_label (struct label);

参数 | 说明 | 锁定
--- | --- | ---
label | 将被销毁的套接字标记 |

销毁与一个套接字相关联的标记。在该入口函数中，策略应当释放所有在内部分配的，与 label 相关联的存储空间，以便销毁该标记。

#### 6.7.2.23. `mpo_destroy_socket_peer_label`

    void
                  mpo_destroy_socket_peer_label (struct label);

参数 | 说明 | 锁定
--- | --- | ---
peerlabel | 将被销毁的套接字对等实体标记 |

销毁与一个套接字相关联的对等实体标记。在该入口函数中，策略应当释放所有在内部分配的，与 label 相关联的存储空间，以便销毁该标记。

#### 6.7.2.24. `mpo_destroy_pipe_label`

    void
                  mpo_destroy_pipe_label (struct label);

参数 | 说明 | 锁定
--- | --- | ---
label | 管道标记 |

销毁一个管道的标记。在该入口函数中，策略应当释放所有在内部分配的，与 label 相关联的存储空间，以便销毁该标记。

#### 6.7.2.25. `mpo_destroy_proc_label`

    void
                  mpo_destroy_proc_label (struct label);

参数 | 说明 | 锁定
--- | --- | ---
label | 进程标记 |

销毁一个进程的标记。在该入口函数中，策略应当释放所有在内部分配的，与 label 相关联的存储空间，以便销毁该标记。

#### 6.7.2.26. `mpo_destroy_vnode_label`

    void
                  mpo_destroy_vnode_label (struct label);

参数 | 说明 | 锁定
--- | --- | ---
label | 进程标记 |

销毁一个 vnode 的标记。在该入口函数中，策略应当释放所有在内部分配的，与 label 相关联的存储空间，以便销毁该标记。

#### 6.7.2.27. `mpo_copy_mbuf_label`

    void
                  mpo_copy_mbuf_label (struct label,
                                            struct label);

参数 | 说明 | 锁定
--- | --- | ---
src | 源标记 |
dest | 目标标记 |

将 src 中的标记信息拷贝到 dest中。

#### 6.7.2.28. `mpo_copy_pipe_label`

    void
                  mpo_copy_pipe_label (struct label,
                                            struct label);

参数 | 说明 | 锁定
--- | --- | ---
src | 源标记 |
dest | 目标标记 |

将 src 中的标记信息拷贝至 dest。

#### 6.7.2.29. `mpo_copy_vnode_label`

    void
                  mpo_copy_vnode_label (struct label,
                                             struct label);

参数 | 说明 | 锁定
--- | --- | ---
src | 源标记 |
dest | 目标标记 |

将 src 中的标记信息拷贝至 dest。

#### 6.7.2.30. `mpo_externalize_cred_label`

    int
                  mpo_externalize_cred_label (struct label *label,
                                                  char *element_name,
                                                  struct sbuf *sb,
                                                  int *claimed);

参数 | 说明 | 锁定
--- | --- | ---
label | 将用外部形式表示的标记 |
element_name | 需要外部表示标记的策略的名字 |
sb | 用来存放标记的文本表示形式的字符buffer |
claimed | 如果可以填充element_data 域，则其数值递增 |

根据传入的标记结构，产生一个以外部形式表示的标记。 一个外部形式标记，是标记内容的文本表示，它由用户级的应用程序使用，是用户可读的。 目前的MAC实现方案将依次调用策略的相应入口函数，因此， 具体策略的实现代码，需要在填写sb之前，先检查element_name中指定的名字。 如果element_name中的内容与你的策略名字不相符，则直接返回0。 仅当转换标记数据的过程中出现错误时，才返回非0值。 一旦策略决定填写element_data，递增*claim的数值。

#### 6.7.2.31. `mpo_externalize_ifnet_label`

    int
                  mpo_externalize_ifnet_label (struct label *label,
                                                   char *element_name,
                                                   struct sbuf *sb,
                                                   int *claimed);

参数 | 说明 | 锁定
--- | --- | ---
label | 将用外部形式表示的标记 |
element_name | 需要外部表示标记的策略的名字 |
sb | 用来存放标记的文本表示形式的字符buffer |
claimed | 如果可以填充element_data 域，则其数值递增 |

根据传入的标记结构，产生一个以外部形式表示的标记。 一个外部形式标记，是标记内容的文本表示，它由用户级的应用程序使用，是用户可读的。 目前的MAC实现方案将依次调用策略的相应入口函数，因此， 具体策略的实现代码，需要在填写sb之前，先检查element_name中指定的名字。 如果element_name中的内容与你的策略名字不相符，则直接返回0。 仅当转换标记数据的过程中出现错误时，才返回非0值。 一旦策略决定填写element_data，递增*claim的数值。

#### 6.7.2.32. `mpo_externalize_pipe_label`

    int
                  mpo_externalize_pipe_label (struct label *label,
                                                  char *element_name,
                                                  struct sbuf *sb,
                                                  int *claimed);

参数 | 说明 | 锁定
--- | --- | ---
label | 将用外部形式表示的标记 |
element_name | 需要外部表示标记的策略的名字 |
sb | 用来存放标记的文本表示形式的字符buffer |
claimed | 如果可以填充element_data 域，则其数值递增 |

根据传入的标记结构，产生一个以外部形式表示的标记。 一个外部形式标记，是标记内容的文本表示，它由用户级的应用程序使用，是用户可读的。 目前的MAC实现方案将依次调用策略的相应入口函数，因此， 具体策略的实现代码，需要在填写sb之前，先检查element_name中指定的名字。 如果element_name中的内容与你的策略名字不相符，则直接返回0。 仅当转换标记数据的过程中出现错误时，才返回非0值。 一旦策略决定填写element_data，递增*claim的数值。

#### 6.7.2.33. `mpo_externalize_socket_label`

    int
                  mpo_externalize_socket_label (struct label *label,
                                                    char *element_name,
                                                    struct sbuf *sb,
                                                    int *claimed);

参数 | 说明 | 锁定
--- | --- | ---
label | 将用外部形式表示的标记 |
element_name | 需要外部表示标记的策略的名字 |
sb | 用来存放标记的文本表示形式的字符buffer |
claimed | 如果可以填充element_data 域，则其数值递增 |

根据传入的标记结构，产生一个以外部形式表示的标记。 一个外部形式标记，是标记内容的文本表示，它由用户级的应用程序使用，是用户可读的。 目前的MAC实现方案将依次调用策略的相应入口函数，因此， 具体策略的实现代码，需要在填写sb之前，先检查element_name中指定的名字。 如果element_name中的内容与你的策略名字不相符，则直接返回0。 仅当转换标记数据的过程中出现错误时，才返回非0值。 一旦策略决定填写element_data，递增*claim的数值。

#### 6.7.2.34. `mpo_externalize_socket_peer_label`

    int
                  mpo_externalize_socket_peer_label (struct label *label,
                                                         char *element_name,
                                                         struct sbuf *sb,
                                                         int *claimed);

参数 | 说明 | 锁定
--- | --- | ---
label | 将用外部形式表示的标记 |
element_name | 需要外部表示标记的策略的名字 |
sb | 用来存放标记的文本表示形式的字符buffer |
claimed | 如果可以填充element_data 域，则其数值递增 |

根据传入的标记结构，产生一个以外部形式表示的标记。 一个外部形式标记，是标记内容的文本表示，它由用户级的应用程序使用，是用户可读的。 目前的MAC实现方案将依次调用策略的相应入口函数，因此， 具体策略的实现代码，需要在填写sb之前，先检查element_name中指定的名字。 如果element_name中的内容与你的策略名字不相符，则直接返回0。 仅当转换标记数据的过程中出现错误时，才返回非0值。 一旦策略决定填写element_data，递增*claim的数值。

#### 6.7.2.35. `mpo_externalize_vnode_label`

    int
                  mpo_externalize_vnode_label (struct label *label,
                                                   char *element_name,
                                                   struct sbuf *sb,
                                                   int *claimed);

参数 | 说明 | 锁定
--- | --- | ---
label | 将用外部形式表示的标记 |
element_name | 需要外部表示标记的策略的名字 |
sb | 用来存放标记的文本表示形式的字符buffer |
claimed | 如果可以填充element_data 域，则其数值递增 |

根据传入的标记结构，产生一个以外部形式表示的标记。 一个外部形式标记，是标记内容的文本表示，它由用户级的应用程序使用，是用户可读的。 目前的MAC实现方案将依次调用策略的相应入口函数，因此， 具体策略的实现代码，需要在填写sb之前，先检查element_name中指定的名字。 如果element_name中的内容与你的策略名字不相符，则直接返回0。 仅当转换标记数据的过程中出现错误时，才返回非0值。 一旦策略决定填写element_data，递增*claim的数值。

#### 6.7.2.36. `mpo_internalize_cred_label`

    int
                  mpo_internalize_cred_label (struct label *label,
                                                  char *element_name,
                                                  char *element_data,
                                                  int *claimed);

参数 | 说明 | 锁定
--- | --- | ---
label | 将被填充的标记 |
element_name | 需要内部表示标记的策略的名字 |
element_data | 需要被转换的文本数据 |
claimed | 如果数据被正确转换，则其数值递增 |

根据一个文本形式的外部表示标记数据，创建一个内部形式的标记结构。 目前的MAC方案将依次调用所有策略的相关入口函数，来响应标记的内部转换请求， 因此，实现代码必须首先通过比较element_name中的内容和自己的策略名字， 来确定是否需要转换element_data中存放的数据。 类似的，如果名字不匹配或者数据转换操作成功，该函数返回0，并递增*claimed的值。

#### 6.7.2.37. `mpo_internalize_ifnet_label`

    int
                  mpo_internalize_ifnet_label (struct label *label,
                                                   char *element_name,
                                                   char *element_data,
                                                   int *claimed);

参数 | 说明 | 锁定
--- | --- | ---
label | 将被填充的标记 |
element_name | 需要内部表示标记的策略的名字 |
element_data | 需要被转换的文本数据 |
claimed | 如果数据被正确转换，则其数值递增 |

根据一个文本形式的外部表示标记数据，创建一个内部形式的标记结构。 目前的MAC方案将依次调用所有策略的相关入口函数，来响应标记的内部转换请求， 因此，实现代码必须首先通过比较element_name中的内容和自己的策略名字， 来确定是否需要转换element_data中存放的数据。 类似的，如果名字不匹配或者数据转换操作成功，该函数返回0，并递增*claimed的值。

#### 6.7.2.38. `mpo_internalize_pipe_label`

    int
                  mpo_internalize_pipe_label (struct label *label,
                                                  char *element_name,
                                                  char *element_data,
                                                  int *claimed);

参数 | 说明 | 锁定
--- | --- | ---
label | 将被填充的标记 |
element_name | 需要内部表示标记的策略的名字 |
element_data | 需要被转换的文本数据 |
claimed | 如果数据被正确转换，则其数值递增 |

根据一个文本形式的外部表示标记数据，创建一个内部形式的标记结构。 目前的MAC方案将依次调用所有策略的相关入口函数，来响应标记的内部转换请求， 因此，实现代码必须首先通过比较element_name中的内容和自己的策略名字， 来确定是否需要转换element_data中存放的数据。 类似的，如果名字不匹配或者数据转换操作成功，该函数返回0，并递增*claimed的值。

#### 6.7.2.39. `mpo_internalize_socket_label`

    int
                  mpo_internalize_socket_label (struct label *label,
                                                    char *element_name,
                                                    char *element_data,
                                                    int *claimed);

参数 | 说明 | 锁定
--- | --- | ---
label | 将被填充的标记 |
element_name | 需要内部表示标记的策略的名字 |
element_data | 需要被转换的文本数据 |
claimed | 如果数据被正确转换，则其数值递增 |

根据一个文本形式的外部表示标记数据，创建一个内部形式的标记结构。 目前的MAC方案将依次调用所有策略的相关入口函数，来响应标记的内部转换请求， 因此，实现代码必须首先通过比较element_name中的内容和自己的策略名字， 来确定是否需要转换element_data中存放的数据。 类似的，如果名字不匹配或者数据转换操作成功，该函数返回0，并递增*claimed的值。

#### 6.7.2.40. `mpo_internalize_vnode_label`

    int
                  mpo_internalize_vnode_label (struct label *label,
                                                   char *element_name,
                                                   char *element_data,
                                                   int *claimed);

参数 | 说明 | 锁定
--- | --- | ---
label | 将被填充的标记 |
element_name | 需要内部表示标记的策略的名字 |
element_data | 需要被转换的文本数据 |
claimed | 如果数据被正确转换，则其数值递增 |

根据一个文本形式的外部表示标记数据，创建一个内部形式的标记结构。 目前的MAC方案将依次调用所有策略的相关入口函数，来响应标记的内部转换请求， 因此，实现代码必须首先通过比较element_name中的内容和自己的策略名字， 来确定是否需要转换element_data中存放的数据。 类似的，如果名字不匹配或者数据转换操作成功，该函数返回0，并递增*claimed的值。

### 6.7.3. 标记事件

策略模块使用MAC 框架提供的"`标记事件`"类入口函数，对内核对象的标记进行操作。策略模块将感兴趣的被标记内核对象的相关生命周期事件 注册在恰当的入口点上。对象的初始化、创建和销毁事件均提供了钩子点。在某些对象上还可以实现重新标记，即，允许用户进程改变对象上的标记值。 对某些对象可以实现其特定的对象事件，比如与 IP 重组相关的标记事件。一个典型的被标记对象在其生命周期中将拥有下列入口函数：

    标记初始化                     o
    （对象相关的等待）              \
    标记创建                         o
                                      \
    重新标记事件，                     o----.
    各种对象相关的，                   |     |
    访问控制事件                       ~----o
                                              \
    标记销毁                                   o

使用标记初始化入口函数，策略可以以一种统一的、与对象使用环境无关的方式设置标记的初始值。 分配给一个策略的缺省 slot 值为0，这样不使用标记的策略可能并不需要执行专门的初始化操作。

标记的创建事件发生在将一个内核数据结构同一个真实的内核对象相关联（内核对象实例化）的时刻。 例如，在真正被使用之前，在一个缓冲池内已分配的 mbuf 数据结构，将保持为"`未使用`"状态。 因此，mbuf 的分配操作将导致针对该 mbuf 的标记初始化操作，而 mbuf 的创建操作则被推迟到该 mbuf 真正与一个数据报相关联的时刻。 通常，调用者将会提供创建事件的上下文，包括创建环境、创建过程中涉及的其他对象的标记等。例如，当一个套接字创建一个 mbuf 时， 除了新创建的 mbuf 及其标记之外，作为创建者的套接字与其标记也被提交给策略检查。 不提倡在创建对象时就为其分配内存的原因有两个：创建操作可能发生在对性能有严格要求的内核接口上； 而且，因为创建调用不允许失败，所以无法报告内存分配失败。

对象特有的事件一般不会引发其他的标记事件，但是在对象上下文发生改变时，策略使用它们可以对相关标记进行修改或更新操作。 例如，在MAC_UPDATE_IPQ 入口函数之内，某个 IP 分片重组队列的标记可能会因为队列中接收了新的 mbuf 而被更新。

访问控制事件将在后续章节中详细讨论。

策略通过执行标记销毁操作，释放为其分配的存储空间或维护的状态，之后内核才可以重用或者释放对象的内核数据结构。

除了与特定内核对象绑定的普通标记之外，还有一种额外的标记类型：临时标记。这些标记用于存放由用户进程提交的更新信息。 它们的初始化和销毁操作与其他标记一样，只是创建事件，MAC_INTERNALIZE，略有不同： 该函数接受用户提交的标记，负责将其转化为内核表示形式。

#### 6.7.3.1. 文件系统对象标记事件操作

##### 6.7.3.1.1. `mpo_associate_vnode_devfs`

    void
                    mpo_associate_vnode_devfs (struct mount,
                                                    struct label,
                                                    struct devfs_dirent,
                                                    struct label,
                                                    struct vnode,
                                                    struct label);

参数 | 说明 | 锁定
--- | --- | ---
mp | Devfs 挂载点 |
fslabel | Devfs 文件系统标记 (`mp-mnt_fslabel`) |
de | Devfs 目录项 |
delabel | 与 de 相关联的策略标记 |
vp | 与 de 相关联的 vnode |
vlabel | 与 vp 相关联的策略标记 |

根据参数 de 传入的 devfs 目录项及其标记信息，为一个新近创建的 devfs vnode 填充标记（vlabel）。

##### 6.7.3.1.2. `mpo_associate_vnode_extattr`

    int
                    mpo_associate_vnode_extattr (struct mount,
                                                     struct label,
                                                     struct vnode,
                                                     struct label);

参数 | 说明 | 锁定
--- | --- | ---
mp | 文件系统挂载点 |
fslabel | 文件系统标记 |
vp | 将被标记的 vnode |
vlabel | 与 vp 相关联的策略标记 |

从文件系统扩展属性中读取 vp 的标记。成功，返回 `0`。 不成功，则在 `errno` 指定的相应的错误编码。 如果文件系统不支持扩展属性的读取操作，则可以考虑将 fslabel 拷贝至 vlabel。

##### 6.7.3.1.3. `mpo_associate_vnode_singlelabel`

    void
                    mpo_associate_vnode_singlelabel (struct mount,
                                                          struct label,
                                                          struct vnode,
                                                          struct label);

参数 | 说明 | 锁定
--- | --- | ---
mp | 文件系统挂载点 |
fslabel | 文件系统标记 |
vp | 将被标记的 vnode |
vlabel | 与 vp 相关联的策略标记 |

在非多重标记文件系统上，使用该入口函数，根据文件系统标记，fslabel， 为 vp 设置策略标记。

##### 6.7.3.1.4. `mpo_create_devfs_device`

    void
                    mpo_create_devfs_device (dev_t dev,
                                                  struct devfs_dirent,
                                                  struct label);

参数 | 说明 | 锁定
--- | --- | ---
dev | devfs_dirent 对应的设备 |
devfs_dirent | 将被标记的 Devfs 目录项 |
label | 将被填写的 devfs_dirent 标记 |

为传入设备新建的 devfs_dirent 填写标记。该函数将在设备文件系统加载、重构或添加新设备时被调用。

##### 6.7.3.1.5. `mpo_create_devfs_directory`

    void
                    mpo_create_devfs_directory (char *dirname,
                                                     int dirnamelen,
                                                     struct devfs_dirent,
                                                     struct label);

参数 | 说明 | 锁定
--- | --- | ---
dirname | 新建目录的名字 |
namelen | 字符串 dirname 的长度 |
devfs_dirent | 新建目录在 Devfs 中对应的目录项 |

为传入目录参数的新建 devfs_dirent 填写标记。该函数将在加载、重构设备文件系统，或者添加一个需要指定目录结构的新设备时被调用。

##### 6.7.3.1.6. `mpo_create_devfs_symlink`

    void
                    mpo_create_devfs_symlink (struct ucred,
                                                   struct mount,
                                                   struct devfs_dirent,
                                                   struct label,
                                                   struct devfs_dirent,
                                                   struct label);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
mp | devfs 挂载点 |
dd | 链接目标 |
ddlabel | 与 dd 相关联的标记 |
de | 符号链接项 |
delabel | 与 de 相关联的策略标记 |

为新近创建的 devfs(5) 符号链接项填写标记（delabel）。

##### 6.7.3.1.7. `mpo_create_vnode_extattr`

    int
                    mpo_create_vnode_extattr (struct ucred,
                                                  struct mount,
                                                  struct label,
                                                  struct vnode,
                                                  struct label,
                                                  struct vnode,
                                                  struct label,
                                                  struct componentname);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
mount | 文件系统挂载点 |
label | 文件系统标记 |
dvp | 父目录 vnode |
dlabel | 与 dvp 相关联的策略标记 |
vp | 新创建的 vnode |
vlabel | 与 vp 相关联的策略标记 |
cnp | vp中的子域名字 |

将 vp 的标记写入文件扩展属性。成功，将标记填入 vlabel， 并返回 0。否则，返回对应的错误编码。

##### 6.7.3.1.8. `mpo_create_mount`

    void
                    mpo_create_mount (struct ucred,
                                           struct mount,
                                           struct label,
                                           struct label);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
mp | 客体；将被挂载的文件系统 |
mntlabel | 将被填写的 mp 的策略标记 |
fslabel | 将被挂载到 mp 的文件系统的策略标记。 |

为传入的主体信任状所创建的挂载点填写标记。该函数将在文件系统挂载时被调用。

##### 6.7.3.1.9. `mpo_create_root_mount`

    void
                    mpo_create_root_mount (struct ucred,
                                                struct mount,
                                                struct label,
                                                struct label);

参数 | 说明 | 锁定
--- | --- | ---
见 `mpo_create_mount`. |  |

为传入的主体信任状所创建的挂载点填写标记。该函数将在挂载根文件系统时，mpo_create_mount; 之后被调用。

##### 6.7.3.1.10. `mpo_relabel_vnode`

    void
                    mpo_relabel_vnode (struct ucred,
                                            struct vnode,
                                            struct label,
                                            struct label);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
vp | 将被重新标记的 vnode |
vnodelabel | vp 现有的策略标记 |
newlabel | 将取代vnodelabel的新（可能只是部分）标记 |

根据传入的新标记和主体信任状，更新参数 vnode 的标记。

##### 6.7.3.1.11. `mpo_setlabel_vnode_extattr`

    int
                    mpo_setlabel_vnode_extattr (struct ucred,
                                                    struct vnode,
                                                    struct label,
                                                    struct label);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
vp | 写出标记所对应的 vnode |
vlabel | vp的策略标记 |
intlabel | 将被写入磁盘的标记 |

将参数 intlabel 给出的标记信息写入指定 vnode 的扩展属性。 该函数被 `vop_stdcreatevnode_ea` 所调用。

##### 6.7.3.1.12. `mpo_update_devfsdirent`

    void
                    mpo_update_devfsdirent (struct devfs_dirent,
                                                 struct label,
                                                 struct vnode,
                                                 struct label);

参数 | 说明 | 锁定
--- | --- | ---
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`

    void
                    mpo_create_mbuf_from_socket (struct socket,
                                                      struct label,
                                                      struct mbuf *m,
                                                      struct label);

参数 | 说明 | 锁定
--- | --- | ---
socket | 套接字 | 套接字锁定 WIP
socketlabel | socket 的策略标记 |
m | 客体；mbuf |
mbuflabel | 将被填写的 m 的策略标记 |

根据传入的套接字标记为新创建的mbuf头部设置标记。 每当套接字产生一个新的数据报或者消息，并将其存储在参数 mbuf 中时，将调用该函数。

##### 6.7.3.2.2. `mpo_create_pipe`

    void
                    mpo_create_pipe (struct ucred,
                                          struct pipe,
                                          struct label);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
pipe | 管道 |
pipelabel | pipe 的策略标记 |

根据传入的主体信任状参数，设置新建管道的标记。每当一个新管道被创建，该函数将被调用。

##### 6.7.3.2.3. `mpo_create_socket`

    void
                    mpo_create_socket (struct ucred,
                                            struct socket,
                                            struct label);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 | 不可改变
so | 客体；将被标记的套接字 |
socketlabel | 将被填写的 so 的标记 |

根据传入的主体信任状参数，设置新建套接字的标记。每当新建一个套接字，该函数将被调用。

##### 6.7.3.2.4. `mpo_create_socket_from_socket`

    void
                    mpo_create_socket_from_socket (struct socket,
                                                        struct label,
                                                        struct socket,
                                                        struct label);

参数 | 说明 | 锁定
--- | --- | ---
oldsocket | 监听套接字 |
oldsocketlabel | oldsocket 的策略标记 |
newsocket | 新建套接字 |
newsocketlabel | newsocket 的策略标记 |

根据 listen(2) 套接字 oldsocket， 为新建 accept(2) 的套接字 newsocket，设置标记。

##### 6.7.3.2.5. `mpo_relabel_pipe`

    void
                    mpo_relabel_pipe (struct ucred,
                                           struct pipe,
                                           struct label,
                                           struct label);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
pipe | 管道 |
oldlabel | pipe 的当前策略标记 |
newlabel | 将为pipe 设置的新的策略标记 |

为pipe设置新标记newlabel。

##### 6.7.3.2.6. `mpo_relabel_socket`

    void
                    mpo_relabel_socket (struct ucred,
                                             struct socket,
                                             struct label,
                                             struct label);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 | 不可改变
so | 客体；套接字 |
oldlabel | so 的当前标记 |
newlabel | 将为socket 设置的新标记 |

根据传入的标记参数，对套接字的当前标记进行更新。

##### 6.7.3.2.7. `mpo_set_socket_peer_from_mbuf`

    void
                    mpo_set_socket_peer_from_mbuf (struct mbuf,
                                                        struct label,
                                                        struct label,
                                                        struct label);

参数 | 说明 | 锁定
--- | --- | ---
mbuf | 从套接字接收到的第一个数据报 |
mbuflabel | mbuf 的标记 |
oldlabel | 套接字的当前标记 |
newlabel | 将为套接字填写的策略标记 |

根据传入的 mbuf 标记，设置某个 stream 套接字的对等标志。 除Unix域的套接字之外，每当一个 stream 套接字接收到第一个数据报时，该函数将被调用。

##### 6.7.3.2.8. `mpo_set_socket_peer_from_socket`

    void
                    mpo_set_socket_peer_from_socket (struct socket,
                                                          struct label,
                                                          struct socket,
                                                          struct label);

参数 | 说明 | 锁定
--- | --- | ---
oldsocket | 本地套接字 |
oldsocketlabel | oldsocket 的策略标记 |
newsocket | 对等套接字 |
newsocketpeerlabel | 将为newsocket填写的策略标记 |

根据传入的远程套接字端点，为一个 stream UNIX 与套接字设置对等标记。 每当相应的套接字对之间进行连接时，该函数将在两端分别被调用。

#### 6.7.3.3. Network Object Labeling Event Operations

##### 6.7.3.3.1. `mpo_create_bpfdesc`

    void
                    mpo_create_bpfdesc (struct ucred,
                                             struct bpf_d,
                                             struct label);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 | 不可改变
bpf_d | 客体；bpf 描述子 |
bpf | 将为bpf_d填写的策略标记 |

根据传入的主体信任状参数，为新建的 BPF 描述子设置标记。 当进程打开 BPF 设备节点时，该函数将被调用。

##### 6.7.3.3.2. `mpo_create_ifnet`

    void
                    mpo_create_ifnet (struct ifnet,
                                           struct label);

参数 | 说明 | 锁定
--- | --- | ---
ifnet | 网络接口 |
ifnetlabel | 将为ifnet填写的策略标记 |

为新建的网络接口设置标记。该函数在以下情况下被调用： 当一个新的物理接口变为可用时，或者当一个伪接口在引导时或由于某个用户操作而实例化时。

##### 6.7.3.3.3. `mpo_create_ipq`

    void
                    mpo_create_ipq (struct mbuf,
                                         struct label,
                                         struct ipq,
                                         struct label);

参数 | 说明 | 锁定
--- | --- | ---
fragment | 第一个被接收的 IP 分片 |
fragmentlabel | fragment 的策略标记 |
ipq | 将被标记的 IP 重组队列 |
ipqlabel | 将为ipq填写的策略标记 |

根据第一个接收到的分片的 mbuf 头部信息，为新建的 IP 分片重组队列设置标记。

##### 6.7.3.3.4. `mpo_create_datagram_from_ipq`

    void
                    mpo_create_create_datagram_from_ipq (struct ipq,
                                                              struct label,
                                                              struct mbuf,
                                                              struct label);

参数 | 说明 | 锁定
--- | --- | ---
ipq | IP 重组队列 |
ipqlabel | ipq 的策略标记 |
datagram | 将被标记的数据报 |
datagramlabel | 将为datagramlabel填写的策略标记 |

根据 IP 分片重组队列，为刚刚重组完毕的 IP 数据报设置标记。

##### 6.7.3.3.5. `mpo_create_fragment`

    void
                    mpo_create_fragment (struct mbuf,
                                              struct label,
                                              struct mbuf,
                                              struct label);

参数 | 说明 | 锁定
--- | --- | ---
datagram | 数据报 |
datagramlabel | datagram 的策略标记 |
fragment | 将被标记的分片 |
fragmentlabel | 将为datagram填写的策略标记 |

根据数据报所对应的 mbuf 头部信息，为其新建的分片的 mbuf 头部设置标记。

##### 6.7.3.3.6. `mpo_create_mbuf_from_mbuf`

    void
                    mpo_create_mbuf_from_mbuf (struct mbuf,
                                                    struct label,
                                                    struct mbuf,
                                                    struct label);

参数 | 说明 | 锁定
--- | --- | ---
oldmbuf | 已有的（源）mbuf |
oldmbuflabel | oldmbuf 的策略标记 |
newmbuf | 将被标记的新建 mbuf |
newmbuflabel | 将为newmbuf填写的策略标记 |

根据某个现有数据报的 mbuf 头部信息，为新建数据报的 mbuf 头部设置标记。在许多条件下将会调用该函数， 比如，由于对齐要求而重新分配某个 mbuf 时。

##### 6.7.3.3.7. `mpo_create_mbuf_linklayer`

    void
                    mpo_create_mbuf_linklayer (struct ifnet,
                                                    struct label,
                                                    struct mbuf,
                                                    struct label);

参数 | 说明 | 锁定
--- | --- | ---
ifnet | 网络接口 |
ifnetlabel | ifnet 的策略标记 |
mbuf | 新建数据报的 mbuf 头部 |
mbuflabel | 将为mbuf填写的策略标记 |

为在给定接口上由于某个链路层响应而新建的数据报的mbuf头部设置标记。 该函数将在若干条件下被调用，比如当IPv4和IPv6协议栈在响应ARP或者ND6时。

##### 6.7.3.3.8. `mpo_create_mbuf_from_bpfdesc`

    void
                    mpo_create_mbuf_from_bpfdesc (struct bpf_d,
                                                       struct label,
                                                       struct mbuf,
                                                       struct label);

参数 | 说明 | 锁定
--- | --- | ---
bpf_d | BPF 描述子 |
bpflabel | bpflabel 的策略标记 |
mbuf | 将被标记的新建 mbuf |
mbuflabel | 将为mbuf填写的策略标记 |

为使用参数 BPF 描述子创建的新数据报的 mbuf 头部设置标记。 当对参数 BPF 描述子所关联的 BPF 设备进行写操作时，该函数将被调用。

##### 6.7.3.3.9. `mpo_create_mbuf_from_ifnet`

    void
                    mpo_create_mbuf_from_ifnet (struct ifnet,
                                                     struct label,
                                                     struct mbuf,
                                                     struct label);

参数 | 说明 | 锁定
--- | --- | ---
ifnet | 网络接口 |
ifnetlabel | ifnetlabel 的策略标记 |
mbuf | 新建数据报的 mbuf 头部 |
mbuflabel | 将为mbuf填写的策略标记 |

为从网络接口参数创建的数据报的 mbuf 头部设置标记。

##### 6.7.3.3.10. `mpo_create_mbuf_multicast_encap`

    void
                    mpo_create_mbuf_multicast_encap (struct mbuf,
                                                          struct label,
                                                          struct ifnet,
                                                          struct label,
                                                          struct mbuf,
                                                          struct label);

参数 | 说明 | 锁定
--- | --- | ---
oldmbuf | 现有数据报的 mbuf 头部 |
oldmbuflabel | oldmbuf 的策略标记 |
ifnet | 网络接口 |
ifnetlabel | ifnet 的策略标记 |
newmbuf | 将被标记的新建数据报 mbuf 头部 |
newmbuflabel | 将为newmbuf填写的策略标记 |

当传入的已有数据报被给定多播封装接口（multicast encapsulation interface）处理时被调用， 为新创建的数据报所在 mbuf 头部设置标记。 每当使用该虚拟接口传递一个mbuf时，将调用该函数。

##### 6.7.3.3.11. `mpo_create_mbuf_netlayer`

    void
                    mpo_create_mbuf_netlayer (struct mbuf,
                                                   struct label,
                                                   struct mbuf,
                                                   struct label);

参数 | 说明 | 锁定
--- | --- | ---
oldmbuf | 接收的数据报 |
oldmbuflabel | oldmbuf 的策略标记 |
newmbuf | 新建数据报 |
newmbuflabel | newmbuf 的策略标记 |

为由 IP 堆栈因为响应接收数据报（oldmbuf）而新建的数据报设置其 mbuf 头部的标记。 许多情况下需要调用该函数，比如，响应 ICMP 请求数据报时。

##### 6.7.3.3.12. `mpo_fragment_match`

    int
                    mpo_fragment_match (struct mbuf,
                                            struct label,
                                            struct ipq,
                                            struct label);

参数 | 说明 | 锁定
--- | --- | ---
fragment | IP 数据报分片 |
fragmentlabel | fragment 的策略标记 |
ipq | IP 分片重组队列 |
ipqlabel | ipq 的策略标记 |

根据所传入的 IP 分片重组队列（ipq）的标记， 检查包含一个 IP 数据报（fragment）的 mbuf 的头部是否符合其要求。 符合，则返回1。否则，返回0。 每当 IP 堆栈尝试将一个刚刚接收到的分片放入某个已有的分片重组队列中时，将调用该函数进行安全检查； 如果失败，将为分片重新实例化一个新的分片重组队列。 策略可以利用该入口函数，根据标记或者其他信息阻止不期望的 IP 分片重组。

##### 6.7.3.3.13. `mpo_relabel_ifnet`

    void
                    mpo_relabel_ifnet (struct ucred,
                                            struct ifnet,
                                            struct label,
                                            struct label);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
ifnet | 客体；网络接口 |
ifnetlabel | ifnet 的策略标记 |
newlabel | 将为ifnet设置的新标记 |

根据所传入的新标记，newlabel，以及主体信任状， cred，对网络接口的标记进行更新。

##### 6.7.3.3.14. `mpo_update_ipq`

    void
                    mpo_update_ipq (struct mbuf,
                                         struct label,
                                         struct ipq,
                                         struct label);

参数 | 说明 | 锁定
--- | --- | ---
mbuf | IP 分片 |
mbuflabel | mbuf 的策略标记 |
ipq | IP 分片重组队列 |
ipqlabel | 将被更新的ipq的当前策略标记 |

根据所传入的 IP 分片 mbuf 头部（mbuf）为接收 它的 IP 分片重组队列（ipq）的标记进行更新。

#### 6.7.3.4. 进程标记事件操作

##### 6.7.3.4.1. `mpo_create_cred`

    void
                    mpo_create_cred (struct ucred,
                                          struct ucred);

参数 | 说明 | 锁定
--- | --- | ---
parent_cred | 父主体信任状 |
child_cred | 子主体信任状 |

根据所传入的主体信任状，为新建的主体信任状设置标记。 每当为一个新建的 struct ucred调用 crcopy(9) 时，将调用此函数。 该函数不应与进程复制（forking）或者创建事件混为一谈。

##### 6.7.3.4.2. `mpo_execve_transition`

    void
                    mpo_execve_transition (struct ucred,
                                                struct ucred,
                                                struct vnode,
                                                struct label);

参数 | 说明 | 锁定
--- | --- | ---
old | 已有的主体信任状 | 不可改变
new | 将被标记的新主体信任状 |
vp | 将被执行的文件 | 已被锁定
vnodelabel | vp 的策略标记 |

一个拥有信任状old的主体由于执行(vp文件而导致标记转换时， 该函数根据vnode标记为该主体重新标记为new。 每当一个进程请求执行vnode文件，而通过 入口函数`mpo_execve_will_transition` 有成功返回的策略时，将调用该函数。 策略模块可以通过传入两个主体信任状和简单地调用 `mpo_create_cred` 来实现该入口函数， so as not to implement a transitioning event. 一旦策略实现了`mpo_create_cred`函数，即使没有实现 `mpo_execve_will_transition`，也应该实现该函数。

##### 6.7.3.4.3. `mpo_execve_will_transition`

    int
                    mpo_execve_will_transition (struct ucred,
                                                    struct vnode,
                                                    struct label);

参数 | 说明 | 锁定
--- | --- | ---
old | 在执行execve(2)之前的主体信任状 | 不可改变
vp | 将被执行的文件 |
vnodelabel | vp 的策略标记 |

由策略决定，当参数主体信任状执行参数 vnode 时，是否需要进行一个标记转换操作。如果需要，返回1； 否则，返回0。即使一个策略返回0，它也必须为自己不期望的对 `mpo_execve_transition`的调用作好准备，因为只要有其他任何一个策略要求转换，就将执行此函数。

##### 6.7.3.4.4. `mpo_create_proc0`

    void
                    mpo_create_proc0 (struct ucred);

参数 | 说明 | 锁定
--- | --- | ---
cred | 将被填写的主体信任状 |

为进程0，所有内核进程的祖先，创建主体信任状。

##### 6.7.3.4.5. `mpo_create_proc1`

    void
                    mpo_create_proc1 (struct ucred);

参数 | 说明 | 锁定
--- | --- | ---
cred | 将被填写的主体信任状 |

为进程1，所有用户进程的祖先，创建主体信任状。

##### 6.7.3.4.6. `mpo_relabel_cred`

    void
                    mpo_relabel_cred (struct ucred,
                                           struct label);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
newlabel | 将被应用到 cred 上的新标记 |

根据传入的新标记，对主体信任状上的标记进行更新。

### 6.7.4. 访问控制检查

通过访问控制入口函数，策略模块能影响内核的访问控制决策。 通常情况下，不是绝对，一个访问控制入口函数的参数有，一个或者若干个授权信任状，和相关操作涉及的其他任何对象的信息（其中可能包含标记）。 访问控制入口函数返回0，表示允许该操作；否则，返回一个 errno(2) 错误编码。调用该入口函数，将遍历所有系统注册的策略模块，逐一进行 策略相关的检查和决策，之后按照下述方法组合不同策略的返回结果：只有当所有的模块均允许该操作时，才成功返回。 否则，如果有一个或者若干模块失败返回，则整个检查不通过。如果有多个模块的检查出错返回，将由定义在kern_mac.c 中的 `error_select()` 函数从它们返回的错误编码中，选择一个合适的，返回给用户。

最高优先级 | EDEADLK
| EINVAL
| ESRCH
| EACCES
最低优先级 | EPERM

如果所有策略模块返回的错误编码均没有出现在上述优先级序列表中，则任意选择一个返回。 选择错误编码的一般次序为：内核错误，无效的参数，对象不存在，访问被拒绝，和其他错误。

#### 6.7.4.1. `mpo_check_bpfdesc_receive`

    int
                  mpo_check_bpfdesc_receive (struct bpf_d,
                                                 struct label,
                                                 struct ifnet,
                                                 struct label);

参数 | 说明 | 锁定
--- | --- | ---
bpf_d | 主体；BPF 描述子 |
bpflabel | bpf_d 的策略标记 |
ifnet | 客体；网络接口 |
ifnetlabel | ifnet 的策略标记 |

决定 MAC 框架是否应该允许将由参数接口接收到的数据报传递给由 BPF 描述子所对应的缓冲区。成功，则返回0； 否则，返回错误编码信息`errno`。建议使用的错误编码有：EACCES，用于标记不符的情况； EPERM，用于缺少特权的情况。

#### 6.7.4.2. `mpo_check_kenv_dump`

    int
                  mpo_check_kenv_dump (struct ucred);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |

决定相关主体是否应该被允许查询内核环境状态（参考 kenv(2)）。

#### 6.7.4.3. `mpo_check_kenv_get`

    int
                  mpo_check_kenv_get (struct ucred,
                                          char *name);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
name | 内核的环境变量名字 |

决定相关主体是否可以查询内核中给定环境变量的状态。

#### 6.7.4.4. `mpo_check_kenv_set`

    int
                  mpo_check_kenv_set (struct ucred,
                                          char *name);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
name | 内核的环境变量名字 |

决定相关主体是否有权设置给定内核环境变量的值。

#### 6.7.4.5. `mpo_check_kenv_unset`

    int
                  mpo_check_kenv_unset (struct ucred,
                                            char *name);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
name | 内核的环境变量名字Kernel environment variable name |

决定相关主体是否有权清除给定的内核环境变量的设置。

#### 6.7.4.6. `mpo_check_kld_load`

    int
                  mpo_check_kld_load (struct ucred,
                                          struct vnode,
                                          struct label);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
vp | 内核模块的 vnode |
vlabel | vp的策略标记 |

决定相关主体是否有权加载给定的模块文件。

#### 6.7.4.7. `mpo_check_kld_stat`

    int
                  mpo_check_kld_stat (struct ucred);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |

决定相关主体是否有权访问内核的加载模块文件链表以及相关的统计数据。

#### 6.7.4.8. `mpo_check_kld_unload`

    int
                  mpo_check_kld_unload (struct ucred);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |

决定相关主体是否有权卸载一个内核模块。

#### 6.7.4.9. `mpo_check_pipe_ioctl`

    int
                  mpo_check_pipe_ioctl (struct ucred,
                                            struct pipe,
                                            struct label,
                                            unsigned long,
                                            void *data);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
pipe | 管道 |
pipelabel | pipe的策略标记 |
cmd | ioctl(2) 命令 |
data | ioctl(2) 数据 |

决定相关主体是否有权调用指定的 ioctl(2) 系统调用。

#### 6.7.4.10. `mpo_check_pipe_poll`

    int
                  mpo_check_pipe_poll (struct ucred,
                                           struct pipe,
                                           struct label);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
pipe | 管道 |
pipelabel | pipe的策略标记 |

决定相关主体是否有权对管道pipe执行poll操作。

#### 6.7.4.11. `mpo_check_pipe_read`

    int
                  mpo_check_pipe_read (struct ucred,
                                           struct pipe,
                                           struct label);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
pipe | 管道 |
pipelabel | pipe的策略标记 |

决定该主体是否有权读取pipe。

#### 6.7.4.12. `mpo_check_pipe_relabel`

    int
                  mpo_check_pipe_relabel (struct ucred,
                                              struct pipe,
                                              struct label,
                                              struct label);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
pipe | 管道 |
pipelabel | pipe的当前策略标记 |
newlabel | 将为pipelabel设置的新标记 |

决定该主体是否有权为pipe重新设置标记。

#### 6.7.4.13. `mpo_check_pipe_stat`

    int
                  mpo_check_pipe_stat (struct ucred,
                                           struct pipe,
                                           struct label);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
pipe | 管道 |
pipelabel | pipe的策略标记 |

决定该主体是否有权查询与pipe相关的统计信息。

#### 6.7.4.14. `mpo_check_pipe_write`

    int
                  mpo_check_pipe_write (struct ucred,
                                            struct pipe,
                                            struct label);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
pipe | 管道 |
pipelabel | pipe的策略标记 |

决定该主体是否有权写pipe。

#### 6.7.4.15. `mpo_check_socket_bind`

    int
                  mpo_check_socket_bind (struct ucred,
                                             struct socket,
                                             struct label,
                                             struct sockaddr);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
socket | 将被绑定的套接字 |
socketlabel | socket的策略标记 |
sockaddr | socket的地址 |

#### 6.7.4.16. `mpo_check_socket_connect`

    int
                  mpo_check_socket_connect (struct ucred,
                                                struct socket,
                                                struct label,
                                                struct sockaddr);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
socket | 将被连接的套接字 |
socketlabel | socket的策略标记 |
sockaddr | socket的地址 |

决定该主体（cred）是否有权将套接字（socket）绑定到地址 sockaddr。成功，返回0，否则返回一个错误编码`errno`。 建议采用的错误编码有：EACCES，用于标记不符的情况；EPERM，用于特权不足的情况。

#### 6.7.4.17. `mpo_check_socket_receive`

    int
                  mpo_check_socket_receive (struct ucred,
                                                struct socket,
                                                struct label);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
so | 套接字 |
socketlabel | so的策略标记 |

决定该主体是否有权查询套接字so的相关信息。

#### 6.7.4.18. `mpo_check_socket_send`

    int
                  mpo_check_socket_send (struct ucred,
                                             struct socket,
                                             struct label);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
so | 套接字 |
socketlabel | so的策略标记 |

决定该主体是否有权通过套接字so发送信息。

#### 6.7.4.19. `mpo_check_cred_visible`

    int
                  mpo_check_cred_visible (struct ucred,
                                              struct ucred);

参数 | 说明 | 锁定
--- | --- | ---
u1 | 主体信任状 |
u2 | 对象信任状 |

确定该主体信任状u1是否有权 "see" 具有信任状u2 的其他主体。 成功，返回0；否则，返回错误编码`errno`。建议采用的错误编码有： EACCES，用于标记不符的情况；EPERM，用于特权不足的情况；ESRCH， 用来提供不可见性。该函数可在许多环境下使用，包括命令`ps`所使用的进程间的状态 sysctl，以及通过procfs 的状态查询操作。

#### 6.7.4.20. `mpo_check_socket_visible`

    int
                  mpo_check_socket_visible (struct ucred,
                                                struct socket,
                                                struct label);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
socket | 客体；套接字 |
socketlabel | socket的策略标记 |

#### 6.7.4.21. `mpo_check_ifnet_relabel`

    int
                  mpo_check_ifnet_relabel (struct ucred,
                                               struct ifnet,
                                               struct label,
                                               struct label);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
ifnet | 客体；网络接口 |
ifnetlabel | ifnet现有的策略标记 |
newlabel | 将被应用到ifnet上的新的策略标记 |

决定该主体信任状是否有权使用传入的标记更新参数对给定的网络接口的标记进行重新设置。

#### 6.7.4.22. `mpo_check_socket_relabel`

    int
                  mpo_check_socket_relabel (struct ucred,
                                                struct socket,
                                                struct label,
                                                struct label);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
socket | 客体；套接字 |
socketlabel | socket现有的策略标记 |
newlabel | 将被应用到socketlabel上的更新标记 |

决定该主体信任状是否有权采用传入的标记对套接字参数的标记进行重新设置。

#### 6.7.4.23. `mpo_check_cred_relabel`

    int
                  mpo_check_cred_relabel (struct ucred,
                                              struct label);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
newlabel | 将被应用到cred上的更新标记 |

决定该主体信任状是否有权将自己的标记重新设置为给定的更新标记。

#### 6.7.4.24. `mpo_check_vnode_relabel`

    int
                  mpo_check_vnode_relabel (struct ucred,
                                               struct vnode,
                                               struct label,
                                               struct label);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 | 不可改变
vp | 客体；vnode | 已被锁定
vnodelabel | vp现有的策略标记 |
newlabel | 将被应用到vp上的策略标记 |

决定该主体信任状是否有权将参数 vnode 的标记重新设置为指定标记。

#### 6.7.4.25. `mpo_check_mount_stat`

    int mpo_check_mount_stat (struct ucred,
                              struct mount,
                              struct label);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
mp | 客体；文件系统挂载 |
mountlabel | mp的策略标记 |

确定相关主体信任状是否有权查看在给定文件系统上执行 statfs 的结果。 成功，则返回 0；否则，返回一个`errno`值。 建议使用的错误编码：EACCES，用于标记不匹配； EPERM，用于权限不够。 该函数可能在下列情况下被调用： 在 statfs(2) 和其他相关调用期间，或者当需要从文件系统列表中选择排除哪个文件系统时，比如， 调用 getfsstat(2)时。

#### 6.7.4.26. `mpo_check_proc_debug`

    int
                  mpo_check_proc_debug (struct ucred,
                                            struct proc);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 | 不可改变
proc | 客体；进程 |

确定相关主体信任状是否有权 debug 给定进程。 成功，则返回 0；否则，返回一个`errno`值。 建议使用的错误编码：EACCES，用于标记不匹配；EPERM，用于权限不够； ESRCH，用于隐瞒目标的存在。 ptrace(2) 和 ktrace(2) API，以及某些 procfs 操作将调用该函数。

#### 6.7.4.27. `mpo_check_vnode_access`

    int
                  mpo_check_vnode_access (struct ucred,
                                              struct vnode,
                                              struct label,
                                              int flags);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
vp | 客体；vnode |
label | vp的策略标记 |
flags | access(2) 标志 |

根据相关主体信任状决定其对给定 vnode 以给定访问标志执行的 access(2) 和其他相关调用的返回值。一般，应采用与`mpo_check_vnode_open` 相同的语义来实现该函数。 成功，则返回 0；否则，返回一个`errno`值。 建议使用的错误编码：EACCES，用于标记不匹配； EPERM，用于权限不够。

#### 6.7.4.28. `mpo_check_vnode_chdir`

    int
                  mpo_check_vnode_chdir (struct ucred,
                                             struct vnode,
                                             struct label);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
dvp | 客体；chdir(2) 的目的 vnode |
dlabel | dvp的策略标记 |

确定相关主体信任状是否有权将进程工作目录切换到给定 vnode。成功，则返回 0； 否则，返回一个 `errno`值。 建议使用的错误编码：EACCES，用于标记不匹配； EPERM，用于权限不够。

#### 6.7.4.29. `mpo_check_vnode_chroot`

    int
                  mpo_check_vnode_chroot (struct ucred,
                                              struct vnode,
                                              struct label);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
dvp | 目录 vnode |
dlabel | 与dvp相关联的策略标记 |

确定相关主体是否有权 chroot(2) 到由 (dvp)给定的目录。

#### 6.7.4.30. `mpo_check_vnode_create`

    int
                  mpo_check_vnode_create (struct ucred,
                                              struct vnode,
                                              struct label,
                                              struct componentname,
                                              struct vattr);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
dvp | 客体；vnode |
dlabel | dvp的策略标记 |
cnp | dvp中的成员名 |
vap | vap的 vnode 属性 |

确定相关主体信任状是否有权在给定父目录，以给定的名字和属性， 常见一个 vnode。成功，则返回 0；否则， 返回一个`errno`值。 建议使用的错误编码：EACCES 来表示用于标记不匹配， 而用 EPERM，用于权限不足。 以O_CREAT为参数调用 open(2)，或对 mknod(2)，mkfifo(2) 等的调用将导致该函数被调用。

#### 6.7.4.31. `mpo_check_vnode_delete`

    int
                  mpo_check_vnode_delete (struct ucred,
                                              struct vnode,
                                              struct label,
                                              struct vnode,
                                              void *label,
                                              struct componentname);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
dvp | 父目录 vnode |
dlabel | dvp的策略标记 |
vp | 客体；将被删除的 vnode |
label | vp的策略标记 |
cnp | vp中的成员名 |

确定相关主体信任状是否有权从给定的父目录中，删除给定名字的 vnode。 成功，则返回 0；否则，返回一个`errno`值。 建议使用的错误编码：EACCES，用于标记不匹配； EPERM，用于权限不够。 使用 unlink(2) 和 rmdir(2)，将导致该函数被调用。 提供该入口函数的策略还必须实现一个 `mpo_check_rename_to`， 用来授权由于重命名操作导致的目标文件的删除。

#### 6.7.4.32. `mpo_check_vnode_deleteacl`

    int
                  mpo_check_vnode_deleteacl (struct ucred *cred,
                                                 struct vnode *vp,
                                                 struct label *label,
                                                 acl_type_t type);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 | 不可改变
vp | 客体；vnode | 被锁定
label | vp的策略标记 |
type | ACL 类型 |

确定相关主体信任状是否有权删除给定 vnode 的给定类型的 ACL。 成功，则返回 0；否则，返回一个`errno`值。 建议使用的错误编码：EACCES，用于标记不匹配； EPERM，用于权限不够。

#### 6.7.4.33. `mpo_check_vnode_exec`

    int
                  mpo_check_vnode_exec (struct ucred,
                                            struct vnode,
                                            struct label);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
vp | 客体；将被执行的 vnode |
label | vp的策略标记 |

确定相关主体信任状是否有权执行给定 vnode。 对于执行特权的决策与任何瞬时事件的决策是严格分开的。 成功，则返回 0；否则，返回一个`errno`值。 建议使用的错误编码：EACCES，用于标记不匹配； EPERM，用于权限不够。

#### 6.7.4.34. `mpo_check_vnode_getacl`

    int
                  mpo_check_vnode_getacl (struct ucred,
                                              struct vnode,
                                              struct label,
                                              acl_type_t);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
vp | 客体；vnode |
label | vp的策略标记 |
type | ACL 类型 |

确定相关主体信任状是否有权查询给定 vnode 上的给定类型的 ACL。 成功，则返回 0；否则，返回一个`errno`值。 建议使用的错误编码：EACCES，用于标记不匹配； EPERM，用于权限不够。

#### 6.7.4.35. `mpo_check_vnode_getextattr`

    int
                  mpo_check_vnode_getextattr (struct ucred,
                                                  struct vnode,
                                                  struct label,
                                                  int,
                                                  const char,
                                                  struct uio);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
vp | 客体；vnode |
label | vp的策略标记 |
attrnamespace | 扩展属性名字空间 |
name | 扩展属性名 |
uio | I/O 结构指针；参见 uio(9) |

确定相关主体信任状是否有权查询给定 vnode 上给定名字空间和名字的扩展属性。 使用扩展属性实现标记存储的策略模块可能会需要对这些扩展属性的操作进行特殊处理。 成功，则返回 0；否则，返回一个`errno`值。 建议使用的错误编码：EACCES，用于标记不匹配； EPERM，用于权限不够。

#### 6.7.4.36. `mpo_check_vnode_link`

    int
                  mpo_check_vnode_link (struct ucred,
                                            struct vnode,
                                            struct label,
                                            struct vnode,
                                            struct label,
                                            struct componentname);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
dvp | 目录 vnode |
dlabel | 与dvp相关联的策略标记 |
vp | 链接目的 vnode |
label | 与vp相关联的策略标记 |
cnp | 将被创建的链接对应的成员名 |

确定相关主体是否有权为参数vp给定的 vnode 创建一个由参数cnp给定名字的链接。

#### 6.7.4.37. `mpo_check_vnode_mmap`

    int
                  mpo_check_vnode_mmap (struct ucred,
                                            struct vnode,
                                            struct label,
                                            int prot);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
vp | 将被映射的 vnode |
label | 与vp相关联的策略标记 |
prot | mmap 保护 (参见 mmap(2)) |

确定相关主体是否有权将给定 vnode vp 以 prot指定的保护方式进行映射.

#### 6.7.4.38. `mpo_check_vnode_mmap_downgrade`

    void
                  mpo_check_vnode_mmap_downgrade (struct ucred,
                                                       struct vnode,
                                                       struct label,
                                                       int *prot);

参数 | 说明 | 锁定
--- | --- | ---
cred | See `mpo_check_vnode_mmap`. |
vp |  |
label |  |
prot | 将被降级的 mmap protections |

根据主体和客体标记，降低 mmap protections。

#### 6.7.4.39. `mpo_check_vnode_mprotect`

    int
                  mpo_check_vnode_mprotect (struct ucred,
                                                struct vnode,
                                                struct label,
                                                int prot);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
vp | 映射的 vnode |
prot | 存储保护 |

确定相关主体是否有权将给定 vnode vp 映射内存空间的存储保护参数设置为指定值。

#### 6.7.4.40. `mpo_check_vnode_poll`

    int
                  mpo_check_vnode_poll (struct ucred,
                                            struct ucred,
                                            struct vnode,
                                            struct label);

参数 | 说明 | 锁定
--- | --- | ---
active_cred | 主体信任状 |
file_cred | 与struct file相关联的信任状 |
vp | 将被执行 poll 操作的 vnode |
label | 与vp相关联的策略标记 |

确定相关主体是否有权对给定 vnode vp执行 poll 操作。

#### 6.7.4.41. `mpo_check_vnode_rename_from`

    int
                  mpo_vnode_rename_from (struct ucred,
                                             struct vnode,
                                             struct label,
                                             struct vnode,
                                             struct label,
                                             struct componentname);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
dvp | 目录 vnode |
dlabel | 与dvp相关联的策略标记 |
vp | 将被重命名的 vnode |
label | 与vp相关联的策略标记 |
cnp | vp中的成员名 |

确定相关主体是否有权重命名给定vnode，vp。

#### 6.7.4.42. `mpo_check_vnode_rename_to`

    int
                  mpo_check_vnode_rename_to (struct ucred,
                                                 struct vnode,
                                                 struct label,
                                                 struct vnode,
                                                 struct label,
                                                 int samedir,
                                                 struct componentname);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
dvp | 目录 vnode |
dlabel | 与dvp相关联的策略标记 |
vp | 被覆盖的 vnode |
label | 与vp相关联的策略标记 |
samedir | 布尔型变量；如果源和目的目录是相同的，则被置为`1` |
cnp | 目标component名 |

确定相关主体是否有权重命名给定 vnode vp，至指定目录 dvp，或更名为cnp。如果无需覆盖已有文件， 则vp 和 label 的值将为 NULL.

#### 6.7.4.43. `mpo_check_socket_listen`

    int
                  mpo_check_socket_listen (struct ucred,
                                               struct socket,
                                               struct label);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
socket | 客体；套接字 |
socketlabel | socket的策略标记 |

确定相关主体是否有权监听给定套接字。 成功，则返回0；否则，返回错误编码值`errno`。 建议使用的错误编码：EACCES，用于标记不匹配； EPERM，用于权限不够。

#### 6.7.4.44. `mpo_check_vnode_lookup`

    int
                  mpo_check_vnode_lookup (struct ucred,
                                              struct vnode,
                                              struct label,
                                              struct componentname);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
dvp | 客体；vnode |
dlabel | dvp的策略标记 |
cnp | 被检查的成员名 |

确定相关主体信任状是否有权在给定的目录 vnode 中为查找给定名字执行lookup操作。 成功，则返回 0；否则，返回一个 `errno`值。 建议使用的错误编码：EACCES，用于标记不匹配； EPERM，用于权限不够。

#### 6.7.4.45. `mpo_check_vnode_open`

    int
                  mpo_check_vnode_open (struct ucred,
                                            struct vnode,
                                            struct label,
                                            int);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
vp | 客体；vnode |
label | vp的策略标记 |
acc_mode | open(2) 访问模式 |

确定相关主体信任状是否有权在给定 vnode 上以给定的访问模式执行 open 操作。 如果成功，则返回 0；否则，返回一个错误编码。 建议使用的错误编码：EACCES，用于标记不匹配； EPERM，用于权限不够。

#### 6.7.4.46. `mpo_check_vnode_readdir`

    int
                  mpo_check_vnode_readdir (struct ucred,
                                               struct vnode,
                                               struct label);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
dvp | 客体；目录 vnode |
dlabel | dvp的策略标记 |

确定相关主体信任状是否有权在给定的目录 vnode 上执行 `readdir` 操作。 成功，则返回 0；否则，返回一个错误编码 `errno`。 建议使用的错误编码：EACCES，用于标记不匹配；EPERM，用于权限不够。

#### 6.7.4.47. `mpo_check_vnode_readlink`

    int
                  mpo_check_vnode_readlink (struct ucred,
                                                struct vnode,
                                                struct label);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
vp | 客体；vnode |
label | vp的策略标记 |

确定相关主体信任状是否有权在给定符号链接 vnode 上执行 `readlink` 操作。成功，则返回 0；否则，返回一个`errno`值。 建议使用的错误编码：EACCES，用于标记不匹配；EPERM，用于权限不够。 该函数可能在若干环境下被调用，包括由用户进程显式执行的 `readlink` 调用， 或者是在进程执行名字查询时隐式执行的 `readlink` 。

#### 6.7.4.48. `mpo_check_vnode_revoke`

    int
                  mpo_check_vnode_revoke (struct ucred,
                                              struct vnode,
                                              struct label);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
vp | 客体；vnode |
label | vp的策略标记 |

确定相关主体信任状是否有权撤销对给定 vnode 的访问。 成功，则返回 0；否则，返回一个`errno`值。 建议使用的错误编码：EACCES，用于标记不匹配；EPERM，用于权限不够。

#### 6.7.4.49. `mpo_check_vnode_setacl`

    int
                  mpo_check_vnode_setacl (struct ucred,
                                              struct vnode,
                                              struct label,
                                              acl_type_t,
                                              struct acl);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
vp | 客体；vnode |
label | vp的策略标记 |
type | ACL 类型 |
acl | ACL |

确定相关主体信任状是否有权设置给定 vnode 的给定类型的 ACL。 成功，则返回 0；否则，返回一个`errno`值。 建议使用的错误编码：EACCES，用于标记不匹配；EPERM，用于权限不够。

#### 6.7.4.50. `mpo_check_vnode_setextattr`

    int
                  mpo_check_vnode_setextattr (struct ucred,
                                                  struct vnode,
                                                  struct label,
                                                  int,
                                                  const char,
                                                  struct uio);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
vp | 客体；vnode |
label | vp的策略标记 |
attrnamespace | 扩展属性名字空间 |
name | 扩展属性名 |
uio | I/O 结构指针；参见 uio(9) |

确定相关主体信任状是否有权设置给定 vnode 上给定名字空间中给定名字的扩展属性的值。 使用扩展属性备份安全标记的策略模块可能需要对其使用的属性实施额外的保护。另外， 由于在检查和实际操作时间可能存在的竞争， 策略模块应该避免根据来自uio中的数据做出决策。 如果正在执行一个删除操作，则参数 uio 的值也可能为 `NULL`。 成功，则返回 0；否则，返回一个`errno`值。 建议使用的错误编码：EACCES，用于标记不匹配；EPERM，用于权限不够。

#### 6.7.4.51. `mpo_check_vnode_setflags`

    int
                  mpo_check_vnode_setflags (struct ucred,
                                                struct vnode,
                                                struct label,
                                                u_long flags);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
vp | 客体；vnode |
label | vp的策略标记 |
flags | 文件标志；参见 chflags(2) |

确定相关主体信任状是否有权为给定的 vnode 设置给定的标志。 成功，则返回 0；否则，返回一个`errno`值。 建议使用的错误编码：EACCES，用于标记不匹配； EPERM，用于权限不够。

#### 6.7.4.52. `mpo_check_vnode_setmode`

    int
                  mpo_check_vnode_setmode (struct ucred,
                                               struct vnode,
                                               struct label,
                                               mode_t mode);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
vp | 客体；vnode |
label | vp的策略标记 |
mode | 文件模式；参见 chmod(2) |

确定相关主体信任状是否有权将给定 vnode 的模式设置为给定值。 成功，则返回 0；否则，返回一个`errno`值。 建议使用的错误编码：EACCES，用于标记不匹配； EPERM，用于权限不够。

#### 6.7.4.53. `mpo_check_vnode_setowner`

    int
                  mpo_check_vnode_setowner (struct ucred,
                                                struct vnode,
                                                struct label,
                                                uid_t uid,
                                                gid_t gid);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
vp | 客体；vnode |
label | vp的策略标记 |
uid | 用户ID |
gid | 组ID |

确定相关主体信任状是否有权将给定 vnode 的文件 uid 和文件 gid 设置为给定值。如果无需更新， 相关参数值可能被设置为(`-1`)。 成功，则返回 0；否则，返回一个`errno`值。 建议使用的错误编码：EACCES，用于标记不匹配； EPERM，用于权限不够。

#### 6.7.4.54. `mpo_check_vnode_setutimes`

    int
                  mpo_check_vnode_setutimes (struct ucred,
                                                 struct vnode,
                                                 struct label,
                                                 struct timespec,
                                                 struct timespec);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
vp | 客体；vp |
label | vp的策略标记 |
atime | 访问时间；参见 utimes(2) |
mtime | 修改时间；参见 utimes(2) |

确定相关主体信任状是否有权将给定 vnode 的访问时间标签设置为给定值。 成功，则返回 0；否则，返回一个`errno`值。 建议使用的错误编码：EACCES，用于标记不匹配； EPERM，用于权限不够。

#### 6.7.4.55. `mpo_check_proc_sched`

    int
                  mpo_check_proc_sched (struct ucred,
                                            struct proc);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
proc | 客体；进程 |

确定相关主体信任状是否有权改变给定进程的调度参数。 成功，则返回 0；否则，返回一个`errno`值。 建议使用的错误编码：EACCES，用于标记不匹配； EPERM，用于权限不够； ESRCH，用于提供不可见性质。

See setpriority(2) for more information.

#### 6.7.4.56. `mpo_check_proc_signal`

    int
                  mpo_check_proc_signal (struct ucred,
                                             struct proc,
                                             int signal);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
proc | 客体；进程 |
signal | 信号；参见 kill(2) |

确定相关主体信任状是否有权向给定进程发送给定信号。 成功，则返回 0；否则，返回一个`errno`值。 建议使用的错误编码：EACCES， 用于标记不匹配；EPERM，用于权限不够； ESRCH，用于提供不可见性质。

#### 6.7.4.57. `mpo_check_vnode_stat`

    int
                  mpo_check_vnode_stat (struct ucred,
                                            struct vnode,
                                            struct label);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
vp | 客体；vnode |
label | vp的策略标记 |

确定相关主体信任状是否有权在给定 vnode 上执行 `stat` 操作。 成功，则返回 0；否则，返回一个`errno`值。 建议使用的错误编码：EACCES，用于标记不匹配； EPERM，用于权限不够。

See stat(2) for more information.

#### 6.7.4.58. `mpo_check_ifnet_transmit`

    int
                  mpo_check_ifnet_transmit (struct ucred,
                                                struct ifnet,
                                                struct label,
                                                struct mbuf,
                                                struct label);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
ifnet | 网络接口 |
ifnetlabel | ifnet的策略标记 |
mbuf | 客体；将被发送的 mbuf |
mbuflabel | mbuf的策略标记 |

确定相关网络接口是否有权传送给定的 mbuf。成功，则返回 0； 否则，返回一个`errno`值。 建议使用的错误编码：EACCES，用于标记不匹配； EPERM，用于权限不够。

#### 6.7.4.59. `mpo_check_socket_deliver`

    int
                  mpo_check_socket_deliver (struct ucred,
                                                struct ifnet,
                                                struct label,
                                                struct mbuf,
                                                struct label);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
ifnet | 网络接口 |
ifnetlabel | ifnet的策略标记 |
mbuf | 客体；将被传送的 mbuf |
mbuflabel | mbuf的策略标记 |

确定相关套接字是否有权从给定的 mbuf 中接收数据报。 成功，则返回 0；否则，返回一个`errno`值。 建议使用的错误编码：EACCES，用于标记不匹配； EPERM，用于权限不够。

#### 6.7.4.60. `mpo_check_socket_visible`

    int
                  mpo_check_socket_visible (struct ucred,
                                                struct socket,
                                                struct label);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 | 不可改变
so | 客体；套接字 |
socketlabel | so的策略标记 |

确定相关主体信任状cred 是否有权使用系统监控函数，比如， 由netstat(8) 和 sockstat(1)使用的程序来观察 给定的套接字(socket)。成功， 则返回 0；否则，返回一个`errno`值。 建议使用的错误编码：EACCES，用于标记不匹配； EPERM，用于权限不够； ESRCH，用于提供不可见性质。

#### 6.7.4.61. `mpo_check_system_acct`

    int
                  mpo_check_system_acct (struct ucred,
                                             struct vnode,
                                             struct label);

参数 | 说明 | 锁定
--- | --- | ---
ucred | 主体信任状 |
vp | 审计文件；acct(5) |
vlabel | 与vp相关联的标记 |

根据主体标记和审计日志文件的标记，确定该主体是否有权启动审计。

#### 6.7.4.62. `mpo_check_system_nfsd`

    int
                  mpo_check_system_nfsd (struct ucred);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |

确定相关主体是否有权调用 nfssvc(2)。

#### 6.7.4.63. `mpo_check_system_reboot`

    int
                  mpo_check_system_reboot (struct ucred,
                                               int howto);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
howto | 来自 reboot(2)的howto 参数 |

确定相关主体是否有权以指定方式重启系统。

#### 6.7.4.64. `mpo_check_system_settime`

    int
                  mpo_check_system_settime (struct ucred);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |

确定相关用户是否有权设置系统时钟。

#### 6.7.4.65. `mpo_check_system_swapon`

    int
                  mpo_check_system_swapon (struct ucred,
                                               struct vnode,
                                               struct label);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
vp | swap设备 |
vlabel | 与vp相关联的标记 |

确定相关主体是否有权增加一个作为swap设备的 vp 。

#### 6.7.4.66. `mpo_check_system_sysctl`

    int
                  mpo_check_system_sysctl (struct ucred,
                                               int *name,
                                               u_int *namelen,
                                               void *old,
                                               size_t,
                                               int inkernel,
                                               void *new,
                                               size_t newlen);

参数 | 说明 | 锁定
--- | --- | ---
cred | 主体信任状 |
name | 参见 sysctl(3) |
namelen |  |
old |  |
oldlenp |  |
inkernel | 布尔型变量；如果从内核被调用，其值被置为`1` |
new | 参见 sysctl(3) |
newlen |  |

确定相关主体是否应该被允许执行指定的 sysctl(3) 事务。

### 6.7.5. 标记管理调用

当用户进程请求对某个对象的标记进行修改时，将引发重新标记事件。对应的更新操作分两步进行： 首先，进行访问控制检查，确认此次更新操作是有效且被允许的；然后，调用另一个独立的入口函数对标记进行修改。 重新标记入口函数通常接收由请求进程提交的对象、对象标记指针和请求新标记，作为输入参数。 对象重新标记操作的失败将由先期的标记检查报告，所以，不允许在接下来的标记修改过程中报告失败，故而不提倡在此过程中新分配内存。

## 6.8. 应用层体系结构

TrustedBSD MAC 框架包含了一组策略无关的组成元素，包括管理抽象标记的 MAC 接口库， 对系统信任状管理体系的修改, 为用户分配 MAC 标记提供支持的 login 库函数， 以及若干负责维护和更新内核对象(进程、文件和网络接口等)安全标记的工具。 不久，将有更多关于应用层体系结构的详细信息被包含进来。

### 6.8.1. 策略无关的标记管理 API

TrustedBSD MAC 提供的大量库函数和系统调用，允许应用程序使用一种统一的、策略无关的接口来处理对象的 MAC 标记。 如此，应用程序可以轻松管理各种策略的标记，无需为增加对某个特定策略的支持而重新编码。许多通用工具，比如 ifconfig(8)，ls(1) 和 ps(1)，使用这些策略无关的接口查询网络结构、文件和进程的标记信息。 这些 API 也被用于支持 MAC 管理工具，比如，getfmac(8)，getpmac(8)， setfmac(8)， setfsmac(8)，和 setpmac(8)。 MAC API的设计细节可参考 mac(3).

应用程序处理的 MAC 标记有两种存在形式：内部形式，用来返回和设置进程和对象的标记（`mac_t`）； 基于 C 字符串的外部形式，作为标记在配置文件中的存放形式，用于向用户显示或者由用户输入。 每一个 MAC 标记由一组标记元素组成，其中每个元素是一个形如（名字，值）的二元组。 内核中的每个策略模块分别被指定一个特定的名字，由它们对标记中与该名字对应的值采用其策略特有的方式进行解析。 采用外部形式表示的标记，其标记元素表示为名字 `/` 值，元素之间以逗号分隔。 应用程序可以使用 MAC 框架提供的 API 将一个安全标记在内部形式和文本形式之间进行转换。 每当向内核查询某个对象的安全标记时，内部形式的标记必须针对所需的元素集合作好内部标记存储准备。 为此，通常采用下面两种方式之一：使用 mac_prepare(3) 和一个包含所需标记元素的任意列表；或者， 使用从mac.conf(5) 配置文件中加载缺省元素集合的某个系统调用。在对象级别设置缺省标记，将允许应用程序在不确定 系统是否采用相关策略的情况下，也能向用户返回与对象相关联的有意义的安全标记。

| 目前的 MAC 库不支持直接修改内部形式的标记元素，所有的修改必须按照下列的步骤进行： 将内部形式的标记转换成文本字符串，对字符串进行编辑，最后将其转换成内部形式标记。如果应用程序的作者证明确实有需要， 可以在将来的版本中加入对内部形式标记进行直接修改的接口。

### 6.8.2. 为用户指定标记

用户上下文管理的标记接口， setusercontext(3) ，的行为已经被修改为，从 login.conf(5) 中查询与某个用户登录类别相关联的 MAC 安全标记。 当 `LOGIN_SETALL` 被设置，或者当 `LOGIN_SETMAC` 被明确指定时，这些安全标记将和其他用户上下文参数一起被设置。

| 可以预期，在今后的某个版本中，FreeBSD 将把 MAC 标记从 login.conf 的用户类别数据库中抽出，为其维护一个独立的数据库。 不过在此前后，setusercontext(3) API应该保持不变。

## 6.9. 小结

TrustedBSD MAC 框架使得内核模块能以一种集中的方式，完善系统的安全策略。 它们既可利用现有的内核对象属性，又能使用由 MAC 框架协助维护的安全标记数据，来实施访问控制。 框架提供的灵活性使得开发人员可以在其上实现各种策略，如利用 BSD 现有的信任状（credential） 与文件保护机制的策略，以及信息流安全策略（如 MLS 和 Biba）。 实现新安全服务的策略编程人员，可以参考本文档，以了解现有安全模块的信息。

Last modified on: February 18, 2025 by Fernando Apesteguía

Prev
Home
Next