CVE-2019-0609: Edge浏览器use-after-unmap漏洞深度分析
漏洞概述
CVE-2019-0609是Microsoft Edge浏览器中ChakraCore JavaScript引擎的一个安全漏洞,属于use-after-unmap类型。该漏洞源于JavaScript引擎在处理栈函数boxing时的缺陷,可能导致内存破坏和远程代码执行。
背景知识
JavaScript引擎中的函数分配
在JavaScript中,函数是第一类对象,可以像其他值一样被传递和返回。当函数在全局代码或其他函数中声明时,JavaScript引擎通常会在栈上分配这些函数对象。但在某些情况下,需要将这些函数移动到堆中:
- 当函数被多个指针引用时
- 当函数的作用域需要逃逸出原始声明范围时(如函数返回内部声明的函数)
这种从栈到堆的移动过程称为"boxing",类似于Java中的装箱(boxing)概念。
漏洞详细分析
漏洞触发机制
漏洞的核心在于ChakraCore引擎对栈函数的boxing处理不当。当满足以下条件时,会导致use-after-unmap漏洞:
- 函数在栈上分配
- 函数需要被box(如被返回或跨作用域引用)
- 函数的栈分配在私有区域而非原生栈
关键代码分析
问题代码路径
在StackScriptFunction::BoxState::Box函数中,存在以下关键断言:
StackScriptFunction *stackFunction = interpreterFrame->GetStackNestedFunction(i);
ScriptFunction *boxedFunction = this->BoxStackFunction(stackFunction);
Assert(stackFunction->boxedScriptFunction == boxedFunction);
this->UpdateFrameDisplay(stackFunction);
当断言失败时,表明boxing过程出现问题:stackFunction->boxedScriptFunction为nullptr,而boxedFunction指向有效对象。
Boxing失败原因
BoxStackFunction函数的实现如下:
ScriptFunction * StackScriptFunction::BoxState::BoxStackFunction(ScriptFunction * scriptFunction)
{
// Box the frame display first, which may in turn box the function
FrameDisplay * frameDisplay = scriptFunction->GetEnvironment();
FrameDisplay * boxedFrameDisplay = BoxFrameDisplay(frameDisplay);
if (!ThreadContext::IsOnStack(scriptFunction))
{
return scriptFunction;
}
// ...
boxedFunction = ScriptFunction::OP_NewScFunc(boxedFrameDisplay,
reinterpret_cast<FunctionInfoPtrPtr>(&functionInfo));
stackFunction->boxedScriptFunction = boxedFunction;
问题在于ThreadContext::IsOnStack检查:当函数分配在私有区域而非原生栈时,该检查会返回false,导致函数未被正确box。
根本原因
在InterpreterStackFrame::InterpreterHelper中,引擎会根据本地变量数量决定分配方式:
if (varAllocCount > InterpreterStackFrame::LocalsThreshold)
当本地变量超过阈值(LocalsThreshold)时,引擎会分配私有区域作为栈框架。然而:
ThreadContext::IsOnStack不将私有区域视为栈框架- 导致需要box的函数未被正确box
- 函数返回后,栈被unmap,但未box的函数仍指向原栈地址
- 后续访问导致use-after-unmap
补丁分析
ChakraCore 1.11.7中修复了该漏洞,关键补丁如下:
if (stackVarAllocCount != 0)
{
size_t stackVarSizeInBytes = stackVarAllocCount * sizeof(Var);
PROBE_STACK_PARTIAL_INITIALIZED_INTERPRETER_FRAME(GetScriptContext(),
Js::Constants::MinStackInterpreter + stackVarSizeInBytes);
stackAllocation = (Var*)_alloca(stackVarSizeInBytes);
}
补丁的主要改进:
- 显式计算
stackVarAllocCount作为boxing决策依据 - 使用
_alloca确保stackScriptFunctions在堆中分配 - 避免了私有区域分配导致的boxing失败
漏洞利用(PoC)
以下是触发该漏洞的概念验证代码:
function test() {
function a() {
function d() {
let e = function() {};
return e;
}
function b() {
let fun_d = [d];
return fun_d;
}
// 使用足够大的对象触发私有区域分配
var obj = [/* big-size object */];
return b();
}
return a();
}
var f = test();
function test1() {
// 重新分配内存以触发use-after-unmap
var obj = [/* big-size object */];
print(f[0]); // 未box的函数d仍指向栈地址
}
test1();
漏洞影响与缓解
影响
- 可能导致远程代码执行
- 影响Microsoft Edge浏览器(使用ChakraCore引擎的版本)
- 可结合其他漏洞实现完整攻击链
缓解措施
- 更新到已修复的Edge浏览器版本
- 启用适当的沙箱保护机制
- 禁用不必要的JavaScript功能
总结
CVE-2019-0609展示了JavaScript引擎实现中内存管理的复杂性。该漏洞的关键在于:
- 栈分配策略与boxing逻辑的不一致
- 内存区域检测的不完备性
- 作用域逃逸处理中的边界条件
理解此类漏洞有助于开发者更好地设计安全的内存管理策略,也为安全研究人员提供了分析类似漏洞的参考框架。