最近有一个需求,就是要在服务器端执行用户传过来的命令。为了防止用户运行一些恶意指令,我们需要构造一个沙箱来运行。这种隔离的需求第一反应就想到了之前写过的 使用 overlayfs 和 chroot 搭建临时环境,但是这需要 root 权限。有没有办法在不需要 root 的情况运行 mount
和 chroot
呢?简单搜了下就找到了想要的答案。
unshare
是 linux 下的一个命令,通过构造一个新的 namespace 来和现有的环境隔离。基本用法很简单,就是一些选项,然后再加上要运行的命令即可,看起来有点像一个精简版的 docker。而且和 docker 一样的是,只要运行的命令退出了,在命令运行期间做的操作都会被自动清除。这个完美符合我们对沙箱的要求。
先来做个测试。通过 unshare
把当前用户映射成 root,并创建一个新的 mount namespace
:
unshare -rm /bin/bash
运行之后发现我们的用户变成了 root!另外一个不容易发现的内容就是,在我们所创建的这个 mount namespace
中进行 mount
和 umount
操作,不会影响外面的真实的文件系统。也就是说如果我们在这个环境中 unmount 了一个挂载点,退出进程(也就是我们这里的 bash)之后这个挂载点还在,并没有被 unmount。但是除此之外的其它操作(例如增删文件)都会被保留下来。这也是 unshare
这个名字的由来:只有指定的选项才会被 unshare,没有指定的都还是 share 的。
稍微解释下 -rm
这两个选项。-r
表示将当前用户映射成 root 用户,-m
就是刚提到的 mount namespace
。
又因为是沙箱环境,我们不希望用户发现系统中正在运行的其它进程,所以我们可以创建一个 pid namespace
:
unshare -rpf /bin/bash
这里我们多加了 -f
选项,表示使用 fork()
来运行我们的程序(这里的 /bin/bash
),如果不加这个选项会报错:
bash: fork: Cannot allocate memory
这个错误在 pid_namespaces(7)
中有说明。
我们使用 -r
选项把自己映射成了 root,但只在新创建的 namespace 中有效。如果我们在隔离出来的 namespace 中尝试去删掉一个外部 root 权限的文件时仍然会报错,因为这两个不是同一个 namespace 内的 root,就相当于一个 namespace 中的用户不能操作另一个 namespace 中的文件,这样才是隔离的效果。