ACL 机制与 setfacl 命令
1. 基础语法结构
ACL(Access Control List)条目采用三段式格式:
[类型]:[标识]:[权限]
| 字段 | 含义 | 示例 |
|---|---|---|
| 类型 | 主体类别 | u(用户)、g(组)、o(其他)、m(掩码) |
| 标识 | 具体用户名/组名,空表示默认主体 | alice、wheel、空 |
| 权限 | 标准 Unix 权限位 | rwx、rx、r、- |
双冒号 :: 的语义 :当标识字段为空时,两个分隔符相邻形成 ::,表示"该类型的默认主体"。
| 完整写法 | 展开理解 | 指向的主体 |
|---|---|---|
u::rx |
u:[空]:rx |
文件当前属主(owner) |
g::rx |
g:[空]:rx |
文件当前属组(group) |
o::rx |
o:[空]:rx |
其他用户(other) |
m::rx |
m:[空]:rx |
掩码(mask) |
2. 空标识符与显式标识符
| 语法 | 类型 | 标识 | 权限 | 语义 |
|---|---|---|---|---|
u::rwx |
用户 | 空 | rwx |
文件当前属主 |
u:root:rwx |
用户 | root |
rwx |
用户 root(无论是否属主) |
g::r-x |
组 | 空 | r-x |
文件当前属组 |
g:wheel:r-x |
组 | wheel |
r-x |
组 wheel(无论是否属组) |
使用空标识符的典型场景:
| 场景 | 原因 |
|---|---|
| 修改文件属主自身的 ACL 权限 | 自动绑定 inode 中的 uid,不硬编码用户名,提升可移植性 |
| 与标准 Unix 权限位对齐 | u::、g::、o:: 分别对应传统 user/group/other 三组 |
| 批量脚本化运维 | 泛化处理不同属主的文件,无需逐个查询用户名 |
| 区分"属主权限"与"特定用户权限" | u::rx(属主)与 u:alice:rwx(特定用户)语义明确分离 |
3. 标准权限与 ACL 的叠加机制
3.1 存储层面的物理分离
现代 Linux 文件系统(ext4、xfs、btrfs 等)中,权限数据分两处存储:
| 存储位置 | 内容 | 特性 |
|---|---|---|
| inode 内部固定字段 | st_mode(标准 rwx 位 + SUID/SGID/SBIT) |
文件系统元数据,ls -l 直接显示 |
| inode 外部 xattr 块 | system.posix_acl_access(ACL 条目列表) |
扩展属性,getfacl 读取,ls -l 以 + 标记存在 |
setfacl 仅修改 xattr 中的 ACL 列表,不触碰 inode 的 st_mode 字段。因此标准权限位在 ACL 操作前后保持不变。
3.2 内核权限检查的叠加算法
内核函数 posix_acl_permission() 的执行逻辑:
1. 检查 ACL 是否存在
└── 无 ACL → 回退到传统 mode 位检查(rwx)
2. 有 ACL → 按优先级匹配:
a. 请求者是文件属主:
- 优先匹配 ACL 的 OWNER 条目(u::perms)
- 若无 OWNER 条目,回退到 mode 的 user 位
b. 请求者属于文件属组:
- 优先匹配 ACL 的 GROUP 条目(g::perms 或 g:name:perms)
- 若无匹配组条目,回退到 mode 的 group 位
c. 其他用户:
- 匹配 ACL 的 OTHER 条目(o::perms)
- 若无,回退到 mode 的 other 位
3. 掩码(mask)限制:
- 所有 GROUP 类 ACL 条目需与 mask 做按位与
- 最终有效权限 = ACL 条目权限 & mask
叠加的本质 :ACL 条目优先于 标准 mode 位被检查,标准 mode 位作为后备默认值 始终存在。两者是逻辑上的"或"关系------任一机制授予的权限均有效,而非数值位运算叠加。
3.3 具体场景演示
场景 :文件权限 000,添加 ACL u::rx
bash
# 初始状态
$ touch /tmp/test && chmod 000 /tmp/test
$ ls -l /tmp/test
----------. 1 root root 0 Apr 22 08:00 /tmp/test
# 添加 ACL
$ setfacl -m u::rx /tmp/test
# 标准权限未变,但出现 ACL 标记 '+'
$ ls -l /tmp/test
----------+ 1 root root 0 Apr 22 08:00 /tmp/test
# ACL 层存在独立规则
$ getfacl /tmp/test
user::r-x # ACL 属主条目
group::other::```
**内核判断流程**(root 作为属主访问时):
| 检查层级 | 来源 | 权限结果 |
|:---|:---|:---|
| 标准 mode 位 | `st_mode` | `---`(拒绝) |
| ACL 属主条目 | `system.posix_acl_access` | `r-x`(允许读、执行) |
| **最终裁决** | ACL 优先 | **允许读、执行** |
## 4. 底层数据结构
### 4.1 内核 ACL 数据结构定义
ACL 在内核中的实现位于 `include/linux/posix_acl.h` 与 `fs/posix_acl.c`。
#### 4.1.1 ACL 条目结构 `posix_acl_entry`
```c
/**
* struct posix_acl_entry - 单个 ACL 条目
*
* 每个条目定义一个主体(用户/组/掩码/其他)及其权限。
* 多个条目组成完整的 ACL 规则集。
*/
struct posix_acl_entry {
/**
* @e_tag: 条目类型,标识该条目对应的主体类别
*
* ACL_USER_OBJ (0x01): 文件属主(对应 u::)
* ACL_USER (0x02): 特定用户(对应 u:uid:)
* ACL_GROUP_OBJ (0x04): 文件属组(对应 g::)
* ACL_GROUP (0x08): 特定组(对应 g:gid:)
* ACL_MASK (0x10): 掩码(对应 m::)
* ACL_OTHER (0x20): 其他用户(对应 o::)
*/
short e_tag;
/**
* @e_perm: 权限位掩码
*
* 使用 POSIX 标准权限常量:
* MAY_READ (0x04) → r
* MAY_WRITE (0x02) → w
* MAY_EXEC (0x01) → x
*
* 存储为无符号短整型,实际仅低 3 位有效。
*/
unsigned short e_perm;
/**
* @e_uid / @e_gid: 特定用户或组的标识符
*
* 当 e_tag 为 ACL_USER 或 ACL_GROUP 时有效,
* 分别存储特定用户的 kuid_t 或特定组的 kgid_t。
*
* 当 e_tag 为 ACL_USER_OBJ、ACL_GROUP_OBJ、
* ACL_MASK、ACL_OTHER 时,该联合体成员无意义。
*/
union {
kuid_t e_uid; /* ACL_USER 条目使用 */
kgid_t e_gid; /* ACL_GROUP 条目使用 */
};
};
4.1.2 ACL 规则集结构 posix_acl
c
/**
* struct posix_acl - 完整 ACL 规则集
*
* 包含若干 posix_acl_entry 条目,构成文件的完整访问控制列表。
* 该结构通过引用计数管理生命周期,支持多 inode 共享同一 ACL
*(通过 rcu 机制实现无锁读访问)。
*/
struct posix_acl {
/**
* @a_refcount: 引用计数
*
* 使用 atomic_t 实现原子操作,支持多 CPU 并发访问。
* 当引用计数归零时,结构体被释放。
*/
atomic_t a_refcount;
/**
* @a_count: 条目数量
*
* 表示 a_entries 数组中实际有效的条目数。
* 最小值为 3(必须包含 USER_OBJ、GROUP_OBJ、OTHER),
* 最大值为 ACL_MAX_ENTRIES(通常为 32 或 1024,取决于文件系统)。
*/
unsigned int a_count;
/**
* @a_entries: 变长数组,存储实际的 ACL 条目
*
* 采用 C99 柔性数组成员(flexible array member)实现。
* 分配内存时按 a_count 计算总大小:
* sizeof(struct posix_acl) + a_count * sizeof(struct posix_acl_entry)
*
* 条目顺序要求(由 setfacl 工具保证):
* 1. ACL_USER_OBJ(属主)
* 2. ACL_USER(特定用户,按 uid 排序)
* 3. ACL_GROUP_OBJ(属组)
* 4. ACL_GROUP(特定组,按 gid 排序)
* 5. ACL_MASK(掩码)
* 6. ACL_OTHER(其他)
*/
struct posix_acl_entry a_entries[];
};
4.2 ACL 与 xattr 的序列化格式
ACL 数据在用户空间与内核空间之间传输时,需序列化为 xattr 值。格式定义于 include/uapi/linux/posix_acl_xattr.h。
4.2.1 xattr 头部结构
c
/**
* struct posix_acl_xattr_header - xattr 存储的 ACL 头部
*
* 所有 ACL xattr(access/default)均以该头部开头,
* 后跟若干 posix_acl_xattr_entry 条目。
*/
struct posix_acl_xattr_header {
/**
* @a_version: ACL 版本号
*
* 当前仅定义 POSIX_ACL_XATTR_VERSION (0x0002)。
* 用于区分不同版本的 ACL 格式,确保内核与用户空间工具兼容。
*/
__le32 a_version;
};
#define POSIX_ACL_XATTR_VERSION 0x0002
4.2.2 xattr 条目结构
c
/**
* struct posix_acl_xattr_entry - xattr 存储的单个 ACL 条目
*
* 与内核结构 posix_acl_entry 对应,但使用固定宽度类型
* 确保跨架构(32/64 位、大/小端)兼容性。
*/
struct posix_acl_xattr_entry {
/**
* @e_tag: 条目类型,与 posix_acl_entry.e_tag 语义一致
*/
__le16 e_tag;
/**
* @e_perm: 权限位,与 posix_acl_entry.e_perm 语义一致
*/
__le16 e_perm;
/**
* @e_id: 特定用户或组的标识符
*
* 当 e_tag 为 ACL_USER 时,存储 uid(__le32);
* 当 e_tag 为 ACL_GROUP 时,存储 gid(__le32);
* 其他类型时,该字段值为未定义(通常置 0)。
*/
__le32 e_id;
};
4.2.3 序列化布局
xattr 值的完整二进制布局:
偏移 0x00: +-----------------------------+
| posix_acl_xattr_header |
| a_version (4 bytes, LE) |
偏移 0x04: +-----------------------------+
| posix_acl_xattr_entry[0] | ← ACL_USER_OBJ (u::)
| e_tag (2 bytes, LE) |
| e_perm (2 bytes, LE) |
| e_id (4 bytes, LE) |
偏移 0x0C: +-----------------------------+
| posix_acl_xattr_entry[1] | ← 可能为 ACL_USER (u:uid:)
| ... |
+-----------------------------+
| ... 更多条目 ... |
+-----------------------------+
| posix_acl_xattr_entry[n-1] | ← ACL_OTHER (o::)
+-----------------------------+
大小计算:
xattr_size = sizeof(struct posix_acl_xattr_header)
+ a_count * sizeof(struct posix_acl_xattr_entry)
= 4 + a_count * 8 (字节)
4.3 内核权限检查函数实现
4.3.1 主检查函数 posix_acl_permission
c
/**
* posix_acl_permission - 基于 ACL 检查访问权限
*
* @inode: 目标文件的 inode
* @acl: 该文件的 ACL 规则集(从 xattr 解析得到)
* @want: 请求的访问模式(MAY_READ / MAY_WRITE / MAY_EXEC)
*
* 返回值: 0 表示允许,-EACCES 表示拒绝
*/
int posix_acl_permission(struct inode *inode,
const struct posix_acl *acl,
int want)
{
const struct posix_acl_entry *pa, *pe;
const struct posix_acl_entry *mask_entry = NULL;
int found = 0;
kuid_t uid = current_uid(); /* 当前进程的有效用户 ID */
kgid_t gid = current_gid(); /* 当前进程的有效组 ID */
kgid_t fsgid = current_fsgid(); /* 当前进程的文件系统组 ID */
/* 遍历 ACL 条目数组 */
FOREACH_ACL_ENTRY(pa, acl, pe) {
switch (pa->e_tag) {
case ACL_USER_OBJ: /* 属主条目 (u::) */
/* 检查当前用户是否为文件属主 */
if (uid_eq(uid, inode->i_uid)) {
/* 属主权限不受掩码限制 */
goto check_perm;
}
break;
case ACL_USER: /* 特定用户条目 (u:uid:) */
/* 检查是否匹配当前用户 */
if (uid_eq(uid, pa->e_uid)) {
found = 1; /* 标记找到特定用户条目 */
goto check_mask;
}
break;
case ACL_GROUP_OBJ: /* 属组条目 (g::) */
/* 检查当前用户是否属于文件属组 */
if (gid_eq(gid, inode->i_gid) ||
gid_eq(fsgid, inode->i_gid) ||
in_group_p(inode->i_gid)) {
found = 1;
goto check_mask;
}
break;
case ACL_GROUP: /* 特定组条目 (g:gid:) */
/* 检查当前用户是否属于该特定组 */
if (gid_eq(gid, pa->e_gid) ||
gid_eq(fsgid, pa->e_gid) ||
in_group_p(pa->e_gid)) {
found = 1;
goto check_mask;
}
break;
case ACL_MASK: /* 掩码条目 (m::) */
/* 记录掩码位置,后续用于限制权限 */
mask_entry = pa;
break;
case ACL_OTHER: /* 其他用户条目 (o::) */
/* 当无更具体匹配时,使用 OTHER 条目 */
if (!found) {
pa = pa; /* 直接使用 OTHER 条目 */
goto check_perm;
}
break;
}
}
/* 未找到任何匹配条目,拒绝访问 */
return -EACCES;
check_mask:
/* 若存在掩码条目,将当前条目权限与掩码做按位与 */
if (mask_entry) {
int masked_perm = pa->e_perm & mask_entry->e_perm;
/* 检查掩码后的权限是否满足请求 */
if ((masked_perm & want) == want)
return 0; /* 允许访问 */
return -EACCES; /* 掩码限制后权限不足 */
}
check_perm:
/* 无掩码或属主/其他条目,直接检查原始权限 */
if ((pa->e_perm & want) == want)
return 0; /* 允许访问 */
return -EACCES; /* 权限不足 */
}
4.3.2 逻辑说明
| 检查阶段 | 处理逻辑 |
|---|---|
属主匹配 (ACL_USER_OBJ) |
若当前 uid 等于 inode->i_uid,直接以该条目权限裁决,不经过掩码限制 |
特定用户匹配 (ACL_USER) |
遍历所有 ACL_USER 条目,按 uid 匹配。匹配成功后需经过掩码检查 |
属组匹配 (ACL_GROUP_OBJ) |
检查当前用户是否属于文件属组(有效 gid、文件系统 gid、补充组列表) |
特定组匹配 (ACL_GROUP) |
检查当前用户是否属于特定组。多个组条目可能同时匹配,取权限并集 |
掩码应用 (ACL_MASK) |
对 ACL_USER(非 OBJ)、ACL_GROUP_OBJ、ACL_GROUP 条目的权限与掩码做按位与 |
其他用户匹配 (ACL_OTHER) |
当上述均无匹配时,以 OTHER 条目裁决,不经过掩码限制 |
4.4 ACL 与 inode 的关联机制
4.4.1 inode 中的 ACL 缓存
c
/**
* struct inode - 文件系统索引节点(ACL 相关字段摘录)
*/
struct inode {
/* ... 其他字段 ... */
/**
* @i_acl: 访问 ACL 缓存指针
*
* 指向该 inode 的 access ACL(system.posix_acl_access)。
* 使用 RCU 机制实现无锁并发读取。
* 值为 ACL_NOT_CACHED 表示尚未加载,NULL 表示无 ACL。
*/
struct posix_acl *i_acl;
/**
* @i_default_acl: 默认 ACL 缓存指针
*
* 仅对目录有效,指向 default ACL(system.posix_acl_default)。
* 新创建文件/目录时继承此 ACL。
*/
struct posix_acl *i_default_acl;
};
/* ACL 缓存的特殊标记值 */
#define ACL_NOT_CACHED ((void *)(-1))
4.4.2 ACL 加载流程
用户空间请求访问文件
│
▼
┌─────────────────┐
│ vfs_permission() │ ← VFS 层入口
└────────┬────────┘
│
▼
┌─────────────────────┐
│ 检查 inode->i_acl │
│ 是否为 ACL_NOT_CACHED │
└────────┬────────────┘
│
┌────┴────┐
▼ ▼
已缓存 未缓存
│ │
▼ ▼
直接使用 ┌─────────────────┐
缓存的 ACL │ xattr 读取 │
│ get_inode_acl() │
└────────┬────────┘
│
▼
┌─────────────────┐
│ 解析 xattr 值 │
│ posix_acl_from_xattr() │
│ 转换为 posix_acl 结构 │
└────────┬────────┘
│
▼
┌─────────────────┐
│ 存入 inode->i_acl│
│ 供后续缓存使用 │
└─────────────────┘
4.4.3 xattr 与底层文件系统的交互
不同文件系统对 xattr 的存储实现:
| 文件系统 | xattr 存储方式 | ACL 大小限制 |
|---|---|---|
| ext4 | inode 预留空间(inode 内 xattr)+ 外部 xattr 块 | 单 xattr ≤ 64 KiB,受块大小限制 |
| xfs | 扩展属性区(noattr 分支),使用 B+ 树索引大 xattr | 无硬性限制,受文件系统容量约束 |
| btrfs | 与文件数据统一的 extent 树,Copy-on-Write | 无硬性限制 |
| tmpfs | 内存中的 shmem 结构,xattr 存于页缓存 | 受内存限制 |
ext4 的 inode 内 xattr 布局(inode 大小 256 字节时):
偏移 0x00-0x7F: 固定 inode 字段(i_mode, i_uid, i_gid, i_size 等)
偏移 0x80-0xFF: 扩展属性空间(约 128 字节)
若 ACL 较小(如 3-5 个条目,约 28-52 字节),可直接存入 inode 内,无需额外磁盘 I/O 读取外部块,提升访问性能。若 ACL 较大,超出 inode 预留空间,则分配外部 xattr 块存储,inode 内仅保留指向该块的指针。
4.5 用户空间与内核空间的接口
4.5.1 系统调用路径
用户空间调用
│
├── setfacl 工具 ──► libacl 库 ──► setxattr() 系统调用
│ │
│ ▼
│ ┌─────────────┐
│ │ sys_setxattr │
│ │ (x86: 188) │
│ └──────┬──────┘
│ │
▼ ▼
getfacl 工具 ──► libacl 库 ──► getxattr() 系统调用
│
▼
┌─────────────┐
│ sys_getxattr │
│ (x86: 191) │
└──────┬──────┘
│
▼
┌─────────────┐
│ vfs_setxattr │
│ vfs_getxattr │
└──────┬──────┘
│
▼
┌─────────────┐
│ 文件系统特定 │
│ xattr 处理 │
│ ext4_xattr_set │
│ ext4_xattr_get │
└─────────────┘
4.5.2 setxattr 系统调用参数
c
/**
* setxattr - 设置扩展属性
*
* @pathname: 文件路径
* @name: xattr 名称(ACL 使用 "system.posix_acl_access" 或
* "system.posix_acl_default")
* @value: xattr 值(序列化后的 posix_acl_xattr_header + entries)
* @size: 值的长度(字节)
* @flags: 操作标志(XATTR_CREATE / XATTR_REPLACE)
*
* 返回值: 0 表示成功,-1 表示失败并设置 errno
*/
int setxattr(const char *pathname,
const char *name,
const void *value,
size_t size,
int flags);
5. 完整应用案例:chmod 000 的 ACL 恢复方案
bash
setfacl -m u::rx /bin/chmod
chmod 755 /bin/chmod
setfacl -b /bin/chmod
| 步骤 | 命令 | 权限状态 | 效果 |
|---|---|---|---|
| 1 | setfacl -m u::rx /bin/chmod |
000 + ACL u::rx |
通过 ACL 赋予属主执行权,不改动标准 rwx 位 |
| 2 | chmod 755 /bin/chmod |
-rwxr-xr-x (755) |
调用已恢复执行的 /bin/chmod 修复标准权限 |
| 3 | setfacl -b /bin/chmod |
-rwxr-xr-x (755) |
移除所有 ACL 扩展规则,回归纯标准权限模型 |
第一步有效性原理:
setfacl 通过 setxattr("system.posix_acl_access", ...) 系统调用修改 xattr 扩展属性,该操作仅需对文件本身具有写权限(root 具备 CAP_DAC_OVERRIDE,不受 000 限制)。
ACL 赋予属主(root)执行权后,Shell 的 execve() 系统调用触发内核权限检查,posix_acl_permission() 匹配到 ACL_USER_OBJ 条目的 r-x 权限,允许加载并运行 /bin/chmod,进而自举修复标准权限位。
6. 概念对照表
| 维度 | 标准权限(mode bits) | ACL |
|---|---|---|
| 存储位置 | inode st_mode 字段 |
xattr system.posix_acl_access |
| 修改命令 | chmod |
setfacl |
| 显示命令 | ls -l |
getfacl |
| 存在标记 | 无 | ls -l 文件名后显示 + |
| 检查优先级 | 后备默认 | 优先匹配 |
| 与另一机制的关系 | 独立存在 | 逻辑互补("或"关系,非位运算) |
| 属主表示 | u 位(固定) |
u::(显式条目) |
| 掩码控制 | 无 | m:: 限制 GROUP 类条目有效权限 |
| 底层数据结构 | umode_t(16 位,存于 inode) |
posix_acl + posix_acl_entry[](xattr 序列化) |
| 内核检查函数 | generic_permission()(mode 位分支) |
posix_acl_permission() |
| 序列化格式 | 无(直接存储于 inode) | posix_acl_xattr_header + posix_acl_xattr_entry[] |
| 缓存机制 | 无(直接读取 inode) | inode->i_acl / i_default_acl(RCU 缓存) |
特殊权限位:SUID、SGID、SBIT
1. 基础概念
| 缩略 | 全称 | 命名来源 | 八进制值 | 符号模式 |
|---|---|---|---|---|
| SUID | Set User ID upon execution | set 的首字母 | 4000 | u+s |
| SGID | Set Group ID upon execution | set 的首字母 | 2000 | g+s |
| SBIT | Sticky Bit | sticky 的尾字母 | 1000 | o+t 或 +t |
符号表示法解析:
u+s:为 u ser(属主)设置 set-UID 位g+s:为 g roup(属组)设置 set-GID 位o+t:为 o ther(其他)设置 sticky 位
2. 权限位在 st_mode 中的布局
st_mode 为 16 位无符号整数,权限相关位域分布如下:
位 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐
│文件类型 (4 bits) │SUID│SGID│SBIT│ rwx (属主) │ rwx (属组) │ rwx (其他) │
└─────────────────┴────┴────┴────┴────────────┴────────────┴────────────┘
│ │ │
│ │ └── SBIT (1000)
│ └─────── SGID (2000)
└──────────── SUID (4000)
数值计算示例:
4755= SUID +755=-rwsr-xr-x2755= SGID +755=-rwxr-sr-x1755= SBIT +755=-rwxr-xr-t6755= SUID + SGID +755=-rwsr-sr-x
3. 显示机制:复用执行位 x
SUID、SGID、SBIT 在 ls -l 输出中复用 执行位 x 的位置显示,通过字符形态区分是否同时具备执行权限:
| 特殊位 | 执行位状态 | 显示字符 | 位置 | 含义 |
|---|---|---|---|---|
| SUID | 有执行权限 | s |
属主执行位 | 属主可执行,且 SUID 生效 |
| SUID | 无执行权限 | S |
属主执行位 | SUID 已设置,但属主不可执行(异常状态) |
| SGID | 有执行权限 | s |
属组执行位 | 属组可执行,且 SGID 生效 |
| SGID | 无执行权限 | S |
属组执行位 | SGID 已设置,但属组不可执行(异常状态) |
| SBIT | 有执行/进入权限 | t |
其他执行位 | 其他用户可执行/进入,且 SBIT 生效 |
| SBIT | 无执行/进入权限 | T |
其他执行位 | SBIT 已设置,但其他用户不可执行(对目录罕见) |
注 :SUID 与 SGID 均使用字母 s(或 S)显示,区别在于所处位置不同------SUID 占据属主执行位,SGID 占据属组执行位。
4. SUID(Set User ID)
4.1 机制
| 项目 | 内容 |
|---|---|
| 作用对象 | 可执行文件 |
| 触发时机 | 进程执行该文件时 |
| 效果 | 进程有效用户 ID(effective UID)临时提升为文件属主的 UID |
4.2 典型应用
bash
-rwsr-xr-x 1 root root /usr/bin/passwd
passwd 需修改 /etc/shadow(仅 root 可写),普通用户通过 SUID 临时获得 root 身份执行密码修改。
4.3 安全风险
SUID 程序若存在漏洞,攻击者可借此提升至文件属主权限(常为 root)。
5. SGID(Set Group ID)
5.1 对可执行文件
| 项目 | 内容 |
|---|---|
| 触发时机 | 进程执行该文件时 |
| 效果 | 进程有效组 ID(effective GID)临时提升为文件属组的 GID |
典型应用:
bash
-rwxr-sr-x 1 root mail /usr/bin/mail
mail 程序需访问邮件队列目录(属组 mail 可写),SGID 使执行者临时获得 mail 组权限。
5.2 对目录
| 项目 | 内容 |
|---|---|
| 触发时机 | 在目录内创建新文件/子目录时 |
| 效果 | 新创建项的属组自动继承该目录的属组,而非创建者的主组 |
典型应用:
bash
drwxrwsr-x 2 root developers /srv/project
团队成员在 /srv/project 下创建的文件自动属组为 developers,便于组内协作共享。
6. SBIT(Sticky Bit)
6.1 历史语义
早期 Unix 中用于标记频繁使用的可执行文件,建议内核将其交换空间镜像保留在内存,减少换入换出开销。现代 Linux 已废弃此语义。
6.2 现代语义
| 项目 | 内容 |
|---|---|
| 作用对象 | 目录 |
| 效果 | 仅文件属主、目录属主或 root 可删除/重命名该目录下的文件 |
机制:即使其他用户具有该目录的写权限,SBIT 仍阻止其删除不属于自己的文件。
6.3 典型应用
bash
drwxrwxrwt 10 root root 4096 /tmp
/tmp 目录对所有用户开放写权限,SBIT 防止用户 A 删除用户 B 创建的临时文件。
7. 完整示例对照
bash
# SUID 生效且可执行(s 位于属主执行位)
-rwsr-xr-x 1 root root /usr/bin/passwd
# SUID 设置但无执行权限(大写 S,属主执行位)
-rwSr--r-- 1 root root /tmp/broken_suid
# SGID 生效且可执行(s 位于属组执行位)
-rwxr-sr-x 1 root mail /usr/bin/mail
# SGID 设置但无执行权限(大写 S,属组执行位)
-rwxr-Sr-x 1 root mail /tmp/broken_sgid
# SUID + SGID 同时生效(两个 s 分别位于属主、属组执行位)
-rwsr-sr-x 1 root mail /usr/bin/combined
# SBIT 生效且目录可进入(t 位于其他执行位)
drwxrwxrwt 10 root root /tmp
# SBIT 设置但目录不可进入(大写 T,其他执行位)
drwxrwxrwT 10 root root /tmp/broken_sticky
8. 功能对照表
| 特性 | SUID | SGID(文件) | SGID(目录) | SBIT |
|---|---|---|---|---|
| 全称 | Set User ID | Set Group ID | Set Group ID | Sticky Bit |
| 作用域 | 可执行文件 | 可执行文件 | 目录 | 目录 |
| 身份变更 | 有效 UID → 文件属主 UID | 有效 GID → 文件属组 GID | 新文件属组 → 目录属组 | 无 |
| 典型场景 | 需 root 权限的系统工具 | 需特定组权限的共享工具 | 协作目录的组继承 | 公共临时目录保护 |
| 符号设置 | u+s |
g+s |
g+s |
o+t / +t |
| 八进制值 | 4000 | 2000 | 2000 | 1000 |
| 显示位置 | 属主执行位 | 属组执行位 | 属组执行位 | 其他执行位 |
| 显示字符(有执行) | s |
s |
s |
t |
| 显示字符(无执行) | S |
S |
S |
T |
| 安全顾虑 | 提权攻击面 | 组权限提升 | 组信息泄露 | 无显著风险 |
ACL 与特殊权限位的关系与相互影响
1. 存储层面的物理隔离
| 机制 | 存储位置 | 数据格式 | 修改命令 |
|---|---|---|---|
| 标准权限(含 SUID/SGID/SBIT) | inode st_mode 字段(固定 16 位) |
umode_t 位掩码 |
chmod |
| ACL | xattr system.posix_acl_access |
posix_acl_xattr_header + posix_acl_xattr_entry[] |
setfacl |
| 默认 ACL(仅目录) | xattr system.posix_acl_default |
同上 | setfacl -d |
注 :两者物理存储位置完全独立,修改互不影响。setfacl 不触碰 st_mode 的 SUID/SGID/SBIT 位;chmod 不修改 xattr 中的 ACL 条目。
2. 权限检查时的交互逻辑
2.1 内核检查优先级
进程请求访问文件
│
▼
┌─────────────────┐
│ 检查文件是否具 │ ← 第一步:文件类型与基本可访问性
│ 有 ACL │
└────────┬────────┘
│
┌────┴────┐
▼ ▼
无 ACL 有 ACL
│ │
▼ ▼
┌─────────┐ ┌─────────────────┐
│ 传统 │ │ ACL 权限检查 │
│ mode 位 │ │ posix_acl_permission() │
│ 检查 │ │ │
│ │ │ 按优先级匹配: │
│ SUID/ │ │ 1. ACL_USER_OBJ │
│ SGID/ │ │ 2. ACL_USER │
│ SBIT │ │ 3. ACL_GROUP_OBJ│
│ 在此生效 │ │ 4. ACL_GROUP │
│ │ │ 5. ACL_MASK │
│ │ │ 6. ACL_OTHER │
└─────────┘ └─────────────────┘
│
▼
检查通过?
┌────┴────┐
▼ ▼
允许 拒绝
2.2 交互规则
| 场景 | 行为 |
|---|---|
| 文件有 ACL | 优先使用 ACL 检查,标准 mode 位仅作为 ACL 缺失条目时的后备默认值 |
ACL 的 u:: 条目存在 |
属主权限由 ACL u:: 决定,忽略 st_mode 的 user 位(含 SUID 的 x) |
ACL 的 g:: 条目存在 |
属组权限由 ACL g:: 决定,忽略 st_mode 的 group 位(含 SGID 的 x) |
ACL 的 o:: 条目存在 |
其他用户权限由 ACL o:: 决定,忽略 st_mode 的 other 位(含 SBIT 的 x) |
| SUID/SGID/SBIT 标志位 | 无论 ACL 是否存在,始终保留在 st_mode 中 ,在 execve() 系统调用阶段生效 |
3. SUID/SGID 与 ACL 的具体交互
3.1 SUID 与 ACL
| 阶段 | 机制 | 说明 |
|---|---|---|
execve() 加载文件 |
SUID 生效 | 内核检查 st_mode 的 SUID 位,设置进程 effective UID |
| 运行时文件访问 | ACL 生效 | 进程以提升后的 UID 作为身份标识,匹配 ACL 的 u:: 或 u:uid: 条目 |
示例:
bash
# /usr/bin/passwd: SUID + ACL
-rwsr-xr-x+ 1 root root /usr/bin/passwd
getfacl /usr/bin/passwd:
user::r-x # 属主(root)权限
user:alice:r-- # 特定用户 alice 仅可读
group::r-x
mask::r-x
other::---
# 普通用户执行 passwd 时:
# 1. SUID 使 effective UID 变为 root
# 2. ACL 检查以 root 身份匹配 user::r-x,允许执行
# 3. 用户 alice 执行时,匹配 user:alice:r--,拒绝执行
3.2 SGID 与 ACL
| 场景 | 行为 |
|---|---|
| SGID 可执行文件 + ACL | 执行时 effective GID 提升为文件属组,ACL 的 g:: 或 g:gid: 按提升后的 GID 匹配 |
| SGID 目录 + ACL | 目录内新创建文件继承目录属组;若目录同时有 default ACL,新文件 ACL 由 default ACL 与继承属组共同决定 |
SGID 目录与 default ACL 的协同:
bash
# /srv/project: SGID + default ACL
drwxrwsr-x+ 2 root developers /srv/project
getfacl /srv/project:
user::rwx
group::rwx # 属组 developers
group:contractors:r-x # 特定组 contractors
mask::rwx
other::---
default:user::rwx
default:group::rwx
default:group:contractors:r-x
default:mask::rwx
default:other::---
# 用户(属组 developers)创建新文件时:
# 1. SGID 使新文件属组 = developers
# 2. default ACL 自动复制为新文件的 access ACL
# 3. 结果:新文件具有预设的 ACL 规则,且属组正确
3.3 SBIT 与 ACL
| 场景 | 行为 |
|---|---|
| SBIT 目录 + ACL | SBIT 的删除保护独立于 ACL 检查。即使 ACL 授予写权限,SBIT 仍限制非属主删除 |
SBIT 的强制优先级:
bash
drwxrwxrwt+ 10 root root /tmp
getfacl /tmp:
user::rwx
group::rwx
other::rwx # ACL 授予所有用户写权限
# 用户 alice 在 /tmp 创建文件 /tmp/alice_file
# 用户 bob 即使通过 ACL 获得 /tmp 的 rwx 权限
# SBIT 阻止 bob 删除 /tmp/alice_file(非属主/非目录属主/非 root)
4. ACL 对属主(root)的权限覆盖
4.1 核心机制
在标准 Linux DAC(Discretionary Access Control)模型中,root 用户(有效 UID 为 0)默认持有 CAP_DAC_OVERRIDE 与 CAP_DAC_READ_SEARCH capability,可绕过读、写权限检查,但执行权限检查对 root 同样生效。
ACL 引入后,属主(包括 root)的权限由 ACL 的 ACL_USER_OBJ 条目(u::)显式定义,不再自动获得全部权限。
4.2 root 在 ACL 环境下的权限判定
| 条件 | 行为 |
|---|---|
| 文件无 ACL | root 凭 CAP_DAC_OVERRIDE 绕过读/写限制,但执行权限仍受 st_mode 的 x 位限制 |
文件有 ACL,且 u:: 条目存在 |
root 作为属主,权限受 u:: 条目限制,不再自动获得全部权限 |
文件有 ACL,但 u:: 条目缺失 |
回退到 st_mode 的 user 位,root 凭 capability 绕过读/写,执行仍受限 |
4.3 典型场景:root 被 ACL 限制
bash
# 创建测试文件,设置 ACL 使属主(root)仅可读
touch /tmp/test_acl_root
setfacl -m u::r-- /tmp/test_root
# 检查状态
-rw-r-----+ 1 root root 0 /tmp/test_acl_root
getfacl /tmp/test_acl_root:
user::r-- # 属主(root)仅可读
group::---
other::---
# root 尝试写入
echo "data" >> /tmp/test_acl_root
# 结果:Permission denied
# 原因:ACL 的 u::r-- 显式限制属主写权限
# root 的 CAP_DAC_OVERRIDE 在 ACL 存在时**不覆盖** ACL_USER_OBJ 条目
4.4 root 绕过 ACL 限制的方法
root 可通过以下机制绕过 ACL 限制:
| 方法 | 原理 |
|---|---|
CAP_FOWNER capability |
允许对任意文件执行操作,无视 ACL 限制 |
| 修改或移除 ACL | setfacl -b /tmp/test_acl_root 清除 ACL,回退到标准权限 |
| 直接修改 xattr | 使用 setxattr 系统调用修改 system.posix_acl_access |
bash
# root 清除 ACL 后恢复完全控制
setfacl -b /tmp/test_acl_root
chmod 644 /tmp/test_acl_root
5. chmod 对 ACL 的副作用
5.1 权限位同步规则
当文件已存在 ACL 时,chmod 修改标准 mode 位会触发内核的ACL 同步机制:
chmod 操作 |
对 ACL 的影响 |
|---|---|
修改属主权限位(如 u+r) |
同步更新 ACL 的 u:: 条目权限 |
修改属组权限位(如 g-w) |
同步更新 ACL 的 g:: 条目权限 |
修改其他权限位(如 o=x) |
同步更新 ACL 的 o:: 条目权限 |
| 设置 SUID/SGID/SBIT | 不修改 ACL ,仅更新 st_mode 标志位 |
清除所有 ACL(setfacl -b) |
将 ACL 的 u::、g::、o:: 回写为 mode 位,删除 xattr |
5.2 掩码(mask)的自动调整
bash
# 文件初始状态:ACL 存在,mask 为 rwx
-rwxrwxrwx+ 1 root root /tmp/test
getfacl /tmp/test:
user::rwx
group::rwx
mask::rwx
other::rwx
# 执行 chmod 640
chmod 640 /tmp/test
# 结果:mode 位变为 640,同时 ACL 被同步
-rw-r-----+ 1 root root /tmp/test
getfacl /tmp/test:
user::rw- # 同步为 6
group::r-- # 同步为 4
mask::r-- # mask 被压缩为 group 位
other::--- # 同步为 0
# 注意:原有 ACL 特定条目可能被 mask 限制
# 若存在 user:alice:rwx,chmod 640 后 mask::r-- 使其失效
6. 特殊权限位在 ACL 环境中的保留意义
| 特殊位 | ACL 存在时的作用 | 与 ACL 的交互 |
|---|---|---|
| SUID | 仍控制 execve() 时的 effective UID 提升 |
在 ACL 检查之前生效,提升后的 UID 参与 ACL 匹配 |
| SGID | 仍控制 execve() 时的 effective GID 提升(文件)或新文件属组继承(目录) |
与 default ACL 协同决定新创建对象的初始权限与属组 |
| SBIT | 仍强制限制目录内文件的删除权限 | 覆盖 ACL 可能授予的写权限,提供额外的删除保护 |
7. 完整状态示例
7.1 复合权限文件
bash
-rwsr-sr-t+ 1 root mail 48712 /usr/bin/mailq
# 解析:
# - rws: 属主可读可写可执行,SUID 生效(s 替代 x)
# - r-s: 属组可读可执行,SGID 生效(s 替代 x)
# - r-t: 其他用户可读可执行,SBIT 生效(t 替代 x)
# - +: 存在 ACL 扩展规则
getfacl /usr/bin/mailq:
user::rwx # 对应 mode 的 user 位(含 SUID)
user:postfix:r-x # 特定用户 postfix
group::r-x # 对应 mode 的 group 位(含 SGID)
group:adm:r-- # 特定组 adm
mask::r-x # 限制 user:postfix 和 group:adm 的有效权限
other::r-x # 对应 mode 的 other 位(含 SBIT)
7.2 权限检查流程(用户 postfix 执行 mailq)
1. execve("/usr/bin/mailq")
└── 内核检查 st_mode:
├── SUID 置位 → effective UID = root(文件属主)
├── SGID 置位 → effective GID = mail(文件属组)
└── SBIT 无关(非目录)
2. 运行时的文件访问检查(以 effective UID/GID 评估)
└── posix_acl_permission(inode, acl, want=READ|EXEC)
├── 当前 UID = root,匹配 ACL_USER_OBJ (user::rwx)
│ └── 属主条目不受 mask 限制
└── 允许读、执行
3. 若当前 UID = postfix(非属主)
└── 匹配 ACL_USER (user:postfix:r-x)
├── 受 mask::r-x 限制 → r-x & r-x = r-x
└── 允许读、执行
4. 若当前 GID = adm(非属组)
└── 匹配 ACL_GROUP (group:adm:r--)
├── 受 mask::r-x 限制 → r-- & r-x = r--
└── 仅允许读,拒绝执行
8. 功能对照表
| 维度 | 标准权限(SUID/SGID/SBIT) | ACL |
|---|---|---|
| 存储位置 | inode st_mode |
xattr system.posix_acl_access |
| 作用时机 | execve()(SUID/SGID)、目录操作(SGID/SBIT) |
每次文件访问权限检查 |
| 身份控制粒度 | 仅属主/属组/其他三类 | 任意用户、任意组、掩码 |
| root 权限 | CAP_DAC_OVERRIDE 绕过读/写限制 |
ACL u:: 显式定义属主权限,root 受限于 u::(除非 CAP_FOWNER) |
| 与另一机制关系 | SUID/SGID 提升身份后,ACL 按提升后的身份检查 | ACL 检查以 SUID/SGID 生效后的 effective ID 为基准 |
| 删除保护 | SBIT 强制限制(独立于 ACL) | ACL 不直接控制删除权限(需配合 SBIT) |
| 目录默认规则 | SGID 控制新文件属组 | default ACL 控制新文件的初始 access ACL |
chmod 影响 |
直接修改 | 同步更新 u::/g::/o::,mask 可能压缩特定条目 |
| 显示标记 | s/S/t/T 替代 x |
+ 标记存在,具体规则由 getfacl 显示 |