← cd ../posts

权限三段式:rwx / chmod / chown / suid 终极指南

2026-06-24

这是 Linux 系列的第 11 篇,进入"文件与权限"章。前面讲了"怎么找文件、怎么处理文本"——这一篇讲"为什么有时你看得见、读不了、改不了"。

0. 一行 ls -l 输出

-rw-r--r-- 1 alice  staff  4521 Apr 22 10:15 README.md

每一段都有含义:

-rw-r--r--   1   alice   staff    4521 Apr 22 10:15 README.md
│└──┬──┘     │    │       │        │
│   │        │    │       │        └─ 大小
│   │        │    │       └─ 属组
│   │        │    └─ 属主
│   │        └─ 硬链接数
│   └─ 权限(9 个 bit)
└─ 文件类型

这一篇主要拆解第二段——9 个 bit 的权限


1. 三段式:u / g / o,每段 r / w / x

Linux 权限的设计极简到惊人:

   ┌─ owner (u) ─┐ ┌─ group (g) ──┐ ┌─ others (o) ─┐
   │             │ │              │ │              │
-  rwx           rwx              rwx
│  │││           │││              │││
│  │││           │││              ││└─ 其他人 execute
│  │││           │││              │└── 其他人 write
│  │││           │││              └─── 其他人 read
│  │││           ││└────────────────── 同组 execute
│  │││           │└─────────────────── 同组 write
│  │││           └──────────────────── 同组 read
│  ││└──────────────────────────────── 属主 execute
│  │└─────────────────────────────── 属主 write
│  └──────────────────────────────── 属主 read
└─────────────────────────────────── 文件类型

3 段 × 3 权限 = 9 个 bit。每个 bit 独立:1 就有,0 就无。

权限 文件 目录
r read 能 cat / less 能 ls 列出内容
w write 能改写内容 能在里面新建/删除文件
x execute 能跑(脚本 / 二进制) cd 进去 / 访问内部文件

目录的 rx 经常混淆——记住:

  • 有 r 没 x:能 ls 看到名字,但访问不了内部任何文件
  • 没 r 有 x:能 cd 进去、能访问已知名字的文件,但 ls 列不出
  • 同时有 r + x:正常使用

2. 数字权限:八进制

每段 3 个 bit → 一个 0-7 的数字。所以 rwxr-xr-x = 755

r w x      r - x      r - x
4 2 1      4 . 1      4 . 1
─────      ─────      ─────
  7          5          5

对照表:

数字 二进制 rwx 含义
0 000 ---
1 001 --x 只能执行
2 010 -w- 只能写
3 011 -wx 写+执行(罕见)
4 100 r-- 只读
5 101 r-x 读+执行
6 110 rw- 读+写
7 111 rwx 全部

记 5 个最常见的:

chmod 含义
755 自己改,所有人能读+跑——可执行文件 / 目录的常见值
644 自己改,所有人能读——普通文件常见
600 只有自己读+写——SSH 私钥、密码文件
700 只有自己使用——~/.ssh/ 目录
777 所有人全权(几乎一定是错的——别用)

3. chmod:改权限

数字方式

$ chmod 755 script.sh         # 给所有人加可执行
$ chmod 644 README.md         # 改回普通文件
$ chmod 600 ~/.ssh/id_rsa     # SSH 私钥必须 600,不然 ssh 拒绝用

符号方式(更精确)

$ chmod u+x script.sh         # 给属主加 execute
$ chmod g-w file              # 移除组的 write
$ chmod o=r file              # 让 others 只剩 read
$ chmod a+x script.sh         # all = u+g+o,全部加 x
$ chmod u+x,g-w file          # 一次多条规则

# 递归(小心)
$ chmod -R 755 /var/www/html

# 只改文件不动目录(或反过来)
$ find . -type f -exec chmod 644 {} +
$ find . -type d -exec chmod 755 {} +

chmod -R 777 是新手最毁系统的操作——所有 SSH key 被它一搞 ssh 立刻拒绝登录,所有 systemd 服务起不来,恢复成本巨大。永远不要 chmod -R 777


4. chown / chgrp:改属主 / 属组

# 改属主
$ sudo chown alice file
$ sudo chown alice:devs file       # 同时改属主 + 属组
$ sudo chown :devs file            # 只改属组(前面留空)

# 递归
$ sudo chown -R www-data:www-data /var/www/html

# 复制别人的属性(参考 file,把 file2 调成一样)
$ sudo chown --reference=file file2

只有 root 能 chown——普通用户不能把文件"送"给别人(防止恶意"嫁祸")。


5. suid / sgid / sticky:被忽视但关键的三个 bit

除了 9 个基本 bit,还有 3 个特殊 bit,藏在 chmod 4755 那个 4 里。

suid(4000):跑这个程序时身份切到属主

最经典的例子 passwd

$ ls -l /usr/bin/passwd
-rwsr-xr-x 1 root root 68208 ... /usr/bin/passwd
   ↑
   s 而不是 x —— suid 已设

你(普通用户)跑 passwd 时,进程的 effective uid 变成 root——这样它能写 /etc/shadow(密码文件)。出了 passwd 这个进程,你还是你。

suid 让"普通用户做需要 root 权限的事"成为可能。

# 设 suid
$ sudo chmod u+s program          # 或 chmod 4755 program

suid 是巨大的安全敏感面——suid 程序里任何 bug 都能被普通用户提权成 root。自己写的脚本不要随便 suid

趣事:很多发行版(Ubuntu 等)禁用 shell 脚本的 suid——因为太容易被攻击。即使你 chmod u+s script.sh 也不会生效。

sgid(2000):

  • 用在目录上:在目录里新建的文件自动继承目录的属组。多人协作目录的标配:
$ sudo mkdir /srv/shared
$ sudo chown :devs /srv/shared
$ sudo chmod 2775 /srv/shared      # 2 = sgid
# 之后 alice、bob 在 /srv/shared 里建的文件都自动属于 devs 组
  • 用在文件上:跑这个程序时 effective gid 切到文件属组(少见)。

sticky(1000):

只对目录生效——"只有文件属主能删自己的文件"。

最经典的 /tmp

$ ls -ld /tmp
drwxrwxrwt 18 root root 4096 ... /tmp
         ↑
         t 而不是 x —— sticky 已设

/tmp 所有人都能 rwx 写文件——但因为有 sticky,你的文件别人删不了(即使他对 /tmp 有 w 权限)。

$ sudo chmod +t /some/shared/dir

6. umask:新文件的默认权限

新建文件时,权限不是凭空决定的——是 666 - umask(目录是 777 - umask)。

$ umask
0022           # 默认值(首位是给特殊 bit 的)

$ touch newfile
$ ls -l newfile
-rw-r--r--    # = 666 - 022 = 644

$ umask 077    # 严格点,只有自己能读写
$ touch private
$ ls -l private
-rw-------    # = 666 - 077 = 600

写在 ~/.bashrc 里就是永久的。家里没人用同一台机器的话 077 是最安全的默认值。


7. ACL:当三段式不够用

三段式(u/g/o)有时不够灵活——比如"我希望 alice 能读,bob 能写,charlie 啥也不能"。这种用 ACL(Access Control List):

# 看文件 ACL(如果有)
$ getfacl file.txt
# file: file.txt
# owner: me
# group: staff
user::rw-
user:alice:r--          # alice 单独有读权
user:bob:rw-            # bob 读写
group::r--
mask::rw-
other::---

# 加 ACL
$ setfacl -m u:alice:r file.txt          # 给 alice 加读权
$ setfacl -m u:bob:rw file.txt           # 给 bob 加读写
$ setfacl -x u:alice file.txt            # 移除 alice 的特权
$ setfacl -b file.txt                    # 清空 ACL

ls -l 时有 ACL 的文件末尾会带 +

-rw-r--r--+ 1 me staff 0 ... file.txt
         ↑

ACL 是 ext4 / xfs / btrfs 等都支持的标准功能。但优先用三段式 + 合理设组,ACL 是兜底——它跟备份 / rsync 配合时容易丢失。


8. 几个"为什么明明 sudo 还不让我写"

case 1:immutable 属性

$ sudo touch /tmp/test
$ sudo chattr +i /tmp/test         # +i 加 immutable
$ sudo rm /tmp/test
rm: cannot remove '/tmp/test': Operation not permitted

$ sudo chattr -i /tmp/test         # 取消
$ sudo rm /tmp/test                # OK 了

chattr 是 ext 文件系统的扩展属性,凌驾于 rwx 之上。+i 让文件谁也改不了,+a 让文件只能追加(适合日志)。

排查"为什么 root 都改不了" → lsattr file 看是不是被 chattr 了。

case 2:文件系统挂成只读

$ mount | grep '/data'
/dev/sda1 on /data type ext4 (ro,relatime)
                              ↑↑
                              只读

整个挂载点 ro,再 chmod 也没用。

case 3:SELinux / AppArmor

某些发行版(RHEL/Fedora)开了 SELinux——它在 rwx 之上加了一层"什么进程能访问什么类型"的策略。报错通常是 "Permission denied" 但 ls -l 看权限明明对。

$ getenforce         # 看 SELinux 状态
$ ausearch -m avc -ts recent      # 查最近的拒绝日志

Ubuntu/Debian 的 AppArmor 类似。

case 4:容器里的 root 不是真 root

$ docker run -it alpine sh
# whoami
root
# touch /etc/foo
touch: /etc/foo: Permission denied  # ??? 我是 root 啊

如果容器 namespace 配了 user remapping、或者 /etc 是从宿主机 ro 挂载的、或者 capability 被 drop,root 也写不动。


9. 实战:一份"刚装好的 Web 服务"权限模板

# /var/www/myapp 目录给 nginx 用,但 deploy 时由 deploy 用户改
$ sudo chown -R deploy:www-data /var/www/myapp
$ sudo chmod -R 750 /var/www/myapp
$ sudo find /var/www/myapp -type d -exec chmod g+s {} +    # sgid 让新文件继承 group

# 上传目录要 nginx 能写
$ sudo chmod -R g+w /var/www/myapp/uploads

# 私钥严格 600
$ chmod 600 /home/deploy/.ssh/id_rsa
$ chmod 700 /home/deploy/.ssh

记忆口诀:

  • 目录默认 755,需要分享写的加 g+s + g+w
  • 文件默认 644,要执行的 755
  • 凭据文件 600(SSH key、密码、token)
  • 不确定时 chmod 750 / 640——拒绝陌生人,组内有限度

10. 现在做一件事

# 1. 看你 home 下隐私文件的权限
$ ls -la ~/.ssh ~/.aws ~/.config 2>/dev/null

# 2. 找权限"太松"的私钥
$ find ~/.ssh -type f -name 'id_*' ! -name '*.pub' -perm /077

# 3. 看系统里所有 suid 程序(理解为什么它们要 suid)
$ find / -perm /4000 -type f 2>/dev/null | head -20

# 4. 看你 umask 现在是几
$ umask

如果第 2 步出现了文件——立刻 chmod 600 它,ssh 才不会拒绝用。


下一篇links-inodes——硬链接、符号链接、inode 到底是什么;为什么 rm 大文件后磁盘没释放;ext4 的 inode 用尽了空间还在却写不了文件。