☰
Linux权限模型详解:从ls -l到chmod、umask与特殊权限位
2026/10/24 8:27:18 网站建设 项目流程

很多人第一次真正被 Linux 权限"教育",都是从一条chmod 777开始的。服务读不到配置文件、上传的图片返回 403、定时任务脚本跑不起来,搜索几轮之后得到的答案往往是"先加上 777 试试"。命令敲下去,问题确实消失了,但服务器上也从此多了一个谁都能读写执行的口子。过一段时间再回头看,没人记得当初为什么这么改,也没人敢改回去。

这就是权限管理最尴尬的地方:它平时不出声,出问题时又总被当成"玄学"。权限报错看起来只有一句Permission denied,背后却可能牵扯到用户身份、用户组、文件权限位、上层目录的可执行位、umask 默认值、特殊权限位,甚至文件系统挂载参数。只盯着报错的那一个文件,往往怎么改都没用。

这篇文章要讲清楚的是 Linux 权限的"介绍层"——也就是根子上那套模型:到底有几个身份、几种权限、权限位怎么读、目录和文件上的rwx为什么不是一回事、特殊权限位分别在解决什么问题。这里先给一个明确判断:权限管理真正难的从来不是记命令,而是建立"身份 + 权限位 + 作用对象"这三层认知模型。命令只有那么几个,模型不清楚,改多少次都是碰运气。

读完之后你应该能做到:拿到一个ls -l的输出,能逐位读明白;遇到权限异常,知道按什么顺序排查;给团队共享目录做权限规划时,不再靠777兜底。文章以常见的 Linux 发行版为例(CentOS、Ubuntu、Debian 均适用),具体版本以你的实际环境为准,所有命令都是通用的标准工具。

1. 权限管理到底解决什么问题

在多用户操作系统出现之前,"能不能访问一个文件"这个问题几乎不需要讨论,机器上就一个人。多用户系统一出现,问题立刻变得复杂:同一台服务器上跑着 Web 服务、数据库、定时任务和若干开发人员的登录会话,它们共用同一块磁盘、同一套进程管理、同一个内核。如果没有权限模型,任何一个用户都能读取数据库的数据文件、改写别的服务配置、删掉别人的代码。这在工程上是不可接受的。

所以权限管理要解决的核心问题可以拆成三句话:谁能访问、能怎么访问、访问哪个对象。Linux 用一个非常克制的方式回答了这三个问题:用 UID 和 GID 标识"谁",用读、写、执行三个比特标识"能怎么访问",用 inode 上的权限字段描述"访问哪个对象"。整个模型只有十几个比特,却支撑了几十年来的多用户系统。

这个设计有一个容易被忽略的后果:权限检查发生在每一次系统调用上,而不是在应用启动时检查一次。也就是说,进程打开文件、读目录、执行程序、删除文件,每一个动作都会触发内核的权限判定。这就解释了为什么"启动服务的时候没问题,运行到一半突然报权限错误"——很可能是因为运行过程中访问了一个启动时没碰过的路径,或者进程中途切换了有效用户。

对开发者来说,权限管理的价值不只是"别让报错出现"。它直接关系到几件事:生产环境的隔离边界(一个服务被攻破后能影响多大范围)、多团队协作时的文件归属、日志与数据的可审计性、容器与宿主机之间的文件映射关系。这些都不是靠chmod 777能解决的。

需要明确的是,本文讲的是 Linux 传统权限模型(也叫基本权限位)。它不包括 SELinux、AppArmor 这类强制访问控制,也不深入讲 POSIX ACL,这两块会在最后简单提一句作为延伸方向。先把基本模型吃透,再看上层扩展,才不至于混乱。

2. Linux 权限模型的核心概念

2.1 三类身份:属主、属组、其他

Linux 给每个文件都记录了两个身份属性:属主(owner,也叫 user)和属组(group)。再加上系统里的其他所有人,就构成了权限判定的三类主体:

身份类别英文标识含义
属主u(user)文件的所有者,通常是创建者,可被chown修改
属组g(group)文件归属的用户组,组内成员按组权限访问
其他o(other)既不是属主、也不在属组里的所有用户
全部a(all)u+g+o的简写,常用于 chmod

这里有个常被误解的点:权限判定是"就近匹配、只匹配一次"。内核按"属主 → 属组 → 其他"的顺序检查,一旦当前用户命中某一类,就只用那一类的权限位,不会再往下叠加。举个具体例子:文件report.txt属主是alice,属组是devteam,权限是-rw-------。如果bob也在devteam组里,他能不能读?答案是不能。因为bob不是属主,会进入属组判定,而组权限位是---,判定到这里就结束了,不会再看"其他"。很多"我明明在组里为什么还是不行"的问题,根源都在这里。

