High firefox Memory Corruption 🔧 Commit mapped

Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
Impacthigh
DescriptionMemory safety bugs present in Firefox 151 and Thunderbird 151. Some of these bugs showed evidence of memory corruption and we presume that with enough effort some of these could have been exploited to run arbitrary code.
ComponentCore
Bug ClassMemory Corruption
Tracker2039050
Fix commit1e7358dfe1f0 (firefox) +46/-16
CISA KEVNot listed
CreditedAshley Zebrowski, Christian Holler, Dan Baker, Jan de Mooij, Jon Coppeard, Maurice Dauer, Nicolas B. Pierron, Nika Layzell, Randell Jesup, Rob Wu, Ryan Hunt, Steve Fink, Tom Schuster, Tomoya Nakanishi, Yannis Juglaret, Serge Guelton and the Mozilla Fuzzing Team
Disclosed2026-06-16

Changed Functions

FunctionChangeNotes
if
mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/Codec.java
modified
if
mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/SamplePool.java
modified

Files Changed

  • mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/Codec.java
  • mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/SamplePool.java
diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/Codec.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/Codec.java
index 4ae1f0c97e8..15dad9982d1 100644
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/Codec.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/Codec.java
@@ -244,7 +244,12 @@ import org.mozilla.gecko.gfx.GeckoSurface;
 
   private class OutputProcessor {
     private final boolean mRenderToSurface;
-    private boolean mHasOutputCapacitySet;
+    // Tracks the largest output frame size observed so far. MediaCodec's
+    // getOutputBuffer(idx).capacity() is not reliable as a max-buffer hint
+    // on modern Android (it reports the valid data range of the current
+    // frame, e.g. ~28 bytes for the first SPS/PPS), so we grow the
+    // SamplePool's default monotonically from observed info.size instead.
+    private int mMaxObservedOutputSize;
     private Queue<Output> mSentOutputs = new LinkedList<>();
     private boolean mStopped;
 
@@ -285,6 +290,14 @@ import org.mozilla.gecko.gfx.GeckoSurface;
     }
 
     private Sample obtainOutputSample(final int index, final MediaCodec.BufferInfo info) {
+      // Grow the pool's minimum-useful capacity ahead of allocation so
+      // setDefaultBufferSize sweeps any now-stranded recycled samples
+      // before obtainOutput could hand one back.
+      if (!mRenderToSurface && info.size > mMaxObservedOutputSize) {
+        mMaxObservedOutputSize = info.size;
+        mSamplePool.setOutputBufferSize(mMaxObservedOutputSize);
+      }
+
       final Sample sample = mSamplePool.obtainOutput(info);
 
       if (mRenderToSurface) {
@@ -292,14 +305,6 @@ import org.mozilla.gecko.gfx.GeckoSurface;
       }
 
       final ByteBuffer output = mCodec.getOutputBuffer(index);
-      if (!mHasOutputCapacitySet) {
-        final int capacity = output.capacity();
-        if (capacity > 0) {
-          mSamplePool.setOutputBufferSize(capacity);
-          mHasOutputCapacitySet = true;
-        }
-      }
-
       if (info.size > 0) {
         try {
           mSamplePool
diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/SamplePool.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/SamplePool.java
index a2101b3aebd..134e01154be 100644
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/SamplePool.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/SamplePool.java
@@ -8,6 +8,7 @@ import android.media.MediaCodec;
 import android.util.SparseArray;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.List;
 import org.mozilla.gecko.mozglue.SharedMemory;
 
@@ -26,23 +27,47 @@ final class SamplePool {
       mBufferless = bufferless;
     }
 
-    private void setDefaultBufferSize(final int size) {
+    private synchronized void setDefaultBufferSize(final int size) {
       if (mBufferless) {
         throw new IllegalStateException("Setting buffer size of a bufferless pool is not allowed");
       }
+      if (size <= mDefaultBufferSize) {
+        return;
+      }
       mDefaultBufferSize = size;
+      // Sweep: any sample admitted under a smaller default is now stranded.
+      final Iterator<Sample> it = mRecycledSamples.iterator();
+      while (it.hasNext()) {
+        final Sample s = it.next();
+        final SampleBuffer buf = mBuffers.get(s.bufferId);
+        if (buf == null || buf.capacity() < mDefaultBufferSize) {
+          it.remove();
+          disposeSample(s);
+        }
+      }
     }
 
     private synchronized Sample obtain(final int size) {
-      if (!mRecycledSamples.isEmpty()) {
-        return mRecycledSamples.remove(0);
-      }
-
       if (mBufferless) {
+        if (!mRecycledSamples.isEmpty()) {
+          return mRecycledSamples.remove(0);
+        }
         return Sample.obtain();
-      } else {
-        return allocateSampleAndBuffer(size);
       }
+      final Iterator<Sample> it = mRecycledSamples.iterator();
+      while (it.hasNext()) {
+        final Sample candidate = it.next();
+        final SampleBuffer buf = mBuffers.get(candidate.bufferId);
+        if (buf != null && buf.capacity() >= size) {
+          it.remove();
+          return candidate;
+        }
+        it.remove();
+        if (buf != null) {
+          disposeSample(candidate);
+        }
+      }
+      return allocateSampleAndBuffer(size);
     }
 
     private Sample allocateSampleAndBuffer(final int size) {
Loading diff…