From d555fc4025cd2a6e3cb010a38def27d178850464 Mon Sep 17 00:00:00 2001 From: Allen Chen Date: Wed, 24 Sep 2025 17:52:31 -0500 Subject: [PATCH 1/2] Safeguard against very large Bitmap creation Because we rely on the transform `Matrix` returned from the `Canvas` to size a `Bitmap` when using software rendering, an erroneous matrix value can cause a very large Bitmap to be allocated, triggering an OutOfMemory exception. We've encountered such a situation when using `layoutlib` for screenshot tests, which uses a `NopCanvas` [on initial render](https://cs.android.com/android/_/android/platform/frameworks/layoutlib/+/7b05b277beee599532606e9bb6d7a71f5ca2ab6e:bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java;l=543;bpv=1;bpt=0;drc=085857f145aac790e2a08cf6eb9546f98e26c338) that can return an invalid `Matrix. --- lottie/src/main/java/com/airbnb/lottie/LottieDrawable.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lottie/src/main/java/com/airbnb/lottie/LottieDrawable.java b/lottie/src/main/java/com/airbnb/lottie/LottieDrawable.java index 9c2f424545..69eb1a3392 100644 --- a/lottie/src/main/java/com/airbnb/lottie/LottieDrawable.java +++ b/lottie/src/main/java/com/airbnb/lottie/LottieDrawable.java @@ -1798,7 +1798,8 @@ private void renderAndDrawAsBitmap(Canvas originalCanvas, CompositionLayer compo int renderWidth = (int) Math.ceil(softwareRenderingTransformedBounds.width()); int renderHeight = (int) Math.ceil(softwareRenderingTransformedBounds.height()); - if (renderWidth <= 0 || renderHeight <= 0) { + // Safeguard against errors during Bitmap creation by returning early if dimensions are invalid. + if (renderWidth <= 0 || renderHeight <= 0 || renderWidth > bounds.width() || renderHeight > bounds.height()) { return; } From 2b2088a9cd0e96f98c3ce4a573b6d05ed5aa9e6e Mon Sep 17 00:00:00 2001 From: Allen Chen Date: Thu, 30 Oct 2025 18:12:51 -0500 Subject: [PATCH 2/2] Use a hardcoded upper bound and add additional checks --- .../com/airbnb/lottie/LottieDrawable.java | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/lottie/src/main/java/com/airbnb/lottie/LottieDrawable.java b/lottie/src/main/java/com/airbnb/lottie/LottieDrawable.java index 69eb1a3392..4d2d1d026d 100644 --- a/lottie/src/main/java/com/airbnb/lottie/LottieDrawable.java +++ b/lottie/src/main/java/com/airbnb/lottie/LottieDrawable.java @@ -111,6 +111,11 @@ private enum OnVisibleAction { "reducedmotion" ); + /** + * Significantly larger than the largest expected size, equivalent to about 1.5x an 8K display. + */ + private static final long MAX_SOFTWARE_BITMAP_PIXELS = 50_000_000L; + private LottieComposition composition; private final LottieValueAnimator animator = new LottieValueAnimator(); @@ -1795,11 +1800,22 @@ private void renderAndDrawAsBitmap(Canvas originalCanvas, CompositionLayer compo softwareRenderingTransformedBounds.intersect(canvasClipBounds.left, canvasClipBounds.top, canvasClipBounds.right, canvasClipBounds.bottom); } + if (!isFiniteRect(softwareRenderingTransformedBounds)) { + Logger.warning("Skipping software rendering: transformed bounds contain non-finite values."); + return; + } + int renderWidth = (int) Math.ceil(softwareRenderingTransformedBounds.width()); int renderHeight = (int) Math.ceil(softwareRenderingTransformedBounds.height()); - // Safeguard against errors during Bitmap creation by returning early if dimensions are invalid. - if (renderWidth <= 0 || renderHeight <= 0 || renderWidth > bounds.width() || renderHeight > bounds.height()) { + if (renderWidth <= 0 || renderHeight <= 0) { + Logger.warning("Skipping software rendering: transformed bounds have negative values."); + return; + } + + long renderPixelCount = (long) renderWidth * (long) renderHeight; + if (renderPixelCount > MAX_SOFTWARE_BITMAP_PIXELS) { + Logger.warning("Skipping software rendering: bitmap request exceeds safe pixel count (" + renderPixelCount + ")"); return; } @@ -1891,6 +1907,17 @@ private void convertRect(Rect src, RectF dst) { src.bottom); } + private static boolean isFiniteRect(RectF rect) { + return isFinite(rect.left) && + isFinite(rect.top) && + isFinite(rect.right) && + isFinite(rect.bottom); + } + + private static boolean isFinite(float value) { + return !Float.isNaN(value) && !Float.isInfinite(value); + } + private void scaleRect(RectF rect, float scaleX, float scaleY) { rect.set( rect.left * scaleX,