返回首页

Linux 权限问题排查 — 从 Permission denied 到根因定位的完整指南

📅 创建于 2026-07-14 🔄 更新于 2026-07-14 📝 1933 字

Linux 权限问题排查 — 从 Permission denied 到根因定位的完整指南

来源:马哥Linux运维 | 发布日期:2026-07-09 原文:https://mp.weixin.qq.com/s/-t4AkYJ-ei3Tp5p8JuCwIg

"Permission denied" 的根因往往不在"chmod 一个数字",而在四层叠加:标准 UNIX 权限位 → ACL → LSMs(SELinux/AppArmor)→ 其他限制(capability/namespace/挂载选项)。如果只看一层,查不出真正的根因。

常见表现:cat 报 Permission denied、chmod +x 后仍无法执行、root 能进普通用户进不去、容器内 EACCES、NFS 能读不能写、setuid 程序运行不正常、Operation not permittedchmod 777 了还提示 permission denied。


一、核心知识点

1. UNIX 经典权限三元组

每个文件有三组权限位:owner / group / other。ls -la 看到的 rwxr-xr-- 即这三组。数字表示:r=4,w=2,x=1(如 rwxr-xr-- → 754)。

文件 vs 目录的 rwx 含义差异:

权限 文件含义 目录含义
r 读文件内容 列出目录内容(ls)
w 修改文件内容 在目录里增删改文件
x 作为可执行文件运行 cd 进入 / 访问目录内文件 inode

目录必须同时有 rxx 是进入门槛。父目录没有 x,即使文件本身是 0777 也打不开。 例:目录 drwxr-x--- root www /var/www/htmlwww 组用户可进入,其他用户连 cd 都做不到。

访问匹配逻辑: 如果进程 uid 与文件 owner 相同 → 套 owner 位;否则如果 primary/supplementary groups 命中文件 group → 套 group 位;否则套 other 位。id 显示的 supplementary groups 全部参与匹配。

修改属主:chown user:group file 常用写法,group 留空则不变。chgrp 只改 group 不动 owner。

2. setuid / setgid / sticky bit

ls 显示 含义
setuid -rws------ (owner 位的 s) 执行时进程 euid 取文件 owner
setgid -rwxrws--- (group 位的 s) 执行时进程 egid 取文件 group
sticky drwxrwxrwt (other 位的 t) 目录内只有 owner/root 可删自己文件

典型场景:/usr/bin/passwd 是 setuid root,普通用户才能写 /etc/shadow/tmp 有 sticky bit,普通用户能创建文件但不能删别人的文件。ssh-agent 等会用 setgid 切换到特定组。/var/tmp 没有 sticky bit,是所有用户共享的临时目录。

挂载 nosuid 时 setuid/setgid 被忽略。没有 x 位时 s 显示为大写 S,表示"已设但无效"。一些发行版默认屏蔽 setuid 程序。

3. ACL(POSIX ACL)

标准 ugo 之外,用 getfacl / setfacl 设置更多访问控制:

# file: test.txt
# owner: alice
# group: dev
user::rw-
user:lisi:rw-        # 给 lisi 特殊权限
group::r--
mask::rw-            # 有效 mask,限制所有 named user/group 最高权限
other::---

关键点:

  • chmod 后会锁定 mask——chmod 600 后 named user 权限可能不通
  • setfacl -b 清空全部扩展 ACL(不只是 mask)
  • 备份工具(tar/rsync)要带 --acls 参数才能保留 ACL
  • NFS 不支持 ACL 时静默失败(mount -o noacl 可强制关闭)

4. SELinux(RHEL/CentOS/Fedora 默认)

LSM 框架,对每个操作做额外校验。即使 rwx 全部通过,SELinux 仍可拒绝。

模式 行为 切换命令
enforcing 拒绝并审计 setenforce 1
permissive 只审计不拒绝 setenforce 0(排错用)
disabled 彻底关闭 /etc/selinux/config 重启

