Linux中系统调用sys_access函数的实现
Linux 权限检查sys_access函数概览
核心组件层次结构
用户空间系统调用↓
sys_access() / 其他文件操作↓
__user_walk() → 用户/内核空间路径名传递↓
path_lookup() → 路径解析起点↓
link_path_walk() → 路径组件遍历├── do_lookup() → 目录项查找├── follow_dotdot() → 父目录处理├── follow_mount() → 挂载点处理└── do_follow_link() → 符号链接解析↓
permission() → 权限验证├── 文件系统特定检查├── generic_permission() → 通用检查└── LSM安全模块检查
权限检查分层模型
传统DAC检查 (Unix权限位)↓
POSIX ACL扩展检查 ↓
能力机制覆盖 (Capabilities)↓
LSM强制访问控制 (SELinux/AppArmor)
安全体系设计
身份与权限管理
// access()系统调用的真实身份检查
current->fsuid = current->uid; // 使用真实UID
current->fsgid = current->gid; // 使用真实GID// 能力集调整
if (current->uid)cap_clear(current->cap_effective); // 非root用户清空能力
能力机制精细化控制
// CAP_DAC_OVERRIDE - 完全权限覆盖
if (capable(CAP_DAC_OVERRIDE))return 0;// CAP_DAC_READ_SEARCH - 只读权限覆盖
if (mask == MAY_READ || (S_ISDIR(inode->i_mode) && !(mask & MAY_WRITE)))if (capable(CAP_DAC_READ_SEARCH))return 0;
只有root用户才能有能力覆盖的可能,普通用户会清空能力集
检查当前进程是否有权按指定模式访问文件sys_access
asmlinkage long sys_access(const char __user * filename, int mode)
{struct nameidata nd;int old_fsuid, old_fsgid;kernel_cap_t old_cap;int res;if (mode & ~S_IRWXO) /* where's F_OK, X_OK, W_OK, R_OK? */return -EINVAL;old_fsuid = current->fsuid;old_fsgid = current->fsgid;old_cap = current->cap_effective;current->fsuid = current->uid;current->fsgid = current->gid;/** Clear the capabilities if we switch to a non-root user** FIXME: There is a race here against sys_capset. The* capabilities can change yet we will restore the old* value below. We should hold task_capabilities_lock,* but we cannot because user_path_walk can sleep.*/if (current->uid)cap_clear(current->cap_effective);elsecurrent->cap_effective = current->cap_permitted;res = __user_walk(filename, LOOKUP_FOLLOW|LOOKUP_ACCESS, &nd);if (!res) {res = permission(nd.dentry->d_inode, mode, &nd);/* SuS v2 requires we report a read only fs too */if(!res && (mode & S_IWOTH) && IS_RDONLY(nd.dentry->d_inode)&& !special_file(nd.dentry->d_inode->i_mode))res = -EROFS;path_release(&nd);}current->fsuid = old_fsuid;current->fsgid = old_fsgid;current->cap_effective = old_cap;return res;
}
函数功能概述
sys_access 函数检查当前进程是否有权按指定模式访问文件,用于实现POSIX标准的access()系统调用
代码详细解析
asmlinkage long sys_access(const char __user * filename, int mode)
{
函数声明:
asmlinkage: 表示函数参数通过栈传递,用于系统调用long: 返回类型,系统调用通常返回longsys_access: 系统调用函数名const char __user * filename: 用户空间文件名指针int mode: 访问模式标志
变量定义
struct nameidata nd;int old_fsuid, old_fsgid;kernel_cap_t old_cap;int res;
struct nameidata nd: 用于存储路径查找结果的数据结构int old_fsuid, old_fsgid: 保存原来的文件系统用户ID和组IDkernel_cap_t old_cap: 保存原来的有效能力集int res: 操作结果返回值
模式参数验证
if (mode & ~S_IRWXO) /* where's F_OK, X_OK, W_OK, R_OK? */return -EINVAL;
if (mode & ~S_IRWXO): 检查mode参数是否包含非法标志S_IRWXO: 其他用户的读、写、执行权限位掩码(0007)~S_IRWXO: 取反后得到所有非法位的掩码return -EINVAL: 如果包含非法标志,返回无效参数错误
保存原始安全上下文
old_fsuid = current->fsuid;old_fsgid = current->fsgid;old_cap = current->cap_effective;
current->fsuid: 当前进程的文件系统用户IDcurrent->fsgid: 当前进程的文件系统组IDcurrent->cap_effective: 当前进程的有效能力集- 作用: 保存当前安全状态,以便后续恢复
设置真实身份
current->fsuid = current->uid;current->fsgid = current->gid;
current->fsuid = current->uid: 将文件系统UID设置为真实UIDcurrent->fsgid = current->gid: 将文件系统GID设置为真实GID- access系统调用语义: 使用进程的真实用户ID进行检查
能力集调整
/** Clear the capabilities if we switch to a non-root user** FIXME: There is a race here against sys_capset. The* capabilities can change yet we will restore the old* value below. We should hold task_capabilities_lock,* but we cannot because user_path_walk can sleep.*/if (current->uid)cap_clear(current->cap_effective);elsecurrent->cap_effective = current->cap_permitted;
-
非root用户处理
if (current->uid): 如果当前用户不是root(uid != 0)cap_clear(current->cap_effective): 清空有效能力集
-
root用户处理:
else: 如果是root用户current->cap_effective = current->cap_permitted: 设置有效能力集为允许的能力集
路径查找
res = __user_walk(filename, LOOKUP_FOLLOW|LOOKUP_ACCESS, &nd);
__user_walk(): 从用户空间文件名解析到内核文件对象filename: 用户空间文件名指针LOOKUP_FOLLOW: 跟随符号链接LOOKUP_ACCESS: 为权限检查进行路径查找&nd: 存储查找结果的nameidata结构res: 返回查找结果,0表示成功
权限检查
if (!res) {res = permission(nd.dentry->d_inode, mode, &nd);
if (!res): 如果路径查找成功permission(nd.dentry->d_inode, mode, &nd): 检查文件访问权限nd.dentry->d_inode: 文件的inode对象mode: 请求的访问模式&nd: 路径查找上下文
只读文件系统检查
/* SuS v2 requires we report a read only fs too */if(!res && (mode & S_IWOTH) && IS_RDONLY(nd.dentry->d_inode)&& !special_file(nd.dentry->d_inode->i_mode))res = -EROFS;
- 条件检查:
!res: 之前的权限检查通过(mode & S_IWOTH): 请求写权限(其他用户写权限位)IS_RDONLY(nd.dentry->d_inode): 文件所在文件系统是只读的!special_file(...): 不是特殊文件(设备文件等)
res = -EROFS: 如果满足条件,返回只读文件系统错误
释放路径资源
path_release(&nd);}
path_release(&nd): 释放路径查找过程中获取的引用和锁- 无论权限检查结果如何,都需要释放资源
恢复原始上下文
current->fsuid = old_fsuid;current->fsgid = old_fsgid;current->cap_effective = old_cap;
- 恢复原来的文件系统用户ID
- 恢复原来的文件系统组ID
- 恢复原来的有效能力集
- 重要性: 确保系统调用不会永久改变进程的安全状态
返回结果
return res;
}
res: 包含整个操作的最终结果- 成功返回0,失败返回负的错误代码
函数功能总结
主要功能:检查真实用户对指定文件的访问权限
核心特性:
- 真实身份检查 - 使用真实用户ID而非有效用户ID
- 完整权限验证 - 考虑文件权限、文件系统状态、能力集
- 资源安全管理 - 正确保存和恢复进程安全上下文
- 错误全面处理 - 处理各种边界条件和错误情况
安全设计:
-
凭据隔离 - 临时修改凭据并在完成后恢复
-
能力控制 - 根据用户身份调整能力集
-
资源清理 - 确保路径查找资源正确释放
-
真实用户ID:表明"你是谁" - 进程的真实所有者
-
有效用户ID:决定"你能做什么" - 实际的权限级别,用于临时提权
解析用户空间提供的路径名__user_walk
int fastcall __user_walk(const char __user *name, unsigned flags, struct nameidata *nd)
{char *tmp = getname(name);int err = PTR_ERR(tmp);if (!IS_ERR(tmp)) {err = path_lookup(tmp, flags, nd);putname(tmp);}return err;
}
int fastcall path_lookup(const char *name, unsigned int flags, struct nameidata *nd)
{int retval;nd->last_type = LAST_ROOT; /* if there are only slashes... */nd->flags = flags;nd->depth = 0;read_lock(¤t->fs->lock);if (*name=='/') {if (current->fs->altroot && !(nd->flags & LOOKUP_NOALT)) {nd->mnt = mntget(current->fs->altrootmnt);nd->dentry = dget(current->fs->altroot);read_unlock(¤t->fs->lock);if (__emul_lookup_dentry(name,nd))return 0;read_lock(¤t->fs->lock);}nd->mnt = mntget(current->fs->rootmnt);nd->dentry = dget(current->fs->root);} else {nd->mnt = mntget(current->fs->pwdmnt);nd->dentry = dget(current->fs->pwd);}read_unlock(¤t->fs->lock);current->total_link_count = 0;retval = link_path_walk(name, nd);if (unlikely(current->audit_context&& nd && nd->dentry && nd->dentry->d_inode))audit_inode(name,nd->dentry->d_inode->i_ino,nd->dentry->d_inode->i_rdev);return retval;
}
函数功能概述
这两个函数协同工作,用于解析用户空间提供的路径名,将其转换为内核中的目录项(dentry)和挂载点(mnt)结构,是Linux内核VFS层路径解析的核心组件
代码详细解释
第一部分:__user_walk函数
int fastcall __user_walk(const char __user *name, unsigned flags, struct nameidata *nd)
{char *tmp = getname(name);int err = PTR_ERR(tmp);
fastcall:编译器优化,表示使用寄存器传递参数__user *name:指向用户空间的路径名字符串getname(name):关键函数,从用户空间复制路径名到内核空间PTR_ERR(tmp):将指针转换为错误码,如果getname失败会返回错误指针
if (!IS_ERR(tmp)) {err = path_lookup(tmp, flags, nd);putname(tmp);}return err;
IS_ERR(tmp):检查getname是否成功(返回的不是错误指针)- 如果成功:调用
path_lookup进行实际的路径查找 putname(tmp):释放getname分配的内核空间内存- 返回错误码或查找结果
第二部分:path_lookup函数 - 初始化
int fastcall path_lookup(const char *name, unsigned int flags, struct nameidata *nd)
{int retval;nd->last_type = LAST_ROOT; /* if there are only slashes... */nd->flags = flags;nd->depth = 0;
nameidata *nd:输出参数,存储查找结果(dentry,mnt等)last_type = LAST_ROOT:初始化最后组件类型为根目录flags:查找标志(如LOOKUP_FOLLOW等)depth = 0:符号链接深度计数器,防止无限递归
第三部分:路径起点确定
read_lock(¤t->fs->lock);if (*name=='/') {if (current->fs->altroot && !(nd->flags & LOOKUP_NOALT)) {nd->mnt = mntget(current->fs->altrootmnt);nd->dentry = dget(current->fs->altroot);read_unlock(¤t->fs->lock);if (__emul_lookup_dentry(name,nd))return 0;read_lock(¤t->fs->lock);}nd->mnt = mntget(current->fs->rootmnt);nd->dentry = dget(current->fs->root);} else {nd->mnt = mntget(current->fs->pwdmnt);nd->dentry = dget(current->fs->pwd);}read_unlock(¤t->fs->lock);
-
读锁保护:
read_lock(¤t->fs->lock);- 保护进程的文件系统状态(current->fs)不被并发修改
-
绝对路径处理 (
/开头):if (*name=='/') {- 检查是否是绝对路径
-
替代根目录检查:
if (current->fs->altroot && !(nd->flags & LOOKUP_NOALT)) {altroot:chroot环境的替代根目录LOOKUP_NOALT:标志位,表示不使用替代根目录
-
设置替代根目录:
nd->mnt = mntget(current->fs->altrootmnt); nd->dentry = dget(current->fs->altroot);mntget():增加挂载点的引用计数dget():增加目录项的引用计数
-
尝试在替代根中查找:
read_unlock(¤t->fs->lock); if (__emul_lookup_dentry(name,nd))return 0; read_lock(¤t->fs->lock);- 临时释放锁(
__emul_lookup_dentry可能需要睡眠) - 如果找到则直接返回成功
- 重新获取锁继续处理
- 临时释放锁(
-
设置正常根目录:
nd->mnt = mntget(current->fs->rootmnt); nd->dentry = dget(current->fs->root);- 使用进程的根目录"/"
-
相对路径处理:
} else {nd->mnt = mntget(current->fs->pwdmnt);nd->dentry = dget(current->fs->pwd); }- 使用进程的当前工作目录
-
释放读锁:
read_unlock(¤t->fs->lock);
第四部分:路径遍历和审计
current->total_link_count = 0;retval = link_path_walk(name, nd);
total_link_count = 0:重置符号链接计数器link_path_walk(name, nd):实际遍历路径的每个组件
if (unlikely(current->audit_context&& nd && nd->dentry && nd->dentry->d_inode))audit_inode(name,nd->dentry->d_inode->i_ino,nd->dentry->d_inode->i_rdev);return retval;
unlikely():编译器优化,提示条件 unlikely 成立- 审计检查:如果启用了内核审计且找到了有效的
inode,记录审计信息 audit_inode():记录inode访问审计信息(inode号、设备号)- 返回路径查找的结果
函数功能总结
- 用户空间接口:
__user_walk处理用户空间到内核空间的数据传输 - 安全复制:使用
getname/putname安全地复制用户路径 - 路径起点解析:根据绝对/相对路径确定查找起点
- chroot支持:处理替代根目录情况
- 引用计数管理:正确管理
dentry和vfsmount的引用计数 - 并发保护:使用读写锁保护进程文件系统状态
- 路径遍历:委托给
link_path_walk进行组件级遍历 - 审计支持:集成内核审计功能
在替代根目录中进行路径查找__emul_lookup_dentry
static int __emul_lookup_dentry(const char *name, struct nameidata *nd)
{if (path_walk(name, nd))return 0; /* something went wrong... */if (!nd->dentry->d_inode || S_ISDIR(nd->dentry->d_inode->i_mode)) {struct dentry *old_dentry = nd->dentry;struct vfsmount *old_mnt = nd->mnt;struct qstr last = nd->last;int last_type = nd->last_type;/** NAME was not found in alternate root or it's a directory. Try to find* it in the normal root:*/nd->last_type = LAST_ROOT;read_lock(¤t->fs->lock);nd->mnt = mntget(current->fs->rootmnt);nd->dentry = dget(current->fs->root);read_unlock(¤t->fs->lock);if (path_walk(name, nd) == 0) {if (nd->dentry->d_inode) {dput(old_dentry);mntput(old_mnt);return 1;}path_release(nd);}nd->dentry = old_dentry;nd->mnt = old_mnt;nd->last = last;nd->last_type = last_type;}return 1;
}
函数功能概述
这个函数用于在chroot环境中进行路径查找,当在替代根目录中找不到目标文件时,自动回退到正常根目录中继续查找,主要用于支持某些特殊系统调用
代码详细解释
第一部分:在替代根目录中查找
static int __emul_lookup_dentry(const char *name, struct nameidata *nd)
{if (path_walk(name, nd))return 0; /* something went wrong... */
- 函数参数:
name: 要查找的路径名nd: 已经指向替代根目录的nameidata结构
- 在替代根目录中查找:
path_walk(name, nd)- 在当前的替代根目录环境中解析路径
- 错误处理:如果查找失败(返回非零),直接返回0表示失败,查找成功则继续执行
第二部分:检查查找结果
if (!nd->dentry->d_inode || S_ISDIR(nd->dentry->d_inode->i_mode)) {
- 结果检查条件:
!nd->dentry->d_inode: 找到的目录项没有对应的inodeS_ISDIR(nd->dentry->d_inode->i_mode): 找到的是目录
- 逻辑含义:如果没有找到文件或者找到的是目录,需要尝试在正常根目录中查找
struct dentry *old_dentry = nd->dentry;struct vfsmount *old_mnt = nd->mnt;struct qstr last = nd->last;int last_type = nd->last_type;
- 保存当前状态:
old_dentry: 保存替代根目录中找到的目录项old_mnt: 保存替代根目录的挂载点last: 保存最后一个路径组件信息last_type: 保存最后一个组件类型
- 目的:为了在回退查找失败时能够恢复状态
第三部分:切换到正常根目录
/** NAME was not found in alternate root or it's a directory. Try to find* it in the normal root:*/nd->last_type = LAST_ROOT;read_lock(¤t->fs->lock);nd->mnt = mntget(current->fs->rootmnt);nd->dentry = dget(current->fs->root);read_unlock(¤t->fs->lock);
- 重置查找状态:
nd->last_type = LAST_ROOT: 标记为从根目录开始
- 切换到正常根目录:
- 获取读锁保护
current->fs结构 nd->mnt = mntget(current->fs->rootmnt): 设置正常根目录挂载点,增加引用计数nd->dentry = dget(current->fs->root): 设置正常根目录dentry,增加引用计数- 释放读锁
- 获取读锁保护
第四部分:在正常根目录中查找
if (path_walk(name, nd) == 0) {if (nd->dentry->d_inode) {dput(old_dentry);mntput(old_mnt);return 1;}path_release(nd);}
-
在正常根目录中查找:
path_walk(name, nd) == 0- 在正常根目录环境中解析相同路径
- 如果返回0表示查找成功(没有错误)
-
成功找到非目录文件:
if (nd->dentry->d_inode) {dput(old_dentry);mntput(old_mnt);return 1; }- 检查找到的目录项有对应的
inode(不是目录) - 释放之前替代根目录的资源:
dput(old_dentry): 减少替代根dentry的引用计数mntput(old_mnt): 减少替代根挂载点的引用计数
- 返回1表示成功(在正常根目录中找到)
- 检查找到的目录项有对应的
-
查找失败处理:
path_release(nd);- 如果正常根目录查找失败或者找到的是目录,释放当前查找资源
第五部分:恢复状态和返回
nd->dentry = old_dentry;nd->mnt = old_mnt;nd->last = last;nd->last_type = last_type;}return 1;
}
- 恢复替代根目录状态:
- 将
nameidata恢复为替代根目录的查找结果 - 恢复
dentry、mnt、last、last_type等所有状态
- 将
- 直接使用替代根查找的结果
函数功能总结
-
chroot环境支持:专门为替代根目录环境设计的查找函数
-
智能回退机制:在替代根中找不到时自动尝试正常根目录
-
状态保存恢复:完整保存和恢复查找状态,确保操作原子性
-
资源管理:正确管理
dentry和vfsmount的引用计数 -
并发保护:使用读锁保护进程文件系统状态
-
目录特殊处理:对目录和文件采用不同的查找策略
权限检查函数permission
int permission(struct inode * inode,int mask, struct nameidata *nd)
{int retval;int submask;/* Ordinary permission routines do not understand MAY_APPEND. */submask = mask & ~MAY_APPEND;if (inode->i_op && inode->i_op->permission)retval = inode->i_op->permission(inode, submask, nd);elseretval = generic_permission(inode, submask, NULL);if (retval)return retval;return security_inode_permission(inode, mask, nd);
}
int generic_permission(struct inode *inode, int mask,int (*check_acl)(struct inode *inode, int mask))
{umode_t mode = inode->i_mode;if (mask & MAY_WRITE) {/** Nobody gets write access to a read-only fs.*/if (IS_RDONLY(inode) &&(S_ISREG(mode) || S_ISDIR(mode) || S_ISLNK(mode)))return -EROFS;/** Nobody gets write access to an immutable file.*/if (IS_IMMUTABLE(inode))return -EACCES;}if (current->fsuid == inode->i_uid)mode >>= 6;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))mode >>= 3;}/** If the DACs are ok we don't need any capability check.*/if (((mode & mask & (MAY_READ|MAY_WRITE|MAY_EXEC)) == mask))return 0;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;return -EACCES;
}
函数功能概述
这两个函数协同工作,实现了Linux内核完整的文件权限检查机制,包括传统Unix权限、POSIX ACL、文件属性检查和能力(capability)覆盖机制
代码详细解释
第一部分:permission函数 - 入口处理
int permission(struct inode * inode,int mask, struct nameidata *nd)
{int retval;int submask;/* Ordinary permission routines do not understand MAY_APPEND. */submask = mask & ~MAY_APPEND;
- 函数参数:
inode: 要检查权限的文件inodemask: 请求的权限掩码(MAY_READ, MAY_WRITE, MAY_EXEC, MAY_APPEND)nd: 路径查找数据
- MAY_APPEND处理:
submask = mask & ~MAY_APPEND- 传统Unix权限检查没有
MAY_APPEND标志,只进行MAY_READ, MAY_WRITE, MAY_EXEC检查 - 避免将append权限错误地映射为write权限
- 有些文件系统也不支持append-only语义
- 先过滤掉这个标志,后续检查使用
submask
- 传统Unix权限检查没有
if (inode->i_op && inode->i_op->permission)retval = inode->i_op->permission(inode, submask, nd);elseretval = generic_permission(inode, submask, NULL);
- 文件系统特定检查:
- 如果
inode操作集存在且有permission方法,优先调用文件系统特定的权限检查 - 否则回退到通用权限检查
generic_permission
- 如果
if (retval)return retval;return security_inode_permission(inode, mask, nd);
- 错误处理:如果前面的检查返回非零(错误),直接返回
- LSM安全钩子:调用Linux安全模块的权限检查
- 支持
SELinux、AppArmor等安全模块 - 使用原始的
mask(包含MAY_APPEND)
- 支持
第二部分:generic_permission - 基础属性检查
int generic_permission(struct inode *inode, int mask,int (*check_acl)(struct inode *inode, int mask))
{umode_t mode = inode->i_mode;
- 函数参数:
inode: 文件inodemask: 请求的权限(已过滤MAY_APPEND)
- 获取文件模式:
mode = inode->i_mode- 文件的权限和类型信息
if (mask & MAY_WRITE) {/** Nobody gets write access to a read-only fs.*/if (IS_RDONLY(inode) &&(S_ISREG(mode) || S_ISDIR(mode) || S_ISLNK(mode)))return -EROFS;
- 写权限特殊检查:如果请求包含写权限
- 只读文件系统检查:
IS_RDONLY(inode): 检查文件系统是否只读- 影响普通文件(
S_ISREG)、目录(S_ISDIR)、符号链接(S_ISLNK) - 如果只读,返回
-EROFS(Read-only file system)
/** Nobody gets write access to an immutable file.*/if (IS_IMMUTABLE(inode))return -EACCES;}
- 不可变文件检查:
IS_IMMUTABLE(inode): 检查文件是否被标记为不可变(immutable)- 如果不可变,返回
-EACCES(Permission denied)
第三部分:用户身份和权限位选择
if (current->fsuid == inode->i_uid)mode >>= 6;
- 文件所有者检查:
current->fsuid == inode->i_uid: 当前进程的文件系统UID等于文件所有者UIDmode >>= 6: 将权限位右移6位,从rwxrwxrwx变为只看前3位rwx(所有者权限)
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;}
- 非文件所有者路径:
- POSIX ACL检查:
IS_POSIXACL(inode): 文件系统支持ACL(mode & S_IRWXG): 文件有组权限位设置check_acl: 提供了ACL检查函数
- ACL结果处理:
-EACCES: ACL拒绝访问,跳转到能力检查- 其他错误(非
-EAGAIN):直接返回 -EAGAIN: 继续后续的传统权限检查
- POSIX ACL检查:
if (in_group_p(inode->i_gid))mode >>= 3;}
- 组用户检查:
in_group_p(inode->i_gid): 当前进程在文件所属组中mode >>= 3: 右移3位,查看中间3位组权限
- 其他用户:如果既不是所有者也不在组中,使用原始mode的最后3位(其他用户权限)
第四部分:传统DAC权限验证
/** If the DACs are ok we don't need any capability check.*/if (((mode & mask & (MAY_READ|MAY_WRITE|MAY_EXEC)) == mask))return 0;
- 传统DAC权限检查:
mode & mask & (MAY_READ|MAY_WRITE|MAY_EXEC): 实际权限与请求权限的交集- 过滤掉非基本权限标志,只比较READ/WRITE/EXEC
- 如果等于请求的mask,说明传统Unix权限足够,返回0(成功)
第五部分:能力机制覆盖检查
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;
- CAP_DAC_OVERRIDE检查:
- 条件1:不需要执行权限(
!(mask & MAY_EXEC)) - 条件2:文件有任何执行位设置(
inode->i_mode & S_IXUGO)- 用户、组或其他用户的执行位 - 条件3:是目录(
S_ISDIR(inode->i_mode)) capable(CAP_DAC_OVERRIDE): 检查进程是否有忽略DAC限制的能力- 如果条件满足且有该能力,返回0(成功)
- 条件1:不需要执行权限(
/** 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;
- CAP_DAC_READ_SEARCH检查:
- 条件1:只需要读权限(
mask == MAY_READ) - 条件2:目录搜索(是目录且不需要写权限)
capable(CAP_DAC_READ_SEARCH): 检查进程是否有读取和搜索覆盖的能力- 如果条件满足且有该能力,返回0(成功)
- 条件1:只需要读权限(
- CAP_DAC_READ_SEARCH允许进程绕过读和搜索相关的自主访问控制检查
return -EACCES;
- 最终拒绝:所有检查都失败,返回
-EACCES(Permission denied)
CAP_DAC_OVERRIDE: 覆盖 读 + 写 + 执行 权限
CAP_DAC_READ_SEARCH:只覆盖 读 + 搜索 权限,具有CAP_DAC_READ_SEARCH就可以扫描所有用户的文件
关键权限标志说明
| 标志 | 值 | 含义 |
|---|---|---|
MAY_READ | 4 | 读权限 |
MAY_WRITE | 2 | 写权限 |
MAY_EXEC | 1 | 执行权限 |
MAY_APPEND | 8 | 追加权限 |
S_IRWXU | 00700 | 用户读写执行 |
S_IRWXG | 00070 | 组读写执行 |
S_IRWXO | 00007 | 其他用户读写执行 |
函数功能总结
- 统一入口:
permission提供统一的权限检查接口 - 标志处理:正确处理MAY_APPEND特殊标志
- 文件系统扩展:支持文件系统特定的权限检查方法
- 基础属性检查:只读文件系统和不可变文件保护
- 用户身份识别:正确区分文件所有者、组用户和其他用户
- ACL支持:集成POSIX ACL扩展权限机制
- 传统DAC检查:标准的Unix权限位检查
- 能力覆盖:支持capabilities机制覆盖传统权限限制
- 安全模块集成:LSM钩子支持强制访问控制
