From bc440a89d47aa8f3a5d02b01985fd7f6fb7e7e0a Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Tue, 14 Jul 2026 08:52:21 +0200 Subject: [PATCH 1/6] mime.c: avoid integer overflow in base64 size calculation Reported-by: xmoezzz on github Fixes #22320 Closes #22322 --- lib/mime.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/mime.c b/lib/mime.c index 11eb66207d..638831d9c8 100644 --- a/lib/mime.c +++ b/lib/mime.c @@ -419,6 +419,11 @@ static size_t encoder_base64_read(char *buffer, size_t size, bool ateof, return cursize; } +/* The maximum input size that does not cause an overflow. */ +#define BASE64_MAX_INPUT_SIZE \ + (((CURL_OFF_T_MAX / (MAX_ENCODED_LINE_LENGTH + 2)) * \ + MAX_ENCODED_LINE_LENGTH / 4) * 3 - 3) + static curl_off_t encoder_base64_size(curl_mimepart *part) { curl_off_t size = part->datasize; @@ -426,6 +431,10 @@ static curl_off_t encoder_base64_size(curl_mimepart *part) if(size <= 0) return size; /* Unknown size or no data. */ + /* Prevent integer overflows */ + if(size > BASE64_MAX_INPUT_SIZE) + return -1; + /* Compute base64 character count. */ size = 4 * (1 + ((size - 1) / 3)); From cb81c5f4e2b51f77e3e913013a236a89bb74f130 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 15 Jul 2026 14:58:06 +0000 Subject: [PATCH 2/6] GHA: update dependency codespell to v2.4.3 Closes #22332 --- .github/scripts/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/scripts/requirements.txt b/.github/scripts/requirements.txt index adf6d03ba4..8d1a74af04 100644 --- a/.github/scripts/requirements.txt +++ b/.github/scripts/requirements.txt @@ -3,6 +3,6 @@ # SPDX-License-Identifier: curl cmakelang==0.6.13 -codespell==2.4.2 +codespell==2.4.3 reuse==6.2.0 ruff==0.15.16 From d52c7e78a3531f975591eb1bb1250185a9fd14c2 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Tue, 14 Jul 2026 19:06:37 +0200 Subject: [PATCH 3/6] idn: restore `MultiByteToWideChar()` `MB_ERR_INVALID_CHARS` flag Also: - curlx: pass this flag to the actual conversion calls, for consistency and robustness. (It's not stricly necessary because the initial call to determine size, with this flag passed, fails already on bad input.) - schannel: unfold `MultiByteToWideChar()` line (formatting). Ref: https://learn.microsoft.com/windows/win32/api/stringapiset/nf-stringapiset-multibytetowidechar Follow-up to 6694a42aa0e820a6fe1e59d85ff8597b6d768d8d #19798 Closes #22326 --- lib/curlx/fopen.c | 2 +- lib/curlx/multibyte.c | 4 ++-- lib/idn.c | 6 ++++-- lib/vtls/schannel.c | 6 ++---- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/curlx/fopen.c b/lib/curlx/fopen.c index 37ca02671a..976eff9e2b 100644 --- a/lib/curlx/fopen.c +++ b/lib/curlx/fopen.c @@ -68,7 +68,7 @@ static wchar_t *fn_convert_UTF8_to_wchar(const char *str_utf8) if(str_w_len > 0) { str_w = CURLX_MALLOC(str_w_len * sizeof(wchar_t)); if(str_w) { - if(MultiByteToWideChar(CP_UTF8, 0, + if(MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, str_utf8, -1, str_w, str_w_len) == 0) { CURLX_FREE(str_w); return NULL; diff --git a/lib/curlx/multibyte.c b/lib/curlx/multibyte.c index 715d2b8dc2..4ee39d962a 100644 --- a/lib/curlx/multibyte.c +++ b/lib/curlx/multibyte.c @@ -41,8 +41,8 @@ wchar_t *curlx_convert_UTF8_to_wchar(const char *str_utf8) if(str_w_len > 0) { str_w = curlx_malloc(str_w_len * sizeof(wchar_t)); if(str_w) { - if(MultiByteToWideChar(CP_UTF8, 0, str_utf8, -1, str_w, - str_w_len) == 0) { + if(MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, + str_utf8, -1, str_w, str_w_len) == 0) { curlx_free(str_w); return NULL; } diff --git a/lib/idn.c b/lib/idn.c index b26f251d97..943a520eff 100644 --- a/lib/idn.c +++ b/lib/idn.c @@ -172,7 +172,8 @@ static CURLcode win32_idn_to_ascii(const char *in, char **out) /* Returned in_w_len includes the null-terminator, which then gets preserved across the calls that follow, ending up terminating the buffer returned to the caller. */ - in_w_len = MultiByteToWideChar(CP_UTF8, 0, in, -1, in_w, IDN_MAX_LENGTH); + in_w_len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, + in, -1, in_w, IDN_MAX_LENGTH); if(in_w_len) { wchar_t punycode[IDN_MAX_LENGTH]; int chars = IdnToAscii(0, in_w, in_w_len, punycode, IDN_MAX_LENGTH); @@ -198,7 +199,8 @@ static CURLcode win32_ascii_to_idn(const char *in, char **out) /* Returned in_w_len includes the null-terminator, which then gets preserved across the calls that follow, ending up terminating the buffer returned to the caller. */ - in_w_len = MultiByteToWideChar(CP_UTF8, 0, in, -1, in_w, IDN_MAX_LENGTH); + in_w_len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, + in, -1, in_w, IDN_MAX_LENGTH); if(in_w_len) { WCHAR idn[IDN_MAX_LENGTH]; /* stores a UTF-16 string */ int chars = IdnToUnicode(0, in_w, in_w_len, idn, IDN_MAX_LENGTH); diff --git a/lib/vtls/schannel.c b/lib/vtls/schannel.c index d322c755e8..2b8166de12 100644 --- a/lib/vtls/schannel.c +++ b/lib/vtls/schannel.c @@ -473,10 +473,8 @@ static CURLcode get_client_cert(struct Curl_cfilter *cf, if(pszPassword) { int str_w_len = 0; if(pwd_len > 0) - str_w_len = MultiByteToWideChar(CP_UTF8, - MB_ERR_INVALID_CHARS, - sslc->key_passwd, - (int)pwd_len, + str_w_len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, + sslc->key_passwd, (int)pwd_len, pszPassword, (int)(pwd_len + 1)); if((str_w_len >= 0) && (str_w_len <= (int)pwd_len)) From 5f2a70abe3f780b6a97ac7039b7862bd828302cd Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Wed, 15 Jul 2026 14:08:18 +0200 Subject: [PATCH 4/6] tidy-up: `TEXT()` vs `_TEXT()` vs `_T()` use (Windows) Use `_TEXT()` when interacting with CRT functions (also prefer over synonym `_T()`), `TEXT()` for Win32 functions. Within curl, they mean the same because CRT/Win32 Unicode mode are always enabled in sync. Ref: https://devblogs.microsoft.com/oldnewthing/20040212-00/?p=40643/ Closes #22334 --- lib/curl_sspi.c | 8 ++++---- lib/curlx/fopen.c | 2 +- lib/vtls/schannel.c | 25 +++++++++++++------------ src/tool_main.c | 2 +- 4 files changed, 19 insertions(+), 18 deletions(-) diff --git a/lib/curl_sspi.c b/lib/curl_sspi.c index 3ea17621b1..b7dcb7d55c 100644 --- a/lib/curl_sspi.c +++ b/lib/curl_sspi.c @@ -101,7 +101,7 @@ CURLcode Curl_create_sspi_identity(const char *userp, const char *passwdp, xcharp_u passwd, dup_passwd; size_t domlen = 0; - domain.const_tchar_ptr = TEXT(""); + domain.const_tchar_ptr = _TEXT(""); /* Initialize the identity */ memset(identity, 0, sizeof(*identity)); @@ -110,9 +110,9 @@ CURLcode Curl_create_sspi_identity(const char *userp, const char *passwdp, if(!useranddomain.tchar_ptr) return CURLE_OUT_OF_MEMORY; - user.const_tchar_ptr = _tcschr(useranddomain.const_tchar_ptr, TEXT('\\')); + user.const_tchar_ptr = _tcschr(useranddomain.const_tchar_ptr, _TEXT('\\')); if(!user.const_tchar_ptr) - user.const_tchar_ptr = _tcschr(useranddomain.const_tchar_ptr, TEXT('/')); + user.const_tchar_ptr = _tcschr(useranddomain.const_tchar_ptr, _TEXT('/')); if(user.tchar_ptr) { domain.tchar_ptr = useranddomain.tchar_ptr; @@ -121,7 +121,7 @@ CURLcode Curl_create_sspi_identity(const char *userp, const char *passwdp, } else { user.tchar_ptr = useranddomain.tchar_ptr; - domain.const_tchar_ptr = TEXT(""); + domain.const_tchar_ptr = _TEXT(""); domlen = 0; } diff --git a/lib/curlx/fopen.c b/lib/curlx/fopen.c index 976eff9e2b..c3601af06a 100644 --- a/lib/curlx/fopen.c +++ b/lib/curlx/fopen.c @@ -121,7 +121,7 @@ static bool fix_excessive_path(const TCHAR *in, TCHAR **out) *out = NULL; /* skip paths already normalized */ - if(!_tcsncmp(in, _T("\\\\?\\"), 4)) + if(!_tcsncmp(in, _TEXT("\\\\?\\"), 4)) goto cleanup; #ifndef _UNICODE diff --git a/lib/vtls/schannel.c b/lib/vtls/schannel.c index 2b8166de12..6a69237f4e 100644 --- a/lib/vtls/schannel.c +++ b/lib/vtls/schannel.c @@ -316,34 +316,35 @@ static CURLcode get_cert_location(TCHAR *path, DWORD *store_name, TCHAR *store_path_start; size_t store_name_len; - sep = _tcschr(path, TEXT('\\')); + sep = _tcschr(path, _TEXT('\\')); if(!sep) return CURLE_SSL_CERTPROBLEM; store_name_len = sep - path; - if(_tcsncmp(path, TEXT("CurrentUser"), store_name_len) == 0) + if(_tcsncmp(path, _TEXT("CurrentUser"), store_name_len) == 0) *store_name = CERT_SYSTEM_STORE_CURRENT_USER; - else if(_tcsncmp(path, TEXT("LocalMachine"), store_name_len) == 0) + else if(_tcsncmp(path, _TEXT("LocalMachine"), store_name_len) == 0) *store_name = CERT_SYSTEM_STORE_LOCAL_MACHINE; - else if(_tcsncmp(path, TEXT("CurrentService"), store_name_len) == 0) + else if(_tcsncmp(path, _TEXT("CurrentService"), store_name_len) == 0) *store_name = CERT_SYSTEM_STORE_CURRENT_SERVICE; - else if(_tcsncmp(path, TEXT("Services"), store_name_len) == 0) + else if(_tcsncmp(path, _TEXT("Services"), store_name_len) == 0) *store_name = CERT_SYSTEM_STORE_SERVICES; - else if(_tcsncmp(path, TEXT("Users"), store_name_len) == 0) + else if(_tcsncmp(path, _TEXT("Users"), store_name_len) == 0) *store_name = CERT_SYSTEM_STORE_USERS; - else if(_tcsncmp(path, TEXT("CurrentUserGroupPolicy"), store_name_len) == 0) + else if(_tcsncmp(path, _TEXT("CurrentUserGroupPolicy"), store_name_len) == 0) *store_name = CERT_SYSTEM_STORE_CURRENT_USER_GROUP_POLICY; - else if(_tcsncmp(path, TEXT("LocalMachineGroupPolicy"), store_name_len) == 0) + else if(_tcsncmp(path, _TEXT("LocalMachineGroupPolicy"), store_name_len) == + 0) *store_name = CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY; - else if(_tcsncmp(path, TEXT("LocalMachineEnterprise"), store_name_len) == 0) + else if(_tcsncmp(path, _TEXT("LocalMachineEnterprise"), store_name_len) == 0) *store_name = CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE; else return CURLE_SSL_CERTPROBLEM; store_path_start = sep + 1; - sep = _tcschr(store_path_start, TEXT('\\')); + sep = _tcschr(store_path_start, _TEXT('\\')); if(!sep) return CURLE_SSL_CERTPROBLEM; @@ -351,9 +352,9 @@ static CURLcode get_cert_location(TCHAR *path, DWORD *store_name, if(_tcslen(*thumbprint) != CERT_THUMBPRINT_STR_LEN) return CURLE_SSL_CERTPROBLEM; - *sep = TEXT('\0'); + *sep = _TEXT('\0'); *store_path = curlx_tcsdup(store_path_start); - *sep = TEXT('\\'); + *sep = _TEXT('\\'); if(!*store_path) return CURLE_OUT_OF_MEMORY; diff --git a/src/tool_main.c b/src/tool_main.c index 5ee8fc9ddd..4ed2f41d58 100644 --- a/src/tool_main.c +++ b/src/tool_main.c @@ -150,7 +150,7 @@ int main(int argc, char *argv[]) #ifdef _WIN32 /* Undocumented diagnostic option to list the full paths of all loaded modules. This is purposely pre-init. */ - if(argc == 2 && !_tcscmp(argv[1], _T("--dump-module-paths"))) { + if(argc == 2 && !_tcscmp(argv[1], _TEXT("--dump-module-paths"))) { struct curl_slist *item, *head = GetLoadedModulePaths(); for(item = head; item; item = item->next) curl_mprintf("%s\n", item->data); From 899e2edec95b3df01db63ad219d3c5547a3e3ae5 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Wed, 15 Jul 2026 17:02:21 +0200 Subject: [PATCH 5/6] INTERNALS.md: require quiche 0.20.0+ For function `quiche_h3_config_set_max_field_section_size()`. Ref: #22331 Ref: https://github.com/curl/curl/pull/22331#issuecomment-4981647189 Ref: https://github.com/cloudflare/quiche/releases/tag/0.20.0 Closes #22333 --- docs/INTERNALS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/INTERNALS.md b/docs/INTERNALS.md index ff59f733e0..244d7c46c3 100644 --- a/docs/INTERNALS.md +++ b/docs/INTERNALS.md @@ -42,6 +42,7 @@ We aim to support these or later versions: - ngtcp2 1.0.0 (2023-10-15), with OpenSSL 3.5.0+: 1.12.0 (2025-04-16) - OpenLDAP 2.0 (2000-08-01) - OpenSSL 3.0.0 (2021-09-07) +- quiche 0.20.0 (2023-12-12) - Windows Vista 6.0 (2006-11-08 - 2012-04-10) - wolfSSL 5.0.0 (2021-11-01) - zlib 1.2.5.2 (2011-12-11) From f369c7ba437962591c8aa9d0519c7b256e7ea5bf Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Wed, 15 Jul 2026 12:52:21 +0100 Subject: [PATCH 6/6] quiche: set the max field section size quiche 0.29.3 started enforcing a default `SETTINGS_MAX_FIELD_SECTION_SIZE` of 32 KiB, so larger response headers now shut down the whole connection with `CURLE_HTTP3`. curl accepts up to 300 KiB of response headers with every other backend and HTTP version. Tell quiche to allow what curl itself allows. This keeps test_01_11 passing with quiche 0.29.3 and it also advertises our real limit to servers, which the RFC encourages. Ref: 899e2edec95b3df01db63ad219d3c5547a3e3ae5 #22333 Ref: #22329 Ref: #22325 Ref: https://github.com/cloudflare/quiche/commit/9be0e4fa18594a5cca00120f6ae392e217e837e5 Ref: https://github.com/cloudflare/quiche/releases/tag/0.29.3 Closes #22331 --- lib/vquic/cf-quiche.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/vquic/cf-quiche.c b/lib/vquic/cf-quiche.c index 479fbe3b63..b38ab7d3e9 100644 --- a/lib/vquic/cf-quiche.c +++ b/lib/vquic/cf-quiche.c @@ -40,6 +40,7 @@ #include "connect.h" #include "progress.h" #include "select.h" +#include "http.h" #include "http1.h" #include "sockaddr.h" #include "vquic/vquic.h" @@ -1429,6 +1430,10 @@ static CURLcode cf_quiche_connect(struct Curl_cfilter *cf, result = CURLE_OUT_OF_MEMORY; goto out; } + /* quiche 0.29.3+ rejects response headers larger than 32 KiB by + default. Allow as much as curl itself accepts. */ + quiche_h3_config_set_max_field_section_size(ctx->h3config, + MAX_HTTP_RESP_HEADER_SIZE); /* Create a new HTTP/3 connection on the QUIC connection. */ ctx->h3c = quiche_h3_conn_new_with_transport(ctx->qconn, ctx->h3config);