From 9150dd6d07c1ecf8917beb4814adf11f1acbb5a7 Mon Sep 17 00:00:00 2001 From: Darren Carreras Date: Fri, 21 Aug 2026 23:31:41 -0400 Subject: [PATCH 1/4] pwgtopdf: limit bit conversion to row bytes The bit conversion callbacks operate on bytes, but convert_raster passed the pixel width. For 1-bit raster rows this made invert_bits overrun the row buffer. Pass the validated byte count and add an end-to-end AddressSanitizer regression for a 128-pixel, 16-byte black raster row. Assisted-by: OpenAI Codex --- Makefile.am | 3 + cupsfilters/pwgtopdf.c | 15 ++- cupsfilters/test-pwgtopdf-bit-row.sh | 168 +++++++++++++++++++++++++++ 3 files changed, 178 insertions(+), 8 deletions(-) create mode 100755 cupsfilters/test-pwgtopdf-bit-row.sh diff --git a/Makefile.am b/Makefile.am index 2689a6047..0f038b215 100644 --- a/Makefile.am +++ b/Makefile.am @@ -25,6 +25,7 @@ EXTRA_DIST = \ EXTRA_DIST += \ cupsfilters/test-pclm-overflow.sh \ + cupsfilters/test-pwgtopdf-bit-row.sh \ data/makePDFfromPS.sh \ data/classified.ps \ data/confidential.ps \ @@ -93,6 +94,7 @@ lib_LTLIBRARIES = libcupsfilters.la check_SCRIPTS = \ cupsfilters/testfilters.sh \ cupsfilters/test-pclm-overflow.sh \ + cupsfilters/test-pwgtopdf-bit-row.sh \ cupsfilters/test-pdftoraster-copy-height.sh check_PROGRAMS = \ @@ -117,6 +119,7 @@ TESTS = \ test-ps \ cupsfilters/testfilters.sh \ cupsfilters/test-pclm-overflow.sh \ + cupsfilters/test-pwgtopdf-bit-row.sh \ cupsfilters/test-pdftoraster-copy-height.sh # testcmyk # fails as it opens some image.ppm which is nowerhe to be found. diff --git a/cupsfilters/pwgtopdf.c b/cupsfilters/pwgtopdf.c index 0be268909..13bc68652 100644 --- a/cupsfilters/pwgtopdf.c +++ b/cupsfilters/pwgtopdf.c @@ -91,7 +91,7 @@ typedef unsigned char *(*convert_function)(unsigned char *src, // Bit conversion function typedef unsigned char *(*bit_convert_function)(unsigned char *src, unsigned char *dst, - unsigned int pixels); + unsigned int bytes); typedef struct pwgtopdf_doc_s // **** Document information **** { @@ -272,12 +272,12 @@ void free_pdf_info(struct pdf_info *info) static unsigned char* // O - output string of pixels invert_bits(unsigned char *src, // I - source chars unsigned char *dst, // O - destination chars - unsigned int pixels) // I - pixels + unsigned int bytes) // I - bytes { unsigned int i; // Invert black to grayscale... - for (i = pixels, dst = src; i > 0; i --, dst ++) + for (i = bytes, dst = src; i > 0; i --, dst ++) *dst = ~*dst; return (dst); @@ -286,13 +286,14 @@ invert_bits(unsigned char *src, // I - source chars static unsigned char* // O - Output string of bits no_bit_conversion(unsigned char *src, // I - Source chars - unsigned char *dst, // O - destination chars - unsigned int pixels) // I - Pixesl + unsigned char *dst, // O - destination chars + unsigned int bytes) // I - bytes { return (src); } + // // Color conversion functions // @@ -1634,7 +1635,7 @@ convert_raster(cups_raster_t *ras, #endif // perform bit operations if necessary - doc->bit_function(PixelBuffer, buff, width); + doc->bit_function(PixelBuffer, buff, bpl); // write lines and color convert when necessary pdf_set_line(info, cur_line, doc->conversion_function(PixelBuffer, @@ -2068,5 +2069,3 @@ cfFilterPWGToPDF(int inputfd, // I - File descriptor input stream return (ret); } - - diff --git a/cupsfilters/test-pwgtopdf-bit-row.sh b/cupsfilters/test-pwgtopdf-bit-row.sh new file mode 100755 index 000000000..98d3309cc --- /dev/null +++ b/cupsfilters/test-pwgtopdf-bit-row.sh @@ -0,0 +1,168 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +BUILD_ROOT="$(cd "${ROOT}/.." && pwd)" +LIBTOOL="${BUILD_ROOT}/libtool" +CC="${CC:-cc}" +SAN_FLAGS="${SAN_FLAGS:--fsanitize=address -fno-omit-frame-pointer}" +PKG_CFLAGS="$(pkg-config --cflags pdfio lcms2 2>/dev/null \ + || pkg-config --cflags pdfio lcms 2>/dev/null || true)" + +if ! printf 'int main(void){return 0;}\n' \ + | "${CC}" ${SAN_FLAGS} -x c - -o /dev/null >/dev/null 2>&1; then + echo "AddressSanitizer not available; skipping." >&2 + exit 77 +fi + +if [[ ! -x "${LIBTOOL}" ]]; then + echo "libtool helper not found at ${LIBTOOL}" >&2 + exit 99 +fi + +WORKDIR="$(mktemp -d "${TMPDIR:-/tmp}/pwgtopdf-bit-row.XXXXXX")" +cleanup() { + rm -rf "${WORKDIR}" +} +trap cleanup EXIT + +INPUT_PWG="${WORKDIR}/black-1bit.pwg" +OUTPUT_PDF="${WORKDIR}/output.pdf" +GENERATOR_SRC="${WORKDIR}/make-pwg.c" +GENERATOR_BIN="${WORKDIR}/make-pwg" +HARNESS_SRC="${WORKDIR}/run-filter.c" +HARNESS_OBJ="${WORKDIR}/run-filter.lo" +HARNESS_BIN="${WORKDIR}/run-filter" +RUN_LOG="${WORKDIR}/run.log" + +cat > "${GENERATOR_SRC}" <<'EOF' +#include +#include +#include +#include +#include + +int main(int argc, char **argv) { + cups_page_header2_t header; + unsigned char row[16] = {0}; + cups_raster_t *ras; + int fd; + + if (argc != 2) + return 1; + + fd = open(argv[1], O_CREAT | O_TRUNC | O_WRONLY, 0644); + if (fd < 0) + return 1; + + ras = cupsRasterOpen(fd, CUPS_RASTER_WRITE); + if (!ras) { + close(fd); + return 1; + } + + memset(&header, 0, sizeof(header)); + header.HWResolution[0] = header.HWResolution[1] = 300; + header.PageSize[0] = 612; + header.PageSize[1] = 792; + header.cupsPageSize[0] = 612.0f; + header.cupsPageSize[1] = 792.0f; + header.cupsImagingBBox[2] = 612.0f; + header.cupsImagingBBox[3] = 792.0f; + header.cupsWidth = 128; + header.cupsHeight = 1; + header.cupsBitsPerColor = 1; + header.cupsBitsPerPixel = 1; + header.cupsNumColors = 1; + header.cupsBytesPerLine = sizeof(row); + header.cupsColorOrder = CUPS_ORDER_CHUNKED; + header.cupsColorSpace = CUPS_CSPACE_K; + + if (!cupsRasterWriteHeader2(ras, &header) || + cupsRasterWritePixels(ras, row, sizeof(row)) != sizeof(row)) { + cupsRasterClose(ras); + close(fd); + return 1; + } + + cupsRasterClose(ras); + close(fd); + return 0; +} +EOF + +cat > "${HARNESS_SRC}" <<'EOF' +#include +#include +#include +#include + +// Compile the production implementation with ASan even when the surrounding +// project build is not sanitized. +#include "cupsfilters/pwgtopdf.c" + +int main(int argc, char **argv) { + cf_filter_data_t data; + cf_filter_out_format_t outformat = CF_FILTER_OUT_FORMAT_PDF; + int inputfd; + int outputfd; + int result; + + if (argc != 3) + return 1; + + inputfd = open(argv[1], O_RDONLY); + outputfd = open(argv[2], O_CREAT | O_TRUNC | O_WRONLY, 0644); + if (inputfd < 0 || outputfd < 0) + return 1; + + memset(&data, 0, sizeof(data)); + data.content_type = (char *)"image/pwg-raster"; + data.final_content_type = (char *)"application/pdf"; + + result = cfFilterPWGToPDF(inputfd, outputfd, 1, &data, &outformat); + close(inputfd); + close(outputfd); + return result; +} +EOF + +"${CC}" -std=c11 -O0 ${SAN_FLAGS} -o "${GENERATOR_BIN}" \ + "${GENERATOR_SRC}" -lcups +"${GENERATOR_BIN}" "${INPUT_PWG}" + +"${LIBTOOL}" --mode=compile --tag=CC "${CC}" -std=gnu11 -O0 \ + -D_GNU_SOURCE ${SAN_FLAGS} \ + -I"${BUILD_ROOT}" -I"${BUILD_ROOT}/cupsfilters" ${PKG_CFLAGS} \ + -c "${HARNESS_SRC}" -o "${HARNESS_OBJ}" >/dev/null + +"${LIBTOOL}" --mode=link --tag=CC "${CC}" ${SAN_FLAGS} "${HARNESS_OBJ}" \ + "${BUILD_ROOT}/libcupsfilters.la" -lcups -o "${HARNESS_BIN}" >/dev/null + +ASAN_OPTS="${ASAN_OPTIONS:-detect_leaks=0,abort_on_error=0}" +set +e +"${LIBTOOL}" --mode=execute env \ + DYLD_LIBRARY_PATH="${BUILD_ROOT}/.libs${DYLD_LIBRARY_PATH:+:${DYLD_LIBRARY_PATH}}" \ + ASAN_OPTIONS="${ASAN_OPTS}" \ + "${HARNESS_BIN}" "${INPUT_PWG}" "${OUTPUT_PDF}" >"${RUN_LOG}" 2>&1 +STATUS=$? +set -e + +if [[ ${STATUS} -ne 0 ]]; then + cat "${RUN_LOG}" >&2 + echo "pwgtopdf exited with status ${STATUS}" >&2 + exit 1 +fi + +if grep -q "AddressSanitizer" "${RUN_LOG}"; then + cat "${RUN_LOG}" >&2 + echo "AddressSanitizer reported a memory error" >&2 + exit 1 +fi + +if [[ ! -s "${OUTPUT_PDF}" ]]; then + echo "No PDF output generated" >&2 + exit 1 +fi + +exit 0 From 67e09fa42e83942d990e02f7bc015e45ce40ed09 Mon Sep 17 00:00:00 2001 From: Darren Carreras Date: Fri, 21 Aug 2026 23:53:05 -0400 Subject: [PATCH 2/4] tests: link pwgtopdf regression dependencies Link the sanitizer harness through pkg-config so lcms2, PDFio, and the selected CUPS generation are available across the CI matrix. Assisted-by: OpenAI Codex --- cupsfilters/test-pwgtopdf-bit-row.sh | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/cupsfilters/test-pwgtopdf-bit-row.sh b/cupsfilters/test-pwgtopdf-bit-row.sh index 98d3309cc..928ddfde1 100755 --- a/cupsfilters/test-pwgtopdf-bit-row.sh +++ b/cupsfilters/test-pwgtopdf-bit-row.sh @@ -6,8 +6,10 @@ BUILD_ROOT="$(cd "${ROOT}/.." && pwd)" LIBTOOL="${BUILD_ROOT}/libtool" CC="${CC:-cc}" SAN_FLAGS="${SAN_FLAGS:--fsanitize=address -fno-omit-frame-pointer}" -PKG_CFLAGS="$(pkg-config --cflags pdfio lcms2 2>/dev/null \ - || pkg-config --cflags pdfio lcms 2>/dev/null || true)" +PKG_CFLAGS="$(pkg-config --cflags lcms2 pdfio cups 2>/dev/null \ + || pkg-config --cflags lcms2 pdfio cups3 2>/dev/null || true)" +PKG_LIBS="$(pkg-config --libs lcms2 pdfio cups 2>/dev/null \ + || pkg-config --libs lcms2 pdfio cups3 2>/dev/null || true)" if ! printf 'int main(void){return 0;}\n' \ | "${CC}" ${SAN_FLAGS} -x c - -o /dev/null >/dev/null 2>&1; then @@ -128,7 +130,7 @@ int main(int argc, char **argv) { EOF "${CC}" -std=c11 -O0 ${SAN_FLAGS} -o "${GENERATOR_BIN}" \ - "${GENERATOR_SRC}" -lcups + ${PKG_CFLAGS} "${GENERATOR_SRC}" ${PKG_LIBS} "${GENERATOR_BIN}" "${INPUT_PWG}" "${LIBTOOL}" --mode=compile --tag=CC "${CC}" -std=gnu11 -O0 \ @@ -137,7 +139,8 @@ EOF -c "${HARNESS_SRC}" -o "${HARNESS_OBJ}" >/dev/null "${LIBTOOL}" --mode=link --tag=CC "${CC}" ${SAN_FLAGS} "${HARNESS_OBJ}" \ - "${BUILD_ROOT}/libcupsfilters.la" -lcups -o "${HARNESS_BIN}" >/dev/null + "${BUILD_ROOT}/libcupsfilters.la" ${PKG_LIBS} -lm \ + -o "${HARNESS_BIN}" >/dev/null ASAN_OPTS="${ASAN_OPTIONS:-detect_leaks=0,abort_on_error=0}" set +e From 0724c2a0baddb9cd63755ced2c69cc007a6b3601 Mon Sep 17 00:00:00 2001 From: Darren Carreras Date: Sat, 22 Aug 2026 00:08:11 -0400 Subject: [PATCH 3/4] tests: support CUPS 3 raster API --- cupsfilters/test-pwgtopdf-bit-row.sh | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/cupsfilters/test-pwgtopdf-bit-row.sh b/cupsfilters/test-pwgtopdf-bit-row.sh index 928ddfde1..20884c7e1 100755 --- a/cupsfilters/test-pwgtopdf-bit-row.sh +++ b/cupsfilters/test-pwgtopdf-bit-row.sh @@ -6,10 +6,19 @@ BUILD_ROOT="$(cd "${ROOT}/.." && pwd)" LIBTOOL="${BUILD_ROOT}/libtool" CC="${CC:-cc}" SAN_FLAGS="${SAN_FLAGS:--fsanitize=address -fno-omit-frame-pointer}" -PKG_CFLAGS="$(pkg-config --cflags lcms2 pdfio cups 2>/dev/null \ - || pkg-config --cflags lcms2 pdfio cups3 2>/dev/null || true)" -PKG_LIBS="$(pkg-config --libs lcms2 pdfio cups 2>/dev/null \ - || pkg-config --libs lcms2 pdfio cups3 2>/dev/null || true)" +PKG_CFLAGS="$(pkg-config --cflags lcms2 pdfio 2>/dev/null || true)" +PKG_LIBS="$(pkg-config --libs lcms2 pdfio 2>/dev/null || true)" + +if pkg-config --exists cups 2>/dev/null; then + PKG_CFLAGS+=" $(pkg-config --cflags cups)" + PKG_LIBS+=" $(pkg-config --libs cups)" +elif pkg-config --exists cups3 2>/dev/null; then + PKG_CFLAGS+=" $(pkg-config --cflags cups3)" + PKG_LIBS+=" $(pkg-config --libs cups3)" +elif command -v cups-config >/dev/null 2>&1; then + PKG_CFLAGS+=" $(cups-config --cflags)" + PKG_LIBS+=" $(cups-config --libs)" +fi if ! printf 'int main(void){return 0;}\n' \ | "${CC}" ${SAN_FLAGS} -x c - -o /dev/null >/dev/null 2>&1; then @@ -39,13 +48,14 @@ RUN_LOG="${WORKDIR}/run.log" cat > "${GENERATOR_SRC}" <<'EOF' #include +#include "cupsfilters/libcups2-private.h" #include #include #include #include int main(int argc, char **argv) { - cups_page_header2_t header; + cups_page_header_t header; unsigned char row[16] = {0}; cups_raster_t *ras; int fd; @@ -80,7 +90,7 @@ int main(int argc, char **argv) { header.cupsColorOrder = CUPS_ORDER_CHUNKED; header.cupsColorSpace = CUPS_CSPACE_K; - if (!cupsRasterWriteHeader2(ras, &header) || + if (!cupsRasterWriteHeader(ras, &header) || cupsRasterWritePixels(ras, row, sizeof(row)) != sizeof(row)) { cupsRasterClose(ras); close(fd); @@ -130,7 +140,7 @@ int main(int argc, char **argv) { EOF "${CC}" -std=c11 -O0 ${SAN_FLAGS} -o "${GENERATOR_BIN}" \ - ${PKG_CFLAGS} "${GENERATOR_SRC}" ${PKG_LIBS} + -I"${BUILD_ROOT}" ${PKG_CFLAGS} "${GENERATOR_SRC}" ${PKG_LIBS} "${GENERATOR_BIN}" "${INPUT_PWG}" "${LIBTOOL}" --mode=compile --tag=CC "${CC}" -std=gnu11 -O0 \ From dd695d0e7b9c8273130bb719d8143930124db0ff Mon Sep 17 00:00:00 2001 From: Darren Carreras Date: Sat, 22 Aug 2026 00:42:58 -0400 Subject: [PATCH 4/4] tests: skip unusable ASan runtimes Run a trivial sanitized executable before the regression test so QEMU jobs that can link but cannot initialize AddressSanitizer skip cleanly. Assisted-by: OpenAI Codex --- cupsfilters/test-pwgtopdf-bit-row.sh | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/cupsfilters/test-pwgtopdf-bit-row.sh b/cupsfilters/test-pwgtopdf-bit-row.sh index 20884c7e1..b3d7a9280 100755 --- a/cupsfilters/test-pwgtopdf-bit-row.sh +++ b/cupsfilters/test-pwgtopdf-bit-row.sh @@ -20,11 +20,18 @@ elif command -v cups-config >/dev/null 2>&1; then PKG_LIBS+=" $(cups-config --libs)" fi +# The compiler accepting -fsanitize=address is not enough: qemu-user builds +# can link the runtime but abort while initializing it. Run a trivial probe so +# those emulated CI legs skip instead of reporting a product-code failure. +asan_probe="$(mktemp "${TMPDIR:-/tmp}/asan-probe.XXXXXX")" if ! printf 'int main(void){return 0;}\n' \ - | "${CC}" ${SAN_FLAGS} -x c - -o /dev/null >/dev/null 2>&1; then - echo "AddressSanitizer not available; skipping." >&2 + | "${CC}" ${SAN_FLAGS} -x c - -o "${asan_probe}" >/dev/null 2>&1 \ + || ! "${asan_probe}" >/dev/null 2>&1; then + echo "AddressSanitizer not usable in this environment; skipping." >&2 + rm -f "${asan_probe}" exit 77 fi +rm -f "${asan_probe}" if [[ ! -x "${LIBTOOL}" ]]; then echo "libtool helper not found at ${LIBTOOL}" >&2