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);
}
猜对了。