那进程的身份是怎么确定的?每个进程都有三组 ID:真实 UID/GID、有效 UID/GID、保存的 UID/GID。内核做权限判定时用的是有效 UID 和有效 GID。这就是为什么 SUID 程序能临时提升权限——它把有效 UID 切换成了文件属主的 UID。日常排查里用id命令确认当前身份最直接:

# 查看当前用户的 UID、GID 和所属的所有组 id # 查看某个用户的信息 id alice # 只看当前用户名 whoami

id输出的groups=后面会列出所有附加组,这个列表在权限判定时都会被考虑。如果这里没看到你期望的组,那就是usermod -aG没生效或者会话没重新登录——组成员关系在登录时被写入进程的凭证,改完之后必须重新登录(或者newgrp)才生效,这是非常高频的踩坑点。

2.2 文件和目录上的 rwx 含义不同

读、写、执行三个权限位,作用在普通文件和目录上时,语义完全不一样。这一节是整篇文章最重要的部分,因为绝大多数"权限改对了还是不行"的问题,都出在这里。

对普通文件来说:

  • r(read,4):可以读取文件内容,比如cat、less、程序读取配置文件。
  • w(write,2):可以修改文件内容,包括追加和截断。注意它不控制能否删除文件。
  • x(execute,1):可以把文件作为程序执行,比如运行脚本或二进制文件。

对目录来说,同一个字母的含义变了:

  • r(read,4):可以列出目录里的条目名,也就是ls能列出文件名。
  • w(write,2):可以在目录里创建、删除、重命名条目。这跟条目本身的权限无关。
  • x(execute,1):可以进入目录(cd)、可以访问目录下的条目(按已知路径打开文件、读取 inode 元信息)。

这里有两组反直觉的地方,值得反复强调。

第一组:删除一个文件,看的是它所在目录的写权限,而不是文件自己的写权限。假设/data/logs/app.log的权限是-r--r--r--,属主是 root,而/data/logs目录对bob是rwx。那么bob完全可以把app.log删掉,尽管他自己连写都写不进去。反过来,如果目录没有写权限,即使文件是777,也删不掉。这个机制看起来奇怪,但从文件系统角度看是合理的:删除一个目录项是在修改目录本身的内容。

第二组:目录没有x,即使有r也进不去。一个目录权限是r--,你可以ls看到文件名列表,但不能cd进去,也不能打开其中任何一个文件。这在实践中的表现是"文件名能列出来,但cat报 Permission denied"。如果目录是--x,你知道路径能进去,但列不出名字,只能靠完整路径直接访问已知文件。这种"只给执行不给读"的配置在共享上传目录里偶尔会用到。

正因为目录的x如此关键,排查权限问题时经常需要检查整条路径上的每一级目录。手动一层层ls -ld很麻烦,namei命令可以直接把路径上每一级的权限都打出来:

# 逐级展示路径上每个目录的权限与属主,排查"上层目录缺 x"的利器 namei -l /var/www/html/uploads/2024/a.png

输出会列出/、/var、/var/www一直到目标文件,每一级都标明权限。只要其中任意一级目录对当前用户没有x,访问就会失败。这个命令应该成为你排查权限问题的固定动作。

2.3 权限位与特殊权限位的整体布局

一个文件的权限字段一共 12 个比特,ls -l展示的是其中 9 个基本位加 3 个特殊位。用一张表把位置关系固定下来会清晰很多:

位置含义取值
第 1 位文件类型-普通文件、d目录、l符号链接、b块设备、c字符设备、s套接字、p命名管道
第 2-4 位属主权限rwx的组合
第 5-7 位属组权限rwx的组合
第 8-10 位其他用户权限rwx的组合
隐含的 SUID/SGID/Sticky特殊权限会用s、S、t、T覆盖在x位上显示

把这三类身份和三个权限位交叉起来,就得到了 9 个基本位。数字表示的规则很直观:r=4、w=2、x=1,组内求和。于是rwx=7、rw-=6、r-x=5、r--=4,这就是后面chmod 755这类写法的来历。理解了这个换算,权限数字就不再需要死记硬背。

3. 实验环境准备与目录搭建

权限相关操作有一定破坏性,尤其涉及到-R(递归)和chown时,改错目录的代价可能很大。建议在一个独立的实验目录里完成本文的操作,不要直接在生产机器的系统目录上试。

