Linux generic_permission inode ACL与mode权限检查
2026/6/15 6:37:02 网站建设 项目流程

Linux generic_permission是inode权限检查的核心枢纽,它位于fs/namei.c,是所有基于inode的访问控制决策的最终仲裁者。函数接收inode、请求的权限掩码(MAY_READ/MAY_WRITE/MAY_EXEC)和一个可选的cred结构体,经过UNIX mode位、POSIX ACL和扩展权限检查三层验证后返回0或-EACCES。

```
// fs/namei.c
int generic_permission(struct user_namespace *mnt_userns,
struct inode *inode, int mask)
{
int ret;

ret = acl_permission_check(mnt_userns, inode, mask);
if (ret != -EACCES)
return ret;

if (S_ISDIR(inode->i_mode)) {
if (!(inode->i_opflags & IOP_EXEC_SEARCH))
return -EACCES;
}

ret = security_inode_permission(inode, mask);
if (ret)
return ret;

return -EACCES;
}
```

generic_permission首先调用acl_permission_check做基层的mode和ACL检查。如果返回-EACCES表示权限不足,函数不会立即返回,而是继续进行LSM(Linux Security Module)钩子的检查。如果LSM允许访问,则纠正返回0;如果LSM也拒绝,最终返回-EACCES。这种设计让LSM层面可以覆盖ACL的拒绝决定。

```
// fs/namei.c
static int acl_permission_check(struct user_namespace *mnt_userns,
struct inode *inode, int mask)
{
unsigned int mode = inode->i_mode;
kuid_t i_uid;

if (likely(uid_eq(current_fsuid(), inode->i_uid)))
mode >>= 6;
else {
if (IS_POSIXACL(inode) && (mode & S_IRWXG)) {
int error = check_acl(mnt_userns, inode, mask);
if (error != -EAGAIN)
return error;
}

if (in_group_p(inode->i_gid))
mode >>= 3;
else
mode &= ~S_IRWXG;
}

if ((mask & ~mode) == 0)
return 0;
return -EACCES;
}
```

acl_permission_check的精髓在于高效的短路设计。首先检查进程的文件系统UID是否与inode的owner匹配,如果匹配则直接取owner权限位(mode右移6位)。如果不匹配,检查inode是否设置了IS_POSIXACL标志且group权限位不为空,此时调用check_acl遍历文件系统提供的ACL条目。如果check_acl返回-EAGAIN(表示没有ACL或ACL不支持当前操作),或者ACL检查通过后继续fallthrough,则进入group组检查。

```
// fs/namei.c
static int check_acl(struct user_namespace *mnt_userns,
struct inode *inode, int mask)
{
const struct inode_operations *i_op = inode->i_op;

if (i_op->get_inode_acl) {
struct posix_acl *acl;

acl = i_op->get_inode_acl(inode, ACL_TYPE_ACCESS);
if (IS_ERR(acl))
return PTR_ERR(acl);
if (acl) {
int error = posix_acl_permission(mnt_userns, inode, acl, mask);
posix_acl_release(acl);
return error;
}
}
return -EAGAIN;
}
```

check_acl通过inode_operations中的get_inode_acl函数指针获取inode关联的ACL对象。POSIX ACL分为ACCESS类型(控制访问权限)和DEFAULT类型(仅目录,控制继承)。get_inode_acl为ACCESS类型。不同文件系统实现方式不同:ext4调用ext4_get_acl从磁盘扩展属性读取并解析;XFS调用xfs_get_acl从xattr中加载。

```
// fs/posix_acl.c
int posix_acl_permission(struct user_namespace *mnt_userns,
struct inode *inode, const struct posix_acl *acl,
int want)
{
const struct posix_acl_entry *pa, *pe, *mask_obj;
int found = 0;

FOREACH_ACL_ENTRY(pa, pe, acl) {
if (pa->e_tag == ACL_USER_OBJ) {
if (uid_eq(current_fsuid(), inode->i_uid))
goto check_perm;
} else if (pa->e_tag == ACL_USER) {
if (uid_eq(pa->e_uid, current_fsuid()))
goto mask;
} else if (pa->e_tag == ACL_GROUP_OBJ) {
if (in_group_p(inode->i_gid)) {
found = 1;
if (mask_obj == NULL)
mask_obj = pa;
}
} else if (pa->e_tag == ACL_GROUP) {
if (in_group_p(pa->e_gid)) {
found = 1;
if (mask_obj == NULL)
mask_obj = pa;
}
} else if (pa->e_tag == ACL_MASK)
mask_obj = pa;
else if (pa->e_tag == ACL_OTHER)
if (!(want & ~pa->e_perms))
return 0;
}

if (!found)
return -EACCES;

// apply ACL_MASK if present
if (mask_obj) {
// ...
}
return -EACCES;

mask:
if (mask_obj) {
if ((pa->e_perms & mask_obj->e_perms & want) == want)
return 0;
} else if ((pa->e_perms & want) == want)
return 0;
return -EACCES;

check_perm:
if ((pa->e_perms & want) == want)
return 0;
return -EACCES;
}
```

ACL的处理优先级是:ACL_USER_OBJ(文件owner) > ACL_USER(具名用户) > ACL_GROUP_OBJ/ACL_GROUP(组) > ACL_OTHER(其他人)。ACL_MASK条目用于屏蔽ACL_USER、ACL_GROUP_OBJ和ACL_GROUP条目的权限位,这是POSIX ACL语义的核心——mask的引入使得chmod修改group位时可以同时调整ACL mask。

如果检查通过,generic_permission还需要调用security_inode_permission,这是LSM框架的钩子,例如SELinux、AppArmor、Smack等安全模块在此处施加额外的MAC(强制访问控制)策略。

```
// security/security.c
int security_inode_permission(struct inode *inode, int mask)
{
if (unlikely(IS_PRIVATE(inode)))
return 0;
return call_int_hook(inode_permission, 0, inode, mask);
}
```

LSM钩子的返回值可以覆盖ACL的检查结果。如果一个进程拥有DAC(自主访问控制)权限但不符合MAC策略,security_inode_permission返回-EACCES,generic_permission最终也返回-EACCES。反之,如果ACL拒绝了但LSM允许,generic_permission返回0——这在某些特殊安全策略(例如允许备份程序读取所有文件)中有实际用途。

最后,inode->i_opflags中的IOP_EXEC_SEARCH标志与目录执行权限相关。当inode是目录且acl_permission_check返回-EACCES后,generic_permission会检查这个标志位来决定是否立即拒绝。这是为了优化目录搜索权限检查的性能:如果文件系统在lookup阶段已经确认了执行权限,不需要重新走完整的ACL检查。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询