CVE-2026-49918 AI-generated analysis
platform/external/libcupsfilters, raster header processing and PWG/image-to-raster filter functions (cupsfilters/imagetoraster.c, cupsfilters/pwgtoraster.c, cupsfilters/raster.c)
Adds 64-bit overflow checks to the cupsBytesPerLine and line/page buffer size computations in libcupsfilters (imagetoraster.c, pwgtoraster.c, raster.c) so that large raster dimensions no longer wrap to undersized heap allocations.
Root cause
Buffer sizes were computed with 32-bit (unsigned int / int) arithmetic from raster header fields. cupsBytesPerLine = (cupsBitsPerPixel * cupsWidth + 7) / 8, multiplied by cupsNumColors for CUPS_ORDER_BANDED. In pwgtoraster.c out_page() the same applied to inlineoffset = bitmapoffset[0] * 3, inlinesize = cupsWidth * 3, the line buffer size max(cupsBytesPerLine * res_up_factor[0], inlineoffset + inlinesize) (held in int i, j), the lineavg size cupsBytesPerLine * res_down_factor[1], the pagebuf size cupsHeight * inlinesize and the preBuf2 size cupsWidth * 3. In imagetoraster.c it applied to the row buffer malloc(2 * cupsBytesPerLine). With large width, depth or height values these products could wrap, so calloc/malloc received an undersized length. According to the commit message, this led to heap out-of-bounds writes when processing crafted raster streams.
How the fix works
- cupsfilters/raster.c:cfRasterPrepareHeader: computes bpl as uint64_t (casting cupsBitsPerPixel first) and applies the BANDED multiply by cupsNumColors to the 64-bit value. If bpl > UINT32_MAX it logs an error, calls cupsFreeOptions(num_options, options) and returns -1. Otherwise it stores (unsigned int)bpl in h->cupsBytesPerLine.
- cupsfilters/raster.c:cfRasterSetColorSpace: computes (cupsWidth * cupsBitsPerPixel + 7) / 8 the same way in 64 bits and returns -1 (without logging) when the result exceeds UINT32_MAX.
- cupsfilters/imagetoraster.c:cfFilterImageToRaster: computes cupsBytesPerLine (including the BANDED multiply) as uint64_t. On overflow it logs, calls cfImageClose(img) and returns 1. The row buffer size 2ULL * cupsBytesPerLine is checked against UINT32_MAX before malloc, with the same error handling.
- cupsfilters/pwgtoraster.c:out_page: i and j change from int to uint64_t. bytesPerLine is checked against UINT32_MAX before the BANDED multiply, and cupsBytesPerLine is checked again after it (ret = false; goto out). In the cupsNumColors == 3 branch, inlineoffset (bitmapoffset[0] * 3) and inlinesize (cupsWidth * 3) are computed in uint64_t, and the function returns false on overflow. The 'line' calloc size is computed in 64 bits and rejected if it exceeds UINT32_MAX. The lineavg, pagebuf and preBuf2 callocs are each guarded the same way. The preBuf2 path also frees preBuf1 before goto out.
- patches/libcupsfilters-2.1.1-Fix-integer-overflows-in-raster-header-processing.patch: a new file containing the same change as a patch, in the project's patches directory. The material does not show how the build uses it.
Completeness
- Covers every cupsBytesPerLine computation in the diff (cfRasterPrepareHeader, cfRasterSetColorSpace, cfFilterImageToRaster, out_page) and the line, lineavg, pagebuf, preBuf2 and row allocations.
- The checks only reject values that do not fit in 32 bits. Any size up to UINT32_MAX still goes to malloc/calloc, and the diff adds no NULL check on the allocation results (for example row = malloc(...) in cfFilterImageToRaster).
- In out_page, the inlineoffset/inlinesize overflow paths use return (false) instead of ret = false; goto out, unlike the other new checks. The material does not show whether this skips cleanup done at the out label.
- Only the cupsNumColors == 3 branch of the inlineoffset/inlinesize computation changes. The diff does not show the other input_color_mode branches, so their arithmetic cannot be checked.
- cfRasterSetColorSpace now returns -1 on overflow. The material does not show whether its callers check the return value.
Behavior changes
- cfRasterPrepareHeader returns -1 (after freeing options), and cfRasterSetColorSpace returns -1, when the computed bytes-per-line does not fit in 32 bits.
- cfFilterImageToRaster returns 1, and out_page in pwgtoraster.c returns false, on oversized headers or buffers, logging new CF_LOGLEVEL_ERROR messages that mention '(overflow)'.
- Jobs whose raster dimensions or color depth produce sizes above UINT32_MAX are now rejected instead of processed.
Per major
- Android 14: not affected: not listed by the bulletin; platform/external/libcupsfilters is not present in android-security-14.0.0_r29.
- Android 15: not affected: not listed by the bulletin; project not present in android-security-15.0.0_r17.
- Android 16: not affected per the per-major facts (not listed by the bulletin); project not present in android-security-16.0.0_r8. The bulletin's affected-versions field mentions 16-qpr2, but the material contains no 16-qpr2 commit.
- Android 17: same fix: commit 5c0e5bf182a5 (Change-Id I1901738698979c964f89f244cfe8f6be0ebf501f, cherry-picked from two internal commits) in android-security-17.0.0_r1; 8/8 lines of the patched region present.
Unknowns
- Which Android component or process feeds raster streams into these libcupsfilters filter functions, and therefore what attack surface the Critical EoP rating assumes.
- Whether a 16-qpr2 branch received the same change: the affected-versions list includes it, but no commit for it is shown.
- Whether the return (false) paths in out_page skip resource cleanup at the out label.
- The body of the non-RGB inlineoffset/inlinesize branches in out_page, which the diff does not touch.
- How the build uses the added patches/*.patch file, if at all.
- Where the out-of-bounds writes described in the commit message actually happen (the write sites are not in the material).