Heap buffer overflow in AudioData::CopyTo due to missing CheckedInt::isValid() check in WebCodecs
Categories
(Core :: Audio/Video: Web Codecs, defect, P2)
Tracking
()
People
(Reporter: lorenzoauto311, Assigned: chunmin)
References
Details
(4 keywords, Whiteboard: [adv-main149+][adv-ESR140.9+])
Attachments
(3 files)
User Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36
Steps to reproduce:
Heap buffer overflow in AudioData::CopyTo — RCE from web content on Release builds
Affected: All Firefox desktop Release builds with WebCodecs (130+). NOT affected on Nightly/Debug (MOZ_DIAGNOSTIC_ASSERT catches it).
File: dom/media/webcodecs/AudioData.cpp, line 571
The CopyTo method uses CheckedInt<uint32_t> to compute the copy length but calls .value() WITHOUT checking .isValid() first. In Release/Late Beta builds, MOZ_DIAGNOSTIC_ASSERT is a no-op (moz.configure:150-158), so .value() silently returns the truncated overflow result.
Compare with AllocationSize() at line 392 which does it CORRECTLY with CheckedInt<size_t> and .isValid() check.
Vulnerable code (line 568-571):
uint32_t bytesPerSample = BytesPerSamples(destFormat.value());
CheckedInt<uint32_t> copyLength = bytesPerSample;
copyLength *= copyElementCount; // uint64_t — overflows uint32
if (copyLength.value() > destLength) // BUG: no .isValid() check
Correct code in AllocationSize (line 392-399):
CheckedInt<size_t> bytesPerSample = BytesPerSamples(destFormat.ref());
auto res = bytesPerSample * copyElementCount;
if (res.isValid()) { return res.value(); } // checks isValid() first!
aRv.ThrowRangeError("Allocation size too large");
Steps to reproduce:
- Create AudioData with format='u8', numberOfFrames=0x20000001, numberOfChannels=2 (~1GB source buffer)
- Call copyTo(16-byte ArrayBuffer, { planeIndex: 0, format: 'f32' })
- copyElementCount = 0x40000002 (uint64_t), bytesPerSample = 4 (f32)
- copyLength = CheckedInt<uint32_t>(4) * 0x40000002 = 0x100000008 — overflows uint32 to 8
- In Release: .value() returns 8 (MOZ_DIAGNOSTIC_ASSERT is no-op)
- Bounds check: 8 > 16 = false — CHECK PASSES
- CopySamples (line 412) calls ConvertAudioSamples with RAW POINTERS via aDest.data() — bypasses Span MOZ_RELEASE_ASSERT bounds check
- ConvertAudioSamples (AudioSampleFormat.h:242) writes 0x40000002 float values (~4GB) into 16-byte heap buffer
- HEAP BUFFER OVERFLOW — tab crashes
Minimal PoC:
const src = new ArrayBuffer(0x40000002);
new Uint8Array(src).fill(0x41);
const ad = new AudioData({
format: 'u8',
sampleRate: 48000,
numberOfFrames: 0x20000001,
numberOfChannels: 2,
timestamp: 0,
data: src,
transfer: [src]
});
const dest = new ArrayBuffer(16);
await ad.copyTo(dest, { planeIndex: 0, format: 'f32' });
// TAB CRASHES HERE
Why ConvertAudioSamples bypasses Span bounds checking:
Span::operator[] has MOZ_RELEASE_ASSERT (mfbt/Span.h:764), but the interleaved-to-interleaved path at CopySamples line 412 calls:
ConvertAudioSamples(aSource.data() + offset, aDest.data(), count)
This uses .data() to get raw pointers, completely bypassing Span bounds.
ConvertAudioSamples (AudioSampleFormat.h:242) is a simple loop with raw pointer indexing: aTo[i] = ConvertAudioSample<To>(aFrom[i])
Confirmed crash on:
Firefox Release x86_64-pc-mingw32, built from mozilla-release rev eff69d257e44ce2a29f947baf876e992af8bb9b0
Suggested fix (line 568-571):
// Replace CheckedInt<uint32_t> with CheckedInt<size_t> and add isValid() check:
CheckedInt<size_t> copyLength = BytesPerSamples(destFormat.value());
copyLength *= copyElementCount;
if (!copyLength.isValid() || copyLength.value() > destLength) {
aRv.ThrowRangeError("destination buffer too small for copy"_ns);
return;
}
Impact:
- Heap buffer overflow with attacker-controlled size and partially-controlled data
- Reachable from ANY web page (WebCodecs enabled by default on desktop, no SecureContext required)
- Release-only: masked by MOZ_DIAGNOSTIC_ASSERT in Nightly/Debug
- RCE potential via heap object corruption
Actual results:
Tab crashes immediately. Heap buffer overflow: ConvertAudioSamples (AudioSampleFormat.h:242) writes ~4GB of float data through raw pointers (aTo[i]) into a 16-byte heap buffer. The overflow bypasses Span bounds checking because CopySamples line 412 uses aDest.data() to extract raw pointers before passing them to ConvertAudioSamples.
Expected results:
copyTo should check CheckedInt::isValid() before calling .value() and throw a RangeError on overflow, exactly like AllocationSize() already does correctly at line 392-399 of the same file. The fix is to use CheckedInt<size_t> instead of CheckedInt<uint32_t> and add the isValid() check before the bounds comparison.
Updated•8 months ago
|
Comment 1•8 months ago
|
||
This looks like a dupe of bug 2012791. Can you confirm, chunmin? Should that other patch have been backported to other branches? Thanks.
Thank you for the reference. I can confirm bug 2012791 addresses the same root cause.
However, I want to highlight a critical security concern: bug 2012791 was found on a debug build where MOZ_DIAGNOSTIC_ASSERT catches the overflow as a controlled assertion failure. On Release builds, MOZ_DIAGNOSTIC_ASSERT is a no-op (moz.configure:150-158), which means .value() silently returns the truncated result.
The consequence on Release is NOT just an assertion failure — it is a heap buffer overflow. The truncated copyLength (e.g., 8) passes the bounds check, and then ConvertAudioSamples (AudioSampleFormat.h:242) writes gigabytes of data through raw pointers into a small heap buffer. This is exploitable for remote code execution from any web page, since WebCodecs is enabled by default on desktop without SecureContext.
The fix from bug 2012791 landed on mozilla-central (Firefox 149) but has NOT been backported to Release. I confirmed the crash on the current Firefox Release build (mozilla-release rev eff69d257e44). All current Firefox Release desktop users are exposed.
I would recommend,
- Backporting the fix from bug 2012791 to Release and Beta channels urgently
- Evaluating this as a security issue (sec-critical) given the heap overflow impact on Release builds
I discovered this independently through source code audit and confirmed the crash on Release. Happy to provide additional information.
Comment 3•8 months ago
|
||
Yeah, this is kind of a strange situation. It is actually fixed on beta now, but it does still need to be fixed on ESR. Release is probably wontfix because of ESR timing but I'll leave it affected for now.
Comment 4•8 months ago
|
||
Well, for bounty and CVE tracking purposes, let's leave this as a separate bug.
I've locally confirmed that on Nightly with bug 2012791 backed out and the CheckedInt DIAGNOSTIC_ASSERT removed (which should simulate the situation on esr140 and 148) you do hit a buffer overflow with the given test case.
Comment 5•8 months ago
|
||
Comment 6•8 months ago
|
||
[Tracking Requested - why for this release]: We fixed bug 2012791 on m-c without realizing it was a security issue, so we should try to get the fix into the next ESR140. I'm not sure if this is in or out of scope for a dot release fix so I left it affected for now.
Comment 7•8 months ago
|
||
chunmin: maybe just nominate the patch in bug 2012791 for uplift to ESR140 and don't mention that it is a sec issue in the bug? I'm not sure.
Comment 8•8 months ago
|
||
chunmin: maybe just nominate the patch in bug 2012791 for uplift to ESR140 and don't mention that it is a sec issue in the bug? I'm not sure.
| Assignee | ||
Comment 9•8 months ago
•
|
||
esr140 uplift patch is here: https://phabricator.services.mozilla.com/D285612. The uplift request is submitted via https://lando.moz.tools/D285612/
Updated•8 months ago
|
Updated•8 months ago
|
| Assignee | ||
Comment 10•8 months ago
•
|
||
Since this is duplicate of bug 2012791 and D285612 has been uplifted, should we close this bug?
Comment 11•8 months ago
|
||
Yes, thanks.
Updated•8 months ago
|
Updated•8 months ago
|
Updated•7 months ago
|
Updated•7 months ago
|
Updated•7 months ago
|
Comment 12•7 months ago
|
||
Although this is a dupe of a bug we found and fixed a month earlier, that bug was not identified as a security bug and we would not have released the fix on ESR-140 without this report. On that ground we are awarding a bug bounty.
Updated•7 months ago
|
Comment 13•3 months ago
|
||
Updated•3 months ago
|
Description
•