Skip to content

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

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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions