Bootstrap

linux内核open过程的权限管理

【摘要】

【正文一:文件管理】

【常见情况分析】

【正文二:目录管理】

【总结】

 

注意:请使用谷歌浏览器阅读(IE浏览器排版混乱)

 

【摘要】

本文以实例介绍linux系统对文件权限的管理.linux kernel对文件权限的检查、关键处理和难点,都发生在打开文件时.至于读写文件,检查方法非常简单,都是基于打开文件时设置的一些权限标志位,所以本文只针对打开文件做介绍.

【正文一:文件管理】

1 打开文件的接口:

glibc/uclibc中open接口为:

int open(const char *pathname,int flags)

说明:以flags方式打开文件,如果需要创建文件则文件属性为040,即文件所在用户组的用户有读权限.

int open(const char *pathname, int flags, mode_t mode)

说明:以flags方式打开文件。如果需要创建新文件,则指定文件属性为mode;如果不需要创建文件(即flags未设置O_CREATE标志)则mode字段不起作用.

系统中会修改mode&=022;所以即使此时mode权限为0777,也会被改为0755可,参看后文分析,即open创建文件时不允许非文件所属用户具有写权限.不过可以通过chmod重新修改为0777权限.umask()系统调用可以修改022;

内核态对应系统调用为:

sys_open(const char* __user *filename,int flags,umode_t mode);

2 文件打开方式与文件权限

2.1 文件常用打开方式(即open函数的入参flags):

#define O_RDONLY  00000000       表示以只读方式打开文件;

#define O_WRONLY 00000001        表示以只写方式打开文件;

#define O_RDWR      00000002    表示以读写方式打开文件;

#define O_CREATE   00000100      表示如果文件不存在,则创建文件;

2.2 文件访问权限(即open函数的入参mode):

说明:R表示文件有读权限;W表示文件有写权限;X表示文件有执行权限;

    U表示文件所属用户;G表示文件所属用户所在组的用户;O表示其他用户。

1)文件所属用户对文件的访问权限:

#define S_IRWXU 00700      表示文件所属用户,对文件有读写和执行权限

#define S_IRUSR 00400      表示文件所属用户,对文件有读权限

#define S_IWUSR 00200      表示文件所属用户,对文件有写权限

#define S_IXUSR  00100     表示文件所属用户,对文件有执行权限

2)文件所属用户所在组用户对文件的访问权限:

#define S_IRWXG 00070      表示对文件有读写和执行权限

#define S_IRGRP 00040      表示对文件有读权限

#define S_IWGRP 00020      表示对文件有写权限

#define S_IXGRP  00010     表示对文件有执行权限

3)其他用户对文件的访问权限:

#define S_IRWXO 00007     表示对文件有读写和执行权限

#define S_IROTH 00004     表示对文件有读权限

#define S_IWOTH 00002     表示对文件有写权限

#define S_IXOTH 00001     表示对文件有执行权限

4)进程权限

RUID(real user id)是启动该进程的用户的ID,它与父进程用户ID相同,除非被改变.

EUID是内核检查权限时使用的实际ID,因此它确定了进程的权限.

SSUID:设置用户ID,表示执行时设置用户ID程序的进程映像文件的所有者ID.

允许一个进程将其EUID更改为其RUID/SSUID.当然root特权进程可做任何事.

当访问一个文件时,进程的有效用户ID(Effective User ID,EUID)与文件所有者的UID进行对比.如果该用户不是所有者,那么再对GID进行比较.

文件还有一个执行时设置用户ID位(set-user-ID-on-execution)S_ISUID表示的.该位可以设置给一个可执行进程二进制文件,表示以可执行文件所有者的特权而不是用户组的特权运行(即EUID设置为文件所有者ID).exec->bprm_fill_uid()中实现.

原始socket(SOCK_RAW)可以自行组装数据包(伪装本地 IP,本地 MAC),可以接收本机网卡上所有的数据帧(数据包)。另外,必须在管理员权限下才能使用原始套接字.

inet_create创建原始套接字过程中通过if(sock->type==SOCK_RAW&&capable(CAP_NET_RAW))判断权限;可以参考ping命令实现(setuid(getuid));getuid返回的是RUID;

 

3 常见文件访问权限问题分析

3.1 在/mnt/testdir目录下创建文件.

1)情景再现:

/mnt/testdir目录访问权限0777: drwx-rwx-rwx   2   root   root      testdir;

open("/mnt/testdir/testfile",flags,mode);

2)分析:open时系统都做了什么?

long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode)
{
	struct open_flags op;
	/* 根据open入参flags和mode生产open_flags,以后打开文件操作do_filp_open以此为入参 */	
	int fd = build_open_flags(flags, mode, &op);
	struct filename *tmp;

	if (fd)
		return fd;

	tmp = getname(filename);
	if (IS_ERR(tmp))
		return PTR_ERR(tmp);

	fd = get_unused_fd_flags(flags);
	if (fd >= 0) {
		/* 打开文件操作*/
		struct file *f = do_filp_open(dfd, tmp, &op);
		if (IS_ERR(f)) {
			put_unused_fd(fd);
			fd = PTR_ERR(f);
		} else {
			fsnotify_open(f);
			fd_install(fd, f);
		}
	}
	putname(tmp);
	return fd;
}

根据open入参flags和mode生产open_flags,以后打开文件操作do_filp_open以此为入参:

调用过程:do_sys_open->build_open_flags

