VYPR
High severity8.8NVD Advisory· Published May 29, 2026· Updated May 29, 2026

CVE-2026-44420

CVE-2026-44420

Description

FreeRDP is a free implementation of the Remote Desktop Protocol. Prior to 3.26.0, a malicious RDP client can trigger a heap-buffer-overflow write in FreeRDP's server-side clipboard (cliprdr) channel by sending a CB_CLIP_CAPS PDU with a too-small capabilitySetLength. This can crash the server process (remote DoS) and may be exploitable for code execution because it corrupts heap memory. This vulnerability is fixed in 3.26.0.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

A heap-buffer-overflow in FreeRDP's server-side clipboard channel allows a malicious client to crash the server or potentially execute code.

Vulnerability

In FreeRDP versions prior to 3.26.0, the server-side clipboard (cliprdr) channel contains a heap-buffer-overflow vulnerability in cliprdr_server_receive_capabilities() within channels/cliprdr/server/cliprdr_main.c. When processing a CB_CLIP_CAPS PDU, the function reads a client-controlled capabilitySetLength and uses it to allocate a heap buffer via realloc(). It then unconditionally writes a CLIPRDR_GENERAL_CAPABILITY_SET structure (12 bytes) into that buffer. By setting capabilitySetLength to a small value (e.g., 1), an attacker can cause a 1-byte allocation followed by an 11-byte out-of-bounds write [1].

Exploitation

An attacker with network access to a FreeRDP server can send a crafted CB_CLIP_CAPS PDU with an undersized capabilitySetLength. No authentication is required because the clipboard channel is negotiated during the initial RDP connection. The server process will then perform a heap-buffer-overflow write, corrupting adjacent heap memory. The exact crash site is in cliprdr_server_receive_general_capability() after the allocation [1].

Impact

Successful exploitation can cause a denial of service by crashing the server process. Because the overflow corrupts heap memory, it may be exploitable for arbitrary code execution, though no public exploit has been demonstrated. The vulnerability is rated High with a CVSS v3 score of 8.8 [1].

Mitigation

The vulnerability is fixed in FreeRDP version 3.26.0, released on 2026-05-29. Users should upgrade to this version or later. No workarounds are documented. The issue is tracked in GitHub Advisory GHSA-mvpx-xj7r-3p3r [1].

AI Insight generated on May 29, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

2
  • Freerdp/Freerdpinferred2 versions
    <3.26.0+ 1 more
    • (no CPE)range: <3.26.0
    • (no CPE)range: <3.26.0

Patches

2
0fba1f4dbff7

fix(cliprdr): validate capabilitySetLength in server caps

