使用 unshare 命令创建临时开发环境

最近有一个需求,就是要在服务器端执行用户传过来的命令。为了防止用户运行一些恶意指令,我们需要构造一个沙箱来运行。这种隔离的需求第一反应就想到了之前写过的 使用 overlayfs 和 chroot 搭建临时环境,但是这需要 root 权限。有没有办法在不需要 root 的情况运行 mountchroot 呢?简单搜了下就找到了想要的答案。

unshare 是 linux 下的一个命令,通过构造一个新的 namespace 来和现有的环境隔离。基本用法很简单,就是一些选项,然后再加上要运行的命令即可,看起来有点像一个精简版的 docker。而且和 docker 一样的是,只要运行的命令退出了,在命令运行期间做的操作都会被自动清除。这个完美符合我们对沙箱的要求。

先来做个测试。通过 unshare 把当前用户映射成 root,并创建一个新的 mount namespace

unshare -rm /bin/bash

运行之后发现我们的用户变成了 root!另外一个不容易发现的内容就是,在我们所创建的这个 mount namespace 中进行 mountumount 操作,不会影响外面的真实的文件系统。也就是说如果我们在这个环境中 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 中的文件,这样才是隔离的效果。

在执行最终的脚本之前,需要用 root 手动挂载 /dev/sys

mount --bind /dev chroot-env/dev
mount -t sysfs sysfs chroot-env/sys

最终使用下面的脚本就可以实现我们的 chroot 需求:

#!/bin/bash

tmpfile=$(mktemp)

cat > $tmpfile <<EOF
export PATH=/usr/sbin:$PATH
mount --bind /home/ouonline/workspace chroot-env/home/ouonline/workspace
chroot chroot-env /bin/bash
EOF

unshare --mount --uts --fork --pid -map-root-user /bin/bash $tmpfile

rm $tmpfile

还有就是进入 chroot 环境之后还需要手动挂载一下 /dev/pts/proc

mount -t proc proc /proc
mount -t devpts devpts /dev/pts

unshare 的 --mount-proc 好像不起作用。

chroot-env 目录可以是我们在用的系统的一个备份(参考 使用 tar 备份 linux),或者是用 debootstrap 获取的一个镜像:

debootstrap --arch amd64 sid chroot-env

为了保持开发环境目录的一致性,可以在进入临时环境之后手动执行一次

usermod -d /home/ouonline -u 0 root

把临时环境中的 root 的家目录改成真实用户的家目录。

FAQ

Q:如果执行 apt-get update 时遇到报错 setgroups 65534 failed - setgroups (1: Operation not permitted)
A:(参考资料 2)进入容器后执行

# apt-config dump | grep Sandbox::User
APT::Sandbox::User "_apt";
# cat <<EOF > /etc/apt/apt.conf.d/sandbox-disable
APT::Sandbox::User "root";
EOF
# apt-config dump | grep Sandbox::User
APT::Sandbox::User "root";

参考资料

[1] Overlay Mounting as non-root
[2] Linux容器——那些你不知道的事

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注