Linux-chroot命令
chroot 可以用来切换当前进程的根目录,它能够将当前进程能够访问的目录树结构限制在某个子目录中,同时由于当前进程创建的子进程将会继承父进程的根目录结构,所以子进程也随之被限定。
为什么要使用 chroot 命令
增加了系统的安全性,限制了用户的权力:
在经过 chroot 之后,在新根下将访问不到旧系统的根目录结构和文件,这样就增强了系统的安全性。一般会在用户登录前应用 chroot,把用户的访问能力控制在一定的范围之内。
建立一个与原系统隔离的系统目录结构,方便用户的开发:
使用 chroot 后,系统读取的是新根下的目录和文件,这是一个与原系统根下文件不相关的目录结构。在这个新的环境中,可以用来测试软件的静态编译以及一些与系统不相关的独立开发。
切换系统的根目录位置,引导 Linux 系统启动以及急救系统等:
chroot 的作用就是切换系统的根位置,而这个作用最为明显的是在系统初始引导磁盘的处理过程中使用,从初始 RAM 磁盘 (initrd) 切换系统的根位置并执行真正的 init,本文的最后一个 demo 会详细的介绍这种用法。
通过 chroot 与busybox 构造Mini系统
busybox 包含了丰富的工具,把这些工具放置在一个目录下,然后通过 chroot 构造出一个 mini 系统(只隔离了文件系统)。简单起见直接使用 docker 的 busybox 镜像打包的文件系统。先在当前目录下创建一个目录 rootfs:
1 | mkdir rootfs |
然后把 busybox 镜像中的文件释放到这个目录中:
1 | (docker export $(docker create busybox) | tar -C rootfs -xvf -) |
执行 chroot 后的 ls 命令
1 | sudo chroot rootfs /bin/ls |
这次运行的命令却是 rootfs/bin/ls。执行过程为首先把进程的根目录设置成/tmp/tmp/rootfs
,然后根目录/
就是/tmp/tmp/rootfs
,所以调用的ls命令其实为 /tmp/tmp/rootfs/bin/ls
运行 chroot 后的 pwd 命令
1 | sudo chroot rootfs /bin/pwd |
不带命令执行 chroot
如果不给 chroot 指定执行的命令,默认它会执行 ${SHELL} -i
,本系统中${SHELL} 为 /bin/zsh
,busybox中不包含zsh。
1 | sudo chroot rootfs |
通过/bin/sh
执行shell并打印当前进程的pid
1 | sudo chroot rootfs /bin/sh |
检查程序是否运行在 chroot 环境下
在另一个shell中查看主机的/proc/$pid/root
文件就可以看到指定进程的root目录被映射到什么位置
chroot 命令的原理
通过 strace 来跟踪一次 chroot 命令执行过程来研究其代码执行过程,重要的系统调用信息如下:
1 | strace chroot rootfs/ sh |
上述过程可以总结为如下几个步骤:
execve 运行 chroot 程序
chroot 系统调用切换当前命令的根目录
chdir 系统调用切换当前命令的工作目录到新的根目录
根据$PATH搜索默认 shell 的位置,使用 execve 进行执行
这里它搜索默认 shell 的顺序是根据主机中的$PATH
环境变量顺序来的
1 | echo $PATH |
从上面的捕获到的 chroot 命令的系统调用可以看到 chroot 命令的核心其实就是调用
chroot系统调用,这也就是其内核态的主要行为,这个行为并不是直接完成这项功能
的,它实际是通过一种间接的方式修改 task_struct 中的数据结构来达成的。
源码
chroot源码在 coreutils 包中,下载地址:https://ftp.gnu.org/gnu/coreutils/
实现chroot程序
代码中涉及到两个函数,分别是 chroot() 函数和 chdir() 函数,真正的 chroot 命令也是通过调用它们实现的
在busybox目录下编译执行下面代码
1 |
|
编译运行
1 | gcc -Wall chroot.c -o mychroot |
实例:通过 chroot 重新设置 root 密码
接下来的 demo 将演示如何通过 chroot 命令重新设置 centos7 中被忘记了的 root 密码。
systemd 的管理机制中,rescure 模式和 emeryency 模式是无法直接取得 root 权限的,需要使用 root 密码才能进入 rescure 和 emeryency 环境。所以我们需要通过其他方式来设置 root 密码。我们可以为内核的启动指定 “rd.break” 参数,从而让系统在启动的早期停下来,此时我们可以通过使用 root 权限并结合 chroot 命令完成设置 root 密码的操作。下面我们一起来看具体的操作过程。
在系统启动过程中进入开机菜单时按下字母键 e 进程开机菜单的编辑模式:
这就是系统的开机菜单,按下 e 后进入编辑界面:
**找到以 “linux16 /vmlinuz-” 开头的行。**如果默认没有看到该行,需要按向下键把它滚动出来。
然后定位到该行结尾处,输入一个空格和字符串 “rd.break”,如下图所示:
接着按下 ctrl + x 以该设置继续启动,启动过程中操作系统会停下来,这是系统启动过程中的一个非常早的时间点:
所以系统的根目录还挂载在 RAM disk 上(就是内存中的一个文件系统),我们可以通过 mount 命令检查系统当前挂载的文件系统,下面是我们比较关心的两条:
上图中 mount 命令输出的第一行说明此时的根目录在一个 RAM disk 中, 即 rootfs。
图中输出的第二行说明我们的文件系统此时被挂载到了 /sysroot 目录,并且是只读的模式:
1 | /dev/mapper/centos-root on /sysroot type xfs (ro,relatime,attr2,inode64,noquota) |
而在我们正常登陆系统的情况下,系统根目录的挂载情况如下:
1 | /dev/mapper/centos-root on / type xfs (rw,relatime,seclabel,attr2,inode64,noquota) |
该时间点的最大优势是我们具有 root 权限!所以让我们开始设置新的 root 密码吧。
先通过下面的命令把 /sysroot 重新挂载为可读写的模式:
1 | switch_root:/# mount -o remount,rw /sysroot |
然后用下面 chroot 命令把根目录切换到我们原来的环境中:
1 | switch_root:/# chroot /sysroot |
此时可以理解为:我们以 root 权限登录了原来的系统,修改密码就很容易了!用下面的命令为 root 用户设置新的密码:
1 | sh-4.2# echo "new_root_pw" | passwd --stdin root |
**接下来还要处理 SELinux 相关的问题。**由于当前的环境中 SELinux 并未启动,所以我们对文件的修改可能造成文件的 context 不正确。为了确保开机时重新设定 SELinux context,必須在根目录下添加隐藏文件 .autorelabel:
1 | sh-4.2# touch /.autorelabel |
最后从 chroot 中退出,并重启系统:
1 | sh-4.2# exit |
重新进入登陆界面时就可以使用刚才设置的密码以 root 登陆了!