关注了就能看到更多这么棒的文章哦~
Unprivileged chroot()
By Jonathan Corbet
March 15, 2021
DeepL assisted translation
https://lwn.net/Articles/849125/
可以说大多数 Linux 开发者都不会在应用程序中使用 chroot()。这个系统调用会将调用它的进程放入一个新的文件系统视角,也就是将传入的目录作为根目录。它可以用来将此进程与文件系统的大部分隔离开,不过这个安全优势还是比较有限的。调用 chroot()是一种特权操作,但是,如果 Mickaël Salaün 能成功推销这组 patch set 的话,至少在某些情况下后面会发生变化。
chroot() 通常会被用来对网络守护进程(network daemon process)放到一个小监狱(jail)里。如果该进程被入侵了,那么它也只能访问这个新设置的根目录之内的子目录。由此而产生了安全防护的效果,尽管这种防护并不是最强防护(有很多方法可以突破 chroot()的限制),但它仍然可以为攻击者增加一些麻烦。chroot()还可以用来让各个进程看到不同的文件系统,比如运行 container 的时候就需要这个能力。
这个系统调用并不是任何人都可以使用的,需要有 CAP_SYS_CHROOT 这个 capability 才能调用 chroot()。进行这个限制是为了防止攻击者在精心制作的文件目录树中运行 setuid 程序,从而来欺骗(和利用)这些程序。举个简单的例子,如果 setuid 程序看到的/etc/passwd 或/etc/sudoers 文件其实是攻击者提供出来的,那么就会产生严重的错误行为。
长久以来,chroot()的局限性导致了它很难得到应用。近年来,它的应用范围更小了,因为出现了 Mount namespace 这种更灵活的机制也可以实现使用新的文件目录树的功能,并且也更难被攻破。所以相对来说,很少有开发者认为在今后的开发中需要使用 chroot()。
因此,当 Salaün 带着他的 chroot()补丁出现时,人们有点惊讶。一旦合入了这个 patch,无权限的进程也可以调用 chroot(),但前提是必须满足几个条件:
-
此进程必须在调用 prctl()时使用 PR_SET_NO_NEW_PRIVS 选项。这就阻止此进程在今后获得任何新的特权。例如运行 setuid 和 setgid 程序也将不再能获得这些程序属主的特权。既然在该模式下不再会有特权程序,那么相应地这些特权也就不会再被利用了。
-
此进程不能与其他任何进程共享它的文件系统上下文(也就是 struct fs_struct,其中包含了 root 目录和当前工作目录这些信息),否则 chroot()调用会对这两个进程都生效,而另一个进程可能不会预料到它的文件系统环境会突然改变。
-
新的 root 必须在文件系统层次结构中的当前 root 之下。这就阻止了那些可能会导致进程可以跳出当前 jail(监牢) 或 mount space 之外的手段。
如果满足这些条件,可以认为,允许进程调用 chroot() 就是安全的。
但是,为什么要这样做呢?其中一个原因是,一个正常运行的 chroot() 环境通常需要有一个包含了最少信息的/dev 目录,而创建这些设备节点仍然是一个需要特权的操作。如上所述,Linux 早就已经有了比 chroot() 更好的选择了。但 Salaün 说,在一些使用场景中,某个进程可能希望在从不受限的环境中加载到它的依赖关系(例如函数库)之后,自己进入沙箱,而设备文件通常可能是用不到的。
对这个 patch 的最初反应比较冷淡。Eric Biederman 担心无权限的 chroot()与其他机制混合使用之后会产生安全问题:
在建立了 sandbox、使用了 seccomp filter、并且启用了 no_new_privs 之后,还仍然允许 chroot,这似乎是在自找麻烦,并且可能会削弱现有的 sandbox。
Casey Schaufler 认为 chroot()已经过时了,同时也担心类似的跟其他机制混合使用的问题:"我们仍然发现了一些 corner case(比如 ptrace),在这些情况下 no_new_privs 是有缺陷的"。他还指出,对 chroot()的访问已经有了更加精细的 capability 控制,也就是 CAP_SYS_CHROOT。
CAP_SYS_CHROOT 就是针对 chroot 的。它不会提供其他不需要的权限,不像 CAP_CHOWN 或 CAP_SYS_ADMIN 那样。把 chroot 变成不需要特权的这种做法是愚蠢的,因为它可能是 capability 机制本来就应该起作用的场景。
Salaün 并没有回答所有这些问题,但似乎并不气馁,在讨论结束后,他发布了第二版 patch set。不过,如果没有更有说服力的回应的话,很难将此改动推到 upstream。以安全为导向的开发者需要被说服,让他们相信 chroot()值得改进。对于那些与其他安全机制混用可能会出现意外的改动,upstream 的门槛会更高。
讨论最终很可能会回到具体使用场景上。在 2021 年是否还真的需要不需要特权的 chroot()?如果有用户可以从这个功能中受益,现在可能是他们站出来解释为什么需要这个功能的好时机。如果没有这些信息,那么 unprivileged chroot() 很可能就是不会成为现实。
全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。
欢迎分享、转载及基于现有协议再创作~
长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~