环境要求很简单:一台能登录的 Linux 机器(物理机、虚拟机、云主机或容器都可以),拥有sudo权限或者能直接用 root 操作。检查一下基础工具是否齐全:

# 确认当前系统与身份 cat /etc/os-release id # 确认关键命令存在(绝大多数发行版默认已安装) which ls chmod chown chgrp umask id namei getfacl setfacl # 确认当前 shell,umask 的表现与 shell 配置有关 echo $SHELL

如果getfacl或setfacl提示找不到,说明 ACL 工具没装,在 Debian/Ubuntu 系上装acl包即可(sudo apt install acl),RHEL 系上同样有acl包。本文主体不依赖 ACL,只有最后一小节会用到。

接下来建立实验用的组、用户和目录。这里模拟一个典型的团队协作场景:一个devteam组,两个成员alice和bob,一个共享目录/srv/share。为了避免污染系统环境,用户也可以先不创建,用当前用户完成前面的观察实验,等到了实战章节再创建。

# 以下命令需要 root 或 sudo 权限执行 sudo groupadd devteam # -m 创建家目录,-G 指定附加组,-s 指定登录 shell sudo useradd -m -G devteam -s /bin/bash alice sudo useradd -m -G devteam -s /bin/bash bob # 为用户设置密码(交互式输入) sudo passwd alice sudo passwd bob # 验证组成员 id alice id bob

执行完useradd后有个细节必须注意:用户的家目录权限默认通常是700或755,取决于发行版的/etc/login.defs配置。如果alice的家目录是700,bob是无法进入的,这在测试共享目录时不构成影响,但在测试"其他用户能读到家目录里的文件"这类场景时会造成困惑。排查时先用ls -ld /home/alice确认。

再准备一个普通用户视角的测试目录,接下来的观察实验都放在这里:

# 建立实验目录 sudo mkdir -p /srv/lab sudo chown $(id -un):$(id -gn) /srv/lab # 进入实验目录 cd /srv/lab # 创建几个测试对象 touch demo.txt mkdir demo_dir echo "hello permission" > demo.txt # 观察默认权限 ls -l demo.txt ls -ld demo_dir

这时候你会看到demo.txt大概是-rw-r--r--,demo_dir大概是drwxr-xr-x。为什么是这两个值而不是777?这就是 umask 在起作用,第 6 章会专门讲。

4. 读懂 ls -l 输出并验证权限

ls -l是权限世界的第一扇门。在改任何权限之前,先学会把它读完整。拿一行典型输出来拆解:

-rw-r--r-- 1 alice devteam 1024 Mar 12 10:20 demo.txt

从左往右依次是:文件类型 + 9 位权限、硬链接数、属主、属组、文件大小、最后修改时间、文件名。很多人只看权限位,忽略了属主和属组,结果改了半天权限发现改的不是自己想的那个人。

把第一段-rw-r--r--按位拆开看:

  • -:普通文件。如果是d就是目录,l是符号链接。
  • rw-:属主alice有读写权限,没有执行权限。
  • r--:属组devteam的成员只读。
  • r--:其他用户只读。

再看一个目录的例子:

drwxr-x--- 2 alice devteam 4096 Mar 12 10:25 demo_dir

d表示目录,属主有rwx(能列、能建删、能进),属组有r-x(能列、能进但不能建删),其他用户完全没有权限(连进都进不去)。

在实验目录里做几个验证,把"权限位到底管什么"变成可观察的现象。第一个实验验证目录x的作用:

# 准备:创建一个目录并在里面放文件 cd /srv/lab mkdir -p perm_test && echo "content" > perm_test/inner.txt # 去掉目录的执行权限,但保留读权限 chmod 644 perm_test ls -ld perm_test # 期望输出类似:drw-r--r-- ... perm_test # 此时可以列出文件名 ls perm_test # 但无法进入,也无法打开里面的文件 cd perm_test # 期望报错:bash: cd: perm_test: Permission denied cat perm_test/inner.txt # 期望报错:cat: perm_test/inner.txt: Permission denied # 恢复权限 chmod 755 perm_test

这个实验说明了一件很关键的事:ls能看到文件名,是因为目录有r;打不开文件、进不去目录,是因为目录没有x。如果只看到 "Permission denied" 就无脑把文件本身改成 777,问题依然存在——因为卡点在目录上。

第二个实验验证"删除文件看目录权限":

