DevKitLab Logo DevKitLab
chmod / Unix / 文件权限 / Linux

chmod 之后还是 Permission denied?原因在这里

明明改过 chmod,脚本或文件仍提示 Permission denied,问题往往不在文件本身。本文按路径、所有权、挂载选项、SELinux、ACL、特殊位和文件系统标记逐层排查。

你已经看过权限,甚至一着急把它改成了 777,终端仍然报:

-bash: ./deploy.sh: Permission denied

这时继续放宽模式,往往是在治错地方。文件的权限位只是访问链中的一层;真正挡住你的,可能是上级目录、所有权、挂载选项,或者叠在权限位之上的安全策略。本文按这些层次排查。

如果还不确定 755644 本身表示什么,先读什么是 chmod 755。这里假设文件模式看起来已经合理,继续找为什么它仍不够。

先检查,不要先放宽权限

先回答一个问题:阻断发生在文件本身,还是发生在它周围? 不要把 chmod 777 当探针。它会改变状态,也未必是在模拟真正失败的账号。先查看整条路径:

namei -l /home/alice/app/deploy.sh

namei -l 会从 / 一直列出到目标文件,显示每一段的模式、所有者和组。再配合真正失败账号下的 id 与文件的 ls -l,通常不用改任何权限就能定位问题。namei 属于 util-linux;macOS 上可以对每一级目录逐个执行 ls -ld

不要为了“测试”就执行 chmod 777chmod u+rwx 只改所有者位;当你不是所有者时,它并没有回答问题。用 sudo 测试也只说明 root 能否访问,并不能说明运行服务、定时任务或普通用户的权限是否正确。

原因 1:问题在路径上,而不在文件上

打开 /home/alice/app/deploy.sh 时,//home/home/alice/home/alice/app 的每一级目录都需要有 执行/遍历 权限。任何一级缺少 x,下方的文件模式就还没机会生效。

ls -ld / /home /home/alice /home/alice/app

如果确实需要让 other 这一类用户穿过目录,可以这样恢复遍历权:

chmod o+x /home/alice

o+x 只允许穿过目录,不允许列出目录内容。在共享环境里,给合适的组使用 g+x 通常更窄、更安全。

还有一个经常漏掉的区别:读取或运行已有文件,需要文件自身对应的权限加上每一级父目录的 x;而创建、删除、重命名条目,看的却是父目录,它必须同时有 w+x。文件本身可不可写,对能否删除它并不重要。

操作需要检查什么
打开、读取或执行已有文件文件自身权限,以及每一级父目录的 x
创建、删除、重命名条目父目录的 w+x
列出并访问目录条目该目录的 r+x

因此,rmmv 或创建文件时的 Permission denied,往往指向目录而不是目标文件。sticky bit 会再增加一条删除限制,后文会讲。

原因 2:所有权不对

权限按所有者、属组和其他用户三类计算。你实际落入哪一类,要看你的身份、组成员关系,以及文件的属主和属组:

ls -l deploy.sh      # -rwxr-x--- 1 root staff ...
id                   # uid=1000(alice) gid=1000(alice) groups=1000(alice)

这里文件是 root:staff、模式为 750,而 Alice 不在 staff。她会落到“其他用户”这一类,而 750 对这一类没有任何权限。chmod 改的是各类能做什么,不会改变文件的属主、属组,也不会把用户加入某个组。真正可行的修复要和实际身份对应:

id -nG alice                     # 先确认 alice 真正属于哪些组
sudo chown alice deploy.sh       # 让 alice 成为所有者,或……
sudo chgrp developers deploy.sh  # 仅当 alice 属于 developers 时才有效,或……
sudo usermod -aG staff alice     # 把 alice 加入 staff,随后重新登录

把文件改到用户并不属于的组,没有任何帮助。这也是很多 chmod 777 冲动背后的真实问题:访问者落在了错误的权限类,应该修所有权或组关系,而不是把文件对所有人打开。

原因 3:文件系统不允许

挂载选项可以覆盖模式位,最常见的是:

  • noexec:禁止直接执行该挂载点上的文件,即使它有 x。在加固过的临时目录、移动磁盘或容器中很常见。执行 ./script 仍可能报 Permission denied;bash ./script 这类由解释器读取脚本的方式是另一回事。
  • ro:只读挂载。即使文件有 w,也不能写入。
findmnt -T /tmp/deploy.sh
# → TARGET SOURCE FSTYPE OPTIONS
#   /tmp   tmpfs  tmpfs  rw,nosuid,nodev,noexec

