浅谈Linux权限管理
# 引言
本文将从linux内核源码的视角聊聊Linux操作系统如何权限校验的流程设计和位运算法落地,希望对你有所启发。
你好,我是 SharkChili ,禅与计算机程序设计艺术布道者,希望我的理念对您有所启发。
📝 我的公众号:写代码的SharkChili
在这里,我会分享技术干货、编程思考与开源项目实践。
🚀 我的开源项目:mini-redis
一个用于教学理解的 Redis 精简实现,欢迎 Star & Contribute:
https://github.com/shark-ctrl/mini-redis (opens new window)
👥 欢迎加入读者群
关注公众号,回复 【加群】 即可获取联系方式,期待与你交流技术、共同成长!
# 详解文件权限管理步骤
# sys_open内核调用
例如一个test用户现在要打开的info.log文件,对应文件基本信息和权限如下所示,可以看到对应所属用户、所属者和其他用户的权限分别是可读写、只读、无任何权限,同时在文件权限最后面有个+号,即存在一些特殊用户的acl权限配置:
-rw-r-----+ 1 sharkchili sharkchili 0 Dec 24 23:45 info.log
对应就会执行open函数以只读模式执行调用,对应代码层面对执行链路大体是通过用户态的open函数调用指明文件名和读写模式,然后触发内核调用执行sys_open函数其内部会携带用户需要打开的文件名和需要的文件访问模式经历重重调用最终走到do_last进行路径检查和权限校验等重要步骤:

基于上述的说明,我们给出一段用户层面的代码操作,如下所示,可以看到该文件最终会返回我们需要操作的文件句柄,然后进行读写操作:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
// 打开文件
int fd = open("info.log", O_RDONLY);
if (fd == -1) {
perror("open failed");
return -1;
}
// 创建缓冲区
char buffer[1024];
// 读取文件内容
ssize_t bytes_read = read(fd, buffer, sizeof(buffer)-1);
if (bytes_read == -1) {
perror("read failed");
close(fd);
return -1;
}
// 添加字符串结束符
buffer[bytes_read] = '\0';
printf("Read %zd bytes: %s\n", bytes_read, buffer);
close(fd);
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
此时open函数底层就会触发内核调用即sys_open函数调用,其内部通过do_sys_open这个函数真正进行文件打开的操作:
asmlinkage long sys_open(const char __user *filename, int flags, int mode)
{
long ret;
/*检查内核是不是支持大文件,如果是大文件的话就对flags标记对应的标记位置位*/
if (force_o_largefile())
flags |= O_LARGEFILE;
//实际实际文件打开的函数do_sys_open
ret = do_sys_open(AT_FDCWD, filename, flags, mode);
/* 禁止编译器尾部优化 */
prevent_tail_call(ret);
return ret;
}
2
3
4
5
6
7
8
9
10
11
12
这期间会经过重重函数调用,这里就不做展开,本质上就是携带文件名和需要的权限准备好,最终走到do_last函数,开始一些必要的权限校验的过程。
# 权限检查
权限校验的第一步也就是DAC校验,以我们open操作对应的用户为test为例,内核调用会触发generic_permission函数根据UGO权限,以我们的示例中的info的UGO权限信息为例,对应校验过程为:
- 验证是所属用户,若是则所有二进制右移6位返回移位后的
mode,例如我们的640二进制移位后就是6也就是(110 100 000全体右移6位得到110也就是6),基于6执行后续的权限校验步骤,查看当前文件属于sharkchili,具备读写权限,而本次打开文件的用户为test,并非所属用户触发所属组校验 - 特殊的ACL(access control list)访问权限列表校验,若发现ACL有配置test用户具备读写权限,DAC验证通过,直接结束,反之进入步骤3
- 若是所属组则移位3位将所属组的权限3bit移动到最低3位得到100执行权限校验,当前文件属于sharkchili用户组,当前用户在test组,并非所属组,触发other权限校验
- 当上述都不符合直接拿最低位的other权限判断当前用户是否具备权限,前文件属于other但是other没有任何权限,DAC验证失败

