权限这个话题,我踩过的坑比大多数人想象中要多。早年维护一台多用户协作的服务器时,遇到过一件特别费解的事:一个普通用户抱怨自己上传到共享目录的文件被同事误删了,我去看ls -l,权限明明写着drwxrwxrwx,按理说谁都能读写,但为什么会出现"删了别人的文件"这种越界行为?后来才意识到,问题根本不在rwx这三个字母上,而是藏在它们背后的那几个特殊权限位——suid、sgid、sticky。这三个东西平时不显山不露水,可一旦你在做多用户系统、团队共享目录、或者排查系统里那些莫名其妙的权限继承问题时,它们就是绕不开的核心。很多刚上手 Linux 的朋友,看到rwsr-xr-x里那个小写的s、或者目录权限末尾的t,第一反应是"这是不是打错了",其实那都是内核在明确告诉你:这个文件或目录,除了常规读写执行之外,还带了一层特殊身份逻辑。这篇内容我打算把这三个特殊权限位从设计动机、内核判定逻辑、到实际配置、再到排查踩坑,完整地捋一遍。不管你是在准备面试、日常运维、还是自己搭开发环境,把这套东西吃透,能省下不少半夜爬起来查日志的时间。
1. 为什么普通 rwx 权限不够用:特殊权限位的设计动机
1.1 从一个真实的权限继承问题说起
在 Unix/Linux 的权限模型里,最基础的一套规则其实很干净:每个文件有一个属主(owner)、一个属组(group)、以及其他用户(others),每类身份对应三位权限,分别是读(r=4)、写(w=2)、执行(x=1)。这套模型管的是"谁能对这个文件干什么",逻辑直观,没毛病。
但真正落到多用户系统里,你会发现有两类需求它天然覆盖不了。第一类是"身份临时切换"的需求:比如普通用户要改自己的登录密码,密码存在/etc/shadow里,而这个文件只有 root 能读写,那普通用户是怎么做到改密码的?答案就是passwd这个命令文件带了suid位,让普通用户在执行它的那一刻,临时获得文件属主 root 的身份。第二类是"目录内容归属"的需求:一个团队共享目录,希望组内每个人都能新建文件,但每个文件的属组自动跟目录保持一致,而不是跟创建者本人的主组走。这靠普通权限做不到,得靠sgid。
而sticky解决的是另一个维度的尴尬:像/tmp这种所有人都有写权限的目录,如果没有额外约束,任何人都能删掉别人创建的文件——因为它只看目录的写权限,不看文件的属主。sticky位就是加在这类共享目录上的"只能删自己东西"的护栏。
这三个特殊位本质上不是"更高级的读写权限",而是给权限模型打了三块补丁,用来处理身份借用、属组继承、删除约束这三类常规 rwx 表达不了的场景。
1.2 三个特殊位各自解决什么问题
我把它们的分工整理成一张表,先建立整体印象,后面再逐个拆开讲。
| 特殊位 | 数字值 | 作用对象 | 核心效果 | 典型场景 |
|---|---|---|---|---|
| SUID | 4 | 可执行文件 | 执行时临时获得文件属主身份 | /usr/bin/passwd、/usr/bin/sudo |
| SGID | 2 | 文件 / 目录 | 文件:临时获得属组身份;目录:新建内容继承目录属组 | 团队共享目录、协作文档夹 |
| Sticky | 1 | 目录 | 只有文件属主或 root 能删除该文件 | /tmp、/var/tmp |
理解这张表的关键,是搞清楚一个前提:这几个位在ls -l的显示位上,是"借用"了原来 x 位的位置来展示的。执行位是 x,如果同时有 suid,x 就变成 s(小写表示底下的 x 也在,即真的可执行);如果没有执行位只有 suid,那就是大写 S。很多人看到大写 S 会懵,其实它只是告诉你"这个文件设了 suid,但它本身不可执行",这种组合在普通可执行文件上很少见,一般出现在一些特殊打包或损坏的文件上。
而权限数字上,原本是三位rwx对应属主、属组、其他,加了特殊位之后就变成四位,最前面那位分别用 4、2、1 表示 suid、sgid、sticky,可以叠加,比如4755、2775、1777、6777(suid + sgid 叠加)。这个四位数的换算,是很多人面试时最容易答错的地方,后面我会专门用一节讲清楚。
2. SUID 深入拆解:让普通用户临时"借"到文件所有者的身份
2.1 SUID 的内核判定逻辑到底是怎么回事
SUID,全称 Set User ID。它的核心机制只有一句话:当一个带有 suid 位的可执行文件被运行时,进程的有效用户 ID(EUID)会被设置为该文件的属主 ID,而不是运行者的实际用户 ID(RUID)。
这里必须区分 RUID 和 EUID 这两个概念,因为很多理解偏差都出在这。RUID 是"你本人是谁",EUID 是"内核现在认为你有权干什么"。平时这两者相等,你以 alice 身份登录,运行任何自己的程序,RUID 和 EUID 都是 alice。可一旦你运行了一个属主是 root 且带 suid 的程序,进程的 EUID 就变成了 root,于是内核在检查文件访问权限时,会按 root 的权限放行——这就是passwd能改写/etc/shadow的根本原因。
我打个生活化的比方:RUID 就像你的身份证,EUID 就像你进某个大楼时临时拿到的门禁卡。身份证没变,但门禁卡决定了你能刷开哪些门。suid 的作用,就是让某个程序在运行时自动给你发一张"属主权限"的临时门禁卡,程序结束,卡就作废。
注意:内核查权限看的是 EUID,不是 RUID。理解这一点,才能理解为什么 suid 能"提权",也才能理解为什么 suid 是个危险的东西。
正因为这套机制等于给程序开了一道临时的权限后门,内核对它管得非常严。普通用户对文件执行chmod u+s时,如果这个文件的属主不是他自己,设置会失败;即使是自己的文件,在很多现代系统配置下,普通用户设置的 suid 位也可能被自动清除。只有 root 才能可靠地对任意文件设置 suid。
2.2 设置与查看 SUID 的正确姿势
先说查看。看一个文件有没有 suid,最直接的办法是ls -l:
ls -l /usr/bin/passwd你会看到类似这样的输出:
-rwsr-xr-x. 1 root root 27832 Jun 10 2014 /usr/bin/passwd注意属主那一段的rws,本来应该是rwx,这里的x被写成了小写s,说明这个文件既设了 suid,又保留了执行权限。
设置 suid 有两种写法。数字法是四位权限,最高位加 4:
chmod 4755 /path/to/program符号法更直观,用u+s给属主位加 suid:
chmod u+s /path/to/program取消则对应chmod u-s或者数字位减 4(比如从4755变成0755)。
要批量找出系统里所有带 suid 的文件,可以这样:
find / -perm -4000 -type f 2>/dev/null这里的-4000表示"至少包含 suid 位"。这条命令是我做安全审计时最常用的,因为系统里每一个 suid 文件理论上都是一个潜在的权限放大器,你得清楚有哪些。
2.3 SUID 的经典应用与那些容易踩的坑
系统里最典型的 suid 程序就是passwd、sudo、su、mount、ping(部分发行版)。它们都属于"普通用户必须能做,但又必须借更高权限才能完成"的操作。以ping为例,早期它需要构造原始套接字(raw socket),这需要 root 权限,所以 ping 被设了 suid。不过后来内核引入了CAP_NET_RAW这种更细粒度的能力机制,很多发行版就改用能力位而不是 suid 了,你可以用getcap /usr/bin/ping看看是不是这样。
这里有几个实操中必须注意的点,都是我自己踩过的:
第一,suid 只对二进制可执行文件有意义,对 shell 脚本基本无效。你给一个.sh脚本设 suid,运行起来 EUID 往往不会变成脚本属主,因为内核在处理#!解释器时,suid 是针对解释器(比如/bin/bash)生效的,而现代系统出于安全考虑会忽略脚本上的 suid。所以别指望用 suid 给脚本提权,这条路早就被堵死了。
第二,suid 生效时,环境变量和信号处理都有额外限制。带 suid 的程序运行时,出于安全考虑,系统会清掉一部分危险环境变量(比如LD_PRELOAD、LD_LIBRARY_PATH),防止有人通过动态库劫持来让 suid 程序加载恶意代码。这正是为什么你写 C 程序给 suid 时会发现某些依赖库加载不进来——这是保护机制,不是你的代码写错了。
第三,千万谨慎对待自己写的 suid 程序。suid 程序一旦有逻辑漏洞(比如没有校验输入、调用了未做绝对路径限定的外部命令、继承的 shell 没清理),就可能被人利用来执行任意命令,效果等同于直接拿到属主(通常是 root)的身份。所以非必要不设 suid,能改用sudo精细授权、或者能力位(capability)的,就优先用后者。
实操心得:我现在的习惯是,任何 suid 程序上线前,都会用
find / -perm -4000定期盘点服务器上的 suid 清单,一旦发现多出来不认识的,立刻查来源。自己做开发时,默认不给自己写的程序加 suid,宁可多写两行 sudo 规则。
3. SGID 的两副面孔:文件继承与目录继承
3.1 作用在文件上:借用属组的身份
SGID,全称 Set Group ID。用在可执行文件上时,逻辑和 suid 几乎对称:程序运行时,进程的有效组 ID(EGID)被设置为文件的属组,而不是运行者的主组。也就是说 suid 借的是"属主身份",sgid 借的是"属组身份"。
举个能直接感受到区别的例子。假设有个程序属主是 root、属组是 devteam,且带了 sgid。那么 devteam 组的用户运行它时,进程会以 devteam 的属组身份运行,在访问那些"属组可写"的文件时就有权限。日常系统里,文件形态的 sgid 用得远不如 suid 多,很多人甚至以为 sgid 只是给目录用的——其实不是,只是目录场景太经典,把文件场景的光环盖过去了。
文件设 sgid 的命令是chmod g+s file或者数字位加 2:
chmod 2755 /path/to/programls -l里,它显示在属组权限段,rwx会变成rws,同样是 x 位被借用来显示这个标记。
3.2 作用在目录上:团队协作目录的必杀技
真正让 sgid 成为日常运维高频词的,是它作用在目录上的场景。这时候 sgid 的语义完全变了,它不再关乎"运行程序时借组身份",而是变成了目录内新建内容的属组继承规则。
具体来说:一个目录如果设了 sgid,那么在这个目录里新建的文件和子目录,其属组会自动设置为该目录的属组,而不是创建者当前的主组。而且新建的子目录也会自动继承 sgid 位,形成递归效果。
这个特性解决了什么痛点?设想一个项目目录/project,属组是devteam,组内成员 alice 和 bob 都在devteam里。alice 在目录里新建了一个文件,如果目录没设 sgid,这个文件的属组会是 alice 的主组(通常是 alice 自己那个同名组),属组权限对这个文件可能就没法给 devteam 用,bob 想改还得额外授权。而一旦目录设了 sgid,alice 新建的文件属组会自动变成 devteam,全组都能按组权限操作,省去大量手动chgrp的麻烦。
我做过一个协作平台的文件服务器,最初没设 sgid,结果每个人上传的文件属组五花八门,团队共用的组权限形同虚设,后来把项目根目录统一设上 sgid,问题一次性解决,新建的所有内容都规规矩矩归到同一个组下。
3.3 SGID 目录实战配置流程
光说原理不够,来一套能直接抄的配置。假设要搭一个团队共享目录/srv/teamshare,组是devteam,成员 alice、bob 都要能读写、且新建文件自动归组:
# 1. 建组(如果还没有) sudo groupadd devteam # 2. 把用户加入组,注意要重新登录才生效 sudo usermod -aG devteam alice sudo usermod -aG devteam bob # 3. 创建目录并设置归属 sudo mkdir -p /srv/teamshare sudo chown root:devteam /srv/teamshare # 4. 设置权限:属主可读写执行,属组可读写执行,其他人无权限 # 2770 中的 2 就是 sgid sudo chmod 2770 /srv/teamshare # 5. 验证 ls -ld /srv/teamshare最后一条应该看到类似drwxrws--- root devteam的输出,属组权限位的x变成了小写s,说明 sgid 生效了。此后 alice 在目录里创建文件,文件属组会自动是 devteam。
这里有个容易忽略的细节:如果目录设了 sgid 但组权限位没有w(写),那成员还是没法新建文件,因为 sgid 管的是"新建内容的属组",管不了"你能不能写"。所以实战里一般配合2775或2770使用。
注意:给目录加 sgid 后,子目录会继承 sgid,但这是一个"逐级传递"的行为,老的已经存在的子目录不会自动获得,需要你手动对新目录补上,或者用
find /srv/teamshare -type d -exec chmod g+s {} \;批量处理历史目录。
4. Sticky Bit:共享目录里"只能删自己的"
4.1 /tmp 目录为什么需要它
Sticky Bit的场景最单一,但也最容易被误解。它只对目录有实际意义(在 Linux 上),作用是:在一个设了 sticky 位的目录里,只有文件的属主、目录的属主或者 root 才能删除或重命名该文件,即使你对这个目录有写权限也不行。
这个机制存在的唯一原因,就是像/tmp这种目录的尴尬处境。/tmp是所有人共用的临时目录,权限通常是1777,也就是所有人都能读、写、进。问题来了:如果谁都有写权限,那 alice 就能删掉 bob 放在里面的文件,只要 alice 对目录有写权限——因为删除一个文件,内核检验的是"你对所在目录有没有写权限",而不是"你对这个文件有没有权限"。这是个反直觉但极其重要的知识点。
sticky位就是来堵这个漏洞的。加上它之后,即便/tmp依然777对所有人开放,删除操作也会被额外校验:你是不是这个文件的属主?不是的话,删不了。
你可以自己验证一下drwxrwxrwt:
ls -ld /tmp输出通常是drwxrwxrwt. ... /tmp,末尾的t就是 sticky 标记。注意这里同样借用了 others 的执行位来显示,t小写表示底下有执行位(也就是目录可进入),大写T表示没有。
4.2 设置、验证与那些坑
设置 sticky 的命令:
chmod +t /path/to/dir # 或者数字法,最高位加 1 chmod 1777 /path/to/dir取消就是chmod -t或把最高位减 1。
用一条实验就能直观感受它的作用。以普通用户身份,在一个设了 sticky 的共享目录里:
# 创建两个用户场景:alice 建的文件,bob 尝试删 mkdir /tmp/shared chmod 1777 /tmp/shared # alice 创建文件 touch /tmp/shared/alice_file # bob 尝试删除,会失败 rm /tmp/shared/alice_file # 输出:rm: cannot remove '/tmp/shared/alice_file': Operation not permitted如果这时你chmod -t /tmp/shared把 sticky 去掉,bob 再删,就能成功了。这个对比实验能帮你在脑子里把 sticky 的作用钉死。
几个容易踩的坑:
第一,别把 sticky 用在普通文件上。在早期的 Unix 里,给可执行文件加 sticky 意味着"执行后把代码留在内存/交换区",但那套语义在现代 Linux 上基本废弃了。现在你把 sticky 加到一个普通文件上,内核大概率直接忽略,不会报错但也没效果。所以记牢:Linux 下 sticky 就是给目录用的。
第二,sticky 不能阻止别人读取或修改文件内容,只约束删除和重命名。如果 bob 对 alice 的文件有写权限,他照样能往里写内容,只是不能把整个文件删掉或改名。这个边界要分清楚,别指望 sticky 能当文件级访问控制用。
第三,sticky 和 sgid 可以叠加。团队共享目录常见的组合是3775(sgid + sticky),既保证新建内容归组,又保证大家不能乱删别人的东西。
5. 数字权限换算与实操全流程
5.1 四位数权限的换算规则
这是面试里高频、实操中又绕不开的基础,我把它彻底讲清。常规三位数权限,从左到右是属主、属组、其他,每一位是 r(4)+w(2)+x(1) 的组合。加上特殊位后,最前面多一位,这一位是三个值的叠加:
| 叠加值 | 含义 |
|---|---|
| 0 | 无特殊位 |
| 1 | sticky |
| 2 | sgid |
| 3 | sgid + sticky |
| 4 | suid |
| 5 | suid + sticky |
| 6 | suid + sgid |
| 7 | suid + sgid + sticky |
比如4755表示 suid + 属主 rwx + 属组 r-x + 其他 r-x;2775表示 sgid + 全组可读写执行;1777表示 sticky + 全开放且都可执行(目录可进入);3775表示 sgid + sticky + 属主全权 + 属组读写执行 + 其他只读执行。
换算时记住一句话:第四位是按照 4、2、1 三个权值直接相加,和 rwx 的 4、2、1 是同一套十进制加法逻辑,只是对象换成了特殊位。
5.2 一套完整的团队协作目录搭建实操
接下来我把 sgid、sticky、以及常规权限揉到一起,给你一套可以直接落地的方案。目标:/srv/project作为项目共享目录,组devteam成员都能读写,新建内容自动归组,且彼此不能删对方文件。
# 1. 准备组和用户 sudo groupadd devteam sudo usermod -aG devteam alice sudo usermod -aG devteam bob # 2. 建目录并设归属 sudo mkdir -p /srv/project sudo chown root:devteam /srv/project # 3. 设置 3775:sgid(2) + sticky(1) + 属主 rwx + 属组 rwx + 其他 r-x sudo chmod 3775 /srv/project # 4. 验证结果 ls -ld /srv/project期望输出类似:
drwxrwsr-t 2 root devteam 4096 ... /srv/project解释一下这个输出:rwx是属主;rws里的s是 sgid;r-x对其他人;末尾的t是 sticky。注意 sticky 和 sgid 同时出现在同一个权限串里,这是完全合法的。
然后做功能验证:让 alice 进目录建个文件,看属组是不是 devteam;再让 bob 试着删 alice 的文件,看是否被拦。这套流程我在好几台服务器上部署过,稳定可靠。
5.3 符号法、数字法、参考法三种改权限方式对照
改权限不只有数字和符号两条路,还有--reference这个容易被忽略的用法。整理成表方便查阅:
| 方式 | 示例 | 适用场景 | 注意点 |
|---|---|---|---|
| 数字法 | chmod 3775 dir | 快速精确设置 | 必须写全四位才不丢特殊位 |
| 符号法 | chmod u+s,g+s,o+t dir | 只调某个特殊位 | 直观,适合追加 |
| 参考法 | chmod --reference=模板 目标 | 批量对齐权限 | 会复制模板的权限,特殊位也可能带上 |
这里有个特别阴的坑:用数字法时,如果你只写三位,比如chmod 775 dir,那么原有的 suid/sgid/sticky 会被直接抹掉。很多人图省事,本来目录是2775,他随手一个chmod 775一敲,sgid 就没了,然后团队目录的属组继承突然失效,排查半天才发现是自己手抖。所以改权限时,尽量养成先ls -ld看当前权限、确认带不带特殊位、再动手的习惯。或者干脆用符号法追加,避免误伤其他位。
6. 常见问题与排查技巧实录
6.1 高频问题速查表
这块我把自己和身边同事真实遇到过的问题整理成表,方便你对照排查。
| 现象 | 可能原因 | 排查方式 |
|---|---|---|
| suid 设置后不生效 | 普通用户无权设置,或系统自动清除 | ls -l看是否有 s;确认用了 sudo |
| suid 脚本不借身份 | 内核忽略脚本 suid | 改用二进制程序或 sudo |
| sgid 目录新建文件属组不对 | 目录没设 sgid,或子目录未继承 | ls -ld查目录权限,检查子目录 |
| sticky 目录里还是能删别人文件 | sticky 未生效或文件属主相同 | ls -ld看 t;确认运行者身份 |
chmod 775后特殊位消失 | 三位数字覆盖了四位权限 | 重新按四位设置,改前先看当前权限 |
| 组权限没生效 | 用户未重新登录,组身份未刷新 | id确认当前组成员身份 |
6.2 排查思路:先看身份,再看权限,最后看继承
权限问题排查,我总结出一个固定顺序,屡试不爽。第一步,先确认"你现在是谁":用id命令看当前的 UID、GID 和所属的所有组。很多"没权限"的问题,根源是用户虽然被usermod -aG加进了组,但当前会话还没重新登录,组身份没刷新,id里看不到目标组。
第二步,确认目标文件或目录的权限和归属:ls -l看文件,ls -ld看目录,重点看属主、属组、以及那三个特殊位显示成什么样了(s、S、t、T有没有)。
第三步,顺着目录路径逐级检查继承关系。如果涉及 sgid,要检查从根目录到目标的每一级目录是不是都设了 sgid,中间断了一环,继承就会失效。这一步我用得最多,因为目录层级的继承问题往往出在某个被遗忘的中间目录上。
第四步,才去看更底层的东西,比如挂载选项。有些文件系统或挂载参数会禁用特殊位(比如某些nosuid挂载),导致你在挂载点内怎么设 suid 都不生效。用mount | grep 目标看一眼有没有nosuid、noexec之类的限制。
6.3 那些文档里不会写的避坑经验
最后分享几条我自己攒下来的经验,都是吃过亏才记住的。
关于 suid,我的态度是"能不用就不用"。现在很多发行版默认开启了nosuid挂载选项在某些用户可写的分区(比如/home、/tmp)上,你就算在那里设了 suid 也不生效,这是系统故意设计的防线,防止用户在自己能写的地方放恶意 suid 程序然后触发。所以别在/tmp里测试 suid,会得到误导性的结论。
关于 sgid,切记"目录必须有组可执行位(x)才能进入",很多新手设了2770但忘了组有没有 x,结果组员连目录都进不去,以为 sgid 坏了。目录的 x 位对目录来说含义是"能否进入/遍历",这个和文件的 x 位含义不同,务必分清。
关于 sticky,最典型的问题是在/tmp里做实验时,用同一个用户建文件和删文件,发现怎么都删得掉,就误以为 sticky 没用——其实同属主本来就能删自己的,你得用两个不同用户交叉验证才有意义。
还有一个跨平台提醒:如果你同时接触 macOS、BSD,它们对 sticky 的历史语义和 Linux 有细微差别,尤其在可执行文件上的表现不一样。做跨平台运维时,别拿 Linux 的行为去套其他系统,遇到权限诡异的情况,先查一下特定系统对该位的定义。
我个人在实际操作中的体会是,这三个特殊位真正的价值不在于它们多高深,而在于它们填补了基础权限模型那几块绕不开的空白。把 suid 理解成"身份借用",sgid 理解成"属组继承"(目录场景),sticky 理解成"删除约束",你脑子里就有一套稳定的心智模型,遇到任何权限现象都能往这三个方向去对号入座。剩下的就是多动手、多验证,尤其建议自己开个实验环境,把4755、2775、1777、3775这几个组合挨个设一遍,用不同用户交叉测一测,那种"亲眼看到行为差异"的记忆,比看十遍文档都牢。