内核加固

本页目录

配置打开了一个加固选项,并不代表所有相关攻击都被阻止。先判断机制针对攻击面、错误检测、利用条件还是信息泄露⁠,再核对实际启用状态。

查阅目标阅读位置
地址、权限与边界检查第 1~3 节
检测覆盖与 lockdown第 4~5 节
seccomp、分配防护与观测第 6~8 节
加固机制分别作用于不同阶段 可达攻击面 权限 / seccomp / lockdown 错误发生 边界检查 / sanitizer 利用条件 KASLR / 只读与不可执行 后续影响 清零 / 故障处置 同一漏洞可能受到多层约束,但任何一层都不是完整修复 构建支持、启动启用和运行时有效,要分别验证。

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 当作这种绑定。

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,再结合运行状态判断。配置里有能力不代表启动时采用它;某个状态文件不可见也可能只是权限或命名空间造成的观测限制。