diff --git a/app/src/main/java/org/signal/glide/common/decode/FrameSeqDecoder.java b/app/src/main/java/org/signal/glide/common/decode/FrameSeqDecoder.java index 238797e49a..040a3686b6 100644 --- a/app/src/main/java/org/signal/glide/common/decode/FrameSeqDecoder.java +++ b/app/src/main/java/org/signal/glide/common/decode/FrameSeqDecoder.java @@ -20,6 +20,7 @@ import org.signal.glide.common.executor.FrameDecoderExecutor; import org.signal.glide.common.io.Reader; import org.signal.glide.common.io.Writer; import org.signal.glide.common.loader.Loader; +import org.signal.glide.load.resource.apng.decode.APNGDecoder; import java.io.IOException; import java.nio.ByteBuffer; @@ -238,9 +239,10 @@ public abstract class FrameSeqDecoder { return fullRect; } - private void initCanvasBounds(Rect rect) { + private void initCanvasBounds(Rect rect) throws IOException { fullRect = rect; - frameBuffer = ByteBuffer.allocate((rect.width() * rect.height() / (sampleSize * sampleSize) + 1) * 4); + int capacity = APNGDecoder.getSafeAllocationSize(fullRect.width(), fullRect.height(), sampleSize); + frameBuffer = ByteBuffer.allocate(capacity); if (mWriter == null) { mWriter = getWriter(); } diff --git a/app/src/main/java/org/signal/glide/load/resource/apng/decode/APNGDecoder.java b/app/src/main/java/org/signal/glide/load/resource/apng/decode/APNGDecoder.java index eeb9837bee..a108443aa9 100644 --- a/app/src/main/java/org/signal/glide/load/resource/apng/decode/APNGDecoder.java +++ b/app/src/main/java/org/signal/glide/load/resource/apng/decode/APNGDecoder.java @@ -38,6 +38,8 @@ public class APNGDecoder extends FrameSeqDecoder { private int mLoopCount; private final Paint paint = new Paint(); + public static final int MAX_DIMENSION = 4096; + public static final long MAX_TOTAL_PIXELS = 64_000_000L; private class SnapShot { byte dispose_op; @@ -126,8 +128,12 @@ public class APNGDecoder extends FrameSeqDecoder { otherChunks.add(chunk); } } - frameBuffer = ByteBuffer.allocate((canvasWidth * canvasHeight / (sampleSize * sampleSize) + 1) * 4); - snapShot.byteBuffer = ByteBuffer.allocate((canvasWidth * canvasHeight / (sampleSize * sampleSize) + 1) * 4); + + int capacity = getSafeAllocationSize(canvasWidth, canvasHeight, sampleSize); + + frameBuffer = ByteBuffer.allocate(capacity); + snapShot.byteBuffer = ByteBuffer.allocate(capacity); + return new Rect(0, 0, canvasWidth, canvasHeight); } @@ -208,4 +214,27 @@ public class APNGDecoder extends FrameSeqDecoder { Log.e(TAG, "Failed to render!", t); } } + + public static int getSafeAllocationSize(int width, int height, int sampleSize) throws IOException { + if (width <= 0 || height <= 0 || width > MAX_DIMENSION || height > MAX_DIMENSION) { + throw new IOException("APNG dimensions exceed safe limits: " + width + "x" + height); + } + + int capacity; + try { + int ss = Math.multiplyExact(sampleSize, sampleSize); + int canvasSize = Math.multiplyExact(width, height); + + int pixelCount = canvasSize / ss + 1; + if (pixelCount <= 0 || pixelCount > MAX_TOTAL_PIXELS) { + throw new IOException("APNG pixel count exceeds safe limits: " + pixelCount); + } + + capacity = Math.multiplyExact(pixelCount, 4); + } catch (ArithmeticException e) { + throw new IOException("Failed to multiply dimensions and sample size: " + width + "x" + height + " @ sample size " + sampleSize + " (overflow?)", e); + } + + return capacity; + } }