1.chroot技术
在Linux系统中,系统默认的目录结构都是以/,即根(root)开始的。而在使用chroot之后,进程的系统目录结构将以指定的位置作为根(/)位置。chroot实际作用就是将进程描述符中struct fs_struct中的root的值设置为选定的目录。
在经过chroot之后,进程读取到的目录和文件将不再是系统根目录下的,而是指定的新根目录下的目录和文件。为什么需要chroot呢?因为其带来以下3个好处。
(1)限制了用户进程访问文件系统的范围,增加了系统的安全性。
在经过chroot之后,在新根下将访问不到系统的根目录,这样就增强了系统的安全性。一般是在登录(login)前使用chroot,以此达到用户不能访问一些特定文件的目的。
(2)建立一个与原系统隔离的虚拟系统目录结构,方便用户的开发和测试。
使用chroot后,系统读取的是新根下的目录和文件,它是用原系统根目录下的子目录作为新的根目录。在这个新根目录下,可以用来为被测软件提供一个独立于当前系统根目录结构的独立开发和测试环境。
(3)通过切换系统的根目录位置,协助引导Linux系统的启动过程、切换到特定的应急急救系统等。
chroot的作用就是切换系统的根目录位置,而这个作用最为明显的是在系统初始引导磁盘的处理过程中使用,从初始RAM磁盘(initrd)切换系统的根位置并执行真正的init。另外,当系统出现一些问题时,还可以使用chroot切换到一个临时的应急急救系统。
chroot命令的基本使用方式如下。
sudo chroot newroot [command]
注意:执行chroot命令需要root权限,而且command是新根目录(newroot)下的可执行命令,一般需要在新根目录下放一个根文件系统,简单的做法是可以放一个静态编译的可执行文件。
以下面的实际命令为例,其中hello是一个静态编译的可执行文件,它存储在新根目录(即/home/mengning/container)中的bin目录下。
$ sudo chroot /home/mengning/container /bin/hello
第一个参数是存放容器根文件系统的目录,第二个参数是可执行文件的路径,这个路径是相对于第一个参数而言的,原系统的绝对路径为/home/mengning/container/bin/hello。如果要执行的可执行文件不是静态编译的,此时在指定的根文件系统目录中必须包含标准C 库、动态链接器等相关依赖,自行管理依赖比较烦琐,所以用一个小型的、自包含的Linux 发行版比较好。因为发行版包含实用程序、依赖库和系统配置文件,可以作为一个完整的系统在容器中运行。
2.minirootfs创建
使用chroot创建一个私有的根文件目录。
首先在个人目录下建立container目录,这里使用Alpine Linux作为根文件系统。
$ mkdir container
$ wget https://dl-cdn.alpinelinux.org/alpine/v3.15/releases/x86_64/alpine-
minirootfs-3.15.1-x86_64.tar.gz
到阿里云开源镜像站下载
alpine-v3.15-releases-x86_64安装包下载_开源镜像站-阿里云
$ cd container
$ tar -zxvf ../alpine-minirootfs-3.15.0-x86_64.tar.gz
$ ls
bin dev etc home lib media mnt opt proc root run sbin srv sys
tmp usr var
$ cd ..
$ sudo chown -R root:root container/
$ wget https://dl-cdn.alpinelinux.org/alpine/v3.15/releases/x86_64/alpine-
minirootfs-3.15.1-x86_64.tar.gz
到阿里云开源镜像站下载
alpine-v3.15-releases-x86_64安装包下载_开源镜像站-阿里云
此时在container目录下已经得到一个小型但可用的Linux根文件系统。执行chroot命令如下。
$ sudo chroot /home/mengning/container /bin/sh -l
这条指令可以执行container目录下的sh并以-l(login)的方式得到一个Shell进程。此时在Shell下使用ps命令查看进程列表,结果显示为空,因为当前系统目录下的/dev和/proc都是空目录,没有挂载主机系统的/dev和/proc文件系统。
chroot命令只是修改了进程的根目录进行了文件系统的隔离,pid、hostname、net等并没有进行隔离。
chroot命令实际上只是通过调用chroot系统调用修改当前进程的根目录位置,然后调用execve系统调用加载可执行程序,这样当前运行的程序能够读取的范围就仅限于指定的新根目录范围内。chroot系统调用和execve系统调用的库函数原型如下。
#include <unistd.h>
int chroot(const char *path);
int execve(const char *filename, char *const argv[],char *const envp[]);
Linux-5.4.34内核中的chroot系统调用的内核处理函数见fs/open.c。
SYSCALL_DEFINE1(chroot, const char __user *, filename)
{
return ksys_chroot(filename);
}
其中ksys_chroot()函数如下。
int ksys_chroot(const char __user *filename)
{
struct path path;
int error;
unsigned int lookup_flags = LOOKUP_FOLLOW | LOOKUP_DIRECTORY;
retry:
error = user_path_at(AT_FDCWD, filename, lookup_flags, &path);
if (error)
goto out;
error = inode_permission(path.dentry->d_inode, MAY_EXEC | MAY_CHDIR);
if (error)
goto dput_and_out;
error = -EPERM;
if (!ns_capable(current_user_ns(), CAP_SYS_chroot))
goto dput_and_out;
error = security_path_chroot(&path);
if (error)
goto dput_and_out;
set_fs_root(current->fs, &path);
error = 0;
dput_and_out:
path_put(&path);
if (retry_estale(error, lookup_flags)) {
lookup_flags |= LOOKUP_REVAL;
goto retry;
}
out:
return error;
}
其中为当前进程调用了set_fs_root(),参见fs/fs_struct.c文件。
/*
* Replace the fs->{rootmnt,root} with {mnt,dentry}. Put the old values.
* It can block.
*/
void set_fs_root(struct fs_struct *fs, const struct path *path)
{
struct path old_root;
path_get(path);
spin_lock(&fs->lock);
write_seqcount_begin(&fs->seq);
old_root = fs->root;
fs->root = *path;
write_seqcount_end(&fs->seq);
spin_unlock(&fs->lock);
if (old_root.dentry)
path_put(&old_root);
}
此处改变了当前进程的根目录fs->root。显然chroot技术只能改变进程描述符struct task_struct相关的struct fs_struct中的root,影响的是路径查找(path lookup)的起始点,是一种非常简单的隔离进程对文件系统访问范围的方法,Mount namespace则可以隔离进程的整个mount树,11.2.3节中再详细讨论。
3.测试用例
ubuntu@ubuntu:~/book/paodingjieniu/container/minirootfs$ sudo tar -zxvf ../../download/alpine-minirootfs-3.15.1-x86_64.tar.gz
ubuntu@ubuntu:~/book/paodingjieniu/container/minirootfs$ cd ..
ubuntu@ubuntu:~/book/paodingjieniu/container$ sudo chown -R root:root minirootfs/
ubuntu@ubuntu:~/book/paodingjieniu/container$ sudo chroot /home/ubuntu/book/paodingjieniu/container/minirootfs/ /bin/sh -l
ubuntu:/# uname -a