diff --git a/channels-audin-limit-FramesPerPacket.patch b/channels-audin-limit-FramesPerPacket.patch new file mode 100644 index 0000000..3876690 --- /dev/null +++ b/channels-audin-limit-FramesPerPacket.patch @@ -0,0 +1,37 @@ +From 18deeba63c4091dfa96973a02df5bc214b4844c3 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 23093fb8e..7c403cbbb 100644 +--- a/channels/audin/client/audin_main.c ++++ b/channels/audin/client/audin_main.c +@@ -485,7 +485,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) + { +@@ -494,6 +493,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..4b8325b --- /dev/null +++ b/channels-audin-tighten-parameter-checks.patch @@ -0,0 +1,228 @@ +From 3b25c78c98d25b23246a5267d45f3654c2a8735d 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 | 43 +++++++++++++++------- + 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, 51 insertions(+), 30 deletions(-) + +diff --git a/channels/audin/client/alsa/audin_alsa.c b/channels/audin/client/alsa/audin_alsa.c +index e74097129..5b9f4ea5b 100644 +--- a/channels/audin/client/alsa/audin_alsa.c ++++ b/channels/audin/client/alsa/audin_alsa.c +@@ -138,8 +138,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 39b3dc848..2f27bd60f 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; +@@ -109,8 +109,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) +@@ -201,7 +201,10 @@ static SLresult openSLRecOpen(OPENSL_STREAM* p) + format_pcm.containerSize = 8; + } + else +- WINPR_ASSERT(0); ++ { ++ WLog_ERR(TAG, "bits_per_sample=%" PRIuz, p->bits_per_sample); ++ return -1; ++ } + + SLDataSink audioSnk = { &loc_bq, &format_pcm }; + // create audio recorder +@@ -306,24 +309,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 NULL; + +- p = (OPENSL_STREAM*)calloc(1, sizeof(OPENSL_STREAM)); ++ OPENSL_STREAM* p = (OPENSL_STREAM*)calloc(1, sizeof(OPENSL_STREAM)); + + if (!p) + return NULL; + + 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)) +@@ -335,9 +345,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 e99522cfe..07255de5c 100644 +--- a/channels/audin/client/opensles/opensl_io.h ++++ b/channels/audin/client/opensles/opensl_io.h +@@ -51,13 +51,12 @@ extern "C" + size in frames. Returns a handle to the OpenSL stream + */ + 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); + /* + Close the audio device + */ + FREERDP_LOCAL void android_CloseRecDevice(OPENSL_STREAM* p); +- + #ifdef __cplusplus + }; + #endif +diff --git a/channels/audin/client/oss/audin_oss.c b/channels/audin/client/oss/audin_oss.c +index 8b6ad6cc5..c76326d9c 100644 +--- a/channels/audin/client/oss/audin_oss.c ++++ b/channels/audin/client/oss/audin_oss.c +@@ -229,6 +229,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 (NULL == buffer) +diff --git a/channels/audin/client/sndio/audin_sndio.c b/channels/audin/client/sndio/audin_sndio.c +index af5426e72..655145db0 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 = NULL; +- 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 == NULL) +diff --git a/channels/audin/client/winmm/audin_winmm.c b/channels/audin/client/winmm/audin_winmm.c +index daa59254f..d6cceaf18 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 = NULL; +- int size = 0; + WAVEHDR waveHdr[4] = { 0 }; + 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/client-common-deactivate-cli-parsing-in-rdp-files.patch b/client-common-deactivate-cli-parsing-in-rdp-files.patch new file mode 100644 index 0000000..c75f8d2 --- /dev/null +++ b/client-common-deactivate-cli-parsing-in-rdp-files.patch @@ -0,0 +1,51 @@ +From c4286faee798f10cfa9897f8c482439d1e0a1a83 Mon Sep 17 00:00:00 2001 +From: Armin Novak +Date: Thu, 2 Jul 2026 10:21:54 +0200 +Subject: [PATCH] [client,common] deactivate cli parsing in RDP files + +This feature was uncodumented and might have unintended side effects. +Deactivate it by default and (for the time being) keep an option to +enable it at compile time. +--- + client/common/CMakeLists.txt | 5 +++++ + client/common/file.c | 4 ++++ + 2 files changed, 9 insertions(+) + +diff --git a/client/common/CMakeLists.txt b/client/common/CMakeLists.txt +index fedac6c5e..dc6fde72a 100644 +--- a/client/common/CMakeLists.txt ++++ b/client/common/CMakeLists.txt +@@ -47,6 +47,11 @@ else() + set(OPT_FUSE_DEFAULT OFF) + endif() + ++option(WITH_EMBEDDED_CLI_IN_RDP_FILES "[dangrous] allow embedded cli arguments in rdp files" OFF) ++if(WITH_EMBEDDED_CLI_IN_RDP_FILES) ++ add_compile_definitions(WITH_EMBEDDED_CLI_IN_RDP_FILES) ++endif() ++ + option(WITH_FUSE "Build clipboard with FUSE file copy support" ${OPT_FUSE_DEFAULT}) + if(WITH_FUSE) + find_package(PkgConfig REQUIRED) +diff --git a/client/common/file.c b/client/common/file.c +index f178cad23..696775361 100644 +--- a/client/common/file.c ++++ b/client/common/file.c +@@ -881,13 +881,17 @@ static BOOL parse_line(rdpFile* file, char* line, size_t length, rdp_file_fkt_pa + + const char* beg = line; + #if !defined(WITHOUT_FREERDP_3x_DEPRECATED) ++#if defined(WITH_EMBEDDED_CLI_IN_RDP_FILES) + if (beg[0] == '/') + { ++ WLog_WARN(TAG, "Parsing CLI options within an RDP file is deprecated and " ++ "will be removed in FreeRDP 4.0"); + if (!freerdp_client_add_option(file, line)) + return FALSE; + + return TRUE; /* FreeRDP option */ + } ++#endif + #endif + + char* d1 = strchr(line, ':'); diff --git a/client-common-deprecate-cli-options-in-rdp-files.patch b/client-common-deprecate-cli-options-in-rdp-files.patch new file mode 100644 index 0000000..f9c8539 --- /dev/null +++ b/client-common-deprecate-cli-options-in-rdp-files.patch @@ -0,0 +1,191 @@ +From 802647fa254b761dc01240bcf4e94740aa39e894 Mon Sep 17 00:00:00 2001 +From: Armin Novak +Date: Thu, 2 Jul 2026 08:34:18 +0200 +Subject: [PATCH] [client,common] deprecate freerdp command line options in rdp + files + +--- + client/common/file.c | 140 ++++++++++++++++++++----------------------- + 1 file changed, 64 insertions(+), 76 deletions(-) + +diff --git a/client/common/file.c b/client/common/file.c +index 35e8643f2..f178cad23 100644 +--- a/client/common/file.c ++++ b/client/common/file.c +@@ -774,16 +774,6 @@ static SSIZE_T freerdp_client_rdp_file_add_line(rdpFile* file) + return index; + } + +-static BOOL freerdp_client_parse_rdp_file_string(rdpFile* file, char* name, char* value) +-{ +- return freerdp_client_rdp_file_set_string(file, name, value); +-} +- +-static BOOL freerdp_client_parse_rdp_file_option(rdpFile* file, const char* option) +-{ +- return freerdp_client_add_option(file, option); +-} +- + BOOL freerdp_client_parse_rdp_file_buffer(rdpFile* file, const BYTE* buffer, size_t size) + { + return freerdp_client_parse_rdp_file_buffer_ex(file, buffer, size, NULL); +@@ -884,19 +874,67 @@ static BOOL trim_strings(rdpFile* file) + return TRUE; + } + ++static BOOL parse_line(rdpFile* file, char* line, size_t length, rdp_file_fkt_parse parse) ++{ ++ if (length <= 1) ++ return TRUE; ++ ++ const char* beg = line; ++#if !defined(WITHOUT_FREERDP_3x_DEPRECATED) ++ if (beg[0] == '/') ++ { ++ if (!freerdp_client_add_option(file, line)) ++ return FALSE; ++ ++ return TRUE; /* FreeRDP option */ ++ } ++#endif ++ ++ char* d1 = strchr(line, ':'); ++ ++ if (!d1) ++ return TRUE; /* not first delimiter */ ++ ++ const char* type = &d1[1]; ++ char* d2 = strchr(type, ':'); ++ ++ if (!d2) ++ return TRUE; /* no second delimiter */ ++ ++ if ((d2 - d1) != 2) ++ return TRUE; /* improper type length */ ++ ++ *d1 = 0; ++ *d2 = 0; ++ const char* name = beg; ++ const char* value = &d2[1]; ++ ++ if (parse && parse(file->context, name, *type, value)) ++ return TRUE; ++ ++ if (*type == 'i') ++ { ++ /* integer type */ ++ return freerdp_client_parse_rdp_file_integer(file, name, value); ++ } ++ if (*type == 's') ++ { ++ /* string type */ ++ return freerdp_client_rdp_file_set_string(file, name, value); ++ } ++ if (*type == 'b') ++ { ++ /* binary type */ ++ WLog_ERR(TAG, "Unsupported RDP file binary option %s [value=%s]", name, value); ++ } ++ ++ return TRUE; ++} ++ + BOOL freerdp_client_parse_rdp_file_buffer_ex(rdpFile* file, const BYTE* buffer, size_t size, + rdp_file_fkt_parse parse) + { + BOOL rc = FALSE; +- size_t length = 0; +- char* line = NULL; +- char* type = NULL; +- char* context = NULL; +- char* d1 = NULL; +- char* d2 = NULL; +- char* beg = NULL; +- char* name = NULL; +- char* value = NULL; + char* copy = NULL; + + if (!file) +@@ -907,9 +945,8 @@ BOOL freerdp_client_parse_rdp_file_buffer_ex(rdpFile* file, const BYTE* buffer, + if ((buffer[0] == BOM_UTF16_LE[0]) && (buffer[1] == BOM_UTF16_LE[1])) + { + LPCWSTR uc = (LPCWSTR)(&buffer[2]); +- size = size / sizeof(WCHAR) - 1; +- +- copy = ConvertWCharNToUtf8Alloc(uc, size, NULL); ++ const size_t charlen = size / sizeof(WCHAR) - 1; ++ copy = ConvertWCharNToUtf8Alloc(uc, charlen, &size); + if (!copy) + { + WLog_ERR(TAG, "Failed to convert RDP file from UCS2 to UTF8"); +@@ -926,65 +963,16 @@ BOOL freerdp_client_parse_rdp_file_buffer_ex(rdpFile* file, const BYTE* buffer, + memcpy(copy, buffer, size); + } + +- line = strtok_s(copy, "\r\n", &context); ++ char* context = NULL; ++ char* line = strtok_s(copy, "\r\n", &context); + + while (line) + { +- length = strnlen(line, size); +- +- if (length > 1) +- { +- beg = line; +- if (beg[0] == '/') +- { +- if (!freerdp_client_parse_rdp_file_option(file, line)) +- goto fail; +- +- goto next_line; /* FreeRDP option */ +- } +- +- d1 = strchr(line, ':'); +- +- if (!d1) +- goto next_line; /* not first delimiter */ ++ const size_t length = strnlen(line, size); + +- type = &d1[1]; +- d2 = strchr(type, ':'); +- +- if (!d2) +- goto next_line; /* no second delimiter */ +- +- if ((d2 - d1) != 2) +- goto next_line; /* improper type length */ +- +- *d1 = 0; +- *d2 = 0; +- name = beg; +- value = &d2[1]; +- +- if (parse && parse(file->context, name, *type, value)) +- { +- } +- else if (*type == 'i') +- { +- /* integer type */ +- if (!freerdp_client_parse_rdp_file_integer(file, name, value)) +- goto fail; +- } +- else if (*type == 's') +- { +- /* string type */ +- if (!freerdp_client_parse_rdp_file_string(file, name, value)) +- goto fail; +- } +- else if (*type == 'b') +- { +- /* binary type */ +- WLog_ERR(TAG, "Unsupported RDP file binary option %s [value=%s]", name, value); +- } +- } ++ if (!parse_line(file, line, length, parse)) ++ goto fail; + +- next_line: + line = strtok_s(NULL, "\r\n", &context); + } + diff --git a/client-common-fix-double-free.patch b/client-common-fix-double-free.patch new file mode 100644 index 0000000..196f7e9 --- /dev/null +++ b/client-common-fix-double-free.patch @@ -0,0 +1,21 @@ +From 005c5abacbb569f0f66deee710e1f1a87794f368 Mon Sep 17 00:00:00 2001 +From: Armin Novak +Date: Wed, 1 Jul 2026 23:03:29 +0200 +Subject: [PATCH] [client,common] fix double free + +--- + client/common/file.c | 1 - + 1 file changed, 1 deletion(-) + +diff --git a/client/common/file.c b/client/common/file.c +index 35e8643f2..606aab0f7 100644 +--- a/client/common/file.c ++++ b/client/common/file.c +@@ -2457,7 +2457,6 @@ BOOL freerdp_client_populate_settings_from_rdp_file(const rdpFile* file, rdpSett + if ((val >= UINT32_MAX) && (errno != 0)) + { + CommandLineParserFree(ptr); +- free(list); + return FALSE; + } + list[x] = (UINT32)val; diff --git a/core-message-fix-update_message_WindowIcon.patch b/core-message-fix-update_message_WindowIcon.patch new file mode 100644 index 0000000..64f950f --- /dev/null +++ b/core-message-fix-update_message_WindowIcon.patch @@ -0,0 +1,60 @@ +From 5bf8d59e880336d5a22d7f40e3bb1d74dc81d87b 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 73961a026..a3f376026 100644 +--- a/libfreerdp/core/message.c ++++ b/libfreerdp/core/message.c +@@ -1293,30 +1293,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 = NULL; +- WINDOW_ICON_ORDER* lParam = NULL; +- rdp_update_internal* up = NULL; +- + 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) +@@ -1352,7 +1351,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..7a8ad13 --- /dev/null +++ b/core-redirection-check-redirection-values-for-validity.patch @@ -0,0 +1,201 @@ +From 81747fa9100fdb3fde2aae71cda2a79c82f6e363 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 | 11 +++++++++++ + libfreerdp/core/redirection.c | 25 ++++++++++++++++++++----- + libfreerdp/core/utils.c | 26 ++++++++++++++++++++++++++ + libfreerdp/core/utils.h | 2 ++ + 4 files changed, 59 insertions(+), 5 deletions(-) + +diff --git a/libfreerdp/core/gateway/arm.c b/libfreerdp/core/gateway/arm.c +index 4433f6ac9..0e82cf775 100644 +--- a/libfreerdp/core/gateway/arm.c ++++ b/libfreerdp/core/gateway/arm.c +@@ -26,6 +26,7 @@ + #include + + #include ++#include + #include + #include + #include +@@ -654,6 +655,10 @@ static BOOL arm_parse_ipv6(rdpSettings* settings, WINPR_JSON* ipv6, size_t* pAdd + const char* addr = WINPR_JSON_GetStringValue(adressN); + if (utils_str_is_empty(addr)) + continue; ++ ++ if (!winpr_str_is_valid_url(addr)) ++ return FALSE; ++ + if (!freerdp_settings_set_pointer_array(settings, FreeRDP_TargetNetAddresses, + (*pAddressIdx)++, addr)) + return FALSE; +@@ -684,6 +689,9 @@ static BOOL arm_parse_ipv4(rdpSettings* settings, WINPR_JSON* ipv4, size_t* pAdd + const char* publicIp = WINPR_JSON_GetStringValue(publicIpNode); + if (!utils_str_is_empty(publicIp)) + { ++ if (!utils_is_valid_ip(publicIp)) ++ return FALSE; ++ + if (!freerdp_settings_set_pointer_array(settings, FreeRDP_TargetNetAddresses, + (*pAddressIdx)++, publicIp)) + return FALSE; +@@ -696,6 +704,9 @@ static BOOL arm_parse_ipv4(rdpSettings* settings, WINPR_JSON* ipv4, size_t* pAdd + const char* privateIp = WINPR_JSON_GetStringValue(privateIpNode); + if (!utils_str_is_empty(privateIp)) + { ++ 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 286976929..6f7dcdb57 100644 +--- a/libfreerdp/core/redirection.c ++++ b/libfreerdp/core/redirection.c +@@ -699,6 +699,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) +@@ -712,13 +714,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); + } + +@@ -726,7 +732,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); + } + +@@ -784,7 +791,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); + } + +@@ -792,7 +800,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); + } + +@@ -801,6 +810,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) +@@ -862,7 +873,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]); + } +@@ -890,7 +902,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 0f395d893..c46c21e23 100644 +--- a/libfreerdp/core/utils.c ++++ b/libfreerdp/core/utils.c +@@ -28,6 +28,13 @@ + #include + #include + ++#ifdef _WIN32 ++#include ++#include ++#else ++#include ++#endif ++ + #include + #define TAG FREERDP_TAG("core.gateway.utils") + +@@ -480,3 +487,22 @@ BOOL utils_reload_channels(rdpContext* context) + return freerdp_channels_pre_connect(context->channels, context->instance) == CHANNEL_RC_OK; + return rc; + } ++ ++static BOOL isValidIPv4(const char* ipAddress) ++{ ++ struct sockaddr_in sa = { 0 }; ++ int result = inet_pton(AF_INET, ipAddress, &(sa.sin_addr)); ++ return result != 0; ++} ++ ++static BOOL isValidIPv6(const char* ipAddress) ++{ ++ struct sockaddr_in6 sa = { 0 }; ++ 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 a42dcad94..6b25c580c 100644 +--- a/libfreerdp/core/utils.h ++++ b/libfreerdp/core/utils.h +@@ -61,4 +61,6 @@ char* utils_redir_flags_to_string(UINT32 flags, char* buffer, size_t size); + + BOOL utils_reload_channels(rdpContext* context); + ++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..7bf0078 --- /dev/null +++ b/core-redirection-relax-checks.patch @@ -0,0 +1,34 @@ +From 1c4b028928be8f38e968d8bfb7d3fb6864540b42 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 6f7dcdb57..f9690567d 100644 +--- a/libfreerdp/core/redirection.c ++++ b/libfreerdp/core/redirection.c +@@ -714,9 +714,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) +@@ -810,8 +807,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/crypto-fix-out-buffer-check.patch b/crypto-fix-out-buffer-check.patch new file mode 100644 index 0000000..12a34d1 --- /dev/null +++ b/crypto-fix-out-buffer-check.patch @@ -0,0 +1,31 @@ +From 86472e2d967e0b211eeb0405955ba146c7a97f5f Mon Sep 17 00:00:00 2001 +From: Armin Novak +Date: Thu, 2 Jul 2026 08:02:26 +0200 +Subject: [PATCH] [crypto] fix out buffer check + +--- + libfreerdp/crypto/crypto.c | 9 ++++++--- + 1 file changed, 6 insertions(+), 3 deletions(-) + +diff --git a/libfreerdp/crypto/crypto.c b/libfreerdp/crypto/crypto.c +index 806f9b986..e6f156494 100644 +--- a/libfreerdp/crypto/crypto.c ++++ b/libfreerdp/crypto/crypto.c +@@ -103,11 +103,14 @@ static SSIZE_T crypto_rsa_common(const BYTE* input, size_t length, UINT32 key_le + goto fail; + if (BN_mod_exp(y, x, exp, mod, ctx) != 1) + goto fail; +- output_length = BN_bn2bin(y, output); ++ { ++ const int len = BN_num_bytes(y); ++ if ((len < 0) || ((size_t)len > out_length)) ++ goto fail; ++ output_length = BN_bn2bin(y, output); ++ } + if (output_length < 0) + goto fail; +- if ((size_t)output_length > out_length) +- goto fail; + crypto_reverse(output, output_length); + + if ((size_t)output_length < key_length) diff --git a/freerdp.spec b/freerdp.spec index edab8e7..a97c18a 100644 --- a/freerdp.spec +++ b/freerdp.spec @@ -30,7 +30,7 @@ Name: freerdp Epoch: 2 Version: 3.10.3 -Release: 12%{?dist}.7 +Release: 12%{?dist}.8 Summary: Free implementation of the Remote Desktop Protocol (RDP) # The effective license is Apache-2.0 but: @@ -277,6 +277,38 @@ Patch: channels-rdpear-disable-ndr-pointer-aliasing.patch Patch: core-orders-add-codecID-checks.patch Patch: gdi-graphics-fix-gdi_Bitmap_Decompress.patch +# CVE-2026-64624 +# https://github.com/FreeRDP/FreeRDP/commit/e551807abfb87a8a1890c8b7e36fe421cd5596be +# https://github.com/FreeRDP/FreeRDP/commit/22c5deea52404f51a13276b3abda44e1e60704cf +Patch: client-common-deprecate-cli-options-in-rdp-files.patch +Patch: client-common-deactivate-cli-parsing-in-rdp-files.patch + +# CVE-2026-64621 +# https://github.com/FreeRDP/FreeRDP/commit/7696267929ae8ba045c1bbe275b3915653828313 +Patch: client-common-fix-double-free.patch + +# CVE-2026-64620 +# https://github.com/FreeRDP/FreeRDP/commit/27014741a8c22a34d3040c559c82f4ed82841bef +Patch: crypto-fix-out-buffer-check.patch + +# 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++ BuildRequires: alsa-lib-devel @@ -600,6 +632,12 @@ find %{buildroot} -name "*.a" -delete %{_libdir}/pkgconfig/winpr-tools3.pc %changelog +* Tue Aug 04 2026 RHEL Packaging Agent - 2:3.10.3-12.8 +- Backport several CVE fixes (CVE-2026-64620, CVE-2026-64621, CVE-2026-64624, + CVE-2026-67289, CVE-2026-67299, CVE-2026-68580) + Resolves: RHEL-212621, RHEL-212978, RHEL-213168, RHEL-222783, RHEL-222983, + Resolves: RHEL-223604 + * Sat Jul 11 2026 RHEL Packaging Agent - 2:3.10.3-12.7 - Add codecID checks for CACHE_BITMAP_V3_ORDER (CVE-2026-55827) - Fix boundary checks in gdi_Bitmap_Decompress (CVE-2026-55827) 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..c8e96b7 --- /dev/null +++ b/winpr-crt-add-functions-to-test-string.patch @@ -0,0 +1,313 @@ +From 65d51c15af42a4d6cce83eac416fa25af0c2ca93 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 | 25 ++++++ + winpr/libwinpr/crt/CMakeLists.txt | 8 ++ + winpr/libwinpr/crt/string.c | 115 +++++++++++++++++++++++++++ + winpr/libwinpr/crt/test/TestString.c | 80 +++++++++++++++++++ + 4 files changed, 228 insertions(+) + +diff --git a/winpr/include/winpr/string.h b/winpr/include/winpr/string.h +index 45a4098cc..0821293ec 100644 +--- a/winpr/include/winpr/string.h ++++ b/winpr/include/winpr/string.h +@@ -39,6 +39,31 @@ extern "C" + WINPR_API char* winpr_str_url_encode(const char* str, size_t len); + WINPR_API 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_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_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_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 18f6ad139..b2cc1cf94 100644 +--- a/winpr/libwinpr/crt/CMakeLists.txt ++++ b/winpr/libwinpr/crt/CMakeLists.txt +@@ -17,6 +17,14 @@ + + set(CRT_FILES alignment.c conversion.c buffer.c memory.c unicode.c string.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 4dec0b141..b78bbdc2e 100644 +--- a/winpr/libwinpr/crt/string.c ++++ b/winpr/libwinpr/crt/string.c +@@ -33,6 +33,10 @@ + #include + #endif + ++#if defined(WINPR_HAVE_REGCOMP) ++#include ++#endif ++ + /* String Manipulation (CRT): http://msdn.microsoft.com/en-us/library/f0151s4x.aspx */ + + #include "../log.h" +@@ -43,6 +47,28 @@ + #endif + + #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 = NULL; ++ UriUriA uri = { 0 }; ++ 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 = NULL; ++ UriUriA uri = { 0 }; ++ 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); +@@ -155,6 +181,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 = { 0 }; ++ 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, NULL, 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) +@@ -845,3 +924,39 @@ WCHAR* wcsndup(const WCHAR* s, size_t n) + memcpy(copy, s, n * sizeof(WCHAR)); + return copy; + } ++ ++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 e77bf02da..0ab06ab78 100644 +--- a/winpr/libwinpr/crt/test/TestString.c ++++ b/winpr/libwinpr/crt/test/TestString.c +@@ -104,6 +104,80 @@ fail: + return rc; + } + ++ ++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 = NULL; +@@ -114,6 +188,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; + +