2774 字
约 9 分钟
18
S15-X04 · 文件权限与属主:chmod/chown 到 ACL

权限问题的报错只有三种,但对应的是三套完全不同的机制: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. 自测

  1. 目录权限是 r--,用户能否 cat 其中已知文件名的文件?为什么?
  2. 666 & ~0027 的结果是多少?它对应文件还是目录?
  3. SUID 与目录 SGID 的语义差别是什么?
  4. getfacl 显示 user:alice:rwx 但 alice 写不进去,先查哪一项?
  5. chown 之后为什么 SUID 位会消失?
  6. 判断“这条路径上哪一级挡住的”,用哪条命令最快?
S15-X04 · 文件权限与属主:chmod/chown 到 ACL
https://www.clxhxhhr.top/posts/5706/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。