Backport several CVE fixes

It fixes CVE-2026-67289, CVE-2026-67299 and CVE-2026-68580.

Resolves: RHEL-222792, RHEL-222965, RHEL-223607

Co-authored-by: Ondrej Holy <oholy@redhat.com>
This commit is contained in:
RHEL Packaging Agent 2026-08-17 10:22:25 +00:00 committed by Ondrej Holy
parent d8e8413c9c
commit c2efdf022e
7 changed files with 866 additions and 1 deletions

View File

@ -0,0 +1,37 @@
From 22cc4e8e563eaed5628bf510f26091d554d8533d Mon Sep 17 00:00:00 2001
From: Armin Novak <armin.novak@thincast.com>
Date: Sun, 12 Jul 2026 17:37:48 +0200
Subject: [PATCH] [channels,audin] limit FramesPerPacket
---
channels/audin/client/audin_main.c | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/channels/audin/client/audin_main.c b/channels/audin/client/audin_main.c
index 4c5e7977d..697fd2848 100644
--- a/channels/audin/client/audin_main.c
+++ b/channels/audin/client/audin_main.c
@@ -487,7 +487,6 @@ static UINT audin_process_open(AUDIN_PLUGIN* audin, AUDIN_CHANNEL_CALLBACK* call
Stream_Read_UINT32(s, initialFormat);
WLog_Print(audin->log, WLOG_DEBUG, "FramesPerPacket=%" PRIu32 " initialFormat=%" PRIu32 "",
FramesPerPacket, initialFormat);
- audin->FramesPerPacket = FramesPerPacket;
if (initialFormat >= callback->formats_count)
{
@@ -496,6 +495,15 @@ static UINT audin_process_open(AUDIN_PLUGIN* audin, AUDIN_CHANNEL_CALLBACK* call
return ERROR_INVALID_DATA;
}
+ /* The RDP protocol field allows UINT32_MAX, but most backend API use INT32 as input parameter
+ * or do some math with it, so ensure that the value does not exceed reasonable limits */
+ if (FramesPerPacket >= INT32_MAX)
+ {
+ WLog_Print(audin->log, WLOG_ERROR, "invalid frames per packet %" PRIu32, FramesPerPacket);
+ return ERROR_INVALID_DATA;
+ }
+
+ audin->FramesPerPacket = FramesPerPacket;
audin->format = &callback->formats[initialFormat];
if (!audin_open_device(audin, callback))

View File

@ -0,0 +1,222 @@
From cc57fb28a27f8563375ac435a9f16985135a1789 Mon Sep 17 00:00:00 2001
From: Armin Novak <armin.novak@thincast.com>
Date: Wed, 15 Jul 2026 17:52:28 +0200
Subject: [PATCH] [channels,audin] tighten parameter checks
---
channels/audin/client/alsa/audin_alsa.c | 4 +--
channels/audin/client/opensles/opensl_io.c | 40 ++++++++++++++--------
channels/audin/client/opensles/opensl_io.h | 5 ++-
channels/audin/client/oss/audin_oss.c | 3 ++
channels/audin/client/sndio/audin_sndio.c | 9 +++--
channels/audin/client/winmm/audin_winmm.c | 17 ++++-----
6 files changed, 48 insertions(+), 30 deletions(-)
diff --git a/channels/audin/client/alsa/audin_alsa.c b/channels/audin/client/alsa/audin_alsa.c
index 20564dc04..3882b734f 100644
--- a/channels/audin/client/alsa/audin_alsa.c
+++ b/channels/audin/client/alsa/audin_alsa.c
@@ -139,8 +139,8 @@ static DWORD WINAPI audin_alsa_thread_func(LPVOID arg)
goto out;
}
- buffer =
- (BYTE*)calloc(alsa->frames_per_packet + alsa->aformat.nBlockAlign, alsa->bytes_per_frame);
+ buffer = (BYTE*)calloc(1ull * alsa->frames_per_packet + alsa->aformat.nBlockAlign,
+ alsa->bytes_per_frame);
if (!buffer)
{
diff --git a/channels/audin/client/opensles/opensl_io.c b/channels/audin/client/opensles/opensl_io.c
index a447dac34..c4598a634 100644
--- a/channels/audin/client/opensles/opensl_io.c
+++ b/channels/audin/client/opensles/opensl_io.c
@@ -54,10 +54,10 @@ struct opensl_stream
SLRecordItf recorderRecord;
SLAndroidSimpleBufferQueueItf recorderBufferQueue;
- unsigned int inchannels;
- unsigned int sr;
- unsigned int buffersize;
- unsigned int bits_per_sample;
+ size_t inchannels;
+ size_t sr;
+ size_t buffersize;
+ size_t bits_per_sample;
queue_element* prep;
queue_element* next;
@@ -108,8 +108,8 @@ engine_end:
static SLresult openSLRecOpen(OPENSL_STREAM* p)
{
SLresult result;
- SLuint32 sr = p->sr;
- SLuint32 channels = p->inchannels;
+ SLuint32 sr = WINPR_ASSERTING_INT_CAST(SLuint32, p->sr);
+ SLuint32 channels = WINPR_ASSERTING_INT_CAST(SLuint32, p->inchannels);
WINPR_ASSERT(!p->recorderObject);
if (channels)
@@ -202,7 +202,7 @@ static SLresult openSLRecOpen(OPENSL_STREAM* p)
}
else
{
- WLog_ERR(TAG, "bits_per_sample=%" PRIu32, p->bits_per_sample);
+ WLog_ERR(TAG, "bits_per_sample=%" PRIuz, p->bits_per_sample);
return -1;
}
@@ -314,24 +314,31 @@ static void opensles_queue_element_free(void* obj)
}
// open the android audio device for input
-OPENSL_STREAM* android_OpenRecDevice(void* context, opensl_receive_t receive, int sr,
- int inchannels, int bufferframes, int bits_per_sample)
+OPENSL_STREAM* android_OpenRecDevice(void* context, opensl_receive_t receive, size_t sr,
+ size_t inchannels, size_t bufferframes, size_t bits_per_sample)
{
- OPENSL_STREAM* p;
-
if (!context || !receive)
return nullptr;
- p = (OPENSL_STREAM*)calloc(1, sizeof(OPENSL_STREAM));
+ OPENSL_STREAM* p = (OPENSL_STREAM*)calloc(1, sizeof(OPENSL_STREAM));
if (!p)
return nullptr;
p->context = context;
p->receive = receive;
+ if (inchannels > INT32_MAX)
+ goto fail;
p->inchannels = inchannels;
+
+ if (sr > INT32_MAX)
+ goto fail;
p->sr = sr;
+
+ if (bufferframes > SIZE_MAX / 2)
+ goto fail;
p->buffersize = bufferframes;
+
p->bits_per_sample = bits_per_sample;
if ((p->bits_per_sample != 8) && (p->bits_per_sample != 16))
@@ -343,9 +350,14 @@ OPENSL_STREAM* android_OpenRecDevice(void* context, opensl_receive_t receive, in
if (openSLRecOpen(p) != SL_RESULT_SUCCESS)
goto fail;
+ const size_t bytesPerSample = p->bits_per_sample / 8ull;
+ if (p->buffersize >= SIZE_MAX / bytesPerSample)
+ goto fail;
+
+ const size_t queuesize = p->buffersize * bytesPerSample;
/* Create receive buffers, prepare them and start recording */
- p->prep = opensles_queue_element_new(p->buffersize * p->bits_per_sample / 8);
- p->next = opensles_queue_element_new(p->buffersize * p->bits_per_sample / 8);
+ p->prep = opensles_queue_element_new(queuesize);
+ p->next = opensles_queue_element_new(queuesize);
if (!p->prep || !p->next)
goto fail;
diff --git a/channels/audin/client/opensles/opensl_io.h b/channels/audin/client/opensles/opensl_io.h
index 4f9b2db4f..bdba57cab 100644
--- a/channels/audin/client/opensles/opensl_io.h
+++ b/channels/audin/client/opensles/opensl_io.h
@@ -56,10 +56,9 @@ extern "C"
size in frames. Returns a handle to the OpenSL stream
*/
WINPR_ATTR_MALLOC(android_CloseRecDevice, 1)
- WINPR_ATTR_NODISCARD
FREERDP_LOCAL OPENSL_STREAM* android_OpenRecDevice(void* context, opensl_receive_t receive,
- int sr, int inchannels, int bufferframes,
- int bits_per_sample);
+ size_t sr, size_t inchannels,
+ size_t bufferframes, size_t bits_per_sample);
#ifdef __cplusplus
};
#endif
diff --git a/channels/audin/client/oss/audin_oss.c b/channels/audin/client/oss/audin_oss.c
index f709130be..d2e10ef3d 100644
--- a/channels/audin/client/oss/audin_oss.c
+++ b/channels/audin/client/oss/audin_oss.c
@@ -190,6 +190,9 @@ static DWORD WINAPI audin_oss_thread_func(LPVOID arg)
buffer_size =
(1ull * oss->FramesPerPacket * oss->format.nChannels * (oss->format.wBitsPerSample / 8ull));
+ if (buffer_size > INT32_MAX)
+ goto err_out;
+
buffer = (BYTE*)calloc((buffer_size + sizeof(void*)), sizeof(BYTE));
if (nullptr == buffer)
diff --git a/channels/audin/client/sndio/audin_sndio.c b/channels/audin/client/sndio/audin_sndio.c
index d922855be..6da44e612 100644
--- a/channels/audin/client/sndio/audin_sndio.c
+++ b/channels/audin/client/sndio/audin_sndio.c
@@ -82,7 +82,7 @@ static void* audin_sndio_thread_func(void* arg)
struct sio_hdl* hdl;
struct sio_par par;
BYTE* buffer = nullptr;
- size_t n, nbytes;
+ size_t n;
AudinSndioDevice* sndio = (AudinSndioDevice*)arg;
UINT error = 0;
DWORD status;
@@ -125,8 +125,11 @@ static void* audin_sndio_thread_func(void* arg)
goto err_out;
}
- nbytes =
- (sndio->FramesPerPacket * sndio->format.nChannels * (sndio->format.wBitsPerSample / 8));
+ const size_t nbytes = (1ull * sndio->FramesPerPacket * sndio->format.nChannels *
+ (sndio->format.wBitsPerSample / 8));
+ if (nbytes > INT32_MAX)
+ goto err_out;
+
buffer = (BYTE*)calloc((nbytes + sizeof(void*)), sizeof(BYTE));
if (buffer == nullptr)
diff --git a/channels/audin/client/winmm/audin_winmm.c b/channels/audin/client/winmm/audin_winmm.c
index 0c1cd3867..573d1d6d1 100644
--- a/channels/audin/client/winmm/audin_winmm.c
+++ b/channels/audin/client/winmm/audin_winmm.c
@@ -169,8 +169,6 @@ static BOOL test_format_supported(const PWAVEFORMATEX pwfx)
static DWORD WINAPI audin_winmm_thread_func(LPVOID arg)
{
AudinWinmmDevice* winmm = (AudinWinmmDevice*)arg;
- char* buffer = nullptr;
- int size = 0;
WAVEHDR waveHdr[4] = WINPR_C_ARRAY_INIT;
DWORD status = 0;
MMRESULT rc = 0;
@@ -184,19 +182,22 @@ static DWORD WINAPI audin_winmm_thread_func(LPVOID arg)
return ERROR_INTERNAL_ERROR;
}
- size =
- (winmm->pwfx_cur->wBitsPerSample * winmm->pwfx_cur->nChannels * winmm->frames_per_packet +
- 7) /
- 8;
+ const size_t framesize =
+ 1ull * winmm->pwfx_cur->wBitsPerSample / 8ull * winmm->pwfx_cur->nChannels;
+ if (framesize > UINT32_MAX)
+ return ERROR_INTERNAL_ERROR;
+ const size_t size = framesize * winmm->frames_per_packet;
+ if (size > UINT32_MAX)
+ return ERROR_INTERNAL_ERROR;
for (int i = 0; i < 4; i++)
{
- buffer = (char*)malloc(size);
+ char* buffer = (char*)calloc(framesize, winmm->frames_per_packet);
if (!buffer)
return CHANNEL_RC_NO_MEMORY;
- waveHdr[i].dwBufferLength = size;
+ waveHdr[i].dwBufferLength = WINPR_ASSERTING_INT_CAST(DWORD, size);
waveHdr[i].dwFlags = 0;
waveHdr[i].lpData = buffer;
rc = waveInPrepareHeader(winmm->hWaveIn, &waveHdr[i], sizeof(waveHdr[i]));

View File

@ -0,0 +1,60 @@
From bc37414b55a6061995fed3ee61836aaecfb055f6 Mon Sep 17 00:00:00 2001
From: Armin Novak <armin.novak@thincast.com>
Date: Tue, 7 Jul 2026 12:58:11 +0200
Subject: [PATCH] [core,message] fix update_message_WindowIcon
---
libfreerdp/core/message.c | 17 ++++++++---------
1 file changed, 8 insertions(+), 9 deletions(-)
diff --git a/libfreerdp/core/message.c b/libfreerdp/core/message.c
index 1d61fd333..e9dda6ac2 100644
--- a/libfreerdp/core/message.c
+++ b/libfreerdp/core/message.c
@@ -1297,30 +1297,29 @@ static BOOL update_message_WindowUpdate(rdpContext* context, const WINDOW_ORDER_
static BOOL update_message_WindowIcon(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
const WINDOW_ICON_ORDER* windowIcon)
{
- WINDOW_ORDER_INFO* wParam = nullptr;
- WINDOW_ICON_ORDER* lParam = nullptr;
- rdp_update_internal* up = nullptr;
-
if (!context || !context->update || !orderInfo || !windowIcon)
return FALSE;
- wParam = (WINDOW_ORDER_INFO*)malloc(sizeof(WINDOW_ORDER_INFO));
+ WINDOW_ORDER_INFO* wParam = (WINDOW_ORDER_INFO*)malloc(sizeof(WINDOW_ORDER_INFO));
if (!wParam)
return FALSE;
- CopyMemory(wParam, orderInfo, sizeof(WINDOW_ORDER_INFO));
- lParam = (WINDOW_ICON_ORDER*)calloc(1, sizeof(WINDOW_ICON_ORDER));
+ *wParam = *orderInfo;
+
+ WINDOW_ICON_ORDER* lParam = (WINDOW_ICON_ORDER*)calloc(1, sizeof(WINDOW_ICON_ORDER));
if (!lParam)
goto out_fail;
+ *lParam = *windowIcon;
lParam->iconInfo = calloc(1, sizeof(ICON_INFO));
if (!lParam->iconInfo)
goto out_fail;
- CopyMemory(lParam, windowIcon, sizeof(WINDOW_ICON_ORDER));
+ *lParam->iconInfo = *windowIcon->iconInfo;
+
WLog_VRB(TAG, "update_message_WindowIcon");
if (windowIcon->iconInfo->cbBitsColor > 0)
@@ -1356,7 +1355,7 @@ static BOOL update_message_WindowIcon(rdpContext* context, const WINDOW_ORDER_IN
windowIcon->iconInfo->cbColorTable);
}
- up = update_cast(context->update);
+ rdp_update_internal* up = update_cast(context->update);
return MessageQueue_Post(up->queue, (void*)context, MakeMessageId(WindowUpdate, WindowIcon),
(void*)wParam, (void*)lParam);
out_fail:

View File

@ -0,0 +1,178 @@
From c720ad9f8e9a308c035d029b1d61b249a525f474 Mon Sep 17 00:00:00 2001
From: Armin Novak <armin.novak@thincast.com>
Date: Thu, 9 Jul 2026 08:34:24 +0200
Subject: [PATCH] [core,redirection] check redirection values for validity
Add some basic validity checks for data provided by server.
---
libfreerdp/core/gateway/arm.c | 10 ++++++++++
libfreerdp/core/redirection.c | 25 ++++++++++++++++++++-----
libfreerdp/core/utils.c | 19 +++++++++++++++++++
libfreerdp/core/utils.h | 2 ++
4 files changed, 51 insertions(+), 5 deletions(-)
diff --git a/libfreerdp/core/gateway/arm.c b/libfreerdp/core/gateway/arm.c
index 804d0c4f9..4bb62882e 100644
--- a/libfreerdp/core/gateway/arm.c
+++ b/libfreerdp/core/gateway/arm.c
@@ -685,6 +685,9 @@ static BOOL arm_parse_ipv6(rdpSettings* settings, WINPR_JSON* ipv6, size_t* pAdd
return FALSE;
}
+ if (!winpr_str_is_valid_url(addr))
+ return FALSE;
+
if (!freerdp_settings_set_pointer_array(settings, FreeRDP_TargetNetAddresses,
(*pAddressIdx)++, addr))
return FALSE;
@@ -722,6 +725,10 @@ static BOOL arm_parse_ipv4(rdpSettings* settings, WINPR_JSON* ipv4, size_t* pAdd
WLog_ERR(TAG, "Exceeded TargetNetAddresses, parsing failed");
return FALSE;
}
+
+ if (!utils_is_valid_ip(publicIp))
+ return FALSE;
+
if (!freerdp_settings_set_pointer_array(settings, FreeRDP_TargetNetAddresses,
(*pAddressIdx)++, publicIp))
return FALSE;
@@ -740,6 +747,9 @@ static BOOL arm_parse_ipv4(rdpSettings* settings, WINPR_JSON* ipv4, size_t* pAdd
WLog_ERR(TAG, "Exceeded TargetNetAddresses, parsing failed");
return FALSE;
}
+ if (!utils_is_valid_ip(privateIp))
+ return FALSE;
+
if (!freerdp_settings_set_pointer_array(settings, FreeRDP_TargetNetAddresses,
(*pAddressIdx)++, privateIp))
return FALSE;
diff --git a/libfreerdp/core/redirection.c b/libfreerdp/core/redirection.c
index 57971a4f7..c30bce904 100644
--- a/libfreerdp/core/redirection.c
+++ b/libfreerdp/core/redirection.c
@@ -707,6 +707,8 @@ static state_run_t rdp_recv_server_redirection_pdu(rdpRdp* rdp, wStream* s)
{
if (!rdp_redirection_read_unicode_string(s, &(redirection->TargetNetAddress), 80))
return STATE_RUN_FAILED;
+ if (!utils_is_valid_ip(redirection->TargetNetAddress))
+ return STATE_RUN_FAILED;
}
if (redirection->flags & LB_LOAD_BALANCE_INFO)
@@ -720,13 +722,17 @@ static state_run_t rdp_recv_server_redirection_pdu(rdpRdp* rdp, wStream* s)
if (!rdp_redirection_read_data(LB_LOAD_BALANCE_INFO, s, &redirection->LoadBalanceInfoLength,
&redirection->LoadBalanceInfo))
return STATE_RUN_FAILED;
+ if (!winpr_str_is_valid_urlN((const char*)redirection->LoadBalanceInfo,
+ redirection->LoadBalanceInfoLength))
+ return STATE_RUN_FAILED;
}
if (redirection->flags & LB_USERNAME)
{
if (!rdp_redirection_read_unicode_string(s, &(redirection->Username), 512))
return STATE_RUN_FAILED;
-
+ if (winpr_str_has_newlines(redirection->Username))
+ return STATE_RUN_FAILED;
WLog_DBG(TAG, "Username: %s", redirection->Username);
}
@@ -734,7 +740,8 @@ static state_run_t rdp_recv_server_redirection_pdu(rdpRdp* rdp, wStream* s)
{
if (!rdp_redirection_read_unicode_string(s, &(redirection->Domain), 52))
return STATE_RUN_FAILED;
-
+ if (winpr_str_has_newlines(redirection->Domain))
+ return STATE_RUN_FAILED;
WLog_DBG(TAG, "Domain: %s", redirection->Domain);
}
@@ -792,7 +799,8 @@ static state_run_t rdp_recv_server_redirection_pdu(rdpRdp* rdp, wStream* s)
{
if (!rdp_redirection_read_unicode_string(s, &(redirection->TargetFQDN), 512))
return STATE_RUN_FAILED;
-
+ if (!winpr_str_is_valid_url(redirection->TargetFQDN))
+ return STATE_RUN_FAILED;
WLog_DBG(TAG, "TargetFQDN: %s", redirection->TargetFQDN);
}
@@ -800,7 +808,8 @@ static state_run_t rdp_recv_server_redirection_pdu(rdpRdp* rdp, wStream* s)
{
if (!rdp_redirection_read_unicode_string(s, &(redirection->TargetNetBiosName), 32))
return STATE_RUN_FAILED;
-
+ if (!winpr_str_is_valid_url(redirection->TargetNetBiosName))
+ return STATE_RUN_FAILED;
WLog_DBG(TAG, "TargetNetBiosName: %s", redirection->TargetNetBiosName);
}
@@ -809,6 +818,8 @@ static state_run_t rdp_recv_server_redirection_pdu(rdpRdp* rdp, wStream* s)
if (!rdp_redirection_read_data(LB_CLIENT_TSV_URL, s, &redirection->TsvUrlLength,
&redirection->TsvUrl))
return STATE_RUN_FAILED;
+ if (!winpr_str_is_valid_urlN((const char*)redirection->TsvUrl, redirection->TsvUrlLength))
+ return STATE_RUN_FAILED;
}
if (redirection->flags & LB_REDIRECTION_GUID)
@@ -870,7 +881,8 @@ static state_run_t rdp_recv_server_redirection_pdu(rdpRdp* rdp, wStream* s)
{
if (!rdp_redirection_read_unicode_string(s, &(redirection->TargetNetAddresses[i]), 80))
return STATE_RUN_FAILED;
-
+ if (!utils_is_valid_ip(redirection->TargetNetAddresses[i]))
+ return STATE_RUN_FAILED;
WLog_DBG(TAG, "TargetNetAddresses[%" PRIu32 "]: %s", i,
redirection->TargetNetAddresses[i]);
}
@@ -898,7 +910,10 @@ state_run_t rdp_recv_enhanced_security_redirection_packet(rdpRdp* rdp, wStream*
status = rdp_recv_server_redirection_pdu(rdp, s);
if (state_run_failed(status))
+ {
+ WLog_Print(rdp->log, WLOG_ERROR, "redirection packet invalid, aborting");
return status;
+ }
if (Stream_GetRemainingLength(s) >= 1)
{
diff --git a/libfreerdp/core/utils.c b/libfreerdp/core/utils.c
index c2e79608c..226e7527f 100644
--- a/libfreerdp/core/utils.c
+++ b/libfreerdp/core/utils.c
@@ -597,3 +597,22 @@ const char* guid2str(const GUID* guid, char* buffer, size_t len)
RpcStringFreeA(&strguid);
return buffer;
}
+
+static BOOL isValidIPv4(const char* ipAddress)
+{
+ struct sockaddr_in sa = WINPR_C_ARRAY_INIT;
+ int result = inet_pton(AF_INET, ipAddress, &(sa.sin_addr));
+ return result != 0;
+}
+
+static BOOL isValidIPv6(const char* ipAddress)
+{
+ struct sockaddr_in6 sa = WINPR_C_ARRAY_INIT;
+ int result = inet_pton(AF_INET6, ipAddress, &(sa.sin6_addr));
+ return result != 0;
+}
+
+BOOL utils_is_valid_ip(const char* ipAddress)
+{
+ return isValidIPv4(ipAddress) || isValidIPv6(ipAddress);
+}
diff --git a/libfreerdp/core/utils.h b/libfreerdp/core/utils.h
index d683db1da..d5725ea72 100644
--- a/libfreerdp/core/utils.h
+++ b/libfreerdp/core/utils.h
@@ -115,4 +115,6 @@ BOOL freerdp_areApplicationDetailsCustomized(void);
FREERDP_LOCAL const char* guid2str(const GUID* guid, char* buffer, size_t len);
+FREERDP_LOCAL BOOL utils_is_valid_ip(const char* ipAddress);
+
#endif /* FREERDP_LIB_CORE_UTILS_H */

View File

@ -0,0 +1,34 @@
From cef38c3063c8f5d6d62bdfff3234b6499cb4c589 Mon Sep 17 00:00:00 2001
From: Armin Novak <armin.novak@thincast.com>
Date: Tue, 14 Jul 2026 12:30:27 +0200
Subject: [PATCH] [core,redirection] relax checks
* TsvUrl is opaque, do not assume any format
* LoadBalanceInfo is opaque, do not assume any format
---
libfreerdp/core/redirection.c | 5 -----
1 file changed, 5 deletions(-)
diff --git a/libfreerdp/core/redirection.c b/libfreerdp/core/redirection.c
index c30bce904..9aaab65ec 100644
--- a/libfreerdp/core/redirection.c
+++ b/libfreerdp/core/redirection.c
@@ -722,9 +722,6 @@ static state_run_t rdp_recv_server_redirection_pdu(rdpRdp* rdp, wStream* s)
if (!rdp_redirection_read_data(LB_LOAD_BALANCE_INFO, s, &redirection->LoadBalanceInfoLength,
&redirection->LoadBalanceInfo))
return STATE_RUN_FAILED;
- if (!winpr_str_is_valid_urlN((const char*)redirection->LoadBalanceInfo,
- redirection->LoadBalanceInfoLength))
- return STATE_RUN_FAILED;
}
if (redirection->flags & LB_USERNAME)
@@ -818,8 +815,6 @@ static state_run_t rdp_recv_server_redirection_pdu(rdpRdp* rdp, wStream* s)
if (!rdp_redirection_read_data(LB_CLIENT_TSV_URL, s, &redirection->TsvUrlLength,
&redirection->TsvUrl))
return STATE_RUN_FAILED;
- if (!winpr_str_is_valid_urlN((const char*)redirection->TsvUrl, redirection->TsvUrlLength))
- return STATE_RUN_FAILED;
}
if (redirection->flags & LB_REDIRECTION_GUID)

