【摘要】
【正文一:文件管理】
【常见情况分析】
【正文二:目录管理】
【总结】
注意:请使用谷歌浏览器阅读(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);
6 [linux]进程(十一)——进程权能 : cap_effective