diff --git a/channels-audin-limit-FramesPerPacket.patch b/channels-audin-limit-FramesPerPacket.patch new file mode 100644 index 0000000..9cfdb4f --- /dev/null +++ b/channels-audin-limit-FramesPerPacket.patch @@ -0,0 +1,37 @@ +From 22cc4e8e563eaed5628bf510f26091d554d8533d Mon Sep 17 00:00:00 2001 +From: Armin Novak +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)) diff --git a/channels-audin-tighten-parameter-checks.patch b/channels-audin-tighten-parameter-checks.patch new file mode 100644 index 0000000..ab33466 --- /dev/null +++ b/channels-audin-tighten-parameter-checks.patch @@ -0,0 +1,222 @@ +From cc57fb28a27f8563375ac435a9f16985135a1789 Mon Sep 17 00:00:00 2001 +From: Armin Novak +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])); diff --git a/core-message-fix-update_message_WindowIcon.patch b/core-message-fix-update_message_WindowIcon.patch new file mode 100644 index 0000000..c5489cc --- /dev/null +++ b/core-message-fix-update_message_WindowIcon.patch @@ -0,0 +1,60 @@ +From bc37414b55a6061995fed3ee61836aaecfb055f6 Mon Sep 17 00:00:00 2001 +From: Armin Novak +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: diff --git a/core-redirection-check-redirection-values-for-validity.patch b/core-redirection-check-redirection-values-for-validity.patch new file mode 100644 index 0000000..d15bbd5 --- /dev/null +++ b/core-redirection-check-redirection-values-for-validity.patch @@ -0,0 +1,178 @@ +From c720ad9f8e9a308c035d029b1d61b249a525f474 Mon Sep 17 00:00:00 2001 +From: Armin Novak +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 */ diff --git a/core-redirection-relax-checks.patch b/core-redirection-relax-checks.patch new file mode 100644 index 0000000..aaf7c47 --- /dev/null +++ b/core-redirection-relax-checks.patch @@ -0,0 +1,34 @@ +From cef38c3063c8f5d6d62bdfff3234b6499cb4c589 Mon Sep 17 00:00:00 2001 +From: Armin Novak +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) diff --git a/freerdp.spec b/freerdp.spec index def0096..6bcac3e 100644 --- a/freerdp.spec +++ b/freerdp.spec @@ -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 - 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 - 2:3.28.0-1 - Update to 3.28.0 (CVE-2026-57156, CVE-2026-57157, CVE-2026-57158) Resolves: RHEL-171999 diff --git a/winpr-crt-add-functions-to-test-string.patch b/winpr-crt-add-functions-to-test-string.patch new file mode 100644 index 0000000..7abb5ef --- /dev/null +++ b/winpr-crt-add-functions-to-test-string.patch @@ -0,0 +1,313 @@ +From 03074b64415bf757be38549e44fa98a8d92a56ed Mon Sep 17 00:00:00 2001 +From: Armin Novak +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 + #endif + ++#if defined(WINPR_HAVE_REGCOMP) ++#include ++#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 ++ ++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(®ex, pattern, REG_EXTENDED); ++ if (ret) ++ { ++ printf("Could not compile regex\n"); ++ return FALSE; ++ } ++ ++ ret = regexec(®ex, str, 0, nullptr, 0); ++ regfree(®ex); ++ 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; +