Linux 的安全模型建立在身份(Identity)、所有权(Ownership) 与 访问控制(Access Control) 三者紧密耦合的基础上。这一模型从 Unix 时代延续至今,并在现代内核中不断被细化与分层,形成了经典 DAC(自主访问控制)+ 现代 MAC(强制访问控制)+ 能力机制(Capabilities)+ 命名空间(Namespaces)+ 资源控制(cgroups)的复合体系。
每个进程在内核眼中只有两个数字:真实 UID(ruid)、有效 UID(euid)、保存 UID(suid) 以及对应的 GID 集合。
经典例子:/usr/bin/passwd 程序文件拥有 setuid 位且 owner 为 root。
执行流程:
这种“临时提升权限”的机制是 setuid/setgid 程序存在的根本原因,但也成为历史上最常见的安全漏洞来源之一。
现代内核引入 capabilities 来取代大部分 setuid root 程序,将 root 的全能权限拆分为约 40 种细粒度能力(如 cap_net_bind_service、cap_sys_ptrace、cap_dac_override 等)。
当进程尝试 open/read/write/exec 一个文件时,内核 VFS 层执行的完整决策顺序(简化版):
关键点:目录的 x 权限决定能否“进入”并解析路径中的后续组件。即使文件本身有读权限,如果上级目录没有 x,也无法访问。
这些位存储在 inode 的 i_mode 高 12 位中,与普通 rwx 位并列。
新建文件/目录时的初始模式由以下公式决定:
文件默认模式 = 0666 & ~umask
目录默认模式 = 0777 & ~umask
常见 umask 值与结果:
| umask | 文件模式 | 目录模式 | 典型场景 |
|---|---|---|---|
| 0022 | 0644 | 0755 | 标准单用户服务器 |
| 0002 | 0664 | 0775 | 组内协作开发(组可写) |
| 0027 | 0640 | 0750 | 严格服务器(其他用户无权限) |
| 0077 | 0600 | 0700 | 最高隔离(仅拥有者可见) |
umask 影响的是“允许”的权限,而非“强制”的权限。最终还受文件系统默认 ACL 或继承规则影响。
Linux 内核从 2.6.24 开始引入 capabilities,将传统 root(capable() 全为 true)拆分为独立的可授予能力。
每个进程拥有一个 capability 集合(inheritable、permitted、effective、bounding、ambient 五组)。
最重要三组:
典型场景:让非 root 进程绑定低端口
文件能力:cap_net_bind_service=+ep
→ execve 后 permitted 与 effective 都获得此能力
→ 进程无需以 root 身份启动即可 listen 80/443
User namespace 允许进程在自己的“用户视图”中把 UID 0 映射到宿主机的非特权 UID。
容器典型映射:
这导致了根本性的权限隔离:容器内看似 root 的进程,在宿主机上只是普通用户,无法直接影响其他 namespace 或真实 root。
结合 mount/pid/net/uts/cgroup/time 等 namespace,形成完整的容器隔离基础。
误区 1:认为 777 就能“解决问题” → 实际暴露了所有用户可写权限,极易被恶意进程利用
误区 2:把服务跑在 root 下“最保险” → 一旦被突破,攻击面无限大;应尽量用专用系统用户 + capabilities
误区 3:忽略目录的 x 位 → 文件有 644 但上级目录只有 rx→,导致“Permission denied”
正确心法顺序:
Linux 权限体系的本质是“一切决策基于身份与规则的匹配”,而非“谁运行谁最大”。从经典的三组九位权限,到 capabilities 的细粒度拆分,再到 namespace 的身份隔离,每一层都在试图把“过度授权”的窗口越缩越小。
真正掌握这一体系的关键不在于记住所有命令,而在于养成提问习惯:
建立这种思考路径后,无论面对传统服务器、容器环境还是未来的 eBPF + Landlock 加固场景,你都能快速定位问题根源。