无新权限标志

execve 系统调用可以授予新启动的程序其父进程所没有的权限。最明显的例子是 setuid/setgid 程序和文件能力(file capabilities)。为了防止父进程也获得这些权限,内核和用户代码必须小心,防止父进程执行任何可能破坏子进程的操作。例如

  • 如果程序是 setuid 的,动态加载器会以不同的方式处理 LD_* 环境变量。

  • 非特权进程不允许使用 chroot,因为这会允许从继承了 chroot 的进程的角度来看,替换掉 /etc/passwd

  • exec 代码对 ptrace 有特殊处理。

这些都是临时修复措施。no_new_privs 位(从 Linux 3.5 开始引入)是一种新的通用机制,旨在让进程能够以在 execve 之后持久存在的方式安全地修改其执行环境。任何任务都可以设置 no_new_privs。一旦该位被设置,它就会通过 fork、clone 和 execve 被继承,且无法被取消设置。设置了 no_new_privs 后,execve() 承诺不会授予任何在没有 execve 调用时无法完成的操作权限。例如,setuid 和 setgid 位将不再改变 uid 或 gid;文件能力不会增加到允许集(permitted set)中,并且 LSM 在 execve 之后也不会放宽约束。

要设置 no_new_privs,请使用

prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);

不过要小心:在 no_new_privs 模式下,LSM 可能也不会收紧对 exec 的约束。(这意味着在执行守护进程之前设置通用的服务启动器以启用 no_new_privs 可能会干扰基于 LSM 的沙箱。)

请注意,no_new_privs 不会阻止不涉及 execve() 的权限变更。具有适当权限的任务仍然可以调用 setuid(2) 并接收 SCM_RIGHTS 数据报。

到目前为止,no_new_privs 有两个主要用例

  • 为 seccomp 模式 2 沙箱安装的过滤器会在 execve 期间持久存在,并可能改变新执行程序的行为。因此,只有在设置了 no_new_privs 的情况下,才允许非特权用户安装此类过滤器。

  • no_new_privs 本身可用于减少非特权用户可用的攻击面。如果以给定 uid 运行的所有进程都设置了 no_new_privs,那么该 uid 将无法通过直接攻击 setuid、setgid 和使用 fcap(文件能力)的二进制文件来提升其权限;它需要先攻破没有设置 no_new_privs 位的某些内容。

未来,如果设置了 no_new_privs,其他潜在危险的内核功能可能会对非特权任务开放。原则上,当设置了 no_new_privs 时,unshare(2)clone(2) 的几个选项将是安全的,并且 no_new_privs + chroot 比单独使用 chroot 的危险性要小得多。