chroot
在 內核中的實現
在 Linux 5.6 版本中 chroot
函數的系統調用對應的函數位於:./fs/open.c:539:SYSCALL_DEFINE1(chroot, const char __user *, filename)
via: https://elixir.bootlin.com/linux/v5.6/source/fs/open.c#L539
SYSCALL_DEFINE1(chroot, const char __user *, filename)
{
return ksys_chroot(filename);
}
ksys_chroot
via: https://elixir.bootlin.com/linux/v5.6/source/fs/open.c#L506
int ksys_chroot(const char __user *filename)
{
struct path path;
int error;
unsigned int lookup_flags = LOOKUP_FOLLOW | LOOKUP_DIRECTORY;
retry:
// 根據文件名找到 path 結構
error = user_path_at(AT_FDCWD, filename, lookup_flags, &path);
if (error)
goto out;
// 解析 path 的 mm_root dentry 結構,再解析相應的 inode 結構,即 d_inode,就可找到掛載點相應的 inode 結構
error = inode_permission(path.dentry->d_inode, MAY_EXEC | MAY_CHDIR);
if (error)
goto dput_and_out;
error = -EPERM;
// 判斷當前進程所有者是不是有執行 chroot 操作的權限
// 這里是 namespace, cred 的內容了,不展開
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;
}
flag 含義
#define LOOKUP_FOLLOW 0x0001 /* follow links at the end */
#define LOOKUP_DIRECTORY 0x0002 /* require a directory */
set_fs_root
主要函數,就是在這個函數里修改了程序的 “根目錄”
via: https://elixir.bootlin.com/linux/v5.6/source/fs/fs_struct.c#L15
先來看一下 fs_struct
struct fs_struct {
int users;
spinlock_t lock;
seqcount_t seq;
int umask;
int in_exec;
struct path root, pwd;
// root:根目錄的目錄項
// pwd:當前工作目錄的目錄項
} __randomize_layout;
/*
* 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; // 設置 根目錄 為 path 的目錄項
write_seqcount_end(&fs->seq);
spin_unlock(&fs->lock);
if (old_root.dentry)
path_put(&old_root);
}
struct path
via: https://elixir.bootlin.com/linux/v5.6/source/include/linux/path.h#L8
struct path {
struct vfsmount *mnt;
struct dentry *dentry;
} __randomize_layout;
struct vfsmount
描述獨立文件系統的掛載信息,每個不同的掛載點對應一個獨立的 vfsmount
結構,屬於同一文件系統的所有目錄和文件隸屬同一 vfsmount
該 vfsmount
結構對應於該文件系統頂層目錄,即掛載目錄
via: https://elixir.bootlin.com/linux/v5.6/source/include/linux/mount.h#L68
struct vfsmount {
struct dentry *mnt_root; /* 上一層掛載點對應的 dentry */
struct super_block *mnt_sb; /* 指向超級塊 */
int mnt_flags;
} __randomize_layout;
struct dentry
目錄項,是Linux文件系統中某個 索引節點(inode) 的鏈接
via:https://elixir.bootlin.com/linux/v5.6/source/include/linux/dcache.h#L89
struct dentry {
/* RCU lookup touched fields */
unsigned int d_flags; /* protected by d_lock */
seqcount_t d_seq; /* per dentry seqlock */
struct hlist_bl_node d_hash; /* lookup hash list */
struct dentry *d_parent; /* 父目錄項指針 */
struct qstr d_name; // 文件或者目錄的名稱
// 目錄的 inode
struct inode *d_inode; /* Where the name belongs to - NULL is
* negative */
unsigned char d_iname[DNAME_INLINE_LEN]; /* small names */
/* Ref lookup also touches following */
struct lockref d_lockref; /* per-dentry lock and refcount */
const struct dentry_operations *d_op;
struct super_block *d_sb; /* The root of the dentry tree */
unsigned long d_time; /* used by d_revalidate */
void *d_fsdata; /* fs-specific data */
union {
struct list_head d_lru; /* LRU list */
wait_queue_head_t *d_wait; /* in-lookup ones only */
};
struct list_head d_child; /* child of parent list */
struct list_head d_subdirs; /* our children */
/*
* d_alias and d_rcu can share memory
*/
union {
struct hlist_node d_alias; /* inode alias list */
struct hlist_bl_node d_in_lookup_hash; /* only for in-lookup ones */
struct rcu_head d_rcu;
} d_u;
} __randomize_layout;
上面這兩個都是 文件系統 的東西,不在這里詳細分析
總結
其實 chroot 修改了進程的 root 目錄的核心操作就是修改了 進程 的 task_struct -> fs -> root
因為是 struct path root
,所以
user_path_at(AT_FDCWD, filename, lookup_flags, &path);
就是通過文件名去解析 文件夾 對應的 path
結構,存在 path
變量里面
然后就是權限檢查
在然后把 path
傳進 set_fs_root
函數
fs->root = *path;
修改了 root
這樣進程就認為 filename
是根目錄,因為 fs->root
存的是 filename
目錄的 path
結構