Closed Bug 2020422 (CVE-2026-4697) Opened 8 months ago Closed 8 months ago

Heap buffer overflow in AudioData::CopyTo due to missing CheckedInt::isValid() check in WebCodecs

Categories

(Core :: Audio/Video: Web Codecs, defect, P2)

defect

Tracking

()

RESOLVED FIXED
150 Branch
Tracking Status
firefox-esr115 --- disabled
firefox-esr140 149+ fixed
firefox148 --- wontfix
firefox149 + fixed
firefox150 + fixed

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:

  1. Create AudioData with format='u8', numberOfFrames=0x20000001, numberOfChannels=2 (~1GB source buffer)
  2. Call copyTo(16-byte ArrayBuffer, { planeIndex: 0, format: 'f32' })
  3. copyElementCount = 0x40000002 (uint64_t), bytesPerSample = 4 (f32)
  4. copyLength = CheckedInt<uint32_t>(4) * 0x40000002 = 0x100000008 — overflows uint32 to 8
  5. In Release: .value() returns 8 (MOZ_DIAGNOSTIC_ASSERT is no-op)
  6. Bounds check: 8 > 16 = false — CHECK PASSES
  7. CopySamples (line 412) calls ConvertAudioSamples with RAW POINTERS via aDest.data() — bypasses Span MOZ_RELEASE_ASSERT bounds check
  8. ConvertAudioSamples (AudioSampleFormat.h:242) writes 0x40000002 float values (~4GB) into 16-byte heap buffer
  9. 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.

Group: core-security → media-core-security

This looks like a dupe of bug 2012791. Can you confirm, chunmin? Should that other patch have been backported to other branches? Thanks.

Flags: needinfo?(cchang)

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,

  1. Backporting the fix from bug 2012791 to Release and Beta channels urgently
  2. 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.

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.

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.

Status: UNCONFIRMED → NEW
Depends on: 2012791
Ever confirmed: true
Attached file ASan stack —

[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.

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.

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.

Keywords: ai-involved

esr140 uplift patch is here: https://phabricator.services.mozilla.com/D285612. The uplift request is submitted via https://lando.moz.tools/D285612/

Flags: needinfo?(cchang)
Severity: -- → S2
Priority: -- → P2

Since this is duplicate of bug 2012791 and D285612 has been uplifted, should we close this bug?

Yes, thanks.

Assignee: nobody → cchang
Status: NEW → RESOLVED
Closed: 8 months ago
Resolution: --- → FIXED
Group: media-core-security → core-security-release
QA Whiteboard: [sec] [qa-triage-done-c150/b149]
Target Milestone: --- → 150 Branch
Whiteboard: [adv-main149+][adv-ESR140.9+]

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.

Flags: sec-bounty+
Summary: Heap buffer overflow in AudioData::CopyTo due to missing CheckedInt::isValid() check → Heap buffer overflow in AudioData::CopyTo due to missing CheckedInt::isValid() check in WebCodecs
Alias: CVE-2026-4697
Duplicate of this bug: 2056492
Group: core-security-release
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: