一个目录权限乱了——可能是遗留脚本用 root 建的文件,也可能是 SCP 传过来的文件属主对不上——网站访问直接 403,部署脚本报 Permission denied。这时候大部分人的第一反应是抄一条 chmod -R 777,网站是能访问了,但等于把整个目录对所有人开放读写执行,治标不治本,出了安全事故说不清楚。

chmod 和 chown 是治本的工具,但数字模式怎么算、setuid/setgid/sticky bit 到底在干嘛,很多人一直没搞清楚过。这篇把这两个命令的实际用法过一遍,最后用一个真实场景收尾。

这是 Linux 常见命令系列的第一篇详细教程,承接 Linux 常见命令与工具地图

权限模型回顾:读懂 ls -l 那串字符

ls -l 输出的第一列长这样:-rwxr-xr--。十个字符拆成四段:

  • 第 1 位:文件类型(- 普通文件,d 目录,l 符号链接)
  • 第 2-4 位:owner(文件所有者)的读写执行权限
  • 第 5-7 位:group(所属组)的读写执行权限
  • 第 8-10 位:other(其他所有人)的读写执行权限

每一组里 r/w/x 对应的数字是固定的:r=4w=2x=1,一组权限的数字就是这三个数加起来。rwxr-xr-- 换算成数字就是 754——owner 是 4+2+1=7,group 是 4+0+1=5,other 是 4+0+0=4。下一节的数字模式全靠这套加法。

chmod 数字模式:三位数背后的加法

数字模式就是把 owner、group、other 三组权限各自换算成 0-7 的数字,拼在一起传给 chmod:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
chmod 644 config.yaml
# 6=4+2:owner 读写,不能执行
# 4:group 只读
# 4:other 只读
# 配置文件的标准权限——自己能改,别人只能看

chmod 755 deploy.sh
# 7=4+2+1:owner 读写执行
# 5=4+1:group、other 能读能执行,不能改
# 可执行脚本的标准权限

chmod 600 id_rsa
# 6:owner 读写
# 0、0:group、other 什么都不能干
# 私钥文件必须这么设,权限太开 SSH 会直接拒绝使用这把钥匙

记这三组数字对(644 配置、755 可执行、600 私钥)就能覆盖日常八成场景,其余情况现算:想清楚 owner/group/other 各自要不要读、写、执行,三个数字加出来拼在一起就是。

chmod 符号模式:什么时候比数字模式好用

数字模式的前提是你想清楚了三组权限的最终状态。但很多时候你只想”在现有权限基础上加一位”,这时候符号模式更顺手:

1
2
3
4
5
6
7
8
9
10
11
12
13
chmod u+x run.sh
# u:user(owner的简写),+x:加上执行权限,不动其他位

chmod g-w shared/
# g:group,-w:去掉写权限,owner 和 other 的权限不受影响

chmod o=r public.conf
# o:other,=r:直接把 other 的权限设成只读(=是赋值,不是叠加)

