> For the complete documentation index, see [llms.txt](https://man.bsdcn.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://man.bsdcn.org/man4/mac_do.4.md).

# mac\_do(4)

`mac_do` — 允许非特权用户更改进程凭证的策略

## 名称

`mac_do`

## 概要

要将 **mac\_do** 策略编译进内核，请在内核配置文件中加入以下行：

> options MAC options MAC\_DO

或者，要在引导时加载此策略模块，请在内核配置文件中加入以下行：

> options MAC

并在 loader.conf(5) 中加入：

```sh
mac_do_load="YES"
```

## 描述

`mac_do` 策略模块允许非特权用户根据管理员配置的规则更改进程凭证。它支持按 jail 配置。

`mac_do` 策略模块仅对从可配置白名单中特定可执行文件生成的进程生效。默认情况下，此白名单仅包含 `/usr/bin/mdo` 可执行文件，有关此程序的更多详情，请参见 mdo(1)。

Sx 凭证规则 小节指定了凭证转换规则的格式，Sx 配置 小节解释了如何配置 `mac_do`，包括规则和授权的可执行文件。

## 凭证规则

规则基于当前进程凭证和期望的最终凭证，指定 `mac_do` 将允许哪些进程凭证转换。它们由管理员以字符串形式按以下描述的特定语法自上而下传入。其设计目标是以安全、紧凑的方式精细描述期望的目标凭证。

### 规则的顶层列表

在顶层，规则是一个可能为空的单个规则列表，规则之间以分号（;）分隔：

```sh
<rules >[<rule >[‘; ’<rule >]* ]>]>]>
```

它们构成一个析取式，即只要列表中至少有一条规则匹配，`mac_do` 就会授权凭证转换。

一条规则由 部分（也称为“匹配”）和 部分（也称为“目标”）组成，按此顺序，以大于号（`>`）分隔：

```sh
<rule ><from >‘> ’<to >>>>
```

### 规则的 `<from>` 部分

规则的第一部分 与请求某些凭证转换的进程的凭证进行匹配。其形式为：

```sh
<from ><type >‘= ’<id >>>>
```

必须为：

```sh
<type >[‘uid ’‘gid ’]>
```

即字面字符串 `uid` 或 `gid` 之一。 必须是用户或组的数字 ID，并与相应类型的当前进程实际 ID 进行匹配，对于 `gid` 类型还会与补充组进行匹配。

### 规则的 `<to>` 部分

规则的第二部分 是一个以逗号分隔的、非空的目标子句列表：

```sh
<to ><target_clause >[‘, ’<target_clause >]*>]>>
```

给定规则的目标子句也构成一个析取式，即它们指定的 ID 是目标凭证的备选项，下文描述的某些情况除外。

接下来的小节描述目标子句的语法、适用的默认值，以及每条规则的 部分中非冗余和非矛盾的原则。

### 目标子句

规则 部分中的目标子句必须为以下形式之一：

```sh
<target_clause >‘any ’>
```

```sh
<target_clause ><flags ><type >‘= ’<id >>>>>
```

第一种形式是一种紧凑的方式，指定允许任何目标凭证。第二种形式类似于 子句的形式，但有以下扩展：

* 也可以是字面值 `*`、`any` 或 `.`。`*` 和 `any` 都表示指定 的任意 ID，处理方式相同。. 表示进程在指定 下的当前 ID，如下所述。
* 最多可包含 `+`、`-` 和 ! 字符中的一个，仅当 为 `gid` 时才可非空。此外，如果 为 `*` 或 `any`，则只能出现 `+` 标志。

对于 `gid` 类型的目标子句，缺少标志表示指定的组 ID 可作为实际、有效和/或保存的组 ID（“主”组）。相反，存在任何允许的标志表示规范涉及补充组。每个标志有特定含义：

* `+` 表示该组 ID 可作为补充组。
* ! 表示该组 ID 是强制的，即必须列在补充组中。
* `-` 表示该组 ID 不得列在补充组中。

带 `-` 的规范只有与带 `+ Ns` 标记的规范结合使用才有用，其中只有一个规范的 为 . 。带有 ! 或 `-` 标志的目标子句是“强制”子句，因此不参与其他目标子句的析取，而是在其规则中无条件适用。

. 是调用进程在权限检查时已拥有的 ID 的占位符。对于 `uid` 类型，它表示进程的实际、有效或保存的用户 ID 中的任何一个。对于 `gid` 类型，其效果取决于是否存在标志。如果没有，它表示进程的实际、有效或保存的组 ID 中的任何一个。如果存在，它表示进程的补充组中的任何一个。

### 部分的默认值

如果 部分未列出类型为 `uid` 的目标子句，则调用进程的任何当前用户 ID 都被接受。换言之，在这种情况下，`mac_do` 的行为相当于列出了如下目标子句：

```sh
uid=.
```

类似地，如果 部分未列出类型为 `gid` 的目标子句，则假定调用进程的所有组都是必需的。更准确地说，每个期望的实际、有效和保存的组 ID 必须是当前实际、有效或保存的组 ID 之一，且所有补充组必须与当前的补充组相同。这相当于 部分包含以下两个子句：

```sh
gid=.,!gid=.
```

### 部分中的非冗余和非矛盾

单条规则的任意两个目标子句不得表达完全相同的逻辑意图，也不得相互矛盾。

实际上，任意两个子句不得显示相同的 ID，但对于组 ID，仅当每次相同的 ID 出现时都以不同的标志出现，或仅一次不带标志时才允许。此外，多次出现时指定的标志不得矛盾。例如，相同的组 ID 同时带有 `+` 和 `-` 会导致规则被拒绝。

### 解析细节

上述语法的标记周围允许任意数量的空白，但目标子句中的 和 之间不得有空格。

为方便起见，数字 ID 可指定为负整数，然后按 C 标准中 `uid_t` 和 `gid_t` 类型（均为 32 位无符号整数）的规定转换为无符号值。

## 配置

`mac_do` 的每个参数都有对应的 [sysctl(8)](/man8/sysctl.8.md) 旋钮。`mac_do` 的大多数参数支持按 jail 设置。

### Sysctl 旋钮

`mac_do` 提供的 [sysctl(8)](/man8/sysctl.8.md) 旋钮有两种类型：全局旋钮，影响该模块的所有使用；按 jail 旋钮，允许从 jail 内部（或主机内）检索或更改适用于该 jail（或主机）的设置。它们在下方的列表中相应标记。

按 jail 参数指示的默认值适用于主机。适用于 jail 的默认值在下面的 Sx Jail 参数 小节中描述。

以下旋钮可用：

**`security.mac.do.enabled`** 启用 `mac_do` 策略。（全局。默认：1。）

**`security.mac.do.print_parse_error`** 在尝试通过 `security.mac.do.rules` [sysctl(8)](/man8/sysctl.8.md) 旋钮设置不正确的规则时记录一条消息。（全局。默认：1。）

**`security.mac.do.rules`** 凭证转换规则列表，其语法在上面的 Sx 凭证规则 小节中描述。空字符串实际上会禁用该策略。（按 jail。默认：空。）

**`security.mac.do.exec_paths`** 授权可执行文件的绝对路径列表（相对于当前 jail 的根目录），以冒号（:）分隔。仅从这些可执行文件启动的进程会被 `mac_do` 策略考虑。空字符串实际上会禁用该策略。（按 jail。默认：`/usr/bin/mdo`。）

### Jail 参数

`mac_do` 的大多数参数是按 jail 的（参见上面的 Sx Sysctl 旋钮 小节）。按 jail 的参数有对应的 jail 参数，可用于在创建 jail 时设置其初始值，或从 jail 外部修改正在运行的 jail 中的值。

默认情况下，与主机一样，新 jail 对其进程禁用 `mac_do`，如同下面的 `mac.do` 参数被显式设为 `disable`。

除 `mac.do` 外，每个未指定的参数默认为复制正在运行的 jail 当前适用配置中的值，或在创建 jail 时从父 jail 复制。

以下 jail 参数可用：

**`new`** `mac_do` 将为 jail 使用特定配置。如果其他 jail 参数中的任何一个在应用默认值规则（参见前言）后最终为空，此情况降级为 `disable`。 **`disable`** 在 jail 中禁用 `mac_do`。通过确保 `mac.do.rules` 和 `mac.do.exec_paths` jail 参数中至少有一个为空来实现。如果在应用默认值规则（参见前言）后没有一个最终为空，`mac.do.rules` 将被强制设为空值。显式的 `disable` 与所有其他 jail 参数的显式非空值不兼容，将触发错误。 **`inherit`** 其他按 jail 参数从适用于 jail 父级的参数继承（这些参数本身可能也是继承的）。配置修改会立即传播到所有配置为继承的后代 jail（只要中间没有具有自己特定配置的 jail）。

**`mac.do`** 可能的值为：默认值取决于其他 jail 参数的缺失或存在及值。一旦后者之一存在且非空，默认值为 `new`，否则为 `disable`。继承永远不会隐式建立，必须显式请求。

**`mac.do.rules`** jail 的凭证规则。参见上面 Sx Sysctl 旋钮 小节中对应 [sysctl(8)](/man8/sysctl.8.md) 旋钮 `security.mac.do.rules` 的描述。另请参见前言中的默认值规则。

**`mac.do.exec_paths`** 授权的可执行文件。参见上面 Sx Sysctl 旋钮 小节中对应 [sysctl(8)](/man8/sysctl.8.md) 旋钮 `security.mac.do.exec_paths` 的描述。另请参见前言中的默认值规则。

### 一致性

从 jail 参数读取或设置的值始终与对应的 [sysctl(8)](/man8/sysctl.8.md) 旋钮一致，实际上操作的是同一个内部“变量”。

对某个参数或某个 jail 配置整体的访问，无论是读取还是修改，都是顺序一致的。换言之，它们看起来是原子的，所有线程都以相同的顺序看到它们发生。

## 实例

以下是匹配实际用户 ID 为 10001 的进程的若干单条规则示例：

**`uid=10001>uid=10002`** 允许进程将其所有实际、有效或保存的用户 ID 切换为 10002，但保留其所在的组，并保持相同的主/补充组划分。

**`uid=10001>uid=10002,uid=10003`** 与第一个示例相同，但还允许切换到 UID 10003 而非 10002，或在不同用户 ID 中同时拥有两者。

**`uid=10001>uid=10002,gid=10002`** 与第一个示例相同，但新主组必须设为 10002，且不应设置补充组。

**`uid=10001>uid=10002,gid=10002,+gid=.`** 与前一个示例相同，但额外允许保留任何当前补充组。

**`uid=10001>uid=10002,gid=10002,!gid=.`** 与前一个示例相同，但附加约束是必须保留所有当前补充组。

**`uid=10001>uid=10002,gid=10002,+gid=.,-gid=10001`** 与上面的 `uid=10001>uid=10002,gid=10002,+gid=.` 相同，但 10001 不能作为补充组保留。

**`uid=10001>uid=10002,gid=10002,+gid=.,!gid=10003`** 与上面的 `uid=10001>uid=10002,gid=10002,+gid=.` 相同，附加约束是 10003 必须出现在补充组中。

**`uid=10001>uid=10002,gid=*,+gid=*`** 与第一个示例相同，但解除对组的任何约束，允许进程加入其认为合适的任何组。

以下是匹配实际组 ID 为 10001 或在其补充组中包含 10001 的进程的若干单条规则示例：

**`gid=10001>uid=0`** 使 10001 成为更强大的 `wheel` 组，允许其成员无需密码切换到 root。

**`gid=10001>gid=10002`** 允许进程将 GID 10002 作为主组加入，但前提是放弃其所有补充组。

**`gid=10001>gid=10002,+gid=.`** 与前一个示例相同，但允许保留任何当前补充组。

**`gid=10001>gid=10002,!gid=.`** 与前一个示例相同，但附加约束是必须保留所有当前补充组。

## 参见

mdo(1), setcred(2), [mac(4)](/man4/mac.4.md), [jail(8)](/man8/jail.8.md), [sysctl(8)](/man8/sysctl.8.md)

## 作者

Olivier Certner <olce@FreeBSD.org> Baptiste Daroussin <bapt@FreeBSD.org> Kushagra Srivastava <kushagra1403@gmail.com>

## 缺陷

目前，`mac_do` 仅考虑通过 setcred(2) 系统调用请求的凭证转换。创建此系统调用很大程度上是为了让 `mac_do` 能查看整个凭证转换并决定是否授权，而传统 UNIX 通过连续更改不同部分的方式无法做到。

然而，对传统或标准凭证更改函数的调用本身可视为完整的转换（无论多么有限），因此同样应受到 `mac_do` 的监控。未来的工作将解除此限制。

## 安全考虑

`mac_do` 的威胁模型是将用户态程序视为通常不可信，无法决定哪些凭证更改是可接受的。这与传统的 UNIX 更改凭证方式形成对比，后者通过设置 setuid 位安装专用程序，赋予其完全的管理员权限，使其能够实际建立新的凭证。此类凭证更改程序中的漏洞可能对系统完整性造成灾难性后果。

因此，`mac_do` 不依赖伴随的用户态程序来决定某个凭证转换是否可接受。相反，它独立于用户态密码和组数据库维护自己的配置。建立此配置本身当前依赖于用户态程序发出 sysctl(3) 或 jail(2) 调用。因此，应在系统启动或 jail 启动附近、系统可能遭受任何攻击之前建立配置，并应采取进一步措施确保潜在的损坏不会影响后续重启中的配置，例如恢复原始状态或确保直至 `mac_do` 配置的引导过程可信。
