配置打开了一个加固选项,并不代表所有相关攻击都被阻止。先判断机制针对攻击面、错误检测、利用条件还是信息泄露,再核对实际启用状态。
| 查阅目标 | 阅读位置 |
| 地址、权限与边界检查 | 第 1~3 节 |
| 检测覆盖与 lockdown | 第 4~5 节 |
| seccomp、分配防护与观测 | 第 6~8 节 |
1. KASLR 与严格内存权限
KASLR 在启动过程中随机化内核的部分地址布局。随机化范围和粒度依架构、配置及实现不同,不能笼统说代码、数据、模块都由同一个随机基址控制。x86 的早期解压/重定位代码参与选择位置,不应说成由 bootloader 独立完成。
它提高攻击者预测代码或数据位置的难度;地址泄露和有限熵会削弱效果,因此要结合地址信息限制。kptr_restrict=2 对受 %pK 规则控制的输出隐藏地址,不是“只有非 root 看不到”,也不承诺所有内核地址输出都由这一项控制。
CONFIG_STRICT_KERNEL_RWX、模块相关严格权限等机制约束可写与可执行区域。只读数据、不可执行数据页限制修改代码和直接执行注入数据,但不自动阻止所有代码复用和数据篡改。见内核自保护与 kptr_restrict 说明。
用户态 /proc/sys/kernel/randomize_va_space 控制的是用户进程 ASLR,不能作为内核 KASLR 或每次 syscall 内核栈偏移随机化已经开启的证据。
2. Stack protector 与对象边界检查
编译器只给符合选项规则的函数插入 canary,并非每个函数必然有 canary。检查通常在受保护函数退出前发生,检测到 canary 损坏后进入失败处置;它无法覆盖任意位置的越界写,也不保证在数据被误用之前立即发现。
内核 CONFIG_FORTIFY_SOURCE 根据编译器能够推导的对象大小强化部分内存/字符串操作。已知错误可在编译阶段诊断,部分动态越界可在运行时触发报告及终止路径。未知对象边界和未覆盖操作仍是边界,不能推导“所有 memcpy 都安全”。它也不是 glibc 的 _FORTIFY_SOURCE=3 编译宏。
CONFIG_HARDENED_USERCOPY 检查内核与用户态复制时的对象及区域约束,解决的是某些不合法复制;不会证明复制内容符合业务协议,也不会使传入长度自动可信。
3. KPTI:限制用户态页表中的内核映射
Meltdown 类问题涉及特定处理器上的瞬态执行泄露。KPTI 让用户态使用的页表只保留用户映射和进入内核所需的少量映射,进入内核时切换到适当的内核页表。
这减少了用户态瞬态访问大范围内核映射的条件,但不是全部 Spectre/Meltdown 类漏洞的统一解决方案。页表切换和 TLB 管理有开销,PCID 等能力可缓解;实际影响应按工作负载测量,不能固定套用一个百分比。
是否启用取决于处理器漏洞状态、内核及启动策略,不能用“某代 Intel/AMD 以后都自动关闭”概括。查看 /sys/devices/system/cpu/vulnerabilities/ 的实际报告,并区分 Not affected、已缓解和仍存在的风险。参见 x86 PTI。
4. 检测工具的覆盖范围不同
| 工具 | 主要发现的问题 | 重要边界 |
| KASAN | 越界与释放后使用等内存错误 | generic、软件标签、硬件标签模式的能力和代价不同 |
| KMSAN | 未初始化值的使用与传播 | 需要相应编译器、架构和插桩支持 |
| UBSAN | 所选未定义行为检查 | 启用哪些子检查决定覆盖;不能统称检查所有整数溢出 |
| KFENCE | 采样检测堆内存错误 | 低开销依赖采样,未报告不等于所有访问无错 |
无符号回绕本身不是 C 的未定义行为;内核的编译选项和子检查也影响整数行为。一次测试没有报告,只说明在这次执行和覆盖范围内没有捕获问题。
这些工具并不都只能用于开发内核:例如硬件标签 KASAN 或 KFENCE 可适用于不同生产目标,但应评估真实开销与支持条件,不能给出统一“慢两倍”的结论。参考 KASAN、KMSAN、UBSAN、KFENCE。
5. Lockdown:限制高权限用户改变内核信任边界
lockdown 可以限制会读写内核内存、替换未验证代码或暴露机密的接口。integrity 与 confidentiality 模式约束不同,后者进一步限制某些读取途径;并非所有 BPF 和调试功能都用一句“全部禁止”准确描述。
状态接口通常是 securityfs 中的 /sys/kernel/security/lockdown,不是 /proc/sys/kernel/lockdown。Secure Boot 可能依发行版、配置和补丁触发 lockdown,但二者不能无条件画等号;签名验证、模块加载策略和 lockdown 还需分别核对。详见 kernel_lockdown(7)。
6. Seccomp:按系统调用 ABI 收窄接口
seccomp filter 读取系统调用号、架构标识、指令地址及标量参数,用经典 BPF 过滤程序返回 ALLOW、ERRNO、TRAP、KILL_PROCESS、USER_NOTIF 等动作。不存在一个实际名为 SECCOMP_RET_DENY 的通用返回动作。
安装通常要求 no_new_privs,或满足相应 namespace 中的权限条件。过滤器附着在线程上,可用 TSYNC 等方式同步线程组;允许的 fork/exec 会继承过滤约束。叠加过滤器不能用后来的 ALLOW 放宽此前更严格的结果。
| 过滤器看到什么 | 仍不能直接知道什么 |
| 系统调用号 | 不结合 ABI,不能正确确定调用含义 |
| 标量参数与指针数值 | 不能解引用成稳定的路径字符串 |
| 架构标识 | 需要纳入规则判断,不能忽略 |
因此允许 openat 并比较指针数值,不等于只允许打开指定路径。
监督者检查之后,目标内存还可能变化
USER_NOTIF 让监督者接收通知并处理或响应,但它读取目标进程可变内存后再允许原 syscall 继续,会面对 TOCTOU 问题。通知机制不是自动安全的文件访问控制器。应与 LSM、文件描述符权限及其他隔离机制配合,见 seccomp filter 文档。
在检查之后改动路径缓冲,再分别选择“继续使用当前指针”或“操作已持有的对象”。比较实际使用的目标,便能看见检查时成立的事实为何会过期。
正在呈现知识画面
检查过的对象,和随后使用的对象是同一个吗?检查目标进程的一次内存快照,不会冻结它之后的参数。安全操作必须绑定到实际验证过的对象和权限,不能把 CONTINUE 当作这种绑定。
{"id":"seccomp-check-use-race","title":"检查过的对象,和随后使用的对象是同一个吗?","summary":"检查目标进程的一次内存快照,不会冻结它之后的参数。安全操作必须绑定到实际验证过的对象和权限,不能把 CONTINUE 当作这种绑定。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"presets\" role=\"group\" data-i18n-label=\"target\"><button data-mode=\"A\" data-i18n=\"a\"></button><button data-mode=\"B\" data-i18n=\"b\"></button></div><div class=\"actions\"><button id=\"check\" data-i18n=\"check\"></button></div><label for=\"mode\" data-i18n=\"mode\"></label><select id=\"mode\"><option value=\"continue\" data-i18n=\"continueCall\"></option><option value=\"held\" data-i18n=\"held\"></option></select><div class=\"track-row\"><span data-i18n=\"buffer\"></span><div id=\"buffer\" class=\"track\"></div></div><div class=\"track-row\"><span data-i18n=\"checked\"></span><div id=\"checked\" class=\"track\"></div></div><div class=\"actions\"><button id=\"perform\" data-i18n=\"perform\"></button></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><div class=\"actions\"><button id=\"reset\" data-i18n=\"reset\"></button></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.readout{min-height:4.8em}","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)));}let target='A',held=null,approved=false,result=null;function draw(){cells('buffer',[{text:'path → '+target,cls:'on'}]);cells('checked',[{text:held===null?t('unseen'):'ref → '+held+' · '+t(approved?'approved':'rejected'),cls:approved?'on':'gold'}]);set('result',result===null?t('pending'):result.via+' → '+result.object);$('#perform').disabled=!approved;pressed(target);set('explanation',t('note'));}document.querySelectorAll('[data-mode]').forEach(e=>e.onclick=()=>{target=e.dataset.mode;draw();});$('#check').onclick=()=>{held=target;approved=held==='A';draw();};$('#perform').onclick=()=>{if(!approved)return;const stable=$('#mode').value==='held';result={object:stable?held:target,via:stable?'ref':'path'};draw();};$('#reset').onclick=()=>{target='A';held=null;approved=false;result=null;draw();};draw();","audio":false,"strings":{"a":"指向 A","approved":"验证允许","b":"指向 B","buffer":"可变路径缓冲","check":"取得引用并验证","checked":"检查时持有的对象","continueCall":"原调用 CONTINUE","heading":"检查过的对象,和随后使用的对象是同一个吗?","held":"监督者使用已验证引用","intro":"先取得并验证对象 A,再改变目标进程的路径缓冲。分别选择让原调用继续,或由监督者使用已验证的稳定对象引用。","mode":"如何执行操作","note":"A 验证成功后切到 B,再执行。原调用重新读取可变参数会得到 B;持有引用的操作仍使用已经验证的 A。","pending":"尚未访问","perform":"执行所选操作","rejected":"验证拒绝","reset":"重置","result":"最近一次实际访问","scope":"A/B 是无真实文件访问的玩具对象,策略只允许 A。检查动作先取得稳定引用再验证;受控方案由监督者通过该引用执行操作,而不是随后重开原字符串。省略通知 ID、ADDFD、路径解析约束和并发协议;不是完整 seccomp 沙箱实现。","scopeLabel":"模型范围与约定","target":"目标进程当前参数","unseen":"尚未检查"}}
7. 清零与 freelist 防护
init_on_alloc / init_on_free 可在相应分配或释放路径清零对象与页面,降低旧数据暴露等风险,但不使悬空指针恢复合法,也不意味着所有分配器和特殊路径都采用相同处理。
CONFIG_SLAB_FREELIST_RANDOM 改变空闲对象排列,CONFIG_SLAB_FREELIST_HARDENED 强化 freelist 元数据;随机顺序与指针保护是不同机制。它们增加部分堆利用的难度,不能取代修复 UAF。
8. 只读确认运行状态
from pathlib import Path
for name in ('/proc/sys/kernel/kptr_restrict',
'/sys/kernel/security/lockdown',
'/sys/devices/system/cpu/vulnerabilities/meltdown'):
try:
print(name, Path(name).read_text().strip())
except OSError as exc:
print(name, type(exc).__name__)
同时检查目标内核配置和 /proc/cmdline,再结合运行状态判断。配置里有能力不代表启动时采用它;某个状态文件不可见也可能只是权限或命名空间造成的观测限制。