Skip to content

imagetoraster: heap-buffer-overflow in cupsfilters/image-zoom.c:273 with small grayscale PNG #148

@kimaiden1984-boop

Description

@kimaiden1984-boop

Summary

Running imagetoraster on a small valid grayscale PNG with a valid PPD triggers
an AddressSanitizer heap-buffer-overflow in zoom_bilinear():

SUMMARY: AddressSanitizer: heap-buffer-overflow cupsfilters/image-zoom.c:273 in zoom_bilinear

The issue reproduces with libcupsfilters built from current upstream
origin/master:

42d458abe27c6930c7c4bcc42bc115cf33145a96
42d458ab charset: Add Devanagari Unicode range to utf-8 charsets (#141)

The replay uses the cups-filters imagetoraster wrapper. The local wrapper
checkout has no relevant filter/imagetoraster.c diff against current
cups-filters origin/master.

Impact

This is a one-byte read past a two-byte heap allocation during image scaling.
The immediate observed impact is a process abort under ASan. Without ASan, this
can become an out-of-bounds read from adjacent heap memory during print-filter
processing.

Minimal Reproducer

Save this PPD as repro.ppd:

*PPD-Adobe: "4.3"
*FormatVersion: "4.3"
*FileVersion: "1.0"
*LanguageVersion: English
*LanguageEncoding: ISOLatin1
*Manufacturer: "SMT-Fuzzer"
*ModelName: "ImageToRaster Coverage Options"
*ShortNickName: "SMTTemplate"
*NickName: "ImageToRaster Coverage Options"
*PCFileName: "SMTPPD.PPD"
*Product: "(SMTTemplate)"
*PSVersion: "(3010) 0"
*cupsVersion: 1.0
*cupsModelNumber: 0
*cupsManualCopies: False
*cupsFilter: "image/x-portable-anymap 0 imagetoraster"
*OpenUI *ColorModel: PickOne
*DefaultColorModel: RGB
*ColorModel Gray/Gray: "<</cupsColorSpace 18/cupsBitsPerColor 8/cupsBitsPerPixel 8>>setpagedevice"
*ColorModel RGB/RGB: "<</cupsColorSpace 1/cupsBitsPerColor 8/cupsBitsPerPixel 24>>setpagedevice"
*ColorModel CMYK/CMYK: "<</cupsColorSpace 6/cupsBitsPerColor 8/cupsBitsPerPixel 32>>setpagedevice"
*ColorModel Black/Black: "<</cupsColorSpace 3/cupsBitsPerColor 8/cupsBitsPerPixel 8>>setpagedevice"
*CloseUI: *ColorModel
*OpenUI *PrintQuality: PickOne
*DefaultPrintQuality: Draft
*PrintQuality Draft/Draft: "<</cupsInteger0 3>>setpagedevice"
*PrintQuality Normal/Normal: "<</cupsInteger0 4>>setpagedevice"
*PrintQuality High/High: "<</cupsInteger0 5>>setpagedevice"
*PrintQuality Photo/Photo: "<</cupsInteger0 6>>setpagedevice"
*CloseUI: *PrintQuality
*OpenUI *MediaType: PickOne
*DefaultMediaType: Plain
*MediaType Plain/Plain: "<</MediaType(Plain)>>setpagedevice"
*MediaType Glossy/Glossy: "<</MediaType(Glossy)>>setpagedevice"
*MediaType Transparency/Transparency: "<</MediaType(Transparency)>>setpagedevice"
*MediaType Envelope/Envelope: "<</MediaType(Envelope)>>setpagedevice"
*CloseUI: *MediaType
*OpenUI *Duplex: PickOne
*DefaultDuplex: None
*Duplex None/Off: "<</Duplex false>>setpagedevice"
*Duplex DuplexNoTumble/Long edge: "<</Duplex true/Tumble false>>setpagedevice"
*Duplex DuplexTumble/Short edge: "<</Duplex true/Tumble true>>setpagedevice"
*CloseUI: *Duplex
*OpenUI *PageSize: PickOne
*DefaultPageSize: A4
*PageSize A4: "<</PageSize[595 842]/ImagingBBox null>>setpagedevice"
*CloseUI: *PageSize
*OpenUI *PageRegion: PickOne
*DefaultPageRegion: A4
*PageRegion A4: "<</PageSize[595 842]/ImagingBBox null>>setpagedevice"
*CloseUI: *PageRegion
*DefaultImageableArea: A4
*ImageableArea A4: "12 12 583 830"
*DefaultPaperDimension: A4
*PaperDimension A4: "595 842"

Generate repro.png:

#!/usr/bin/env python3
import struct
import zlib


def png_chunk(kind, payload):
    crc = zlib.crc32(kind + payload) & 0xFFFFFFFF
    return struct.pack(">I", len(payload)) + kind + payload + struct.pack(">I", crc)


width = 9
height = 2
raw = bytearray()
for y in range(height):
    raw.append(0)
    for x in range(width):
        raw.append((x * 23 + y * 17) & 0xFF)

ihdr = struct.pack(">IIBBBBB", width, height, 8, 0, 0, 0, 0)
data = (
    b"\x89PNG\r\n\x1a\n"
    + png_chunk(b"IHDR", ihdr)
    + png_chunk(b"IDAT", zlib.compress(bytes(raw)))
    + png_chunk(b"IEND", b"")
)
open("repro.png", "wb").write(data)

The generated input is:

PNG image data, 9 x 2, 8-bit grayscale, non-interlaced

Observed hashes for the original replay artifacts:

document.png  sha256 4f5f710fca2ff97193c3584c9ec5ab6cb1a381a2256c1e649b2eb7d557b47afe
candidate.ppd sha256 c1a6d5622f4a422bcbb81b393e42287a5c661331c924e1b27b29599022b135c5

Build Configuration Used For Verification

libcupsfilters was built from upstream origin/master with ASan:

./autogen.sh
ASAN_OPTIONS=detect_leaks=0 \
PKG_CONFIG_PATH=/data/pre-gsoc/env/pdfio-install/lib/pkgconfig \
CC=gcc CXX=g++ \
CFLAGS='-g -O1 -fsanitize=address -fno-omit-frame-pointer' \
CXXFLAGS='-g -O1 -fsanitize=address -fno-omit-frame-pointer' \
LDFLAGS='-fsanitize=address' \
./configure --prefix=/data/pre-gsoc/libcupsfilters-master-install

ASAN_OPTIONS=detect_leaks=0 make -j4

The replay confirmed that the imagetoraster process loaded this freshly built
library:

libcupsfilters.so.2 => /data/pre-gsoc/libcupsfilters-master-asan/.libs/libcupsfilters.so.2
libpdfio.so.1 => /data/pre-gsoc/env/pdfio-install/lib/libpdfio.so.1

Trigger Command

env \
  LD_LIBRARY_PATH=/data/pre-gsoc/libcupsfilters-master-asan/.libs:/data/pre-gsoc/env/pdfio-install/lib \
  ASAN_OPTIONS=abort_on_error=0:detect_leaks=0:symbolize=1:exitcode=86 \
  PPD=./repro.ppd \
  /path/to/cups-filters/imagetoraster \
  1 test test 1 '' \
  ./repro.png

Observed exit code:

86

ASan Excerpt

INFO: cfFilterImageToRaster: Formatting page 1.
=================================================================
ERROR: AddressSanitizer: heap-buffer-overflow
READ of size 1
    #0 in zoom_bilinear cupsfilters/image-zoom.c:273
    #1 in _cfImageZoomFill cupsfilters/image-zoom.c:65
    #2 in cfFilterImageToRaster cupsfilters/imagetoraster.c:1726
    #3 in ppdFilterCUPSWrapper
    #4 in main filter/imagetoraster.c:68

0x502000007b72 is located 0 bytes after 2-byte region [0x502000007b70,0x502000007b72)
allocated by thread T0 here:
    #0 in malloc
    #1 in _cfImageZoomNew cupsfilters/image-zoom.c:202
    #2 in cfFilterImageToRaster cupsfilters/imagetoraster.c:1678

SUMMARY: AddressSanitizer: heap-buffer-overflow cupsfilters/image-zoom.c:273 in zoom_bilinear

Full local replay log:

work/latest-master-replay/imagetoraster-image-zoom-case0001/asan.txt

Notes On The Faulting Code

The faulting expression reads the next input pixel:

*r++ = (inptr[count] * xerr0 + inptr[z_depth + count] * xerr1) / z_xsize;

In the reproducer, the allocated input row is only two bytes wide, and ASan
reports the read at the first byte immediately after that two-byte region. The
latest upstream image-zoom.c change adds a zero-size guard in
_cfImageZoomNew(), but this reproducer still reaches the out-of-bounds read
in zoom_bilinear().

Expected Result

imagetoraster should either render the image or reject the scaling parameters
without reading past the row buffer.

Actual Result

imagetoraster aborts under ASan with a heap-buffer-overflow in
zoom_bilinear().

issue-image-zoom-oob.tar.gz

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions