Show Notes

88 - Chrome Exploits and a Firefox Update Bug

There was a heap overflow due to a signedness issue in the code processing update MAR packages. This vulnerability happened after the package’s signature was verified.

The Problem was that for mar_insert_item the namelen variaible (the length of the name character array) was a signed int that passed into a memcpy call to copy namelen+1 bytes from name. Unfortunately, memcpy expects an unsigned long value, so the integer will be sign extended to the appropiate size. Normally this wouldn’t be a problem but by providing a name/namelen of INT_MAX length, the +1 will overflow into the sign bit so when extended on the way to memcpy it will become a value significantly larger than expected. Resulting in a memcpy well beyond the expected bounds.

First a bit of background terminology as I understand it. Not being familiar with v8 there are likely some subtleties I am missing.

map - Maps in the context of v8 represent the memory layout of an object and are used when optimizing property access code. Multiple objects can share the same map if they have the same memory layout.

When the memory layout of an object changes such as by assigning a value to a new property then a transition is added to the map. When there is a transition it is considered an unstable map, and a stable map is a map without any transition. If, when the new property is added, a transition already exists (so the map was already unstable) the transition will be followed and it will become a stable map.

And so, with this in mind, a lot of optimized code will depend on the fact that a map cannot change without either reassigning a variaible (such as by giving it a new map all together) or by making the map unstable.

Which is where we hit the bug. When Turbofan optimizes code for storing global properties (global vars are properties of the global object) with the kConstantType attribute it will deoptimize when the property is reassigned through the generic path, or if the inserted CheckMaps call fails which ensures the map is the expected map.

Specifically if optimized while the expected map is unstable, then a DependOnStableMap will not be used to trigger a deoptimization when the map becomes unstable.

This allows for an interesting type confusion using two functions optimized at different times relying on different maps of the same object:

  1. Optimize a store/assignment on the global property while its map is unstable.
    function store(y) {
      x = y;
  2. Add a new property to that global so that it recieves a new map.
    x.newProp = 1
  3. Optimize a load/access on the newly created property. This optimized load will be compiled assuming that x has the new map with newProp as a property.
    function load() {
      return x.newProp;
  4. Use the optimized store to store an object with the old map back to the global property (x in the example). This won’t cause a deoptimization because its the expected map type.
  5. The next time the optimized load is used, there will be a type confusion.


Exploitation took advantage of creating a confusion between two different types of Javascript arrays, one of SMI objects (elements are 4 bytes) and a double array (elements are 8 bytes). This give relative out-of-bounds read and write primitives and the address calculations for each index will be off. Once the read and write primitives are obtained a known technique to disable W^X was used for code execution.

There is a use-after-free on Chrome for Android when fetching credit card details to autofill. This vulnerability does require the victim have credit card details saved by Chrome.

It all centralizes around the Java class InternalAuthenticator a method is called on this class (which one depends on the action being performed) that will store a callback to the native internal authenticator as a Java lambda. this native internal authenticator will normally hold a shared reference to the Java InternalAuthenticator and destroy it when it is destroyed. The problem is that the Java lambda will also hold a reference to the InternalAuthenticator.

So even once the native authenticator has been freed and the Java lambda will be keeping the InternalAuthenicator alive with its pointer to the native internal one pointing to a freed object. When the callback is finally invoked, a use-after-free occures.