{"schema":"threatline.record/2","record":{"id":"GHSA-gvq8-v2fm-ffxv","aliases":["GHSA-gvq8-v2fm-ffxv"],"summary":"Heap out-of-bounds read parsing a crafted Remote Assistance (.msrcIncident) file in freerdp_assistance_parse_all_elements_of()","details":"This issue was found by Anthropic using Claude to study the security of open-source software, validated manually\r\nby me from Ada Logics.\r\n\r\n\r\n## Summary\r\n\r\nFreeRDP clients accept Microsoft Remote Assistance invitation files (`.msrcIncident`). When such a\r\nfile is passed to a client, `freerdp_assistance_parse_file_buffer()` parses it, decrypts the\r\n`LHTICKET` blob with the invitation password, and hands the resulting ConnectionString2 XML to a\r\nrecursive element walker, `freerdp_assistance_parse_all_elements_of()`. That walker advances its\r\ncursor past each element it consumes but never subtracts the consumed byte count from the\r\nremaining-length counter. The scan window `[data, data + len)` therefore grows past the end of the\r\nheap buffer that holds the decrypted string, and the `strnlen()` that re-derives the length is\r\nitself the operation that reads out of bounds.\r\n\r\nA remote party who hands a victim an invitation file and its password (email, chat, download — the\r\nnormal distribution channel for these files) triggers a heap over-read in the stock `xfreerdp`\r\nclient the moment the file is opened, before any RDP connection is attempted. The demonstrated\r\neffect is a client-side crash.\r\n\r\n## Affected code\r\n\r\nAt current master `43e8453d121709811051522d1d383b265c0bb63c`:\r\n\r\n`libfreerdp/common/assistance.c:669-687` — `freerdp_assistance_parse_all_elements_of()`\r\n```c\r\nwhile (freerdp_assistance_get_element(data, len, key, &val, &vlen))\r\n{\r\n    data = val + vlen;               // :679 cursor advances by (val+vlen)-data\r\n    len = strnlen(data, len);        // :680 but len is only re-derived, using the OLD len as bound\r\n    if (vlen > 0)\r\n    {\r\n        val[vlen - 1] = '\\0';\r\n        if (!fkt(file, val, vlen))\r\n            return FALSE;\r\n    }\r\n}\r\n```\r\n\r\n`data` and `len` are meant to describe one buffer window. Each iteration moves `data` forward but\r\n`len` is not reduced by the same distance, so the window end `data + len` moves forward by exactly\r\nthe number of bytes consumed on every iteration. Once the consumed distance pushes `data + len` past\r\nthe end of the allocation, `strnlen(data, len)` reads out of bounds — it stops early only if it\r\nhappens to meet a NUL first.\r\n\r\nThe over-read buffer is the caller's own allocation, `strlen(ConnectionString2) + 1` bytes:\r\n\r\n`libfreerdp/common/assistance.c:762` — `freerdp_assistance_parse_connection_string2()`\r\n```c\r\nchar* str = _strdup(file->ConnectionString2);\r\n```\r\n\r\nThe bounds guard inside `freerdp_assistance_get_element()` (`assistance.c:660`,\r\n`if (tag + len > input + ilen) return FALSE;`) does not help, because `input + ilen` is itself the\r\nalready-inflated window end and is out of bounds by the time the loop has advanced a few times.\r\n\r\nReachable path (all frames in the shipped client, nothing mocked):\r\n`main` → `freerdp_client_settings_parse_command_line` → ... → `freerdp_client_settings_parse_assistance_file`\r\n(`client/common/client.c:473`) → `freerdp_assistance_parse_file` → `..._parse_file_buffer` →\r\n`..._parse_uploadinfo` → `freerdp_assistance_decrypt2` (`assistance.c:1016`) →\r\n`freerdp_assistance_parse_connection_string2` (`:784`, buffer allocated at `:762`) →\r\n`..._parse_find_elements_of_c` → `freerdp_assistance_parse_all_elements_of` (`:680`, the over-read).\r\n\r\nCWE-125 (out-of-bounds read).\r\n\r\n## Primitive\r\n\r\nRead, one byte per step (a `strnlen` scan), whose result is only used as a length for subsequent\r\nelement lookups — the scanned bytes are not returned to or placed by the attacker, so this is not a\r\ndirect information-disclosure primitive. The attacker controls where the scan starts and its bound;\r\nthe scan runs past the end of an allocation whose size is the length of the attacker's own decrypted\r\nConnectionString2 (tens to a few hundred bytes for a realistic invitation). The realistic outcome is\r\na crash of the client process while opening the file. We report this as a client-side denial of\r\nservice.\r\n\r\nA secondary, undemonstrated concern: if a later iteration's `freerdp_assistance_get_element()`\r\nsucceeds on out-of-bounds bytes, the loop performs `val[vlen - 1] = '\\0'`, a single-NUL\r\nout-of-bounds write. Whether that is reachable depends on heap contents adjacent to the allocation;\r\nwe did not demonstrate it and this report claims only the read.\r\n\r\n## Evidence\r\n\r\nValidated end-to-end through the shipped `xfreerdp` binary (default build, AddressSanitizer) on a\r\n284-byte crafted invitation file, at released tag 3.30.0\r\n(`6b107f0aadbabc47941c5a5b893b88c01792af6d`) and at master. The decrypted ConnectionString2 is\r\n`<E><A/><C>ZZZZZZZ</C><C></C></E><C>X</C>` (40 chars → a 41-byte `_strdup`); the trailing `<C>X</C>`\r\nafter `</E>` is what leaves consumable element text beyond the window the walker was told about.\r\n\r\n```\r\n==8==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7b3e8e1e5739 ...\r\nREAD of size 1 at 0x7b3e8e1e5739 thread T0\r\n    #0 in strnlen\r\n    #1 in freerdp_assistance_parse_all_elements_of      libfreerdp/common/assistance.c:680:9\r\n    #2 in freerdp_assistance_parse_find_elements_of_c    assistance.c:749:9\r\n    #3 in freerdp_assistance_parse_connection_string2    assistance.c:784:9\r\n    #4 in freerdp_assistance_decrypt2                    assistance.c:1016:7\r\n    #5 in freerdp_assistance_parse_uploadinfo            assistance.c:1145:9\r\n    #6 in freerdp_assistance_parse_file_buffer_int       assistance.c:1190:10\r\n    #7 in freerdp_assistance_parse_file_buffer           assistance.c:1233:17\r\n    #8 in freerdp_assistance_parse_file                  assistance.c:1293:11\r\n    #9 in freerdp_client_settings_parse_assistance_file  client/common/client.c:473:11\r\n   ...\r\n   #15 in main                                           client/X11/cli/xfreerdp.c:78:11\r\n\r\n0x7b3e8e1e5739 is located 0 bytes after 41-byte region [0x7b3e8e1e5710,0x7b3e8e1e5739)\r\nallocated by _strdup <- freerdp_assistance_parse_connection_string2  assistance.c:762:14\r\n```\r\n\r\n## Status at current master\r\n\r\nThe defect is unchanged at master `43e8453d121709811051522d1d383b265c0bb63c`: the\r\ncursor/length skew is at `assistance.c:679-680`, and the reproducer fires there. It is present in\r\nreleased 3.28.0, 3.29.0 and 3.30.0 as well.\r\n\r\n\r\n## Reproduction\r\n\r\n[ANT-2026-BFD51MRM-e2e.zip](https://github.com/user-attachments/files/32198131/ANT-2026-BFD51MRM-e2e.zip)\r\n\r\n\r\nReproducer attached as `ANT-2026-BFD51MRM-e2e.zip`. It builds unmodified upstream FreeRDP from\r\nsource at the pinned commit on `ubuntu:24.04` under clang/AddressSanitizer and runs the stock\r\n`xfreerdp` on the crafted file. No source patching, no mocking, no harness.\r\n\r\n```\r\nunzip ANT-2026-BFD51MRM-e2e.zip && cd ANT-2026-BFD51MRM\r\ndocker build -t ant-bfd51mrm docker-e2e/ && docker run --rm ant-bfd51mrm\r\n\r\n# released 3.30.0 instead of master:\r\ndocker build --build-arg TARGET_COMMIT=6b107f0aadbabc47941c5a5b893b88c01792af6d \\\r\n    -t ant-bfd51mrm-330 docker-e2e/ && docker run --rm ant-bfd51mrm-330\r\n```\r\n\r\nThe container runs `xfreerdp poc.msrcIncident /assistance:48BJQ853X3B4`; the assistance file is\r\nparsed during command-line handling, before any display or connection is needed.\r\n\r\n## Credit\r\n\r\nFound by Anthropic using Claude to study the security of open-source software, validated manually\r\nby Ada Logics. Please credit Claude and Ada Logics in any advisory.\r\n\r\nHandled under Anthropic's coordinated vulnerability disclosure policy\r\n(https://www.anthropic.com/coordinated-vulnerability-disclosure, 90 days). Reference\r\nANT-2026-BFD51MRM; questions to security-cvd@anthropic.com.\r\n","categories":["Unclassified"],"product":"FreeRDP","vendor":"","ecosystem":null,"affected_ranges":[{"source":"GitHub Advisory Database","source_url":"https://github.com/FreeRDP/FreeRDP/security/advisories/GHSA-gvq8-v2fm-ffxv","source_modified_at":"2026-09-28T08:51:41Z","fetched_at":"2026-10-27T10:24:27.527526Z","package":{"ecosystem":"c","name":"FreeRDP"},"range":"<= 3.31.1"}],"fixed_versions":[{"source":"GitHub Advisory Database","source_url":"https://github.com/FreeRDP/FreeRDP/security/advisories/GHSA-gvq8-v2fm-ffxv","source_modified_at":"2026-09-28T08:51:41Z","fetched_at":"2026-10-27T10:24:27.527526Z","package":{"ecosystem":"c","name":"FreeRDP"},"version":"3.32.0"}],"cvss":null,"severity":"Medium","kev":{"listed":false},"epss":{"probability":null,"percentile":null,"score_date":null},"published_at":"2026-09-28T08:51:41Z","modified_at":"2026-09-28T08:51:41Z","provenance":[{"source":"GitHub Advisory Database","url":"https://github.com/FreeRDP/FreeRDP/security/advisories/GHSA-gvq8-v2fm-ffxv","fetched_at":"2026-10-27T10:24:27.527526Z","source_modified_at":"2026-09-28T08:51:41Z"}],"sources":["GitHub Advisory Database"],"references":[],"locations":[],"motoko_assessment":{"status":"requires_architecture_review","automatic_remediation":false},"classification":{"method":"product-first software-layer rules","rules_version":"2.1","matched_on":null,"confidence":"unknown","reviewed":false},"change_timestamp":"2026-09-28T08:51:41Z","content_hash":"c286d64fb6d9616bb0b2a3ccc00111e58d9748ba70559e9c97ced45c58883900"}}
