Commit c2a8e013 authored by Paul Adenot's avatar Paul Adenot Committed by Pier Angelo Vendrame
Browse files

Bug 2014865 - Fix int64_t overflow in TimeUnit::FromSeconds boundary...

Bug 2014865 - Fix int64_t overflow in TimeUnit::FromSeconds boundary condition. r=media-playback-reviewers,alwu

TimeUnit::FromSeconds() had undefined behavior when converting values at the
boundary of int64_t representability. Specifically, when the input value times
the base equals exactly 2^63:

    static_cast<double>(INT64_MAX) rounds UP to 2^63 (INT64_MAX = 2^63-1 cannot be exactly represented as double)
    The check used strict >, so inBase == 2^63 passed the overflow check
    static_cast<int64_t>(std::round(2^63)) is undefined behavior since 2^63 exceeds INT64_MAX, producing INT64_MIN on x86-64
    This created invalid TimeUnits, causing assertion failures in TimeInterval construction (mStart <= mEnd)

The fix changes > to >= to properly catch this boundary case.

Differential Revision: https://phabricator.services.mozilla.com/D282394
parent e19c6d14
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -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
+91 −0
Original line number Diff line number Diff line
<!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>
+1 −0
Original line number Diff line number Diff line
@@ -188,3 +188,4 @@ load 1905231.webm
load 1917627.mp4
skip-if(Android) load audioworkletprocessor-recursion.html
load 2014824.html
load 2014865.html