背景

什么是chroot

chroot 是一个 UNIX 操作系统上的系统调用,用于将一个进程及其子进程的根目录改变到文件系统中的一个新位置,让这些进程只能访问到该目录。这个功能的想法是为每个进程提供独立的磁盘空间

chroot的优势

在经过 chroot 之后,系统读取到的目录和文件将不在是旧系统根下的而是新根下(即被指定的新的位置)的目录结构和文件,因此它带来的好处大致有以下3个:

  • 增加了系统的安全性,限制了用户的权力; 在经过 chroot 之后,在新根下将访问不到旧系统的根目录结构和文件,这样就增强了系统的安全性。这个一般是在登录 (login) 前使用 chroot,以此达到用户不能访问一些特定的文件。
  • 建立一个与原系统隔离的系统目录结构,方便用户的开发; 使用 chroot 后,系统读取的是新根下的目录和文件,这是一个与原系统根下文件不相关的目录结构。在这个新的环境中,可以用来测试软件的静态编译以及一些与系统不相关的独立开发。
  • 切换系统的根目录位置,引导 Linux 系统启动以及急救系统等。 chroot 的作用就是切换系统的根位置,而这个作用最为明显的是在系统初始引导磁盘的处理过程中使用,从初始 RAM 磁盘 (initrd) 切换系统的根位置并执行真正的 init。另外,当系统出现一些问题时,我们也可以使用 chroot 来切换到一个临时的系统。

如何使用 chroot

准备 Linux 镜像

mkdir -p busybox && (sudo docker export $(sudo docker create busybox) | tar -C busybox -xvf -)

mkdir -p stretch && (sudo docker export $(sudo docker create debian:stretch-slim) | tar -C stretch -xvf -)

Busybox 被称为是嵌入式 Linux 中的瑞士军刀。Busybox 包含了许多有用的命令,如 catfind 等,但是它的体积却非常的小,bin 目录下所有命令都是静态编译,不依赖动态共享库文件。

debian:stretch-slim 即为 Debian 9 的稳定发行版,slim 表示精简版。

执行 chroot

chrootbusybox 目录,并执行命令 /bin/busybox --list

