2. Linux内核权限模型基础
在驱动开发中,权限检查是安全的第一道防线。Linux内核的权限模型经历了从传统超级用户模型到细粒度capability机制,再到可插拔的LSM框架的演进。理解这三层结构,是编写安全驱动的基石。
2.1 传统进程权限模型:UID/GID与超级用户
Linux进程使用用户ID(UID)和组ID(GID)标识身份。内核通过比较进程的UID与目标资源的owner/group来决定是否允许操作。
| 权限位 | 含义 | 驱动检查示例 |
|---|---|---|
| owner (u) | 文件/设备所有者的权限 | current->cred->uid 与设备inode的i_uid比较 |
| group (g) | 文件所属组的权限 | in_group_p(inode->i_gid) 检查进程是否在目标组中 |
| other (o) | 其他用户的权限 | 通常用于开放设备节点 |
| root (UID 0) | 超级用户,绕过所有权限检查 | uid_eq(current->cred->uid, GLOBAL_ROOT_UID) |
传统模型的致命缺陷是“全有或全无”:一旦进程获得root权限,就可以执行任何操作。一个需要打开原始套接字的网络监控程序,如果以root运行,同时也拥有了修改系统时间、加载内核模块等本不需要的能力。
uid_eq(current->cred->uid, GLOBAL_ROOT_UID) 作为唯一权限检查。这会迫使合法程序以root运行,违背最小权限原则。应优先使用capability机制。
2.2 Capability机制:最小权限的工程实现
Capability机制将root的无限权力分割为独立的单元。每个单元代表一项特权操作。进程的cred结构体中包含一个capability位图,内核在检查权限时,不再问“你是不是root?”,而是问“你有没有这个capability?”
2.2.1 常用Capability与驱动场景
| Capability | 值 | 驱动相关场景 |
|---|---|---|
CAP_SYS_ADMIN |
21 | 执行特权ioctl、挂载文件系统、访问设备敏感寄存器 |
CAP_SYS_RAWIO |
17 | 操作I/O端口、访问/proc/ioports、配置DMA |
CAP_NET_ADMIN |
12 | 配置网络设备、设置防火墙规则、修改路由表 |
CAP_DAC_OVERRIDE |
1 | 绕过文件权限检查(读写任意文件) |
CAP_SYS_MODULE |
16 | 加载/卸载内核模块(驱动自身) |
2.2.2 驱动中的Capability检查API
在驱动代码中,使用 capable() 函数进行权限检查。该函数检查当前进程的effective capability集合中是否包含指定的capability。
#include <linux/security.h>
static long mydev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
switch (cmd) {
case MYDEV_SET_HW_CONFIG:
// 检查调用进程是否有CAP_SYS_ADMIN能力
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
// 执行硬件配置操作
break;
case MYDEV_READ_STATUS:
// 普通状态读取,不需要特殊权限
break;
default:
return -ENOTTY;
}
return 0;
}
capable() 而非 ns_capable() 时,注意命名空间隔离。在容器场景下,应使用 ns_capable(current_user_ns(), CAP_SYS_ADMIN) 以支持用户命名空间。
2.3 LSM框架:可插拔的安全策略引擎
LSM(Linux Security Module)是一个内核安全框架,它在关键内核操作路径上插入钩子(hooks),允许安全模块在这些钩子中实施自定义的访问控制策略。SELinux、AppArmor、Smack都是基于LSM实现的安全模块。
2.3.1 LSM钩子与驱动的关系
当驱动调用 capable() 时,LSM框架会介入。实际上,capable() 内部会调用 security_capable() 这个LSM钩子。如果系统启用了SELinux,SELinux模块会在这个钩子中执行额外的策略检查。
2.3.2 驱动开发需要关注的LSM钩子
虽然驱动开发者通常不需要直接调用LSM钩子,但理解以下钩子有助于调试权限问题:
| LSM钩子 | 触发时机 | 驱动影响 |
|---|---|---|
file_permission |
读写文件/设备节点前 | SELinux会检查设备节点的安全上下文 |
file_ioctl |
执行ioctl命令前 | SELinux可以根据命令码进行细粒度控制 |
mmap_file |
mmap设备内存前 | 控制用户态能否映射驱动内存区域 |
capable |
调用capable()时 | SELinux可以覆盖或增强capability检查 |
2.4 三层权限模型的协作关系
在驱动运行时,权限检查是分层进行的。理解这个协作关系,有助于定位“为什么我的驱动返回-EPERM?”这类问题。
- DAC检查(传统UID/GID): 内核VFS层首先检查进程是否有权打开设备节点(文件权限位)。
- Capability检查: 驱动代码中显式调用
capable(),检查进程是否拥有特定特权。 - LSM检查: 如果系统启用了SELinux等模块,LSM钩子会执行额外的安全策略。即使前两步通过,LSM仍可能拒绝操作。
1. 检查设备节点权限(ls -l /dev/mydev)
2. 检查驱动代码中capable()调用的参数是否正确
3. 查看SELinux审计日志(ausearch -m avc -ts recent)确认是否被策略阻止
2.5 本章小结
Linux内核权限模型为驱动开发提供了清晰的权限检查路径:
- 传统DAC模型提供基础的UID/GID检查,但粒度太粗。
- Capability机制将root权限细分为独立单元,驱动应使用
capable()进行最小权限检查。 - LSM框架在内核关键路径上插入钩子,允许SELinux等模块实施更强的安全策略。