第 17 章 访问控制列表 (Access Control Lists)
核心结论
-
ACL 概念:ACL 是一系列「条目」的有序集合;每个条目定义一个特定用户或组的文件权限;Linux 自 2.6 内核起支持。
-
六类标签类型:
ACL_USER_OBJ、ACL_USER、ACL_GROUP_OBJ、ACL_GROUP、ACL_MASK、ACL_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 时需 |
一、核心概念
本章围绕 6 个核心概念展开:从 ACL 条目结构入手,再到权限检查算法、ACL_MASK 的语义、默认 ACL 继承、最后到 libacl API。
| 概念 | 定义 + 重要性 | 实现提示 |
|---|---|---|
ACL 条目与标签类型 |
ACL = 一组条目的有序集合;每条目 = (标签类型, 可选标签限定符, 权限集);六类标签覆盖 owner / 命名用户 / owning group / 命名组 / mask / other。 |
§17.1; |
最小与扩展 ACL |
最小 ACL = |
§17.1; |
权限检查算法 |
按 owner → 命名用户 → group class(group_obj 与命名组按 mask)→ other 顺序匹配;至少有一个匹配满足请求权限才允许。 |
§17.2;优先级:单进程最多匹配一条 ACL_USER;若请求的权限被 mask 清除则即便条目列出该权限也无效;group class 中按所有匹配 GID 累加有效权限。 |
ACL_MASK 与 group class |
ACL_MASK 是扩展 ACL 的「上限」——限制 |
§17.4;这一设计是为了兼容传统 |
默认 ACL (Directory inheritance) |
默认 ACL 只对目录有意义;目录有默认 ACL 时,新创建的文件/子目录继承它为访问 ACL;新子目录同时继承默认 ACL 本身。 |
§17.6;继承时把「对应于传统权限位」的 ACL 条目(USER_OBJ、MASK/若缺省则 GROUP_OBJ、OTHER)与 |
libacl API |
操纵 ACL 的 C 库 API:以 |
§17.8;编译需 |
二、详细笔记
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 恰好 1 个 |
文件 owner(对应传统 owner 权限位) |
|
0..N,每 UID 至多 1 条 |
命名用户条目 |
|
每个 ACL 恰好 1 个 |
owning group(对应传统 group 位,但被 MASK 覆盖) |
|
0..N,每 GID 至多 1 条 |
命名组条目 |
|
0 或 1 个(扩展 ACL 必备) |
限制 ACL_USER/ACL_GROUP_OBJ/ACL_GROUP 的实际权限 |
|
每个 ACL 恰好 1 个 |
其他用户(对应传统 other 位) |
最小 ACL 仅含 ACL_USER_OBJ + ACL_GROUP_OBJ + ACL_OTHER;扩展 ACL 额外含 ACL_USER/ACL_GROUP/ACL_MASK。
When:需要为「传统 9 位权限表达不了」的场景——多个独立用户对同一文件有不同权限;项目组多层级权限细分。
Example:ls -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 限定符 | 对应标签类型 |
|---|---|---|
|
无 |
ACL_USER_OBJ |
|
UID 或名字 |
ACL_USER |
|
无 |
ACL_GROUP_OBJ |
|
GID 或名字 |
ACL_GROUP |
|
无 |
ACL_MASK |
|
无 |
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_USER、ACL_GROUP_OBJ、ACL_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_OBJ、ACL_MASK/若缺省则ACL_GROUP_OBJ、ACL_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_t、acl_entry_t、acl_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 对照表
|
|
六类标签与最小/扩展 ACL
|
四、思维导图
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
五、重点与易错点
-
Linux ACL 基于 POSIX.1e Draft 17——POSIX.1e 已被撤回,因此 ACL 是「事实标准」;不同 UNIX 实现的 ACL API 细节差异显著,可移植代码需谨慎。
-
挂载选项
acl:ext2/3/4、Reiserfs 上使用 ACL 须mount -o acl;XFS 默认开启。 -
最小 vs 扩展 ACL:最小 ACL 仅 USER_OBJ/GROUP_OBJ/OTHER 三条;扩展 ACL 才有命名用户/组 + MASK。
ls -l在有扩展 ACL 时权限位后加+。 -
ACL_MASK 必备性:扩展 ACL 必须有 ACL_MASK;创建/修改命名用户/组条目后必须显式调用
acl_calc_mask,否则 mask 不会自动更新。 -
chmod实际改的是 MASK——而不是 ACL_GROUP_OBJ;stat()返回的 group 权限位反映 MASK,不是 ACL_GROUP_OBJ。 -
getfacl的#effective:行——是 mask 校正后的实际有效权限;排查「ACL 设了权限却访问失败」时看这一行。 -
权限检查用 file-system UID,不是 effective UID——这是 ACL 权限检查的特殊性;set-UID 程序要注意这一区别。
-
默认 ACL 仅对目录有意义——试图给普通文件设默认 ACL 通常无效或被忽略;用
setfacl -d命令作用于目录。 -
默认 ACL 与 umask:父目录有默认 ACL 时,umask 不影响新文件访问 ACL;权限由「默认 ACL 与 mode 参数按位与」决定。
-
ACL 条目数量限制:ext2/3/4 ≤ 单块大小 / 8 字节(4K 块约 500 条);XFS ≤ 25 条;Reiserfs/JFS ≤ 8191 条。条目过多影响扫描性能,应通过「设计良好组」控制条目数。
-
acl_get_qualifier返回的指针必须acl_free——否则内存泄漏;这是 libacl 编程的常见 bug。 -
POSIX.1e 撤回但 libacl 是事实标准——Linux、Solaris、FreeBSD 等都实现了 Draft 17 的子集;详细差异看
acl(5)。 -
ACL 不在 SUSv3/SUSv4 中——是 Linux 特有扩展(虽源自 POSIX.1e);跨 UNIX 平台需要条件编译
#ifdef HAVE_ACL之类。 -
跨章衔接:ACL 底层用扩展属性(
system.posix_acl_*,第 16 章);权限检查时 effective UID 与 file-system UID 区分(第 9 章);umask 语义(第 15 章)。