Linux 基础体系 · 第 6/85 篇。示例面向现代主流 Linux 发行版;发行版差异、权限和生产风险会明确说明。
Linux 权限完整指南:UID/GID、mode、umask、ACL 与 capabilities
Linux 权限控制不是单一的“文件有几个 rwx”规则,而是多个层次共同决定的结果:
- 进程以哪个 UID、GID 和附加组运行;
- 文件或目录的所有者、所属组和
mode是什么; - 新对象创建时
umask或默认 ACL 如何参与; - POSIX ACL 是否覆盖了简单的 owner/group/other 判断;
- 进程是否拥有绕过部分 DAC(Discretionary Access Control,自主访问控制)的 capability;
- SELinux/AppArmor、挂载选项、用户命名空间等更高或更低层的约束是否拒绝操作。
因此,“这个用户是 root”或“文件显示为 777”都不足以单独解释一次访问的成败。
一、先区分认证、身份与授权
Linux 中有三个容易混淆的概念:
- 认证(authentication):确认“你是谁”,例如密码、SSH 公钥、PAM、硬件令牌;
- 身份(identity):内核实际用于权限判断的 UID、GID 和附加组;
- 授权(authorization):这个身份能否对目标对象执行读、写、执行、创建、删除等操作。
SSH 登录成功只说明认证阶段通过。sshd 随后会创建或切换到某个用户身份,之后访问文件时仍由内核根据进程凭据、文件元数据和安全模块执行授权检查。sudo 也不是“让某次命令自动成功”,而是通常通过 PAM 和 sudoers 完成授权,再启动一个具有不同凭据的进程。
可以把一次访问粗略表示为:
认证成功
↓
建立进程凭据:UID/GID/附加组/capabilities
↓
路径解析:每级目录需要 traversal 权限
↓
文件 DAC:mode 或 ACL
↓
内核 capability 检查
↓
LSM、挂载选项、文件系统策略
↓
成功或返回 errno
其中任意一层拒绝,最终都可能表现为 EACCES 或 EPERM。
二、UID、GID 与进程凭据
2.1 UID 和 GID 是数字身份
文件系统中保存的是数字 UID 和 GID,而不是用户名和组名。/etc/passwd、/etc/group 或 LDAP/NSS 只是把数字映射为可读名称。
id alice
getent passwd alice
getent group developers
可能得到:
uid=1001(alice) gid=1001(alice) groups=1001(alice),2000(developers)
对内核而言,真正重要的是:
- UID
1001; - 主 GID
1001; - 附加组中是否包含
2000。
如果删除或修改 /etc/passwd 中的用户名,已经存在的文件不会自动改变其 UID。此时 ls -l 可能显示数字:
-rw------- 1 1001 1001 12 file
这不表示权限消失了,只表示当前名称服务无法将数字映射为名字。
2.2 一个进程可能有多个 UID
Linux 进程通常涉及以下 UID:
- real UID(真实 UID):最初启动该进程的用户;
- effective UID(有效 UID):大多数权限检查使用的身份;
- saved set-user-ID:用于保存某个可恢复的有效 UID;
- filesystem UID(fsuid):Linux 文件系统权限检查使用的 UID,通常与 effective UID 相同,但历史上的线程和提权程序可能改变它。
GID 也有类似的 real、effective、saved 和 filesystem GID 概念,并且进程还拥有附加组列表。
查看当前 shell 的身份:
id
grep '^Uid:\|^Gid:\|^Groups:' /proc/$$/status
/proc/<pid>/status 中的 Uid 和 Gid 通常会显示多列数字。工程诊断时,不能只看 ps 显示的用户名;应确认实际的 effective UID、附加组以及 capability。
2.3 附加组如何参与判断
假设文件如下:
-rw-r----- 1 alice project 100 report.txt
其含义是:
- owner:UID 对应的
alice,权限rw-; - group:GID 对应的
project,权限r--; - other:其他身份,权限
---。
一个既不是 alice、又不属于 project 的进程,即使它属于另一个有管理员含义的组,也只能使用 other 类,无法读取该文件。
附加组通常在登录时由 NSS、PAM 等组件建立。修改 /etc/group 后,已存在的登录会话不一定立刻获得新组;重新登录或使用正确的组初始化流程后才会生效。诊断时可以先确认:
id
getent group project
三、路径访问不只检查目标文件
访问 /srv/app/config.yaml 时,内核必须依次解析:
/
└── srv
└── app
└── config.yaml
对每一级目录,进程都需要 执行(x)权限,这里的执行含义是“穿越(search/traverse)目录”,不是运行目录。
目录权限的含义如下:
| 权限 | 对目录的含义 |
|---|---|
r |
列出目录项名称 |
w |
创建、删除、重命名目录项 |
x |
穿越目录,并访问已知名称的目录项 |
典型反例:
mkdir -p /tmp/demo/private
touch /tmp/demo/private/data
chmod 600 /tmp/demo/private/data
chmod 700 /tmp/demo/private
另一个用户即使知道 data 的准确路径,也无法访问它,因为无法穿越 private。反过来,目录有 x 但没有 r 时,用户可能可以访问已知名称的文件,却无法列出目录内容:
chmod 100 /tmp/demo/private
目录中的删除和重命名主要取决于父目录的 w 和 x,而不是目标文件本身是否可写。这是一个常见反例:
touch /tmp/demo/private/data
chmod 000 /tmp/demo/private/data
chmod 700 /tmp/demo/private
目录所有者仍可能删除 data,因为删除操作修改的是父目录中的目录项。
诊断路径权限时,namei 很有用:
namei -l /srv/app/config.yaml
它会逐级显示路径组件的所有者和权限,比只执行 ls -l /srv/app/config.yaml 更容易定位“哪一级目录无法穿越”。
四、mode:传统 Unix 权限位
4.1 mode 的组成
stat 可以显示文件的完整 mode:
stat -c '%A %a %f %U %G %n' file
典型输出:
-rw-r----- 640 81a0 alice project file
ls -l 开头的 10 个字符可以分为:
- rw- r-- ---
│ │ │ └── other
│ │ └────── group
│ └────────── owner
└─────────────文件类型
第一位不是普通权限位:
-:普通文件;d:目录;l:符号链接;c:字符设备;b:块设备;s:套接字;p:FIFO。
随后是 9 个基本权限位:
owner: rwx
group: rwx
other: rwx
数字表示法使用位值:
r = 4
w = 2
x = 1
例如:
0640 = owner 6(rw-)+ group 4(r--)+ other 0(---)
0755 = owner 7(rwx)+ group 5(r-x)+ other 5(r-x)
前面的 0 表示八进制。它不是“权限为 0”,而是为了明确数字解释方式。
4.2 Linux 的权限匹配是“选择一个类”,不是叠加
对没有特殊情况的普通文件,内核通常按以下顺序选择权限类:
- 进程身份匹配文件 owner:使用 owner 位;
- 否则,进程的 effective GID 或附加组匹配文件 group:使用 group 位;
- 否则:使用 other 位。
选择 owner 类后,不会再把 group 和 other 的权限叠加进来。
完整算例:
文件:
UID = 1000
GID = 2000
mode = 0640
进程 A:
UID = 1001
附加组 = {2000}
进程 A 不是 owner,但属于文件组,因此使用 group 的 r--,读取成功,写入失败。
再看进程 B:
进程 B:
UID = 1000
附加组 = {}
即使 owner 位只有 r--,进程 B 也只使用 owner 位;不能因为它还“可能属于某个组”就叠加 group 位。
这也解释了一个常见错误:把一个用户加入文件所属组后,发现仍无法写入。若该用户是文件 owner,则检查已经在 owner 类结束,不会使用 group 类的写权限。
4.3 文件和目录的 x 不同
普通文件的 x 表示允许执行。它并不表示“可以读取源代码”:
chmod 111 program
一个二进制程序可能可以执行但不能读取;脚本则通常需要解释器读取脚本内容,因此“只给脚本 x”不一定能运行,具体还受到解释器、内核执行路径和文件系统实现影响。
目录的 x 表示穿越和搜索,不是运行目录。目录通常需要 r-x 才能“列出并进入”。
4.4 特殊位:setuid、setgid 和 sticky
setuid 文件
普通可执行文件设置 setuid 后,执行时可能把 effective UID 切换为文件 owner:
chmod u+s program
ls -l program
# -rwsr-xr-x ...
例如某些系统程序需要以 root 身份执行少量受控操作。setuid 是权限边界,程序中的任意内存破坏、命令注入或路径竞争都可能变成提权漏洞。
以下因素可能使 setuid 不生效:
- 文件系统以
nosuid挂载; - 安全策略禁用或限制;
- 用户命名空间中的 UID 映射不允许对应的宿主身份;
- 程序本身主动丢弃权限。
Linux 通常不会可靠地对解释器脚本应用 setuid 语义,内核对脚本的解释器处理也存在竞态风险,因此不要用“setuid shell 脚本”实现特权程序。
setgid 文件和目录
setgid 对可执行文件的含义类似:执行时可能切换 effective GID。
对目录而言,setgid 更常用于协作目录:
mkdir shared
chown root:developers shared
chmod 2770 shared
在支持该语义的本地文件系统上,新建对象通常继承父目录的组 developers,而不是创建进程的主组。这可以避免多人协作目录中产生错误的组归属。
sticky 目录
目录设置 sticky 位后:
chmod 1777 /some/shared-dir
目录内的文件通常只能由以下主体删除或重命名:
- 文件 owner;
- 目录 owner;
- root 或具备相应
CAP_FOWNER的进程。
/tmp 常见的 1777 就是“所有人可创建,但不能随意删除其他人的文件”。sticky 位并不阻止读取文件,也不阻止文件 owner 修改自己的文件。
五、umask:创建对象时的默认收紧规则
5.1 基本公式
应用程序创建对象时会传入一个请求 mode。常见默认值是:
- 普通文件:
0666,应用通常不主动请求执行位; - 目录:
0777。
没有默认 ACL 时,可以近似表示为:
实际权限 = 请求 mode & ~umask
其中:
请求 mode是程序传给open(..., O_CREAT, mode)、mkdir(..., mode)等接口的 mode;umask是进程的屏蔽位;~umask表示保留未被屏蔽的位。
例如:
普通文件请求 mode = 0666
umask = 0027
按八进制位计算:
0666
& 0750
= 0640
因此新文件通常为 0640。
目录:
0777
& 0750
= 0750
因此新目录通常为 0750。
查看和设置当前 shell 的 umask:
umask
umask -S
umask 027
umask 是进程属性,会被 fork 继承,通常也会随 exec 保留。shell 中设置的 umask 只影响该 shell 及其后代进程,不会修改系统中所有用户的 umask。
5.2 umask 不是强制访问控制
umask 只影响创建时的初始 mode:
chmod 666 existing-file
创建者或有权限的进程仍可以随后改变 mode。因此,不能把 umask 当成防止泄密的完整策略。
此外,程序可以请求更严格的 mode,也可以在后续调用 chmod。服务管理器、容器运行时和应用框架还可能分别设置自己的默认权限,最终结果应通过 stat 验证,而不是只查看 shell 的 umask。
5.3 并发和临时文件风险
下面这种流程存在竞态:
open("file", O_CREAT | O_WRONLY, 0666)
chmod("file", 0600)
在 open 和 chmod 之间,其他进程可能已经访问或替换该文件。创建敏感文件时,应在创建阶段就传入正确的 mode,或使用安全的临时文件 API,例如 openat 配合 O_CREAT|O_EXCL,并正确检查符号链接、目录权限和错误码。
不要通过临时修改全局 shell 环境来“解决”并发服务的权限问题:
umask 077
some-command
这只改变当前 shell 的后代进程,且不能替代程序自身的安全创建方式。systemd 服务通常可使用 UMask=,但应同时确认服务的 User=、Group=、目录预创建逻辑和应用实际传入的 mode。
5.4 默认 ACL 会改变 umask 的直觉
如果父目录设置了 default ACL,Linux POSIX ACL 文件系统实现会使用该默认 ACL 作为新对象的权限模板。此时不能简单地只用 请求 mode & ~umask 推导最终结果;请求 mode 仍会限制继承权限中相应的类别,但 umask 不再是唯一控制因素。
因此,在发现“umask 是 077,新文件却有额外 ACL”时,应检查:
getfacl parent-dir
getfacl new-file
六、ACL:在 mode 之外授予特定用户或组
6.1 为什么需要 ACL
传统 mode 只能表达:
一个 owner
一个 group
其他所有人
如果需求是:
alice 可以读写;
bob 只能读;
developers 组可以读;
其他人无权限
单个 group 位无法同时表达多个独立主体,这时可使用 POSIX ACL。
确认工具是否安装:
command -v getfacl
command -v setfacl
文件系统和发行版必须支持 POSIX ACL;现代主流 Linux 的常见本地文件系统通常支持,但挂载方式、网络文件系统和特殊存储实现可能不同。
6.2 ACL 的基本条目
创建示例:
mkdir acl-demo
touch acl-demo/report
chmod 640 acl-demo/report
setfacl -m u:alice:rw,u:bob:r acl-demo/report
getfacl acl-demo/report
可能看到:
user::rw-
user:alice:rw-
user:bob:r--
group::r--
mask::rw-
other::---
各条目含义:
user:::文件 owner;user:alice::指定用户;group:::文件所属组;group:name::指定组;mask:::ACL group class 的上限;other:::其他用户。
6.3 ACL mask 是最容易误读的部分
对 named user、named group 和 owning group,实际权限还要与 mask 相交:
实际权限 = 条目权限 & ACL mask
例如:
user:alice:rwx
mask::r-x
Alice 的实际权限是:
rwx & r-x = r-x
因此 getfacl 可能显示:
user:alice:rwx #effective:r-x
完整算例:
user::rw-
user:alice:rwx
group::r--
mask::r--
other::---
- owner 的实际权限:
rw-; - Alice 的条目请求:
rwx; - mask:
r--; - Alice 最终只有
r--; - owning group 的
r--也受到 mask 限制; - other 为
---。
ACL 中的 mask 不是“某一个额外用户”,而是 group class 的权限上限。使用 chmod g-w file 时,Linux 通常会修改 ACL 的 mask,而不是只修改 group:: 条目:
chmod g-w acl-demo/report
getfacl acl-demo/report
这可能同时降低多个 named user 和 named group 的有效权限。因此,修改带 ACL 文件的 mode 后必须重新查看 ACL。
6.4 ACL 的匹配顺序
对请求者进行 ACL 判断时,可以按以下逻辑理解:
- 如果 UID 匹配
user::的 owner,使用 owner 条目; - 否则如果存在匹配的 named user,使用该条目并受 mask 限制;
- 否则合并 owning group 和所有匹配的 named group 条目,再受 mask 限制;
- 如果没有任何匹配的用户或组,使用
other::。
关键点是:如果请求者是文件 owner,不会因为其同时属于某个 named group 就叠加组权限。
6.5 默认 ACL:让新对象继承访问规则
目录可以设置 default ACL:
mkdir project
setfacl -m u:alice:rwx,u:bob:r-x project
setfacl -d -m u::rwx,u:alice:rwx,u:bob:r-x,g::r-x,m::rwx,o::--- project
getfacl project
之后在目录中创建:
touch project/new-file
mkdir project/new-dir
getfacl project/new-file
getfacl project/new-dir
新对象会继承父目录的 default ACL,并结合创建时请求的 mode 形成访问 ACL。目录还会继续拥有自己的 default ACL,以便后代对象继续继承。
风险在于:只执行 chmod 750 project 并不能说明目录下所有对象都符合预期。应使用:
getfacl -R project
审查 ACL 时,尤其要注意:
mask是否意外放宽;- 是否存在不再需要的 named user;
- 删除或复用 UID 后,旧 ACL 是否仍指向同一个数字 UID;
- 备份、打包和跨文件系统复制工具是否保留了 ACL。
七、capabilities:把 root 的部分权限拆开
7.1 为什么需要 capabilities
传统 Unix 模型中,UID 0 拥有大量特殊权限。Linux capabilities 将这些特权拆成若干独立能力,使服务可以不以 root 身份运行,却保留完成特定任务所需的一小部分权限。
常见 capability 包括:
CAP_DAC_OVERRIDE:绕过部分文件读、写、执行 DAC 检查;CAP_DAC_READ_SEARCH:绕过部分读取文件和搜索目录的 DAC 检查;CAP_CHOWN:改变文件 owner 和 group 的部分限制;CAP_FOWNER:绕过某些必须是 owner 的检查;CAP_SETUID:改变 UID;CAP_SETGID:改变 GID 和组列表的部分限制;CAP_NET_BIND_SERVICE:绑定小于 1024 的 TCP/UDP 端口;CAP_NET_ADMIN:执行部分网络管理操作;CAP_SYS_ADMIN:范围很大,常被认为接近“新的 root”,不应轻率授予。
capability 不是普通文件权限的替代品,也不是强制访问控制。一个进程拥有 CAP_NET_BIND_SERVICE,并不意味着它可以读取任意文件;一个进程拥有 CAP_DAC_OVERRIDE,也不代表它绕过 SELinux 的所有规则。
7.2 进程 capability 集合
Linux 进程通常涉及:
- Permitted(P):允许进入 Effective 的上限;
- Effective(E):当前实际用于 capability 检查;
- Inheritable(I):允许在 execve 时参与继承;
- Ambient(A):现代 Linux 中用于让非特权 exec 保留部分能力;
- Bounding(B):系统或进程允许通过 exec 获得的上限。
查看当前 shell:
grep '^Cap' /proc/$$/status
capsh --print
/proc 中通常显示十六进制位图,capsh --print 更容易阅读,但需要安装 libcap 相关工具。
这些集合不是静态全局变量,而是每个线程的凭据状态。一个特权程序可以在启动后主动删除不需要的能力,以缩小攻击面。
7.3 文件 capability
Linux 可以把 capability 写入文件的 extended attribute,常见工具是 setcap 和 getcap:
cp /usr/bin/python3 /tmp/python-test
setcap cap_net_bind_service=ep /tmp/python-test
getcap /tmp/python-test
=ep 中:
e:执行该文件时把对应 capability 放入 Effective;p:执行该文件时把对应 capability 放入 Permitted。
文件 capability 的实际语义还受以下因素影响:
- 文件系统是否支持
security.capabilityxattr; - 挂载是否使用
nosuid; - capability 是否在当前 user namespace 中有意义;
- exec 的目标是否为“特权文件”;
- 进程 bounding set 是否已经删除该能力。
不要把 capability 直接加给解释器或功能复杂的程序。给 Python、Perl、通用编辑器或网络工具授予 CAP_DAC_OVERRIDE,实际上可能允许其运行任意代码并绕过文件 DAC,效果接近扩大 root 权限。
另外,文件 capability 的复制、打包和部署行为依赖工具和文件系统。发布后应验证:
getcap /path/to/program
getfattr -n security.capability /path/to/program
7.4 execve 时 capability 如何变化
为了理解 capability 的生命周期,设:
P:exec 前进程的 permitted;I:exec 前进程的 inheritable;A:exec 前进程的 ambient;B:bounding set;F_P:文件 permitted;F_I:文件 inheritable;F_E:文件 effective 标志。
在简化模型下,exec 后的 permitted 集合可表示为:
P' = (F_P & B) | (I & F_I) | A'
其中:
- 文件 permitted 受 bounding set 限制;
- 进程 inheritable 与文件 inheritable 相交后可以贡献能力;
- 非特权文件 exec 时,ambient 能力可以继续贡献;
- 执行特权文件时,ambient 通常被清空。
exec 后的 effective 集合通常取决于文件的 effective 标志:
E' = F_E ? P' : A'
这不是应用程序可以随意“凭空获得能力”的机制。进程必须已经在相应的 permitted、inheritable 或 ambient 路径上拥有能力,并且还要受到 bounding set、namespace 等约束。
设置了 setuid、文件 capability 或其他特权属性的文件被视为特权 exec 对象。对这类对象,环境变量和 ambient 能力会受到更严格处理,这是为了避免普通用户通过环境继承把能力带入特权程序。
八、root 不是绕过一切的万能身份
UID 0 通常拥有大量 capability,但现代 Linux 中实际特权仍受多种机制限制:
- 普通文件没有执行位时,root 也通常不能通过
execve直接执行它; nosuid可以阻止 setuid 和文件 capability 生效;- SELinux/AppArmor 可以拒绝 root 进程;
- 用户命名空间中的 UID 0 不一定对应宿主机 UID 0;
- NFS root squash 可以把客户端 root 映射为匿名用户;
- 加密、设备策略、LSM 和容器隔离可能进一步限制访问;
- 文件系统故障、只读挂载和 quota 可能导致即使权限正确也无法写入。
需要区分错误类型:
EACCES:通常表示权限检查拒绝;EPERM:通常表示操作不被允许,可能是 capability、所有权、LSM 或特定操作限制;EROFS:文件系统只读;ENOSPC:空间或 inode 耗尽;EDQUOT:quota 超限;ENOENT:路径组件不存在,或某一级路径无法正常解析。
sudo command 失败时,不能只继续增加权限。应先确认命令到底失败在路径穿越、文件 DAC、ACL、LSM、挂载状态还是资源限制。
九、DAC、ACL、capability 与 SELinux/AppArmor 的关系
mode 和 ACL 属于 DAC:文件 owner 或管理员可以改变它们。capability 则决定进程能否绕过某些 DAC 检查。
但在启用了 LSM 的系统上,最终访问还可能经过 SELinux、AppArmor 等强制访问控制机制。典型结果是:
mode 允许
↓
ACL 允许
↓
进程 capability 足够
↓
SELinux/AppArmor 拒绝
因此:
ls -l file
getfacl file
只能证明 DAC 层面的状态,不能证明访问一定成功。
在 SELinux 系统上可以检查:
getenforce
ls -Z file
ausearch -m avc -ts recent
在 AppArmor 系统上,应检查 profile 状态和内核日志。不同发行版启用的 LSM、日志位置和诊断命令存在差异,生产环境应以实际安全策略为准。
挂载选项也属于重要边界:
findmnt -T /path/to/file
mount | grep nosuid
常见影响包括:
ro:只读;nosuid:禁止 setuid/setgid 和文件 capability 的特权效果;noexec:限制从该挂载点直接执行程序;nodev:不解释设备节点;idmap或用户命名空间相关映射:改变看到的 UID/GID 关系。
十、用户命名空间与容器中的 UID 误区
容器内显示的 UID 0,不一定是宿主机 UID 0。用户命名空间可以把容器内 UID 映射为宿主机上的非零 UID。例如:
容器 UID 0 → 宿主机 UID 100000
容器 UID 1 → 宿主机 UID 100001
此时容器内的 root capability 通常只在该 user namespace 内有效,不能自动获得宿主机全局 root 权限。
但容器挂载宿主目录时,映射错误仍可能导致严重问题:
- 宿主机文件显示为容器内某个用户;
- 容器进程创建的文件以意外 UID 留在宿主机;
- 使用
--privileged或大量 capability 会显著削弱隔离; - 共享目录的 ACL、setuid 和设备节点可能产生跨边界影响。
诊断容器权限时,要同时查看:
id
cat /proc/self/uid_map
cat /proc/self/gid_map
grep Cap /proc/self/status
findmnt
不能只依据容器内的 ls -l 判断宿主机真实权限。
十一、权限检查的完整算例
假设有:
文件 /srv/data/report
UID = 1000
GID = 2000
mode = 0660
ACL:
user::rw-
user:alice:r--
group::rw-
group:auditors:r--
mask::rw-
other::---
考虑四个进程:
情况 A:UID 1000 的 owner
进程 UID = 1000
匹配 user::rw-,拥有读写。即使该进程不属于 GID 2000,也不影响 owner 类选择。
情况 B:UID 1001,属于 auditors
进程 UID = 1001
附加组 = {auditors}
匹配 named group auditors:r--,再与 mask rw- 相交:
r-- & rw- = r--
因此只能读。
情况 C:UID alice,匹配 named user
ACL user:alice:r--
如果 alice 同时属于一个有 rw- 的组,仍优先使用 named user 条目,最终只有 r--,不会和组权限叠加。
情况 D:不匹配任何 ACL 条目
使用:
other::---
访问失败。
如果此时给进程授予 CAP_DAC_OVERRIDE,部分 DAC 读写检查可能被绕过,但 SELinux、只读挂载、文件系统错误等仍可能拒绝。这个结果不能简单归纳为“有 capability 就肯定成功”。
十二、创建安全目录和协作目录的端到端示例
12.1 私密服务目录
以下命令需要拥有创建目录和设置 owner 的权限:
sudo install -d -o app -g app -m 0750 /var/lib/myapp
sudo install -d -o app -g app -m 0700 /var/lib/myapp/secrets
sudo -u app sh -c 'umask 077; printf "%s\n" secret > /var/lib/myapp/secrets/token'
sudo stat -c '%A %a %U %G %n' /var/lib/myapp/secrets/token
预期类似:
-rw------- 600 app app /var/lib/myapp/secrets/token
每一步的作用是:
install -d在创建目录时直接指定 owner、group 和 mode,避免“先宽后改”的窗口;umask 077使应用创建的普通文件默认不向 group 和 other 暴露;printf创建普通文件时通常请求0666,经077收紧后为0600;stat验证实际结果,而不是假设结果。
若应用自行调用 chmod,最终权限仍可能不同;还应检查父目录是否被其他用户替换或可写。
12.2 共享项目目录
sudo install -d -o root -g developers -m 2770 /srv/project
sudo setfacl -m g:auditors:rx /srv/project
sudo setfacl -d -m u::rwx,g::rwx,g:auditors:r-x,m::rwx,o::--- /srv/project
getfacl /srv/project
设计含义:
2770的2设置目录 setgid,使新对象继承developers组;- owner 和 group 可读写穿越,other 无权限;
- named group
auditors可以读取和穿越; - default ACL 让后续对象继承协作规则;
- default ACL 中的 mask
rwx允许 group class 的权限生效。
随后验证:
sudo -u developer touch /srv/project/a.txt
getfacl /srv/project/a.txt
不要只检查目录本身。应确认新文件的 owner、group、ACL 和应用实际创建的 mode 是否符合要求。
十三、常见失败表现与诊断顺序
13.1 “我属于这个组,但仍然不能写”
先检查:
id
stat -c '%A %a %u %g %U %G %n' file
getfacl file
重点判断:
- 当前进程是否真的拥有该附加组;
- 文件 owner 是否恰好是当前用户,导致使用 owner 位;
- ACL 的 mask 是否去掉了写权限;
- 父目录是否有
w和x; - SELinux/AppArmor 是否拒绝;
- 文件系统是否只读或 quota 已满。
13.2 “文件是 777,仍然 Permission denied”
可能原因包括:
- 路径上某个父目录没有
x; - SELinux/AppArmor 拒绝;
- 挂载点为
noexec; - 文件系统只读;
- NFS 身份映射或 root squash;
- 目标不是普通文件,操作语义不同;
- 进程在不同 user namespace 中;
- 应用访问的是另一个路径或符号链接目标。
可以使用:
namei -l /path/to/file
findmnt -T /path/to/file
getfacl /path/to/file
ls -Z /path/to/file
strace -e trace=%file,chmod,fchmod,fchown command
strace 的价值在于确认应用实际访问了哪个路径,以及内核返回了什么 errno;它不能绕过安全策略,也不应在包含敏感参数的生产命令上无条件启用。
13.3 “chmod 后 ACL 权限变了”
这是因为 chmod 与 POSIX ACL 不是两套完全独立的权限。尤其是 group 类 mode 位通常对应 ACL mask。修改后立即检查:
chmod g-w file
getfacl file
如果目标是精确管理 ACL,应使用 setfacl 明确修改条目和 mask,而不是混用大量 chmod 操作。
13.4 “服务启动后权限和手工执行不同”
服务通常具有不同的:
User=、Group=;- supplementary groups;
UMask=;- capability bounding/effective 集合;
- 工作目录和环境变量;
- mount namespace;
- SELinux/AppArmor 域或 profile。
应比较服务进程与交互式 shell:
ps -o pid,user,group,egroup,cmd -p "$PID"
cat /proc/"$PID"/status | grep -E '^(Uid|Gid|Groups|Cap)'
cat /proc/"$PID"/mountinfo
服务启动成功不代表其后续访问都成功;很多程序会延迟打开日志、证书、socket 或数据文件,错误可能在运行一段时间后才出现。
十四、生产中的权限取舍
14.1 最小权限应落实到对象和 capability
“以非 root 用户运行”只是起点。还要明确:
- 服务需要访问哪些目录;
- 哪些目录只需穿越,哪些需要列出;
- 哪些文件需要读,哪些需要写;
- 是否需要创建、删除、重命名;
- 是否需要绑定低端口或操作网络;
- 是否可删除不需要的 permitted、effective 和 bounding capabilities。
例如,一个只需监听 443 端口的服务,通常比直接以 root 运行更适合评估 CAP_NET_BIND_SERVICE。但不能把这个 capability 扩展为 CAP_DAC_OVERRIDE,也不能忽略服务自身的文件、网络和 LSM 策略。
14.2 不要把 ACL 当作审计记录
ACL 表示当前授权状态,不记录“谁在什么时候改过权限”。权限变更审计需要 auditd、文件完整性监控、配置管理系统或版本化部署流程。类似地,sudo 的授权规则和执行日志属于提权审计范畴,不等同于文件 ACL。
14.3 备份和迁移必须验证元数据
普通复制命令可能不保留所有权限元数据。根据工具和参数不同,以下内容可能丢失:
- owner 和 group;
- mode 特殊位;
- POSIX ACL;
- extended attributes;
- 文件 capability;
- SELinux label。
迁移后至少检查:
stat file
getfacl file
getcap file
ls -Z file
对跨主机、NFS、容器卷和对象存储尤其要验证,因为这些介质不一定提供与本地 POSIX 文件系统相同的语义。
十五、应建立的权限判断模型
遇到权限问题时,可以按这个顺序推导,而不是直接尝试 chmod 777:
- 目标是什么:普通文件、目录、符号链接、设备、socket,还是路径中的某一级目录;
- 进程是谁:effective UID、GID、附加组、user namespace 映射;
- 路径能否穿越:每一级父目录是否具备所需的
x; - 是否存在 ACL:owner、named user/group、mask、other 如何匹配;
- 传统 mode 如何选择:owner、group、other 三类不会叠加;
- 创建还是访问:创建权限受请求 mode、umask、default ACL 影响;
- 是否存在 capability:尤其是
CAP_DAC_*、CAP_FOWNER、CAP_CHOWN; - 挂载和文件系统是否限制:
ro、nosuid、noexec、NFS 映射、quota; - LSM 是否拒绝:SELinux/AppArmor 日志通常能给出额外证据;
- 应用实际访问路径和系统调用是什么:必要时用
strace验证。
权限的核心不是记住某个命令,而是能够从进程凭据、路径解析、ACL/mode 选择、capability 和安全策略逐层推导出最终结果。这样才能解释“看起来权限正确却失败”的边界,也才能在生产环境中用最小授权替代不可控的全局放权。
系列导航与关联阅读
- 系列入口:Linux 完整学习路线:从内核与文件系统到网络、性能和生产运维
- 上一篇:Linux 文件、Inode 与链接:描述符、硬链接、符号链接和删除语义
- 下一篇:Linux 用户认证与提权:账号、PAM、sudo、密码策略和审计
- 延伸:Linux 主机安全加固:最小化、SELinux/AppArmor、审计与漏洞治理
官方资料
本文依据 Linux 内核、systemd 与主流发行版官方文档重新梳理;正文与实验由 WR BLOG 编写。

评论
0 条讨论