systemd/沙箱
systemd 允许用户加固并把系统服务单元放到沙箱中运行。由于技术限制,以及讽刺的是,出于安全方面的考量,用户服务单元不能被加固或放到沙箱中,因为这可能导致权限提升相关的问题。不过这不会影响使用 User= 的系统单元。
从传统意义上来说,只有服务单元可以被加固或沙箱化处理。详情请参考 systemd.exec(5) 以及 systemd.resource-control(5)。
概述
由于加固或沙箱化处理会限制应用行为,因此不可能应用所有沙箱化指令。例如网络服务器不应该使用 PrivateNetwork=true,因为它一般需要网络访问权限。
可以使用 systemd-analyze security unit 来列出所有沙箱化指令并生成一个评分,有助于帮您决定接下来可以尝试开启什么设置。
Hello world 可以获得一个接近完美的分数。没有应用可以应用所有沙箱化设置。不幸的是,systemd 在沙箱化相关的配置有误时给出的错误信息有时是模糊的,甚至具有误导性。临时将日志等级设为 debug 可以获得真正有价值的信息。
# systemctl log-level debug
能力 (Capabilities)
capabilities(7) 可用于授予进程特定的特权。例如,CAP_NET_BIND_SERVICE 可以让非特权进程绑定低于 1024 的端口,从而避免以 root 特权启动进程。另一个显著的例子是可以授予备份程序 CAP_DAC_READ_SEARCH 以允许其读取任意文件。
- 建议始终同时使用
CapabilityBoundingSet和AmbientCapabilities。 -
systemd-analyze capabilities可以列出 systemd 可使用的能力。当您运行新增了能力的实验性内核时,这个命令会很有用。
要想采用上述建议,您可以在编辑服务单元时使用 AmbientCapabilities= 来授予其能力,并使用 CapabilityBoundingSet= 来确保没有预期外的能力被授予:
example.service
[Service] AmbientCapabilities=CAP_NET_BIND_SERVICE CapabilityBoundingSet=CAP_NET_BIND_SERVICE
还可以在 /etc/systemd/system.conf 中设置 CapabilityBoundingSet= 或在 /etc/systemd/systemd.conf.d 中创建 drop-in 配置文件来移除继承自 PID 1 以及其子进程的能力。不过大部分能力会被实际使用而不应该被移除。
常见指令
大部分指令可以在不引起太多问题的情况下应用到大多数应用中。
没有特殊配置的指令
简单的布尔设置,可以为启用或禁用。它们不能配置细节。
| 指令 | 影响1 | 破坏性2 | 备注 |
|---|---|---|---|
LockPersonality |
中 | 低 | |
MemoryDenyWriteExecute |
中 | 中 | 不兼容运行时动态生成代码的程序,例如 JIT、可执行栈[1]以及 C 编译器生成的代码“蹦床”。可以使用 SystemCallFilter 增强。
|
NoNewPrivileges |
高 | 低 | |
PrivateDevices |
中 | 低 |
/dev/null 等类似设备不会被禁用。
|
PrivateNetwork |
高 | 非常高 | 不允许任何网络访问。 |
PrivateTmp |
中 | 低 | |
PrivateUsers |
高 | 高 | |
RestrictRealtime |
低 | 低 | 可能有助于防止拒绝服务情况出现。 |
RestrictSUIDSGID |
中 | 低 | 建议和 NoNewPrivileges 一起使用。
|
- 指令产生的效果
- 指令破坏程序正常运行的可能性
可配置的指令
这些指令大部分都很有效而不会产生过多的影响。推荐考虑使用一些以下的指令。
| 指令 | 值 | 影响1 | 破坏性2 | 备注 |
|---|---|---|---|---|
ProtectSystem |
strict |
非常高 | 非常高 | 通常和 ReadWritePaths= 一起使用。
|
full |
高 | 中 | 可能会破坏如使用 ACME 来更新密钥的网络服务器等应用,因为它们需要的文件可能位于 /etc。
|
|
true |
高 | 中 | 理论上基本没有程序会往 /boot 和 /usr 写入。
|
|
ProtectHome |
true |
高 | 中 | 一些应用可能需要将持久化数据存储于 XDG_CONFIG_HOME3。
|
tmpfs |
高 | 中 | 家目录包含大量敏感数据,应用 tmpfs 或 true 也许能够避免泄露。4
|
|
read-only |
低 | 低 | 非常合适用于备份服务。 | |
ProtectProc5
|
invisible |
高 | 中 | |
noaccess |
中 | 中 | ||
RestrictAddressFamilies |
例如 AF_UNIX AF_INET AF_INET6
|
低 | 低 | 填写用空格分隔的地址族,详情请参考 address_families(7)。 |
- 指令产生的效果
- 指令破坏程序正常运行的可能性
-
StateDirectory=可以用于缓解一些负面后果。 - 这也会使
/run/user/不可访问,避免使用中的 IPC socket 泄露。理论上来说,sockets 也有可能在另外地方,例如/tmp。 - 当指令被省略时,默认为
/proc的 hidepid 挂载选项的值。通常来说为0(无限制)。
进阶指令
这些指令对多数单元来说不是很有用,因此很少用到。
| 指令 | 值 | 影响 | 破坏性 | 备注 |
|---|---|---|---|---|
DynamicUser |
布尔值,例如 true
|
中 | 中 | 需要和 StateDirectory=、RuntimeDirectory=、CacheDirectory=、LogsDirectory=、ConfigurationDirectory= 结合使用。[1]
|
ProtectClock1,2
|
布尔值 | 低 | 中 | 有些用户返回当该指令启用时,smartctl 不能运行,不过该指令应该是安全的。
|
ProtectControlGroups1
|
布尔值 | 中 | 低 | |
ProtectHostname1,2
|
布尔值 | 低 | 低 | |
ProtectKernelLogs1
|
布尔值 | 低 | 低 | 所有官方内核都将 SECURITY_DMESG_RESTRICT 设置为 y,不过这个选项仍然可以达成纵深防御。
|
ProtectKernelModules1
|
布尔值 | 中 | 低 | |
ProtectKernelTunables1
|
布尔值 | 低 | 低 | |
RestrictFileSystems |
例如 ext4 tmpfs
|
中 | 中 | 填写用空格分隔的文件系统列表或指定集合,如 @network。详情请运行 systemd-analyze filesystemd。
|
SystemCallArchitectures |
例如 native
|
低 | 低 | 另请参阅 #禁止非原生架构系统调用;对单元文件设置这个指令可以覆盖系统默认配置,从而把特定单元排除在系统限制之外。 |
SystemCallFilter |
例如 @system-service
|
中 | 高 | 另请参阅 systemd.exec(5) § SYSTEM CALL FILTERING;少写哪怕一个系统调用都可能使您的应用在不合时宜的情况下崩溃。 |
SocketBindAllow/Deny |
例如 SocketBindAllow=ipv4:22、SocketBindDeny=any
|
中 | 中 | 建议和 CAP_NET_BIND_SERVICE 一起设置以确保特权上下文只能绑定预期的端口。
|
- 如果服务单元未以 root 运行或通过
AmbientCapabilities=等指令被授予特权,则设置该指令是多余且不必要的。如果服务单元以另一个低权限普通User=运行,则这些设置完全是多余的并可以被安全地忽略。 - 仅限制应用执行相关的系统调用,而不限制应用通过 systemd 提供的 IPC 服务(即 systemd-timedated 以及 systemd-hostnamed)来访问。[2]如果需要绝对安全,务必注意同时限制涉及 D-Bus/Varlink 的方法。
chroot jail
通过指定 TemporaryFileSystem=/:ro 并只挂载需要的路径到这种类 chroot jail 以激进地限制进程能看到的文件是可能的。
RootDirectory 要求路径必须存在,而 TemporaryFileSystem 则不需要,且会无缝修改 /。由于没有使用 chroot 系统调用,两者,尤其是后者,看起来都属于安全的类 chroot 指令,无法被轻易逃逸。
ProtectSystem 和 ProtectHome 这两者与 TemporaryFileSystem=/:ro 不兼容且会导致后者失效,导致 / 再次可见。不过这两个指令都不需要,因为可见的路径是由白名单决定的。请参阅上面的警告,出现这个问题的原因是这些指令之间的相互作用并不被支持或纳入考量。所有需要的路径都需要通过 BindReadOnlyPaths 和 BindPaths 挂载到 jail 中:
example_jailed_unit.service
[Unit] Description=示例单元 [Service] ExecStart=/home/someuser/executable User=someuser Group=someuser TemporaryFileSystem=/:ro PrivateTmp=true BindReadOnlyPaths=/usr/lib /lib64 /lib BindPaths=/home/someuser/executable
以上只是一个最小的示例而大多数应用需要更多的白名单路径。常见的有:
-
/etc/ca-certificates、/etc/ssl /etc/resolv.conf/usr/share/zoneinfo- 任何需要使用的 sockets,如
/var/run/mysqld/mysqld.sock
首次尝试把一个单元放到沙箱中运行往往需要调试。如果它完全不能启动或以 status=203/EXEC 失败,则说明无法访问可执行文件或需要的库文件。可以从宽泛的路径(如允许整个 /usr)开始,逐步缩小可访问的路径范围。
system.conf
/etc/systemd/system.conf 的更改是全局性的,因此会影响所有单元。另请参见 systemd-system.conf(5)。
禁止非原生架构系统调用
非原生架构二进制文件,大部分情况下是 32 位二进制文件,可能一定程度上损害系统安全性,因为部分加固并不能良好工作。此前也有过影响非原生系统调用的低危害漏洞,例如 CVE-2009-0835。
file not found 错误。/etc/systemd/system.conf
SystemCallArchitectures=native
这个配置在大多数系统上都可以正常工作,但如果安装了 multilib 的软件包的话,就需要在一部分情况下禁用。尤其注意使用 Wine 玩游戏可能会受影响。使用 systemd-run 或修改会话切片可以按需覆盖 SystemCallArchitectures 配置以禁用限制。
启用更多单元统计
systemd 默认情况下不会跟踪一个单元的所有资源使用情况。开启 Default*Accounting 可以在 systemctl status 输出和日志中查看更多统计信息。严格意义上来说,这不是一个安全设置,但是它确实可以使调试更简单,并有助于深入了解资源使用情况。
/etc/systemd/system.conf
DefaultIOAccounting=yes DefaultIPAccounting=yes DefaultMemoryAccounting=yes DefaultTasksAccounting=yes
参考资料
https://docs.rockylinux.org/10/guides/security/systemd_hardening/
- ↑ 译者注:真有程序还在用这玩意吗?