chmod a+X find_scripts/*
# a:all(u+g+o 的简写),+X:对目录总是加执行位(方便进入),
# 对普通文件只有已经对某个身份(owner/group/other 任一)开了执行权限才加,
# 不会把本来不可执行的普通文件也变成可执行文件——这正是它和小写 x 的区别

u/g/o/a 决定改谁,+/-/= 决定加、减还是直接赋值,r/w/x/X 决定改哪个权限位。这四个维度组合起来,比每次都要心算数字模式更不容易出错。

特殊权限位:setuid、setgid、sticky bit 到底在干嘛

回顾第一节的权限模型:rwx 三组权限,每一位要么是字母要么是 -。但用 ls -l 查看某些文件或目录时,你会碰到不在这个范围内的符号:

1
2
3
-rwsr-xr-x  1 root root  /usr/bin/passwd
drwxrwsr-x 2 root dev /srv/shared-project
drwxrwxrwt 10 root root /tmp

第一行 owner 的 x 位置变成了 s,第二行 group 的 x 位置也是 s,第三行 other 的 x 位置变成了 t。这三个符号不属于 rwx 常规模型,就是这一节要讲的特殊权限位:setuid、setgid、sticky bit,分别顶替 owner / group / other 三组里的 x 位置。它们不是额外发明出来的权限,而是在执行权限基础上叠加的特殊标记,各自解决一个具体问题,一个一个来看。

setuid:让别人执行你的程序时,暂时”变成你”

普通用户能改自己的密码,但密码是存在 /etc/shadow 里的,这个文件只有 root 能写。矛盾就在这——普通用户没有权限写 /etc/shadow,却要靠 passwd 这个程序去改它。

setuid 就是解决这个矛盾的:给 passwd 这个可执行文件加上 setuid,效果是不管谁来执行它,执行的过程中都”变成”这个文件的所有者(也就是 root)。所以普通用户执行 passwd,程序运行期间临时有了 root 的权限,改完密码,权限又还回去。

1
2
chmod u+s /usr/bin/passwd
# setuid 只对可执行文件有意义,加在目录上没有效果,会被系统忽略

只对可执行文件生效,这个属性本身也有风险——如果一个装了 setuid 的程序本身有漏洞,攻击者可能借着它拿到 root 权限,所以只在真正需要的地方加,不要乱设。

setgid:让共享文件夹里新建的文件自动归到同一个组

团队共享一个目录时经常遇到这种烦心事:A 建的文件属组是 A 的主组,B 建的文件属组是 B 的主组,两人互相没权限改对方的文件,得手动 chgrp 一遍遍改。

setgid 加在目录上就能解决:这个目录下新建的任何文件和子目录,属组都自动跟目录本身一致,不看是谁建的。

1
2
3
chmod g+s /srv/shared-project
# 之后组内任何人在这个目录下新建文件,属组都自动是 shared-project 的组
# 不用每次手动 chgrp,协作目录必备

(setgid 加在可执行文件上也有效果,跟 setuid 类似——变成文件所属组的身份,但这个用法比加在目录上少见得多,日常基本用不到。)

sticky bit:谁建的文件谁能删,别人删不了

想象小区里的公共快递柜:谁都能往里放包裹,但只有放的人自己能取走,别人拿不走你的包裹。/tmp 目录就是这个逻辑——所有人都能在里面写文件(不然没法用),但如果没有额外限制,任何人也能删掉别人的临时文件,那就乱套了。

sticky bit 加在目录上,效果是:即使某人对这个目录有写权限,也只能删自己建的文件,删不了别人的。

1
2
chmod +t /tmp
# /tmp 默认就是这个设置,所有人能写,但谁也删不了别人的文件

三个位对应的数字,以及 ls -l 怎么看

三个特殊位在数字模式里对应第四位(写在平时那三位数字前面),可以叠加:

权限位 数字 加在哪 ls -l 里顶替谁的 x 一句话
setuid 4 可执行文件 owner 组的 x(第 3 位) 别人执行时临时”变成”文件所有者
setgid 2 目录 group 组的 x(第 6 位) 目录下新建的文件自动跟目录同一个组
sticky bit 1 目录 other 组的 x(第 9 位) 谁建的文件谁能删,别人删不了

三个位置各管各的,不会挤在一起:setuid 是 owner 的事,setgid 是 group 的事,sticky 是 other 的事,加在哪组权限上就顶替哪组的 x。

1
2
3
chmod 4755 /usr/bin/passwd     # setuid(4) + 标准的 755
chmod 2775 /srv/shared-project # setgid(2) + 目录标准的 775
chmod 1777 /tmp # sticky(1) + 完全开放

开头那三行 ls -l 输出里的 s/t 都是小写,这是正常情况——owner(或 other)本身也有执行权限。看大小写的规则就一条:**小写表示正常,大写表示”这个位置本该有执行权限,但没有”**。比如 rwSr--r--,大写 S 说明 setuid 生效了,但 owner 没有执行权限,这种设置通常没意义,多半是配错了。看到大写的 S 或 T,基本可以判断是漏加了执行位,回去检查一下。

chmod -R 递归的坑:符号链接会不会被改

chmod -R 递归修改目录下所有内容时,符号链接是这里最容易被误解的地方。符号链接本身的权限在 Linux 上不生效(永远显示 lrwxrwxrwx,改它没有意义)。默认行为其实比较克制:只有当符号链接直接出现在命令行参数里、并且指向一个目录时才会跟随继续递归;递归过程中在目录内部碰到的符号链接不会被跟随,所以正常情况下一条 chmod -R 755 . 不会意外爬到符号链接指向的别处。真正危险的是显式加了 -L——这个选项让 chmod 遇到任何目录型符号链接都跟随,如果目录里的链接指向了 / 或者其他敏感路径,递归范围会远超预期。

三个选项控制这个行为,只有最后一个生效(重复指定以最后一个为准):

1
2
3
4
chmod -R -H 755 project/   # 默认行为:命令行里直接给出的符号链接,如果指向目录会跟随;
# 但递归过程中在目录内部新发现的符号链接不跟随
chmod -R -L 755 project/ # 遇到目录型符号链接就跟随(最危险,容易改到意料之外的地方)
chmod -R -P 755 project/ # 完全不跟随任何符号链接,比默认的 -H 更保守

不确定目录里有没有埋着符号链接时,先跑一遍 find project/ -type l 心里有数,比事后排查权限为什么跑到别处去了省事得多。

chmod 常用参数速查表

前面几节讲的都是这张表里的内容,汇总在一起方便直接查:

参数 作用
-R, --recursive 递归处理目录下所有文件和子目录
-v, --verbose 每处理一个文件都输出结果
-c, --changes -v 类似,但只在权限真的发生变化时才输出
-f, --silent, --quiet 屏蔽大部分错误提示
--reference=文件 权限抄自参考文件,不用自己写数字或符号模式
-H 递归时只跟随命令行直接给出的符号链接(默认行为)
-L 递归时跟随所有遇到的目录型符号链接(最危险)
-P 递归时完全不跟随任何符号链接
u / g / o / a 符号模式里指定改谁:owner / group / other / all
+ / - / = 符号模式里指定加上、去掉还是直接赋值
r / w / x / X 读 / 写 / 执行 / 条件执行(目录总加,文件按原有执行权限判断)
数字模式前导 4/2/1 特殊权限位:setuid / setgid / sticky bit,可叠加写在三位数字模式前面

chown:属主属组一起改,还是分开改

chown 修改文件的所有者和所属组,语法是 chown 属主:属组 文件

1
2
3
4
5
6
7
8
chown deploy:www-data app.log
# 属主改成 deploy,属组改成 www-data,一步到位

chown deploy app.log
# 只写属主,属组不变

chown :www-data app.log
# 只写属组(前面留空),等价于 chgrp www-data app.log

chgrpchown :group 做的是同一件事——chgrp 只是历史遗留的专用命令,只能改组,功能被 chown:group 语法完全覆盖。新写脚本用 chown :group 更统一,不用记两个命令。

用户名在 /etc/passwd 里查不到(比如跨系统同步过来的文件,UID 在本机没有对应用户名)时,直接传数字 UID:GID 也行:

1
2
chown 1001:1001 orphaned_file
# UID 和 GID 直接是数字,不依赖 /etc/passwd 里有没有这个用户名

-R 递归的语义比 chmod 简单得多——符号链接本身没有属主属组的概念(继承自它指向的目标),chown 的递归不存在上面 chmod 那种”改错地方”的风险。

还有两个不算高频、但排查时很有用的参数:--from=旧属主:旧属组 只在文件当前属主属组和指定值一致时才真正执行改动,批量操作时能防止误伤不该改的文件;-h/--no-dereference 改的是符号链接本身而不是它指向的目标——默认情况下 chown 是解引用符号链接、改目标,这一点和很多人的直觉相反,需要改链接本身时必须显式加 -h

chown 常用参数速查表

参数 作用
-R, --recursive 递归处理目录下所有文件和子目录
-v, --verbose 每处理一个文件都输出结果
-f, --silent, --quiet 屏蔽大部分错误提示
-h, --no-dereference 改符号链接本身,不改它指向的目标(默认是改目标)
--reference=文件 属主属组抄自参考文件
--from=旧属主:旧属组 只有当前属主/属组和这里指定的一致才真正执行改动,防误伤
user:group 同时指定属主和属组
:group 只改属组(前面留空),等价于 chgrp

实战:修一个权限乱掉的部署目录

场景:Nginx 的静态资源目录 /var/www/site,本来该属于 www-data 用户和组,结果某次运维手滑用 sudo 直接解压部署包,整个目录变成了 root:root,Nginx 进程读不了文件,网站全站 403。

第一步,改回属主属组:

1
2
sudo chown -R www-data:www-data /var/www/site
# -R:递归改整个目录树,属主属组都改成 www-data

第二步,权限也得配套——目录需要可进入(执行位),文件不需要:

1
2
3
4
5
sudo find /var/www/site -type d -exec chmod 755 {} \;
# -type d:只挑目录,755 让 Nginx 能进入并列出目录内容

sudo find /var/www/site -type f -exec chmod 644 {} \;
# -type f:只挑文件,644 让 Nginx 能读文件内容,但不给执行权限

这里不能图省事直接 chmod -R 755 /var/www/site——那样会把所有静态文件也变成可执行,权限比实际需要的更开放。目录和文件分开处理才是准确的做法,find + chmod 这个组合在权限修复场景里比手写循环更简洁可靠。

权限之外

sudo 决定”谁能用 root 身份执行命令”,和 chmod/chown 决定”文件本身谁能读写执行”是两个独立的问题,值得单开一篇讲。Linux 命令系列下一篇计划写 rsync——它的参数比 chmod/chown 多得多,增量同步、排除规则、远程传输这些够写满一整篇。