Chrome V8 issue 1486342浅析

admin 2024年7月7日00:25:58评论11 views字数 7192阅读23分58秒阅读模式
前言
首先,这是一个issue,不是一个漏洞。但不排除它可能可以被利用,通过issue(https://issues.chromium.org/issues/40282853)修复者的邮件回复可以印证这一观点。虽然它不是一个漏洞,但弄清它的前因后果,对理解turbofan的sea of nodes以及编译过程有极大的帮助,进一步有助于代码审计去发现新的漏洞、维护v8及各类浏览器的安全。接下来我将从这个思路出发,陆续分析多个issue,加深对v8的理解,以期发现新的安全问题。
这是一个系列文章,本文是第五篇。
◆第一篇:chrome v8漏洞CVE-2021-30632浅析(https://www.freebuf.com/vuls/394933.html)
◆第二篇:chrome v8漏洞CVE-2021-37975浅析(https://www.freebuf.com/vuls/400324.html)
◆第三篇:chrome v8漏洞CVE-2023-3420浅析(https://www.freebuf.com/vuls/401044.html)
◆第四篇:chrome v8漏洞CVE-2020-16040浅析(https://www.freebuf.com/vuls/402175.html)
正文

POC


编译v8


# 推荐香港服务器,可以避免网络问题导致的编译失败。 git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git export PATH=/path/to/depot_tools:$PATH mkdir ~/v8 cd ~/v8 fetch v8 cd v8 # 补丁前一笔提交 git checkout 24861cbefe4 gclient sync alias gm=~/v8/tools/dev/gm.py gm x64.release gm x64.debug # test ./out/x64.release/d8 --help


运行POC


// test.js const o13 = { "maxByteLength": 5368789, }; const v14 = new ArrayBuffer(129, o13); const v16 = new Uint16Array(v14); function f3(param) { for (let i = 0; i < 5; i++) { try {"resize".includes(v14); } catch (e) {} v14.resize(3.0, ..."resize", ...v16); } let f = function() { return param; } } %PrepareFunctionForOptimization(f3); f3(); %OptimizeFunctionOnNextCall(f3); f3(); // 运行./out/x64.deubg/d8 --allow-natives-syntax test.js,将会得到崩溃堆栈



背景

sea of nodes


什么是sea of nodes?
1.Turbofan用它来表示js程序。
2.它由节点和边组成。一个节点代表一个compute。一条边代表control、value或者effect。Chrome V8 issue 1486342浅析
◆compute可以表示:常量、参数、运算符、内存操作、函数调用等。
◆control表示控制流。
◆value表示值依赖,如图"+"这个compute值依赖"x"和"3"compute。
◆effect表示内存操作的先后依赖顺序。比如"i++"涉及load和store操作,store操作effect依赖于load操作,即:先执行load,然后执行+1,最后再执行store操作。
  1. sea of nodes包含了CFG骨架,但不仅仅是CFG。
2.节点可以有若干输入和输出,输入和输出均为边。
Chrome V8 issue 1486342浅析
◆上图为函数 function get_number(x) { return x > 0 ? 1 : 0;} 的sea of nodes。

◆return有4个输入依赖,分别为:
a. NumberConstant_0节点:value依赖。表示return的第一个固定参数,含义暂时不介绍。
b. Phi节点:value依赖。表示返回的值。Phi节点表示一个未决的值,这里是0或者1。
c. SpeculativeNumberLessThan节点:effect依赖。表示return的内存操作需要在SpeculativeNumberLessThan之后执行,SpeculativeNumberLessThan表示x和0的大小判断操作。
d. Merge节点:control依赖。表示return的执行需要在Merge之后。Merge节点表示iftrue和iffalse分支节点的结合处。
◆return 有1个输出,为control边。它是End节点的control输入。表示return执行完之后,就执行End。
1.turbofan的编译是基于节点的。根据节点及其输入输出,结合编译优化策略,对节点及其输入输出进行调整和替换,从而实现了编译。
2.编译优化分为下面几个阶段,每个阶段又涉及不同的编译优化选项。其本质就是对节点组成的图(sea of nodes)进行修改。
◆TurboFan各个编译阶段
Chrome V8 issue 1486342浅析
◆Inlining阶段的各个优化选项,见pipeline.cc
Chrome V8 issue 1486342浅析
1.查看sea of nodes
function get_number(x) { return x > 0 ? 1 : 0; } // 多次执行get_number,触发优化 for (var i = 0; i < 20000; i++) { get_number(3); } get_number(3); /* ./out/x64.deubg/d8 --allow-natives-syntax --trace-turbo test.js --trace-turbo 将生成turbo-get_number-1.json文件, v8提供了一个在线查看工具:https://v8.github.io/tools/head/turbolizer/index.html,加载turbo-get_number-1.json 可以查看sea of nodes */

结合源码理解TurboFan编译过程


以函数get_number为例,来观察turbofan Inlining阶段 CommonOperatorReducer优化策略。

function get_number(x) { return x > 0 ? 1 : 0; }
优化前它的sea of nodes图如下:Chrome V8 issue 1486342浅析

优化后如下:Chrome V8 issue 1486342浅析

对比优化前后,可以发现是将merge节点(21, 25)删除,将return(27)删除。新建两个return节点(32,33),分别采用ifture和iffalse的输出作为control输入,同时输出control边到end。接下来看代码。

// common-operator-reducer.cc // ReduceReturn 优化return 节点 Reduction CommonOperatorReducer::ReduceReturn(Node* node) { ... // return节点的effect输入 Node* effect = NodeProperties::GetEffectInput(node); // return节点的第一个value输入 Node* pop_count = NodeProperties::GetValueInput(node, 0); // return节点的第二个value输入,get_number函数里它为phi(1|0) Node* value = NodeProperties::GetValueInput(node, 1); // control输入 Node* control = NodeProperties::GetControlInput(node); if (value->opcode() == IrOpcode::kPhi && NodeProperties::GetControlInput(value) == control && control->opcode() == IrOpcode::kMerge) { /* 3个条件均满足,参考sea of nodes图 1. value类型为phi 2. value的control输入 跟 return的control输入 是同一个节点 3. control输入是一个merge节点 */ // control为merge节点(25),control_inputs代表了merge节点的两个输入:iftrue, iffalse Node::Inputs control_inputs = control->inputs(); Node::Inputs value_inputs = value->inputs(); DCHECK_NE(0, control_inputs.count()); DCHECK_EQ(control_inputs.count(), value_inputs.count() - 1); DCHECK_EQ(IrOpcode::kEnd, graph()->end()->opcode()); DCHECK_NE(0, graph()->end()->InputCount()); // control作为node和value的输入,满足 // value作为node的输入,满足 if (control->OwnedBy(node, value) && value->OwnedBy(node)) { // control_inputs: iftrue, iffalse两个分支 for (int i = 0; i < control_inputs.count(); ++i) { // newNode(操作码,pop_count, value_input, effect_input, control_input) // newNode函数参数如上,使用newNode创建两个新的return节点 Node* ret = graph()->NewNode(node->op(), pop_count, value_inputs[i], effect, control_inputs[i]); // 将新建return节点的输出control指向end节点 MergeControlToEnd(graph(), common(), ret); } // merge节点丢弃 Replace(control, dead()); // 原来的return节点丢弃 return Replace(dead()); } ... } return NoChange(); }

结合sea of nodes图,看上述代码注释,可以更加清晰的了解。


issue分析
先来看issue的patch,它位于src/compiler/js-call-reducer.cc文件ReduceArrayIterator函数,属于Inlining阶段call_reducer优化选项,当出现数组迭代器指令的时候,对这个内置函数进行优化。
Chrome V8 issue 1486342浅析
patch逻辑很简单,把RelaxControls(node);替换成ReplaceWithValue(node, node, node, control);
RelaxControls代码如下:
void RelaxControls(Node* node) { ReplaceWithValue(node, node, node, nullptr); } /* 也就是说patch是将 ReplaceWithValue(node, node, node, nullptr); 替换成 ReplaceWithValue(node, node, node, control); */

我们再来看看ReplaceWithValue函数,它的代码如下:

void ReplaceWithValue(Node* node, Node* value, Node* effect = nullptr, Node* control = nullptr) { DCHECK_NOT_NULL(editor_); editor_->ReplaceWithValue(node, value, effect, control); }

这个函数的作用是修正node输出的边。参数分别node本身,node的value输出、effect输出、control输出。当effect或者control传递nullptr的时候,将不会发生替换。假设node A和node B存在control的边,这条边为node A的输出,B的输入。

此时执行ReplaceWithValue(nodeA, nodeA, nodeA, nodeC),那么A的输出将不再为B的输入,而nodeC和nodeB之间将建立边,为C的输出,B的输入。

再回过头去看补丁,补丁前的代码将node的输出设置为空(即不变),而应该是control。结合补丁前面的代码查看:

Reduction JSCallReducer::ReduceArrayIterator(Node* node, ArrayIteratorKind array_kind, IterationKind iteration_kind) { ... if (array_kind == ArrayIteratorKind::kTypedArray) { // Make sure we deopt when the JSArrayBuffer is detached. if (!dependencies()->DependOnArrayBufferDetachingProtector()) { CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } // 在经过两个if的条件下,进入到这里 JSCallReducerAssembler a(this, node); a.CheckIfTypedArrayWasDetached( TNode<JSTypedArray>::UncheckedCast(receiver), std::move(elements_kinds), p.feedback()); // 此时修改了contorl和effect,它们将作为node节点新的输出 <--- label 1 std::tie(effect, control) = ReleaseEffectAndControlFromAssembler(&a); } } /* patch前: RelaxControls(node); 即为ReplaceWithValue(node, node, node, nullptr); 也就是说control输出不发生变化。"label 1" 处,control被赋予了新的节点,本意是需要修改当前节点的control输出,但patch前RelaxControls(node);没有修改conctrol输出。 patch后: ReplaceWithValue(node, node, node, control); 使用赋值过的"control变量"作为control输出,符合预期。 */ RelaxControls(node); // 当前节点的输入依次换为receiver、context、effect、control node->ReplaceInput(0, receiver); node->ReplaceInput(1, context); node->ReplaceInput(2, effect); node->ReplaceInput(3, control); node->TrimInputCount(4); // 修改当前节点的操作符为CreateArrayIterator NodeProperties::ChangeOp(node, javascript()->CreateArrayIterator(iteration_kind)); return Changed(node); }

对比patch前后的--trace-turbo得到的json如下:


Chrome V8 issue 1486342浅析

通过调试也可以打印出control id如下:
Chrome V8 issue 1486342浅析
应该由397指向358,而非350。而397即为"label 1"处代码新得到的control id。

通过对比sea of nodes也可以得到结论:

patch前:
Chrome V8 issue 1486342浅析

350指向了358,而397的control 输出为空。

patch后:
Chrome V8 issue 1486342浅析
397正确指向了358。
思考
为什么说这个issue有非常大的可能可以被利用?此处issue导致的最后结果是程序的控制流不符合预期,这可能导致修改程序逻辑,跳过sea of nodes的某些必须要执行的节点,而这些节点如果是checkMap的话,那么将导致Map不被检查,将导致优化函数里面对数据结构的假设不再成立,从而实现类型混淆,进一步实现漏洞。那么如何构造出这样一个精巧的sea of nodes,让它跳过checkMap节点呢?这是一个问题。


参考


TurboFan JIT Design(https://docs.google.com/presentation/d/1sOEF4MlF7LeO7uq-uThJSulJlTh--wgLeaVibsbb3tc/edit#slide=id.p)

chromium issues(https://issues.chromium.org/issues/40282853)

Chrome V8 issue 1486342浅析

看雪ID:coolboyme

https://bbs.kanxue.com/user-home-492418.htm

*本文为看雪论坛优秀文章,由 coolboyme 原创,转载请注明来自看雪社区

原文始发于微信公众号(看雪学苑):Chrome V8 issue 1486342浅析

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年7月7日00:25:58
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Chrome V8 issue 1486342浅析https://cn-sec.com/archives/2926949.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息