分类

chroot 时做了什么

2019-12-31 23:18 linux

debian 社区,大都使用 chroot 环境做一些干净的编译打包操作,之前手动安装 debian 和 archlinux 系统时,也有在使用 chroot。但并不了解 chroot 时,系统究竟发生了什么。 在 2019 年最后一天,找时间看了下内核代码,才了解到里面的操作。

先说结论:chroot 改变当前进程所在的内核空间的一个叫做 current->root 的全局变量, 之后该进程所有的文件系统操作的路径,都以新的 path 作为根目录。

首先看 fs/open.c 中实现的 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;
}

它最终调用了 fs/fs_struct.c 中的 set_fs_root() 函数:

/*
 * 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);
}

而这里用到的 current 全局变量, 在 x86 架构中的定义位于 arch/x86/include/asm/current.h

struct task_struct;

DECLARE_PER_CPU(struct task_struct *, current_task);

static __always_inline struct task_struct *get_current(void)
{
	return this_cpu_read_stable(current_task);
}

#define current get_current()

总之,除了文件系统之外,chroot 并没有提供额外的资源隔离。 另外,默认情况下,在 chroot 环境里,进程也无法访问文件系统及进程信息,因为它们 分别位于 /dev/proc,只有手动挂载它们到 chroot 所在的目录才行。