High firefox Logic Error 🔧 Commit mapped

Overview

High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impacthigh
DescriptionIncorrect boundary conditions in the Audio/Video: Playback component
ComponentDOM
Bug ClassLogic Error
Tracker2071733
Fix commit51eb2db4754c (firefox) +85/-13
CISA KEVNot listed
CreditedMozilla
Disclosed2026-10-27

Changed Functions

FunctionChangeNotes
if
dom/media/platforms/ffmpeg/FFmpegVideoDecoder.cpp
modified
if
dom/media/platforms/ffmpeg/FFmpegVideoUtils.h
modified
for
dom/media/platforms/ffmpeg/FFmpegVideoUtils.h
modified

Files Changed

  • dom/media/platforms/ffmpeg/FFmpegVideoDecoder.cpp
  • dom/media/platforms/ffmpeg/FFmpegVideoUtils.h
diff --git a/dom/media/platforms/ffmpeg/FFmpegVideoDecoder.cpp b/dom/media/platforms/ffmpeg/FFmpegVideoDecoder.cpp
index 389ffbb0cdc..e5b118371ba 100644
--- a/dom/media/platforms/ffmpeg/FFmpegVideoDecoder.cpp
+++ b/dom/media/platforms/ffmpeg/FFmpegVideoDecoder.cpp
@@ -1342,14 +1342,6 @@ int FFmpegVideoDecoder<LIBAV_VER>::GetVideoBuffer(
     return rv;
   }
 
