• Linux 内核源码分析 -- chroot


    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 结构,属于同一文件系统的所有目录和文件隶属同一 vfsmountvfsmount 结构对应于该文件系统顶层目录,即挂载目录

    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 结构

  • 相关阅读:
    mysql中的enum型
    mysql中的时间year/date/time/datetime
    一些数字的属性
    mysql增删
    Perl6 Bailador框架(8):自定义400/500
    react: typescript jest && enzyme
    webstorm tslint配置
    react: typescript import images alias
    JSONP原理及简单实现
    纯css画三角形
  • 原文地址:https://www.cnblogs.com/crybaby/p/Linux_kernel_chroot.html
Copyright © 2020-2023  润新知