首页 / 内容指南 / 当前文章

systemd 报 status=226/NAMESPACE 或目录只读:ProtectSystem 与 ReadWritePaths 排查

发布于 2026-08-26 · Content Fleet 编辑部

systemd 报 status=226/NAMESPACE 或目录只读:ProtectSystem 与 ReadWritePaths 排查

先取完整的单元与日志证据

保存 systemctl status <unit> --no-pager -ljournalctl -u <unit> -b --no-pager、systemd 版本、内核版本和服务的实际用户。对 226 错误,查找失败的 namespace 类型、路径和 errno;对应用内部的写入拒绝,保存准确的目标路径和操作类型。

再运行 systemctl cat <unit> 查看主单元及全部 drop-in,不要只打开 /usr/lib/systemd/system 中的 vendor 文件。实际生效配置可能来自 /etc/systemd/system/<unit>.d//run/systemd/system 或模板/实例 drop-in。

`status=226/NAMESPACE` 的准确含义

systemd.exec 的退出码表将 226 定义为 EXIT_NAMESPACE,即设置 mount、UTS 或 IPC namespace 失败。这一阶段通常发生在 ExecStart= 的程序真正执行前,所以更换应用参数或修改其内部日志级别往往没有帮助。

常见诱因包括 allow-list 中的路径不存在、必需挂载尚未准备、容器或内核不支持所需 namespace、文件系统传播条件不满足,以及多个 sandbox 设置组合后无法建立视图。先用日志找出失败操作,不要一次关闭全部加固项。

`ProtectSystem=` 为什么让 root 服务也写不进去

ProtectSystem=true 会使 /usr 和引导目录对该服务只读;full 还包括 /etcstrict 则将几乎整个文件系统层次设为只读,仅排除特定 API 文件系统。这是服务命名空间中的挂载限制,即使进程的 UID 为 0,也不等于可以无视只读挂载。

不要将修复做成 ProtectSystem=false。先列出服务真正需要持久写入的少量路径,再通过托管目录或 ReadWritePaths= 精确开放。

`ReadWritePaths=` 不是普通的 chmod

ReadWritePaths= 可以从 ProtectSystem= 造成的只读视图中排除指定路径,但它不会自动创建目录、改变宿主机上的所有者,也不会绕过上层目录不可访问、SELinux/AppArmor 或底层文件系统本身只读。

在宿主机上先确认路径存在,然后以服务的 User=/Group= 检查每一级目录的搜索和写入权限。修复后还要在单元的 namespace 内验证挂载视图,因为宿主机 shell 中的 touch 成功不能证明服务视图可写。

优先使用托管目录指令

对稳定的服务数据,优先使用 StateDirectory=CacheDirectory=LogsDirectory=RuntimeDirectory=。systemd 可为这些指令创建标准目录、设置合适所有者,并将它们排除在 ProtectSystem= 的只读效果之外。

RuntimeDirectory= 适合随服务生命周期或启动期管理的运行时文件,不应放持久业务数据。StateDirectory= 用于持久状态,CacheDirectory= 用于可重建缓存,LogsDirectory= 用于专用日志。按语义分开可以避免一个宽泛 ReadWritePaths=/var 破坏 sandbox。

`DynamicUser=` 会隐式开启更多保护

systemd.exec 说明,启用 DynamicUser= 时,ProtectSystem=strictProtectHome=read-onlyNoNewPrivileges= 等保护会被隐式应用。因此只在新版 drop-in 中加了 DynamicUser=yes,却没改变任何显式 ProtectSystem 行,仍可能让原来写入任意目录的服务失败。

对 DynamicUser 使用托管目录,不要把动态 UID 直接当成宿主机持久目录的稳定所有者。检查该单元每次启动看到的目录映射,以及停止后哪些运行时文件应被清理。

`ProtectHome=` 与非标准数据路径

ProtectHome=trueread-onlytmpfs 会以不同方式限制 /home/root/run/user。如果应用把数据、密钥或模型放在这些目录,服务可能看到空视图、只读视图或完全不可访问。

不要为了便利直接允许整个 home。将服务数据迁移到标准状态目录,或精确绑定必要的只读/可写路径。如果开放密钥目录,必须保持最小可读范围和正确所有者。

`PrivateTmp=` 为什么让两个进程看到不同文件

PrivateTmp= 为服务提供私有的 /tmp/var/tmp 视图。宿主 shell 或另一个服务写入 /tmp/token,当前单元不一定能看到同一文件。这不是数据随机丢失,而是隔离的预期效果。

不要用公共 /tmp 作为跨服务的可靠 IPC 或持久数据交换区。如果两个单元需共享状态,使用受控的 runtime/state 目录或明确 IPC 机制,并给出最小访问权限。