核心字段: 进程 domain(httpd_tsshd_tunconfined_t),文件 type(httpd_sys_content_ttmp_tdefault_t),通过 te rules 控制 domain → type 的访问。若文件 type 未正确设置(如 default_t),即使权限 777 也无法访问。

getenforce                     # 查看模式
ausearch -m avc -ts recent     # 最近 AVC 拒绝
sealert -a /var/log/audit/audit.log  # 生成修复建议
ls -Z <file>                   # 查看文件 SELinux context
chcon -t httpd_sys_content_t file    # 改 type
restorecon -v file             # 恢复默认 context
restorecon -Rv /var/www/html   # 递归恢复目录下所有文件

常见错误:AVC 中出现 avc: denied { read } comm="nginx" scontext=httpd_t tcontext=default_t,说明 type 为 default_t 的文件被拒绝——restoreconhttpd_sys_content_t 即可修复。

5. AppArmor(Ubuntu/Debian 默认)

基于路径的 LSM。配置文件在 /etc/apparmor.d/。关键命令:

aa-status              # 查看状态与加载的 profile
aa-complain /path/bin  # 切换为仅记录模式
aa-enforce /path/bin   # 恢复强制模式

6. Linux Capabilities

传统 setuid root 的精细化替代,将 root 拆分为独立单元:

# 给二进制加 cap(无需 setuid root)
setcap cap_net_bind_service=+ep /usr/local/bin/myapp
getcap /usr/local/bin/myapp
getpcaps <PID>
Capability 作用 典型场景
CAP_NET_BIND_SERVICE 绑定低于 1024 端口 Web 服务不用 root
CAP_DAC_OVERRIDE 绕过文件权限检查 备份恢复工具
CAP_SYS_ADMIN 多种系统管理操作 mount/namespace
CAP_NET_RAW 使用 RAW/ICMP 套接字 ping/traceroute
CAP_CHOWN 修改文件 owner 普通用户执行 chown
CAP_KILL 发送信号给任意进程 监控/管理工具

Docker 容器默认带部分 cap,可通过 --cap-drop=ALL --cap-add=NET_BIND_SERVICE 精细控制。容器运行时限制也会影响文件权限,如 --read-only 参数让容器根文件系统变为只读。

7. PAM(Pluggable Authentication Modules)

影响登录和认证过程。涉及 loginsshdsusudo。配置文件在 /etc/pam.d/ 下。常见问题:PAM 配置错误导致 SSH 登录直接被拒绝(即使密码和文件权限都对),或 sudo 权限无故失效。

# 查看某个服务的 PAM 配置
cat /etc/pam.d/sshd
# 测试 PAM 验证(不实际登录)
pamtester sshd <username> authenticate

8. Mount 选项

mount | grep /data
# 若显示 noexec → 脚本/程序无法运行
mount -o remount,exec /data
选项 影响 检查命令
nosuid 忽略 setuid/setgid mount \| grep nosuid
noexec 阻止可执行运行 mount \| grep noexec
nodev 阻止设备文件 mount \| grep nodev
ro 只读文件系统 mount \| grep "(ro,"
noacl ACL 被忽略 mount \| grep noacl
noatime 不更新访问时间 mount \| grep noatime

9. Namespace(容器场景)

容器内 uid 对应宿主机不同范围。root(uid 0)在宿主机上可能是普通用户(如 uid 100000),这意味着容器内认为自己是 root 但宿主机权限受限。

cat /proc/<PID>/uid_map
# 示例输出: 0 100000 65536
# 含义:容器内 uid 0-65535 → 宿主机 uid 100000-165535

Kubernetes Pod 默认使用用户命名空间隔离,挂载卷的宿主机 uid 与容器 uid 不一致时会出现权限问题。可以用 securityContextrunAsUser / fsGroup 指定容器 uid。

10. NFS 权限三层叠加

