Linux 用户、用户组与权限访问机制:从身份到访问检查
Linux 的权限系统看起来很朴素:用户、用户组、rwx 三组权限位。可是只要真的排查过“为什么这个进程能写这个文件”“为什么 root 装出来的文件被某个用户拿不到”“为什么目录有读权限却进不去”,就会发现它并不只是 chmod 755 这么简单。
这篇文章不从某个项目出发,而是从 Linux 自身的模型出发,梳理用户、用户组、进程身份、文件权限、目录权限、ACL、setuid/setgid/sticky bit,以及一次访问检查大致是如何发生的。
一、用户不是名字,而是 UID
我们平时说的 root、nginx、mysql、test,本质上只是名字。内核真正识别的是数字 ID:
1 | UID: user id |
/etc/passwd 里保存的是用户基本信息:
1 | test:x:982:982::/opt/test-agent:/sbin/nologin |
字段拆开是:
1 | 用户名:密码占位:UID:GID:描述:home:shell |
这里真正决定身份的是:
1 | uid = 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 | primary group |
可以用 id 查看:
1 | id test |
输出可能是:
1 | uid=982(test) gid=982(test) groups=982(test),1001(app) |
这里:
1 | gid=982(test) primary group |
当进程访问文件时,内核会看进程是否匹配文件 owner,或者进程的任一 group 是否匹配文件 group。这个判断发生在内核权限检查阶段,而不是 shell 里。
三、进程身份:比“当前用户”更细
一个登录用户启动进程后,进程会带着一组 credentials。粗略说包括:
1 | real UID / real GID |
平时权限检查主要看 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 | user group other |
比如:
1 | -rw-r----- 1 root test 1024 config.yaml |
可以拆成:
1 | owner: root -> rw- |
对普通文件来说:
1 | r: 可以读取文件内容 |
访问时大致按这个顺序判断:
- 如果进程是 root,通常直接通过普通 DAC 检查。
- 如果进程 effective UID 等于文件 owner UID,用 owner 位。
- 否则如果进程任一 group 匹配文件 group GID,用 group 位。
- 否则用 other 位。
注意,这不是三组权限叠加。命中 owner 后就看 owner 位,不会再因为 group 或 other 更宽而补权限。
例如:
1 | ----rwx--- 1 test app file |
如果进程用户就是 test,会命中 owner 位 ---,即使 group 位是 rwx,也不会因此获得权限。
五、目录权限:x 比 r 更关键
目录的 rwx 和普通文件不一样。
对目录来说:
1 | r: 可以列出目录项名字,比如 ls |
最容易误解的是 x。目录没有 x,你即使知道里面文件名,也访问不了。
例如:
1 | chmod 600 /tmp/demo-dir |
这个目录 owner 有读写,但没有执行权限。结果可能是:
1 | ls /tmp/demo-dir |
对目录而言,常见可用权限是:
1 | 755: 所有人可进入和列出,只有 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 | CAP_DAC_OVERRIDE |
其中 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 | 文件: 0666 & ~0022 = 0644 |
这就是为什么大多数新文件默认是 rw-r--r--,而目录默认是 rwxr-xr-x。
如果 umask 是 077,新文件就会更严格:
1 | 文件: 0600 |
理解 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 | setfacl -R -m u:alice:rwX dir |
这里的 X 是条件执行:
1 | 目录会获得 x |
所以递归处理目录树时,rwX 比 rwx 更安全。
ACL 里还有一个很容易踩坑的字段:mask。
1 | user:alice:rw- #effective: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 | id |
看用户和组数据库:
1 | getent passwd username |
看文件 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)





