Linux ACL 机制与 setfacl 命令及特殊权限位之间的关系

ACL 机制与 setfacl 命令

1. 基础语法结构

ACL(Access Control List)条目采用三段式格式:

复制代码
[类型]:[标识]:[权限]
字段 含义 示例
类型 主体类别 u(用户)、g(组)、o(其他)、m(掩码)
标识 具体用户名/组名,空表示默认主体 alicewheel
权限 标准 Unix 权限位 rwxrxr-

双冒号 :: 的语义 :当标识字段为空时,两个分隔符相邻形成 ::,表示"该类型的默认主体"。

完整写法 展开理解 指向的主体
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_OBJACL_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-x
  • 2755 = SGID + 755 = -rwxr-sr-x
  • 1755 = SBIT + 755 = -rwxr-xr-t
  • 6755 = 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_OVERRIDECAP_DAC_READ_SEARCH capability,可绕过读、写权限检查,但执行权限检查对 root 同样生效

ACL 引入后,属主(包括 root)的权限由 ACL 的 ACL_USER_OBJ 条目(u::)显式定义,不再自动获得全部权限

4.2 root 在 ACL 环境下的权限判定

条件 行为
文件无 ACL root 凭 CAP_DAC_OVERRIDE 绕过读/写限制,但执行权限仍受 st_modex 位限制
文件有 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 显示