Head First Chroot

2019-11-05:后来屡次回顾这篇短小精悍的文章,因为它为我提供了一种深入探究Linux系统机制的思路。


Head First系列计划用于记录一些工具的简单使用方法以及有意思的概念。尽量做到由表及里,层层深入。

后面如果未加说明,参考的 Linux 内核源码版本均为 4.10.10。

1. 介绍

man chroot

2. 实验

以下实验以root权限在/root目录下进行。建立如下目录结构:

sandbox
|- hello.c
|- lib/

hello.c即最简单的打印Hello, world程序。

gcc -o hello hello.c
ldd hello

得到动态链接信息:

linux-gate.so.1 =>  (0xb7778000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb75be000)
/lib/ld-linux.so.2 (0xb7779000)

为了保证chroot后程序能正确找到动态链接器和动态链接库:

cp /lib/i386-linux-gnu/libc.so.6 ./sandbox/lib
cp /lib/ld-linux.so.2 ./sandbox/lib

测试如下,因为原来的根目录下无hello程序,所以第一次运行失败:

3. 原理

就这个工具来讲,应该是使用了 API chroot(),可参考man 2 chroot

我写了个简单的mychroot,同样有效:

// mychroot.c
#include <stdio.h>
#include <unistd.h>

int main(int argc, char **argv)
{
    if(argc < 3){
        printf("%s NEWROOTPATH FILENAME\n", argv[0]);
        return 0;
    }
    if(chroot(argv[1])){
        perror("chroot");
        return -1;
    }
    execve(argv[2], &(argv[2]), NULL);

    return 0;
}

更深层次的原理:猜测是更改了进程图像的task_struct->fs->root

验证

它依赖的头文件是<unistd.h>

找到/usr/include/linux/unistd.h

#include <asm/unistd.h>

找到/usr/include/asm-generic/unistd.h

#define __NR_chroot 51
__SYSCALL(__NR_chroot, sys_chroot)

好吧,是一个系统调用,看内核源码。

这是一个一参的系统调用,所以应该是用SYSCALL_DEFINE1宏定义来定义的。一开始我找不到源码中对sys_chroot的定义在哪里,后来看了【参考】的文章才知道新的内核中对系统调用的统一调用方式是SYSCALL_DEFINEX,这里的X是参数个数。【参考】的文章非常好,有理有据,推荐阅读。

fs/open.c中:

SYSCALL_DEFINE1(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(current->fs, &path);

可以跟进一下,在fs/fs_struct.c中:

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

猜对了。

参考文献

Per Aspera Ad Astra