容器化技术在现代操作系统中扮演着越来越重要的角色,而chroot是容器化思想的早期形式之一。在早期,chroot被用来创建一个与宿主机隔离的环境,常用于测试目的。chroot的名称来源于change和root(文件系统的根),意味着改变当前进程的根目录。
Linux文件系统树通常看起来像下面这样(也参见文件系统层次结构标准):
$ tree -d -L 1 /
/
├── bin -> usr/bin
├── boot
├── data
├── dev
├── etc
├── home
├── lib -> usr/lib
├── lib64 -> usr/lib
├── lost+found
├── mnt
├── opt
├── proc
├── root
├── run
├── sbin -> usr/bin
├── srv
├── sys
├── tmp
├── usr
└── var
chroot()允许创建一个嵌套的文件系统树,可以通过以下示例进行演示:
接下来,将通过一些C代码示例,更详细地了解chroot()以及chroot工具及其在操作系统中的使用。
chroot() - Linux系统调用
chroot旨在通过改变其根目录来限制对文件系统的访问。也就是说,进程将只能看到通过chroot()参数限制的顶级目录结构。
让创建一些目录用于示例:
$ mkdir -p /tmp/chroot/{1,2,3,4}
然后编写以下C代码:
#include
#include
#include
int main(void) {
// check path before chroot()
char t_cwd[PATH_MAX];
getcwd(t_cwd, sizeof(t_cwd));
printf("Current dir before chroot(): %s\n", t_cwd);
// do chroot()
chdir("/tmp/chroot/");
if (chroot("/tmp/chroot/") != 0) {
perror("chroot /tmp/chroot/");
return 1;
}
// check path after chroot()
char a_cwd[PATH_MAX];
getcwd(a_cwd, sizeof(a_cwd));
printf("Current dir after chroot(): %s\n", a_cwd);
// point dr struct to the "root"
struct dirent *de;
DIR *dr = opendir("/");
// run readdir() and list "root"'s content
while ((de = readdir(dr)) != NULL)
printf("%s\n", de->d_name);
// try to open /etc/passwd from a "host" filesystem
FILE *f;
f = fopen("/etc/passwd", "r");
if (f == NULL) {
perror("/etc/passwd");
return 1;
} else {
char buf[100];
while (fgets(buf, sizeof(buf), f)) {
printf("%s", buf);
}
}
return 0;
}
这里将执行以下操作:
构建它:
$ gcc chroot_example.c -o chroot_example
并运行以检查(需要使用sudo,因为只有root才能使用chroot()):
$ sudo ./chroot_example
Current dir before chroot(): /home/setevoy/Scripts/C
Current dir after chroot(): / .
..
4
3
2
1
/etc/passwd: No such file or directory
chroot()本身在内核的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():
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);
}
可以在这里找到良好的系统调用描述:
chroot -Linux实用程序
要在Linux中创建一个隔离的空间,可以使用chroot实用程序:
$ which chroot
/usr/bin/chroot
$ file /usr/bin/chroot
/usr/bin/chroot: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=f3861107940247a67dbbf6343fa5ff1c1c70305c, stripped
让为“监狱”(FreeBSD的jail是UNIX的chroot的高级后继者)创建一个带有隔离文件系统的目录:
$ cd /tmp/ $ mkdir changed_root
实际上,chroot实用程序将调用相同的chroot()系统调用 - 让用strace检查它:
$ sudo strace -e trace=chroot chroot changed_root/
chroot("changed_root/") = 0
chroot: failed to run command ‘/bin/bash’: No such file or directory
+++ exited with 127 +++
‘/bin/bash’: No such file or directory错误是由于在这个新环境中没有/bin目录和bash可执行文件。
如果尝试调用任何其他程序,也会返回类似的错误:
[setevoy@setevoy-arch-work /tmp] $ which ls
/usr/bin/ls
[setevoy@setevoy-arch-work /tmp] $ sudo chroot changed_root /usr/bin/ls
chroot: failed to run command ‘/usr/bin/ls’: No such file or directory
让修复它 - 在/tmp/changed_root中创建/bin目录,并将“主机”中的bash文件复制到这个“容器”中:
[setevoy@setevoy-arch-work /tmp] $ mkdir changed_root/bin
[setevoy@setevoy-arch-work /tmp] $ cp /bin/bash changed_root/bin
[setevoy@setevoy-arch-work /tmp] $ file changed_root/bin/bash
changed_root/bin/bash: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=357034d1736cd97d2c8f8347045250dbd0de998e, stripped
再次尝试:
[setevoy@setevoy-arch-work /tmp] $ sudo chroot changed_root /bin/bash
chroot: failed to run command ‘/bin/bash’: No such file or directory
好的。但现在它是因为缺少必要的库 - chroot无法告知这一点。
使用ldd检查bash的依赖关系:
[setevoy@setevoy-arch-work /tmp] $ ldd /bin/bash
linux-vdso.so.1 (0x00007ffe37f16000)
libreadline.so.8 => /usr/lib/libreadline.so.8 (0x00007f39b13d2000)
libdl.so.2 => /usr/lib/libdl.so.2 (0x00007f39b13cd000)
libc.so.6 => /usr/lib/libc.so.6 (0x00007f39b1209000)
libncursesw.so.6 => /usr/lib/libncursesw.so.6 (0x00007f39b119a000)
/lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007f39b153f000)
在新工作目录中创建两个更多的目录 - /lib和/lib64:
[setevoy@setevoy-arch-work /tmp] $ mkdir changed_root/usr/lib changed_root/lib64
并复制库文件:
[setevoy@setevoy-arch-work /tmp] $ cp /usr/lib/libreadline.so.8 changed_root/usr/lib/
[setevoy@setevoy-arch-work /tmp] $ cp /usr/lib/libdl.so.2 changed_root/usr/lib/
[setevoy@setevoy-arch-work /tmp] $ cp /usr/lib/libc.so.6 changed_root/usr/lib/
[setevoy@setevoy-arch-work /tmp] $ cp /usr/lib/libncursesw.so.6 changed_root/usr/lib/
[setevoy@setevoy-arch-work /tmp] $ cp /lib64/ld-linux-x86-64.so.2 changed_root/lib64
再次运行chroot:
[setevoy@setevoy-arch-work /tmp] $ sudo chroot changed_root/
bash-5.0#
现在在这里运行bash,以及它的所有内置功能:
bash-5.0# pwd/
但显然 - 没有其他外部实用程序将在这里工作:
bash-5.0# ls -l
bash: ls: command not found
这可以像对bash所做的那样修复:
[setevoy@setevoy-arch-work /tmp] $ which ls
/usr/bin/ls
[setevoy@setevoy-arch-work /tmp] $ cp /usr/bin/ls changed_root/bin/
[setevoy@setevoy-arch-work /tmp] $ ldd /usr/bin/ls
linux-vdso.so.1 (0x00007ffdebbf5000)
libcap.so.2 => /usr/lib/libcap.so.2 (0x00007fa5b147d000)
libc.so.6 => /usr/lib/libc.so.6 (0x00007fa5b12b9000)
/lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007fa5b14d8000)
[setevoy@setevoy-arch-work /tmp] $ cp /usr/lib/libcap.so.2 changed_root/usr/lib/
bash-5.0# /bin/ls -l /
total 0
drwxr-xr-x 2 1000 1000 80 Mar 22 11:45 bin
drwxr-xr-x 2 1000 1000 120 Mar 22 11:37 lib
drwxr-xr-x 2 1000 1000 60 Mar 22 11:38 lib64
drwxr-xr-x 3 1000 1000 60 Mar 22 11:39 usr