NFS 权限是客户端 ugo + 服务端文件系统权限 + NFS export 选项的三层叠加:

选项 作用 场景
root_squash 客户端 root 映射为 nobody(默认) 安全推荐
no_root_squash 禁用 squash,客户端 root 保留 需要信任的场景
all_squash 所有用户映射为匿名用户 公共共享
anonuid/anongid 指定匿名映射的 uid/gid 配合服务端用户权限

服务端 /etc/exportsno_root_squash 后客户端 root 才能在 NFS 上 chown。默认 root_squash 下客户端 root 的 chown 静默失败。排查 NFS 权限时,应同时检查客户端 mount 选项、服务端 export 选项和文件实际 owner。

11. FUSE

sshfs、s3fs、rclone mount 等用户态文件系统,即使本地是 root 也可能被 FUSE 自身权限限制拒绝——FUSE 在内核外实现权限检查,不受标准 VFS 权限控制。典型排查思路:检查 FUSE 挂载选项(allow_otheruidgid)、查看 FUSE 进程是否正常运行、确认目标存储(如 S3/MinIO)上的 ACL 设置。


二、排查路径(从外层到内层)

明确报错命令/用户/文件/上下文
  → 1. 标准 ugo 位(ls -la)
  → 2. owner/group 匹配(id + stat)
  → 3. 目录链权限(namei)
  → 4. ACL(getfacl)
  → 5. SELinux/AppArmor(getenforce/aa-status)
  → 6. capability/mount/namespace/FUSE

Step 1 — 基础信息

ls -la <file>
# 看 rwx 位是否正确?owner/group 是谁?

Step 2 — 用户与属主匹配

id
stat -c '%a %A %u %g %U %G' <file>

判断:uid 匹配 owner → 套 owner 位;group 匹配 → 套 group 位;否则套 other 位。仅做这一步还不够,很多人忽略了目录链和 SELinux。

Step 3 — 目录链(新人最易出错)

从根到文件的每级目录均需 r + x。最终文件是 644 但中间某级目录缺 x 仍然打不开。示例:/home/user/app/data,如果 /home/user 是 750 (root:user)、/home/user/app 是 755、/home/user/app/data 是 755,其他用户无法进入 /home/user 因此也进不了 data 目录。

namei -l /var/www/html/app/config/db.php
# 输出每层的权限、owner、group
# 找出哪一层缺 x 或 owner 不对

Step 4 — ACL

getfacl <file>
# 看是否有扩展 ACL、mask 是否被锁定

Step 5 — SELinux / AppArmor

getenforce
# 如果 enforcing,查审计日志
ausearch -m avc -ts recent

Step 6 — 其他限制

getcap <file>                   # capability
mount | grep <path>             # 挂载选项
cat /proc/<PID>/uid_map         # 容器 uid 映射

三、常用命令速查

命令 用途 关键输出
ls -la <file> rwx 和属主 -rwxr-xr-- 1 root www 1234 file
stat -c '%a %A %u %g %U %G' file 数字权限+属主 644 -rw-r--r-- 1000 alice alice
namei -l <path> 逐层目录链检查 每级权限/owner/group
id 当前用户和组 uid=1000(alice) gid=4(adm) groups=...
getfacl <file> ACL 查看 user:lisi:rw- mask::rw-
setfacl -m u:lisi:rw file 设置 ACL -
setfacl -b file 清空 ACL -
getenforce SELinux 模式 Enforcing / Permissive
ausearch -m avc -ts recent 最近 AVC 拒绝 avc: denied { read } comm="nginx"
sealert -a /var/log/audit/audit.log 生成修复建议 restorecon 命令
ls -Z file SELinux context system_u:object_r:httpd_sys_content_t:s0
chcon -t httpd_sys_content_t file 改 SELinux type -
restorecon -v file 恢复默认 context -
getcap <file> 文件 cap cap_net_bind_service=ep
setcap cap=+ep file 设置能力 -
getpcaps <PID> 进程 cap cap_net_bind_service+i
mount \| grep <path> 挂载选项 rw,noexec,nosuid
cat /proc/<PID>/uid_map 容器 uid 映射 0 100000 65536
umask 默认掩码 0022(文件 644,目录 755)
aa-status AppArmor 状态 12 profiles loaded
auditctl -w <path> -p rwxa 文件访问监控 写入 audit.log
cat /etc/audit/auditd.conf auditd 配置 日志文件位置和轮转策略

