一、打开权限
这里其实比较感兴趣的是文件夹的权限,假设对于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) (" 00 04 02 06"[(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的具体应用,在之后的描述中将会尝试逐步展开。