https://github.com/freerdp/freerdpKevin ValerioApr 29, 2026Fixed in 3.26.0via llm-release-walk
1 file changed · +34 4
  • channels/cliprdr/server/cliprdr_main.c+34 4 modified
    @@ -525,15 +525,45 @@ static UINT cliprdr_server_receive_capabilities(CliprdrServerContext* context, w
     	for (size_t index = 0; index < capabilities.cCapabilitiesSets; index++)
     	{
     		void* tmp = nullptr;
    -		if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
    +		const size_t cap_set_offset = cap_sets_size;
    +		if (!Stream_CheckAndLogRequiredLength(TAG, s, sizeof(CLIPRDR_CAPABILITY_SET)))
     			goto out;
     		Stream_Read_UINT16(s, capabilitySetType);   /* capabilitySetType (2 bytes) */
     		Stream_Read_UINT16(s, capabilitySetLength); /* capabilitySetLength (2 bytes) */
     
    +		if (capabilitySetLength < sizeof(CLIPRDR_CAPABILITY_SET))
    +		{
    +			WLog_ERR(TAG,
    +			         "invalid capabilitySetLength %" PRIu16 " < %" PRIuz
    +			         " (capabilitySetType=%" PRIu16 ")",
    +			         capabilitySetLength, sizeof(CLIPRDR_CAPABILITY_SET), capabilitySetType);
    +			goto out;
    +		}
    +
    +		if (!Stream_CheckAndLogRequiredLength(
    +		        TAG, s, (size_t)capabilitySetLength - sizeof(CLIPRDR_CAPABILITY_SET)))
    +			goto out;
    +
    +		switch (capabilitySetType)
    +		{
    +			case CB_CAPSTYPE_GENERAL:
    +				if (capabilitySetLength != sizeof(CLIPRDR_GENERAL_CAPABILITY_SET))
    +				{
    +					WLog_ERR(TAG,
    +					         "invalid capabilitySetLength %" PRIu16 " != %" PRIuz
    +					         " (capabilitySetType=CB_CAPSTYPE_GENERAL)",
    +					         capabilitySetLength, sizeof(CLIPRDR_GENERAL_CAPABILITY_SET));
    +					goto out;
    +				}
    +				break;
    +			default:
    +				WLog_ERR(TAG, "unknown cliprdr capability set: %" PRIu16 "", capabilitySetType);
    +				goto out;
    +		}
    +
     		cap_sets_size += capabilitySetLength;
     
    -		if (cap_sets_size > 0)
    -			tmp = realloc(capabilities.capabilitySets, cap_sets_size);
    +		tmp = realloc(capabilities.capabilitySets, cap_sets_size);
     		if (tmp == nullptr)
     		{
     			WLog_ERR(TAG, "capabilities.capabilitySets realloc failed!");
    @@ -543,7 +573,7 @@ static UINT cliprdr_server_receive_capabilities(CliprdrServerContext* context, w
     
     		capabilities.capabilitySets = (CLIPRDR_CAPABILITY_SET*)tmp;
     
    -		capSet = &(capabilities.capabilitySets[index]);
    +		capSet = (CLIPRDR_CAPABILITY_SET*)(((BYTE*)capabilities.capabilitySets) + cap_set_offset);
     
     		capSet->capabilitySetType = capabilitySetType;
     		capSet->capabilitySetLength = capabilitySetLength;
    
d00dc5d3be36

[channels,cliprdr] abort on duplicate caps

https://github.com/freerdp/freerdpArmin NovakApr 30, 2026Fixed in 3.26.0via llm-release-walk
1 file changed · +43 28
  • channels/cliprdr/server/cliprdr_main.c+43 28 modified
    @@ -473,6 +473,15 @@ static UINT cliprdr_server_receive_general_capability(CliprdrServerContext* cont
     	WINPR_ASSERT(context);
     	WINPR_ASSERT(cap_set);
     
    +	if (cap_set->capabilitySetLength != sizeof(CLIPRDR_GENERAL_CAPABILITY_SET))
    +	{
    +		WLog_ERR(TAG,
    +		         "invalid capabilitySetLength %" PRIu16 " != %" PRIuz
    +		         " (capabilitySetType=CB_CAPSTYPE_GENERAL)",
    +		         cap_set->capabilitySetLength, sizeof(CLIPRDR_GENERAL_CAPABILITY_SET));
    +		return ERROR_INVALID_DATA;
    +	}
    +
     	if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
     		return ERROR_INVALID_DATA;
     
    @@ -497,6 +506,27 @@ static UINT cliprdr_server_receive_general_capability(CliprdrServerContext* cont
     	return CHANNEL_RC_OK;
     }
     
    +WINPR_ATTR_NODISCARD
    +static BOOL cliprdr_capabilities_contain_capset(const CLIPRDR_CAPABILITIES* capabilities,
    +                                                const CLIPRDR_CAPABILITY_SET* capset)
    +{
    +	WINPR_ASSERT(capabilities);
    +
    +	const BYTE* offset = (const BYTE*)capabilities->capabilitySets;
    +	for (UINT32 x = 0; x < capabilities->cCapabilitiesSets; x++)
    +	{
    +		const CLIPRDR_CAPABILITY_SET* cur = (const CLIPRDR_CAPABILITY_SET*)offset;
    +		offset += cur->capabilitySetLength;
    +
    +		if (cur->capabilitySetType == capset->capabilitySetType)
    +		{
    +			WLog_ERR(TAG, "duplicate  CLIPRDR_CAPABILITY_SET %" PRIu16, cur->capabilitySetType);
    +			return TRUE;
    +		}
    +	}
    +	return FALSE;
    +}
    +
     /**
      * Function description
      *
    @@ -505,12 +535,9 @@ static UINT cliprdr_server_receive_general_capability(CliprdrServerContext* cont
     static UINT cliprdr_server_receive_capabilities(CliprdrServerContext* context, wStream* s,
                                                     const CLIPRDR_HEADER* header)
     {
    -	UINT16 capabilitySetType = 0;
    -	UINT16 capabilitySetLength = 0;
     	UINT error = ERROR_INVALID_DATA;
     	size_t cap_sets_size = 0;
     	CLIPRDR_CAPABILITIES capabilities = WINPR_C_ARRAY_INIT;
    -	CLIPRDR_CAPABILITY_SET* capSet = nullptr;
     
     	WINPR_ASSERT(context);
     	WINPR_UNUSED(header);
    @@ -519,17 +546,16 @@ static UINT cliprdr_server_receive_capabilities(CliprdrServerContext* context, w
     	if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
     		return ERROR_INVALID_DATA;
     
    -	Stream_Read_UINT16(s, capabilities.cCapabilitiesSets); /* cCapabilitiesSets (2 bytes) */
    +	const UINT16 cCapabilitiesSets = Stream_Get_UINT16(s); /* cCapabilitiesSets (2 bytes) */
     	Stream_Seek_UINT16(s);                                 /* pad1 (2 bytes) */
     
    -	for (size_t index = 0; index < capabilities.cCapabilitiesSets; index++)
    +	for (size_t index = 0; index < cCapabilitiesSets; index++)
     	{
    -		void* tmp = nullptr;
     		const size_t cap_set_offset = cap_sets_size;
     		if (!Stream_CheckAndLogRequiredLength(TAG, s, sizeof(CLIPRDR_CAPABILITY_SET)))
     			goto out;
    -		Stream_Read_UINT16(s, capabilitySetType);   /* capabilitySetType (2 bytes) */
    -		Stream_Read_UINT16(s, capabilitySetLength); /* capabilitySetLength (2 bytes) */
    +		const UINT16 capabilitySetType = Stream_Get_UINT16(s);   /* capabilitySetType (2 bytes) */
    +		const UINT16 capabilitySetLength = Stream_Get_UINT16(s); /* capabilitySetLength (2 bytes) */
     
     		if (capabilitySetLength < sizeof(CLIPRDR_CAPABILITY_SET))
     		{
    @@ -544,40 +570,29 @@ static UINT cliprdr_server_receive_capabilities(CliprdrServerContext* context, w
     		        TAG, s, (size_t)capabilitySetLength - sizeof(CLIPRDR_CAPABILITY_SET)))
     			goto out;
     
    -		switch (capabilitySetType)
    -		{
    -			case CB_CAPSTYPE_GENERAL:
    -				if (capabilitySetLength != sizeof(CLIPRDR_GENERAL_CAPABILITY_SET))
    -				{
    -					WLog_ERR(TAG,
    -					         "invalid capabilitySetLength %" PRIu16 " != %" PRIuz
    -					         " (capabilitySetType=CB_CAPSTYPE_GENERAL)",
    -					         capabilitySetLength, sizeof(CLIPRDR_GENERAL_CAPABILITY_SET));
    -					goto out;
    -				}
    -				break;
    -			default:
    -				WLog_ERR(TAG, "unknown cliprdr capability set: %" PRIu16 "", capabilitySetType);
    -				goto out;
    -		}
    -
     		cap_sets_size += capabilitySetLength;
     
    -		tmp = realloc(capabilities.capabilitySets, cap_sets_size);
    +		CLIPRDR_CAPABILITY_SET* tmp = realloc(capabilities.capabilitySets, cap_sets_size);
     		if (tmp == nullptr)
     		{
     			WLog_ERR(TAG, "capabilities.capabilitySets realloc failed!");
     			free(capabilities.capabilitySets);
     			return CHANNEL_RC_NO_MEMORY;
     		}
     
    -		capabilities.capabilitySets = (CLIPRDR_CAPABILITY_SET*)tmp;
    +		capabilities.capabilitySets = tmp;
     
    -		capSet = (CLIPRDR_CAPABILITY_SET*)(((BYTE*)capabilities.capabilitySets) + cap_set_offset);
    +		CLIPRDR_CAPABILITY_SET* capSet =
    +		    (CLIPRDR_CAPABILITY_SET*)(((BYTE*)capabilities.capabilitySets) + cap_set_offset);
     
     		capSet->capabilitySetType = capabilitySetType;
     		capSet->capabilitySetLength = capabilitySetLength;
     
    +		if (cliprdr_capabilities_contain_capset(&capabilities, capSet))
    +			goto out;
    +
    +		capabilities.cCapabilitiesSets++;
    +
     		switch (capSet->capabilitySetType)
     		{
     			case CB_CAPSTYPE_GENERAL:
    

Vulnerability mechanics

Root cause

"Missing validation of the client-supplied `capabilitySetLength` field allows a heap-buffer-overflow write when the server allocates a buffer based on the attacker-controlled size but writes a larger fixed-size struct into it."

Attack vector

A malicious RDP client sends a crafted `CB_CLIP_CAPS` PDU with a `capabilitySetLength` field set to a value smaller than the size of the `CLIPRDR_GENERAL_CAPABILITY_SET` struct (e.g., 1 byte). The server allocates a heap buffer of that undersized length and then writes the full 12-byte struct into it, causing an 11-byte heap-buffer-overflow write. This corrupts heap memory, leading to a remote denial of service and potentially exploitable for remote code execution. The attack requires only that the server has the cliprdr channel enabled and the attacker can reach the CLIPRDR capability exchange.

Affected code

The vulnerability resides in `channels/cliprdr/server/cliprdr_main.c` within the `cliprdr_server_receive_capabilities()` function. This function is called from `cliprdr_server_receive_pdu()` when a `CB_CLIP_CAPS` PDU is dispatched. The bug is in the capability parsing routine where `capabilitySetLength` is read from the client-controlled stream and used to grow a heap buffer via `realloc()` before unconditionally writing a 12-byte `CLIPRDR_GENERAL_CAPABILITY_SET` struct into it.

What the fix does

The patch rejects `capabilitySetLength` values smaller than the minimum expected struct sizes before allocating and writing. Instead of blindly using the attacker-supplied length for the allocation, the fix ensures the allocation is sized based on the actual struct size that will be written. This prevents the heap-buffer-overflow by guaranteeing the buffer is large enough to hold the `CLIPRDR_GENERAL_CAPABILITY_SET` data before it is written.

Preconditions

  • configThe FreeRDP server must have the cliprdr (clipboard) server channel enabled.
  • networkThe attacker must be able to reach the CLIPRDR capability exchange phase of the RDP connection.
  • authThe attacker must be an authenticated RDP client (CVSS PR:L).
  • inputThe attacker sends a crafted CB_CLIP_CAPS PDU with an undersized capabilitySetLength field.

Reproduction

The advisory includes a complete PoC program (`repro_bug.c`) that reproduces the issue with ASAN. Steps: clone FreeRDP at commit 23b36cd, build with ASAN, compile the PoC linking against the built libraries, and run with `ASAN_OPTIONS='detect_leaks=0:abort_on_error=1:halt_on_error=1:print_stacktrace=1' ./repro_bug`. ASAN reports a heap-buffer-overflow write at `channels/cliprdr/server/cliprdr_main.c:548` with the allocation at line 536.

Generated on May 29, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

1

News mentions

0

No linked articles in our index yet.