static inline int build_open_flags(int flags, umode_t mode, struct open_flags *op)
{
	int lookup_flags = 0;
	int acc_mode;
	/*
	注意此处:	
	1如果open时flags没有设置O_CREATE标志,则无论open入参mode是何值,系统都认为是0,即未使用mode入参.
	2如果open时flags设置O_CREATE标志,则需要重新生成mode,其中S_IALLUGO是所有访问权限的并集.
	例如:以mode=0777打开文件,经此处理后变为0100777;
	S_IFREG=0100000 表示普通文件
	S_IFDIR=040000  表示目录文件
	 */
	if (flags & (O_CREAT | __O_TMPFILE))
		op->mode = (mode & S_IALLUGO) | S_IFREG;
	else
		op->mode = 0;

	flags &= ~FMODE_NONOTIFY & ~O_CLOEXEC;

	if (flags & __O_SYNC)
		flags |= O_DSYNC;

	if (flags & __O_TMPFILE) {
		if ((flags & O_TMPFILE_MASK) != O_TMPFILE)
			return -EINVAL;
		acc_mode = MAY_OPEN | ACC_MODE(flags);
		if (!(acc_mode & MAY_WRITE))
			return -EINVAL;
	} else if (flags & O_PATH) {
		flags &= O_DIRECTORY | O_NOFOLLOW | O_PATH;
		acc_mode = 0;
	} else {
	/*
	acc_mode主要是对读写权限的校验:
        真正打开文件之前may_open通过inode_permission函数把acc_mode与inode->i_mode做比较,实现访问权限校验,inode->i_mode就是ls中看到的文件形如-rwx-的权限.
	举例: ls /mnt/testdir/testfile -l :-rwx-rwx-rwx 即0777;
	open("/mnt/testdir/testfile",O_RW);以读写方式打开一个文件,即flags=O_RW,从而acc_mode=06 (表示rwx中r位和w位都为1);
	打开文件之前会在inode_permission中校验inode->imode是否有读写权限,如本例中0777显然对所有用户都有读写权限,此处可以参考inode_permission一起分析.
	acc_mode=MAY_OPEN|ACC_MODE(flags):
	其中:MAY_OPEN=0x20.
	ACC_MODE(flags)取值为:
	当open函数入参flags = O_RDONLY (O_RDONLY=0)时,ACC_MODE(flags)=004 即:004代表bit2=1表示读权限打开,acc_mode=024.
	当flags = O_WRONLY =1时ACC_MODE(flags)=002 即:002代表bit1=1表示写权限,acc_mode=0x22.
	当flags = O_RDWR = 2时ACC_MODE(flags)=006 即:006代表bit2=1|bit1=1表示读写权限,acc_mode=0x26.
	*/
		acc_mode = MAY_OPEN | ACC_MODE(flags);
	}

	op->open_flag = flags;

	/* O_TRUNC implies we need access checks for write permissions */
	if (flags & O_TRUNC)
		acc_mode |= MAY_WRITE;

	/* Allow the LSM permission hook to distinguish append
	   access from general write access. */
	if (flags & O_APPEND)
		acc_mode |= MAY_APPEND;

	op->acc_mode = acc_mode;

	op->intent = flags & O_PATH ? 0 : LOOKUP_OPEN;

	if (flags & O_CREAT) {
		op->intent |= LOOKUP_CREATE;
		if (flags & O_EXCL)
			op->intent |= LOOKUP_EXCL;
	}

	if (flags & O_DIRECTORY)
		lookup_flags |= LOOKUP_DIRECTORY;
	if (!(flags & O_NOFOLLOW))
		lookup_flags |= LOOKUP_FOLLOW;
	op->lookup_flags = lookup_flags;
	return 0;
}

do_filp_open->path_openat打开文件:

static struct file *path_openat(int dfd, struct filename *pathname,
		struct nameidata *nd, const struct open_flags *op, int flags)
{
	struct file *base = NULL;
	struct file *file;
	struct path path;
	int opened = 0;
	int error;
        /* 创建struct *file并初始化,open过程中会把一个文件句柄与该file绑定,用户读写文件时会通过文件句柄找到该file进行一系列操作 */
	file = get_empty_filp();
	if (IS_ERR(file))
		return file;

	file->f_flags = op->open_flag;

	if (unlikely(file->f_flags & __O_TMPFILE)) {
		error = do_tmpfile(dfd, pathname, nd, flags, op, file, &opened);
		goto out2;
	}
	/* 
	作用:此时flags=0x41
	1 初始化nameidata 包括文件名赋值给nd->last.name等;path_init中初始:nd->path=nd->root=current->fs->root
	2 初始化nd->path.dentry为该superblock根目录 / 对应的目录项.此时nd->path.dentry->d_iname=/;
	nd->path.dentry->d_inode->i_ino为根目录/对应的inode num; squashfs_fill_super中打印可以看到根目录的inode->i_ino;
        该函数为下一步link_path_walk中查找文件所在目录文件的目录项做准备,
	因为此时dentry初始化为根目录,所以从根目录开始查找文件所在目录.
	实际上path_init中将nd->path,nd->root都初始化为current->fs->root,nd->root也是struct path结构,且当前进程的current->fs->root是根目录"/";
	*/
	error = path_init(dfd, pathname->name, flags | LOOKUP_PARENT, nd, &base);
	if (unlikely(error))
		goto out;

	current->total_link_count = 0;
	/*	
	作用:找到文件所在目录的目录项。	
	举例:open("/mnt/testdir/testfile",flags,mode);
	此时nd->path.dentry->d_iname为/mnt/testdir;
	即:dentry->d_iname=testdir;dentry->d_inode->i_ino=testdir对应的inode number.
	*/
	error = link_path_walk(pathname->name, nd);
	if (unlikely(error))
		goto out;
	/*关键函数 该函数中会初始化struct file;*/
	error = do_last(nd, &path, file, op, &opened, pathname);
	return file;
}

path_openat->link_path_walk()该函数十分重要,它遍历文件所在路径上的所有目录项,而且能够校验每个目录项对应inode的权限;

link_path_walk之前先通过path_openat->path_init初始nameidata它可以表明从哪个目录开始遍历路径:

static int path_init(int dfd, const char *name,unsigned int flags,struct nameidata *nd,struct file *fp)

{  /*绝对路径名,从进程的根目录遍历*/

   if(*name=='/'){ 

      /*[path_init]/mnt/testdir/testfile:flags=0x51;root=/,pwd=/,ino=66即根目录的inode num;在根文件系统挂载时

       squashfs_fill_super->(root=new_inode)中创建文件系统里的根目录inode信息(该信息从flash上的文件系统镜像中读取),

       此时可以通过打印看到根目录的inode num*/

      printk("[%s]%s:flags=0x%x;root=%s,pwd=%s;ino=%lu\n",__FUNCTION__,name,flags,

            current->fs->root.dentry->d_iname,current->fs->pwd.dentry->d_iname,

            current->fs->root.dentry->d_inode->i_ino);

      set_root(nd);

   }else if (dfd==AT_FDCWD){ /*相对路径名,从当前目录开始遍历*/

     get_fs_pwd(current->fs,&nd->path);

   }

}

这个函数通过调用walk_component->lookup_fast/lookup_slow找到每个目录项对应的inode.

link_path_walk->walk_componet先通过lookup_fast查找系统是否创建过dentry(dram上存在dentry结构),如果未找到则再通过

walk_componet->lookup_slow->__lookup_hash->lookup_real->(dir->i_op->lookup()=ubifs_lookup())从flash上读取信息,依此信息,系统创建dentry;

注意此时 nd->path中是current->fs->root即文件系统根目录/;

static int link_path_walk(const char *name, struct nameidata *nd)
{
	struct path next;
	int err;
	
	while (*name=='/')
		name++;
	if (!*name)
		return 0;

	/* At this point we know we have a real path component. */
	for(;;) {
		u64 hash_len;
		int type;
		/*
		for循环里,通过may_lookup函数校验文件所在路径上每个目录项对应的inode权限。
		其实在may_open和may_create时都需要对inode权限进行检查。
		注意它和may_open区别:
		第一点区别:may_open是打开文件前,对文件对应的inode的权限检查,而没有检查文件所在路径上每个目录项对应的inode,
		正是因为在此检查过了。
		第二点区别:may_open检查inode的acc_mode主要对读写bit进行检查,即may_open->inode_permission(inode,acc_mode);
		acc_mode可以参考build_open_flags和acl_permission_check两个函数的分析;may_lookup中检查的是MAY_EXEC|MAY_NOT_BLOCK;
		即may_lookup->inode_permisson(inode,MAY_EXEC|MAY_NOT_BLOCK),其实主要检查每个inode的rwx中的x位;
		通过may_lookup和may_open一个文件inode的rwx都检查到了。
		举例:open(/mnt/testdir/testfile,O_RDWR);testdir权限为0641;
		处理过程:系统通过link_path_walk遍历了mnt,testdir,testfile对应的目录项,并通过may_lookup检查了每个目录项对应inode的权限
		注意只检查了执行权限,即x位,如果检查通过,会在以后的may_open中继续检查rw位,may_open的检查可以看后续分析。
		还有一点如果文件不存在,还会在lookup_open->vfs_create->may_create中检查文件所在目录testdir的写权限.	
		值得注意的是,link_path_walk之后path->dentry指向了testdir对应目录项,而不是testfile对应的目录项;
		link_path_walk虽然在for循环中遍历了/mnt/testdir/testfile所有目录项和inode,但并没有对testfile文件对应的目录项dentry和inode
		做校验。而是在以后的处理流程中处理的,可以参考本文以下介绍。
		*/
		err = may_lookup(nd);
 		if (err)
			break;

		hash_len = hash_name(name);

		type = LAST_NORM;
		if (name[0] == '.') switch (hashlen_len(hash_len)) {
			case 2:
				if (name[1] == '.') {
					type = LAST_DOTDOT;
					nd->flags |= LOOKUP_JUMPED;
				}
				break;
			case 1:
				type = LAST_DOT;
		}
		if (likely(type == LAST_NORM)) {
			struct dentry *parent = nd->path.dentry;
			nd->flags &= ~LOOKUP_JUMPED;
			if (unlikely(parent->d_flags & DCACHE_OP_HASH)) {
				struct qstr this = { { .hash_len = hash_len }, .name = name };
				err = parent->d_op->d_hash(parent, &this);
				if (err < 0)
					break;
				hash_len = this.hash_len;
				name = this.name;
			}
		}

		nd->last.hash_len = hash_len;
		nd->last.name = name;
		nd->last_type = type;

		name += hashlen_len(hash_len);
		if (!*name)
			return 0;
		/*
		 * If it wasn't NUL, we know it was '/'. Skip that
		 * slash, and continue until no more slashes.
		 */
		do {
			name++;
		} while (unlikely(*name == '/'));
		if (!*name)
			return 0;
		/*
		for循环中通过wak_component找到文件路径上每个目录项和对应的inode,并在for循环中通过may_lookup检查权限。
		检查到的目录项和inode赋值给nd->path->dentry和nd->path->dentry->d_inode
		*/
		err = walk_component(nd, &next, LOOKUP_FOLLOW);
		if (err < 0)
			return err;

		if (err) {
			err = nested_symlink(&next, nd);
			if (err)
				return err;
		}
		if (!d_can_lookup(nd->path.dentry)) {
			err = -ENOTDIR; 
			break;
		}
	}
	terminate_walk(nd);
	return err;
}

path_openat->do_last()打开文件:

