OpenBSD 7.9被37岁老漏洞拉下马,Calif研究员公开本地提权链绕过KARL

导语:安全公司Calif 2026年10月16日发布了一份针对OpenBSD 7.9的本地提权研究——一条由”1988年BSD源码遗留的16位计数器溢出”和”内核use-after-free”组成的攻击链,在KARL、SMAP、SMEP全部开启的情况下拿到了root。OpenBSD已经在9月分两次通过 syspatch 推送了补丁。


事件概览

这次被公开的并不是单一漏洞,而是一条由三段组合而成的本地提权链:

  • 入口:shmat(4) 共享内存附加函数里的整数溢出,可追溯到 1988 年的原始 BSD 源码;
  • 中段:与该溢出耦合的引用计数 use-after-free;
  • 收尾:一段独立的内核越界写,被改造成 SMAP/SMEP 绕过原语。

研究员 Andrew Griffiths 在 Calif 的技术博客《Big Fish: Exploiting a 37-Year-Old Kernel Bug in OpenBSD》中写道,整条链只在同一台机器、同一块硬件上反复启动后即可稳定生效——没有任何信息泄露漏洞的辅助,也没有依赖内核符号表。

OpenBSD本地提权链流程图

OpenBSD 已经在两个 errata 里分别修复了前两段问题:

  • Errata 018(2026-09-14)以”可靠性修复”的名义修复整数溢出;
  • Errata 026(2026-09-30)以”安全修复”的名义修复引用计数 use-after-free。

两者都通过 syspatch 分发,覆盖所有架构,OpenBSD 7.8 也回溯获得了 Errata 018。截至发稿,这两个问题均未分配 CVE,也未见野外利用报告。


一个三十七岁的计数器

这次故事的源头是 SysV 共享内存的一个计数器 shm_nattch。在 1988 年的 BSD 源码里,它被定义为有符号 16 位 short,最大值为 32767。OpenBSD 直接继承了这个类型定义,从未扩展过宽度。

Calif 的 PoC 走了一条很直白的路径:映射 127 个共享内存段,fork 259 次,让父进程和子进程各自记录附加关系,反复叠加之后,shm_nattch 越过 32767 翻转到一个很大的负数(实测约为 −32643)。此时内核仍然认为自己”还在用”这部分内存,而引用计数却告诉它”已经没人用了”——一次典型的引用计数 use-after-free,触发位置就是一个共享内存段被销毁的 race。

后续步骤在 Griffiths 的 writeup 里写得很细致:

  • 通过越界写改写内核中的多播成员计数器;
  • 等到对应 socket 关闭时,内核执行 decrement,这一改写作用在 page-table entry 上,直接把一段用户态内存映射改成 supervisor 可访问;
  • 这一步同时绕过了 SMAP(管理模式下也不能访问用户页)和 SMEP(管理模式不能跳转到用户页);
  • 接下来在内核一处已知地址写入新的函数指针,劫持控制流。

整条链没有调用任何 root-only 的 sysctl 或 proc 接口,也没有读 /dev/kmem——一个普通登录用户即可走完全部步骤。


KARL 为什么失效

OpenBSD 引以为豪的 KARL(Kernel Address Randomized Linker)会在每次启动时重新链接内核二进制,把函数和数据地址都做随机化处理。问题是:

“While OpenBSD’s KARL relinks the kernel binary to randomize both function and data addresses, the kernel’s memory allocators remain deterministic.” —— Andrew Griffiths

也就是说,KARL 负责把”代码长什么样”打乱,但分配器把代码放到哪这件事仍然是确定的。具体而言:

  • 内核的 boot-time 分配器没有基地址随机化;
  • 同样常用的 best-fit 分配器也缺少运行时随机化;
  • 早期启动时分配的关键结构——例如 lo0、enc0 这两个网络接口的 ioctl handler——在同一台机器上每次重启都落在相同的虚拟地址。

这正是利用链最后一步的依赖:不需要泄露任何指针,直接覆盖一个早就知道会落在哪里的函数指针。这也是为什么这条链可以在没有信息泄露漏洞的情况下端到端跑通。


补丁之外,Calif 给出的四类加固

Calif 没有停在”披露两个补丁”上,而是在同一篇 writeup 里列出了四类系统性加固,目标是消除这条链所依赖的每一类原语,而不只是堵当前这两个漏洞:

  1. safe-unlinking:在双向链表 unlink 之前检查链表不变量,违反就直接 abort 或 panic。FreeBSD 和 NetBSD 的 sys/queue.h 已有实现,OpenBSD 缺这一层。
  2. PTE 自指映射随机化:用 .openbsd.randomdata 段里的随机字节在 PML4 中挑选一个槽位,让递归页表基址每次启动都不同。这样 PoC 里”把特定内存映射改 supervisor 可访问”的那一页落点就不再固定。
  3. 内核分配器随机化:既可以做基地址随机化(代价基本为零,因为间隙之后还能复用),也可以做运行时分配顺序随机化(有一定性能开销,但能彻底打破”同一台机器地址可预测”)。
  4. 编译器层面的边界检查:Calif 已经私下向 FreeBSD 报告了 21 个数组边界可被越界写的案例,阵列边界 sanitizer(属于 UBSan 的一支)在 release 构建里开启即可拦截这一类问题。
OpenBSD内核加固建议四象限图

安全公司 Calif 的定位很明确:他们日常做的就是把研究挖深、把补丁一起推给上游,这次对 OpenBSD 的报告和辅助工作只是其中一个切片——他们最近公开的研究还包括 CVE-2026-86950(The Great Glyph Grift)、OEMpocalypse Now(从 Android untrusted app 到 root 的通用利用策略)等。


现实意义

这条 PoC 给出三点具体提醒:

  • “代码年龄”和”暴露面”不总成正比。1988 年定义的 short 类型,在 64 位、内核 ASLR、KARL 全面铺开的 2026 年仍然能成为提权链的入口。类型宽度迁移这种”看上去无趣”的内务工作,才是真正消除一整类问题的杠杆。
  • KASLR / KARL 这种”对二进制动手”的方案,在确定性分配器面前仍有盲区。只要内核里存在任何”地址可预测的代码指针”,整条 ASLR 防线就从那一处被绕过。
  • errata 018 标的是”可靠性”,不是”安全”,但它就是整条提权链的入口。系统管理员不能只看 security 标签,该打的可靠性补丁也得打。

对于当前在用 OpenBSD 7.8 / 7.9 的环境,两条 errata 都应该立刻应用;长期来看,跟进 Calif 列出的强化方案,会比单独修一个 use-after-free 更有价值。

© 版权声明
THE END
喜欢就支持一下吧
点赞8 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容