OP_ENTER 空指针解引用针对 CVE-2026-86547 的独立概念验证,该漏洞是 mrubyc 的 op_enter() 处理函数中存在的空指针解引用,影响至 4.0.0 版本。
mrubyc 是面向嵌入式系统的轻量级 Ruby 实现。其字节码虚拟机将当前调用帧指针保存在 mrbc_vm.callinfo_tail 中。在顶层,mrbc_vm_begin() 将该指针初始化为 NULL,因为此时尚不存在方法调用帧。
在 release4.0.0 中存在漏洞的 op_enter() 实现里,处理函数在未先检查 vm->callinfo_tail 是否为 NULL 的情况下读取了 callinfo->reg_offset:
mrbc_callinfo *callinfo = vm->callinfo_tail;
int reg_offset = callinfo->reg_offset;
一个精心构造的 .mrb 字节码程序可以将 OP_ENTER 放在顶层,导致解释器在 callinfo_tail 为 NULL 的情况下执行到这段代码,并通过空指针解引用崩溃。
CVE: CVE-2026-86547
类型: CWE-476 — 空指针解引用
影响: 可用性 / 拒绝服务
受影响版本: mrubyc 至 4.0.0
严重程度: 中危,CVSS 6.9(依据公告)
该 PoC 复刻了相关的 mrbc_callinfo 和 mrbc_vm 内存布局,并在不依赖完整 mrubyc 运行时的情况下复现了存在漏洞的内存访问。
它演示了两条路径:
callinfo_tail 为 NULL,且 callinfo->reg_offset 在无保护的情况下被解引用,导致段错误。OP_ENTER,随后进行控制测试,表明有效的调用帧仍可正常工作。该测试框架在漏洞指针上使用了 volatile,并在其描述中使用了 __builtin_trap(),以使复现程序适合基于 sanitizer 的观察。实际存在漏洞的访问是 callinfo->reg_offset 的读取。
gcc -O0 -g -o poc poc.c
./poc
漏洞路径上的预期结果是在以下输出之后发生段错误:
[VULNERABLE PATH] op_enter without NULL guard
vm->callinfo_tail = NULL (top-level frame)
About to dereference NULL...
gcc -O0 -g -fsanitize=address -fno-omit-frame-pointer -o poc-asan poc.c
./poc-asan
sanitizer 应报告在对应于以 NULL mrbc_callinfo 为基址的 reg_offset 字段的地址上发生 SEGV。使用此处采用的结构布局,offsetof(mrbc_callinfo, reg_offset) 为 0x14。
./poc fixed
预期输出包括:
[FIXED PATH] op_enter with NULL guard
[GUARD] top-level OP_ENTER — rejected safely
[control, valid frame] reg_offset = 5
Fixed path: no crash.
原始存在漏洞的处理函数位于 mrubyc release4.0.0 的 src/vm.c 中,约在第 1537 行。相关操作是在将 vm->callinfo_tail 赋值给 callinfo 后,未进行 NULL 检查便直接解引用 callinfo->reg_offset。
研究中引用的修复后源码为提交 4261cf5e5ae5579e3110dab98a04b91c7d919429。
该独立测试框架演示了底层的内存错误。在 mrubyc 本身中,触发需要构造一个 .mrb 字节码文件,其中在顶层、方法定义之外包含 OP_ENTER 指令。如果应用程序加载并执行不受信任的 .mrb 文件,攻击者控制的字节码文件便可使解释器进程崩溃。
这是一种拒绝服务状况。该研究并未声称存在代码执行、信息泄露或超出空指针解引用之外的内存破坏。
src/vm.csrc/vm.cOP_SUPER NULL 检查问题,促成了对 op_enter() 的审计发现过程记录于:I Read Someone Else's Bug Report, Then Found The Same Missing Check In The Next Function Over。
关键的研究观察是,op_super() 在同一个 vm->callinfo_tail 不变量周围缺少 NULL 保护。检查相邻的 op_enter() 处理函数后发现,那里也存在相同的假设,却没有相应的保护。