Software/Scripts Getting RCE in Chrome with incomplete object initialization in the Maglev compiler

Git

Premium
Premium
Регистрация
09.02.2010
Сообщения
270
Реакции
41
Баллы
28
Native language | Родной язык
English
In this post I’ll exploit CVE-2023-4069, a type confusion vulnerability that I reported in July 2023. The vulnerability—which allows remote code execution (RCE) in the renderer sandbox of Chrome by a single visit to a malicious site—is found in , the Javascript engine of Chrome. It was filed as and subsequently fixed in version .

Vulnerabilities like this are often the starting point for a “one-click” exploit, which compromises the victim’s device when they visit a malicious website. What’s 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 “sandbox escape” vulnerability (either another vulnerability in the Chrome browser process or one in the operating system) to compromise Chrome itself or the device.

While many of the most powerful and sophisticated “one-click” 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.

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.

Maglev compiler​


The is a mid-tier JIT compiler used by v8. Compared to the top-tier JIT compiler, , 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’ll find a more optimal tradeoff between compilation time and runtime optimization.

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—but 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 by Benedikt Meurer for more details of how the compilation process works.

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 , Maglev uses an intermediate representation and first transforms bytecodes into SSA (Static Single-Assignment) nodes, which are declared in the file . At the time of writing, the compilation process of Maglev consists mainly of two phases of optimizations: the first phase involves from the SSA nodes, while the second phase consists of .

Object construction in v8​


The bug in this post really has more to do with object constructions than with Maglev, so now I’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 new keyword. When it is called with new, the [URL='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new.target']new.target[/URL] variable exists in the function scope that specifies the function being called with new. In the following case, new.target is the same as the function itself.

Код:
function foo() {
  %DebugPrint(new.target);
}
new foo();  // foo
foo();      // undefined

This, however, is not always the case and new.target may be different from the function itself. For example, in case of a construction via a derived constructor:

Код:
class A {
  constructor() {
    %DebugPrint(new.target);
  }
}

class B extends A {
}

new A();  // A
new B();  // B

Another way to have a different new.target is to use the [URL='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/construct']Reflect.construct[/URL] built-in function:

Код:
Reflect.construct(A, [], B);  // B

The signature of Reflect.construct is as follows, which specifies newTarget as the new.target:

Код:
Reflect.construct(target, argumentsList, newTarget)

The Reflect.construct method sheds some light on the role of new.target in object construction. According to the documentation, target is the constructor that is actually executed to create and initialize an object, while newTarget provides the prototype for the created object. For example, the following creates a Function type object and only Function is called.

Код:
var x = Reflect.construct(Function, [], Array);

This is consistent with construction via class inheritance:

Код:
class A {}

class B extends A {}

var x = new B();
console.log(x.__proto__ == B.prototype);  //<--- true

Although in this case, the derived constructor B does get called. So what is the object that’s actually created? For functions that actually return a value, or for class constructors, the answer is more clear:

Код:
function foo() {return [1,2];}
function bar() {}
var x = Reflect.construct(foo, [], bar); //<--- returns [1,2]

but less so otherwise:

Код:
function foo() {}
function bar() {}
var x = Reflect.construct(foo, [], bar); //<--- returns object {}, instead of undefined

So even if a function does not return an object, using it as target in Reflect.construct still creates a Javascript object. Roughly speaking, object constructions follow these steps: (see, for example, [URL='https://source.chromium.org/chromium/chromium/src/+/0e4c9a3d8b9194c4885a1576fce9e0dc9004a22c:v8/src/builtins/x64/builtins-x64.cc;l=146']Generate_JSConstructStubGeneric[/URL].)

First a default receiver (the this object) is created using [URL='https://source.chromium.org/chromium/chromium/src/+/79c9dc7ddf20ae484798ec359d62aafb09a8c291:v8/src/builtins/builtins-constructor-gen.cc;l=260;bpv=0;bpt=0']FastNewObject[/URL], and then the target function is invoked. If the target function returns an object, then the default receiver is discarded and the return value of target is used as the returned object instead; otherwise, the default receiver is returned.

Default receiver object​


The default receiver object created by FastNewObject is relevant to this bug, so I’ll explain it in a bit more detail. Most Javascript functions contain an internal field, initial_map. This is a Map object that determines the type and the memory layout of the default receiver object created by this function. In v8, Map determines the hidden type of an object, in particular, its memory layout and the storage of its fields. Readers can consult “ ” by Mathias Bynens to get a high-level understanding of object types and maps.

When creating the default receiver object, FastNewObject will try to use the initial_map of new.target (new_target) as the Map for the default receiver:

Код:
TNode ConstructorBuiltinsAssembler::FastNewObject(
    TNode context, TNode target,
    TNode new_target, Label* call_runtime) {
  // Verify that the new target is a JSFunction.
  Label end(this);
  TNode new_target_func =
      HeapObjectToJSFunctionWithPrototypeSlot(new_target, call_runtime);
  ...
  GotoIf(DoesntHaveInstanceType(CAST(initial_map_or_proto), MAP_TYPE),
         call_runtime);
  TNode initial_map = CAST(initial_map_or_proto);
  TNode
 

AI G

Moderator
Команда форума
Регистрация
07.09.2023
Сообщения
786
Реакции
2
Баллы
18
Местоположение
Метагалактика
Сайт
golo.pro
Native language | Родной язык
Русский
Sorry I couldn't contact the ChatGPT think tank :(
 
198 111Темы
635 082Сообщения
3 618 399Пользователи
DimJenНовый пользователь
Верх