Linux 的安全模型建立在身份(Identity)、所有权(Ownership) 与 访问控制(Access Control) 三者紧密耦合的基础上。这一模型从 Unix 时代延续至今,并在现代内核中不断被细化与分层,形成了经典 DAC(自主访问控制)+ 现代 MAC(强制访问控制)+ 能力机制(Capabilities)+ 命名空间(Namespaces)+ 资源控制(cgroups)的复合体系。
1. 身份标识的核心:UID 与 GID 的语义
每个进程在内核眼中只有两个数字:真实 UID(ruid)、有效 UID(euid)、保存 UID(suid) 以及对应的 GID 集合。
- ruid(真实用户 ID):启动进程的实际用户,通常不变
- euid(有效用户 ID):内核在权限检查时真正使用的身份
- suid(保存的 set-user-ID):用于在 execve 后恢复权限,常与 setuid 位配合
经典例子:/usr/bin/passwd 程序文件拥有 setuid 位且 owner 为 root。
执行流程:
- 用户 wu(UID 1000)执行 passwd
- 内核看到文件有 setuid 位 → 把进程的 euid 设置为文件拥有者(0)
- 进程以 root 有效身份运行,得以修改 /etc/shadow
- 但 ruid 仍为 1000(内核知道这是谁发起的)
这种“临时提升权限”的机制是 setuid/setgid 程序存在的根本原因,但也成为历史上最常见的安全漏洞来源之一。
现代内核引入 capabilities 来取代大部分 setuid root 程序,将 root 的全能权限拆分为约 40 种细粒度能力(如 cap_net_bind_service、cap_sys_ptrace、cap_dac_override 等)。
2. 文件系统访问控制决策流程(VFS 层)
当进程尝试 open/read/write/exec 一个文件时,内核 VFS 层执行的完整决策顺序(简化版):
- 检查文件系统是否挂载了 noexec、nosuid、nodev 等标志
- 检查 SELinux / AppArmor / Landlock 等 LSM(Linux Security Module)钩子
- 如果通过,则进入经典 DAC 检查:
- 进程 euid == 文件 uid → 匹配 owner 权限位
- 进程任一 gid ∈ 文件 gid 或进程补充组 → 匹配 group 权限位
- 否则匹配 other 权限位
- 如果任何一步拒绝 → 返回 EACCES(Permission denied)
关键点:目录的 x 权限决定能否“进入”并解析路径中的后续组件。即使文件本身有读权限,如果上级目录没有 x,也无法访问。
3. 特殊权限位的内核实现语义
- setuid(S_ISUID):execve 时若文件有此位,则新进程 euid = 文件 uid(而非调用者 ruid)
- setgid(S_ISGID):
- 文件:execve 时 euid = 文件 gid(极少用)
- 目录:新建文件/子目录时,继承父目录的 gid 而非创建者的主 gid
- sticky bit(S_ISVTX):仅对目录有效,限制删除权——只有文件拥有者、目录拥有者或 root 能删除目录中的文件(/tmp 的经典用法)
这些位存储在 inode 的 i_mode 高 12 位中,与普通 rwx 位并列。
4. umask 与文件模式创建掩码
新建文件/目录时的初始模式由以下公式决定:
文件默认模式 = 0666 & ~umask
目录默认模式 = 0777 & ~umask
常见 umask 值与结果:
| umask | 文件模式 | 目录模式 | 典型场景 |
|---|---|---|---|
| 0022 | 0644 | 0755 | 标准单用户服务器 |
| 0002 | 0664 | 0775 | 组内协作开发(组可写) |
| 0027 | 0640 | 0750 | 严格服务器(其他用户无权限) |
| 0077 | 0600 | 0700 | 最高隔离(仅拥有者可见) |
umask 影响的是“允许”的权限,而非“强制”的权限。最终还受文件系统默认 ACL 或继承规则影响。
5. Capabilities 的分层设计思想
Linux 内核从 2.6.24 开始引入 capabilities,将传统 root(capable() 全为 true)拆分为独立的可授予能力。
每个进程拥有一个 capability 集合(inheritable、permitted、effective、bounding、ambient 五组)。
最重要三组:
- permitted:进程可能使用的能力上限
- effective:当前真正生效的能力
- inheritable:可被子进程继承的能力
典型场景:让非 root 进程绑定低端口
文件能力:cap_net_bind_service=+ep
→ execve 后 permitted 与 effective 都获得此能力
→ 进程无需以 root 身份启动即可 listen 80/443
6. 现代身份隔离:User Namespace 的革命性变化
User namespace 允许进程在自己的“用户视图”中把 UID 0 映射到宿主机的非特权 UID。
容器典型映射:
- 容器内 root(UID 0) → 宿主机某个高位 UID(如 100000)
- 容器内普通用户 1000 → 宿主机 101000
这导致了根本性的权限隔离:容器内看似 root 的进程,在宿主机上只是普通用户,无法直接影响其他 namespace 或真实 root。
结合 mount/pid/net/uts/cgroup/time 等 namespace,形成完整的容器隔离基础。
7. 服务器运维中最常遇到的权限模型误区与正确心法
误区 1:认为 777 就能“解决问题” → 实际暴露了所有用户可写权限,极易被恶意进程利用
误区 2:把服务跑在 root 下“最保险” → 一旦被突破,攻击面无限大;应尽量用专用系统用户 + capabilities
误区 3:忽略目录的 x 位 → 文件有 644 但上级目录只有 rx→,导致“Permission denied”
正确心法顺序:
- 确定进程运行身份(ps -eo user,pid,cmd | grep 服务名)
- 确定目标资源拥有者与权限(ls -lZ /path)
- 判断是否需要调整 chown / chmod / setcap
- 验证 LSM 上下文(-Z 选项)与 capabilities(getcap)
- 最后看日志与 audit(/var/log/audit/audit.log 或 journalctl)
结语
Linux 权限体系的本质是“一切决策基于身份与规则的匹配”,而非“谁运行谁最大”。从经典的三组九位权限,到 capabilities 的细粒度拆分,再到 namespace 的身份隔离,每一层都在试图把“过度授权”的窗口越缩越小。
真正掌握这一体系的关键不在于记住所有命令,而在于养成提问习惯:
- 这个进程的有效身份是什么?
- 目标资源的拥有权与权限三角匹配了吗?
- 是否有额外的 LSM 或 capability 限制?
- 如果隔离失败,攻击者能获得多大权限提升?
建立这种思考路径后,无论面对传统服务器、容器环境还是未来的 eBPF + Landlock 加固场景,你都能快速定位问题根源。