Skip to content

第 16 章 权限模型详解

Part 4 · 系统管理


引子

上一章我们搞定了用户。Linux 现在认识你了——它知道你的 UID 是 1000,你的主组是 charlie,你有 sudo 权限。

但有了身份只是第一步。

接下来 Linux 要做第二件事:检查你「有没有资格」对某个文件做某件事。你有一张门禁卡,但不是每扇门都认你的卡。读、写、执行,三个维度。文件属主、同组用户、其他人,三个级别。rwx rwx rwx,九个字符。

简洁是优点,但也是门槛。你可能已经见过 chmod 755 这种写法——为什么是 755?755rwxr-xr-x 是什么关系?umask 022 在背后悄悄做了什么?还有 SUID、SGID、Sticky Bit——这三个听起来像黑魔法的东西,为什么能让普通用户改自己的密码?

别急。我们把 rwx 的每一位都拆开来看。


背景与动机

如果你是从 Windows 过来的,权限对你来说可能只是一个偶尔弹出的「管理员身份运行」对话框。Windows 的权限体系(ACL)其实比 Linux 的 rwx 模型更细粒度——但正因为太细,大多数人在大多数时候根本不管它。

Linux 走了相反的路:用最少的维度(r、w、x)和最少的级别(属主、组、其他人)组合出一套权限模型。这套模型诞生于 1970 年代的 Unix,快六十年了,至今几乎没变过。不是因为懒,是因为它刚好够用——覆盖了绝大多数场景,又简单到你在脑子里就能心算。

在嵌入式开发中,权限问题几乎是排错的第一站:编译输出的二进制文件没有执行权限(Permission denied)、共享目录的组权限没设对导致同事无法写入、/dev/ttyUSB0 的属组不是 dialout 导致串口工具无法打开——这些都是每天都会遇到的事。


概念层

rwx——三个位,三个世界

每个文件和目录都有一组权限属性。用 ls -l 可以看到:

bash
$ ls -l
total 4
-rw-r--r-- 1 charlie charlie  231 Jan 10 10:00 hello.c
drwxr-xr-x 2 charlie charlie 4096 Jan 10 10:00 projects/
-rwxr-xr-x 1 charlie charlie 8296 Jan 10 10:00 hello

最左边那串字符 -rw-r--r-- 就是权限位。它可以拆成四个部分:

- | rw- | r-- | r--
│   │     │     └── 其他人(Other)的权限
│   │     └──────── 同组用户(Group)的权限
│   └────────────── 文件属主(User/Owner)的权限
└────────────────── 文件类型(- 普通、d 目录、l 链接)

每个位置要么是对应的字母(r/w/x),要么是 -(没有这个权限)。三种权限的含义取决于目标是文件还是目录:

权限对文件对目录
r (Read)查看文件内容列出目录下的文件名(ls
w (Write)修改文件内容在目录中创建/删除/重命名文件
x (Execute)把文件当程序运行进入目录(cd

这里有一个反直觉的地方:对目录来说,x 不是「执行」,而是「进入」。没有 x 权限的目录,你 cd 不进去;即使有 r 权限能看到里面的文件名列表,你也无法访问那些文件的详细信息(ls -l 会报一堆 Permission denied)。

你可以把权限模型理解为一把三面钥匙——每面钥匙对应一个级别(属主、组、其他人),每面钥匙上有三个齿(r、w、x)。系统检查权限时,先看你是不是属主,是的话拿属主那面钥匙开;不是的话看你是不是同组,是的话拿组那面;都不是的话拿「其他人」那面。

但「三面钥匙」这个比喻有一个地方是错的:真正的钥匙是独立的——你有属主钥匙就不需要组钥匙了。而 Linux 的权限检查是短路的——一旦确定你是属主,就只看属主权限,不会再去看组权限和其他人权限,哪怕属主权限比组权限还少。这意味着如果你是文件的属主,而属主权限是 ---,那你就是打不开这个文件——即使组权限和其他人权限都是 rwx


八进制表示——把九个字符压缩成三个数字

r、w、x 三个权限恰好对应三个二进制位。把三位二进制转成一位八进制,每个级别(属主/组/其他)就能用一个 0-7 的数字表示:

r = 4 (100)
w = 2 (010)
x = 1 (001)

组合方式就是简单相加:

权限串计算八进制
rwx4+2+17
r-x4+0+15
r--4+0+04
rw-4+2+06
---0+0+00

三个级别拼在一起,就得到了你随处可见的三位数:755 = rwxr-xr-x644 = rw-r--r--777 = rwxrwxrwx

如果你玩过单片机或者看过寄存器配置,这个设计会让你觉得格外亲切——因为它本质上就是把三个 3-bit 的寄存器拼在了一起。Unix 的设计者 Dennis Ritchie 和 Ken Thompson 都是搞操作系统和硬件出身,rwx 的位运算设计是那个年代的标准操作。


umask——新文件默认权限的「减法器」

当你用 touch 创建文件、用 mkdir 创建目录时,它们的默认权限不是随机的——是有一个基准值,减去一个掩码得到的。这个掩码就是 umask

在 Ubuntu 上,运行 umask 看看你的默认值:

bash
$ umask
0002

如果你在别的教程里看到「默认 umask 是 022」,那不是针对你的——那是 root 用户的 umask。普通用户的 umask 是 002。原因藏在 /etc/login.defs 里:

UMASK		022
USERGROUPS_ENAB	yes

系统全局配置确实是 022,但 USERGROUPS_ENAB yes 这一行做了微妙的事情:当你登录时,如果 UID 等于 GID(也就是你使用的是「用户私有组」,Ubuntu 默认就是这样),pam_umask 会自动把属主权限位复制到组权限位——022 变成了 002。这样同组用户(其实也就是你自己)拥有和属主相同的权限。

计算规则:

  • 文件:基准权限 666(rw-rw-rw-),减去 umask 002,得到 664(rw-rw-r--)
  • 目录:基准权限 777(rwxrwxrwx),减去 umask 002,得到 775(rwxrwxr-x)

注意这里的「减法」不是算术减法,而是按位取反再按位与0666 & ~0002 = 0664。这也解释了为什么文件的基准是 666 而不是 777——普通文件默认不应该有执行权限,这是一个安全设计:你不会希望随便 touch 出来的文件都能直接运行。

验证一下:

bash
$ touch newfile
$ ls -l newfile
-rw-rw-r-- 1 charlie charlie 0 Jan 10 10:00 newfile

$ mkdir newdir
$ ls -ld newdir
drwxrwxr-x 2 charlie charlie 4096 Jan 10 10:00 newdir

文件 664,目录 775——属主和组权限相同,其他人只有读和执行(目录)或只有读(文件)。这和计算结果完全一致。


实践层

4.1 chmod——修改权限

数字法

bash
# 创建一个脚本文件
$ echo '#!/bin/bash' > hello.sh
$ echo 'echo "Hello"' >> hello.sh

# 当前权限
$ ls -l hello.sh
-rw-r--r-- 1 charlie charlie 20 Jan 10 10:00 hello.sh

# 给所有人加上执行权限(755)
$ chmod 755 hello.sh
$ ls -l hello.sh
-rwxr-xr-x 1 charlie charlie 20 Jan 10 10:00 hello.sh

# 现在可以运行了
$ ./hello.sh
Hello

字母法

字母法不需要心算八进制。它的语法是「对谁、做什么、做什么权限」:

  • 对象u(属主)、g(组)、o(其他人)、a(所有人)
  • 操作+(加权限)、-(减权限)、=(设为精确值)
  • 权限rwx
bash
# 给属主加执行权限
$ chmod u+x hello.sh

# 同时给属主和组加执行权限
$ chmod ug+x hello.sh

# 去掉其他人的写权限
$ chmod o-w hello.sh

# 把组权限精确设为 r-x
$ chmod g=rx hello.sh

字母法的优势在于它是增量操作——你不需要知道当前权限是什么,只需要告诉它「加什么」或「减什么」。这在写脚本的时候特别好用,因为你不用先 ls -l 再算数字。

递归修改

bash
$ chmod -R 755 ~/projects/

-R 会让 chmod 递归地修改整个目录树。这在对齐项目目录权限时很常用。

⚠️ 注意chmod -R 777 是新手最常犯的错误之一。它把所有文件和目录的权限全部打开——包括你不想让别人改的配置文件和敏感数据。如果你发现自己在敲 chmod 777,先停下来想一想:你是在解决问题,还是在掩盖问题?真正的问题往往是属主或属组设错了,而不是权限不够宽。


4.2 chown 和 chgrp——修改归属

chown:修改文件的属主和属组

bash
# 创建一个测试文件
$ touch testfile
$ ls -l testfile
-rw-r--r-- 1 charlie charlie 0 Jan 10 10:00 testfile

# 把属主和属组都改成 root
$ sudo chown root:root testfile
$ ls -l testfile
-rw-r--r-- 1 root root 0 Jan 10 10:00 testfile

chown 的格式是 用户:组(中间用冒号,旧写法用点 .,两种都认)。

bash
# 只改属主
$ sudo chown root testfile

# 只改属组(冒号前面的部分留空)
$ sudo chown :root testfile

chgrp:修改文件的属组

bash
$ sudo chgrp dialout testfile
$ ls -l testfile
-rw-r--r-- 1 charlie dialout 0 Jan 10 10:00 testfile

chgrp 等价于 chown :组名,但语义更明确。

递归修改归属

bash
$ sudo chown -R charlie:charlie ~/projects/

这在从别人手里接过一个项目目录时经常用到——你需要把所有文件的属主改成自己。


4.3 SUID、SGID、Sticky Bit——权限模型的三张王牌

rwx 九个字符覆盖了 99% 的日常场景。但有一个场景它搞不定:普通用户需要改自己的密码,密码存在 /etc/shadow 里,而这个文件只有 root 能读写。怎么办?

答案是 SUID(Set User ID)

SUID——以文件属主的身份执行

bash
$ ls -l /usr/bin/passwd
-rwsr-xr-x 1 root root 68208 Jan 10 10:00 /usr/bin/passwd

注意属主权限那三位:不是 rwx,而是 rws——那个 s 就是 SUID 位。

当 SUID 被设置在一个可执行文件上时,无论谁执行这个文件,进程的有效 UID(EUID)都会变成文件的属主 UIDpasswd 的属主是 root(UID 0),所以任何用户执行 passwd 时,进程的 EUID 都是 0,拥有 root 权限,可以修改 /etc/shadow

验证一下:

bash
$ passwd
Changing password for charlie.
Current password:
New password:

普通用户改自己的密码,修改的是只有 root 能写的 /etc/shadow——这就是 SUID 在背后起作用。

设置 SUID:

bash
$ sudo chmod u+s /usr/bin/myapp
# 或者数字法:在三位权限前面加第四位 4
$ sudo chmod 4755 /usr/bin/myapp

⚠️ 注意 SUID 对 Shell 脚本无效。Linux 内核会忽略脚本文件上的 SUID 位——这是因为 Shell 脚本在执行时是由解释器(如 /bin/bash)来读取和运行的,实际的进程 UID 取决于解释器的权限,而不是脚本文件本身的 SUID 位。只有编译后的二进制可执行文件才能享受 SUID 的效果。

SGID——以文件属组的身份执行 / 新文件继承组

SGID 有两种用法:

对可执行文件:类似 SUID,但改的是有效 GID。

对目录:在这个目录下新建的文件,属组会自动继承目录的属组,而不是创建者的主组。这在共享项目中非常有用:

bash
$ sudo groupadd team
$ sudo mkdir /srv/project
$ sudo chgrp team /srv/project
$ sudo chmod g+s /srv/project    # 设置 SGID
$ ls -ld /srv/project
drwxrwsr-x 2 root team 4096 Jan 10 10:00 /srv/project

# 现在 charlie(属于 team 组)在此目录下创建文件
$ touch /srv/project/test.txt
$ ls -l /srv/project/test.txt
-rw-r--r-- 1 charlie team 0 Jan 10 10:00 /srv/project/test.txt

属组自动是 team 而不是 charlie——同组的人都能按照组的权限来访问。

Sticky Bit——防删除保护

bash
$ ls -ld /tmp
drwxrwxrwt 15 root root 4096 Jan 10 10:00 /tmp

权限最后那个 t 就是 Sticky Bit。

Sticky Bit 只对目录有意义。设置之后,目录里的文件只能被文件的属主(或 root)删除或重命名——即使你对该目录有写权限。这就是为什么 /tmp 的权限是 777(所有人可读写执行),但你没法删除别人放在 /tmp 里的文件。

bash
$ sudo chmod +t /srv/shared
# 或者数字法:在三位权限前面加第四位 1
$ sudo chmod 1777 /srv/shared

三张王牌速查表

特殊权限数字字母作用
SUID4u+s执行时 EUID = 文件属主
SGID2g+s执行时 EGID = 文件属组;目录下新文件继承属组
Sticky1o+t目录下文件只能被属主删除

练习题

走到这里,rwx 的每一根神经都应该理清了——或者你以为理清了。下面几道题递进难度,第三题如果做出来了,说明你真的懂了。

练习 16.1 ⭐(理解)

一个文件的权限是 rwxr-x---,属主是 charlie,属组是 dev。请问:

  1. charlie 对这个文件有什么权限?
  2. 用户 bob(属于 dev 组)对这个文件有什么权限?
  3. 用户 alice(不属于 dev 组,也不是 charlie)对这个文件有什么权限?

如果 charlie 执行了 chmod 000 这个文件,charlie 自己还能读取它吗?为什么?

练习 16.2 ⭐⭐(应用)

你创建了一个共享目录 /srv/embedded,需要实现以下效果:

  1. embedded 组的所有成员都能在此目录下创建和修改文件
  2. 新建文件的属组自动是 embedded(不管创建者的主组是什么)
  3. 用户只能删除自己创建的文件

请写出创建这个目录并设置权限的完整命令序列。

提示:SGID 和 Sticky Bit 的组合使用。

练习 16.3 ⭐⭐⭐(思考)

/usr/bin/passwd 设置了 SUID,所以普通用户执行 passwd 时 EUID 是 root。但 passwd 命令只允许你改自己的密码,不允许你改别人的密码(除非你是 root)。既然进程已经拥有 root 权限了,它是怎么做到「只让你改自己的密码」的?

提示:SUID 给了你能力,但程序可以自己选择用或不用这个能力。想一想 passwd 的源代码里会做什么判断。


本章回响

本章建立的核心认知是:Linux 的权限模型用九个字符覆盖了几乎所有场景,而且它的检查逻辑是严格短路、按级别匹配的——先看属主,再看组,最后看其他人,匹配即停。这个设计简洁到极致,也精确到极致。

rwx 不只是三个字母。对文件和对目录,它们的意思不一样;属主权限为空时,即使组和其他人权限全开,属主自己也进不去——因为权限检查在属主那一层就短路了,根本不会往后看。理解了短路机制,之前那些看起来矛盾的权限行为就不再令人困惑了。

还记得开头那个问题吗——chmod 755 到底什么意思?现在你应该能在大脑里拆开它了:7 = rwx(属主全能),5 = r-x(组可读可执行),5 = r-x(其他人可读可执行)。755 不是魔法数字,是三个 3-bit 寄存器的值。而 SUID 那个 rws 里的 s——它让 passwd 临时借用 root 的力量来帮你改密码,用完立刻还回去。这种「借用即归还」的设计哲学,贯穿了整个 Linux 的安全体系。

权限保证了一个用户不能乱动另一个用户的文件——但安装软件涉及的是系统级操作,需要往 /usr/bin 里写文件、往 /lib 里放库、往 /etc 里改配置。这些事情都需要 root 权限,而包管理器就是帮你在安全框架内完成这些操作的自动化工具。

下一章我们就来拆解 Linux 上安装软件的三种方式:apt、snap 和源码编译。


← 上一章下一章 →

Built with VitePress