# 在上一步恢复后的 perm_test 目录里操作 chmod 755 perm_test echo "readonly content" > perm_test/locked.txt # 把文件本身设为只读(去掉所有写权限) chmod 444 perm_test/locked.txt ls -l perm_test/locked.txt # 期望:-r--r--r-- ... locked.txt # 尝试改写文件内容,应该失败 echo "try to write" >> perm_test/locked.txt # 期望报错:Permission denied # 但如果目录本身可写,就可以直接删掉这个"只读"文件 rm -f perm_test/locked.txt ls perm_test # 文件已被删除,没有报错

这里就体现出了权限模型的分层:文件的w管的是内容能否修改,目录的w管的是条目能否增删。二者互不干涉。这也是为什么在共享目录里,如果想防止别人删你的文件,光把文件设成只读是没用的,必须依赖粘滞位(Sticky Bit,见第 7 章)或者干脆不给目录写权限。

第三个观察点是用stat看更底层的信息,包括数字权限和 inode 号:

stat demo.txt # 输出中会包含:Access: (0644/-rw-r--r--) Uid: (1001/alice) Gid: (1002/devteam)

0644这个八进制数就是权限位的数字表示,前面多出来的那个0是特殊权限位(本文第 7 章会用到它)。习惯看这个数字,比对着rwx数字符更稳。

5. chmod:符号法与数字法实操

chmod(change mode)是修改权限的主命令,有两种写法:数字法和符号法。两种都要会,因为适用场景不同。

5.1 数字法:适合一次性设定完整权限

数字法把 9 个位用三个八进制数字表示,每个数字是r=4、w=2、x=1的和。它的特点是绝对赋值:你写什么,结果就是什么,不会保留原有位。

数字权限常见用途
777rwxrwxrwx极少应该使用,任何用户都能读写执行
755rwxr-xr-x目录、可执行程序的常规选择
750rwxr-x---组内可访问、其他用户禁止的目录
700rwx------仅属主可访问的私有目录,如家目录
644rw-r--r--普通配置文件、静态资源
600rw-------私钥、含密码的配置文件
640rw-r-----属主可写、组内可读的配置
444r--r--r--纯只读文件

用起来很直接:

cd /srv/lab # 给脚本加执行权限,属主读写执行,组和其他只读 chmod 744 demo.sh 2>/dev/null || { echo '#!/bin/bash' > demo.sh; chmod 744 demo.sh; } ls -l demo.sh # 目录设为 750:属主全权,属组可读可进,其他用户禁止 chmod 750 demo_dir ls -ld demo_dir # 敏感文件设为 600 echo "password=secret" > secret.conf chmod 600 secret.conf ls -l secret.conf

数字法的坑在于"绝对赋值"这个特性:执行chmod 644 script.sh会把执行权限一并抹掉,哪怕它原来是755。对脚本、二进制程序、目录来说,这是常见的"改完就坏"的原因。所以对可执行文件动手之前,先ls -l看一眼当前是什么。

5.2 符号法:适合精确增删某一位

符号法的语法是[ugoa][+-=][rwx],可以理解为"给谁、做什么、哪些权限"。它最大的优势是相对修改,不会误伤其他位。

cd /srv/lab # 给属主增加执行权限,其他位保持不变 chmod u+x demo.sh ls -l demo.sh # 给属组增加写权限 chmod g+w secret.conf ls -l secret.conf # 去掉其他用户的所有权限 chmod o-rwx secret.conf ls -l secret.conf # 一次性给所有人加读权限 chmod a+r demo.sh # 精确赋值:属主读写执行,属组读执行,其他无权限(等价于 750) chmod u=rwx,g=rx,o= demo_dir ls -ld demo_dir # 也可以用逗号同时操作多类身份 chmod u=rw,g=r,o=r demo.sh

=和+/-的区别要留意:+和-是在原有基础上加减,=是直接重置。chmod o=就是清空"其他"的所有权限,等价于chmod o-rwx,但更简洁。

符号法在脚本里特别有用。比如部署脚本需要确保配置文件是640,但不想关心它原来是多少,可以写成chmod u=rw,g=r,o= config.yml,语义比chmod 640更明确,读代码的人一眼就知道意图。

5.3 递归修改的坑:-R 不是万能钥匙

chmod -R会递归地改目录下所有内容,命令执行很快,后果可能很久。最常见的翻车现场是执行了chmod -R 777 /var/www,结果所有 HTML 文件都变成了可执行,所有目录变成了任何人都能写。虽然能让站点暂时跑起来,但等于给整个 Web 目录开了一个后门。