View File

@ -30,7 +30,7 @@
Name: freerdp
Epoch: 2
Version: 3.28.0
Release: 1%{?dist}
Release: 2%{?dist}
Summary: Free implementation of the Remote Desktop Protocol (RDP)
# The effective license is Apache-2.0 but:
@ -47,6 +47,23 @@ URL: http://www.freerdp.com/
Source0: FreeRDP-%{version}-repack.tar.gz
Source1: freerdp_download_and_repack.sh
# CVE-2026-68580
# https://github.com/FreeRDP/FreeRDP/commit/3bf07d4470bbe42faf4ef8197c8fe1b6b759b00f
# https://github.com/FreeRDP/FreeRDP/commit/9ca0aa69c4679b2be274ac59f30e76d0fa81aaf4
Patch: channels-audin-limit-FramesPerPacket.patch
Patch: channels-audin-tighten-parameter-checks.patch
# CVE-2026-67299
# https://github.com/FreeRDP/FreeRDP/commit/172edbddd24f33033b9eafae6ea1fff0641a24f4
Patch: core-message-fix-update_message_WindowIcon.patch
# CVE-2026-67289
# https://github.com/FreeRDP/FreeRDP/commit/f163b2d2080d922cb725800781f43f16bc2882c0
# https://github.com/FreeRDP/FreeRDP/commit/ab5905cbada0d34cf85ffd32881cacaf8275744f
# https://github.com/FreeRDP/FreeRDP/commit/1db16b4b121a1221c0454f7d223096cac87bf244
Patch: winpr-crt-add-functions-to-test-string.patch
Patch: core-redirection-check-redirection-values-for-validity.patch
Patch: core-redirection-relax-checks.patch
BuildRequires: gcc
BuildRequires: gcc-c++
@ -379,6 +396,10 @@ find %{buildroot} -name "*.a" -delete
%{_libdir}/pkgconfig/winpr-tools3.pc
%changelog
* Mon Aug 17 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 2:3.28.0-2
- Backport several CVE fixes (CVE-2026-67289, CVE-2026-67299, CVE-2026-68580)
Resolves: RHEL-222792, RHEL-222965, RHEL-223607
* Mon Jul 13 2026 Ondrej Holy <oholy@redhat.com> - 2:3.28.0-1
- Update to 3.28.0 (CVE-2026-57156, CVE-2026-57157, CVE-2026-57158)
Resolves: RHEL-171999

