Coordinated Disclosure Timeline

Summary

Type confusion in the Chrome renderer reachable from a malicious website.

Product

Chromium

Tested Version

Tested on: Chrome version: d8 main branch commit de4e492 and Chrome 109.0.5414.119

Details

Issue: Type confusion in v8 value serializer (GHSL-2023-023)

When deserializing Javascript objects, the function ReadJSObjectProperties[1] will try to create a map using existing transitions:

Maybe<uint32_t> ValueDeserializer::ReadJSObjectProperties(
    Handle<JSObject> object, SerializationTag end_tag,
    bool can_use_transitions) {
  ...
  // Fast path (following map transitions).
  if (can_use_transitions) {
    ...
    while (transitioning) {
      ...
      } else {
        ...
        if (key->IsString(isolate_)) {
          key =
              isolate_->factory()->InternalizeString(Handle<String>::cast(key));
          // Don't reuse |transitions| because it could be stale.
          transitioning = TransitionsAccessor(isolate_, *map)
                              .FindTransitionToField(Handle<String>::cast(key))
                              .ToHandle(&target);                                  //<------ 1.
        } else {
          transitioning = false;
        }
      }

      // Read the value that corresponds to it.
      Handle<Object> value;
      if (!ReadObject().ToHandle(&value)) return Nothing<uint32_t>();             //<------- 2.

      // If still transitioning and the value fits the field representation
      // (though generalization may be required), store the property value so
      // that we can copy them all at once. Otherwise, stop transitioning.
      if (transitioning) {
        // Deserializaton of |value| might have deprecated current |target|,
        // ensure we are working with the up-to-date version.
        target = Map::Update(isolate_, target);                                  //<-------- 3.
        ...

      CommitProperties(object, map, properties);                                 //<-------- 4.
      num_properties = static_cast<uint32_t>(properties.size());

At (1), a the transitiion tree is searched to find suitable transition as the new object map. Then at (2), the value of the property is read, which may involve new object creation. This, in particular, may deprecate the target map and change the structure of the transition tree. To take this potential change into account, the target map is updated in (3), as the comment suggests.

The problem is that, updating a deprecated map may result in it being normalized into a dictionary map, while the subsequence code in (4) assumes the map has fast properties. By crafting an object such that:

  1. At (2), the target map becomes deprecated after the value of the property is serialized
  2. At (3), the migration causes target to become a dictionary map

A type confusion between fast properties and dictionary properties will be created. This, for example, can be done as follows:

  1. Create two objects with the following structure:
    obj = {a : 1};
    obj.a1 = 2;
    obj.c = {};
    

This object, when deserialized, creates the following transitions in the target context:

{a : SMI} -> {a: SMI, a1 : SMI} -> {a: SMI, a1: SMI, c: Tagged}

Next create an array of objects as follows. The first element of the array is:

arr0 = {a : 1};
arr0.a1 = 1.1;

and the rest of the elements are of the following structures:

arr_i = {a : 1};
arr_i.a1 = 1;
arr_i['b' + i] = 2;

Then create a message object as an array:

[obj0, obj1]

where obj1 is:

obj1 = {a : 1};
obj1.a1 = 2;
obj1.c = arr;

and arr contains the elements arr0,...,arr_i.

When this object is deserialized, obj0 will be deserialized first, which will create the transitions:

{a : SMI} -> {a: SMI, a1 : SMI} -> {a: SMI, a1: SMI, c: Tagged}

in the target context. Then, when obj1 is deserialized, all of its maps will be found via these transitions. So when the last field, c is deserialized in obj1, transitioning[2] will be set to true. After this, ReadObject is called to deserialize the value of c, which is the array arr. When the first element in arr, arr0 is deserialized, the target map becomes deprecated due to the incompatible representation in a1 (from SMI to double). This removes the transition to c from the transition tree. After this, the objects arr_i are deserialized, which will insert new transitions from a1 to bi in the new map. By controlling the number of these new transitions, it is possible to create a situation such that, when the target map is updated and ConstructNewMap[3] is called, the split_map, which is the new map containing a, a1 and all the transitions to the bi, cannot have anymore transitions inserted. This then causes the updated map to become normalized and turned into a dictionary map:

MapUpdater::State MapUpdater::ConstructNewMap() {
  ...
  if (maybe_transition.is_null() &&
      !TransitionsAccessor::CanHaveMoreTransitions(isolate_, split_map)) {
    return Normalize("Normalize_CantHaveMoreTransitions");
  }

  1. https://source.chromium.org/chromium/chromium/src/+/ed7dcb0b9e64a3e72beff2bd0d358398f959e5d2:v8/src/objects/value-serializer.cc;drc=2a7fa9b6065bba9cbe273ddc4f5e5a4a51bb450d;l=2407
  2. https://source.chromium.org/chromium/chromium/src/+/ed7dcb0b9e64a3e72beff2bd0d358398f959e5d2:v8/src/objects/value-serializer.cc;l=2455;drc=2a7fa9b6065bba9cbe273ddc4f5e5a4a51bb450d
  3. https://source.chromium.org/chromium/chromium/src/+/ed7dcb0b9e64a3e72beff2bd0d358398f959e5d2:v8/src/objects/map-updater.cc;l=275

Impact

The causes type confusion in the Chrome renderer reachable from a malicious website.

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-2023-023 in any communication regarding this issue.