更隐蔽的问题是:文件和目录需要不同的权限。目录通常需要x才能进入,文件通常不需要x。用一条chmod -R命令施加同一个权限值,必然有一类对象是错配的。正确的做法是分开处理:

# 先处理文件:目录下的普通文件统一设为 644 find /srv/lab/website -type f -exec chmod 644 {} \; # 再处理目录:所有子目录统一设为 755 find /srv/lab/website -type d -exec chmod 755 {} \; # 单独给需要执行的脚本加执行位 find /srv/lab/website -type f -name "*.sh" -exec chmod 755 {} \;

这段写法在线上环境非常值得固化成部署脚本的一部分。它把"权限"和"对象类型"绑定在一起,避免了一次性赋值带来的错配。

如果确实要用-R,建议加上--preserve-root这类保护,并且在执行前先把当前权限快照下来,方便回滚:

# 记录目录下所有对象的权限、属主、属组,便于事后对比或恢复 cd /srv/lab find . -exec stat -c '%a %U:%G %n' {} \; > /tmp/perms_backup_$(date +%F).txt head -20 /tmp/perms_backup_$(date +%F).txt

这个快照文件在"改错了想还原"的时候价值极高。生产环境做权限变更前,拍这么一张快照应该成为固定动作。

6. chown、chgrp 与 umask 的配合使用

6.1 修改属主与属组

chmod改的是权限位,chown改的是归属,两者不是一回事。很多"权限看起来是对的但就是不行"的问题,实际上是归属错了——文件属主是一个已经不存在的用户,或者属组不是期望的那个组。

# 只改属主 sudo chown alice demo.txt # 同时改属主和属组,用冒号分隔 sudo chown alice:devteam demo.txt # 只改属组(等价于 chgrp devteam demo.txt) sudo chown :devteam demo.txt # 递归修改目录下所有内容,需要谨慎 sudo chown -R alice:devteam /srv/share

有三个细节必须记住。

第一,普通用户不能把文件送人。即使bob对自己的文件有完全控制权,他也不能chown alice file。改变属主需要 root 权限,这是防止用户之间互相"甩锅"的机制。反过来,属主可以把自己的文件改到自己所在的某个组(chown :somegroup file只要自己在那个组里就行),但把组改成自己不属于的组同样需要 root。

第二,-R加上chown同样危险。在系统目录上误执行chown -R可能直接破坏系统的权限体系,尤其是把/或者/usr的属主改掉。务必确认路径写对了再回车。

第三,符号链接的坑。默认情况下chown改的是符号链接指向的目标文件,而不是链接本身。如果确实需要改链接自身(少见),要用-h。这个差异在部署脚本处理软链接时会造成困惑。

chgrp是只改组的功能,用法和chown :group等价,有些场景写起来更语义化:

# 把目录的属组改为 devteam sudo chgrp devteam /srv/share ls -ld /srv/share

6.2 umask:新建对象的默认权限从哪来

前面在实验目录里创建文件时,我们看到默认权限是644而不是777。这个差距就是 umask 造成的。

umask 是一个"权限掩码",作用是从基础权限里扣掉某些位。规则是:

  • 新建普通文件的基础权限是666(默认不给执行权限,出于安全考虑)。
  • 新建目录的基础权限是777(目录必须有x才能用)。
  • 实际权限 = 基础权限按位与~umask。

常见的 umask 值和结果:

umask新建文件权限新建目录权限适用场景
022644755绝大多数服务器的默认值,其他用户只读
002664775团队共享目录,同组成员可写
077600700安全要求高的环境,完全私有
027640750组内可读、其他用户禁止

验证方式很直接:

# 查看当前 umask umask # 临时修改 umask 后创建对象,观察差异 cd /srv/lab umask 077 touch private.txt mkdir private_dir ls -l private.txt ls -ld private_dir # 期望:-rw------- 和 drwx------ # 恢复默认(新开的 shell 会恢复,或显式设置) umask 022 touch public.txt ls -l public.txt # 期望:-rw-r--r--

umask 有一个非常重要的特性:它是进程级的属性,不是系统级的全局配置。每个 shell 有自己的 umask,从 shell 里启动的进程会继承它。所以会出现一种很典型的现象:手动在终端里创建文件权限正常,但通过 systemd 服务或 cron 任务创建的文件权限不对,因为那些服务有自己的 umask 设置(systemd 单元里可以用UMask=显式指定)。

持久化配置需要写到 shell 的启动文件(如/etc/profile、~/.bashrc)或者 systemd 单元里。这里建议:不要习惯性地把 umask 设成000图方便,那等于让所有新文件对所有人可读可写,风险远大于收益。