static int do_last(struct nameidata *nd, struct path *path,
		   struct file *file, const struct open_flags *op,
		   int *opened, struct filename *name)
{
	struct dentry *dir = nd->path.dentry;
	int open_flag = op->open_flag;
	bool will_truncate = (open_flag & O_TRUNC) != 0;
	bool got_write = false;
	int acc_mode = op->acc_mode;
	struct inode *inode;
	bool symlink_ok = false;
	struct path save_parent = { .dentry = NULL, .mnt = NULL };
	bool retried = false;
	int error;

	nd->flags &= ~LOOKUP_PARENT;
	nd->flags |= op->intent;

	if (nd->last_type != LAST_NORM) {
		error = handle_dots(nd, nd->last_type);
		if (error)
			return error;
		goto finish_open;
	}
	/* 没有设置O_CREATE标志,此时op->mode=0 */
	if (!(open_flag & O_CREAT)) {
		if (nd->last.name[nd->last.len])
			nd->flags |= LOOKUP_FOLLOW | LOOKUP_DIRECTORY;
		if (open_flag & O_PATH && !(nd->flags & LOOKUP_FOLLOW))
			symlink_ok = true;
		/* we _can_ be in RCU mode here */
		error = lookup_fast(nd, path, &inode);
		/*
		对于已经存在的文件,lookup_fast找到inode,并跳过lookup_open函数;
		对于不存在的文件,lookup_open在创建新文件时使用,会为新文件申请inode等操作。
		*/		
		if (likely(!error))
			goto finish_lookup;
		/* 未找到 inode,打开文件失败*/
		if (error < 0)
			goto out;

		BUG_ON(nd->inode != dir->d_inode);
	} else {
		error = complete_walk(nd);
		if (error)
			return error;
		/* 设置了O_CREATE标志 ,op->mode为build_open_flags中生成*/
		audit_inode(name, dir, LOOKUP_PARENT);
		error = -EISDIR;
		/* trailing slashes? */
		if (nd->last.name[nd->last.len])
			goto out;
	}
/* 
open新建文件时 在此申请新文件的目录项dentry和inode结点
open已创建文件时,不需要执行,直接goto finish_lookup:
*/
retry_lookup:
	if (op->open_flag & (O_CREAT | O_TRUNC | O_WRONLY | O_RDWR)) {
		error = mnt_want_write(nd->path.mnt);
		if (!error)
			got_write = true;
	}
	mutex_lock(&dir->d_inode->i_mutex);
	/*
	当需要创建新文件时,此函数完成inode申请及初始化
	其中包括文件权限的初始化:inode->i_mode;目录项的申请和初始化;
	may_open校验文件权限时会用到inode->i_mode文件权限
	*/
	
	error = lookup_open(nd, path, file, op, got_write, opened);
	mutex_unlock(&dir->d_inode->i_mutex);

	if (error <= 0) {
		if (error)
			goto out;

		if ((*opened & FILE_CREATED) ||
		    !S_ISREG(file_inode(file)->i_mode))
			will_truncate = false;

		audit_inode(name, file->f_path.dentry, 0);
		goto opened;
	}
/*
新建文件时lookup_open中将opened |= FILE_CREATED;此时acc_mode=MAY_OPEN,所以接下来,打开文件之前
may_open->inode_permission->acl_permission_check中不再进行rwx权限检查.因为检查的是acc_mode,其没有指定的rwx
*/
	if (*opened & FILE_CREATED) {
		/* Don't check for write permission, don't truncate */
		open_flag &= ~O_TRUNC;
		will_truncate = false;
		acc_mode = MAY_OPEN;
		path_to_nameidata(path, nd);
		goto finish_open_created;
	}

	if (d_is_positive(path->dentry))
		audit_inode(name, path->dentry, 0);

	if (got_write) {
		mnt_drop_write(nd->path.mnt);
		got_write = false;
	}

	error = -EEXIST;
	if ((open_flag & (O_EXCL | O_CREAT)) == (O_EXCL | O_CREAT))
		goto exit_dput;

	error = follow_managed(path, nd->flags);
	if (error < 0)
		goto exit_dput;

	if (error)
		nd->flags |= LOOKUP_JUMPED;

	BUG_ON(nd->flags & LOOKUP_RCU);
	inode = path->dentry->d_inode;
finish_lookup:
	/* we _can_ be in RCU mode here */
	error = -ENOENT;
	if (!inode || d_is_negative(path->dentry)) {
		path_to_nameidata(path, nd);
		goto out;
	}

	if (should_follow_link(path->dentry, !symlink_ok)) {
		if (nd->flags & LOOKUP_RCU) {
			if (unlikely(nd->path.mnt != path->mnt ||
				     unlazy_walk(nd, path->dentry))) {
				error = -ECHILD;
				goto out;
			}
		}
		BUG_ON(inode != path->dentry->d_inode);
		return 1;
	}

	if ((nd->flags & LOOKUP_RCU) || nd->path.mnt != path->mnt) {
		path_to_nameidata(path, nd);
	} else {
		save_parent.dentry = nd->path.dentry;
		save_parent.mnt = mntget(path->mnt);
		nd->path.dentry = path->dentry;

	}
	nd->inode = inode;
	/* Why this, you ask?  _Now_ we might have grown LOOKUP_JUMPED... */
finish_open:
	error = complete_walk(nd);
	if (error) {
		path_put(&save_parent);
		return error;
	}
	audit_inode(name, nd->path.dentry, 0);
	error = -EISDIR;
	if ((open_flag & O_CREAT) && d_is_dir(nd->path.dentry))
		goto out;
	error = -ENOTDIR;
	if ((nd->flags & LOOKUP_DIRECTORY) && !d_can_lookup(nd->path.dentry))
		goto out;
	if (!S_ISREG(nd->inode->i_mode))
		will_truncate = false;

	if (will_truncate) {
		error = mnt_want_write(nd->path.mnt);
		if (error)
			goto out;
		got_write = true;
	}
finish_open_created:
	/*
	文件访问权限的校验,主要是校验是否有权限访问inode,函数may_open->inode_permission中实现;
	其中may_open函数能根据nd->path找到文件对应的目录项dentry和inode结点.
	acc_mode是在build_open_flags根据open函数入参flags生成的.对于新建文件,acc_mode可能被更改.
	如果新建文件时lookup_open中将opened |= FILE_CREATED则在上面retry_lookup中acc_mode=MAY_OPEN.
	acc_mode主要是对读写权限的校验:
        真正打开文件之前may_open通过inode_permission函数把acc_mode与inode->i_mode做比较,实现访问权限校验,inode->i_mode就是ls中看到的文件形如-rwx-的权限。
	举例: ls /mnt/testdir/testfile -l :-rwx-rwx-rwx 即0777;
	open("/mnt/testdir/testfile",O_RW);以读写方式打开一个文件,即flags=O_RW,从而acc_mode=06 (表示rwx中r位和w位都为1)
	打开文件之前会在inode_permission中校验inode->imode是否有读写权限,如本例中0777显然对所有用户都有读写权限,此处可以参考inode_permission一起分析。
	acc_mode=MAY_OPEN|ACC_MODE(flags):
	其中:MAY_OPEN=0x20
	ACC_MODE(flags)取值为:
	     当open函数入参flags=O_RDONLY=0时ACC_MODE(flags)=004 即:004代表bit2=1表示读权限打开;
	     当flags=O_WRONLY=1时ACC_MODE(flags)=002 即:002代表bit1=1表示写权限;
	     当flags=O_RDWR=2时ACC_MODE(flags)=006 即:006代表bit2=1|bit1=1表示读权限;
	*/
	error = may_open(&nd->path, acc_mode, open_flag);
	if (error)
		goto out;

	BUG_ON(*opened & FILE_OPENED); /* once it's opened, it's opened */
        /* 真正的文件打开操作:vfs_open->do_dentry_open系列调用中初始化struct file*/
	error = vfs_open(&nd->path, file, current_cred());
	if (!error) {
		*opened |= FILE_OPENED;
	} else {
		if (error == -EOPENSTALE)
			goto stale_open;
		goto out;
	}
opened:
	error = open_check_o_direct(file);
	if (error)
		goto exit_fput;
	error = ima_file_check(file, op->acc_mode, *opened);
	if (error)
		goto exit_fput;

	if (will_truncate) {
		error = handle_truncate(file);
		if (error)
			goto exit_fput;
	}
out:
	if (got_write)
		mnt_drop_write(nd->path.mnt);
	path_put(&save_parent);
	terminate_walk(nd);
	return error;

exit_dput:
	path_put_conditional(path, nd);
	goto out;
exit_fput:
	fput(file);
	goto out;

stale_open:
	/* If no saved parent or already retried then can't retry */
	if (!save_parent.dentry || retried)
		goto out;

	BUG_ON(save_parent.dentry != dir);
	path_put(&nd->path);
	nd->path = save_parent;
	nd->inode = dir->d_inode;
	save_parent.mnt = NULL;
	save_parent.dentry = NULL;
	if (got_write) {
		mnt_drop_write(nd->path.mnt);
		got_write = false;
	}
	retried = true;
	goto retry_lookup;
}