sudo chroot busybox /bin/busybox --list
[
[[
acpid
add-shell
addgroup
adduser
adjtimex
ar
...

chrootbusybox 目录,并执行命令 /bin/sh

sudo chroot busybox /bin/sh

sh 中执行 busybox 中的命令

ls
# bin dev etc home proc root sys tmp usr var
whoami
# root

mount -t proc proc /proc
ps aux
# PID   USER     TIME  COMMAND
#    1 root      0:50 /usr/lib/systemd/systemd --system --deserialize 20
#    2 root      0:00 [kthreadd]
# ...

ifconfig
# ifconfig: /proc/net/dev: No such file or directory
# docker0   Link encap:Ethernet  HWaddr 02:42:88:28:3C:C5
#          inet addr:172.17.0.1  Bcast:172.17.255.255  Mask:255.255.0.0
#          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
#
# eth0      Link encap:Ethernet  HWaddr 00:16:3E:0C:86:F9
#          inet addr:172.18.113.32  Bcast:172.18.127.255  Mask:255.255.240.0
#          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
#
...

对比宿主机 IP 配置会发现网络是没有隔离的。

chrootstretch 目录,默认执行 /bin/bash

sudo chroot stretch

bash 中执行 stretch 中的命令

cat /etc/issue
# Debian GNU/Linux 9 \n \l
whoami
# root

chroot 如何执行可执行文件

前面演示了 chroot 切换到 busyboxstretch 目录执行命令,给人的感觉好像在使用一个全新的系统。事实上,执行命令的还是宿主操作系统,它只不过把根目录限定在了指定目录,执行文件的相关依赖也必须在此根目录下。

chroot 执行命令,并不需要目标目录有一个完整的 linux 内核,只要是和宿主操作系统同体系符合 ELF 格式的可执行文件,就可以直接执行。

示例1

rm -fr rootfs && mkdir rootfs
cp ~/busybox/bin/ls ~/rootfs/
sudo chroot rootfs ./ls
# ls

示例2

rm -fr rootfs && mkdir rootfs
cp ~/busybox/bin/{sh,ls,echo,cat,mkdir} ~/rootfs/
sudo chroot rootfs ./sh
./mkdir tmp
./echo 'Hello World' > /tmp/hello.txt
./cat /tmp/hello.txt
# Hello World
./ls /
# cat    echo   ls     mkdir  sh     tmp
./ls /tmp
hello.txt

示例3

cp ~/stretch/bin/bash ~/rootfs/
sudo chroot rootfs ./bash
# chroot: failed to run command ‘./bash’: No such file or directory

这里报错,是因为,在 stretchbash 不是静态编译的,它依赖于其它的动态库,只有把依赖文件也准备好,bash 命令才可以正确执行。

# 查看 bash 依赖的动态库
ldd ~/stretch/bin/bash

mkdir ~/rootfs/lib ~/rootfs/lib64
cp ~/stretch/lib/x86_64-linux-gnu/ld-2.24.so ~/rootfs/lib64/ld-linux-x86-64.so.2
cp ~/stretch/lib/x86_64-linux-gnu/libtinfo.so.5.9 ~/rootfs/lib/libtinfo.so.5
cp ~/stretch/lib/x86_64-linux-gnu/libdl-2.24.so ~/rootfs/lib/libdl.so.2
cp ~/stretch/lib/x86_64-linux-gnu/libc.so.6 ~/rootfs/lib/libc.so.6

tree ~/rootfs
# /home/james/rootfs
# ├── bash
# ├── lib
# │   ├── libc.so.6
# │   ├── libdl.so.2
# │   └── libtinfo.so.5
# └── lib64
#     └── ld-linux-x86-64.so.2
#     
sudo chroot rootfs ./bash

chroot 系统调用

上面介绍了 chroot 命令行使用,但是更常见的方式是其它程序语言通过系统调用的方式来使用。chroot 的编写涉及了2个函数,chroot() 以及 chdir(),它们都包含在 unistd.h 头文件中。

示例1

本例演示了 chroot~/busybox 目录中执行命令。

vim chroot_demo.py
chmod +x chroot_demo.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
import sys

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("Usage: chroot [COMMAND...] \n")
    else:
        os.chroot("/home/james/busybox")
        os.chdir("/")

        os.system("umount ./proc")
        os.system("mount -t proc ./proc /proc")

        os.execvp(sys.argv[1], sys.argv[1:])
sudo ./chroot_demo.py ls
# bin dev etc home proc root sys tmp usr var
sudo ./chroot_demo.py whoami
# root

sudo ./chroot_demo.py ps
# PID   USER     TIME  COMMAND
#    1 root      0:49 /usr/lib/systemd/systemd --system --deserialize 20
#    2 root      0:00 [kthreadd]
#    3 root      0:02 [ksoftirqd/0]
# ...

sudo ./chroot_demo.py ifconfig
# docker0   Link encap:Ethernet  HWaddr 02:42:88:28:3C:C5
#           inet addr:172.17.0.1  Bcast:172.17.255.255  Mask:255.255.0.0
#           UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
#           RX packets:3702045 errors:0 dropped:0 overruns:0 frame:0
#           TX packets:4221866 errors:0 dropped:0 overruns:0 carrier:0
#           collisions:0 txqueuelen:0
#           RX bytes:150990614 (143.9 MiB)  TX bytes:36552003396 (34.0 GiB)
# 
# eth0      Link encap:Ethernet  HWaddr 00:16:3E:0C:86:F9
#           inet addr:172.18.113.32  Bcast:172.18.127.255  Mask:255.255.240.0
#           UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
#           RX packets:105629916 errors:0 dropped:0 overruns:0 frame:0
#           TX packets:43745130 errors:0 dropped:0 overruns:0 carrier:0
#           collisions:0 txqueuelen:1000
#           RX bytes:102811026834 (95.7 GiB)  TX bytes:70775210676 (65.9 GiB)
# 
...

示例2

本例展示如何用系统调用的方式来实现一个类 chroot 命令。

vim chroot_rootfs.py
chmod +x chroot_rootfs.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
import sys

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("Usage: chroot [COMMAND...] \n")
    else:
        root = sys.argv[1]
        print("root = {}".format(root))

        try:
            os.chroot(root)
            os.chdir("/")

            if len(sys.argv) == 2:
                argv = []
                shell = os.getenv("SHELL")
                if not shell:
                    shell = "/bin/bash"
                argv.append(shell)
                argv.append("-i")
            else:
                argv = sys.argv[2:]

            os.execvp(argv[0], argv)
        except OSError as e:
            print("OSError: {0} with command {1}".format(e, sys.argv))
./chroot_rootfs.py
Usage: chroot NEWROOT [COMMAND...]

sudo ./chroot_rootfs.py ~/busybox /bin/ls
# bin dev etc home proc root sys tmp usr var