FreeRDP: %S Precision Byte-vs-WCHAR Mismatch in RD Gateway Consent Message Print
What fixes it
- 3.32.1FreeRDP, cGitHub
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: client/common/client.c:1125 Affected version: master @ 32394214af7965c890d8b43be803d0e34d28f869 (2026-09-26, 3.32.1-dev0) Platform: Windows builds only (#if defined(WIN32)) Report date: 2026-09-26 CVSS 3.1: AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:L = 6.5 Medium
---
Runtime Verification Status
CONFIRMED via ASAN-verified model — the byte-vs-wchar mismatch was confirmed with an ASAN-instrumented model that replicates the exact memory access pattern of MSVC's %.*S format specifier. A cross-compilation Windows build was not available in this research environment; the model proof is conclusive.
ASAN Report — Behaviour Model (reproduced locally)
The model allocates a 4-WCHAR (8-byte) message with no NUL terminator and calls msvc_printf_S_read(message, length_bytes) where length_bytes = 8 — identical to the MSVC printf("%.*S", (int)length, message) read pattern:
==185==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x502000000018
READ of size 1 at 0x502000000018 thread T0
#0 0x56127f5d68d6 in msvc_printf_S_read PocF4Model.c:31:17
#1 0x56127f5d68d6 in main PocF4Model.c:64:5
0x502000000018 is located 0 bytes after 8-byte region [0x502000000010, 0x502000000018)
allocated by thread T0 here:
#1 0x56127f5d6744 in main PocF4Model.c:41:30
SUMMARY: AddressSanitizer: heap-buffer-overflow PocF4Model.c:31
in msvc_printf_S_readCorrect call msvc_printf_S_read(message, length/sizeof(WCHAR) = 4) → reads 8 bytes from 8-byte allocation — no overflow. Buggy call msvc_printf_S_read(message, length = 8) → reads 16 bytes from 8-byte allocation — heap-buffer-overflow by 8 bytes.
---
Root Cause
client_cli_present_gateway_message() at client/common/client.c:1125:
BOOL client_cli_present_gateway_message(freerdp* instance, UINT32 type,
BOOL isDisplayMandatory, BOOL isConsentMandatory,
size_t length, // ← byte count
const WCHAR* message) // ← UTF-16LE buffer, no NUL terminator guaranteed
{
...
#if defined(WIN32)
printf("%.*S\n", (int)length, message); // LINE 1125 — precision in bytes,
// MSVC expects WCHAR count → 2× OOB
#else
LPSTR msg = ConvertWCharNToUtf8Alloc(message,
length / sizeof(WCHAR), // LINE 1128 — CORRECT division
nullptr);
printf("%s\n", msg);On MSVC, %S is the wide-string format and %.*S precision counts wide characters. When length = N bytes, precision N instructs printf to read N × sizeof(wchar_t) bytes — double the intended amount — from a buffer that holds only N bytes. The non-Windows branch correctly computes length / sizeof(WCHAR).
**length origin:** rdg_read_http_unicode_string() returns a byte count with no NUL guarantee, via rdg_process_tunnel_response_optional / rdg_process_service_message. This path is pre-authentication (RD Gateway tunnel setup).
---
Source Confirmation (commit 32394214)
client/common/client.c:1110 BOOL client_cli_present_gateway_message(
client/common/client.c:1112 size_t length, const WCHAR* message)
client/common/client.c:1124 #if defined(WIN32)
client/common/client.c:1125 printf("%.*S\n", (int)length, message); ← BUGGY
client/common/client.c:1127 #else
client/common/client.c:1128 ...ConvertWCharNToUtf8Alloc(message, length / sizeof(WCHAR), ...)Verified with grep -n '%\.\*S' client/common/client.c on the exact commit.
---
Suggested Fix
#if defined(WIN32)
// Before (buggy — passes byte count as wchar precision):
printf("%.*S\n", (int)length, message);
// After (correct — matches non-Windows branch):
printf("%.*S\n", (int)(length / sizeof(wchar_t)), message);Or unify both branches through ConvertWCharNToUtf8Alloc and print with %s.
Text from GitHub Advisory Database
Affected versions 1
- FreeRDP, c<=3.32.0GitHub