-  CheckedInt32 dataSize = mLib->av_image_get_buffer_size(
-      aCodecContext->pix_fmt, aCodecContext->coded_width,
-      aCodecContext->coded_height, 32);
-  if (!dataSize.isValid()) {
-    FFMPEG_LOG("Data size overflow!");
-    return AVERROR(EINVAL);
-  }
-
   if (!mImageContainer) {
     FFMPEG_LOG("No Image container!");
     return AVERROR(EINVAL);
@@ -1408,12 +1400,25 @@ int FFmpegVideoDecoder<LIBAV_VER>::GetVideoBuffer(
   auto imageWrapper = MakeRefPtr<ImageBufferWrapper>(std::move(image));
 #  endif
 
+  // The buffer spans to the end of the last plane, not the packed image size.
+  auto planeEnd = [](const layers::MappedYCbCrChannelData& aPlane) {
+    return aPlane.data + aPlane.stride * aPlane.size.height;
+  };
+  const uint8_t* bufferEnd =
+      std::max({planeEnd(mapped.y), planeEnd(mapped.cb), planeEnd(mapped.cr)});
+  const CheckedInt<int> bufferSize =
+      CheckedInt<int>(bufferEnd - aFrame->data[0]);
+  if (!bufferSize.isValid() || bufferSize.value() <= 0) {
+    FFMPEG_LOG("Invalid mapped buffer extent");
+    return AVERROR(EINVAL);
+  }
+
   // This will hold a strong reference to imageWrapper, and the reference will
   // be dropped when ffmpeg tells us that the buffer is no longer needed. Since
   // the wrapper keeps image alive, it will prevent recycling until ffmpeg is
   // done with the buffer.
   aFrame->buf[0] =
-      mLib->av_buffer_create(aFrame->data[0], dataSize.value(),
+      mLib->av_buffer_create(aFrame->data[0], bufferSize.value(),
                              ReleaseVideoBufferWrapper, imageWrapper.get(), 0);
   if (!aFrame->buf[0]) {
     FFMPEG_LOG("Failed to allocate buffer");
@@ -1425,7 +1430,7 @@ int FFmpegVideoDecoder<LIBAV_VER>::GetVideoBuffer(
 
   FFMPEG_LOG("Created av buffer, buf={}, data={}, image={}, sz={}",
              fmt::ptr(aFrame->buf[0]), fmt::ptr(aFrame->data[0]),
-             fmt::ptr(imageWrapperPtr), dataSize.value());
+             fmt::ptr(imageWrapperPtr), bufferSize.value());
   mIsUsingShmemBufferForDecode = Some(true);
   return 0;
 }
@@ -2048,6 +2053,20 @@ MediaResult FFmpegVideoDecoder<LIBAV_VER>::CreateImage(
   b.mPlanes[0].mHeight = mFrame->height;
   SetChromaPlaneGeometryFromAVFormat(b, static_cast<int>(mFrame->format),
                                      mFrame->width, mFrame->height);
+#if LIBAVCODEC_VERSION_MAJOR >= 58
+  if (!FramePlanesFitBackingBuffers(mFrame, b)) {
+    FFMPEG_LOG(
+        "Decoded frame plane layout does not match its buffers: format {} "
+        "{}x{}, Y {}x{} stride {}, CbCr {}x{} stride {}, buffer sizes {} {} {}",
+        mFrame->format, mFrame->width, mFrame->height, b.mPlanes[0].mWidth,
+        b.mPlanes[0].mHeight, b.mPlanes[0].mStride, b.mPlanes[1].mWidth,
+        b.mPlanes[1].mHeight, b.mPlanes[1].mStride, FrameBufferSize(mFrame, 0),
+        FrameBufferSize(mFrame, 1), FrameBufferSize(mFrame, 2));
+    return MediaResult(
+        NS_ERROR_DOM_MEDIA_DECODE_ERR,
+        RESULT_DETAIL("Decoded frame plane layout does not match its buffers"));
+  }
+#endif
   b.mYUVColorSpace = GetFrameColorSpace();
   b.mColorPrimaries = GetFrameColorPrimaries();
   b.mColorRange = GetFrameColorRange();
diff --git a/dom/media/platforms/ffmpeg/FFmpegVideoUtils.h b/dom/media/platforms/ffmpeg/FFmpegVideoUtils.h
index e7086ddb962..aba5fba7c48 100644
--- a/dom/media/platforms/ffmpeg/FFmpegVideoUtils.h
+++ b/dom/media/platforms/ffmpeg/FFmpegVideoUtils.h
@@ -8,6 +8,13 @@
 #include "MediaData.h"
 #include "libavcodec/version.h"
 #include "libavutil/pixfmt.h"
+#include "mozilla/CheckedInt.h"
+
+#if LIBAVCODEC_VERSION_MAJOR >= 58
+extern "C" {
+#  include "libavutil/frame.h"
+}
+#endif
 
 #if LIBAVCODEC_VERSION_MAJOR < 54
 #  ifndef AV_PIX_FMT_YUV420P10LE
@@ -35,13 +42,14 @@
 
 namespace mozilla {
 
+// Internal linkage: each FFmpeg version flavor compiles these against its own
+// AVFrame layout.
 // Fills the chroma plane geometry (Cb/Cr dimensions, chroma subsampling and
 // colour depth) of aBuffer from aFormat, an AVPixelFormat value, and the luma
 // dimensions aWidth and aHeight. The luma plane (index 0) must already be
 // populated by the caller.
-inline void SetChromaPlaneGeometryFromAVFormat(VideoData::YCbCrBuffer& aBuffer,
-                                               int aFormat, int aWidth,
-                                               int aHeight) {
+static inline void SetChromaPlaneGeometryFromAVFormat(
+    VideoData::YCbCrBuffer& aBuffer, int aFormat, int aWidth, int aHeight) {
   if (aFormat == AV_PIX_FMT_YUV444P || aFormat == AV_PIX_FMT_YUV444P10LE ||
       aFormat == AV_PIX_FMT_GBRP || aFormat == AV_PIX_FMT_GBRP10LE
 #if LIBAVCODEC_VERSION_MAJOR >= 57
@@ -90,6 +98,51 @@ inline void SetChromaPlaneGeometryFromAVFormat(VideoData::YCbCrBuffer& aBuffer,
   }
 }
 
+#if LIBAVCODEC_VERSION_MAJOR >= 58
+// 0 when the frame has no such buffer.
+static inline size_t FrameBufferSize(const AVFrame* aFrame, size_t aIndex) {
+  if (aIndex < AV_NUM_DATA_POINTERS && aFrame->buf[aIndex]) {
+    return static_cast<size_t>(aFrame->buf[aIndex]->size);
+  }
+  return 0;
+}
+
+// A plane occupies mHeight * mStride bytes from mData, all of which must
+// belong to the frame buffer holding mData.
+static inline bool PlaneFitsBackingBuffer(
+    const AVFrame* aFrame, const VideoData::YCbCrBuffer::Plane& aPlane) {
+  const CheckedInt<size_t> needed =
+      CheckedInt<size_t>(aPlane.mHeight) * aPlane.mStride;
+  if (!needed.isValid()) {
+    return false;
+  }
+  const uintptr_t plane = reinterpret_cast<uintptr_t>(aPlane.mData);
+  for (const AVBufferRef* buf : aFrame->buf) {
+    if (!buf || !buf->data) {
+      continue;
+    }
+    const uintptr_t start = reinterpret_cast<uintptr_t>(buf->data);
+    const uintptr_t end = start + static_cast<size_t>(buf->size);
+    if (plane >= start && plane < end) {
+      return needed.value() <= end - plane;
+    }
+  }
+  return false;
+}
+
+// avcodec_receive_frame() only delivers reference-counted frames, so every
+// plane must lie inside one of aFrame->buf[]. A frame without buffers fails.
+static inline bool FramePlanesFitBackingBuffers(
+    const AVFrame* aFrame, const VideoData::YCbCrBuffer& aBuffer) {
+  for (const auto& plane : aBuffer.mPlanes) {
+    if (!PlaneFitsBackingBuffer(aFrame, plane)) {
+      return false;
+    }
+  }
+  return true;
+}
+#endif
+
 }  // namespace mozilla
 
 #endif  // DOM_MEDIA_PLATFORMS_FFMPEG_FFMPEGVIDEOUTILS_H_
Loading diff…