7. 特殊权限:SUID、SGID、Sticky

前面讲的 9 个基本位之外,Linux 还有 3 个特殊权限位。它们不常用,但一旦出现,必须能看懂——因为看不懂就意味着无法判断风险。

特殊权限数字作用在文件上作用在目录上
SUID4执行时以文件属主的身份运行无意义
SGID2执行时以文件属组的身份运行新建对象继承目录的属组
Sticky1无意义(早期有)只有属主能删除自己的文件

7.1 SUID:临时借用属主身份

最经典的例子是passwd命令。普通用户需要修改自己的密码,而密码存在/etc/shadow里,这个文件是000权限、属主 root。普通用户显然没有写权限,但passwd又能正常工作,靠的就是 SUID:

ls -l /usr/bin/passwd # 典型输出:-rwsr-xr-x 1 root root ... /usr/bin/passwd

注意属主执行位上显示的是s而不是x,说明 SUID 生效了。进程启动后,有效 UID 变成了 root,于是有权限修改/etc/shadow,写完再退出。整个过程用户只借用了这一小段时间的 root 身份。

S(大写)是另一种情况:表示 SUID 位已设置,但执行位x本身没有。这种组合通常意味着配置有误,因为 SUID 脱离了可执行位没有实际意义。看到大写的S要警惕。

SUID 是安全敏感项。一个配置了 SUID 的 root 所有程序,如果有漏洞被利用,攻击者就能直接获得 root。所以在安全加固中,定期审计系统中所有 SUID 文件是标准动作:

# 列出系统中所有设置了 SUID 的文件 sudo find / -perm -4000 -type f 2>/dev/null | sort # 同时列出 SGID 文件 sudo find / -perm -2000 -type f 2>/dev/null | sort

这份清单应该保持稳定。如果某天多了一个你不认识的文件,那就是值得追查的信号。

7.2 SGID:目录上的组继承

SGID 用在文件上是"以属组身份执行",用得不多。但用在目录上有一个非常实用的效果:在这个目录里新建的文件和子目录,会自动继承该目录的属组,而不是创建者的主组。

这个特性直接解决了团队协作里的一个大麻烦。假设/srv/share属于devteam组,alice在里面创建了文件。如果目录没设 SGID,文件的属组会是alice的主组(可能是alice自己),bob就无法通过组权限访问。设了 SGID 之后,文件属组自动变成devteam,组内成员都能正常访问。

# 给目录设置 SGID,数字表示是 2 开头 sudo chmod 2770 /srv/share # 或者用符号法 sudo chmod g+s /srv/share ls -ld /srv/share # 期望输出:drwxrws--- ... /srv/share # 注意属组执行位显示为 s

这里要注意 SGID 只解决"属组继承",不解决"新文件权限"。新建文件的权限仍然受创建者的 umask 影响,第 8 章会用完整实战把这一层补上。

7.3 Sticky Bit:共享目录的删除保护

Sticky Bit 用在目录上,效果是:目录里的文件,只有文件属主、目录属主或 root 能删除。即使目录对所有人可写,别人也删不掉你的文件。

标准例子是/tmp:

ls -ld /tmp # 典型输出:drwxrwxrwt ... /tmp # 最后的 t 表示 Sticky Bit 生效

/tmp是777,任何用户都能在里面创建文件,但没人能删别人创建的文件。如果没有 Sticky,任何用户都能删掉别人的临时文件,这会带来明显的稳定性问题。

设置方式和 SGID 类似:

sudo chmod 1777 /srv/public_upload # 或者 sudo chmod +t /srv/public_upload ls -ld /srv/public_upload # 期望:drwxrwxrwt

如果看到的是大写的T,说明 Sticky 位设了但其他用户的执行位没有,这在目录上通常意味着配置错误,因为目录缺了x就进不去。

这三个特殊位在ls -l里的显示位置是覆盖在x位上的,所以对照时要注意大小写:小写(s、t)表示特殊位和对应的x同时生效,大写(S、T)表示只有特殊位生效而x缺失。

8. 完整实战:搭建一个团队共享目录

把前面所有知识点串起来,做一个真实场景:/srv/share是devteam组的共享目录,要求成员之间能互相读写对方的文件,其他用户完全不能访问,并且新文件自动归属到devteam组。

具体需求拆解:

  1. 只有devteam组内成员能进入和使用,其他用户彻底隔离。
  2. 组内成员能创建、修改、删除文件。
  3. 新建的文件属组自动是devteam(SGID)。
  4. 新建的文件对组内可写(需要调整 umask 或用 ACL)。
  5. 生产环境还会加一层:防止成员互相误删(可选,加 Sticky)。

先建目录并配置基础权限:

# 创建目录并设置归属 sudo mkdir -p /srv/share sudo chown root:devteam /srv/share # 2770 = SGID + 属主 rwx + 属组 rwx + 其他无权限 sudo chmod 2770 /srv/share # 验证 ls -ld /srv/share # 期望:drwxrws--- 2 root devteam ... /srv/share

2770里的2就是 SGID。这样组内成员可以进目录、可以创建文件,其他用户连目录都进不去。

接着处理 umask 带来的问题。这一层非常容易被忽略:假设alice的 umask 是022,她在共享目录里新建文件,文件权限会是644,属组虽然是devteam(SGID 生效),但组权限只有r,没有w。也就是说bob能读不能改,协作目标只完成了一半。

解决办法有两个。

方案一:让组成员把 umask 改为002。写进各自 shell 的启动文件:

# 写入当前用户的 ~/.bashrc,新建 shell 生效 echo 'umask 002' >> ~/.bashrc source ~/.bashrc umask

这样新建文件是664、新目录是775,组内可写。缺点是依赖每个成员自觉配置,新人加入时容易漏。

方案二:用 POSIX ACL 在目录上设置默认权限,强制新对象继承。这个更可靠,因为它不依赖用户配置:

# 给目录设置默认 ACL:组内成员对新对象拥有读写权限 sudo setfacl -d -m g:devteam:rwx /srv/share sudo setfacl -d -m u::rwx /srv/share sudo setfacl -d -m o::--- /srv/share # 目录本身的访问 ACL sudo setfacl -m g:devteam:rwx /srv/share # 查看结果 sudo getfacl /srv/share

-d表示 default,作用于此后新建的子对象。设置完成后,alice新建的任何文件都会自动带上组rwx的 ACL 条目,bob就能直接修改。这个方案在多人协作目录里是更工程化的做法。

验证环节用两个真实用户跑一遍:

# 切换到 alice 创建文件 sudo -u alice bash -c 'echo "from alice" > /srv/share/alice.txt' sudo -u alice bash -c 'ls -l /srv/share/alice.txt' # 切换到 bob 追加内容 sudo -u bob bash -c 'echo "from bob" >> /srv/share/alice.txt' sudo -u bob bash -c 'cat /srv/share/alice.txt' # 期望能成功追加并看到两行内容 # 用组外用户验证隔离(假设系统里有 nobody 或其他非 devteam 用户) sudo -u nobody ls /srv/share # 期望报错:Permission denied

如果第二步bob追加时报Permission denied,第一件事不是改权限,而是getfacl /srv/share/alice.txt看默认 ACL 是否真的继承下来了。如果 ACL 生效但权限仍然不够,再检查namei -l /srv/share/alice.txt确认路径上每一级目录都有x。

最后补充一个可选项:如果希望防止成员之间误删文件,可以加 Sticky 位,但要注意 Sticky 在770这种组内共享的场景下会把"删除权"限制到文件属主,反而可能影响正常的协作流程。是否加要看业务需求,不要默认加上。

9. 常见问题排查与最佳实践

9.1 高频问题与排查顺序

权限问题的排查要讲顺序,乱试一通只会把问题越改越乱。推荐顺序是:确认身份 → 确认路径上的目录权限 → 确认目标对象权限 → 确认特殊机制(ACL、SELinux、挂载参数)。

问题现象可能原因排查方式解决方案
已经chmod 777还是Permission denied上级目录缺少x,进不去namei -l /完整/路径逐级检查给路径上缺失的目录补上x(通常chmod o+x或调整属组)
明明在组里,还是没权限权限判定只匹配一次,命中属主位后不再看组;或组成员关系未生效id看groups=是否包含该组确认权限位给到了对应的身份;必要时重新登录使组关系生效
新建文件权限不是预期的664umask 是进程级属性,不是全局umask查看当前值在 shell 启动文件或 systemd 单元里显式设置umask 002
chown报Operation not permitted普通用户无权改变属主id确认当前用户用sudo执行,或改为修改自己有权改的属组
脚本无法执行,报Permission denied缺少执行位;或所在文件系统挂载了noexecls -l看执行位;mount看挂载参数补chmod u+x;或把脚本移到允许执行的分区
只读文件能改,只读目录里的文件改不了文件内容的修改看文件w,条目的增删看目录w分别ls -l文件和ls -ld目录按实际需求调整对应层的权限,不要混淆
所有权限都正常,还是报拒绝SELinux 或 AppArmor 拦截;磁盘满;文件系统只读getenforce、dmesg、df -h、mount按安全策略调整上下文,或释放空间、修复挂载
新建文件属组不对目录未设 SGID;或创建者 umask 会清掉组位ls -ld看目录是否有s;ls -l看文件属组对目录设chmod g+s,或用默认 ACL 强制继承