View File

@ -0,0 +1,313 @@
From 03074b64415bf757be38549e44fa98a8d92a56ed Mon Sep 17 00:00:00 2001
From: Armin Novak <armin.novak@thincast.com>
Date: Thu, 9 Jul 2026 08:34:23 +0200
Subject: [PATCH] [winpr,crt] add functions to test string
* winpr_str_has_newlines to check for newlines in a string
* winpr_str_is_valid_url and winpr_str_is_valid_urlN to check if a
string is a valid url
---
winpr/include/winpr/string.h | 28 +++++++
winpr/libwinpr/crt/CMakeLists.txt | 8 ++
winpr/libwinpr/crt/string.c | 115 +++++++++++++++++++++++++++
winpr/libwinpr/crt/test/TestString.c | 79 ++++++++++++++++++
4 files changed, 230 insertions(+)
diff --git a/winpr/include/winpr/string.h b/winpr/include/winpr/string.h
index 8163618ec..7fb3af570 100644
--- a/winpr/include/winpr/string.h
+++ b/winpr/include/winpr/string.h
@@ -53,6 +53,34 @@ extern "C"
WINPR_API WINPR_ATTR_MALLOC(free, 1) char* winpr_str_url_encode(const char* str, size_t len);
WINPR_API WINPR_ATTR_MALLOC(free, 1) char* winpr_str_url_decode(const char* str, size_t len);
+ /** @brief checks if a string is a valid URL
+ *
+ * @param str The string to check
+ * @return \b TRUE if valid, \b FALSE otherwise
+ * @since version 3.29.0
+ */
+ WINPR_ATTR_NODISCARD
+ WINPR_API BOOL winpr_str_is_valid_url(const char* str);
+
+ /** @brief checks if a string is a valid URL
+ *
+ * @param str The string to check
+ * @param len The length of the string in bytes
+ * @return \b TRUE if valid, \b FALSE otherwise
+ * @since version 3.29.0
+ */
+ WINPR_ATTR_NODISCARD
+ WINPR_API BOOL winpr_str_is_valid_urlN(const char* str, size_t len);
+
+ /** @brief checks if a string contains newlines
+ *
+ * @param str The string to check
+ * @return \b TRUE if it has newlines, \b FALSE otherwise
+ * @since version 3.29.0
+ */
+ WINPR_ATTR_NODISCARD
+ WINPR_API BOOL winpr_str_has_newlines(const char* str);
+
WINPR_API BOOL winpr_str_append(const char* what, char* buffer, size_t size,
const char* separator);
diff --git a/winpr/libwinpr/crt/CMakeLists.txt b/winpr/libwinpr/crt/CMakeLists.txt
index 8e07d0e87..1ea112e3f 100644
--- a/winpr/libwinpr/crt/CMakeLists.txt
+++ b/winpr/libwinpr/crt/CMakeLists.txt
@@ -25,6 +25,14 @@ set(CRT_FILES
assert.c
)
+include(CheckSymbolExists)
+check_symbol_exists(regcomp regex.h WINPR_HAVE_REGCOMP)
+check_symbol_exists(regexec regex.h WINPR_HAVE_REGEXEC)
+check_symbol_exists(regfree regex.h WINPR_HAVE_REGFREE)
+if(WINPR_HAVE_REGCOMP AND WINPR_HAVE_REGEXEC AND WINPR_HAVE_REGFREE)
+ winpr_definition_add(WINPR_HAVE_REGCOMP)
+endif()
+
if(WITH_UNICODE_BUILTIN)
list(APPEND CRT_FILES unicode_builtin.c)
else()
diff --git a/winpr/libwinpr/crt/string.c b/winpr/libwinpr/crt/string.c
index 872fd1cd4..0fb1c029e 100644
--- a/winpr/libwinpr/crt/string.c
+++ b/winpr/libwinpr/crt/string.c
@@ -34,12 +34,38 @@
#include <uriparser/Uri.h>
#endif
+#if defined(WINPR_HAVE_REGCOMP)
+#include <regex.h>
+#endif
+
/* String Manipulation (CRT): http://msdn.microsoft.com/en-us/library/f0151s4x.aspx */
#include "../log.h"
#define TAG WINPR_TAG("crt")
#if defined(WITH_URIPARSER)
+BOOL winpr_str_is_valid_urlN(const char* str, size_t len)
+{
+ if (!str || (len == 0))
+ return FALSE;
+
+ const char* errorPos = nullptr;
+ UriUriA uri = WINPR_C_ARRAY_INIT;
+ const int rc = uriParseSingleUriExA(&uri, str, &str[len - 1], &errorPos);
+ return rc == URI_SUCCESS;
+}
+
+BOOL winpr_str_is_valid_url(const char* str)
+{
+ if (!str)
+ return FALSE;
+
+ const char* errorPos = nullptr;
+ UriUriA uri = WINPR_C_ARRAY_INIT;
+ const int rc = uriParseSingleUriA(&uri, str, &errorPos);
+ return rc == URI_SUCCESS;
+}
+
char* winpr_str_url_decode(const char* str, size_t len)
{
char* dst = strndup(str, len);
@@ -152,6 +178,59 @@ char* winpr_str_url_encode(const char* str, size_t len)
}
return dst;
}
+
+BOOL winpr_str_is_valid_urlN(const char* str, size_t len)
+{
+ if (!str || (len == 0))
+ return FALSE;
+
+ char* url = strndup(str, len);
+ if (!url)
+ return FALSE;
+
+ const BOOL rc = winpr_str_is_valid_url(url);
+ free(url);
+ return rc;
+}
+
+#if defined(_WIN32)
+#include <shlwapi.h>
+
+BOOL winpr_str_is_valid_url(const char* str)
+{
+ if (!str)
+ return FALSE;
+ return PathIsURLA(str);
+}
+
+#elif defined(WINPR_HAVE_REGCOMP)
+BOOL winpr_str_is_valid_url(const char* str)
+{
+ if (!str)
+ return FALSE;
+ regex_t regex = WINPR_C_ARRAY_INIT;
+ const char pattern[] = "^([a-z]+://)?([a-zA-Z0-9.-]+)(:[0-9]+)?(/[a-zA-Z0-9./?=&%-]*)?$";
+
+ int ret = regcomp(&regex, pattern, REG_EXTENDED);
+ if (ret)
+ {
+ printf("Could not compile regex\n");
+ return FALSE;
+ }
+
+ ret = regexec(&regex, str, 0, nullptr, 0);
+ regfree(&regex);
+ return ret == 0;
+}
+
+#else
+BOOL winpr_str_is_valid_url(WINPR_ATTR_UNUSED const char* str)
+{
+ WLog_WARN(TAG, "URL validation not supported by your build, not performing check. Build with "
+ "-DWITH_URIPARSER=ON to fix.");
+ return TRUE;
+}
+#endif
#endif
BOOL winpr_str_append(const char* what, char* buffer, size_t size, const char* separator)
@@ -866,3 +945,39 @@ char* winpr_strnstr(char* haystack, const char* needle, size_t hlen)
return nullptr;
#endif
}
+
+BOOL winpr_str_has_newlines(const char* str)
+{
+ if (!str)
+ return FALSE;
+ do
+ {
+ char c = *str++;
+ switch (c)
+ {
+ case '\r':
+ case '\n':
+ case 0x0b: /* VT vertical tab */
+ case 0x0c: /* FF form feed */
+ case (char)0x85: /* NEL next line */
+ return TRUE;
+ case 0x20:
+ {
+ switch (*str)
+ {
+ case 0x28: /* LS line separator */
+ case 0x29: /* PS paragraph separator */
+ return TRUE;
+ default:
+ break;
+ }
+ }
+ break;
+
+ case '\0':
+ return FALSE;
+ default:
+ break;
+ }
+ } while (1);
+}
diff --git a/winpr/libwinpr/crt/test/TestString.c b/winpr/libwinpr/crt/test/TestString.c
index 3d0804e8c..31c82f22f 100644
--- a/winpr/libwinpr/crt/test/TestString.c
+++ b/winpr/libwinpr/crt/test/TestString.c
@@ -163,6 +163,79 @@ static BOOL test_winpr_strnstr(void)
return TRUE;
}
+static BOOL test_valid_url(void)
+{
+ struct test_t
+ {
+ char* string;
+ size_t len;
+ BOOL isUrl;
+ };
+
+ static const struct test_t tests[] = { { "foo.bar.blabla", 15, TRUE },
+ { "somehostname", 12, TRUE },
+ { "somehostname:1234", 17, TRUE },
+ { "tsv://somehostname", 18, TRUE },
+ { "tsv://somehostname/path/foo/gaga", 32, TRUE },
+ { "tsv://somehostname:1234", 23, TRUE },
+ { "tsv://somehostname:1234/path/foo", 33, TRUE },
+ { "lala\rgaga\n", 12, FALSE },
+ { "192.168.0.1", 11, TRUE },
+ //{ "192.168.0.1:1234", 16, FALSE },
+ //{ "192.168.0.1:1234/foo/", 21, FALSE },
+ { "192.168.0.1/bar/foo/", 20, TRUE },
+ { "[::1]", 5, FALSE },
+ { "[::1]:1234", 8, FALSE },
+ { "[2001:db8:3333:4444:5555:6666:7777:8888]", 40,
+ FALSE },
+ { "[2001:db8::1]", 13, FALSE } };
+ BOOL rc = TRUE;
+ for (size_t x = 0; x < ARRAYSIZE(tests); x++)
+ {
+ const struct test_t* cur = &tests[x];
+
+ const BOOL rc1 = winpr_str_is_valid_url(cur->string);
+ const BOOL rc2 = winpr_str_is_valid_urlN(cur->string, cur->len);
+
+#if defined(WINPR_HAVE_REGCOMP) || defined(WITH_URIPARSER)
+ if (rc1 != rc2)
+ rc = FALSE;
+ if (rc1 != cur->isUrl)
+ rc = FALSE;
+#else
+ fprintf(stderr, "[%s] TODO: !defined(WINPR_HAVE_REGCOMP) && !defined(WITH_URIPARSER)\n",
+ __func__);
+#endif
+ }
+
+ return rc;
+}
+
+static BOOL test_newline(void)
+{
+ struct test_t
+ {
+ char* string;
+ size_t len;
+ BOOL hasNewlines;
+ };
+
+ const struct test_t tests[] = { { "foo.bar.blabla", 15, FALSE },
+ { "somehostname", 12, FALSE },
+ { "lala\rgaga\n", 13, TRUE } };
+
+ for (size_t x = 0; x < ARRAYSIZE(tests); x++)
+ {
+ const struct test_t* cur = &tests[x];
+
+ const BOOL rc1 = winpr_str_has_newlines(cur->string);
+ if (rc1 != cur->hasNewlines)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
int TestString(int argc, char* argv[])
{
const WCHAR* p = nullptr;
@@ -173,6 +246,12 @@ int TestString(int argc, char* argv[])
WINPR_UNUSED(argc);
WINPR_UNUSED(argv);
+ if (!test_valid_url())
+ return -1;
+
+ if (!test_newline())
+ return -1;
+
if (!test_winpr_asprintf())
return -1;