All vulnerabilities
GHSA-93g7-296p-j77j

FreeRDP: [RDPEAR] Truncated KERB_RPC_INTERNAL_NAME causes a client near-NULL dereference during cleanup

FreeRDP
CVSS
5.3Medium
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

[RDPEAR] Truncated KERB_RPC_INTERNAL_NAME causes a client near-NULL dereference during cleanup

> Private security report. Please keep this report and its attachments private > during triage and remediation.

Suggested advisory metadata

  • Product: FreeRDP / libfreerdp client RDPEAR
  • Affected versions: 3.9.0 through 3.31.1; current master at

4fa2b8efea2be8769e5499134f25182e64e13671

  • Patched versions: none known
  • Weakness: CWE-476, NULL Pointer Dereference
  • Suggested severity: Moderate
  • Conservative CVSS 3.1 vector:

CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:N/A:H (5.3)

Summary

The RDPEAR NDR reader commits an attacker-controlled NameCount to a partially initialized KERB_RPC_INTERNAL_NAME before it has read the Names ref-pointer. If the packet ends at that point, generic error cleanup calls the descriptor's destructor with nameHints.count >= 2 and Names == NULL.

The destructor indexes the null base. Forming &obj->Names[0] is already undefined behavior; in GCC builds and unoptimized Clang builds, iteration one forms &obj->Names[1] and reads the Buffer field at address 0x28. A production-shaped CreateApReqAuthenticatorReq input terminates the parser in FreeRDP's default GCC Release configuration and under Clang ASan at -O0.

Root cause

ndr_read_KERB_RPC_INTERNAL_NAME() in channels/rdpear/common/rdpear_common.c does this:

if (!ndr_read_uint16(context, s, &res->NameType) ||
    !ndr_read_uint16(context, s, &nameCount))
    return FALSE;

res->nameHints.count = nameCount;

return ndr_read_refpointer(context, s, &names.ptrId) &&
       ndr_push_deferreds(context, &names, 1);

On truncation before the ref-pointer, ndr_read_pointedMessageEx() invokes the destructor on the zero-initialized, partially read object:

for (UINT32 i = 0; i < obj->nameHints.count; i++)
    ndr_destroy_RPC_UNICODE_STRING(context, NULL, &obj->Names[i]);

No guard establishes that obj->Names was allocated before the loop.

Reachability and reproduction

poc_partial_internal_name_cleanup.c embeds a valid CreateApReqAuthenticatorReq primary body and valid EncryptionKey deferred body. It then supplies NameType = 1, NameCount = 2, and ends immediately before the required Names pointer.

The same malicious-server RDPEAR path described in the scalar report reaches the parser when Remote Credential Guard is active. The Clang ASan -O0 result is:

ERROR: AddressSanitizer: SEGV on unknown address 0x000000000028
    #0 ndr_destroy_RPC_UNICODE_STRING     rdpear_common.c:246
    #1 ndr_destroy_KERB_RPC_INTERNAL_NAME rdpear_common.c:370
    #2 ndr_descr_destroy_KERB_RPC_INTERNAL_NAME
    #3 ndr_read_pointedMessageEx          ndr.c:929
    #4 ndr_treat_deferred_read            ndr.c:976
    #5 main                               poc_partial_internal_name_cleanup.c:41

GCC 13.3 at -O0, -O2, and the FreeRDP Release-equivalent -O3 -DNDEBUG LTO configuration also terminates with SIGSEGV for NameCount >= 2. Clang 18.1 at -O2 instead inlines a null-base check and rejects the same packet without a signal, although UBSan still identifies the null-pointer arithmetic. The demonstrated effect is therefore a compiler-dependent client process crash, not a universal deterministic crash. This report does not claim control of the near-NULL address, an arbitrary read/write, or code execution.

Fix direction and validation

PROPOSED_FIX.diff makes cleanup a no-op when Names was never allocated, and REGRESSION_TEST.diff drives the partial pointed-message failure with only four bytes. An alternative is to commit nameHints.count only after the ref-pointer has been parsed successfully, while keeping destructor-side hardening as defense in depth.

After applying the supplied guard, the identical harness exits normally:

DEFERRED_REJECT_WITHOUT_CRASH

TestRdpear TestNdr and TestRdpear TestNdrEar both pass under ASan, and a 1,781,846-execution follow-up fuzz run found no additional sanitizer crash.

The affected-version and public-duplicate review are shared with the scalar report. No exact public match was found; private reports remain unsearchable.

Reporter attribution: Han Sang-Kyung / HAN-BAMBO (@HAN-BAMBO)

Text from GitHub Advisory Database

Affected versions 1

  • FreeRDP, c/c++>= 3.9.0, <= 3.31.1GitHub