核心是通过 mount namespace 和只读挂载实现“看不见、改不了、写不进”,systemd 原生支持:ProtectSystem=strict、ProtectHome=read-only、ProtectKernelTunables=yes 等参数锁定关键路径,配合 ReadWriteDirectories 显式放行必要目录,InaccessiblePaths 和 RestrictAddressFamilies 隔离设备访问,最后通过 findmnt 和写入测试验证 ro 挂载生效。

限制关键业务的物理读写权限,核心是让服务进程“看不见、改不了、写不进”不该碰的路径——不是靠 chmod 或 chown 拦截,而是通过 mount namespace 和只读挂载,在进程视角里彻底移除写入能力。systemd 原生支持这套机制,无需容器运行时。
基础只读保护:从系统关键路径开始
先锁定最常被篡改的系统区域:
-
ProtectSystem=strict:把
/usr、/boot、/etc全部以只读方式 bind-mount 到服务的命名空间中。即使进程是 root,也无法修改二进制、内核镜像或配置文件 -
ProtectHome=read-only:将
/home、/root、/run/user设为只读,防止密钥、SSH 配置、会话凭证被读取或覆盖 -
ProtectKernelTunables=yes 和 ProtectKernelModules=yes:禁止写
/proc/sys和/sys,堵住通过 sysctl 或模块加载实现的提权路径
按需放开写入点:避免“全盘只读”导致启动失败
全局只读(ReadOnlyFileSystem=yes)几乎必然失败。应采用“默认只读 + 显式放行”策略:
- 用 ReadOnlyPaths=/ 实现根目录级只读(慎用,需配合豁免)
- 更推荐组合:ReadWriteDirectories=/var/lib/myapp /run/myapp /var/log/myapp —— 只开放业务真正需要持久化或临时写入的目录
- 对缓存类路径,优先用 TemporaryFileSystem=/tmp:mode=0755 或 /var/run:rw,挂载 tmpfs,重启即清空,不落盘
- 避免
ReadWritePaths=直接指定文件(如/etc/myapp.conf),它不检查父目录权限,可能绕过只读限制;目录级控制更安全
切断物理设备访问:防止绕过文件系统层
某些服务(如备份、监控、存储代理)可能直连块设备或 SCSI 设备,需额外隔离:
-
InaccessiblePaths=/dev/sdb /dev/nvme0n1:将特定磁盘设备设为不可见(挂载为空目录),服务进程
open("/dev/sdb", O_RDWR)直接返回 ENOENT -
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6:禁用
AF_NETLINK、AF_PACKET等,防止通过 netlink socket 查询或控制块设备状态 - 若服务确实需访问某设备(如
/dev/sg0),配合 DeviceAllow=/dev/sg0 rwm 显式授权,并搭配 RestrictDevices=true 关闭其他设备自动暴露
验证是否真正生效
配置后不能只看服务是否启动成功,要确认限制落地:
- 启动服务后执行:
systemctl status myapp.service,留意 mount 相关报错(如 “failed to mount read-only”) - 进入服务命名空间:
systemd-run --scope --same-dir --shell -p "MyAppScope" bash,再运行findmnt | grep myapp,检查关键路径是否含ro标志 - 尝试写入受保护路径:
echo test > /etc/passwd应返回Permission denied,且ls -l /etc/passwd显示仍是宿主机权限(证明是 mount 层拦截,非权限位控制)

