do_last->lookup_open 打开文件:

static int lookup_open(struct nameidata *nd, struct path *path,
			struct file *file,
			const struct open_flags *op,
			bool got_write, int *opened)
{
	struct dentry *dir = nd->path.dentry;
	struct inode *dir_inode = dir->d_inode;
	struct dentry *dentry;
	int error;
	bool need_lookup;

	*opened &= ~FILE_CREATED;
	/*
	作用:为文件创建目录项。找到文件对应的目录项,如果没找到则为新文件申请目录项;
	函数link_path_walk中nd->path.dentry赋值为文件所在目录的目录项。此时根据nd->path.dentry生成文件对应的目录项dentry
	lookup_dcache->d_alloc中申请新建文件对应的dentry,并初始化该dentry	
	*/
	dentry = lookup_dcache(&nd->last, dir, nd->flags, &need_lookup);
	if (IS_ERR(dentry))
		return PTR_ERR(dentry);

	/* Cached positive dentry: will open in f_op->open */
	if (!need_lookup && dentry->d_inode)
		goto out_no_open;

	if ((nd->flags & LOOKUP_OPEN) && dir_inode->i_op->atomic_open) {
		return atomic_open(nd, dentry, path, file, op, got_write,
				   need_lookup, opened);
	}

	if (need_lookup) {
		BUG_ON(dentry->d_inode);

		dentry = lookup_real(dir_inode, dentry, nd->flags);
		if (IS_ERR(dentry))
			return PTR_ERR(dentry);
	}

	/* Negative dentry, just create the file */
	if (!dentry->d_inode && (op->open_flag & O_CREAT)) {
		umode_t mode = op->mode;
		/*
		int current_umask(void)
		{
			return current->fs->umask;
		}
		VFS使用umask,此处mode=mode&022,即非文件所属用户不能写
		current->fs->umask是何时设置的:1 用户可通过umask()系统调用设置;
		2 进程创建时会copy_fs_struct()该函数继承了父进程中的umask;其实都是继承于 INIT_TASK->init_fs.umask=022
		所以open时即使mode权限为0777,在此也会改为0755;注意最开始build_open_flags也可能根据O_CREATE标志修改mode
		*/		
		if (!IS_POSIXACL(dir->d_inode))
			mode &= ~current_umask();
		if (!got_write) {
			error = -EROFS;
			goto out_dput;
		}
		*opened |= FILE_CREATED;
		error = security_path_mknod(&nd->path, dentry, mode, 0);
		if (error)
			goto out_dput;
		/*
		注意:此时dir对应的目录项为文件所在目录对应的目录项:
		例如文件:/mnt/testdir/testfile
                1>dir->d_iname=testdir
		2>dentry对应的目录项为文件对应的目录项dentry->d_iname=testfile
		vfs_create中又修改了mode=mode|S_IFREG所以open时既使mode权限为0777,在此也会改为100755
		注意:对于新建文件,vfs_create->may_create中会针对文件所在目录testdir的目录项对应的inode,即dentry->d_inode进行写权限和执行权限检查,即:
		may_create->inode_permission(inode,MAY_WRITE|MAY_EXEC);可以参考link_path_walk->may_lookup和may_open中介绍。
		*/
		error = vfs_create(dir->d_inode, dentry, mode,
				   nd->flags & LOOKUP_EXCL);
		if (error)
			goto out_dput;
	}
out_no_open:
	path->dentry = dentry;
	path->mnt = nd->path.mnt;
	return 1;

out_dput:
	dput(dentry);
	return error;
}

