All vulnerabilities
GHSA-gvq8-v2fm-ffxv

Heap out-of-bounds read parsing a crafted Remote Assistance (.msrcIncident) file in freerdp_assistance_parse_all_elements_of()

FreeRDP
CVSS
n/aMedium
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.0Fixed versionGitHub
Published28 Sep 2026Updated 28 Sep 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

This issue was found by Anthropic using Claude to study the security of open-source software, validated manually by me from Ada Logics.

Summary

FreeRDP clients accept Microsoft Remote Assistance invitation files (.msrcIncident). When such a file is passed to a client, freerdp_assistance_parse_file_buffer() parses it, decrypts the LHTICKET blob with the invitation password, and hands the resulting ConnectionString2 XML to a recursive element walker, freerdp_assistance_parse_all_elements_of(). That walker advances its cursor past each element it consumes but never subtracts the consumed byte count from the remaining-length counter. The scan window [data, data + len) therefore grows past the end of the heap buffer that holds the decrypted string, and the strnlen() that re-derives the length is itself the operation that reads out of bounds.

A remote party who hands a victim an invitation file and its password (email, chat, download — the normal distribution channel for these files) triggers a heap over-read in the stock xfreerdp client the moment the file is opened, before any RDP connection is attempted. The demonstrated effect is a client-side crash.

Affected code

At current master 43e8453d121709811051522d1d383b265c0bb63c:

libfreerdp/common/assistance.c:669-687 — freerdp_assistance_parse_all_elements_of()

while (freerdp_assistance_get_element(data, len, key, &val, &vlen))
{
    data = val + vlen;               // :679 cursor advances by (val+vlen)-data
    len = strnlen(data, len);        // :680 but len is only re-derived, using the OLD len as bound
    if (vlen > 0)
    {
        val[vlen - 1] = '\0';
        if (!fkt(file, val, vlen))
            return FALSE;
    }
}

data and len are meant to describe one buffer window. Each iteration moves data forward but len is not reduced by the same distance, so the window end data + len moves forward by exactly the number of bytes consumed on every iteration. Once the consumed distance pushes data + len past the end of the allocation, strnlen(data, len) reads out of bounds — it stops early only if it happens to meet a NUL first.

The over-read buffer is the caller's own allocation, strlen(ConnectionString2) + 1 bytes:

libfreerdp/common/assistance.c:762 — freerdp_assistance_parse_connection_string2()

char* str = _strdup(file->ConnectionString2);

The bounds guard inside freerdp_assistance_get_element() (assistance.c:660, if (tag + len > input + ilen) return FALSE;) does not help, because input + ilen is itself the already-inflated window end and is out of bounds by the time the loop has advanced a few times.

Reachable path (all frames in the shipped client, nothing mocked): main → freerdp_client_settings_parse_command_line → ... → freerdp_client_settings_parse_assistance_file (client/common/client.c:473) → freerdp_assistance_parse_file → ..._parse_file_buffer → ..._parse_uploadinfo → freerdp_assistance_decrypt2 (assistance.c:1016) → freerdp_assistance_parse_connection_string2 (:784, buffer allocated at :762) → ..._parse_find_elements_of_c → freerdp_assistance_parse_all_elements_of (:680, the over-read).

CWE-125 (out-of-bounds read).

Primitive

Read, one byte per step (a strnlen scan), whose result is only used as a length for subsequent element lookups — the scanned bytes are not returned to or placed by the attacker, so this is not a direct information-disclosure primitive. The attacker controls where the scan starts and its bound; the scan runs past the end of an allocation whose size is the length of the attacker's own decrypted ConnectionString2 (tens to a few hundred bytes for a realistic invitation). The realistic outcome is a crash of the client process while opening the file. We report this as a client-side denial of service.

A secondary, undemonstrated concern: if a later iteration's freerdp_assistance_get_element() succeeds on out-of-bounds bytes, the loop performs val[vlen - 1] = '\0', a single-NUL out-of-bounds write. Whether that is reachable depends on heap contents adjacent to the allocation; we did not demonstrate it and this report claims only the read.

Evidence

Validated end-to-end through the shipped xfreerdp binary (default build, AddressSanitizer) on a 284-byte crafted invitation file, at released tag 3.30.0 (6b107f0aadbabc47941c5a5b893b88c01792af6d) and at master. The decrypted ConnectionString2 is <E><A/><C>ZZZZZZZ</C><C></C></E><C>X</C> (40 chars → a 41-byte _strdup); the trailing <C>X</C> after </E> is what leaves consumable element text beyond the window the walker was told about.

==8==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7b3e8e1e5739 ...
READ of size 1 at 0x7b3e8e1e5739 thread T0
    #0 in strnlen
    #1 in freerdp_assistance_parse_all_elements_of      libfreerdp/common/assistance.c:680:9
    #2 in freerdp_assistance_parse_find_elements_of_c    assistance.c:749:9
    #3 in freerdp_assistance_parse_connection_string2    assistance.c:784:9
    #4 in freerdp_assistance_decrypt2                    assistance.c:1016:7
    #5 in freerdp_assistance_parse_uploadinfo            assistance.c:1145:9
    #6 in freerdp_assistance_parse_file_buffer_int       assistance.c:1190:10
    #7 in freerdp_assistance_parse_file_buffer           assistance.c:1233:17
    #8 in freerdp_assistance_parse_file                  assistance.c:1293:11
    #9 in freerdp_client_settings_parse_assistance_file  client/common/client.c:473:11
   ...
   #15 in main                                           client/X11/cli/xfreerdp.c:78:11

0x7b3e8e1e5739 is located 0 bytes after 41-byte region [0x7b3e8e1e5710,0x7b3e8e1e5739)
allocated by _strdup <- freerdp_assistance_parse_connection_string2  assistance.c:762:14

Status at current master

The defect is unchanged at master 43e8453d121709811051522d1d383b265c0bb63c: the cursor/length skew is at assistance.c:679-680, and the reproducer fires there. It is present in released 3.28.0, 3.29.0 and 3.30.0 as well.

Reproduction

ANT-2026-BFD51MRM-e2e.zip

Reproducer attached as ANT-2026-BFD51MRM-e2e.zip. It builds unmodified upstream FreeRDP from source at the pinned commit on ubuntu:24.04 under clang/AddressSanitizer and runs the stock xfreerdp on the crafted file. No source patching, no mocking, no harness.

unzip ANT-2026-BFD51MRM-e2e.zip && cd ANT-2026-BFD51MRM
docker build -t ant-bfd51mrm docker-e2e/ && docker run --rm ant-bfd51mrm

# released 3.30.0 instead of master:
docker build --build-arg TARGET_COMMIT=6b107f0aadbabc47941c5a5b893b88c01792af6d \
    -t ant-bfd51mrm-330 docker-e2e/ && docker run --rm ant-bfd51mrm-330

The container runs xfreerdp poc.msrcIncident /assistance:48BJQ853X3B4; the assistance file is parsed during command-line handling, before any display or connection is needed.

Credit

Found by Anthropic using Claude to study the security of open-source software, validated manually by Ada Logics. Please credit Claude and Ada Logics in any advisory.

Handled under Anthropic's coordinated vulnerability disclosure policy (https://www.anthropic.com/coordinated-vulnerability-disclosure, 90 days). Reference ANT-2026-BFD51MRM; questions to security-cvd@anthropic.com.

Text from GitHub Advisory Database

Affected versions 1

  • FreeRDP, c<= 3.31.1GitHub