Linux 的权限系统看起来很朴素:用户、用户组、rwx 三组权限位。可是只要真的排查过“为什么这个进程能写这个文件”“为什么 root 装出来的文件被某个用户拿不到”“为什么目录有读权限却进不去”,就会发现它并不只是 chmod 755 这么简单。

这篇文章不从某个项目出发,而是从 Linux 自身的模型出发,梳理用户、用户组、进程身份、文件权限、目录权限、ACL、setuid/setgid/sticky bit,以及一次访问检查大致是如何发生的。

一、用户不是名字,而是 UID

我们平时说的 rootnginxmysqltest,本质上只是名字。内核真正识别的是数字 ID:

1
2
UID: user id
GID: group id

/etc/passwd 里保存的是用户基本信息:

1
test:x:982:982::/opt/test-agent:/sbin/nologin

字段拆开是:

1
用户名:密码占位:UID:GID:描述:home:shell

这里真正决定身份的是:

1
2
uid = 982
gid = 982

用户名只是用户态工具展示和解析时使用的名字。比如 ls -l 看到的是:

1
-rw-r--r-- 1 test test 1024 config.yaml

但文件系统 inode 里记录的是 UID/GID 数字。ls 只是把数字反查成名字。如果系统里没有对应名字,就可能显示数字。

这也解释了一个常见现象:同一个磁盘挂到另一台机器上,如果 UID 1001 在机器 A 叫 app,在机器 B 叫 deploy,那么文件显示出来的 owner 名字会变,但底层数字没有变。

二、用户组:权限复用的集合

用户组解决的是“多个用户共享同一组权限”的问题。

/etc/group 里一行大概长这样:

1
test:x:982:alice,bob

它表示:

1
组名:密码占位:GID:附加成员列表

一个进程通常有:

1
2
primary group
supplementary groups

可以用 id 查看:

1
id test

输出可能是:

1
uid=982(test) gid=982(test) groups=982(test),1001(app)

这里:

1
2
gid=982(test)        primary group
groups=... supplementary groups

当进程访问文件时,内核会看进程是否匹配文件 owner,或者进程的任一 group 是否匹配文件 group。这个判断发生在内核权限检查阶段,而不是 shell 里。

三、进程身份:比“当前用户”更细

一个登录用户启动进程后,进程会带着一组 credentials。粗略说包括:

1
2
3
4
real UID / real GID
effective UID / effective GID
saved UID / saved GID
supplementary groups

平时权限检查主要看 effective UID/GID 和 supplementary groups。

为什么要区分 real 和 effective?因为 setuid 程序需要临时以文件 owner 的身份运行。最典型的是 passwd:普通用户需要修改自己的密码,但密码数据库不是普通用户可写的。于是 passwd 程序通常带 setuid root 位,让它执行时获得 root 的 effective UID。

可以看一下:

1
ls -l /usr/bin/passwd

常见输出类似:

1
-rwsr-xr-x 1 root root ... /usr/bin/passwd

owner 执行位上的 s 就是 setuid。

这也是为什么“谁启动了进程”和“进程以什么身份做访问检查”不是永远一回事。

四、文件权限位:rwx 的三组语义

经典权限模型是三组:

1
2
user  group  other
rwx rwx rwx

比如:

1
-rw-r----- 1 root test 1024 config.yaml

可以拆成:

1
2
3
owner: root -> rw-
group: test -> r--
other: everyone else -> ---

对普通文件来说:

1
2
3
r: 可以读取文件内容
w: 可以修改文件内容
x: 可以把文件作为程序执行

访问时大致按这个顺序判断:

  1. 如果进程是 root,通常直接通过普通 DAC 检查。
  2. 如果进程 effective UID 等于文件 owner UID,用 owner 位。
  3. 否则如果进程任一 group 匹配文件 group GID,用 group 位。
  4. 否则用 other 位。

注意,这不是三组权限叠加。命中 owner 后就看 owner 位,不会再因为 group 或 other 更宽而补权限。

例如:

1
----rwx--- 1 test app file

如果进程用户就是 test,会命中 owner 位 ---,即使 group 位是 rwx,也不会因此获得权限。

五、目录权限:x 比 r 更关键

目录的 rwx 和普通文件不一样。

对目录来说:

1
2
3
r: 可以列出目录项名字,比如 ls
w: 可以在目录中创建、删除、重命名目录项
x: 可以穿越目录,访问目录下的路径

最容易误解的是 x。目录没有 x,你即使知道里面文件名,也访问不了。

例如:

1
chmod 600 /tmp/demo-dir

这个目录 owner 有读写,但没有执行权限。结果可能是:

1
2
3
4
ls /tmp/demo-dir
# Permission denied
cat /tmp/demo-dir/file
# Permission denied

对目录而言,常见可用权限是:

1
2
3
755: 所有人可进入和列出,只有 owner 可写
750: owner 和 group 可进入,other 不能进入
700: 只有 owner 可进入

还有一个很重要的点:删除一个文件,需要的是父目录的写权限和执行权限,而不是文件本身的写权限。

也就是说,即使一个文件是:

1
-r--r--r-- 1 root root file

如果你对它所在目录有 wx,你仍然可能删除这个文件。删除本质上是修改父目录的目录项。

六、root 为什么“无视权限”

我们经常说 root 可以做任何事。更准确地说,root 进程通常拥有绕过 DAC 权限检查的能力。

DAC 是 discretionary access control,也就是传统的 owner/group/other 权限模型。root 不是因为文件 owner 是 root 才强,而是因为 root 拥有一组特权能力。

