Firefox · Core
CVE-2026-12326
Memory Corruption in Core
Overview
High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifmobile/android/geckoview/src/main/java/org/mozilla/gecko/media/Codec.java |
modified | |
ifmobile/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.javamobile/android/geckoview/src/main/java/org/mozilla/gecko/media/SamplePool.java
Patch
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…
References
On This Page