CVE-2026-49927 AI-generated analysis
platform/external/libppd, ppd raster interpreter (ppd/raster-interpret.c) and raster-to-PostScript filter (ppd/rastertops.c)
Adds 64-bit overflow checks when ppdRasterInterpretPPD derives cupsBytesPerLine. In write_flate it rejects cupsBytesPerLine*6 above INT32_MAX and replaces the header-sized stack VLAs and per-line mallocs with single checked heap allocations, freed through a common cleanup path.
Root cause
In ppd/rastertops.c write_flate, buffer sizes came from the raster page header field header.cupsBytesPerLine using 32-bit unsigned arithmetic. This applied to the stack VLAs in[header.cupsBytesPerLine * 6] and out[header.cupsBytesPerLine * 6], and to the per-line heap allocation convertedpix = malloc(header.cupsBytesPerLine * 6). A large cupsBytesPerLine wraps the multiplication, so the buffers end up much smaller than the data convert_pixels then writes, which gives a heap out-of-bounds write. The conversion branch is used when cupsBitsPerColor == 1 and the color space is in a set that includes CUPS_CSPACE_RGB. The VLAs also let a header value drive unbounded stack allocation. In ppd/raster-interpret.c ppdRasterInterpretPPD, h->cupsBytesPerLine = (h->cupsBitsPerPixel * h->cupsWidth + 7) / 8 and the CUPS_ORDER_BANDED multiply by h->cupsNumColors were also computed in 32 bits with no overflow detection. The old write_flate also leaked pixdata on the fwrite error path.
How the fix works
- ppd/raster-interpret.c:ppdRasterInterpretPPD: adds #include <stdint.h>. Computes bpl = ((uint64_t)h->cupsBitsPerPixel * h->cupsWidth + 7) / 8. If bpl > UINT32_MAX it calls _ppdRasterAddError("Invalid raster dimensions (overflow).") and returns -1, before storing into h->cupsBytesPerLine.
- ppd/raster-interpret.c:ppdRasterInterpretPPD: for CUPS_ORDER_BANDED, computes (uint64_t)h->cupsBytesPerLine * h->cupsNumColors. If the result exceeds UINT32_MAX it reports "Invalid raster dimensions/color (overflow)." and returns -1. Otherwise it stores the value.
- ppd/rastertops.c:write_flate: removes the VLAs in[]/out[]. Computes expanded_size = (uint64_t)header.cupsBytesPerLine * 6. If expanded_size > INT32_MAX it logs via doc->logfunc at CF_LOGLEVEL_ERROR and returns Z_MEM_ERROR, before any allocation or deflateInit.
- ppd/rastertops.c:write_flate: allocates in, out and convertedpix once with malloc((size_t)expanded_size), and pixdata with malloc(header.cupsBytesPerLine). If any allocation is NULL it logs and goes to cleanup with Z_MEM_ERROR. convertedpix is always sized to the 6x maximum whichever branch the flag selects, and alloc = (int)expanded_size in the conversion branch.
- ppd/rastertops.c:write_flate: removes the per-iteration malloc/free of pixdata and convertedpix inside the compression loop. The fwrite failure path now sets ret = Z_ERRNO and jumps to cleanup, and success sets ret = Z_OK. A new cleanup label calls deflateEnd only if deflate_initialized is set, then frees in, out, pixdata and convertedpix.
- patches/libppd-2.1.1-Fix-integer-overflow-and-heap-OOB-write-errors.patch: records the same change as a patch file against upstream libppd 2.1.1, alongside the direct source edits.
Completeness
- Covers both size computations in write_flate (stack VLAs and heap convertedpix) with one early check of cupsBytesPerLine*6 against INT32_MAX. No path in write_flate allocates from a wrapped value.
- Covers both multiplications in ppdRasterInterpretPPD (bits*width, and *numColors for banded order).
- write_flate still does not check the return value of cupsRasterReadPixels. A short read leaves pixdata partly uninitialized but within bounds.
- The commit message says unsigned long long and INT_MAX, but the code uses uint64_t and INT32_MAX/UINT32_MAX. These are equivalent on Android targets.
Behavior changes
- ppdRasterInterpretPPD now fails with -1 and a raster error string when bytes-per-line would exceed UINT32_MAX.
- write_flate returns Z_MEM_ERROR, with a CF_LOGLEVEL_ERROR log, when cupsBytesPerLine*6 exceeds INT32_MAX or when any allocation fails, instead of continuing.
- Buffers are now heap allocated once per call instead of on the stack or per line. Peak heap use is up to 4 buffers (three of them 6x cupsBytesPerLine) for the whole call.
- The fwrite error path now frees all buffers. Previously it leaked pixdata.
Per major
- Android 14: not affected: not listed by the bulletin. external/libppd is not present in android-security-14.0.0_r29.
- Android 15: not affected: not listed by the bulletin. external/libppd is not present in android-security-15.0.0_r17.
- Android 16: not affected: Android 16 is not listed by the bulletin, and the project is not present at android-security-16.0.0_r8. The bulletin does list 16-qpr2 separately as affected, but the material has no 16-qpr2 tag facts.
- Android 17: same fix: commit 6ce8492f2566 in android-security-17.0.0_r1, with 8/8 lines of the patched region present. Cherry-picked from two internal changes under Change-Id I934add5868d832899dfebed68a1eff00f535e62d.
Unknowns
- The material does not show which Android process or service runs ppdFilterRasterToPS or ppdRasterInterpretPPD on untrusted input, so the privilege boundary behind the Critical EoP rating is not shown.
- Whether 16-qpr2 received the same commit. The bulletin lists it as affected but no tag facts are provided.
- Where the header passed to write_flate comes from, and whether header fields read directly from a raster stream are validated anywhere besides write_flate's own INT32_MAX check.
- The full set of color spaces that turns on the 6x conversion branch (the condition is cut off in the diff).