http://tsecer.blog.163.com/blog/static/150181720121159527348/


Linux权限管理(一)—打开权限  

2012-02-15 21:52:46|  分类: Linux内核|字号 订阅

一、打开权限
这里其实比较感兴趣的是文件夹的权限,假设对于root用户的一个文件夹,或者另一个不允许其它用户访问的文件夹,如果用户访问这个路径是否可以访问文件夹下的文件?
简单的模型是这样的
[root@Harry ~]# ll /
drwxr-x--x.   2 root root  4096 2012-02-14 21:32 priotest
[root@Harry ~]# ll /priotest/
total 4
-rwx--xr-x. 1 root root 12 2012-02-14 21:34 tsecer.txt
这里的情况是文件夹/priotest文件夹对其他用户设置的是执行权限而没有打开权限,但是该文件夹下的实体文件却是有读出权限的,那么此时普通用户是否能够读取该文件的内容呢?那么此时普通用户是否可以查看该文件的内容呢?
二、内核实现
我们看一下打开文件内核的目录打开执行的流程中权限的检测为
do_path_lookup
使用的权限判断为
        retval = file_permission(file,  MAY_EXEC);
__link_path_walk中判断为
exec_permission_lite(inode, nd)
也就是文件夹打开的权限判断都是使用了文件夹的EXEC权限而不是打开权限。事实上,切换到一个普通用户,然后使用cat命令同样可以查看这个文件的内容:
[tsecer@Harry priotest]$ cat /priotest/tsecer.txt
hello world
tsecer
三、文件夹的打开权限是干什么的
这个输出是我在机器上执行strace命令的一些输出
[tsecer@Harry priotest]$ strace ls /
……
open("/", O_RDONLY|O_NONBLOCK|O_LARGEFILE|O_DIRECTORY|O_CLOEXEC) = 3
fcntl64(3, F_GETFD)                     = 0x1 (flags FD_CLOEXEC)
getdents64(3, /* 26 entries */, 32768)  = 696
getdents64(3, /* 0 entries */, 32768)   = 0
这里可以看到,其中对于根文件夹"/"是直接打开,然后读出目录下的所有文件内容的,所以对于文件夹的读权限和普通文件的读权限是一样的,最后就是文件的读权限判断在open_namei函数的最后,其执行的判断为
error = may_open(nd, acc_mode, flag);
这里判断的就是文件的打开权限判断,此处的判断最终是通过
vfs_permission--->>>permission--->>generic_permission
if (current->fsuid == inode->i_uid) 
        mode >>= 6; 对于一个文件的访问权限设置,最高最高3bits为用户所有者权限,所以如果fsud和文件创建者id相同,则逻辑右移6bits
    else {
        if (IS_POSIXACL(inode) && (mode & S_IRWXG) && check_acl) {
            int error = check_acl(inode, mask);
            if (error == -EACCES)
                goto check_capabilities;
            else if (error != -EAGAIN)
                return error;
        }

        if (in_group_p(inode->i_gid))  如果当前进程和文件在同一用户组中,则右移3bits
            mode >>= 3;
    }  其它的使用的就是mode的最后3bits,作为其它用户的访问权限设置

    /*
     * If the DACs are ok we don't need any capability check.
     */
    if (((mode & mask & (MAY_READ|MAY_WRITE|MAY_EXEC)) == mask)) 这里就是用文件的权限控制列表和对该文件要求的权限判断,如果满足,则返回零,表示进程可以打开该文件
        return 0;
关于这个9bits控制模式,可以看一下chmod命令的说明,这里就不废话了。
这也就是说,如果一个用户对文件夹没有打开权限,那么它不能通过ls来查看该文件夹下文件内容,进一步的一个简单推论就是在bash中,不同通过tab来自动补全文件夹下文件。
四、超级用户如何超越这些判断
超级用户可以绕过这些控制,假设一个极端的情况,一个超级用户自毁长城,将一个自己创建的文件访问属性设置为000,也就是任何人没有任何权限,那么这个文件是不是在系统中就长生不老了?简单测试了一下,不是。超级用户是如何超越这个检测的?
其实同样是在generic_permission函数中实现的,上面的判断并不是文件的全部,而只是一部分,即使上面的
    if (((mode & mask & (MAY_READ|MAY_WRITE|MAY_EXEC)) == mask))
  不满足,还有绿色通道,接下来的就是对权限(能力)的判断,这相当于是root用户的后门:
 check_capabilities:
    /*
     * Read/write DACs are always overridable.
     * Executable DACs are overridable if at least one exec bit is set.
     */
    if (!(mask & MAY_EXEC) || 对于文件夹的打开,这里的第一个逻辑或就已经满足,这是通过文件夹权限检测的步骤。
        (inode->i_mode & S_IXUGO) || S_ISDIR(inode->i_mode))
        if (capable(CAP_DAC_OVERRIDE))
            return 0;

    /*
     * Searching includes executable on directories, else just read.
     */
    if (mask == MAY_READ || (S_ISDIR(inode->i_mode) && !(mask & MAY_WRITE)))这里对读权限的超越
        if (capable(CAP_DAC_READ_SEARCH))
            return 0;
对于root用户,这些权限都是满足的:
[root@Harry priotest]# cat /proc/self/status 
Name:    cat
State:    R (running)
……
CapInh:    0000000000000000
CapPrm:    ffffffffffffffff
CapEff:    ffffffffffffffff
CapBnd:    ffffffffffffffff
可以看到,root用户具有所有的权限,其中的capable是使用了其中的CapEff列来判断权限的。
五、open打开权限到ACCESS MODE的转换
这是一个小问题,因为当用户态调用open的时候,如果是要求可读写状态打开一个文件,传入的参数是O_RDWR,但是在内核里搜索了这个字符串,没有发现对这个属性进行判断的地方。借助调试器,才看到这个转换是在一个不起眼并且比较诡异的方式实现的,在
static struct file *do_filp_open(int dfd, const char *filename, int flags,
                 int mode)
    if ((namei_flags+1) & O_ACCMODE)
        namei_flags++;
然后在
int open_namei(int dfd, const char *pathname, int flag,
        int mode, struct nameidata *nd)
    acc_mode = ACC_MODE(flag);
其中
#define ACC_MODE(x) ("\000\004\002\006"[(x)&O_ACCMODE])
#define O_ACCMODE    00000003
假设传入的属性为
#define O_RDWR        00000002
那么它将会在do_filp_open中转换为3,所以在接下来的ACC_MODE转换中转换为006,也就是3bits访问控制权限中的110,所以这个权限就要求文件有读、写(没有要求执行权限)。
六、一个细节
在generic_permission函数中,文件的访问使用的都是进程的 fsuid 属性,而一个进程描述符中关于uid,有下面一些定义
    uid_t uid,euid,suid,fsuid;
这些id的具体应用,在之后的描述中将会尝试逐步展开。

Logo

更多推荐