权限问题的报错只有三种,但对应的是三套完全不同的机制:Permission denied 是权限位或 ACL 不匹配,Operation not permitted 是普通用户做了只有 root 能做的事(比如 chown),Not a directory / 进不去目录则是 x 位缺失。把这三者混在一起猜,就会陷入“我明明 777 了怎么还不行”的死循环。
这篇从 ls -l 的九个字符读起,一路讲到 ACL,并且解释为什么 chmod 777 从来不是答案。
1. 从 ls -l 的九个字符读起
$ ls -l /srv/app
-rw-r-----+ 1 deploy www-data 1420 Nov 9 04:12 config.yml
drwxr-xr-x 3 deploy www-data 4096 Nov 9 04:10 logs
-rwsr-xr-x 1 root root 31024 Nov 9 03:58 helper
| 位置 | 内容 | 含义 |
|---|---|---|
| 第 1 位 | - d l c b s p |
普通文件、目录、符号链接、字符设备、块设备、socket、管道 |
| 2–4 位 | rwx |
属主(owner)权限 |
| 5–7 位 | rwx |
属组(group)权限,只对“属组且非属主”的人生效 |
| 8–10 位 | rwx |
其他(other)权限 |
第 10 位后的 + |
ACL 存在 | getfacl 才看得到全貌 |
第 10 位后的 . |
有 SELinux 上下文 | ls -Z 查看 |
| 链接数 | 1 / 3 |
文件是硬链接计数;目录是子目录数 + 2 |
匹配顺序是属主 → 属组 → 其他,命中即停。也就是说,如果文件属主是你,即使属组权限更宽,也按属主那三位算。
2. rwx 的八进制换算
$ chmod 640 config.yml # rw- r-- ---
$ chmod 2755 shared # rwx r-x r-x + SGID
$ chmod u+x,g-w,o= deploy.sh
$ chmod -R g+r logs/
| 值 | 位 | 对文件 | 对目录 |
|---|---|---|---|
| 4 | r |
读取内容 | 列出条目名(ls) |
| 2 | w |
修改内容 | 创建/删除/改名条目 |
| 1 | x |
执行 | 进入并访问路径下的条目 |
| 0 | - |
无 | 无 |
chmod 644 是文件,chmod 755 是目录,这是两个不同的默认值,别互相套用。用符号模式写 chmod o= 那种“清空”式赋值,比八进制更难读但更安全——它不会误删你不打算动的位。
3. 目录的 x 为什么是“能进入”而不是“能读”
这是权限里最反直觉的一条。目录的 r 只允许读出条目名列表,x 才允许穿过这个目录去访问它下面的东西。
| 组合 | 能 ls 目录吗 |
能 cat dir/file 吗 |
典型场景 |
|---|---|---|---|
r-- |
能,看到名字 | 不能,路径解析被拒 | 只让人知道有哪些文件 |
--x |
不能 | 能(已知文件名时) | 只给已知路径的访问权 |
r-x |
能 | 能 | 常规目录 |
-wx |
不能 | 能,且能创建/删除 | 投递目录(只进不出) |
rw- |
能 | 不能 | 几乎无意义,常见误配 |
--x 这个组合常被用来做“可执行不可枚举”:上传目录给 -wx,别人能丢文件进来但看不到别人的文件。反过来说,如果你只给 r 不给 x,用户能看到文件名却打不开任何一个,报错还是 Permission denied——这就是“看起来权限对了却读不了”的经典来源。
4. 特殊位:SUID、SGID、粘滞位
$ chmod u+s helper # SUID
$ chmod g+s shared/ # 目录 SGID:新文件继承目录属组
$ chmod +t /tmp # 粘滞位
$ find / -xdev -perm -4000 -type f 2>/dev/null # 盘点 SUID
| 位 | 八进制 | 作用对象 | 语义 | 风险面 |
|---|---|---|---|---|
| SUID | 4000 | 可执行文件 | 执行时 euid 变成文件属主 | root 属主的 SUID 程序一旦可被覆盖/注入即等于提权 |
| SGID | 2000 | 可执行文件 | egid 变成文件属组 | 同类风险,多见于共享工具 |
| SGID | 2000 | 目录 | 新建文件继承目录的属组,而非创建者主组 | 共享目录的标准做法,风险低 |
| 粘滞位 | 1000 | 目录 | 只有条目属主、目录属主、root 能删别人的条目 | /tmp 靠它防止互删 |
/tmp 之所以是 drwxrwxrwt,就是因为所有人可写,如果没有 t,任何用户都能删掉别人正在用的临时文件。共享目录的正确姿势是:chmod 2775(SGID)+ 属组设为共享组,而不是 777。
5. umask 与新建文件的默认权限
新建文件的权限不是 666,而是 666 & ~umask;目录是 777 & ~umask。
$ umask
0022
$ umask -S
u=rwx,g=rx,o=rx
$ touch a.txt && mkdir d && ls -ld a.txt d
-rw-r--r-- 1 deploy deploy 0 Nov 9 05:01 a.txt
drwxr-xr-x 2 deploy deploy 4096 Nov 9 05:01 d
| umask | 新文件 | 新目录 | 适用 |
|---|---|---|---|
022 |
644 |
755 |
单用户/服务端常见默认 |
027 |
640 |
750 |
组内共享、其他人不可读 |
077 |
600 |
700 |
私钥、密钥、个人数据 |
002 |
664 |
775 |
协作目录(配合 SGID) |
注意:umask 是进程属性,只影响该 shell 及其子进程;写进 ~/.bashrc 只对交互式 shell 生效,systemd 起的服务要用 UMask= 单独设。反过来,程序自己调 open(..., 0666) 时也会被 umask 削,所以“我代码里写了 666”不等于文件就是 666。
6. chown、chgrp 与 -R 的坑
$ sudo chown deploy:www-data config.yml
$ sudo chown -R deploy:www-data /srv/app
$ sudo chown --reference=config.yml config.yml.bak
$ sudo chgrp -R www-data /srv/app/uploads
| 坑 | 现象 | 规避 |
|---|---|---|
chown -R 范围写错 |
把 /srv 写成 / 或漏掉尾部路径 |
先用 find ... -exec chown 或 --reference,并 echo 出目标路径确认 |
| 改属主丢了 SUID/SGID | 改完 SUID 位消失 | Linux 上 chown 会清 SUID/SGID,改完要重新 chmod |
| 只改目录不改内容 | 目录归新用户,文件还是旧的 | 需要 -R,但要确认没有软链接指向别处 |
| 软链接 | chown 默认作用于链接目标 |
想改链接本身用 chown -h |
| 大目录卡住 | 百万文件 -R 跑很久 |
分批 + nohup,或先 find -maxdepth 估量 |
chown 自身也要求 root 或被 CAP_CHOWN;普通用户执行会报 Operation not permitted,而不是 Permission denied。这两个报错的区分方法很实用:前者是“你没这个能力”,后者是“你有能力但权限位不允许”。
7. ACL:多用户共享目录的正解
需求:/srv/shared 属主 deploy、属组 dev,但 alice 属于 ops 组,也要读写,且新文件自动继承。
$ sudo setfacl -m u:alice:rwx /srv/shared
$ sudo setfacl -m g:ops:rx /srv/shared
$ sudo setfacl -d -m u:alice:rwx /srv/shared # 默认 ACL,管新建条目
$ getfacl /srv/shared
# file: srv/shared
# owner: deploy
# group: dev
user::rwx
user:alice:rwx
group::r-x
group:ops:r-x
mask::rwx
other::r-x
default:user::rwx
default:user:alice:rwx
| 概念 | 说明 |
|---|---|
user:alice:rwx |
具名用户条目,优先于 group/other |
mask |
具名条目与属组的权限上限,setfacl 会自动重算 |
-d(default) |
只对目录有效,新建条目继承 |
-R |
递归,注意目录树规模 |
-x |
删除单条 ACL;-b 清空全部 |
+ |
ls -l 第 11 位出现即表示有 ACL |
关键细节:ACL 的 mask 会“压住”具名用户的实际权限。getfacl 里写 user:alice:rwx 而 mask::r-x,alice 实际只有 r-x。改了具名条目后 ls -l 的组权限那三位显示的其实是 mask,不是原组权限——这是最常见的误读。
另外,ACL 依赖文件系统支持(ext4/xfs 默认开启,需按挂载参数核对 acl),NFS、FAT32、部分容器卷会直接丢弃。
8. 为什么 chmod 777 是坏习惯
这不是“安全”问题,是可维护性问题:
| 后果 | 具体表现 |
|---|---|
| 权限位失去信息量 | 下次出问题无法从 ls -l 判断“谁本该能写” |
| 掩盖 umask 配置错误 | 新文件本该 644,用 777 把问题糊住,换个目录又炸 |
| 破坏审计 | 日志/备份无法区分“应用写的”和“人改的” |
| 传递到下游 | rsync -a、镜像打包、CI 产物会把 777 带到新环境 |
| 阻碍最小权限收口 | 后续想收紧时不知道哪个权限是真需要的 |
替代方案按场景选:属主不对就 chown,组内共享就 SGID + 2775,跨组给权就 ACL,临时调试用 sudo -u 换身份跑一次。真正需要“所有人可写”的只有 /tmp 这类位置,而且它带粘滞位。
9. 排错:七种权限报错对照
| 现象 | 原因 | 处理 |
|---|---|---|
Permission denied 打开文件 |
位或 ACL 不允许 | namei -l /path/to/file 逐级查 |
目录 ls 可以但 cd 不行 |
缺 x |
chmod +x dir |
cd 可以但 ls 不行 |
缺 r |
视需求补 r |
Operation not permitted |
chown/chmod 超出权限或能力集 |
用 root 或 sudo;查 getcap |
| 改了权限没生效 | ACL mask 压制 或 NFS 服务端映射 | getfacl 看 mask;查 idmapd |
| 新建文件权限不对 | umask 或程序显式 mode | umask、systemd 的 UMask= |
| 备份恢复后权限全乱 | 归档没保留属性 | rsync -a、tar --same-owner -p |
namei -l 值得单独记:它把整条路径上每一级的权限都列出来,比逐层 ls -ld 快得多。
$ namei -l /srv/app/logs/app.log
f: /srv/app/logs/app.log
drwxr-xr-x root root /
drwxr-xr-x root root srv
drwxr-x--- deploy deploy app
drwxr-xr-x deploy www-data logs
-rw-r----- deploy www-data app.log
10. 边界:这些情况不该只调权限
| 需求 | 更合适的做法 | 理由 |
|---|---|---|
| 服务读写某个目录 | 用 systemd User= + 属主改对 |
长期 777 会把错误固化 |
| 多租户隔离 | 容器 + 独立 UID 命名空间 | ACL 只隔离文件,不隔离进程 |
| 精细到“只能读某几列” | 应用层鉴权 | 文件系统没有列级概念 |
| 审计谁改了什么 | auditd 规则 |
权限位不记录“谁” |
| 强制访问控制 | SELinux/AppArmor | DAC 之上还有 MAC 一层 |
一句话收口:权限位回答“谁能做什么”,属主回答“这是谁的”,ACL 回答“例外是谁”,umask 回答“新东西默认给谁”。 四层都对齐,才不需要 777。
11. 自测
- 目录权限是
r--,用户能否cat其中已知文件名的文件?为什么? 666 & ~0027的结果是多少?它对应文件还是目录?- SUID 与目录 SGID 的语义差别是什么?
getfacl显示user:alice:rwx但 alice 写不进去,先查哪一项?chown之后为什么 SUID 位会消失?- 判断“这条路径上哪一级挡住的”,用哪条命令最快?