四、典型场景配置

场景:Nginx 读不到静态文件

这是最常见的权限工单之一,通常原因是 SELinux context 错误或目录链权限不对。

# 1. 检查文件权限
ls -la /var/www/html/index.html
# 2. 目录链检查
namei -l /var/www/html
# 3. SELinux 检查
ls -Z /var/www/html/index.html
# 若 type 为 default_t → 修复
chcon -t httpd_sys_content_t /var/www/html/index.html
# 或递归恢复整目录
restorecon -Rv /var/www/html

场景:普通用户绑定低端口(<1024)

# 推荐方式:capability(无需 setuid root)
setcap cap_net_bind_service=+ep /usr/local/bin/myapp
getcap /usr/local/bin/myapp

# 不推荐:setuid root
chmod u+s /usr/local/bin/myapp

场景:容器内 Permission denied

# 确认 uid 映射
cat /proc/1/uid_map
# 检查卷挂载的宿主机 uid 与容器 uid 是否匹配
# SELinux enforcing 时 docker 默认带 label
docker run --security-opt label=disable ...
# K8s 中可设置
securityContext:
  runAsUser: 1000
  fsGroup: 2000

场景:NFS 权限异常

# 服务端 export
cat /etc/exports
# /data  *(rw,sync,no_root_squash)

# 客户端检查
mount | grep nfs
# 看是否有 root_squash
# 逐层排查:客户端 ugo → 服务端 ugo → export 选项

场景:umask 导致权限不符

umask
# 0022 → 创建文件 644,目录 755
# 0027 → 文件 640,目录 750
# 持久化修改
echo "umask 0027" >> ~/.bashrc
# 对特定服务在 systemd 中设置
echo 'UMask=0027' >> /etc/systemd/system/example.service.d/override.conf

场景:挂载点 noexec 导致程序无法执行

# 检查
mount | grep /app
# 输出 /dev/sda1 on /app ext4 (rw,noexec,nosuid)
# 修复
mount -o remount,exec /app

场景:sudo 权限异常(PAM 或 sudoers)

# 检查 sudoers
visudo -c
cat /etc/sudoers.d/<user>
# 检查 PAM 配置
cat /etc/pam.d/sudo

场景:auditd 日志协助定位

# 监控特定文件的权限访问尝试
auditctl -w /etc/shadow -p rwxa -k shadow_access
# 查看监控到的违规
ausearch -k shadow_access -ts today
# 确认最近被 SELinux 拒绝的操作
ausearch -m avc -ts recent | audit2why

场景:read-only filesystem 误报

mount | grep "ro,"
# 如果文件系统以只读方式挂载
mount -o remount,rw /mount/point
# 检查磁盘硬件错误(可能触发了内核自动 remount ro)
dmesg | grep -i "remount\|I/O error\|ext4.*error"

场景:Docker volume 权限问题

# 查看卷的宿主机 uid
ls -n /var/lib/docker/volumes/<vol>/_data
# 对比容器内预期 uid
docker exec <container> id
# 修复:创建卷时指定 --owner
docker run -v /data --user 1000:1000 ...

场景:rsync 备份后的权限丢失

# rsync 保留权限需要 -a(归档)参数
rsync -avz --delete source/ dest/
# 如果只需保留权限不保留所有者
rsync -avz --no-owner --group source/ dest/
# ACL 和 xattr 需要额外参数
rsync -avAX source/ dest/

场景:find 误操作导致权限异常

