Perform additional APNG validations.

Special thank you to Stanislav Fort of Aisle Research (stanislav.fort@aisle.com)
for finding this issue, bringing it to our attention, and offering a solution!
This commit is contained in:
Greyson Parrelli
2025-09-09 11:42:13 -04:00
parent 40ba967192
commit 57319d3189
2 changed files with 35 additions and 4 deletions

View File

@@ -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<R extends Reader, W extends Writer> {
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();
}

View File

@@ -38,6 +38,8 @@ public class APNGDecoder extends FrameSeqDecoder<APNGReader, APNGWriter> {
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<APNGReader, APNGWriter> {
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<APNGReader, APNGWriter> {
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;
}
}