上表里有两条值得再展开一点。

一条是"在组里但没权限"。这个问题的排查动作应该是先id确认附加组是否真的包含目标组。usermod -aG devteam alice执行完,alice已经登录的会话是不会自动获得新组的,必须重新登录。这是新手最容易卡住的地方,也是最容易被误判成"权限没设对"的地方。

另一条是 SELinux。在 RHEL 系系统上,getenforce返回Enforcing时,即使传统权限全开,SELinux 也可能拦住访问。判断方法是看/var/log/audit/audit.log里有没有对应的AVC denied记录。不要用setenforce 0一关了之,这等于放弃了整个强制访问控制层;正确做法是用ls -Z看上下文,用chcon或semanage fcontext修正标签。

9.2 生产环境的最佳实践

权限配置的本质是在"可用"和"可控"之间找平衡点。下面这些做法在真实项目里验证过有效,值得坚持。

坚持最小权限原则,把777当成红灯。任何时候看到自己准备敲chmod 777,先停下来问一句:到底缺的是哪一位?是目录的x?是文件的w?是属组不对?绝大多数情况下,精确地补上那一位就够了,不需要全开。全开的代价是这台机器上的任何一个被攻破的进程都可能改写你的文件。

业务进程绝不用 root 跑。用独立系统用户运行服务(比如app用户),代码目录归属该用户,配置目录750,密钥文件600。这样即使服务被攻破,攻击者的权限也被限制在app用户范围内。容器场景同样如此,镜像里应该显式创建非 root 用户并USER切换,而不是默认 root 启动。

优先用组管理权限,而不是给单个用户开绿灯。十个用户需要访问同一目录时,正确的做法是建组、加人、把目录属组设成该组、权限给到g位。这样加人减人只需要usermod,不用反复改权限。配合 SGID 和默认 ACL,新建文件也能自动对齐。

变更前拍权限快照。大规模改动前执行一次:

# 快照整个目录的权限、属主、属组和 ACL cd /target/dir find . -exec stat -c '%a %U:%G %n' {} \; > /tmp/perm_snapshot.txt getfacl -R . > /tmp/acl_snapshot.txt # 需要回滚时,ACL 可以直接还原 setfacl --restore=/tmp/acl_snapshot.txt

有了快照,出问题时的回滚成本从"逐个猜"降到"一条命令"。

定期审计敏感文件和高危权限。几条常用的审计命令:

# 找出全局可写的文件(排除临时目录和符号链接) sudo find / -xdev -type f -perm -0002 ! -path '/tmp/*' ! -path '/proc/*' 2>/dev/null # 找出设置了 SUID 的文件 sudo find / -xdev -perm -4000 -type f 2>/dev/null | sort # 找出没有属主的孤儿文件(属主用户已删除) sudo find / -xdev -nouser -o -nouid 2>/dev/null | head -50

这些清单应该定期生成并对比,出现异常时及时追查。

在配置管理和部署脚本里显式声明权限。靠手工chmod是没法保证一致性的,因为环境重建后权限状态就丢了。把权限写进 Dockerfile、Ansible playbook、部署脚本或 systemd 单元,让每次部署都得到相同的权限结果。比如 Dockerfile 里用COPY --chown或显式的RUN chmod,systemd 单元里用UMask=0027。权限作为配置的一部分被版本化,才不会在迭代中悄悄漂移。

区分"临时授权"和"长期授权"。临时需要让某个用户访问一个目录时,用 ACL 加一条条目(setfacl -m u:someone:rx dir),用完就删(setfacl -x u:someone dir),比改o位干净得多。把权限变更当作有生命周期的操作来管理,而不是一次性动作。

到这一步,Linux 权限的整套基础模型应该已经连起来了:身份决定"以谁的名义访问",权限位决定"允许哪些动作",对象类型决定"这些动作作用在什么上面",umask 和特殊位负责处理默认值与继承这类边界情况。真正需要练的是排查习惯——遇到Permission denied时先id再namei -l,而不是条件反射地打开777。建议在你的实验机上把第 8 章那套共享目录从头搭一遍,把getfacl的输出保存下来做对照,比单纯读文档记得牢。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询