区分 mount sandbox 与 SELinux/AppArmor

如果写入失败为 EROFS,优先检查只读挂载和 sandbox。如果是 EACCESEPERM,除了 UNIX 所有者与 mode,还要查 SELinux AVC、AppArmor denial、ACL 和文件系统属性。一个路径在 namespace 中可写,不代表 LSM 也允许该操作。

不要通过全局关闭 SELinux/AppArmor 或把服务改成 root 来证明根因。受控对照可以临时放宽一个单元设置,但最终修复要精确到目录、操作和服务域。

使用 systemd-analyze 检查而不只靠启动结果

systemd-analyze verify 可检查单元文件中的未知指令、缺失依赖等问题。systemd-analyze security <unit> 则评估单元的 systemd sandbox 和安全设置,但它的 exposure 分数不是“服务没有漏洞”的证明。

对每个新增 sandbox 项建立功能回归:启动、读配置、写状态、轮转日志、重载、停止和恢复。不要为了降低 exposure 分数就盲目打开与应用需求冲突的设置。

一套最小化诊断流程

1. 用 systemctl cat 保存完整合并单元,记录 systemd 版本。

2. 从 journal 区分 226 namespace 初始化失败与应用运行后写入失败。

3. 运行 systemd-analyze verify 并核对 drop-in 顺序。

4. 列出 ProtectSystemProtectHomePrivateTmp、ReadOnly/ReadWrite/Inaccessible paths 和 DynamicUser。

5. 以服务用户检查目录存在性、父目录搜索权和写入权。

6. 优先改用 State/Cache/Logs/RuntimeDirectory,只对必需路径开放。

7. 检查 SELinux/AppArmor 和底层挂载是否继续拒绝。

8. 重启后验证功能与 systemd-analyze security,再做重启和回滚测试。

修复时的 drop-in 策略

尽量在 /etc/systemd/system/<unit>.d/ 中创建范围最小的 drop-in,而不要直接改 vendor unit。systemd.unit 说明 drop-in 文件按字典顺序合并,模板单元、实例单元和包含连字号的单元还有层级搜索规则。

发布后执行 daemon-reload,再用 systemctl cat 重新取实际结果。回滚时删除本次明确的 drop-in 并重载,不要用 systemctl revert 无区别删除其他管理员的本地定制。

上线验收标准

  • 单元在干净启动和重启中不再出现 226/NAMESPACE。
  • 服务只能写入明确列出的状态、缓存、日志和运行时目录。
  • 以服务身份读写所需路径成功,对未授权路径写入失败。
  • PrivateTmp 与 ProtectHome 的行为符合数据交换设计。
  • SELinux/AppArmor 保持开启,无未处理 denial。
  • 升级、日志轮转、配置 reload 和宕机恢复不依赖宽泛写权限。
  • 回滚点能恢复上一份已验证单元,不影响无关 drop-in。

常见问题

226/NAMESPACE 表示应用代码崩溃吗?

通常不是。它表示 systemd 在执行程序前建立 mount、UTS 或 IPC namespace 失败。

把服务改成 root 能解决只读文件系统吗?

不一定。ProtectSystem= 建立的只读挂载视图不会因进程 UID 为 0 就自动消失。

`ReadWritePaths=` 会自动创建目录和设置所有者吗?

不会。对标准状态目录,应优先使用 StateDirectory=CacheDirectory=LogsDirectory=RuntimeDirectory=

开启 PrivateTmp 后宿主机 `/tmp` 文件为什么不见了?

因为服务看到独立的 /tmp//var/tmp 视图。跨服务数据不应依赖公共临时目录。

`systemd-analyze security` 分数低就证明服务安全吗?

不证明。它评估 systemd 提供的沙箱设置,不包含应用代码漏洞、IPC 授权和所有外部安全机制。

总结

systemd 下的目录只读与 226/NAMESPACE 问题,核心是理解服务看到的挂载命名空间。先从完整合并单元和 journal 区分 namespace 初始化失败与运行时拒绝,再检查 ProtectSystem、ProtectHome、DynamicUser、PrivateTmp 和路径白名单。修复应优先使用 systemd 托管目录,只开放必需写入路径,并保留 SELinux/AppArmor 等安全层。最终以功能回归、负面权限测试和可恢复 drop-in 发布作为验收。

来源资料

  • systemd.exec(5):https://man7.org/linux/man-pages/man5/systemd.exec.5.html
  • systemd-analyze(1):https://man7.org/linux/man-pages/man1/systemd-analyze.1.html
  • systemd.unit(5):https://man7.org/linux/man-pages/man5/systemd.unit.5.html
  • systemctl(1):https://man7.org/linux/man-pages/man1/systemctl.1.html