如果看到 noexec,修复方式是把需要直接执行的文件移到允许执行的文件系统,或者在你确实控制挂载策略时重新挂载。macOS 可用不带参数的 mount 查看挂载及其标记。

原因 4:SELinux 或 AppArmor

在 RHEL、Fedora 及其衍生系统上,SELinux 会按安全上下文做第二层访问控制。Web 服务器可能因为文件上下文不对而读不到一个所有人都可读的文件。常见信号是:只在服务进程中失败,shell 中却能访问,且普通模式看起来完全正确。

getenforce
ls -Z deploy.sh
sudo ausearch -m avc -ts recent

若有 AVC 拒绝记录,模式位往往只是干扰项。对于本应使用默认策略标签的路径,可用:

sudo restorecon -v /var/www/html/deploy.sh

Ubuntu、SUSE 常见的是 AppArmor:查看 sudo aa-status,并在内核日志中寻找 apparmor="DENIED"。这时排查的是策略,不是 chmod。

原因 5:ACL 让三组权限不再完整

chmod 只处理传统的所有者、属组和其他用户三类;文件还可能带有 POSIX ACL,对特定用户或组定义额外规则。下面的 + 是常见信号:

ls -l report.csv     # -rw-r-----+
getfacl report.csv

ACL 会让简单的三组权限视图不完整:额外条目可以授予额外访问权,而 ACL mask 又会限制具名用户、具名组和属组的有效权限。例如 getfacl 里可能有 group::rwx,但 mask::r--,实际只有 r--。而 ls -l 显示的属组权限三位对应的是 mask,不一定是属组条目本身。先读 getfacl 标出的有效权限,再精确调整需要的条目,例如:

setfacl -m u:alice:rx report.csv

不要把 setfacl -b 当默认修复;它会删除可能本来就有意保留的扩展规则。

原因 6:特殊位和文件系统标记

有时权限模式并没有被忽略,只是它的含义被读错了:

  • sticky bit:像 /tmp1777。共享目录中的条目只能由条目所有者、目录所有者或特权用户删除、重命名;即使目录对所有人可写,也不能随意处理别人的条目。
  • Linux 上的 setuid 脚本:解释器脚本的 setuid/setgid 位会被内核忽略。4755 install.sh 不会像 setuid 二进制程序那样以文件所有者身份运行。
  • immutable 属性:在支持它的 Linux 文件系统上,immutable 属性会阻止写入和很多元数据修改,chmod 看不到它。
lsattr deploy.sh        # i 表示 immutable
sudo chattr -i deploy.sh

macOS 没有 chattr;其对应机制包括 chflagsuchgschg,系统路径还可能受 System Integrity Protection 限制。

有些报错根本不是权限问题

  • 脚本报 bad interpreter: No such file or directory,常见原因是 shebang 路径不对,或 #! 行带了 Windows 的 CRLF。
  • 磁盘空间或 inode 用尽通常报 No space left on device,不是 Permission denied;用 dfdf -i 检查。
  • 往启用 root_squash 的 NFS 挂载写入时,远端 root 会被映射成无特权身份,因此即使用 sudo 也可能被拒绝。这是导出策略问题,不是文件模式问题。

如果手工执行正常、cron 却失败,那是另一类环境问题,可以看为什么 cron 任务没有运行

排查清单

  1. 以真正失败的账号检查。namei -l /完整路径idls -l,不要先执行 chmod 777
  2. 检查整条路径。 任意祖先目录缺 x 都会阻断访问;创建、删除、重命名还需要父目录 w+x
  3. 对照所有权与组。 既不是所有者、也不属于属组时,修 chownchgrp 或组成员关系,而不是扩大模式。
  4. 检查挂载。 findmnt -T <path>noexec 阻止直接执行,ro 阻止写入。
  5. 检查安全模块。 SELinux 看 getenforcels -Z,AppArmor 看 aa-status 与日志。
  6. 检查 ACL。 ls -l+ 后接 getfacl,特别留意 mask。
  7. 检查特殊机制。 sticky bit、setuid 脚本和 immutable 等文件系统标记。

沿着这份清单排查,真正的阻断点通常会很快出现,而且它往往不是 chmod 能单独解决的。需要确认某个八进制或符号模式实际赋予什么权限时,可以使用 chmod 计算器。等阻断点解决之后,最终该把权限设成什么、整个目录树又该怎么批量处理而不至于把每个文件都变成可执行文件,可以看chmod 644 和 755 怎么选