# 误将 644 设为所有权限
find /var/www -type f -exec chmod 777 {} \;
# 修复为合理值
find /var/www -type f -exec chmod 644 {} \;
find /var/www -type d -exec chmod 755 {} \;

五、适用场景与不适用场景

本文覆盖的场景:

  • cat / vi / less / cp / mv / bash / sh 报 Permission denied
  • Web/数据库等服务不能读取配置文件、写入日志目录、创建 socket 文件
  • 容器内应用 EACCES、permission denied,宿主机上看着正常
  • SELinux 触发 avc: denied 日志
  • AppArmor 触发 DENIED 操作
  • NFS/SMB 挂载点写入报错,能读不能写
  • setuid 程序在某些环境运行不正常
  • 二进制无法执行(cannot enable executable stack、缺少 x、挂载了 noexec)
  • systemd 服务启动后拿到错误的 uid/gid
  • Operation not permitted(特别是 mount、chroot、capability 类)

不适用场景: 业务代码层的鉴权(OAuth / RBAC 角色绑定等)不在系统权限讨论范围;文件系统损坏、磁盘 IO 错误、ACL 模块未编译等更偏文件系统/kernel 模块的故障,不在此文深挖。

六、验证方式

# 用目标用户模拟操作
sudo -u www-data cat /var/www/html/config.php
# HTTP 验证
curl -sI http://localhost/config.php | head -5
# SELinux 确认
ausearch -m avc -ts recent
# 文件描述符检查
sudo -u www-data ls -la /proc/self/fd/ 2>&1

每次变更后明确"通过/失败"标准——不能"感觉好了",要可重复验证。示例标准:"用 sudo -u www-data cat /var/www/html/config.php 能正常输出文件内容,curl -sI http://localhost/config.php 返回 200,ausearch -m avc -ts recent 无新增 AVC 拒绝记录。"

七、回滚方案准备

每个修复动作都应该有对应的回滚方式:

修复动作 回滚方式 前置记录命令
chmod 改权限 chmod <原值> <file> stat -c %a <file> 记录原值
chown 改属主 chown <原属主> <file> stat -c '%U %G' <file>
chcon 改 SELinux context restorecon <file> 恢复默认 ls -Z <file> 查看 context
setenforce 0 setenforce 1 恢复强制 执行前记录原因和时间
setcap 设能力 setcap -r <file> 移除 getcap <file> 记录原值
mount -o remount,exec mount -o remount,noexec <path> mount \| grep <path> 记原选项
改 PAM 配置 恢复原文件备份 cp /etc/pam.d/xxx /tmp/pam.bak

八、风险提醒

操作 风险
chmod -R 777 安全灾难,攻击者可写入任意位置
chown -R 对系统目录 可能造成系统不可用
setenforce 0 不写死 掩盖问题,重启后恢复 enforcing
NFS 上执行 chown 被 squash 产生难以诊断的错误
盲目改系统目录 SELinux context 可用 restorecon 恢复
chmod 000 测试权限 可能无法恢复(需 root 才能改回)

九、总结

  1. 从出错的具体用户/命令/文件出发,不猜测
  2. 先查标准 rwx → owner/group → 目录链 → ACL → LSM → 其他
  3. 目录链权限(namei)是新人最容易忽略的一步
  4. SELinux enforcing 下 restorecon 经常直接修复
  5. NFS 和容器的权限是多层叠加,逐层排查不要跳步
  6. 每次变更前备份,变更后明确验证通过标准

关联页面

页面关联点
server-security-hardening-checklistLinux 服务器安全加固清单
linux-filesystem-directory-structure-guide文件系统与目录结构基础
linux-hacked-server-emergency-response服务器被入侵应急响应
ssh-brute-force-protection-guideSSH 暴力破解防护
docker-production-pitfallsDocker 生产踩坑(含 volume 权限)
k8s-service-access-troubleshootingK8s 服务访问排障(含 RBAC)