深入理解Linux中的chroot机制

容器化技术在现代操作系统中扮演着越来越重要的角色,而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; }

这里将执行以下操作:

  • 在调用chroot()之前检查当前路径
  • 调用chroot()
  • 再次检查当前路径
  • 获取“根”目录的内容
  • 尝试打开“真实”文件系统中的/etc/passwd文件

构建它:

$ 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
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485