Unix 权限允许,服务仍可能被策略拒绝;拥有一个 capability,也不能推断所有相关操作都会通过。访问控制需要把主体、对象、操作及策略组合一起解释。
DAC 包括 owner/group/other 与 ACL,通常允许对象所有者参与安排;强制策略可进一步约束服务域。
| 查阅目标 | 阅读位置 |
| LSM 与标签/profile | 第 1~3 节 |
| capability 与 BPF 策略 | 第 4~5 节 |
| keyring、完整性与运行状态 | 第 6~7 节 |
1. LSM 提供检查位置,模块提供策略
LSM hook 位于文件、凭据、进程、网络等内核对象的操作路径中;一次系统调用可能触发多个检查,内核内部操作也可能经过这些位置。hook 既包括返回决策的函数,也包括分配、释放或更新安全状态的通知,不能统一说“所有 hook 返回错误就拒绝”。
例如 security_file_open() 使用具有返回值的 file_open hook;将它写成 call_void_hook() 再声称检查错误是不成立的。读取目标版本的 security/security.c 与 include/linux/lsm_hook_defs.h,比复制旧版 hook 链表结构更可靠。
对常见的拒绝型权限 hook,已启用模块按定义的组合规则参与检查;一项拒绝不能靠另一模块的允许覆盖。不能画成 SELinux 和 AppArmor 在所有系统上固定并行运行:实际启用组合与顺序取决于内核版本、配置和启动参数。securityfs 可见时,/sys/kernel/security/lsm 列出当前活动模块。
LSM 的名字也不意味着它们是可以随意 insmod/rmmod 的普通模块。框架概念见 LSM 文档,其中历史结构体说明应结合当前源码阅读。
2. SELinux:主体域与对象类型
SELinux 安全上下文通常包含 user、role、type 和级别/范围。例如文件标签中的 httpd_sys_content_t 是对象类型;服务进程则运行在某个域中。
读一条 type enforcement 拒绝记录时,对齐四项:
| 字段 | 要确认的问题 |
| source context | 哪个主体域发起访问? |
| target context | 对象实际是什么类型? |
| object class | 文件、目录还是其他对象? |
| permission | 被拒绝的是 read、search 或哪项操作? |
只看路径容易漏掉标签、域转换或目录 search 权限。规则表达的是源域对目标类型某类对象的操作许可。
AVC 缓存访问决策以降低查询成本。标签来源包括文件系统扩展属性和策略为特定对象生成的标签;procfs、tmpfs 等的标记方式不能统一说成都由 genfscon 生成。
Enforcing 执行策略拒绝;permissive 通常记录本应拒绝的访问而不施加该 SELinux 拒绝。其他 DAC、LSM 和系统错误仍可使操作失败,所以 permissive 不等于所有操作都会成功。审计还受策略与日志配置影响,不能假设每次判断都有一条新日志。
3. AppArmor 与 Smack:不同的策略表达
AppArmor 以程序 profile 为核心,文件访问规则常按路径表达,并可限制 capability、网络等操作。相同 Unix 用户下的不同程序因此可以受到不同约束。
路径策略需要理解进程的挂载视图、别名、重命名和链接规则,但不能直接下结论“创建 hardlink 或 bind mount 就能绕过 AppArmor”。链接和挂载本身也受检查,profile、策略 namespace 及具体规则决定结果。下面仅是规则语法片段,不是可直接部署的完整服务 profile:
/var/www/** r,
/var/log/example/*.log w,
capability net_bind_service,
它缺少程序加载、动态库、目录访问和完整运行依赖。理论上理解一条规则,与拥有可用且足够收敛的策略,是两项不同工作。见 AppArmor 文档。
Smack 也使用主体/对象标签和访问规则,但有自己的规则及特殊标签语义。它不是“SELinux 删除固定百分比功能”的版本;选择机制应比较实际表达能力与部署环境。见 Smack 文档。
4. Capabilities:拆分特权,并不生成完整沙箱
内核 capability 语义以线程凭据为基础,包括 permitted、effective、inheritable,以及 bounding 和 ambient 集合。常规权限检查关心 effective 集中的相关能力;exec 时如何重新计算还取决于文件 capability、set-ID、securebits 等条件。
CAP_NET_RAW 涉及 raw/packet socket 等权限。
CAP_SYS_PTRACE 可以参与绕过部分 ptrace 凭据限制,但仍有其他检查。
CAP_NET_BIND_SERVICE 涉及受保护低端口;网络 namespace 的端口阈值设置也会影响是否需要它。
CAP_SYS_ADMIN 覆盖范围很广,不宜把名字理解成一项狭窄操作。
user namespace 内的 capability 只在适用的 namespace 和对象关系中有效,容器内 root 不自动拥有宿主 root 的全部能力。反过来,移除 capability 也不消除普通用户本来就能使用的所有接口。详见 capabilities(7)。
将 DAC、capability 的作用域、LSM 与只读文件系统条件分别打开。下面呈现的是这些约束的组合,不把它们伪装成所有系统都固定遵循的一条函数调用顺序。
正在呈现知识画面
一项允许,能否覆盖另一项拒绝?适用的 capability 可以满足特定 DAC 绕过条件,但不能把 LSM 拒绝或只读文件系统改成允许。user namespace 内的 root 也不是宿主所有对象的通行证。
{"id":"access-control-gates","title":"一项允许,能否覆盖另一项拒绝?","summary":"适用的 capability 可以满足特定 DAC 绕过条件,但不能把 LSM 拒绝或只读文件系统改成允许。user namespace 内的 root 也不是宿主所有对象的通行证。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><label class=\"check\"><input type=\"checkbox\" id=\"dac\"><span data-i18n=\"dac\"></span></label><label class=\"check\"><input type=\"checkbox\" id=\"cap\" checked><span data-i18n=\"cap\"></span></label><label class=\"check\"><input type=\"checkbox\" id=\"scopeOK\"><span data-i18n=\"scopeOK\"></span></label><label class=\"check\"><input type=\"checkbox\" id=\"lsm\" checked><span data-i18n=\"lsm\"></span></label><label class=\"check\"><input type=\"checkbox\" id=\"rw\" checked><span data-i18n=\"rw\"></span></label><div class=\"track-row\"><span data-i18n=\"dacGate\"></span><div id=\"dacGate\" class=\"track\"></div></div><div class=\"track-row\"><span data-i18n=\"lsmGate\"></span><div id=\"lsmGate\" class=\"track\"></div></div><div class=\"track-row\"><span data-i18n=\"fsGate\"></span><div id=\"fsGate\" class=\"track\"></div></div><div class=\"formula\" id=\"formula\"></div><div class=\"card\"><header data-i18n=\"result\"></header><div id=\"result\" class=\"readout\"></div></div><div class=\"copy\"><p id=\"explanation\" role=\"status\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"note\"></p></div><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n.formula{min-height:4.8em}.readout{min-height:3.2em}","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}function draw(){const dac=$('#dac').checked,cap=$('#cap').checked,scope=$('#scopeOK').checked,l=$('#lsm').checked,r=$('#rw').checked,d=dac||(cap&&scope);for(const[k,v]of [['dacGate',d],['lsmGate',l],['fsGate',r]])cells(k,[{text:t(v?'satisfied':'fail'),cls:v?'on':'gold'}]);set('formula','('+Number(dac)+' ∨ ('+Number(cap)+' ∧ '+Number(scope)+')) ∧ '+Number(l)+' ∧ '+Number(r)+' = '+Number(d&&l&&r));set('result',t(d&&l&&r?'allow':'deny'));set('explanation',t('note'));}document.querySelectorAll('input').forEach(e=>e.onchange=draw);draw();","audio":false,"strings":{"allow":"这些条件允许","cap":"持有 effective CAP_DAC_OVERRIDE","dac":"DAC 本身允许写入","dacGate":"DAC 或适用的绕过条件","deny":"至少一项拒绝","fail":"不满足","fsGate":"文件系统写入条件","heading":"一项允许,能否覆盖另一项拒绝?","intro":"为普通文件写入设置 DAC、capability 作用域、LSM 和只读挂载条件。各条约束同时保留,观察最终判断。","lsm":"LSM 策略允许","lsmGate":"LSM 约束","note":"取消 capability 适用范围,模拟仅在子 user namespace 中有权的情况。再单独关闭 LSM 或可写条件,观察为什么增加 capability 仍不能补齐所有条件。","reset":"重置","result":"上述必要条件的合取","rw":"挂载可写","satisfied":"满足","scope":"只分析普通文件写入,假定 inode 身份映射有效、无 ACL 特例或其他错误。capability 为 effective CAP_DAC_OVERRIDE,作用域条件代表对该 inode 适用;不以三格图声称所有 syscall 采用固定检查顺序。不是完整授权模拟器。","scopeLabel":"模型范围与约定","scopeOK":"该 capability 对目标 inode 适用"}}
5. BPF LSM:受验证的程序仍可能写错策略
BPF LSM 允许通过受权限控制的 BPF 程序扩展支持的 hook。libbpf 可处理加载和链接,CO-RE/BTF 帮助适配内核类型。
对于系统范围的 MAC hook,多个 BPF 程序串接时,程序应按该接口保留此前的拒绝结果,不能把传入的非零 ret 无条件改为 0。不同 attach 类型有不同返回语义,不能把一个例子的返回值规则直接套到所有 BPF LSM 程序。
verifier 检查可接受的内存访问、控制流等条件,降低程序破坏内核的风险;它不证明业务授权逻辑正确,也不承诺内核/verifier 永无漏洞。部署错误策略仍可能阻断正常工作。见 BPF LSM 文档。
6. Keyring、IMA 与 EVM
内核 keyring 管理带类型、权限和寿命的 key 对象,并支持将 key 链接到 keyring。它不是一个让所有用户都能读取所有密钥的全局字典,也不保证每种 key payload 都由硬件保护;trusted、encrypted 等 key 类型有各自的封装与根信任方式。
测量记录与使用时拒绝是不同目标
IMA measurement 根据策略测量文件等对象,保留测量日志,并可扩展 TPM PCR。它不是每读取一个字节就重新计算整个文件,也不是把每份文件 hash 都单独存进 TPM。远程验证需要同时理解日志、PCR、可信证明与预期状态。
IMA appraisal 则依据策略验证文件完整性/真实性并决定是否拒绝使用。EVM 保护选定安全扩展属性及相关元数据的完整性,可采用 HMAC 或签名。测量、拒绝执行与磁盘内容加密是三个不同目标;只有测量日志不自动阻止篡改后的程序运行。
7. 只读观察当前环境
from pathlib import Path
for name in ('/sys/kernel/security/lsm', '/sys/fs/selinux/enforce',
'/proc/self/attr/current'):
try:
print(name, Path(name).read_text().strip())
except OSError as exc:
print(name, type(exc).__name__)
for line in Path('/proc/self/status').read_text().splitlines():
if line.startswith(('Cap', 'NoNewPrivs:', 'Seccomp:')):
print(line)
文件不可见可能来自未挂载 securityfs、配置、权限或容器视图,不能单凭读取失败断言系统没有任何访问控制。实际授权问题应结合服务进程的凭据、命名空间、标签/profile 与拒绝日志,而不是只检查当前 shell。
参考:内核 key 管理、完整性机制源码。