chroot 小记
Contents
什么是 chroot
chroot 最早是作为系统调用引入 1979 年的 Unix V7 系统,目的是为了将当前进程及其子进程的 root 目录重定向到某个指定目录。1982 年,chroot 功能被加入到 BSD 中,后经 20 多年,FreeBSD 团队引入虚拟化技术的概念,在原本的 chroot 机制上,开发了新的 jail 机制。
简单来说:一个正在运行的进程经过 chroot 操作后,其根目录将被显式映射为某个指定目录,它将不能够对该指定目录之外的文件进行访问动作。这是一种非常简单的资源隔离化操作,类似于现在 Linux 的 Mount Namespace 功能。当年 Docker 刚开源的时候,有个人就利用 Linux 下 chroot 命令,用 100 多行的 Bash 代码实现了一个模拟版的 Docker。
chroot 的使用
在现今的 Linux 上,chroot 既是一个 CLI 工具(chroot(8)
),又是一个系统调用(chroot(2)
)。
使用 chroot(8)
命令
我们可以利用 Linux 下的 chroot(8)
命令来创建出一种类似于进入某个隔离容器内部的效果。
chroot(8)
的用法很简单,格式如下所示:
|
|
COMMAND 指的是切换 root 目录后需要执行的命令,如果没有指定,默认是 ${SHELL} -i
,大部分情况是 /bin/bash
。执行 chroot(8)
需要使用 root 权限。
简单地,我们可以这样使用:
|
|
下面就让我们来建造我们的监狱(jail)(备注:基于 Ubuntu 16.04)。
-
创建对应的新的根目录
1 2 3
$ export J=$HOME/jail $ mkdir -p $J $ mkdir -p $J/{bin,lib/x86_64-linux-gnu,lib64,etc,var}
-
将几个必要的命令工具 copy 到
bin/
下1
$ sudo cp -vf /bin/{bash,ls} $J/bin
-
将步骤 2 中可执行命令中的依赖动态库 copy 到
jail/
下1 2 3 4 5 6
# 也许可以整合成一个单独的 shell 命令 $ list=`ldd /bin/ls | egrep -o '/lib.*\.[0-9]'` $ for i in $list; do sudo cp -vf $i $J/$i; done $ list=`ldd /bin/bash | egrep -o '/lib.*\.[0-9]'` $ for i in $list; do sudo cp $i -vf $J/$i; done
-
执行 chroot 命令
1
$ sudo chroot $J /bin/bash
此时可见进入了一个 bash shell 的界面:
1 2 3 4 5 6 7 8
bash-4.3# ls bin etc lib lib64 var bash-4.3# cd / bash-4.3# ls bin etc lib lib64 var bash-4.3# cd .. bash-4.3# ls bin etc lib lib64 var
无论我们如何改变目录,其根目录都被隔离在
$J
中,执行exit
命令可退出这一环境;
使用 chroot(2)
系统调用
chroot(2)
的原型是:
|
|
chroot()
将调用进程及其子进程的根目录指定为 path。执行该调用需要使用 root 权限。
如以下代码所示:
|
|
编译和运行代码:
|
|
如何获知某个进程是否处于 chroot 监禁
可通过查看进程的 /proc/<pid>/root
来查看对应进程是否处于 chroot 监禁中,如上文,其 chroot 下 bash 的执行进程为 15768,则有:
|
|
可见其根目录已经被修改为 /home/test/jail
。
通过读取 Linux 专有的 /proc/<pid>/root
符号链接的内容,我们可以获取任何进程的根目录。
chroot 的应用
ftp 程序就是应用 chroot(2)
的典型实例之一。
当用户匿名登陆 ftp 时,ftp 程序将使用 chroot()
为新进程设置根目录:一个专门预留给匿名登陆用户的目录。这样,chroot()
作为一种安全措施,将用户受困于文件系统中新根目录下的子树中,从而无法访问文件系统的其他路径。
chroot 的安全问题
chroot 机制从一开始就并非安全,存在很多安全漏洞,因此产生了不少「越狱」(jailbreak)手段(这部分内容可参考书籍 《Linux/UNIX 系统编程手册》 上册中第 18 章的相关内容)。比如:
-
特权级程序可以在随后对
chroot()
的进一步调用中利用种种手段越狱成功- 如果特权级(
CAP_MKNOD
)程序能够利用mknod()
来创建一个内存设备文件(类似于/dev/mem
),并通过该设备访问 RAM 的内存,那就可借此越狱成功;
- 如果特权级(
-
即便是无特权程序,也可以有以下几种越狱手段:
-
调用
chroot()
并未改变进程的当前工作目录。因此,通过应在调用chroot()
之前或者之后再调用一次chdir()
函数(例如chroot()
调用之后再执行chdir("/")
)。如果不这么做,那么进程就能够使用相对路径取访问监狱之外的文件和目录; -
如果进程一直持有监禁区之外的某一目录的打开文件描述符,那么通过有限次
chdir()
就可以越狱成功,如下代码片段所示:1 2 3 4 5 6 7 8 9 10 11 12 13 14
int main(void) { int x; int fd_before_chroot; mkdir("jail", 0755); fd_before_chroot = open(".", O_RDONLY); chroot("jail"); fchdir("fd_before_chroot"); // 利用 fchdir() 调用将工作目录切换 // 到对应的 fd 且该 fd 于监禁前获得 for (x = 0; x < 1024; x++) { // 经过有限次的 chdir() 调用 chdir(".."); // 理论上可到达监禁区外的根目录 } chroot("."); // 将工作目录切换为监禁区外的根目录 system("/bin/bash"); // 执行监禁区外的根目录下的 shell }
-
利用 UNIX 域套接字来接受另外一个进程指向监禁区外目录的文件描述符
-
综上,要确保使用 chroot()
相对安全,应做到:
- 限制监禁区内程序的权限;
- 执行
chroot()
调用后再调用chdir()
切换工作目录; - 禁止监禁区中的程序持有监禁区外的文件描述符;