Firefox · DOM
CVE-2026-117873
Logic Error in DOM
Overview
High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifdom/media/platforms/ffmpeg/FFmpegVideoDecoder.cpp |
modified | |
ifdom/media/platforms/ffmpeg/FFmpegVideoUtils.h |
modified | |
fordom/media/platforms/ffmpeg/FFmpegVideoUtils.h |
modified |
Files Changed
dom/media/platforms/ffmpeg/FFmpegVideoDecoder.cppdom/media/platforms/ffmpeg/FFmpegVideoUtils.h
Patch
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…
References
On This Page