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机制。
传统权限模型 vs Capability 机制 传统模型:全有或全无 root 进程 普通进程 可以访问所有内核资源 受限访问 Capability 模型:细粒度授权 进程 A 进程 B CAP_NET_RAW CAP_SYS_TIME 仅拥有所需的最小能力集

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模块会在这个钩子中执行额外的策略检查。

LSM 钩子调用流程(以 capable() 为例) 驱动代码 调用 capable(CAP_SYS_ADMIN) 内核 capable() 函数 检查进程 capability 位图 LSM 钩子: security_capable() SELinux 策略检查 AppArmor 策略检查

2.3.2 驱动开发需要关注的LSM钩子

虽然驱动开发者通常不需要直接调用LSM钩子,但理解以下钩子有助于调试权限问题:

LSM钩子 触发时机 驱动影响
file_permission 读写文件/设备节点前 SELinux会检查设备节点的安全上下文
file_ioctl 执行ioctl命令前 SELinux可以根据命令码进行细粒度控制
mmap_file mmap设备内存前 控制用户态能否映射驱动内存区域
capable 调用capable()时 SELinux可以覆盖或增强capability检查

2.4 三层权限模型的协作关系

在驱动运行时,权限检查是分层进行的。理解这个协作关系,有助于定位“为什么我的驱动返回-EPERM?”这类问题。

  1. DAC检查(传统UID/GID): 内核VFS层首先检查进程是否有权打开设备节点(文件权限位)。
  2. Capability检查: 驱动代码中显式调用 capable(),检查进程是否拥有特定特权。
  3. LSM检查: 如果系统启用了SELinux等模块,LSM钩子会执行额外的安全策略。即使前两步通过,LSM仍可能拒绝操作。
调试技巧: 当驱动ioctl返回-EPERM时,按以下顺序排查:
1. 检查设备节点权限(ls -l /dev/mydev)
2. 检查驱动代码中capable()调用的参数是否正确
3. 查看SELinux审计日志(ausearch -m avc -ts recent)确认是否被策略阻止
驱动权限检查三层协作流程 用户态进程 第1层:DAC检查(文件权限位) 第2层:Capability检查(capable()) 第3层:LSM检查(SELinux/AppArmor)

2.5 本章小结

Linux内核权限模型为驱动开发提供了清晰的权限检查路径:

  • 传统DAC模型提供基础的UID/GID检查,但粒度太粗。
  • Capability机制将root权限细分为独立单元,驱动应使用 capable() 进行最小权限检查。
  • LSM框架在内核关键路径上插入钩子,允许SELinux等模块实施更强的安全策略。