容器 08:namespace 之外还需要什么权限边界
把拒绝归还给负责的层
capability 将特权操作拆成较细粒度的授权,内核权限检查会结合针对的 user namespace。CAP_SYS_ADMIN 在新 user namespace 中的有效位不等于初始 namespace 的万能通行证,挂载还受文件系统与上层策略约束。seccomp BPF 按系统调用及可检查参数限制调用面,不识别“这个路径属于秘密”这样的完整安全语义;内核文档明确说过滤器本身不是 sandbox。no_new_privs 禁止 exec 后取得比此前更多的特权,不能据此说所有操作都会拒绝。SELinux、AppArmor 等 LSM 是否启用、具体 profile 如何拒绝,要按专用 VM 的配置和审计日志核查,不能套用另一台机器的默认值。
从系统调用走到目标对象
进程请求 mount 或 sethostname 时,第一步是辨认它调用的确切 syscall、参数和目标对象;第二步辨认发起者的实际凭据、用户 namespace 和 capabilities;再看内核操作该对象时要求的权限、现有 seccomp 过滤器以及 LSM 是否另有拒绝。仅有 EPERM 一行不携带“是谁拒绝”的字段。cgroup 限额又是另外一条资源控制路径,看到调用被拒不等于 CPU、内存都被限制。出于这一原因,实验要一轮只改变一个约束,保存更改前后 CapEff、NoNewPrivs、Seccomp、namespace inode 与相应审计信息,而不是把新 user namespace、capability 丢弃、seccomp 和文件权限一次同时打开,最后猜失败来自哪个设置。
capability 也有多个集合:有效集决定当下哪些特权位可用,允许集和可继承集影响后续变更,bounding 集限制 exec 获得特权的范围。只复制一行 CapEff 足以证明“此刻有效集的哪几位为 1”,不能证明执行下一个二进制之后的权限仍完全相同。即使有效集中有局部 CAP_SYS_ADMIN,其效力要看被检查对象归哪个 user namespace 以及内核具体路径;05 的“内部 uid=0 映射宿主 uid=1001”也不能直接推出“任意 syscall 都以宿主 root 权限运行”。对照应先测一个不需要能力的基本操作,再对同一个对象尝试需要能力的操作,检查能力删除前后的差别。
no_new_privs 的不可逆边界
Linux 内核文档指出,进程设置 no_new_privs 后该位会随 fork、clone、exec 继承,不能再复位;exec 不再赋予进程原本没有的额外特权。它并不主动撤销进程已经持有的所有权限,也不是“默认阻止系统调用”的规则,更不会替不可信代码建立文件系统边界。这个位还是非特权进程安装某些 seccomp filter 的前提:如果设置失败,下一步不能直接写“seccomp 安装成功”。可信实验程序在任何过滤器生效之前打印 prctl 的返回与 /proc/self/status 中对应字段,只给自编进程修改自己的状态,不改变宿主的全局安全机制。
反例是一份 set-user-ID 可执行文件:如果程序指望 exec 后得到文件拥有者身份而执行环境已启用 no_new_privs,那么“程序文件上有 setuid 位”不能单独保证它真的得到了提升。对照不能在共享宿主新建高权限二进制,而应在专用隔离 VM 的自建文件和测试账户上完成,并检查 exec 前后的实际身份、进程状态及文件权限。没有这个环境,本篇只按内核文档解释条件,不把映射实验冒充 setuid 行为验证。
seccomp 测的是调用面,不是目录机密性
过滤器能按系统调用号和当前调用参数的位模式做决策,例如让可信小程序调用 getpid 时返回指定 errno、仍允许 getppid。若过滤器安装成功,拒绝 getpid 不会自动阻止同一程序读取磁盘上的秘密文件:openat 仍需单独检查,更难由 seccomp 安全地判定传入用户指针所指向的路径内容。内核文档明确提醒 system call filtering 不是完整 sandbox;即使过滤器正好拦住一条危险调用,代码仍可能经另一条允许的 syscall 或预先打开的 FD 接触同一个对象。生产安全边界还需要权限、对象视图、LSM、审计与威胁模型,不能因为一个 demo 返回 EPERM 就批准运行第三方程序。
在实验里,先调用未过滤的 getpid 并保存正常结果;设置 no_new_privs,再安装仅针对该可信进程的最小 filter;调用被拒路径,用直接 syscall 读取真实 errno;最后调用仍被允许的 getppid。这比拿 mount EPERM 举例更容易归因,因为此时目标操作不需要 capability 或挂载前提。但本例只能证明选定进程、选定系统调用的过滤器效果;若运行时已施加另一个过滤器,Seccomp 状态与过滤器叠加规则还要单独记。程序退出后过滤器仅随这个实验进程结束,不能对共享宿主进行“关闭 seccomp”或修改别的任务 profile 的清理操作。
LSM 是另一层策略,不是任意 EPERM 的别名
AppArmor、SELinux 以及其他内核安全模块对主体与对象的策略不同;一个节点装了包不等于当前任务启用了特定 profile,也不等于所有拒绝都会在同一个日志路径出现。检查 /sys/kernel/security/lsm、进程对应的标签/profile、规则版本和内核审计信息,才有机会把一次拒绝归因于 LSM。若这些接口因权限不可读,只能记该层没有直接证据,不能推断“肯定是 AppArmor”。容器镜像里的进程 UID、capability 位、seccomp 状态及 LSM 标签应在同一运行时对象与相近时间窗口对齐,避免把另一个容器的 audit 行误配给本次进程。
应用无法写入一个 bind 目录时,可以先从内核返回码出发逐层排除:目标路径是否存在、挂载是否只读、内外 UID/GID 的映射与 mode/ACL 是否允许、操作是否触及特权 syscall、LSM 是否有对应拒绝。如果写入失败发生在文件系统权限检查,增加 CAP_NET_ADMIN 对这类写入没有意义;若发生在网络接口配置,放宽宿主卷目录也不能修复。与其叠加全部权限重试,不如每次仅改变一项受控条件并复现同一个 syscall,记录成功分支、拒绝分支以及残余的不确定性。
正例的设计是对同一可信探针逐步减少 capability、加最小 seccomp 拒绝规则:普通只读文件操作保持可用,而指定敏感操作返回可解释的 errno,记录过滤器安装前后与审计证据。失败案例是 mount 返回 EPERM:它可能来自 capability、容器环境、LSM 或挂载前提,单凭 errno 不能确定是哪一层。为了检验归因,至少各次只改变一个约束,锁定相同内核及配置,并检查 CapEff、NoNewPrivs、Seccomp 和对应审计事件。
本云端基线 CapEff=0、Seccomp=0,尽管 unshare -Ur 内可以获得局部映射身份,--mount-proc 仍被拒绝。可信自编seccomp 源码(校验)在单个实验进程里成功设置 no_new_privs=1,安装 filter 后直接调用 getpid 得到 -1/EPERM,仍允许 getppid 返回正 PID;程序退出后父进程 NoNewPrivs=0、Seccomp=0。完整输入、输出和清理记录记录实际环境、两条调用与退出码。这是局部 LAB_VERIFIED:没有完成 capability drop、容器运行时与 LSM 对照,因此“最少权限和允许/拒绝调用”的整篇验收仍 NOT_RUN。补跑入口与共用探针归档可独立获取;共用归档并不包含新过滤器源码,教学探针不承担不可信代码的沙箱职责。
这一正例的判断比“运行程序时没有报错”更具体:在安装前 getpid 给出实际正 PID,安装后同一个程序的直接系统调用返回 -1 且 errno=1,而 getppid 仍返回正 PID;如果只看到 getpid=-1 却不检查 errno,无法分辨过滤器返回了什么错误。子进程把 no_new_privs 设为 1 后不能在本进程撤回,所以清理方式是它正常退出,而不是试图在原进程把位改回 0。父 shell 随后仍报告两项状态均为 0,表明此次测试没有把过滤策略设在整个宿主上。这既支持“受控调用面生效”,也限定了该证据只属于此次创建的可信进程。
过滤器按 x86_64 ABI 的架构字段与系统调用编号做演示检查,对 getpid 返回 SECCOMP_RET_ERRNO | EPERM,其余调用示例性地放行。实际安全策略不能套用“其余全部允许”:多架构 ABI、x32 调用编号、参数类型、已打开 FD 和动态库调用路径都会改变可达的操作集合。生产策略若只根据这个示例部署,会遗漏许多路径;若粗暴把未列出的所有调用都拒绝,程序可能连打印错误和退出都无法完成。教学实验固定只运行项目自编的最小探针,先说明一个规则如何落在进程上,再在独立的评估里定义威胁模型和完整允许集。
另一个失败分支是 PR_SET_SECCOMP 安装本身返回错误:no_new_privs 未置位、外层运行时已有约束、内核配置或传入的 BPF 程序不被接受都需要逐一检查。只看第二次 getpid 依旧成功并不能证明 seccomp 功能不存在,程序必须先保存 prctl 失败位置、返回码和对应状态。当前云端正常分支运行成功,尚未构造“安装失败”的专用对照;后续 VM 实验应在不改全局保护的条件下固定一项先决条件并观察拒绝,不能为制造漂亮反例把生产 profile 关掉。
判断一套权限设计可否迁移时,把“被保护对象”放回每条规则旁:限制发起者在某个 user namespace 的 capability、过滤某个 syscall、阻止 exec 后权限提升以及用 LSM 约束对文件对象的访问,各自覆盖不同问题。若任务必须写一个本来只读的卷,即使过滤了全部网络相关系统调用也不会让卷可写;若任务必须抵挡不可信进程读取另一个目录,单条 getpid 拒绝规则也毫无帮助。只报告本次规则实际覆盖了什么,其余对象和攻击路径仍需专用安全验证。
目前尚无固定版本运行时创建的隔离容器及可控的 LSM 拒绝对照,故即使在普通 Linux 进程里成功测试一条自定义 filter,也不能把整篇“最小权限容器”实验标为通过。要在专用 VM 重跑可信探针,分别保存过滤器安装结果、被拦 syscall 的真实 errno、普通允许操作的结果和 LSM 审计有无相应事件。若某一次安装被环境拒绝,原样保存 syscall、错误和内核配置,不通过 --privileged 或关闭宿主保护去制造正例。
| 现象 | 分层取证 | 不可直接推出 |
|---|---|---|
EPERM |
capability、seccomp、LSM、文件系统 | 某单层必然负责 |
| 内侧 root 能改 hostname | userns、UTS inode | 能改宿主挂载 |
| 安装 seccomp filter | 规则、调用返回、误报/漏报 | 完整安全沙箱 |
练习
- 先预测在有局部
CAP_SYS_ADMIN的 user namespace 内运行mount是否必然成功,再在专用 VM 对照 capability 与文件系统挂载限制,记录准确错误与审计依据。 - 为自己编写的探针选择一条允许和一条拒绝的系统调用路径,先写出应返回的状态,再在专用 VM 安装过滤器并记录调用结果;若 LSM 也拒绝,要区分证据来源。
系列总目录:从进程隔离到运行时与编排。





