All vulnerabilities
GHSA-f3vg-h45x-6wgf

FreeRDP: Heap OOB Read / Information Disclosure in WinPR Clipboard PNG→CF_DIB Synthesis

FreeRDP
CVSS
6.5Medium
ExploitationNo known exploitationNot in CISA KEV, which does not prove it is unexploitedCISA KEV
Exploit odds (EPSS)No scoreNo FIRST EPSS score in this snapshot yet
Fixed in3.32.1Fixed versionGitHub
Published5 Oct 2026Updated 5 Oct 2026GitHub

What fixes it

A fixed version on one release line is not the upgrade target for every line. Check your exact version and any vendor backports.

Description

Researcher: Kanishka De Silva (HEXER / HEXERLAB / @HEXER365) Vendor: FreeRDP Project — https://github.com/FreeRDP/FreeRDP Affected component: winpr/libwinpr/utils/image.c, winpr/libwinpr/clipboard/synthetic.c Affected version: master @ 32394214af7965c890d8b43be803d0e34d28f869 (2026-09-26, 3.32.1-dev0) Feature gate: WINPR_UTILS_IMAGE_PNG=ON (default in official Android & iOS clients) Report date: 2026-09-26

---

Runtime Verification Status

CONFIRMED — AddressSanitizer heap-buffer-overflow triggered on the exact upstream commit with a crafted 4 KB PNG payload via the public ClipboardSetData / ClipboardGetData API.

ASAN Report (reproduced locally)

==4548==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7f9cf78ef800
READ of size 2048 at 0x7f9cf78ef800 thread T0
    #0  __asan_memcpy
    #1  Stream_Write           winpr/include/winpr/stream.h:1192
    #2  winpr_bitmap_write_buffer  winpr/libwinpr/utils/image.c:339   <- OOB READ
    #3  winpr_image_write_buffer   winpr/libwinpr/utils/image.c:1387
    #4  clipboard_synthesize_image_format_to_bmp  winpr/libwinpr/clipboard/synthetic.c:565
    #5  clipboard_synthesize_image_png_to_bmp     winpr/libwinpr/clipboard/synthetic.c:588
    #6  ClipboardGetData       winpr/libwinpr/clipboard/clipboard.c:483
    #7  main (PoC)
 
0x7f9cf78ef800 is located 0 bytes after 524288-byte region [...]
allocated by thread T0 here:
    #1  winpr_read_png_from_buffer  winpr/libwinpr/utils/image.c:1106  <- undersized malloc
    #3  winpr_image_read_buffer     winpr/libwinpr/utils/image.c:664

Plain-build OOB Measurement

  • CF_DIB returned: 4,194,358 bytes
  • Legitimate pixel content: 524,288 bytes (2048×2048 ÷ 8)
  • Out-of-bounds bytes delivered to clipboard: 3,670,070 (~3.5 MiB)
  • Non-zero OOB bytes (confirmed heap contents, not unmapped): 3,661,884

---

Root Cause

For PNG inputs with bit_depth < 8 (1/2/4-bit grayscale or palette), winpr_read_png_from_buffer() allocates using the bit-packed footprint:

// image.c:1101-1119
const size_t stride = 1ULL * width * bpp / 8ull;   // e.g. 2048×1/8 = 256 bytes/row
const size_t size   = 1ULL * width * height * bpp / 8ull;  // PACKED total
rc = malloc(size);  // UNDERSIZED: only width*height/8 bytes

Every downstream consumer, including winpr_image_read_buffer(), derives stride assuming one byte per pixel for sub-8-bpp images:

// image.c:665-668
image->bytesPerPixel = (image->bitsPerPixel + 7) / 8;  // = 1 for bpp=1
image->scanline = image->width * image->bytesPerPixel;  // = width (not width/8)

winpr_bitmap_write_buffer() then copies full stride-byte rows (where stride = width) from the undersized allocation:

// image.c:335-341
for (size_t y = 0; y < height; y++) {
    const BYTE* line = &data[stride * y];  // stride = width (unpacked assumption)
    Stream_Write(s, line, stride);         // OOB READ once y >= height*bpp/8
}

The size parameter is WINPR_ATTR_UNUSED and provides no bounds check. The partial hardening in upstream commit 53b98fe does not close the hole because for a 1-bpp image both bpp_stride and stride evaluate to width, so the guard is never triggered.