do_last->lookup_open->lookup_dcache->d_alloc->__d_alloc() 申请新建文件对应的目录项dentry:

struct dentry *__d_alloc(struct super_block *sb, const struct qstr *name)
{
	struct dentry *dentry;
	char *dname;
	/*
	作用:申请新建文件对应的dentry
	举例:open("/mnt/testdir/testfile",flags,mode);
	此时申请的dentry->d_iname=testfile
	dentry->d_inode初始化为NULL,后面vfs_create中申请inode
	*/
	dentry = kmem_cache_alloc(dentry_cache, GFP_KERNEL);
	if (!dentry)
		return NULL;

	dentry->d_iname[DNAME_INLINE_LEN-1] = 0;
	if (name->len > DNAME_INLINE_LEN-1) {
		size_t size = offsetof(struct external_name, name[1]);
		struct external_name *p = kmalloc(size + name->len, GFP_KERNEL);
		if (!p) {
			kmem_cache_free(dentry_cache, dentry); 
			return NULL;
		}
		atomic_set(&p->u.count, 1);
		dname = p->name;
	} else  {
		dname = dentry->d_iname;
	}	

	dentry->d_name.len = name->len;
	dentry->d_name.hash = name->hash;
	memcpy(dname, name->name, name->len);
	dname[name->len] = 0;

	/* Make sure we always see the terminating NUL character */
	smp_wmb();
	dentry->d_name.name = dname;

	dentry->d_lockref.count = 1;
	dentry->d_flags = 0;
	spin_lock_init(&dentry->d_lock);
	seqcount_init(&dentry->d_seq);
	dentry->d_inode = NULL;
	dentry->d_parent = dentry;
	dentry->d_sb = sb;
	dentry->d_op = NULL;
	dentry->d_fsdata = NULL;
	INIT_HLIST_BL_NODE(&dentry->d_hash);
	INIT_LIST_HEAD(&dentry->d_lru);
	INIT_LIST_HEAD(&dentry->d_subdirs);
	INIT_HLIST_NODE(&dentry->d_u.d_alias);
	INIT_LIST_HEAD(&dentry->d_child);
	d_set_d_op(dentry, dentry->d_sb->s_d_op);

	this_cpu_inc(nr_dentry);

	return dentry;
}

函数继续执行,因为是ubifs文件系统,所以

do_last->lookup_open->vfs_create->ubifs_create->ubifs_new_inode:

注意:操作系统中的inode信息(dram上描述inode的信息,区别于flash上的inode信息)的初始化时机包括:

1>是在do_last->lookup_open->vfs_create->ubifs_create->ubifs_new_inode创建一个文件时初始化的.

2>对于已存在文件,是在do_last->lookup_open->lookup_real->ubifs_lookup->ubifs_iget

struct inode *ubifs_new_inode(struct ubifs_info *c, const struct inode *dir,
			      umode_t mode)
{
	struct inode *inode;
	struct ubifs_inode *ui;
        /*
	 inode申请并初始化:
	 new_inode->new_inode_pseudo->alloc_inode->inode_init_always
	*/
	inode = new_inode(c->vfs_sb);
	ui = ubifs_inode(inode);
	if (!inode)
		return ERR_PTR(-ENOMEM);

	inode->i_flags |= S_NOCMTIME;
	/* 初始化inode的权限和用户信息*/
	inode_init_owner(inode, dir, mode);
	inode->i_mtime = inode->i_atime = inode->i_ctime =
			 ubifs_current_time(inode);
	inode->i_mapping->nrpages = 0;
	/* Disable readahead */
	inode->i_mapping->backing_dev_info = &c->bdi;

	switch (mode & S_IFMT) {
	case S_IFREG:
		inode->i_mapping->a_ops = &ubifs_file_address_operations;
		inode->i_op = &ubifs_file_inode_operations;
		inode->i_fop = &ubifs_file_operations;
		break;
	case S_IFDIR:
		inode->i_op  = &ubifs_dir_inode_operations;
		inode->i_fop = &ubifs_dir_operations;
		inode->i_size = ui->ui_size = UBIFS_INO_NODE_SZ;
		break;
	case S_IFLNK:
		inode->i_op = &ubifs_symlink_inode_operations;
		break;
	case S_IFSOCK:
	case S_IFIFO:
	case S_IFBLK:
	case S_IFCHR:
		inode->i_op  = &ubifs_file_inode_operations;
		break;
	default:
		BUG();
	}

	ui->flags = inherit_flags(dir, mode);
	ubifs_set_inode_flags(inode);
	if (S_ISREG(mode))
		ui->compr_type = c->default_compr;
	else
		ui->compr_type = UBIFS_COMPR_NONE;
	ui->synced_i_size = 0;

	spin_lock(&c->cnt_lock);
	/* Inode number overflow is currently not supported */
	if (c->highest_inum >= INUM_WARN_WATERMARK) {
		if (c->highest_inum >= INUM_WATERMARK) {
			spin_unlock(&c->cnt_lock);
			ubifs_err("out of inode numbers");
			make_bad_inode(inode);
			iput(inode);
			return ERR_PTR(-EINVAL);
		}
		ubifs_warn("running out of inode numbers (current %lu, max %d)",
			   (unsigned long)c->highest_inum, INUM_WATERMARK);
	}

	inode->i_ino = ++c->highest_inum;
	ui->creat_sqnum = ++c->max_sqnum;
	spin_unlock(&c->cnt_lock);
	return inode;
}

初始化文件权限,所属用户,包括:inode->i_mode,inode->i_uid,inode->i_gid:ubifs_new_inode->inode_init_owner:

void inode_init_owner(struct inode *inode, const struct inode *dir,
			umode_t mode)
{
	/*
	 初始化 inode->i_uid,inode->i_gid为当前进程的fsuid
	如:打开文件的进程所属用户为uid=1001,gid=1001
	*/
	inode->i_uid = current_fsuid();
	if (dir && dir->i_mode & S_ISGID) {
		inode->i_gid = dir->i_gid;
		if (S_ISDIR(mode))
			mode |= S_ISGID;
	} else
		inode->i_gid = current_fsgid();
	/*
	初始化inode->i_mode为mode
	如:open时指定0777权限,则此处为100755
	*/
	inode->i_mode = mode;
}

真正打开文件之前对,文件权限的校验:may_open->inode_permission->generic_permission()->acl_permission_check()

static int acl_permission_check(struct inode *inode, int mask)
{
	unsigned int mode = inode->i_mode;
	/*
	作用:操作文件的进程和当前文件属于同一用户,mode>>6
	这是因为最高3位表示同一用户访问文件的权限。
	如ls /mnt/testdir/testfile -l 为0777 那么最高3位0700表示同一用户访问权限。
	中间的7表示,同组不同用户权限,最低位表示其他用户,即不同组,不同用户的权限
	所以其他用户时mode不用移位,即if和else两个分支都不走。
	*/

	if (likely(uid_eq(current_fsuid(), inode->i_uid)))
		mode >>= 6;
	else {
		/*
		操作文件的进程和当前文件不在同一用户,但在同一组。如0777中中间的7,所以mode>>3
		*/
		if (IS_POSIXACL(inode) && (mode & S_IRWXG)) {
			int error = check_acl(inode, mask);
			if (error != -EAGAIN)
				return error;
		}

		if (in_group_p(inode->i_gid))
			mode >>= 3;
	}

	/*
	 * If the DACs are ok we don't need any capability check.
	 */
	/*
	作用:把文件的打开方式与文件所属用户  具有的文件访问权限inode->i_mode做比较
	如果testfile文件所属用户、所属组、其他用户都是只读权限,即0444,
	 ls testfile -l :	--r--r--r--;
	那么任何用户:open("testfile",O_WRONLY)都会打开失败。
	这是因为flags=O_WRONLY,在build_open_flags中acc_mode=02|MAY_OPEN;
	inode->i_mode=0444对于任何用户,rwx中r位都是1,即bit2=1;
	(mask=acc_mode=02)&(~mode=03)&007  !=  0 所以不能访问。
	*/
	if ((mask & ~mode & (MAY_READ | MAY_WRITE | MAY_EXEC)) == 0)
		return 0;
	return -EACCES;
}

至此完成了文件访问权限的校验,前面的分析,其实都是为acl_permission_check()做铺垫,值得注意的时,超级用户权限不完全受acl_permission_check()检查的限制,如果它检查不过,会在acl_permission_check()之后进一步做其他检查,此处不再分析,感兴趣的同学可以看下.

通过上面分析,可以总结出open一个文件时,几个权限校验的关键节点:

第一步权限检查: 最开始对文件所在路径上每个目录项对应的inode进行执行权限检查.

对应代码:path_openat->link_path_walk->may_lookup->inode_permission;

第二步权限检查:如果是新建文件,对文件所在目录项的inode做写和可执行权限检查.

对应代码:path_openat->do_last->lookup_open->vfs_create->may_create->inode_permission.

第三步权限检查:真正打开文件之前,对文件所对应的inode做读写权限检查.

对应代码:path_openat->do_last->may_open->inode_permission.

可以在常见情况分析中看到,实际使用情况。

【常见情况分析】

1 /mnt/testdir属性为0666,属于root用户。即:ls /mnt -l :-drw-rw-rw root roottestdir

非root用户创建新文件:

操作:open("/mnt/testdir/testfile",O_CREATE|O_RDWR,S_IRWO);以读写方式打开文件,如果新建文件则inode属性设为其他用户可读写执行,即007;

结果:open失败;

原因分析:根据上面分析,在代码path_openat->link_path_walk->may_lookup->inode_permission中,即在权限校验的第一步中,会校验testdir的执行权限,此处校验失败;

2  /mnt/testdir属性为0661,属于root用户。即:ls /mnt -l :-drw-rw-x root root testdir

