A Matching Pair of Use-After-Free Bugs in Chakra asm.js

December 22, 2017 | Simon Zuckerbraun

This is the final blog in our series of Top 5 interesting cases from 2017. Each of these bugs has some element that sets them apart from the approximately 1,000 advisories released by the program this year. Today’s post details two bugs in Chakra – Microsoft’s JavaScript engine used in the Edge browser – discovered by ZDI researchers that could allow for remote code execution by browsing to a malicious website on an affected browser.

Is a pair of JavaScript bugs called a “brace”? If it isn’t, it should be, and here’s a great example of such a pair. In August of 2017, Zero Day Initiative (ZDI) researchers WanderingGlitch (sometimes known as Jasiel Spelman) and HexKitchen (sometimes known as me) discovered two use-after-free (UAFs) in Chakra that turned out to be mirror-images of one another.

Around that time, we had been spending some time thinking about the possible attack surface introduced by asm.js. The asm.js intermediate programming language is a relatively new feature in JavaScript designed for executing computationally-intensive code at near-native code speeds.

To use asm.js, the script author uses a syntax to declare what is called a “module”. I like to think of an asm.js module as a kind of a virtual machine. Within the module, a highly-constrained subset of JavaScript is permitted; essentially, the only permitted types are fundamental numerical types, and the only permitted operations are primitive operations on those types. The execution engine translates these more or less verbatim into native processor instructions that run within the virtual machine.

Naturally, a virtual machine must be equipped with some RAM. This is termed the “heap” and consists of a JavaScript ArrayBuffer. When instantiating an asm.js module, the calling script provides an ArrayBuffer instance. Script running both inside and outside the module has full access to the contents of the ArrayBuffer.

To make use of the module, script calls one or more functions that the module explicitly “exports” using a special syntax.

Since only fundamental numerical types are permitted within the module, if script running outside the module passes a non-numeric argument value, Chakra must first coerce the value into a numeric type before forwarding it to the callee. WanderingGlitch noticed Chakra performs steps in this order when calling a function exported from a module:

1.     Ensure that the ArrayBuffer serving as the module’s heap has not been freed.
2.     Coerce any non-numeric argument values.
3.     Call the exported function.

Can you see the problem? In step 2, coercion can produce execution of arbitrary JavaScript. The JavaScript that executes during step 2 can free the ArrayBuffer, bypassing the check that was performed too early in step 1. When the exported function begins executing, it operates upon a freed ArrayBuffer.

The PoC looks like this:

PoC for ZDI-17-928

The last line of the PoC calls writeU32, which is a function exported from asmModule. The first argument passed is SoonInt, which is a JavaScript object equipped with a malicious valueOf function. The valueOf function is invoked immediately before entering writeU32, and consequently writeU32 operates upon an ArrayBuffer instance that has already been neutered (its underlying buffer has been freed).

To be precise, this is not a use-after-free condition in the typical sense. By the time the module begins accessing the invalid ArrayBuffer, the memory pointer held by the ArrayBuffer has already been set to NULL. The module can access memory at any 32-bit offset added to this NULL base pointer. In other words, script gains unrestricted access to the lowermost 4GB of the address space.

A Converse Bug

Upon studying the bug found by WanderingGlitch described above, I began to suspect that a second bug might also be present. The vulnerability shown above relates to the mechanism for making a call from normal JavaScript into an asm.js module. There exists a second mechanism, termed the “Foreign Function Interface”, that serves the opposite purpose. It allows code running within an asm.js module to make a call out to normal JavaScript. As one might expect, it allows the module code to receive a return value upon completion. But just as above, there is a potential need for coercion when a value is passed from the general JavaScript environment into the module environment. And, just as above, where coercion takes place there may be opportunity for mischief.

Realizing this, I constructed the following PoC:

PoC for ZDI-17-848

“Foreign” function fun1 neuters the ArrayBuffer before it returns control to the asm.js module. When writeU32 continues execution, it operates upon freed memory.

In this case we have a use-after-free condition in the usual sense. An attacker could add additional script at the end of fun1 that reuses the memory of the freed ArrayBuffer for some new, unrelated allocation. Subsequently, writeU32 could be used to corrupt this new data structure, leading to compromise of the process.

Vendor Response

We disclosed both these vulnerabilities to Microsoft in early September of 2017. Upon release of the following month’s patches, they informed us that the October patch contained a fix for both issues, developed in response to a bug report separate from ours. According to the bulletin acknowledgments, the Microsoft Chakra Team also found these bugs and share credit in reporting them. While ZDI lists WanderingGlitch’s report as ZDI-17-928 and my report as ZDI-17-848, both vulnerabilities were assigned the same CVE – CVE-2017-11812. It appears that this separate report was an internal Microsoft finding.

ChakraCore, Microsoft’s open-source version of Chakra, has also received the patch. Examining the code, I was a bit excited to find that these bug reports had prompted the addition of an entirely new opcode in Chakra’s asm.js implementation. Its name: CheckHeap.

Conclusion

JavaScript engines are a hot spot in browser vulnerability research right now. I discussed some of the reasons in my July 2017 blog post, “Understanding Risk in the Unintended Giant: JavaScript”. At the ZDI, we’re looking forward to continuing our Chakra research in 2018. We invite you to contribute by submitting your own Chakra findings. We have several bugs already in the process, so brace yourselves for upcoming vulnerability disclosures - I’ll be blogging about the details of some of them once they are patched.

You can find me on Twitter at @HexKitchen, and you can find my colleague at @WanderingGlitch. We hope you’ve enjoyed this series of interesting bugs from 2017. See you next year!