第 17 章 访问控制列表 (Access Control Lists)

      +

      核心结论

      • ACL 概念:ACL 是一系列「条目」的有序集合;每个条目定义一个特定用户或组的文件权限;Linux 自 2.6 内核起支持。

      • 六类标签类型ACL_USER_OBJACL_USERACL_GROUP_OBJACL_GROUPACL_MASKACL_OTHER;前三个是「最小 ACL」必备,后三个用于扩展 ACL。

      • 权限检查算法:依次匹配 owner → ACL_USER → group class(ACL_GROUP_OBJ / ACL_GROUP 与 ACL_MASK 按位与)→ ACL_OTHER;file-system UID(不是 effective UID)参与判定。

      • ACL_MASK 与 group class:扩展 ACL 必须有 ACL_MASK;传统 chmod 修改 group 权限时实际改的是 ACL_MASK;stat() 返回的 group 位反映的是 ACL_MASK。

      • 默认 ACL:仅对目录有意义;新文件/子目录继承父目录的默认 ACL 为其访问 ACL(且 mode 参数按位与);子目录还继承默认 ACL 本身。

      • 底层实现:ACL 存储在两个 system EAs——system.posix_acl_access(访问 ACL)与 system.posix_acl_default(默认 ACL);libacl 库在用户态操纵这些 EAs。

      本章主旨

      本章介绍 Linux 的访问控制列表(ACL)——传统 9 位权限模型的超集,允许为任意数量的具体用户/组设置权限。读者应掌握:ACL 条目的六大标签类型、权限检查算法(特别是 ACL_MASK 的作用)、最小与扩展 ACL 的区分、默认 ACL 的继承规则、以及 libacl 库 API 的使用范式。Linux ACL 实现基于 POSIX.1e Draft 17(最终草案),但 POSIX.1e 已被撤回,因此 ACL 是「事实标准」而非正式标准。挂载 ext2/3/4/Reiserfs 时需 mount -o acl

      一、核心概念

      本章围绕 6 个核心概念展开:从 ACL 条目结构入手,再到权限检查算法、ACL_MASK 的语义、默认 ACL 继承、最后到 libacl API。

      概念 定义 + 重要性 实现提示

      ACL 条目与标签类型

      ACL = 一组条目的有序集合;每条目 = (标签类型, 可选标签限定符, 权限集);六类标签覆盖 owner / 命名用户 / owning group / 命名组 / mask / other。

      §17.1;ACL_USER_OBJ 对应传统 owner,ACL_GROUP_OBJ 对应传统 owning group,ACL_OTHER 对应传统 other。

      最小与扩展 ACL

      最小 ACL = ACL_USER_OBJ + ACL_GROUP_OBJ + ACL_OTHER(语义等价传统 9 位);扩展 ACL 额外包含 ACL_USER/ACL_GROUP/ACL_MASK

      §17.1;ls -l 在文件有扩展 ACL 时权限位后加 +;扩展 ACL 的访问信息存于 system.posix_acl_access EA。

      权限检查算法

      按 owner → 命名用户 → group class(group_obj 与命名组按 mask)→ other 顺序匹配;至少有一个匹配满足请求权限才允许。

      §17.2;优先级:单进程最多匹配一条 ACL_USER;若请求的权限被 mask 清除则即便条目列出该权限也无效;group class 中按所有匹配 GID 累加有效权限。

      ACL_MASK 与 group class

      ACL_MASK 是扩展 ACL 的「上限」——限制 ACL_USERACL_GROUP_OBJACL_GROUP 条目实际授予的权限;chmod 修改 group 位实际改 mask,stat 返回的 group 位反映 mask。

      §17.4;这一设计是为了兼容传统 chmod 语义——避免 ACL-unaware 程序破坏 ACL-aware 程序设置的权限。

      默认 ACL (Directory inheritance)

      默认 ACL 只对目录有意义;目录有默认 ACL 时,新创建的文件/子目录继承它为访问 ACL;新子目录同时继承默认 ACL 本身。

      §17.6;继承时把「对应于传统权限位」的 ACL 条目(USER_OBJ、MASK/若缺省则 GROUP_OBJ、OTHER)与 mode 参数按位与。

      libacl API

      操纵 ACL 的 C 库 API:以 acl_t 句柄代表 ACL,acl_entry_t 代表条目;acl_get_file/acl_set_file 与磁盘交换,acl_from_text/acl_to_text 与字符串交换。

      §17.8;编译需 -lacl;libacl 内部用 getxattr/setxattr 读写 system.posix_acl_* EA。

      二、详细笔记

      17.1 ACL 概述与条目结构

      What:ACL(Access Control List)是为文件附加的「多个用户/组 × 不同权限」的列表;Linux 2.6 起支持;与文件本身绑定,存于扩展属性(system EAs)。

      Why:传统 9 位权限(owner/group/other × rwx)粒度太粗——多个用户共享同一个 group 时无法差异化;ACL 允许为任意数量的命名用户、命名组分别设置权限。

      How:ACL 由「条目」组成;每条目三要素:

      部分 类型 含义

      标签类型 (tag type)

      枚举

      决定条目适用于哪一类实体

      标签限定符 (tag qualifier)

      UID/GID

      仅 ACL_USER / ACL_GROUP 携带,指定具体用户或组

      权限集 (permission set)

      r/w/x

      该条目授予的权限

      六大标签类型:

      标签类型 数量限制 语义

      ACL_USER_OBJ

      每个 ACL 恰好 1 个

      文件 owner(对应传统 owner 权限位)

      ACL_USER

      0..N,每 UID 至多 1 条

      命名用户条目

      ACL_GROUP_OBJ

      每个 ACL 恰好 1 个

      owning group(对应传统 group 位,但被 MASK 覆盖)

      ACL_GROUP

      0..N,每 GID 至多 1 条

      命名组条目

      ACL_MASK

      0 或 1 个(扩展 ACL 必备)

      限制 ACL_USER/ACL_GROUP_OBJ/ACL_GROUP 的实际权限

      ACL_OTHER

      每个 ACL 恰好 1 个

      其他用户(对应传统 other 位)

      最小 ACL 仅含 ACL_USER_OBJ + ACL_GROUP_OBJ + ACL_OTHER;扩展 ACL 额外含 ACL_USER/ACL_GROUP/ACL_MASK

      When:需要为「传统 9 位权限表达不了」的场景——多个独立用户对同一文件有不同权限;项目组多层级权限细分。

      Examplels -l 在有扩展 ACL 时权限位后加 +

      $ ls -l tfile
      -rwxr-x--x+   1 mtk      users           0 Dec 3 15:42 tfile
                                                ^ 扩展 ACL 标志

      17.2 权限检查算法

      What:当进程访问有 ACL 的文件时,内核按「特权 → owner → 命名用户 → group class → other」顺序匹配;group class 中需按 mask 校正。

      Why:让 ACL 在「语义上扩展」传统模型——大多数场景下「权限检查结果」与传统一致;但允许更细粒度的命名用户/组覆盖。

      How:权限检查算法(§17.2):

      1. 特权进程(CAP_DAC_OVERRIDE)允许访问
         例外:执行文件时需至少一条 ACL 条目授予 x
      2. 进程 file-system UID == 文件 owner UID → 用 ACL_USER_OBJ 权限
      3. 进程 file-system UID 匹配某 ACL_USER 条目的限定符 → 用该条目权限,按位与 ACL_MASK
      4. 进程任一 GID 匹配 ACL_GROUP_OBJ 或某 ACL_GROUP 限定符 → 进入 group class 检查:
         a) 若 ACL_GROUP_OBJ 授予请求权限 → 用其权限,按位与 ACL_MASK
         b) 若某 ACL_GROUP 条目授予请求权限 → 用其权限,按位与 ACL_MASK
         c) 否则拒绝
      5. 否则用 ACL_OTHER 权限

      When:调试「明明 ACL 设了权限却访问失败」——往往因为 mask 把权限清零;getfacl 显示的 #effective: 行就是 mask 校正后的实际权限。

      Example:图 17-1 ACL 例子:

      标签类型 限定符 权限

      ACL_USER_OBJ

      -

      rwx

      ACL_USER

      1007

      r--

      ACL_USER

      1010

      rwx

      ACL_GROUP_OBJ

      -

      rwx

      ACL_GROUP

      102

      r--

      ACL_GROUP

      103

      -w-

      ACL_GROUP

      109

      --x

      ACL_MASK

      -

      rw-

      ACL_OTHER

      -

      r--

      若 mask=rw-,则命名用户 1010 实际只能 rw-(rwx AND rw-);group class 中所有条目按位与 rw-——命名组 109 的 --x 实际只剩 --x AND rw- = --x

      17.3 文本形式(短/长)

      What:ACL 可用两种文本形式表达——长形式(每行一条目,支持 # 注释)与短形式(逗号分隔条目)。

      Why:方便 shell 命令与配置文件读写;getfacl 默认长形式输出,setfacl -M acl-file 接受长形式输入。

      How:每条目格式 tag-type:[tag-qualifier]:permissions

      tag 文本 tag 限定符 对应标签类型

      u / user

      ACL_USER_OBJ

      u / user

      UID 或名字

      ACL_USER

      g / group

      ACL_GROUP_OBJ

      g / group

      GID 或名字

      ACL_GROUP

      m / mask

      ACL_MASK

      o / other

      ACL_OTHER

      When:用 setfacl 命令直接传短形式最方便;用文件传长形式(setfacl -M)便于版本管理 / 部署。

      Example:传统 0650 的几种等价表达:

      u::rw-,g::r-x,o::---
      u::rw,g::rx,o::-
      user::rw,group::rx,other::-

      扩展 ACL 短形式(命名用户 + 命名组 + mask):

      u::rw,u:paulh:rw,u:annabel:rw,g::r,g:teach:rw,m::rwx,o::-

      17.4 ACL_MASK 与 group class

      What:扩展 ACL 必备 ACL_MASK;它限制 ACL_USERACL_GROUP_OBJACL_GROUP 实际授予的权限(按位与);chmod 修改 group 权限时实际改的是 ACL_MASK。

      Why:让传统 ACL-unaware 工具(chmod g=…​)能与 ACL-aware 工具共存——避免 ACL-unaware 工具破坏 ACL 语义。

      How:ACL_MASK 的设计动机——chmod(path, 0700) 应该「只允许 owner」语义,但扩展 ACL 中有命名用户/组。如果 chmod 直接改 ACL_GROUP_OBJ 与 ACL_USER_OBJ 都不够;如果把全部条目都改成 --- 又会破坏 ACL-aware 程序设置的状态。ACL_MASK 把「chmod 修改 group 位」与「ACL-aware 工具修改具体用户」解耦。

      chmod 与 ACL 交互:

      • chmod 修改 group 权限位 → 实际改 ACL_MASK(不是 ACL_GROUP_OBJ)。

      • stat() 返回的 group 权限位 → 反映 ACL_MASK(不是 ACL_GROUP_OBJ)。

      • ls -l 显示的 group 位 → ACL_MASK。

      反之:ACL-aware 工具的 setfacl -m m::x 直接改 ACL_MASK;setfacl 修改具体条目后默认会调用 acl_calc_mask 把 mask 设为所有 group class 条目的并集。

      When:用 getfacl#effective: 行——它就是 mask 校正后的实际有效权限,比条目本身显示的权限更有参考价值。

      Example

      $ setfacl -m u:paulh:rx,g:teach:x tfile
      $ setfacl -m m::x tfile
      $ getfacl --omit-header tfile
      user::rwx
      user:paulh:r-x                  #effective:--x
      group::r-x                      #effective:--x
      group:teach:--x
      mask::--x
      other::--x

      ACL_MASK 按位与语义再强调#effective = 标签条目权限 AND ACL_MASK。

      17.5 默认 ACL 与文件创建

      What:默认 ACL 仅对目录有效;它决定「新文件/子目录」继承的访问 ACL 与默认 ACL。

      Why:在没有默认 ACL 的目录中创建文件,文件 ACL 来自 mode 与 umask——粒度太粗;默认 ACL 让目录的 ACL 政策「向下传播」。

      How:默认 ACL 的继承规则(§17.6):

      • 新子目录继承父目录的默认 ACL 作为自己的默认 ACL。

      • 新文件/子目录继承父目录的默认 ACL 作为自己的访问 ACL。

      • 继承时把「对应于传统权限位」的条目(ACL_USER_OBJACL_MASK/若缺省则 ACL_GROUP_OBJACL_OTHER)与 open()/mkdir() 等的 mode 参数按位与。

      • 父目录有默认 ACL 时,umask 不影响新文件的访问 ACL。

      When:需要项目目录自动生成「继承上级 ACL 策略」的文件——如 /home/proj 目录下所有新文件自动只允许 proj 组读写。

      Example

      $ mkdir sub
      $ setfacl -d -m u::rwx,u:paulh:rx,g::rx,g:teach:rwx,o::- sub
      $ open("sub/tfile", O_RDWR | O_CREAT, S_IRWXU | S_IXGRP | S_IXOTH)
      # 创建后 tfile 的访问 ACL:
      user::rwx
      user:paulh:r-x                  #effective:--x
      group::r-x                      #effective:--x
      group:teach:rwx                 #effective:--x
      mask::--x
      other::---

      新文件的 mask 由父目录默认 ACL 的 mask 与 mode 共同决定。

      17.6 libacl API 概览

      What:libacl 库提供操纵 ACL 的 C API;以「句柄」(acl_tacl_entry_tacl_permset_t)为核心对象。

      Why:避免直接操纵底层 system.posix_acl_* EA;libacl 负责文本↔in-memory↔磁盘的转换。

      How:核心 API 模式:

      // 摘自《The Linux Programming Interface》第 17 章
      #include <sys/acl.h>
      #include <acl/libacl.h>
      // 编译需 -lacl
      
      // 1. 从磁盘读取
      acl_t acl = acl_get_file(pathname, ACL_TYPE_ACCESS);
      //    ACL_TYPE_ACCESS 或 ACL_TYPE_DEFAULT
      
      // 2. 遍历条目
      for (entryId = ACL_FIRST_ENTRY; ; entryId = ACL_NEXT_ENTRY) {
          if (acl_get_entry(acl, entryId, &entry) != 1) break;
          acl_tag_t tag;
          acl_get_tag_type(entry, &tag);
          /* 按 tag 分发处理 ACL_USER/ACL_GROUP/... */
          acl_permset_t permset;
          acl_get_permset(entry, &permset);
      }
      
      // 3. 创建/修改条目
      acl_create_entry(&acl, &entry);
      acl_set_tag_type(entry, ACL_USER);
      uid_t *uidp = malloc(sizeof(uid_t));
      *uidp = 1000;
      acl_set_qualifier(entry, uidp);
      acl_permset_t permset;
      acl_get_permset(entry, &permset);
      acl_clear_perms(permset);
      acl_add_perm(permset, ACL_READ);
      acl_add_perm(permset, ACL_WRITE);
      
      // 4. 关键:计算 mask(重要:调用后才会自动创建 ACL_MASK)
      acl_calc_mask(&acl);
      
      // 5. 写回磁盘
      acl_set_file(pathname, ACL_TYPE_ACCESS, acl);
      
      // 6. 释放
      acl_free(acl);

      要点:

      • acl_get_entry 返回值:1 = 成功取到条目,0 = 列表结束,-1 = 错误。

      • acl_get_qualifier 返回的指针必须用 acl_free 释放。

      • acl_calc_mask 必须显式调用——创建/修改命名用户/组条目后,mask 不会自动更新。

      • acl_valid 校验 ACL 是否符合规范——必备的 USER_OBJ/GROUP_OBJ/OTHER 各一;扩展 ACL 必备 MASK。

      When:写需要程序化操纵 ACL 的工具——备份系统保留 ACL、批量权限迁移工具。

      Example:检查 ACL 条目的有效权限:

      // 摘自《The Linux Programming Interface》第 17 章(Listing 17-1 简化)
      if (acl_get_perm(permset, ACL_READ))  putchar('r');
      if (acl_get_perm(permset, ACL_WRITE)) putchar('w');
      if (acl_get_perm(permset, ACL_EXECUTE)) putchar('x');

      三、关键图表

      (本章无独立编号图表)

      核心 API 对照表
      API 用途

      acl_get_file / acl_set_file

      从/到磁盘读/写 ACL(type = ACCESS 或 DEFAULT)

      acl_get_entry

      遍历 ACL 条目(FIRST_ENTRY / NEXT_ENTRY)

      acl_create_entry / acl_delete_entry

      创建/删除条目

      acl_get_tag_type / acl_set_tag_type

      读取/设置标签类型

      acl_get_qualifier / acl_set_qualifier

      读取/设置 UID/GID 限定符

      acl_get_permset / acl_set_permset

      读取/设置权限集

      acl_get_perm / acl_add_perm / acl_delete_perm / acl_clear_perms

      操纵单条权限位

      acl_from_text / acl_to_text

      字符串 ↔ in-memory ACL

      acl_calc_mask

      计算并设置 ACL_MASK(重要!)

      acl_valid / acl_check / acl_error

      校验 ACL 合法性

      acl_init / acl_dup / acl_free

      创建/复制/释放 ACL

      acl_delete_def_file

      删除目录的默认 ACL

      六类标签与最小/扩展 ACL
      标签类型 最小 ACL 扩展 ACL

      ACL_USER_OBJ

      必备 1 个

      必备 1 个

      ACL_USER

      -

      0..N

      ACL_GROUP_OBJ

      必备 1 个

      必备 1 个

      ACL_GROUP

      -

      0..N

      ACL_MASK

      -

      必备 1 个

      ACL_OTHER

      必备 1 个

      必备 1 个

      四、思维导图

      mindmap
        root((第 17 章 访问控制列表))
          条目结构
            标签类型 6 类
            USER OBJ GROUP
            MASK OTHER
            限定符 UID GID
            权限集 rwx
          最小扩展 ACL
            最小 ACL 三条目
            扩展 ACL 含 MASK
            ls l 加号标志
          权限检查算法
            特权豁免
            owner USER OBJ
            命名用户 USER MASK
            group class MASK
            other 兜底
          ACL MASK 作用
            限制 group class
            chmod 改 MASK
            stat 返回 MASK
            effective 注释
          默认 ACL
            仅目录有效
            新文件继承访问
            新目录继承默认
            mode 按位与
            umask 失效
          libacl API
            acl t 句柄
            acl get set file
            acl from to text
            acl calc mask
            -lacl 编译选项
          底层存储
            system posix acl access
            system posix acl default
            扩展属性 EA

      五、重点与易错点

      1. Linux ACL 基于 POSIX.1e Draft 17——POSIX.1e 已被撤回,因此 ACL 是「事实标准」;不同 UNIX 实现的 ACL API 细节差异显著,可移植代码需谨慎。

      2. 挂载选项 acl:ext2/3/4、Reiserfs 上使用 ACL 须 mount -o acl;XFS 默认开启。

      3. 最小 vs 扩展 ACL:最小 ACL 仅 USER_OBJ/GROUP_OBJ/OTHER 三条;扩展 ACL 才有命名用户/组 + MASK。ls -l 在有扩展 ACL 时权限位后加 +

      4. ACL_MASK 必备性:扩展 ACL 必须有 ACL_MASK;创建/修改命名用户/组条目后必须显式调用 acl_calc_mask,否则 mask 不会自动更新。

      5. chmod 实际改的是 MASK——而不是 ACL_GROUP_OBJ;stat() 返回的 group 权限位反映 MASK,不是 ACL_GROUP_OBJ。

      6. getfacl#effective:——是 mask 校正后的实际有效权限;排查「ACL 设了权限却访问失败」时看这一行。

      7. 权限检查用 file-system UID,不是 effective UID——这是 ACL 权限检查的特殊性;set-UID 程序要注意这一区别。

      8. 默认 ACL 仅对目录有意义——试图给普通文件设默认 ACL 通常无效或被忽略;用 setfacl -d 命令作用于目录。

      9. 默认 ACL 与 umask:父目录有默认 ACL 时,umask 不影响新文件访问 ACL;权限由「默认 ACL 与 mode 参数按位与」决定。

      10. ACL 条目数量限制:ext2/3/4 ≤ 单块大小 / 8 字节(4K 块约 500 条);XFS ≤ 25 条;Reiserfs/JFS ≤ 8191 条。条目过多影响扫描性能,应通过「设计良好组」控制条目数。

      11. acl_get_qualifier 返回的指针必须 acl_free——否则内存泄漏;这是 libacl 编程的常见 bug。

      12. POSIX.1e 撤回但 libacl 是事实标准——Linux、Solaris、FreeBSD 等都实现了 Draft 17 的子集;详细差异看 acl(5)

      13. ACL 不在 SUSv3/SUSv4 中——是 Linux 特有扩展(虽源自 POSIX.1e);跨 UNIX 平台需要条件编译 #ifdef HAVE_ACL 之类。

      14. 跨章衔接:ACL 底层用扩展属性(system.posix_acl_*,第 16 章);权限检查时 effective UID 与 file-system UID 区分(第 9 章);umask 语义(第 15 章)。