chroot命令用来在指定的根目录下运行指令。chroot,即 change root directory (更改 root 目录)。在 linux 系统中,系统默认的目录结构都是以/,即是以根 (root) 开始的。而在使用 chroot 之后,系统的目录结构将以指定的位置作为/位置。
在经过 chroot 之后,系统读取到的目录和文件将不在是旧系统根下的而是新根下(即被指定的新的位置)的目录结构和文件,因此它带来的好处大致有以下3个:
增加了系统的安全性,限制了用户的权力;
在经过 chroot 之后,在新根下将访问不到旧系统的根目录结构和文件,这样就增强了系统的安全性。这个一般是在登录 (login) 前使用 chroot,以此达到用户不能访问一些特定的文件。
建立一个与原系统隔离的系统目录结构,方便用户的开发;
使用 chroot 后,系统读取的是新根下的目录和文件,这是一个与原系统根下文件不相关的目录结构。在这个新的环境中,可以用来测试软件的静态编译以及一些与系统不相关的独立开发。
切换系统的根目录位置,引导 Linux 系统启动以及急救系统等。
chroot 的作用就是切换系统的根位置,而这个作用最为明显的是在系统初始引导磁盘的处理过程中使用,从初始 RAM 磁盘 (initrd) 切换系统的根位置并执行真正的 init。另外,当系统出现一些问题时,我们也可以使用 chroot 来切换到一个临时的系统。
创建一个目录作为根目录,chroot MyRootDir默认会执行${SHELL} -i,即MyRootDir/bin/bash,所以需要拷贝bash到指定目录
直接执行chroot MyRootDir出现错误:chroot: failed to run command ‘/bin/bash’: No such file or directory,原因是缺少bash相关的库,使用ldd查看库并拷贝(此例子中为简化演示,直接拷贝/lib目录)
这样就执行新根目录内的命令
chroot的第三个参数为运行的指令,该指令位于新的root路径,从下面可以验证改命令执行的不是系统路径的命令
chroot MyRootDir /bin/bash
从chroot的用法可以看出它的使用场景有一定的局限性,首先它依赖于一台现成的unix系统(根目录必须存在于某台现有系统上),其次chroot仅仅是在系统目录上进行了隔离,并没有对进程、网络等层面进行隔离。
新开一个终端界面,查看ping进程可以看到在原系统下面是可以看到新根目录下执行的进程。chroot并没有在进程层面上进行隔离
同样地在新根目录下执行ifconfig等网络操作可以看到网络信息跟原系统是完全一样的。chroot并没有在网络层面上进行隔离
chroot并不能完全保证系统安全,在很多层面上chroot并没有进行完全隔离
编写一个 chroot
上面介绍了 chroot 及其使用,但是编写一个简单的 chroot 并不复杂,下面我们就尝试编写chroot 以此来更好的认识 chroot 的处理过程,先编写一个粗略的 chroot 然后再完善它的功能。chroot 的编写涉及了2个函数,chroot() 以及 chdir(),它们都包含在 unistd.h 头文件中。
编写 chroot 涉及的2个函数
#include
int chroot(const char *path);
int chdir(const char *path);
chroot() 将切换参数 path 所指位置为根目录 (/),chdir() 用来将当前的工作目录改变成以参数path 所指的目录。以此我们可以编写一个非常粗略的 chroot
。
#include
int main(int argc, char *argv[])
{
chroot(“.”);
chdir(“/”);
char *arrays[]={"ash",NULL};
execvp("ash", arrays);
return 0; }
一、创建沙盒的运行环境
(1)创建沙箱根目录,即受限用户登陆时的根目录,可随意指定
mkdir /home/chroot
1
(2)拷贝及创建受限用户运行所需的目录及文件。proc目录为非必需。
cp /bin /home/chroot/ -rf
cp /lib /home/chroot/ -rf
cp /lib64 /home/chroot/ -rf
cp /usr /home/chroot/ -rf
cp /sbin /home/chroot/ -rf
cp /etc /home/chroot/ -rf
mkdir /home/chroot/dev
mkdir /home/chroot/proc
1
2
3
4
5
6
7
8
(3)创建一些特殊文件
mknod -m 666 /home/chroot/dev/null c 1 3
mknod -m 666 /home/chroot/dev/tty c 5 0
mknod -m 666 /home/chroot/dev/zero c 1 5
mknod -m 666 /home/chroot/dev/random c 1 8
1
2
3
4
二、创建受限用户
(1)创建用户
groupadd chrootuser
useradd -g chrootuser chrootuser
passwd chrootuser
1
2
3
(2)更新沙盒中的passwd和group文件
cp /etc/passwd /home/chroot/etc
cp /etc/group /home/chroot/etc
1
2
三、修改ssh配置
(1)修改ssh配置文件
vi /etc/ssh/sshd_config
在最后新增:
Match User chrootuser
ChrootDirectory /home/chroot
(2)重启ssh
service sshd restart
1
注意:
selinux可能会导致无法访问沙盒,如无法访问,可尝试关闭selinux,需要重启操作系统。
关闭selinux方法:“vi /etc/selinux/config”,将“SELINUX”的值置为“disabled”,重启操作系统
chroot是在unix系统的一个操作,针对正在运作的软件进程和它的子进程,改变它外显的根目录。一个运行在这个环境下,经由chroot设置根目录的程序,它不能够对这个指定根目录之外的文件进行访问动作,不能读取,也不能更改它的内容。chroot这一特殊表达可能指chroot(2)系统调用或chroot(8)前端程序。默认情况下我们指的是chroot(8)前端程序。
由chroot创造出的那个根目录,叫做“chroot监狱”(chroot jail,或chroot prison)。
功能
Linux官方MAN手册对chroot命令的定义是: run command or interactive shell with special root directory,即运行命令或者具有指定根目录的交互式shell。基本在所有支持该命令的linux系统中,都需要以超级管理员的权限才可以执行该命令。
发展
chroot从1979年诞生至今,已经发展成为支持docker这样的容器的底层技术之一了。
docker约等于chroot+namespace+other。
chroot,即 change root directory (更改 root 目录)。在 linux 系统中,系统默认的目录结构都是以 /,即以根 (root) 开始的。而在使用 chroot 之后,系统的目录结构将以指定的位置作为 / 位置。
基本语法
?
1
chroot NEWROOT [COMMAND [ARG]…]
具体用法请参考本文的 demo。
为什么要使用 chroot 命令
增加了系统的安全性,限制了用户的权力:
在经过 chroot 之后,在新根下将访问不到旧系统的根目录结构和文件,这样就增强了系统的安全性。一般会在用户登录前应用 chroot,把用户的访问能力控制在一定的范围之内。
建立一个与原系统隔离的系统目录结构,方便用户的开发:
使用 chroot 后,系统读取的是新根下的目录和文件,这是一个与原系统根下文件不相关的目录结构。在这个新的环境中,可以用来测试软件的静态编译以及一些与系统不相关的独立开发。
切换系统的根目录位置,引导 Linux 系统启动以及急救系统等:
chroot 的作用就是切换系统的根位置,而这个作用最为明显的是在系统初始引导磁盘的处理过程中使用,从初始 RAM 磁盘 (initrd) 切换系统的根位置并执行真正的 init,本文的最后一个 demo 会详细的介绍这种用法。
通过 chroot 运行 busybox 工具
busybox 包含了丰富的工具,我们可以把这些工具放置在一个目录下,然后通过 chroot 构造出一个 mini 系统。简单起见我们直接使用 docker 的 busybox 镜像打包的文件系统。先在当前目录下创建一个目录 rootfs:
?
1
$ mkdir rootfs
然后把 busybox 镜像中的文件释放到这个目录中:
?
1
$ (docker export $(docker create busybox) | tar -C rootfs -xvf -)
通过 ls 命令查看 rootfs 文件夹下的内容:
?
1
$ ls rootfs
万事俱备,让我们开始吧!
执行 chroot 后的 ls 命令
?
1
$ sudo chroot rootfs /bin/ls
虽然输出结果与刚才执行的 ls rootfs 命令形同,但是这次运行的命令却是 rootfs/bin/ls。
运行 chroot 后的 pwd 命令
?
1
$ sudo chroot rootfs /bin/pwd
哈,pwd 命令真把 rootfs 目录当根目录了!
不带命令执行 chroot
?
1
$ sudo chroot rootfs
这次出错了,因为找不到 /bin/bash。我们知道 busybox 中是不包含 bash 的,但是 chroot 命令为什么会找 bash 命令呢? 原来,如果不给 chroot 指定执行的命令,默认它会执行 ‘${SHELL} -i’,而我的系统中 ${SHELL} 为 /bin/bash。
既然 busybox 中没有 bash,我们只好指定 /bin/sh 来执行 shell 了。
?
1
$ sudo chroot rootfs /bin/sh
运行 sh 是没有问题的,并且我们打印出了当前进程的 PID。
检查程序是否运行在 chroot 环境下
虽然我们做了好几个实验,但是肯定会有朋友心存疑问,怎么能证明我们运行的命令就是在 chroot 目录后的路径中呢?
其实,我们可以通过 /proc 目录下的文件检查进程的中的根目录,比如我们可以通过下面的代码检查上面运行的 /bin/sh 命令的根目录(请在另外一个 shell 中执行):
?
1
2
$ pid=$(pidof -s sh)
$ sudo ls -ld /proc/$pid/root
输出中的内容明确的指出 PID 为 46644 的进程的根目录被映射到了 /tmp/rootfs 目录。
通过代码理解 chroot 命令
下面我们尝试自己实现一个 chroot 程序,代码中涉及到两个函数,分别是 chroot() 函数和 chdir() 函数,其实真正的 chroot 命令也是通过调用它们实现的:
#include
#include
#include
int main(int argc, char *argv[])
{
if(argc<2){
printf(“Usage: chroot NEWROOT [COMMAND…] \n”);
return 1;
}
if(chroot(argv[1])) {
perror(“chroot”);
return 1;
}
if(chdir(“/”)) {
perror(“chdir”);
return 1;
}
if(argc == 2) {
// hardcode /bin/sh for my busybox tools.
argv[0] = (char *)”/bin/sh”;
argv[1] = (char *) "-i";
argv[2] = NULL; } else {
argv += 2; }
execvp (argv[0], argv);
printf(“chroot: cannot run command %s
\n”, *argv);
return 0;
}
把上面的代码保存到文件 mychroot.c 文件中,并执行下面的命令进行编译:
?
1
$ gcc -Wall mychroot.c -o mychroot
mychroot 的用法和 chroot 基本相同:
?
1
$ sudo ./mychroot ./rootfs
特别之处是我们的 mychroot 在没有传递命令的情况下执行了 /bin/sh,原因当然是为了支持我们的 busybox 工具集,笔者在代码中 hardcode 了默认的 shell:
?
1
argv[0] = (char *)”/bin/sh”;
从代码中我们也可以看到,实现 chroot 命令的核心逻辑其实并不复杂。
实例:通过 chroot 重新设置 root 密码
忘记了 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 目录,并且是只读的模式:
复制代码代码如下:
/dev/mapper/centos-root on /sysroot type xfs (ro,relatime,attr2,inode64,noquota)
而在我们正常登陆系统的情况下,系统根目录的挂载情况如下:
复制代码代码如下:
/dev/mapper/centos-root on / type xfs (rw,relatime,seclabel,attr2,inode64,noquota)
该时间点的最大优势是我们具有 root 权限!所以让我们开始设置新的 root 密码吧。
先通过下面的命令把 /sysroot 重新挂载为可读写的模式:
switch_root:/# mount -o remount,rw /sysroot
然后用下面 chroot 命令把根目录切换到我们原来的环境中:
switch_root:/# chroot /sysroot
此时可以理解为:我们以 root 权限登录了原来的系统,修改密码就很容易了!用下面的命令为 root 用户设置新的密码:
?
1
sh-4.2# echo “new_root_pw” | passwd –stdin root
接下来还要处理 SELinux 相关的问题。由于当前的环境中 SELinux 并未启动,所以我们对文件的修改可能造成文件的 context 不正确。为了确保开机时重新设定 SELinux context,必須在根目录下添加隐藏文件 .autorelabel:
sh-4.2# touch /.autorelabel
最后从 chroot 中退出,并重启系统:
sh-4.2# exit
switch_root:/# reboot
重新进入登陆界面时就可以使用刚才设置的密码以 root 登陆了!
总结
chroot 是一个很有意思的命令,我们可以用它来简单的实现文件系统的隔离。但在一个容器技术繁荣的时代,用 chroot 来进行资源的隔离实在是 low 了点。所以 chroot 的主要用途还是集中在系统救援、维护等一些特殊的场景中。