Additional variant: width*bpp < 8 yields malloc(0), and re-encoding fills the returned buffer (or any allocation the allocator returns) entirely with uninitialized heap bytes (CWE-908).

---

Attack Vector and Data Flow

Malicious RDP server
  └─ CLIPRDR FormatDataResponse  "image/png"
     payload: crafted 1-bpp PNG (~4 KB wire size)
  └─ ClipboardSetData("image/png", ...)
  └─ Victim pastes into local app (or always-on clipboard monitor)
      └─ ClipboardGetData(CF_DIB)
          └─ clipboard_synthesize_image_png_to_bmp  (synthetic.c:588)
              └─ winpr_image_read_buffer             malloc(w*h/8)  <- undersized
              └─ winpr_bitmap_write_buffer           READ w*h bytes  <- OOB
                 => ~3.5 MiB adjacent heap memory delivered as CF_DIB
                 => same OOB bytes base64-encoded into HTML clipboard flavor

Impact: Adjacent process heap memory is disclosed to whichever application the user pastes into. The attacker controls the trigger timing and PNG dimensions.

---

Affected Configurations

  • WINPR_UTILS_IMAGE_PNG=ON (libpng decode path). **WITH_LODEPNG is NOT affected** — it always expands to 32 bpp.
  • Official FreeRDP Android client: builds with this option ON by default (client/Android/cmake/ExternalDeps.cmake:15). iOS external build forwards the same option.
  • Distro Linux packages: typically not affected (option usually OFF in package builds).
  • Introduced in WinPR 3.3.0 with the WINPR_UTILS_IMAGE_* option set; still vulnerable at master 3239421 (2026-09-26) inclusive of partial hardening from 53b98fe (contained in 3.32.0).

---

Reproduction Steps

git clone https://github.com/FreeRDP/FreeRDP.git && cd FreeRDP
git checkout 32394214af7965c890d8b43be803d0e34d28f869
 
# Build WinPR with ASAN, PNG support, no LTO
cmake -B build-asan -DBUILD_SHARED_LIBS=OFF -DWITH_SERVER=OFF \
      -DWITH_CLIENT=OFF -DWINPR_UTILS_IMAGE_PNG=ON \
      -DWITH_X11=OFF -DWITH_WAYLAND=OFF \
      -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=OFF \
      -DCMAKE_C_COMPILER=clang \
      -DCMAKE_C_FLAGS="-fsanitize=address -g -O1 -fno-omit-frame-pointer" \
      -DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address"
cmake --build build-asan -- -j$(nproc)
 
# Generate crafted payload (2048×2048, 1-bpp grayscale PNG, ~4 KB)
python3 gen_1bpp_png.py /tmp/poc_1bpp.png   # included with this advisory
 
# Compile PoC driver
clang -fsanitize=address -g -O1 -fno-omit-frame-pointer \
  -I build-asan/winpr/include -I winpr/include \
  PocClipboardPng.c \
  -Wl,--whole-archive build-asan/winpr/libwinpr/libwinpr3.a -Wl,--no-whole-archive \
  -lpng -lssl -lcrypto -lpthread -ldl -lm -lz \
  -lkrb5 -lk5crypto -lkrb5support -lcom_err -licui18n -licuuc -licudata \
  -o PocClipboardPng
 
# Trigger
ASAN_OPTIONS=detect_leaks=0 ./PocClipboardPng /tmp/poc_1bpp.png
# Expected: heap-buffer-overflow at image.c:339 / alloc at image.c:1106

---

Suggested Fix

The most robust fix: expand sub-8-bpp images to one byte per pixel during decode (PNG PNG_TRANSFORM_EXPAND), keeping the wImage invariant intact. Alternatively:

  • Allocate height × png_get_rowbytes() and propagate the packed stride consistently through all consumers, or
  • Reject bit_depth < 8 in winpr_read_png_from_buffer() (functional regression, but minimal change).

Additionally, activate the size parameter in winpr_bitmap_write_buffer() to bound row reads, and reject malloc(0) returns in the decode path.

Text from GitHub Advisory Database

Affected versions 1

  • FreeRDP, c<=3.32.0GitHub