操作系统安全基础:权限、隔离与访问控制
线上事故复盘里,有一大类原因听起来很"低级"却反复出现:某个服务用 root 跑、被攻破后整台机器沦陷;某个目录权限给成了 777,敏感配置被任意读取;某个容器开了特权模式,逃逸后碰到了宿主机。这些都不是黑客多高明,而是权限给宽了。
操作系统安全的起点,其实和"防黑客"没那么直接的关系。只要一台机器上同时跑着多个用户、多个进程、多个服务,"谁能动谁的东西"就成了一个默认必须回答的问题——哪怕没有任何攻击者,一个进程的 bug 也可能误删别人的文件、读到别人的密钥。安全机制首先是为了在这种共享环境里建立秩序。
这一篇我们先把机制讲清楚:内核在你访问一个文件、发起一个特权操作时,到底是怎么一步步检查权限的——UID/GID 怎么定身份、权限位怎么判读写、特权操作怎么被拦、capability 怎么把"全有或全无的 root"拆细,然后再讲这些设计背后的安全原则,以及它们怎么落到你的部署实践里。
先记住一条贯穿全篇的主线:安全的核心是"主体(谁)对客体(什么)能做什么操作"这个三元组的检查,以及"出了事影响范围有多大"的隔离。
一、先建立模型:主体、客体、操作
讲任何访问控制,都绕不开三个角色,先把它们立起来:
- 主体(subject):发起操作的人或程序。在操作系统里,主体通常是进程,而进程代表着某个用户(由 UID/GID 标识);
- 客体(object):被访问的资源。文件、目录、设备、网络端口、其他进程等;
- 操作(operation):主体想对客体做什么。读、写、执行、删除、发信号等。
操作系统安全的全部工作,可以浓缩成一句话:在每一次"主体对客体执行操作"时,检查这个操作是否被允许,不允许就拒绝。 权限模型定义"什么被允许",访问控制负责"在访问点执行检查",隔离负责"即使检查被绕过,也把破坏限制在小范围"。
这三件事——权限、访问控制、隔离——是层层递进的防线,下面逐个拆。
二、身份从哪来:UID、GID 与进程的关系
检查权限前,系统得先知道"你是谁"。Linux 用数字来标识身份:
- UID(用户 ID):每个用户一个数字。
UID 0是特殊的——它就是 root(超级用户),传统上拥有几乎所有权限; - GID(组 ID):用户可以属于一个或多个组,便于按组授权。
关键在于身份是怎么和进程绑定的:
- 每个进程都带着一组身份标识,最重要的是 EUID(有效用户 ID)——内核做权限判断时,看的就是进程的 EUID;
- 进程的身份继承自父进程。你登录后的 shell 带着你的 UID,它启动的所有程序默认也带着你的 UID;
- 身份会沿着 fork 传递——这就是为什么用普通用户启动的服务,它 fork 出的子进程也都是普通用户权限,天然受限。
这里有个精巧又危险的机制:SUID(set-user-ID)。一个可执行文件如果设了 SUID 位,那么任何用户运行它时,进程的 EUID 会变成文件属主的 UID,而不是运行者的。最典型的是 passwd 命令——普通用户改密码需要写 /etc/shadow(只有 root 能写),所以 passwd 设了 SUID root,让普通用户在执行它的瞬间临时获得 root 权限去改密码。
SUID 是"临时提权"的合法机制,但也是攻击者梦寐以求的目标:任何一个有漏洞的 SUID root 程序,都可能成为普通用户提权到 root 的跳板。这就是为什么安全加固时要扫描并最小化系统里的 SUID 程序。
三、机制核心:访问一个文件时,内核怎么判权限
Linux 最基础的权限模型是 DAC(自主访问控制,Discretionary Access Control),落地形式就是你熟悉的 rwxr-xr-x。我们把内核的检查流程拆开看。每个文件的 inode 里(还记得上一篇的 inode 吗)存着:
- 属主 UID 和 属组 GID;
- 三组权限位:属主(owner)、属组(group)、其他人(other),每组三位
rwx(读/写/执行)。
当一个进程要访问文件,内核按这个顺序判定:
- 如果进程 EUID 是 0(root):直接放行(root 几乎无视 DAC 权限位);
- 否则,如果进程 EUID == 文件属主 UID:用 owner 那三位判断;
- 否则,如果进程的组匹配文件属组 GID:用 group 那三位判断;
- 否则:用 other 那三位判断。
注意一个常被搞错的点:匹配是"短路"的,找到第一个匹配的身份类别就用它那组权限,不再往下看。 所以如果你是文件属主,但 owner 位是 r--、而 other 位是 rw-,你反而不能写——因为命中 owner 就停了,不会"降级"去用 other 的权限。
目录的权限位语义还有个容易栽跟头的地方:
- 目录的
r:能否列出目录内容(ls); - 目录的
w:能否在目录里创建/删除文件(注意:删一个文件看的是目录的写权限,不是文件本身的权限!); - 目录的
x:能否进入/穿过这个目录(cd、访问目录下的文件)。
这解释了一个经典困惑:你对某文件有写权限,却删不掉它——因为删除是修改目录,要看目录的
w;反过来,你对某文件没有任何权限,但只要对它所在目录有w,照样能把它删掉。删除权限属于目录,不属于文件本身。
四、把 root 拆开:capability 与最小权限的落地
传统 Unix 的权限是"二元"的:要么是普通用户(处处受限),要么是 root(无所不能)。这带来一个尖锐矛盾:很多程序只需要一项特权能力,却不得不用整个 root 来跑。比如:
ping需要构造原始网络包(一项底层网络特权),但不需要读你所有文件的权限;- 一个 Web 服务器要绑定 80 这种小于 1024 的特权端口,但它绑定后根本不需要其他 root 权限。
如果这些程序都用 root 跑,一旦被攻破,攻击者拿到的是完整的 root,而不只是那一项能力。Linux 的解法是 capability(能力)——把庞大的 root 特权切成几十个细粒度的独立能力,每个程序只授予它真正需要的那几项:
CAP_NET_RAW:构造原始套接字(ping要的就是这个);CAP_NET_BIND_SERVICE:绑定 1024 以下的特权端口;CAP_SYS_ADMIN:一大堆管理操作(这个仍然很大,被戏称为"新 root");CAP_CHOWN、CAP_KILL等等。
于是现代做法是:让服务以普通用户运行,只额外授予它需要的那一两个 capability。这样即使它被攻破,攻击者拿到的也只是那一两项能力,而非整个系统。
capability 是"最小权限原则"在内核层的直接落地:不要给一个只需要开门的人一整串万能钥匙,只给他能开那扇门的那一把。 容器默认就 drop 掉了大部分 capability,只保留必需的子集,这是容器安全的重要一环。
五、用户态/内核态:最底层的那道隔离边界
权限检查能成立,前提是有人强制执行它——总得有个"裁判",且这个裁判不能被普通程序篡改。这道最底层的边界,是 CPU 硬件提供的特权级(privilege level / ring):
- 内核态(ring 0):CPU 在这个模式下,能执行所有指令、直接访问所有硬件和内存。操作系统内核运行在这里;
- 用户态(ring 3):CPU 在这个模式下,禁止执行特权指令、禁止直接访问硬件、只能访问自己被允许的内存。所有应用程序运行在这里。
应用程序想做特权操作(读文件、发网络包、分配内存),不能自己直接干,必须通过系统调用——主动触发一个陷阱(还记得中断异常那篇的 trap 吗),把 CPU 从用户态切到内核态,由内核代为执行并在执行前做权限检查,做完再切回用户态。
这道用户态/内核态的硬件边界,是整个操作系统安全的地基。正因为应用程序无法绕过内核直接碰硬件和别人的内存,内核的每一次权限检查才有意义——否则一个恶意程序直接改内存、直接操作磁盘,所有软件层面的权限模型都形同虚设。 进程地址空间的隔离(靠分页和页表)也是这道防线的一部分:A 进程根本"看不到" B 进程的内存。
六、再加一层:MAC 与强制访问控制
DAC 有个根本弱点:权限由资源属主自由支配(Discretionary 就是"自主"的意思)。属主可以把自己的文件 chmod 777 开放给所有人,root 更是能为所欲为。这意味着一次配置失误或一个被攻破的 root 进程,就能突破所有 DAC 限制。
为此有了 MAC(强制访问控制,Mandatory Access Control),代表是 SELinux 和 AppArmor。它的核心区别是:
- 规则由系统管理员/安全策略统一制定,连 root 也不能随意突破;
- 不再只看"你是谁",而是基于安全标签/上下文和预定义策略来判定。比如策略规定"Web 服务器进程只能读
/var/www、只能监听 80/443",那么即使这个进程以 root 跑、即使它被攻破,它也做不了策略之外的事——读不了/etc/shadow、连不了数据库端口。
DAC 回答"你有没有权限",MAC 在它之上再加一道"就算你有权限,策略也未必允许"。两者叠加形成纵深防御:MAC 的价值,是在 DAC 失守(如 root 被攻破)之后,仍能把破坏框死在策略边界内。 这正是"假设会被攻破,也要限制爆炸半径"的思路。
七、为什么要这样设计:纵深防御与最小权限
机制讲透,回答"为什么"。整套操作系统安全机制,背后是两条贯穿始终的原则:
- 最小权限原则(least privilege):每个主体只授予完成任务所必需的最小权限。capability 拆分 root、服务用普通用户跑、容器 drop capability、目录权限收窄——全是它的体现。理由很直接:权限越小,被攻破后能造成的破坏越小;
- 纵深防御(defense in depth):不指望单一防线万无一失,而是层层设防。用户态/内核态边界 → 进程地址空间隔离 → DAC 权限位 → capability → MAC 策略 → 容器/cgroup 隔离,一层失守还有下一层。
安全设计的核心假设是悲观的:任何单一防线都可能被攻破,配置可能失误、代码可能有洞、root 可能沦陷。 所以真正的目标不是"绝对防住攻击",而是"让攻破的代价更高、让攻破后的破坏范围更小"。最小权限缩小单点的破坏力,纵深防御增加突破的层数——两者合起来,就是操作系统安全的设计哲学。
八、和后端 / SRE 实践的联系
这些机制直接对应你日常的部署安全:
- 永远别用 root 跑业务服务:服务以专用的低权限用户运行,需要特权端口就用 capability(
CAP_NET_BIND_SERVICE)或前置反代,而不是图省事上 root。一旦被 RCE,攻击者拿到的是受限用户而非 root; - 权限别给宽:
chmod 777是事故温床。配置文件、密钥文件应该600(仅属主可读写)、属主设成服务账号。目录权限同理,理解了"删除看目录权限"才能正确设计目录结构; - 容器安全加固:不用
--privileged、drop 掉不需要的 capability、用非 root 用户跑容器、开启 user namespace remap、配 seccomp 限制系统调用——这些都是把最小权限和纵深防御落到容器上; - SUID 程序排查:定期扫描
find / -perm -4000找出 SUID 程序,最小化它们,因为每个都是潜在提权点; - SELinux 别一遇到问题就关:很多人 SELinux 一报错就
setenforce 0图省事,等于自废一道纵深防御。正确做法是看audit.log调策略; - 理解越权的本质:大量"安全事故"不是没有机制,而是权限配置过宽——给了不该给的能力。理解机制,才能在配置时本能地收窄权限。
九、动手做一个小实验:审视一个服务的权限边界
挑一个你跑的后端服务,做一次"权限体检":
1 | # 1. 看服务进程以什么身份在跑(USER 列) |
做完这套体检,你大概率会发现一些"给宽了"的地方——某个服务没必要用 root、某个配置文件权限太松、某个进程握着用不到的 capability。把它们一项项收窄,就是最小权限原则最真实的实践。比起背定义,这种"审视真实系统的攻击面"会让安全直觉扎实得多。
学习这一部分最容易踩的坑
1. 以为安全就是"防黑客"
只要有资源共享,"谁能动谁"就是必答题。内部误操作、程序 bug 同样需要权限和隔离来约束,安全是默认要考虑的基础能力,不是有攻击才需要。
2. 搞错"删除"看谁的权限
删除一个文件看的是它所在目录的写权限,不是文件本身的权限。对文件没权限但对目录有 w,照样能删;对文件有 w 但对目录没 w,反而删不掉。
3. 把"有权限"等同于"应该用"
有 root 不代表要用 root 跑一切。最小权限原则要求只授予必需的能力——这正是 capability 把 root 拆细的意义。权限越大,被攻破后果越严重。
4. 混淆 DAC 和 MAC
DAC 是属主自主支配的权限位,root 可突破;MAC(SELinux/AppArmor)是系统强制的策略,连 root 也受约束。两者叠加才是纵深防御,关掉 MAC 等于自废一层。
总结
操作系统安全不是独立章节,而是贯穿资源管理全流程的底层要求。核心要点:
- 安全的本质是检查"主体(进程/用户)对客体(资源)能否执行某操作",并通过隔离控制破坏范围;
- 身份由 UID/GID 标识、随进程继承,SUID 提供合法的临时提权(也是提权攻击的目标);
- DAC 权限位按 owner→group→other 短路匹配判定;目录的
wx语义特殊,删除权限属于目录; - capability 把全能 root 拆成细粒度能力,是最小权限在内核层的落地;
- 用户态/内核态特权级是最底层的强制边界,让内核的权限检查无法被绕过,是整个安全体系的地基;
- **MAC(SELinux/AppArmor)**在 DAC 之上加一道连 root 也突破不了的强制策略,实现纵深防御;
- 两条贯穿原则:最小权限(缩小单点破坏力)+ 纵深防御(增加突破层数),核心假设是"任何防线都可能失守,要让破坏范围尽量小"。
把"最小权限 + 纵深防御"这两把尺子记牢,你在部署服务、配置权限、加固容器时,就会本能地去问"这个权限真的必须吗、它被攻破后能祸害多大范围"——这正是操作系统安全留给工程师最有价值的直觉。
参考资源:
- 《现代操作系统》(Andrew S. Tanenbaum)安全章节
- 《UNIX 环境高级编程》(APUE)文件权限与进程关系章节
- The Linux Kernel Documentation - Capabilities
- 《计算机安全:原理与实践》(William Stallings)