操作:open("/mnt/testdir/testfile",O_CREATE|O_RDWR,S_IRWXO);以读写方式打开文件,如果新建文件则inode属性设为其他用户可读写,即007;

结果:open失败;

原因分析:根据上面分析,在代码path_openat->do_last->lookup_open->vfs_create->may_create->inode_permission中,即在权限校验的第二步中,会校验testdir的写权限和执行权限,此处校验失败;

3  /mnt/testdir属性为0663,属于root用户。即:ls /mnt -l :-drw-rw-xw root root testdir

操作:open("/mnt/testdir/testfile",O_CREATE|O_RDWR,S_IRWO);以读写方式打开文件,如果新建文件则inode属性设为其他用户可读写,即007;

结果:open成功;

原因分析:根据上面分析,在代码path_openat->do_last->lookup_open->vfs_create->may_create->inode_permission中,即在权限校验的第二步中,会校验testdir的写权限和执行权限,此处testdir权限为0663,所以其他用户权限是003,可见是有写权限和执行权限的;但是在打开文件时需要检查读写权限(可以参考acc_mode的生成),本该打开失败,为什么还会成功呢?这是因为对于新建文件时,do_last中会将acc_mode=MAY_OPEN,所以只要能完成第二步权限检查,在第三步权限检查中就不再进行rwx权限检查了(可参考do_last实现)。

4 创建新文件权限为007,即具有rwx权限

open("/mnt/testdir/testfile",O_CREATE|O_RDWR,S_IRWO)

结果:创建出来的文件为005,即具有rx权限,这是因为创建文件时,权限都要与上~022,即非文件所有者用户不能写文件,可参考上文介绍。

【正文二:目录管理】

目录权限的管理虽然和文件权限的管理不是同一个软件流程,但管理方法大同小异,所有可以参考文件权限管理的流程,去参看目录权限管理.

目前权限管理的简单介绍:

创建目录时mkdir:

第一步权限检查: 最开始对目录所在路径上每个目录项对应的inode进行执行权限检查。

对应代码:path_lookupat->link_path_walk->may_lookup->inode_permission ;

第二步权限检查:目录所在目录项对应的inode做写和可执行权限检查。

对应代码:vfs_mkdir->may_create->inode_permission。

删除目录时rmdir:

第一步权限检查:path_lookupat->link_path_walk->may_lookup->inode_permission ;

第二步权限检查:vfs_rmdir->may_delete中检查目录所在目录项对应的inode的写和可执行权限。

针对几种情况做分析:

1 /mnt/testdir目录访问权限0666:

drw-rw-rw   2   root   root      testdir

场景:文件所属用户(root用户组 uid=0,gid=0)可读写;同组用户(uid!=0,gid=0)可读写、其他用户(uid!=0,gid!=0)可读写

操作:如果其他用户(如uid=1001,gid=1001)要在testdir目录下创建子目录:

结果:mkdir("/mnt/testdir/subdir",0666) 创建会失败;

原因:这是因为目录创建和文件创建一样,也会在may_create中对目录项对应的inode的写权限和可执行权限做检查。

 

2 /mnt/testdir目录访问权限0777:

drwx-rwx-rwx   2   root   root      testdir

1)如果其他用户(如uid=1001,gid=1001)要在testdir目录下创建子目录:mkdir("/mnt/testdir/subdir",0777);

drwx-rwx-rwx   2   1001   1001      subdir

rmdir("/mnt/testdir")成功;

rmdir("/mnt/testdir/subidr")成功;

【总结】

通过上面的分析,我们看到一个文件同时对应一个目录项dentry和一个inode,而且一个文件的目录项dentry->d_inode保存了这个文件对应的inode,所以如果查找一个文件对应的inode,要先查找这个文件对应的目录项dentry。系统为什么要这么做,为什么不直接用一个结构体表达文件,而要同时用dentry和inode表示文件。这是因为文件一般都存在于一个绝对路径之下,比如/mnt/testdir/testfile,一般来说系统里目录项dentry更倾向于表达一个文件整个路径上每个目录,如:mnt,testdir,testfile都对应一个目录项,虽然他们也同时对应inode,但是因为用户操作文件时通常只是想操作testfile对应的inode,所以inode更倾向于表示testfile的inode。不知道是否描述清楚,举个实际例子说明问题吧:

例子:用户操作文件/mnt/testdir/testfile,系统查找testfile对应inode的过程:

1 首先从根目录查起/,根目录“/“”对应一个目录项。

2 link_path_walk中遍历路径上的所有目录项,一直查到testdir对应的目录项,都可以查找到。此过程会对每个目录项对应的inode进行执行权限检查。

3 lookup_dcache中查找到了文件testfile对应的目录项,注意他是基于第2步中查找到的testdir对应的目录项开始查找的。

4 当查找testfile对应的目录项时,有两种情况:

一、文件已经存在,则查找到testfile对应目录项,接下来根据dentry->d_inode,又查找到了文件对应的inode,然后就可以对文件进行操作了。

二、文件不存在,如果时open函数,则可以创建testfile文件,创建文件testfile,首先创建testfile对应的目录项dentry(looup_open->lookup_dcache中完成),目录项创建成功后,再创建inode结点(lookup_open->vfs_create中完成),vfs_create过程会通过d_instantatiate将inode赋值给dentry->d_inode,所以第一种情况,可以根据先查找到的testfile对应的目录项dentry找到testfile对应的inode。创建inode过程会对文件所属目录项对应的inode,进行写权限和执行权限检查。

5 may_open真正打开文件之前会对文件读写权限进行检查。

打开文件时,系统对文件权限的检查,其实就发生在根据文件所属路径查找文件的过程中。

chmod(S_ISUID|SIRUSR);



[linux]进程(十一)——进程权能  : cap_effective

;