现代 Linux 把 root 的超级能力拆成 capabilities,比如:

1
2
3
4
CAP_DAC_OVERRIDE
CAP_CHOWN
CAP_SETUID
CAP_NET_ADMIN

其中 CAP_DAC_OVERRIDE 可以绕过普通文件读写执行权限检查,CAP_CHOWN 可以修改文件属主。

所以如果文件是:

1
-rw-r--r-- 1 test test config.yaml

root 仍然可以写、删、改 owner。owner 改成 test 不会削弱 root;它增加的是 test 对文件的控制能力。

七、setuid、setgid、sticky bit

除了 rwx,权限位里还有三个特殊位。

setuid

作用在可执行文件上。程序执行时,进程 effective UID 变成文件 owner。

1
-rwsr-xr-x 1 root root /usr/bin/passwd

这让普通用户可以运行某些受控的 root 权限程序。

setgid

作用在文件上时,类似 setuid,只是影响 group。

作用在目录上时更常见:目录下新建文件继承目录的 group,而不是进程的 primary group。

1
chmod g+s /srv/shared

这在共享目录里很常用,可以避免一堆文件 group 混乱。

sticky bit

最典型的是 /tmp

1
drwxrwxrwt root root /tmp

目录所有人都能写,但 sticky bit 规定:只有文件 owner、目录 owner 或 root 能删除目录里的文件。

否则 /tmp 这种世界可写目录会变得不可用,因为任何人都能删别人的临时文件。

八、umask:创建文件时先扣掉一层权限

程序创建文件时会给一个初始 mode,比如普通文件常见是 0666,目录常见是 0777。但真正落盘前会被 umask 扣掉。

例如:

1
umask 022

那么:

1
2
文件: 0666 & ~0022 = 0644
目录: 0777 & ~0022 = 0755

这就是为什么大多数新文件默认是 rw-r--r--,而目录默认是 rwxr-xr-x

如果 umask 是 077,新文件就会更严格:

1
2
文件: 0600
目录: 0700

理解 umask 对排查“为什么我明明 mkdir 了目录,但其他用户进不去”非常重要。

九、ACL:当三组权限不够用

传统权限只有 owner、group、other 三组。如果你想给某个额外用户写权限,但又不想改 owner/group,就需要 ACL。

查看 ACL:

1
getfacl file

设置某个用户权限:

1
setfacl -m u:alice:rw- file

设置某个组权限:

1
setfacl -m g:app:rw- file

目录递归授权时常见:

1
2
setfacl -R -m u:alice:rwX dir
setfacl -R -d -m u:alice:rwX dir

这里的 X 是条件执行:

1
2
3
目录会获得 x
已有执行位的文件会获得 x
普通文件不会凭空变成可执行

所以递归处理目录树时,rwXrwx 更安全。

ACL 里还有一个很容易踩坑的字段:mask。

1
2
user:alice:rw-     #effective:r--
mask::r--

这表示虽然给了 alice rw-,但 mask 把实际权限裁成了 r--。如果希望 named user/group 真正可写,必须确保 mask 包含写位:

1
setfacl -m u:alice:rw-,m::rw- file

十、一次访问检查大概怎么发生

把前面的东西串起来,进程访问一个文件时,大致要经过这些关卡。

第一,路径解析。

访问 /a/b/c.txt,不是只检查 c.txt。进程必须能穿越 //a/a/b 这些目录。中间任何一个目录缺少 x,都会失败。

第二,处理 symlink。

如果路径中有符号链接,内核会解析到目标路径。安全程序不能只看字符串路径,否则可能被 symlink 绕过。

第三,检查文件系统权限。

内核根据进程 credentials、文件 UID/GID、mode bits、ACL、capabilities 做 DAC 检查。

第四,检查额外安全机制。

如果系统启用了 SELinux、AppArmor、mount option、只读文件系统、immutable bit 等,还会有额外限制。普通 chmod 777 不一定能突破这些机制。

例如 immutable 文件:

1
chattr +i file

即使 root 也不能普通删除或修改,必须先:

1
chattr -i file

所以“权限不够”不总是 chmod 能解决。

十一、几个排查命令

看身份:

1
2
id
id username

看用户和组数据库:

1
2
getent passwd username
getent group groupname

看文件 owner/mode:

1
stat -c %U:%G %A %a %n path

看 ACL:

1
getfacl -p path

看路径每一级权限:

1
namei -l /a/b/c.txt

看进程身份:

1
ps -o user,group,euser,egroup,supgrp,pid,cmd -p <pid>

看 capabilities:

1
getpcaps <pid>

这些命令组合起来,比盲目 chmod -R 777 靠谱得多。

小结

Linux 权限不是一个 chmod 命令,而是一套身份和访问检查模型:用户名字最终落到 UID,用户组最终落到 GID,进程带着 credentials 访问 inode,文件权限、目录权限、ACL、capabilities 和额外安全模块共同决定结果。

真正理解这套机制之后,很多问题会变清楚:为什么 root 仍然能改普通用户的文件,为什么目录没有 x 就寸步难行,为什么 ACL 明明写了 rw- 却显示 effective:r--,为什么 owner 改成某个用户不是削弱 root,而是扩大这个用户的控制面。

权限设计的目标不是把所有东西都锁死,而是让每个身份只拥有它真正需要的那部分能力。Linux 给了我们足够多的旋钮,难的是不要把它们拧成一团。


封面:Bing 每日壁纸(2026-07-04)