ACL是针对文件一些更细粒度的访问权限控制,例如本例我们的info.log就给test用户配置的特殊的读写权限,对此我们可通过getfacl info.log印证,输出结果如下,可以看到test用户有读写权限,DAC权限通过:
# file: info.log
# owner: sharkchili
# group: sharkchili
user::rw-
user:test:rw-
group::r--
mask::rw-
other::---
2
3
4
5
6
7
8
对应我们也给出这个DAC权限校验的源码acl_permission_check,可以看到步骤和笔者大体说的一致:
- 验证所属用户,获取对应权限,比较返回校验结果
- 验证ACL
- 验证所属组,获取对应权限,比较返回校验结果
- 获取other权限进行校验
static int acl_permission_check(struct inode *inode, int mask)
{
/* (1) 从i_mode中取出UGO规则 */
unsigned int mode = inode->i_mode;
/* (2) User用户取最高3bit规则,主体进程的euid等于客体文件的uid */
if (likely(uid_eq(current_fsuid(), inode->i_uid)))
mode >>= 6;
else {
/* (3) User用户匹配失败首先去匹配ACL规则 */
if (IS_POSIXACL(inode) && (mode & S_IRWXG)) {
int error = check_acl(inode, mask);
if (error != -EAGAIN)
return error;
}
/* (4) ACL匹配失败则尝试匹配Group用户规则,
Group用户取中间3bit规则,主体进程的egid等于客体文件的gid
*/
if (in_group_p(inode->i_gid))
mode >>= 3;
}
/* (5) 如果以上条件都未匹配成功,则为Other用户,取最低3bit规则 */
/*
* If the DACs are ok we don't need any capability check.
*/
/* (6) 使用规则允许的3bit和当前操作进行匹配,决定放行还是拒绝 */
if ((mask & ~mode & (MAY_READ | MAY_WRITE | MAY_EXEC)) == 0)
return 0;
return -EACCES;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# cgroup和selinux
明确上述ACL对应权限判定test具备读写权限后,最后在进行cgroup即硬件访问权限校验,和selinux(Security-Enhanced Linux一个限定服务进程最大限度减小系统服务进程可访问的资源)校验,只有着一切权限、安全规则校验都成功,自此,进程用户才能打开文件。
# 小结
本文结合linux内核源码和操作示例深入分析linux如何基于几个简单位运算和权限分组管理设计出简单实用的文件操作函数open,该设计无论从安全性亦或者性能表现上都有着非常不错的考量,也希望读者可以从这套深入底层的分析中得到一些启发。
你好,我是 SharkChili ,禅与计算机程序设计艺术布道者,希望我的理念对您有所启发。
📝 我的公众号:写代码的SharkChili
在这里,我会分享技术干货、编程思考与开源项目实践。
🚀 我的开源项目:mini-redis
一个用于教学理解的 Redis 精简实现,欢迎 Star & Contribute:
https://github.com/shark-ctrl/mini-redis (opens new window)
👥 欢迎加入读者群
关注公众号,回复 【加群】 即可获取联系方式,期待与你交流技术、共同成长!
# 参考
《趣话计算机底层技术》
open 系统调用实现:https://geekdaxue.co/read/linux-insides-zh/SysCall-linux-syscall-5.md (opens new window)
open系统调用源码解析:https://www.cnblogs.com/lizhaolong/p/16437218.html (opens new window)
open系统调用源码解析 :https://blog.csdn.net/renlonggg/article/details/80701949 (opens new window)
linux内核open过程的权限管理 :https://blog.csdn.net/eleven_xiy/article/details/70210828 (opens new window)
Linux DAC 权限管理详解 :https://www.cnblogs.com/pwl999/p/15534945.html (opens new window)
Linux ACL访问控制权限完全攻略(超详细) :https://blog.csdn.net/xiao_yi_xiao/article/details/117735278 (opens new window)
一文彻底明白linux中的selinux到底是什么 :https://blog.csdn.net/yanjun821126/article/details/80828908 (opens new window)