chmod 777 之后,为什么还是 Permission denied
原文链接:chmod 777 之后,为什么还是 Permission denied
先还原一个很常见的现场。
服务换到新机器,日志目录写不进去。值班的同学熟练地敲下:
sudochmod-R777/srv/app再试一次,还是:
touch: cannot touch '/srv/app/logs/app.log': Permission denied于是权限被加到 7777,加上-R,再加上sudo,最后开始怀疑内核有 bug。
大多数时候,问题不在权限位,而在于我们默认“权限”只有rwx这一层。Linux 在真正打开一个文件之前,要穿过好几道门:路径上的目录、umask、ACL、挂载选项、LSM、文件属性,任何一道没过去,chmod 777都只是把第一道门拆了。
下面按排查顺序,把这几道门逐个走一遍。
第一步:先分清 errno
报错信息里的英文,其实已经给了线索。Permission denied和Operation not permitted不是一回事。
| errno | 典型含义 | 常见来源 |
|---|---|---|
EACCES(13) | 权限不足 | 权限位、ACL、noexec、SELinux |
EPERM(1) | 操作本身不被允许 | chattr +i、非文件所有者执行chmod、能力缺失 |
EROFS(30) | 只读文件系统 | 只读挂载、容器只读根目录 |
EDQUOT(122) | 配额超限 | 用户/项目磁盘配额 |
ETXTBSY(26) | 可执行文件正被占用 | 一边运行一边覆盖二进制 |
看到Operation not permitted还去chmod,方向就已经错了。
第二道门:路径上每一级目录都要有 x
这是最容易被忽略的一条。要访问/srv/app/logs/app.log,内核会依次穿过/、/srv、/srv/app、/srv/app/logs,每一级目录都需要执行位x。目录的x不是“能运行”,而是“能进入、能按名字找到里面的东西”。
$mkdir-p/srv/app/logs $chmod777/srv/app/logs $sudo-unobodytouch/srv/app/logs/a.log touch: cannottouch'/srv/app/logs/a.log':Permission deniedlogs已经是 777,为什么还不行?用namei把整条路径的权限摊开看:
$ namei-l/srv/app/logs/a.log f: /srv/app/logs/a.log drwxr-xr-x root root / drwxr-xr-x root root srv drwx------ root root app drwxrwxrwx root root logs问题出在/srv/app是700。logs再开放,nobody也进不到它父亲那一层。
顺便记住目录三个位的真实含义:
r:能列出目录里的名字;x:能进入目录、能访问已知名字的条目;w:能在目录里创建、删除、重命名条目。
只有r没有x时,ls能看到文件名,但ls -l会满屏?,因为拿不到元数据。只有x没有r时,反而可以在知道完整路径的情况下直接访问文件。
还有一个变种:目录带 sticky bit(比如/tmp的1777),即使大家都有w,也只能删除自己的文件。这是“写得进去却删不掉”的常见原因。
第三道门:umask 决定了新文件的起点
chmod 777只改已经存在的对象。之后新建的文件,权限由创建时的mode & ~umask决定。
$umask0022 $touchdemo.txt $stat-c'%a %n'demo.txt644demo.txt普通文件默认起点是666,目录是777,再被 umask 削掉。所以umask 022下新建文件是644,新建目录是755。
如果某个进程的 umask 是077,它写出来的文件就是600,别的用户自然读不了。systemd 服务可以在 unit 里设置:
[Service] UMask=0027排查时可以看进程的当前 umask:
$grep-iumask/proc/$(pgrep-fmyapp|head-1)/status Umask: 0022第四道门:ACL 会把权限“打折”
ls -l末尾出现+,说明这个文件带 ACL:
$ls-lreport.txt -rw-r--r--+1alice dev1024Nov210:00 report.txt$ getfacl-preport.txt# file: report.txt# owner: alice# group: devuser::rw- user:bob:rwx#effective:r--group::r-- mask::r-- other::r--bob明明被授予了rwx,有效权限却只有r--。原因是 ACL 里的mask相当于“权限上限”,命名用户和所属组的权限都要和 mask 取交集。
更隐蔽的是:文件一旦有 ACL,chmod g+w改的其实是 mask,不是 group 位。所以你会看到chmod执行成功,getfacl里的具体条目却没变。
修复方式是把 mask 一起放开:
setfacl-mu:bob:rwx,m::rwx report.txt目录还可以设置默认 ACL,让以后新建的文件自动继承:
setfacl-d-mu:deploy:rwx /srv/app/logs第五道门:挂载选项可能禁止执行
权限位全是777,./tool仍然报Permission denied,先看挂载参数:
$ findmnt-T/srv/app-oTARGET,SOURCE,FSTYPE,OPTIONS TARGET SOURCE FSTYPE OPTIONS /srv/app /dev/sdb1 ext4 rw,nosuid,nodev,noexec,relatimenoexec会让execve()直接返回EACCES,和文件权限无关。/tmp、/var/tmp、某些数据盘默认这么挂,是常见的安全加固。
一个容易混淆的细节:noexec只拦“把文件本身当作程序执行”。下面这条仍然可能成功:
$bash/srv/app/script.sh因为真正被execve的是/bin/bash,脚本只是被当成参数读进去。反过来,ro挂载会让所有写操作返回EROFS,容器里只读根文件系统时尤其常见。
第六道门:SELinux 和 AppArmor
权限位、ACL、挂载选项都正常,还是被拒,就该怀疑 Linux Security Module 了。
SELinux 环境下先看上下文:
$ getenforce Enforcing $ls-Z/srv/app/logs unconfined_u:object_r:var_t:s0 /srv/app/logs如果服务应该读写httpd_sys_content_t之类的类型,而目录被打成了var_t,即使777也会被拒绝。查最近一次的拒绝记录:
$sudoausearch-mavc-tsrecent $sudodmesg|grep-i'avc: denied'确认是标签问题后,用restorecon恢复默认上下文,而不是直接setenforce 0:
sudorestorecon-Rv/srv/appAppArmor 则看aa-status和dmesg,被 profile 拦下时同样表现为EACCES。
把安全模块一关了之,问题会暂时消失,但下次上线还会回来,而且是以更难查的形式。
第七道门:chattr 的文件属性
chattr +i设置的不可变属性,连 root 都改不了:
$sudochattr +i /etc/resolv.conf $sudosh-c'echo "nameserver 1.1.1.1" > /etc/resolv.conf'sh: /etc/resolv.conf: Operation not permitted注意报错是Operation not permitted,不是Permission denied。查看属性:
$ lsattr /etc/resolv.conf ----i---------e------- /etc/resolv.confi表示不可变,a表示只能追加。去掉再改:
sudochattr-i/etc/resolv.conf这里还有一个 shell 层面的坑:
sudoecho"nameserver 1.1.1.1">/etc/resolv.conf即使文件可写也会失败,因为重定向是当前 shell 做的,sudo只提升了echo的权限。正确写法是让特权进程自己打开文件:
echo"nameserver 1.1.1.1"|sudotee/etc/resolv.conf还有两类环境因素
NFS 的 root squash。服务端开启root_squash后,客户端的 root 会被映射成nobody,在共享目录里并不具备超级用户权限。表现就是“我是 root,为什么还写不进去”。
容器里的 UID 映射。容器内的uid 1000和宿主机上的uid 1000未必是同一个用户,启用 user namespace 后更是如此。挂载卷的属主看起来对,实际对不上,chmod 777在容器内也可能作用在另一层文件系统上。
一张排查清单
遇到权限问题,按这个顺序走,通常五分钟内能定位:
- 看 errno:
EACCES、EPERM、EROFS指向完全不同的方向; namei -l /path/to/file,确认路径上每一级目录都有x;getfacl,检查 ACL 和 mask;findmnt -T /path,确认没有ro、noexec;lsattr,排除i、a属性;getenforce、aa-status、ausearch,检查安全模块;- 最后才考虑 NFS 映射、容器 UID、磁盘配额这些环境因素。
chmod 777的问题不在于它危险——虽然它确实危险——而在于它只覆盖了整条链路上的一小段。真正让人加班到凌晨的,往往是那个700的父目录,或者一行noexec。
下次再看到Permission denied,先别急着加权限。让namei把路径摊开,答案通常就在里面。
参考文档
- Primers 编程伙伴/Bash/帮助手册/sudo
- Primers 编程伙伴/Bash/帮助手册/chmod
- Primers 编程伙伴/HTML/HTML 元素/i
- Primers 编程伙伴/Bash/帮助手册/mkdir
- Primers 编程伙伴/Bash/帮助手册/touch
- Primers 编程伙伴/Bash/帮助手册/namei
- Primers 编程伙伴/Bash/帮助手册/umask
- Primers 编程伙伴/Bash/帮助手册/stat
- Primers 编程伙伴/Bash/帮助手册/grep
- Primers 编程伙伴/Bash/帮助手册/head
- Primers 编程伙伴/Bash/帮助手册/ls
- Primers 编程伙伴/Bash/帮助手册/findmnt
- Primers 编程伙伴/HTML/HTML 元素/var
- Primers 编程伙伴/Bash/帮助手册/dmesg
- Primers 编程伙伴/Bash/帮助手册/echo
- Primers 编程伙伴/HTML/HTML 元素/a
- Primers 编程伙伴/Bash/帮助手册/tee