All vulnerabilities
GHSA-q56j-jjh6-38jf

Allocation of Resources Without Limits or Throttling in FreeRDP

FreeRDP
CVSS
6.5Medium
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
Published29 Sep 2026Updated 29 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

Summary

When a user connects a FreeRDP client to a malicious or compromised RDP server with drive redirection enabled, the server can open a shared file and send IRP_MJ_READ with Length = 0xffffffff. drive_process_irp_read passes 4ull + Length to Stream_EnsureRemainingCapacity before reading the local file and without applying a per-request maximum.

On a 64-bit build, the capacity request is 4294967299 bytes. The stream implementation attempts to grow the output buffer and zeroes the added region, so one small network request can cause roughly 4 GiB of allocation and memory-write pressure. On a 32-bit build, conversion to size_t reduces the same capacity argument to three bytes while the file-read count remains UINT32_MAX, leaving the following file write under-reserved.

The user must initiate the RDP connection and share a drive. The server needs no account or prior access to the client. Once the server has obtained a valid shared-file identifier through the ordinary RDPDR protocol, the malicious length requires no further user interaction.

The vulnerable reservation expression is present in every inspected release from 3.0.0 through 3.31.1 and remains present on current master. Releases through 3.20.0 used Length + 4, which wraps before conversion. Release 3.20.1 changed it to 4ull + Length; that avoids the pre-conversion wrap on 64-bit builds but causes the oversized 64-bit allocation and still truncates on 32-bit builds. No fixed release is known.

The relevant source is drive_process_irp_read in channels/drive/client/drive_main.c:

Stream_Read_UINT32(irp->input, Length);
Stream_Read_UINT64(irp->input, Offset);
file = drive_get_file_by_id(drive, irp->FileId);

if (!file)
{
	irp->IoStatus = STATUS_UNSUCCESSFUL;
	Length = 0;
}
else if (!drive_file_seek(file, Offset))
{
	irp->IoStatus = drive_map_windows_err(GetLastError());
	Length = 0;
}

if (!Stream_EnsureRemainingCapacity(irp->output, 4ull + Length))
{
	WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!");
	return ERROR_INTERNAL_ERROR;
}
else if (Length == 0)
	Stream_Write_UINT32(irp->output, 0);
else
{
	BYTE* buffer = Stream_PointerAs(irp->output, BYTE) + sizeof(UINT32);

	if (!drive_file_read(file, buffer, &Length))
	{
		irp->IoStatus = drive_map_windows_err(GetLastError());
		Stream_Write_UINT32(irp->output, 0);
	}
	else
	{
		Stream_Write_UINT32(irp->output, Length);
		Stream_Seek(irp->output, Length);
	}
}

Stream_EnsureCapacity in winpr/libwinpr/utils/stream.c performs the growth and zero fill:

new_buf = (BYTE*)realloc(s->buffer, new_capacity);
if (!new_buf)
	return FALSE;

s->buffer = new_buf;
s->capacity = new_capacity;
s->length = new_capacity;
ZeroMemory(&s->buffer[old_capacity], s->capacity - old_capacity);

Reproduction

Save the following three files in one directory and run sh ./run.sh. The PoC safely reproduces the exact native arithmetic and extracts the compiled i386 values. It does not allocate 4 GiB, read a local file, contact an RDP server, or require FreeRDP development libraries.

Requirements are a POSIX shell, a GCC-compatible C compiler with i386 code-generation support, GNU objcopy, and od.

Expected output on a 64-bit host:

pointer_bits=64
normal_length=4096 normal_reservation=4100
maximum_length=4294967295 maximum_reservation=4294967299
i386_pocdata=0300000004100000
i386_maximum_reservation=3
i386_normal_reservation=4100

<!-- BEGIN FILE: drive_length_native.c -->

#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>

static uint64_t reservation(uint32_t length)
{
	return 4ULL + length;
}

int main(void)
{
	const uint32_t normal_length = 4096U;
	const uint32_t maximum_length = UINT32_MAX;

	printf("pointer_bits=%zu\n", sizeof(size_t) * 8U);
	printf("normal_length=%" PRIu32 " normal_reservation=%" PRIu64 "\n", normal_length,
	       reservation(normal_length));
	printf("maximum_length=%" PRIu32 " maximum_reservation=%" PRIu64 "\n", maximum_length,
	       reservation(maximum_length));

	if ((sizeof(size_t) != 8U) || (reservation(normal_length) != 4100ULL) ||
	    (reservation(maximum_length) != 4294967299ULL))
		return 1;
	return 0;
}

<!-- END FILE: drive_length_native.c -->

<!-- BEGIN FILE: drive_length_i386.c -->

typedef __SIZE_TYPE__ size_t;

__attribute__((used, section(".pocdata")))
size_t reservation_values[2] = {
	(size_t)(4ULL + 0xffffffffU),
	(size_t)(4ULL + 4096U),
};

<!-- END FILE: drive_length_i386.c -->

<!-- BEGIN FILE: run.sh -->

#!/bin/sh
set -eu

BASE_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
BUILD_DIR=${BUILD_DIR:-"$BASE_DIR/.build"}
CC=${CC:-cc}
OBJCOPY=${OBJCOPY:-objcopy}

mkdir -p "$BUILD_DIR"

"$CC" -std=c11 -Wall -Wextra -Werror -O2 \
	"$BASE_DIR/drive_length_native.c" -o "$BUILD_DIR/drive_length_native"
"$BUILD_DIR/drive_length_native"

"$CC" -m32 -ffreestanding -fno-pic -fno-pie -c \
	"$BASE_DIR/drive_length_i386.c" -o "$BUILD_DIR/drive_length_i386.o"
"$OBJCOPY" -j .pocdata -O binary \
	"$BUILD_DIR/drive_length_i386.o" "$BUILD_DIR/drive_length_i386.bin"

I386_BYTES=$(od -An -tx1 -v "$BUILD_DIR/drive_length_i386.bin" | tr -d ' \n')
printf 'i386_pocdata=%s\n' "$I386_BYTES"
test "$I386_BYTES" = "0300000004100000"
printf 'i386_maximum_reservation=3\n'
printf 'i386_normal_reservation=4100\n'

<!-- END FILE: run.sh -->

Remediation

Reject redirected-drive read lengths above a documented per-request maximum before calling Stream_EnsureRemainingCapacity or drive_file_read. After enforcing the limit, compute sizeof(UINT32) + (size_t)Length and retain the stream helper's overflow checks as defense in depth.

Add regression coverage for zero, the maximum accepted length, one byte above the limit, and UINT32_MAX on 32-bit and 64-bit builds. Oversized requests should be rejected before the local file reader is called.

Text from GitHub Advisory Database

Affected versions 1

  • FreeRDP, c>= 3.0.0GitHub