CVE-2025-48530 AI-generated analysis
platform/external/rust/crabbyavif (AVIF decoder library), plane allocation and C API (src/capi/image.rs, src/image.rs, src/internal_utils/mod.rs)
crabbyavif now allocates image planes with width and height rounded up to even values, allocates and reports only one chroma plane for the Android semi-planar formats (NV12/NV21/P010), and fixes per-format chroma plane width reporting, so plane buffers are no longer undersized for odd-dimension images.
Root cause
Plane buffers were sized from the exact image width and height (width * height * pixel_size) in crabby_avifImageCopy, avif_image_allocate_planes_helper and Image::allocate_planes_with_default_values. For images with odd width or height, particularly with the Android semi-planar formats (AndroidNv12, AndroidNv21, AndroidP010), these buffers and row strides were evidently too small for how the planes are used (the commit title refers to "NV12 crashes"); the accessing code is not in the diff. In addition, avif_image_allocate_planes_helper allocated planes 1 and 2 for all non-monochrome formats, and crabby_avifImagePlaneWidth computed chroma widths generically from chroma_shift_x for both plane 1 and plane 2, which does not match the semi-planar layout that the fix encodes (plane 1 full width, plane 2 width 0).
How the fix works
- src/internal_utils/mod.rs: adds round2_function! macro generating round2_u32 (cfg(feature = "capi")) and round2_usize, which round an odd value up to the next even value.
- src/capi/image.rs:crabby_avifImageCopy: for YUV planes 0..3 and for the alpha plane, the destination allocation size is now alloc_plane_width * alloc_plane_height * pixel_size with both dimensions passed through round2_usize, and dst.yuvRowBytes / dst.alphaRowBytes are set from the rounded width. copy_plane_helper still copies only the real plane_width x plane_height, so the extra space is padding.
- src/capi/image.rs:avif_image_allocate_planes_helper: Y plane row bytes and size are computed from round2_u32(image.width) and round2_u32(image.height). Chroma width/height derived from chroma_shift_x/chroma_shift_y are converted with u32_from_u64 and rounded with round2_u32 before computing uv_row_bytes and uv_size. The chroma loop now runs 1..=plane_end where plane_end is 1 for AndroidP010/AndroidNv12/AndroidNv21 and 2 otherwise, so semi-planar formats allocate a single chroma plane.
- src/capi/image.rs:crabby_avifImagePlaneWidth: replaces the generic chroma_shift_x computation with explicit per-format results: plane 1 returns width for Yuv444/AndroidP010/AndroidNv12/AndroidNv21, width.div_ceil(2) for Yuv420/Yuv422, 0 for None/Yuv400; plane 2 returns width for Yuv444, width.div_ceil(2) for Yuv420/Yuv422, and 0 for None/Yuv400 and the three Android semi-planar formats.
- src/image.rs:Image::allocate_planes_with_default_values: width and height from self.width(plane)/self.height(plane) are passed through round2_usize before computing plane_size and row_bytes, so internally owned plane buffers also have even dimensions; the reuse check compares against the rounded size.
Completeness
- Covers the three allocation sites visible in the diff: the C API copy path (crabby_avifImageCopy), the C API allocate-planes helper (avif_image_allocate_planes_helper) and the core Rust allocator (Image::allocate_planes_with_default_values), for both YUV and alpha planes.
- The rounding is applied uniformly to all pixel formats, not only to the semi-planar Android formats, so planar odd-dimension images also get padded buffers.
- crabby_avifImagePlaneWidth is corrected for plane 1 and plane 2; crabby_avifImagePlaneHeight is not touched in the diff and its behavior for semi-planar plane 2 is not shown in the material.
- The fix enlarges buffers rather than adding bounds checks at the accessing code; the code that performs the accesses is not part of the diff, so any other consumer that assumes a different stride or size is not addressed here.
- round2_u32 is compiled only with the capi feature; the core path relies on round2_usize only.
Behavior changes
- Plane allocations and row strides (yuvRowBytes, alphaRowBytes, Image::row_bytes) are now even-aligned; odd-width images get one extra sample of padding per row and odd-height images one extra padded row.
- For AndroidP010/AndroidNv12/AndroidNv21, avif_image_allocate_planes_helper no longer allocates yuvPlanes[2], and crabby_avifImagePlaneWidth reports 0 for plane 2 and the full image width for plane 1.
- In avif_image_allocate_planes_helper, chroma width/height are now converted with u32_from_u64 before use, so values exceeding u32 range return an error where previously they were handled as u64/usize.
- Existing planes whose size equals the old unrounded size will no longer pass the reuse check in allocate_planes_with_default_values and will be reallocated.
Per major
- Android 14: not affected per bulletin; project platform/external/rust/crabbyavif not present in android-security-14.0.0_r29 (code absent)
- Android 15: not affected per bulletin; project not present in android-security-15.0.0_r17 (code absent)
- Android 16: same fix: commit 9bcc1a311114 (cherry-picked from ag/33332267) in android-security-16.0.0_r1; at android-security-16.0.0_r8 6/8 lines of the patched region are present, and the shown context for crabby_avifImageCopy, allocate_planes_with_default_values and the round2 helpers matches the fix
- Android 17: not affected per the bulletin (not listed); at android-security-17.0.0_r1 6/8 lines of the patched region are present
Unknowns
- Which code performs the out-of-bounds access on the undersized planes (decoder output writer, YUV conversion, or mediacodec path) is not in the material.
- Whether the crashes were out-of-bounds writes or reads; the bulletin rates it Critical RCE but the diff only shows the allocation-size change.
- Why 2 of 8 lines of the patched region differ at android-security-16.0.0_r8 and android-security-17.0.0_r1.
- Whether crabby_avifImagePlaneHeight needs a matching per-format change for semi-planar plane 2; it is not in the diff.
- The exact Android framework caller and the attack surface (e.g. which image decoding service uses crabbyavif) are not shown.