在日常运维和开发中,「这个文件明明是我的,为什么删不掉」「ls 能看见文件,cat 却报 Permission denied」「目录给了 r 为什么还是进不去」这三类问题出现频率极高。它们的答案都指向同一个知识点:Linux 的 rwx 权限,对文件和对目录,是完全两套语义。本文围绕 rwx 在文件和目录上的真实作用展开,从权限位解析、逐位实测,到一个多用户共享目录的完整搭建案例,并附上高频报错对照表和工程化建议。无论你是刚接触 Linux 的学生,还是需要给团队配置共享目录的后端或运维同学,都可以直接照着复现。
1. 从几个「反直觉」的现象说起
1.1 三个最常见的权限困惑
先把几个真实场景摆出来,它们看起来互相矛盾,但答案其实是统一的。
场景一:用户 alice 对一个文件拥有rw-权限,但执行rm时被拒绝,提示rm: cannot remove 'xxx': Permission denied。
场景二:某个目录权限是r--,ls能看到里面的文件名,但ls -l全部显示成-????????? ? ? ? ? ? file.txt,而且cd进不去。
场景三:一个脚本deploy.sh权限是-rw-r--r--,执行./deploy.sh报bash: ./deploy.sh: Permission denied;改成chmod +x之后能跑了,但同事把文件复制到另一台机器,又报同样的错误。
这三个现象不是 Linux 有 bug,而是很多人只记住了「r 是读、w 是写、x 是执行」这句口诀,却没有区分「作用于谁」。口诀对文件基本成立,对目录则完全不成立。目录的 rwx 描述的是「能不能看到目录里有哪些条目」「能不能穿过这个目录」「能不能增删目录里的条目」,跟「读取目录文件内容」没有关系。
理解了这一点,上面三个问题就都有了解释:删除文件真正校验的是父目录的写权限,而不是文件自身的写权限;目录只有 r 没有 x 时,只能读到「名字列表」却无法访问任何条目的元数据;脚本复制后丢失执行位,是因为cp默认不带-p时不保留权限。
1.2 权限模型的三要素:身份、权限位、对象类型
Linux 的权限模型(DAC,自主访问控制)可以用一句话概括:一个主体(进程的有效 uid/gid)对某个客体(文件或目录)能做什么,由客体上记录的三组权限位决定。
拆开来看包含三个要素:
- 身份分组:每个文件记录一个属主(owner/u)、一个属组(group/g),其余所有人归为 others(o)。内核判定时按 u → g → o 的顺序,命中即停,不会「取并集」。
- 权限位:每组身份对应三位,分别是 r(读)、w(写)、x(执行/进入)。三组共 9 位。
- 对象类型:文件、目录、符号链接、设备文件、套接字等。同样的 rwx 位,在不同对象类型上含义不同,本文的重点就是文件和目录这两种。
顺带说明「命中即停」的重要性。假设文件属主是 alice、属组是 devteam、权限是-rw-rw----。alice 属于 devteam,但她访问时内核先用 owner 位判定,得到rw-,就直接结束,不会因为组权限也是rw-而叠加。反过来,如果文件属主是 root、属组是 devteam、权限-rw-r-----,alice 属于 devteam,那么她拿到的是组权限r--,只能读不能写。这个顺序是很多「我明明在组里为什么没权限」问题的根因。
1.3 为什么必须区分文件与目录
用一个类比帮助记忆:
- 文件是「一本书」,r 是能不能翻开看,w 是能不能在书上写字,x 是这本书能不能当成「程序」来运行。
- 目录是「一个书架」,r 是能不能看到书架上贴的书名标签,x 是能不能走到书架前伸手取书,w 是能不能往书架上放书、把书拿下来扔掉、或者把标签换掉。
关键差别在于:书架上有什么书(目录里的条目列表)和书本身能不能读(文件内容)是两件事。你可能有权限看到书架上有《A 书》,但没有权限翻开它;也可能知道《A 书》在哪,却因为没有 x 权限而根本走不到书架前。
所以正确的记忆方式不是背 9 个字母,而是记住「目录的权限管的是条目操作,文件的权限管的是内容操作」这条主线。后文所有细节都是这条主线的推论。
2. 环境准备与权限查看工具
2.1 实验环境说明
本文的命令在主流 Linux 发行版上均可复现,包括 CentOS/RHEL 系、Ubuntu/Debian 系以及各类容器镜像。不同发行版在默认umask、默认用户组策略、sudo配置上有差异,具体数值请以你自己的环境为准;本文示例以常见的 root 可切换环境、默认umask 0022为例,重点演示权限语义,而不是某个发行版的特定行为。
实验前建议确认几件事:
- 你有一个可以
sudo或su -的账号,因为创建用户、用户组、修改他人文件属主都需要特权。 - 有至少两个普通测试用户,用于验证「同组」「不同组」「其他人」三种身份。可以新建,也可以用现有账号。
- 所有实验目录建议放在
/srv或/opt下自建的子目录,不要拿系统目录做实验。涉及删除和权限变更的操作,务必先在测试环境或虚拟机、容器中完成验证,不要直接在生产机上试。
准备两个测试用户和一个测试组,命令如下:
# 以下命令需要 root 或 sudo 权限 # 创建测试组 groupadd devteam # 创建两个测试用户,并加入 devteam 组 useradd -m -s /bin/bash alice useradd -m -s /bin/bash bob usermod -aG devteam alice usermod -aG devteam bob # 再加一个不在组内的用户,用于验证 others useradd -m -s /bin/bash carol # 确认结果 id alice id bob id carol预期输出类似:
uid=1001(alice) gid=1001(alice) groups=1001(alice),1002(devteam) uid=1002(bob) gid=1002(bob) groups=1002(bob),1002(devteam) uid=1003(carol) gid=1003(carol) groups=1003(carol)这里需要注意:usermod -aG中的-a表示追加,漏掉-a会把用户从其它附加组里移除,这是一个非常典型的生产事故来源。改完组之后,用户已经登录的会话不会立即生效,需要重新登录或使用newgrp devteam刷新。
2.2 ls -l 的输出逐位拆解
看权限最常用的命令是ls -l(看目录自身用ls -ld)。以一行典型输出为例:
-rwxr-xr-- 1 alice devteam 1024 Jan 1 10:00 deploy.sh从左到右逐段解释:
- 第 1 个字符
-:对象类型。-普通文件,d目录,l符号链接,c字符设备,b块设备,s套接字,p命名管道。 - 第 2–4 位
rwx:属主(alice)的权限。 - 第 5–7 位
r-x:属组(devteam)的权限。 - 第 8–10 位
r--:其他人(others)的权限。 - 后面的
1是硬链接数(对目录而言是子目录数量相关值),接着是属主、属组、大小、时间、文件名。
如果第 10 位不是-而是s、S、t、T,说明启用了 SUID、SGID 或 Sticky 位。这些属于特殊权限位,本文第 5 节会简单涉及,重点仍放在 rwx 上。
还有一种常见「异常」输出:
-????????? ? ? ? ? ? file.txt问号不是权限位真的坏了,而是ls -l需要读取文件的 inode 元数据才能显示属主、属组、大小,而当前用户对父目录缺少 x 权限,stat()调用失败,于是只能把未知字段打印成问号。这个现象本身就是「目录 r 与 x 分离」的直接证据,第 4 节会专门实测。
2.3 用 stat 与 namei 精确定位权限问题
ls -l适合快速扫一眼,排查问题时更推荐stat和namei。
stat /srv/project/sharedstat会以八进制和符号两种形式输出权限,还会给出 inode、链接数、三个时间戳等信息,非常适合写进排查记录:
File: /srv/project/shared Size: 4096 Blocks: 8 IO Block: 4096 directory Device: fd00h/64768d Inode: 131073 Links: 2 Access: (2775/drwxrwsr-x) Uid: ( 0/ root) Gid: ( 1002/devteam) Access: 2025-01-01 10:00:00.000000000 +0800 Modify: 2025-01-01 10:00:00.000000000 +0800 Change: 2025-01-01 10:00:00.000000000 +0800namei则专门解决「路径上每一级目录的权限分别是什么」这个高频问题:
namei -l /srv/project/shared/deploy.sh输出会逐级列出每一层目录的属主、属组和权限位。当出现「上级目录少一个 x 导致整条路径走不通」时,namei -l几乎是最快的定位手段,比手工一层层ls -ld高效得多。
2.4 chmod、chown、umask 三个基础工具
修改权限用chmod,它支持符号法和数字法两种写法。
符号法适合「只调整某几位」:
chmod u+x deploy.sh # 给属主加执行位 chmod g-w shared # 去掉属组的写权限 chmod o=r deploy.sh # 其他人设为只读(覆盖式赋值) chmod a+r deploy.sh # 所有人加读权限,a = u+g+o chmod u=rwx,g=rx,o= deploy.sh # 精确赋值,others 无任何权限数字法适合「整体设定」:
chmod 644 deploy.sh # rw-r--r-- chmod 755 deploy.sh # rwxr-xr-x chmod 600 private.key # rw------- chmod 750 shared # rwxr-x---数字的算法是把 r=4、w=2、x=1 相加。7=4+2+1,6=4+2,5=4+1,4=4,0=0。九位权限就是三组数字依次排列。
chown改属主和属组,chgrp只改属组:
chown alice deploy.sh # 只改属主 chown alice:devteam deploy.sh # 同时改属主和属组 chown :devteam deploy.sh # 只改属组 chown -R alice:devteam /srv/project/shared # 递归,谨慎使用umask决定新建文件和目录的「初始权限」。真实权限 = 默认最大权限 减去 umask。文件最大是 666(一般不给新建文件执行位),目录最大是 777。
umask # 0022 touch demo.txt && ls -l demo.txt # -rw-r--r-- 1 alice alice 0 Jan 1 10:00 demo.txt mkdir demo_dir && ls -ld demo_dir # drwxr-xr-x 2 alice alice 4096 Jan 1 10:00 demo_dir把umask设为0027,新建文件会变成640、目录变成750,这就是很多安全基线的推荐值。需要注意的是,umask是进程属性,只影响当前 shell 及其子进程,想长期生效要写进/etc/profile、/etc/bashrc或用户自己的~/.bashrc。
3. rwx 对文件的作用
3.1 r:读取文件内容
对普通文件来说,r 权限表示「允许读取文件内容」。它覆盖的操作包括cat、less、head、tail、grep、od等读操作,也包括作为cp的源文件被复制。
# 准备一个测试文件 echo "hello linux permission" > /srv/demo.txt chmod 400 /srv/demo.txt ls -l /srv/demo.txt # -r-------- 1 root root 21 Jan 1 10:00 /srv/demo.txt cat /srv/demo.txt # hello linux permission注意,即使只有 r 没有 w,文件内容依然可以被读取。这一点在配置私钥、证书、含敏感信息的配置文件时很重要:chmod 400表示「只有属主能读,谁都不能改」,比600更严格。
反过来,如果没有 r,即使有 w,也会出现「能覆盖写但不能读」的奇怪状态。此时用echo "x" >> file追加是可行的,但cat file会被拒绝。实践中几乎不会主动配置这种权限,但排查「文件被清空却看不到原内容」时,可以留意这一点。
3.2 w:修改文件内容
w 权限控制的是「修改文件内容」,包括追加、截断重写、原地修改。典型操作是echo >>、vim保存、sed -i、truncate等。
chmod 600 /srv/demo.txt echo "second line" >> /srv/demo.txt cat /srv/demo.txt # hello linux permission # second line chmod 400 /srv/demo.txt echo "third line" >> /srv/demo.txt # -bash: /srv/demo.txt: Permission denied这里有两个容易混淆的点需要澄清。
第一,w 权限不包含「读」。很多人以为「能写就能读」,其实不是。vim file打开一个只有 w 没有 r 的文件时会报错,因为编辑器需要先读取内容到缓冲区。所以实际使用中,可编辑的文件通常同时具备 r 和 w(即6)。
第二,文件权限不等于能否删除文件。这是本文最核心的结论之一,下一小节专门讲。
3.3 x:执行文件
x 权限对文件意味着「把它当作程序来执行」。它适用于两类文件:编译好的二进制程序,以及带 shebang 的脚本。
对二进制可执行文件,内核加载器只需要 x 权限,不需要 r 权限。你可以验证这一点:/bin/ls的权限通常是-rwxr-xr-x,把 r 从 others 拿掉后,普通用户依然可以执行ls(当然这属于危险操作,只在测试机上验证):
# 危险操作,仅在测试环境验证,验证后务必改回 ls -l /bin/ls # -rwxr-xr-x 1 root root 138208 ... /bin/ls chmod o-x /bin/ls ls # -bash: /bin/ls: Permission denied chmod o+x /bin/ls对脚本则不同。脚本本身不是一个可以被内核直接加载的程序,它的执行流程是:内核看到文件头部的#!(shebang),转交给对应的解释器,比如/bin/bash,由解释器以读取文件的方式加载脚本文本再逐行解释。因此脚本执行实际上需要两项权限:内核层面的 x,以及解释器层面读取脚本所需的 r。
实践中这意味着,把脚本权限设成--x是不安全的做法,虽然某些场景下能跑起来,但由于解释器读不到内容,通常会报错。推荐脚本统一用755或750。
3.4 脚本「能跑了又跑不了」的两种典型原因
第一种原因是复制过程丢失权限。cp在不加-p时不保留权限位,新文件按当前umask生成:
ls -l deploy.sh # -rwxr-xr-x 1 alice devteam 120 Jan 1 10:00 deploy.sh cp deploy.sh /tmp/deploy_copy.sh ls -l /tmp/deploy_copy.sh # -rw-r--r-- 1 alice alice 120 Jan 1 10:00 /tmp/deploy_copy.sh正确的做法是cp -p deploy.sh /tmp/(保留权限与时间戳),整体迁移目录时用cp -a或rsync -a。用scp传文件到另一台机器同样会因为目标端umask而丢失执行位,这是「本地能跑,服务器上跑不了」的高频原因。
第二种原因是挂载选项。哪怕权限位是755,如果所在文件系统是以noexec挂载的,执行时会直接报Permission denied。/tmp、/data之类的分区在安全加固后经常被设成noexec。可以这样确认:
findmnt -no OPTIONS /tmp # rw,nosuid,nodev,noexec,relatime如果输出里包含noexec,那么该目录下的所有可执行文件都无法直接运行,需要向管理员申请调整挂载策略,而不是去改chmod。
3.5 关键结论:能否删除一个文件,看的是父目录
这是必须单独强调的一条。文件自身的 w 权限管的是「文件内容能不能改」,删除、重命名、移动一个文件,本质上是修改它所在的目录,因此内核校验的是父目录的 w 和 x,与文件自身权限无关。
理论上的解释是:目录本质上是一张「名字 → inode 编号」的映射表,删除文件就是从这张表里删掉一行,所以这是对目录的写操作。这也能解释为什么rm一个444的文件可能成功,而rm一个777的文件却可能失败——真正起作用的是目录权限。
下一节会用完整实测来验证这个结论。
4. rwx 对目录的作用(重点)
4.1 r:能不能看到目录里有哪些条目
目录的 r 权限允许「列出目录中的条目名称」,对应操作是ls 目录、find 目录 -maxdepth 1、shell 的 Tab 补全。它只保证你能拿到名字列表,不保证你能拿到条目的属性,也不保证你能访问条目。
mkdir -p /srv/lab/ronly echo "content" > /srv/lab/ronly/a.txt chmod 444 /srv/lab/ronly ls /srv/lab/ronly # a.txt能列出名字。但此时访问条目会失败:
cat /srv/lab/ronly/a.txt # cat: /srv/lab/ronly/a.txt: Permission denied cd /srv/lab/ronly # -bash: cd: /srv/lab/ronly: Permission denied ls -l /srv/lab/ronly # ls: cannot access '/srv/lab/ronly/a.txt': Permission denied # total 0 # -????????? ? ? ? ? ? a.txt原因就是目录缺少 x。ls能读到目录的条目列表,但cat、cd、stat都需要在内核里「解析路径」,而路径解析要求路径上每一级目录都有 x 权限。
4.2 x:能不能「穿过」这个目录
目录的 x 权限(也常被叫做「搜索权限」)表示「允许把该目录作为路径的一部分进行穿越」。它带来三种能力:
cd进入该目录。- 访问该目录下已知名字的文件(前提是文件自身权限允许)。
- 对已知路径的文件执行
stat、读、写。
注意这里的关键限定词是「已知名字」。有 x 无 r 时,你无法列举目录内容,但如果别人告诉你确切文件名,你可以直接访问。
mkdir -p /srv/lab/xonly echo "secret" > /srv/lab/xonly/b.txt chmod 111 /srv/lab/xonly ls /srv/lab/xonly # ls: cannot open directory '/srv/lab/xonly': Permission denied cat /srv/lab/xonly/b.txt # secret这个「知道路径就能进」的特性在安全上是需要注意的:chmod 111的目录并不等于「目录内容被保护」,只等于「目录内容不可枚举」。如果文件名可以被猜测(比如config.ini、backup.tar.gz),那么内容依然是暴露的。真正的保护需要同时去掉 x,或者依赖上层应用鉴权。所以生产环境中目录权限不能只设 x,这在权限基线上是一条硬要求。
4.3 w:能不能在目录里增删条目
目录的 w 权限允许修改目录这张「名字表」,具体包括:
- 创建新文件或子目录(
touch、mkdir、cp到该目录、>重定向新建)。 - 删除已有条目(
rm、rmdir)。 - 重命名(
mv同目录内改名)。 - 移动条目进出该目录(同文件系统内的
mv)。
mkdir -p /srv/lab/wdir chmod 777 /srv/lab/wdir # 任意用户都可以在这里创建文件 touch /srv/lab/wdir/newfile.txt ls -l /srv/lab/wdir # -rw-r--r-- 1 alice alice 0 Jan 1 10:00 newfile.txt有一个非常重要的细节:目录的 w 必须搭配 x 才有意义。内核在创建、删除条目时需要解析路径,而解析路径依赖 x。只给w(即-w-,数字2)的目录,实际操作几乎全部失败,这是理论上的「死权限」。
实测一下:
mkdir -p /srv/lab/wonly chmod 222 /srv/lab/wonly touch /srv/lab/wonly/test.txt # touch: cannot touch '/srv/lab/wonly/test.txt': Permission denied4.4 八种目录权限组合对照表
把目录的三种 rwx 位按是否开启组合起来共 8 种,实际效果如下表(以ls、cd、cat 已知文件、touch 新文件、rm 已知文件五类操作为例):
| 权限位(u) | 数字 | ls 列举 | cd 进入 | cat 已知文件 | touch 新建 | rm 删除已有 |
|---|---|---|---|---|---|---|
--- | 0 | 失败 | 失败 | 失败 | 失败 | 失败 |
--x | 1 | 失败 | 成功 | 成功 | 失败 | 失败 |
-w- | 2 | 失败 | 失败 | 失败 | 失败 | 失败 |
-wx | 3 | 失败 | 成功 | 成功 | 成功 | 成功 |
r-- | 4 | 成功 | 失败 | 失败 | 失败 | 失败 |
r-x | 5 | 成功 | 成功 | 成功 | 失败 | 失败 |
rw- | 6 | 成功 | 失败 | 失败 | 失败 | 失败 |
rwx | 7 | 成功 | 成功 | 成功 | 成功 | 成功 |
从表里能读出三条结论:
- x 是「一切访问」的前置条件。缺 x 时,哪怕有 r 有 w,也无法进入目录、无法访问任何条目、无法创建文件。
rw-(6)看起来权限很高,实际是个废权限。 - r 只影响「列举」,不影响「访问」。
--x(1)能进能读写已知文件,但不能ls。 - w 必须与 x 组合才能创建和删除。这是「
-wx可以增删但看不到名字」这种看似矛盾却真实存在的状态。
补充一个常见误区:rm -rf 目录能否成功,取决于目录自身及其各级子目录是否有 w+x,以及路径上每一级父目录是否有 x。逐层递归的删除过程中任何一层缺 w 都会中断。这也是「rm -rf报 Permission denied 却只报了一堆子路径」的原因。
4.5 一个容易踩的坑:编辑器的保存策略
常规认知是「文件没有 w 权限就改不了」。用>或>>重定向时这个结论成立,因为 shell 会以写模式打开文件:
chmod 444 /srv/demo.txt echo "x" > /srv/demo.txt # -bash: /srv/demo.txt: Permission denied但部分编辑器保存文件时并不是原地写入,而是「写一个临时文件,然后 rename 覆盖原文件」。rename 是对目录的操作,只需要目录有 w+x,不需要原文件有 w 权限。所以在某些配置下,目录可写而文件444时,编辑器仍可能保存成功。
这个行为在不同编辑器、不同版本、不同配置下表现不一致,不必当作定论去记,但它给了我们两个实用启示:
- 判断「这个文件到底能不能被改」,不能只看文件权限,还要看父目录的权限,以及所属用户能否通过其它路径(例如同组的可写目录 + rename)绕过。
- 需要真正保护关键文件时,除了设
chmod 400,还应确保其所在目录不可被非特权用户写入,否则保护是不完整的。
5. 完整实战:搭建多用户共享目录
5.1 场景与目标
需求描述:团队devteam(成员 alice、bob)需要一个共享工作目录,同时还需要三种不同粒度的子目录:
shared/:组内成员可以自由读写、互相新增文件。readonly/:只有 admin(root)能写,组内成员只读。dropbox/:组内成员可以上传文件,但不能查看或删除别人的文件,适合「提交材料」场景。
目标目录结构:
/srv/project/ ├── shared/ ├── readonly/ └── dropbox/这个案例覆盖了目录 w 与 x 的组合、SGID 对新建文件属组的影响、Sticky 位对删除的限制,非常适合作为 rwx 语义的综合练习。所有操作请在测试环境完成,生产环境的权限变更需要走变更流程并做好备份与回滚准备。
5.2 创建目录结构并设置属主属组
# 创建顶层目录 mkdir -p /srv/project/{shared,readonly,dropbox} # 顶层目录归 root:devteam,组可进入但不可写 chown root:devteam /srv/project chmod 750 /srv/project # shared:组内可读写,加 SGID 让新建文件继承 devteam 组 chown root:devteam /srv/project/shared chmod 2775 /srv/project/shared # readonly:root 可写,组内只读,others 无权限 chown root:devteam /srv/project/readonly chmod 750 /srv/project/readonly # dropbox:组内可写不可读,加 Sticky 位限制删除他人文件 chown root:devteam /srv/project/dropbox chmod 1730 /srv/project/dropbox逐条解释这些数字的用意:
- 顶层
750:rwxr-x---。属主 root 全权,devteam 成员可以穿过顶层(有 x),但不能在顶层随意创建删除(无 w),防止有人绕过子目录策略直接在/srv/project下建目录。others 完全进不来。 shared的2775:开头的2是 SGID。对目录而言,SGID 的效果是「在该目录下新建的文件和子目录,属组自动继承为目录的属组」,而不是创建者的主组。没有 SGID 时 alice 建的文件属组是 alice,bob 就写不了;有了 SGID,属组统一是 devteam,配合组 w 位才能真正共享。这是共享目录里最容易被忽略的一步。readonly的750:devteam 是r-x,能进去、能读文件,但不能建、不能删。dropbox的1730:1是 Sticky 位(等价于o+t),730表示属主rwx、组-wx、others 无权限。组内成员可以进入和写入(有 w+x),但不能列举目录内容(没有 r),同时 Sticky 位保证只能删除自己创建的文件。
5.3 逐项验证权限效果
验证一:共享目录的读写与属组继承。
# 切到 alice su - alice echo "alice's note" > /srv/project/shared/note_by_alice.txt ls -l /srv/project/shared/note_by_alice.txt # -rw-rw-r-- 1 alice devteam 13 Jan 1 10:00 note_by_alice.txt注意属组是devteam而不是alice,这就是 SGID 生效的结果。此时 bob 可以覆盖修改这个文件,因为组权限是rw-。
验证二:只读目录的组内限制。
su - bob echo "try write" > /srv/project/readonly/c.txt # -bash: /srv/project/readonly/c.txt: Permission denied但读取是允许的:
# 先由 root 在 readonly 放一个文件 echo "public doc" > /srv/project/readonly/doc.txt chmod 644 /srv/project/readonly/doc.txt # bob 读取 cat /srv/project/readonly/doc.txt # public doc验证三:dropbox 的「可写不可读」。
# bob 上传文件 echo "bob's report" > /srv/project/dropbox/report_bob.txt # 成功 ls /srv/project/dropbox # ls: cannot open directory '/srv/project/dropbox': Permission denied # 因为组内没有 r 权限,无法列举 # bob 尝试删除 alice 的文件(先由 alice 放一个) su - alice -c 'echo "alice report" > /srv/project/dropbox/report_alice.txt' su - bob -c 'rm /srv/project/dropbox/report_alice.txt' # rm: cannot remove '/srv/project/dropbox/report_alice.txt': Operation not permitted最后一条正是 Sticky 位的作用。如果没有1前缀(即权限为730),bob 就能删掉 alice 的文件,因为目录对组有 w+x。
验证四:others 被完全挡住。
su - carol cd /srv/project # -bash: cd: /srv/project: Permission denied因为顶层目录的 others 位是---。
5.4 用 setfacl 处理「例外的一个人」
真实场景里经常出现「只有 carol 需要读 shared,但不想把她加进 devteam 组」的情况。此时不要为了一个人去改组、改目录权限,ACL 更合适:
# 给 carol 单独授予 shared 目录的 r-x setfacl -m u:carol:rx /srv/project/shared setfacl -R -m u:carol:rx /srv/project/shared # 查看 getfacl /srv/project/sharedgetfacl输出里会多出user:carol:r-x一行,同时ls -l的权限位末尾会出现一个+号,表示该对象存在 ACL。ACL 的优先级规则是:先看 ACL 中的具体用户项,再看 ACL 的 mask(有效权限),最后才落到传统的 owner/group/others。排查「ACL 加了为什么还是没权限」时,重点看getfacl里的mask行——mask 会把超出它的权限位「裁掉」。
ACL 属于对传统 rwx 模型的扩展,日常维护成本比单纯的组管理高,建议只在确实需要「点对点授权」时使用,并把getfacl的结果纳入配置记录。
6. 常见问题与排查思路
6.1 高频报错对照表
| 报错现象 | 常见原因 | 解决思路 |
|---|---|---|
cd: Permission denied | 目标目录或路径上某一级目录缺少 x | 用namei -l 路径逐级检查,补 x |
ls: cannot open directory: Permission denied | 目录缺少 r | 补 r;若只需访问已知文件,只给 x 也可支撑 |
ls -l显示大量? | 目录有 r 无 x,无法stat条目 | 补 x,通常设为r-x(5) |
rm: cannot remove: Permission denied | 父目录缺 w 或 x | 检查父目录权限,注意与文件自身权限无关 |
rm: cannot remove: Operation not permitted | 目录带 Sticky 位且文件不属于当前用户 | 由属主或 root 删除,或按策略调整 Sticky |
touch: cannot touch: Permission denied | 目录缺 w 或 x | 目录至少需要-wx(3)才能创建文件 |
bash: ./x.sh: Permission denied | 缺 x,或所在分区noexec,或 shebang 路径错误 | chmod +x;findmnt检查挂载选项;检查首行 |
bash: ./x.sh: /bin/bash^M: bad interpreter | 文件是 CRLF 换行 | dos2unix或sed -i 's/\r$//' x.sh |
| 复制后执行位丢失 | cp未加-p,或scp受目标umask影响 | 用cp -p、rsync -a,或复制后重新chmod |
| 「我在组里为什么不能写」 | owner 位优先于 group 位,命中即停 | 用id确认组,用ls -l确认 owner 是谁 |
| 刚加的组不生效 | 已登录会话未刷新组成员关系 | 重新登录,或newgrp 组名 |
| 改完权限仍不生效 | 存在 ACL,mask 截断了权限 | getfacl查看 mask 并调整 |
6.2 一条标准排查顺序
遇到权限类问题,建议按下面顺序走一遍,大多数问题 3 分钟内能定位:
- 确认身份:
id、groups,确认当前用户的实际 uid 和全部附加组,别凭印象。 - 确认对象:
ls -ld 目标或stat 目标,拿到属主、属组和权限位。 - 确认路径:
namei -l 绝对路径,检查每一级父目录是否有 x。 - 确认 ACL:
getfacl 目标,看是否有额外条目或 mask 限制。 - 确认挂载:
findmnt -T 目标,看是否ro(只读)或noexec。 - 确认特殊位:看第 10 位是否有
s/S/t/T,以及文件系统是否nosuid。 - 确认 SELinux/AppArmor:
getenforce、ls -Z、ausearch -m avc -ts recent。如果权限位完全正确但仍被拒绝,多半是强制访问控制在起作用,此时看/var/log/audit/audit.log比反复chmod有效得多。
第 7 步值得单独提醒:在启用了 SELinux 的系统上,权限位没问题却依然Permission denied是常见现象。此时正确的做法是查审计日志并用restorecon修复上下文,而不是无脑chmod 777——后者既不能解决问题,还会扩大攻击面。
6.3 root 能不能无视权限,边界在哪
root(uid 0)在内核判定中会跳过常规的 DAC 检查,所以 root 可以读任何文件、写任何文件,即使权限位是000。这解释了两个现象:一是「root 删不掉文件」几乎不会发生(除非文件系统只读或存在 immutable 属性);二是 root 也不需要文件的 r 权限就能读取内容。
但 root 也有边界,这几点必须知道:
- 执行文件仍然需要至少一个 x 位。一个
644的编译好的二进制,root 也无法直接执行,会报Permission denied。 - 只读挂载对 root 同样生效。
mount -o remount,ro之后 root 也写不进去。 chattr +i设置的不可变属性对 root 有效,需要先chattr -i才能修改。- 强制访问控制(SELinux 等)对 root 有约束。
这也是为什么权限设计的原则是「最小权限」而不是「给 root 就行」:root 绕过的能力在事故发生时同样会放大损失。日常操作应使用普通账号 +sudo精确授权,而不是长期以 root 身份工作。
7. 最佳实践与工程建议
7.1 建立默认权限基线
把「常见的权限该是多少」固化成团队基线,能避免大量临时判断。推荐一套通用起点:
| 对象 | 推荐权限 | 说明 |
|---|---|---|
| 普通文档、配置、源码 | 644 | 属主可改,其他人可读 |
| 可执行脚本、二进制 | 755 | 保留执行位,其他人只读执行 |
| 仅本人可读写的文件 | 600 | 日志、笔记、个人数据 |
| 私钥、证书私钥、凭据文件 | 600或400 | 400更严格,连属主都不能改 |
| 普通目录 | 755 | 可进入、可列举、他人不可写 |
| 团队目录 | 750 | 组内共享,others 拒绝 |
| 共享写目录 | 2775 | 加 SGID 保证属组继承 |
| 上传/投放目录 | 1730 | 加 Sticky,配合-wx实现可写不可读 |
配套设置:把系统默认umask调成027,新文件默认640、新目录默认750,比022更符合安全基线。对敏感服务(数据库、密钥管理),umask 077更合适。
7.2 共享目录必须同时解决「属组继承」和「删除越权」
很多团队配共享目录时只做了chmod 777,短期能用,长期留坑。正确做法是两件事一起做:
- 用SGID(目录上
chmod g+s或数字前缀 2)解决属组继承。否则每个人新建的文件属组都是自己的主组,别人写不了,共享名存实亡。 - 用Sticky(目录上加
o+t或数字前缀 1)限制删除,只允许文件属主、目录属主和 root 删除。 - 用
umask 002让新建文件默认664、目录775,组内可写。
三者组合起来的最小可用共享目录模型:
mkdir -p /srv/team chown root:devteam /srv/team chmod 2775 /srv/team # SGID + 组可读写 setfacl -d -m g:devteam:rwx /srv/team # 默认 ACL,让新子目录也继承组权限 setfacl -d -m o::- /srv/teamsetfacl -d设置的是默认 ACL,只对目录有效,作用是让该目录下新建的子文件、子目录自动获得对应 ACL 条目。这一条能解决「共享目录里再建子目录,组权限就丢了」的连锁问题。
7.3 权限变更必须当成一次生产变更
权限是线上故障的常见诱因,chmod -R、chown -R这类递归命令一旦打错一个数字或者多打一个斜杠,影响面可能是整个服务目录。建议把它当变更来管理:
- 先备份权限:变更前记录关键路径的权限快照,出问题能对照恢复。例如
find /srv/project -exec stat -c '%a %U %G %n' {} + > /root/perm_backup_$(date +%F).txt。 - 先在测试环境验证:至少在一台非生产机器或容器里跑通命令,确认影响范围。
- 拒绝
chmod -R 777:它既解决不了 SELinux、ACL、挂载选项引起的问题,又会把写权限扩散到所有用户,属于典型的「用一个安全问题换一个功能问题」。 - 使用最小范围:能用
chmod改单文件就不加-R;能用组权限解决就不加 others 位。 - 保留回滚手段:变更脚本里同时写好反向命令,并明确谁有权执行。
- 事后核对:变更后用
ls -l、stat、getfacl抽查几个关键路径,确认和预期一致。
7.4 定期审计与自动化检查
手工检查权限不现实,用find做几类常规扫描会比较省心:
# 查找全局可写的普通文件(潜在被篡改风险) find /srv -xdev -type f -perm -0002 -ls # 查找全局可写的目录,且不带 Sticky 位(风险最高) find /srv -xdev -type d -perm -0002 ! -perm -1000 -ls # 查找 SUID 文件 find / -xdev -type f -perm -4000 -ls 2>/dev/null # 查找无属主或无属组的文件 find /srv -xdev \( -nouser -o -nogroup \) -ls建议把这类扫描写成定时任务或纳入巡检脚本,输出做基线对比,出现新增项时告警。尤其是「全局可写目录 + 无 Sticky」这一类,往往是提权路径的来源。
7.5 需要避开的几个反模式
- 依赖 others 位做权限控制。给
o+w意味着系统上任何用户都能写,正确做法是建组并把人加进去。 - 用
chmod 777解决「服务启动不了」。根因往往是属主不对、上级目录缺 x、SELinux 上下文错误,777只是掩盖症状。 - 把密钥文件放在可写目录里。文件本身
600也没用,只要目录可被别人写,就能通过 rename 替换。 - 忽略
umask的进程作用域。在脚本里写umask 027只在子 shell 生效,想让服务进程也生效,要写进服务配置或启动脚本。 - 只检查最终目录,不检查路径。
/a/b/c中任何一级缺 x,后面全废,namei -l是必备工具。
8. 收尾:一张速查表
把全文压缩成一张能贴在屏幕边上的对照表:
| 权限位 | 对文件 | 对目录 |
|---|---|---|
| r | 读取文件内容 | 列出目录条目名称 |
| w | 修改文件内容 | 在目录中创建、删除、重命名条目 |
| x | 作为程序执行(脚本还需 r) | 进入目录、穿越、访问已知路径的条目 |
| 缺 r | 不能 cat,但可能能重定向覆盖 | 不能ls,但能cd并访问已知文件 |
| 缺 w | 不能改写内容,但可能被删除 | 不能增删条目,但能进能读 |
| 缺 x | 不能执行(root 也需要至少一个 x 位) | 一切访问失败,ls -l显示? |
| 删除文件 | 与文件自身权限无关 | 取决于父目录的 w+x |
| 常见取值 | 644/755/600 | 755/750/2775/1730 |
记住一条主线就够用:文件的 rwx 管内容,目录的 rwx 管条目;目录的 x 是通行证,w 是增删权,r 只是列表权。下次再遇到「明明有权限却操作失败」,先跑一遍id、namei -l、getfacl、findmnt这四条命令,绝大多数的 Permission denied 都会当场现形。
实操建议:把第 5 节的共享目录案例在自己的测试机上完整走一遍,尤其是222和444目录那两组反直觉的组合,亲手复现一次比看十遍文字都记得牢。权限这块内容的最佳学习方式就是动手——毕竟它验证起来只需要一条chmod和一次ls。