new file mode 100644
@@ -0,0 +1,131 @@
+From 8b3c2f2eea22886288edb47d4c30177bf8673650 Mon Sep 17 00:00:00 2001
+From: Francesco Chemolli <5175948+kinkie@users.noreply.github.com>
+Date: Sun, 19 Jul 2026 21:22:29 +0000
+Subject: [PATCH] Protect base64 encoding buffers (#2447)
+
+Check bounds before base64-encoding data into fixed-size buffers.
+
+CVE: CVE-2026-104786
+Upstream-Status: Backport [https://github.com/squid-cache/squid/commit/8b3c2f2eea22886288edb47d4c30177bf8673650]
+Signed-off-by: Peter Marko <peter.marko@siemens.com>
+---
+ lib/sspwin32.cc | 1 +
+ src/adaptation/icap/ModXact.cc | 12 +++++++++---
+ src/http.cc | 23 ++++++++++++++++++-----
+ src/peer_proxy_negotiate_auth.cc | 2 ++
+ 4 files changed, 30 insertions(+), 8 deletions(-)
+
+diff --git a/lib/sspwin32.cc b/lib/sspwin32.cc
+index 636731751..a07b5ceb3 100644
+--- a/lib/sspwin32.cc
++++ b/lib/sspwin32.cc
+@@ -492,6 +492,7 @@ const char * WINAPI SSP_MakeChallenge(PVOID PNegotiateBuf, int NegotiateLen)
+ struct base64_encode_ctx ctx;
+ base64_encode_init(&ctx);
+ static char encoded[8192];
++ assert(base64_encode_len(cbOut) < sizeof(encoded));
+ size_t dstLen = base64_encode_update(&ctx, encoded, cbOut, reinterpret_cast<const uint8_t*>(fResult));
+ assert(dstLen < sizeof(encoded));
+ dstLen += base64_encode_final(&ctx, encoded+dstLen);
+diff --git a/src/adaptation/icap/ModXact.cc b/src/adaptation/icap/ModXact.cc
+index 441bfc396..f3a187c19 100644
+--- a/src/adaptation/icap/ModXact.cc
++++ b/src/adaptation/icap/ModXact.cc
+@@ -1402,12 +1402,18 @@ void Adaptation::Icap::ModXact::makeRequestHeaders(MemBuf &buf)
+ String vh=virgin.header->header.getById(Http::HdrType::PROXY_AUTHORIZATION);
+ buf.appendf("Proxy-Authorization: " SQUIDSTRINGPH "\r\n", SQUIDSTRINGPRINT(vh));
+ } else if (request->extacl_user.size() > 0 && request->extacl_passwd.size() > 0) {
++ const auto userLen = request->extacl_user.size();
++ const auto passwdLen = request->extacl_passwd.size();
++ // +1 for the ':' separator between user and passwd
++ const auto plainLen = userLen + 1 + passwdLen;
++ if (plainLen > MAX_LOGIN_SZ)
++ throw TextException("extacl credentials too long for Proxy-Authorization", Here());
++ char base64buf[base64_encode_len(MAX_LOGIN_SZ)];
+ struct base64_encode_ctx ctx;
+ base64_encode_init(&ctx);
+- char base64buf[base64_encode_len(MAX_LOGIN_SZ)];
+- size_t resultLen = base64_encode_update(&ctx, base64buf, request->extacl_user.size(), reinterpret_cast<const uint8_t*>(request->extacl_user.rawBuf()));
++ auto resultLen = base64_encode_update(&ctx, base64buf, userLen, reinterpret_cast<const uint8_t*>(request->extacl_user.rawBuf()));
+ resultLen += base64_encode_update(&ctx, base64buf+resultLen, 1, reinterpret_cast<const uint8_t*>(":"));
+- resultLen += base64_encode_update(&ctx, base64buf+resultLen, request->extacl_passwd.size(), reinterpret_cast<const uint8_t*>(request->extacl_passwd.rawBuf()));
++ resultLen += base64_encode_update(&ctx, base64buf+resultLen, passwdLen, reinterpret_cast<const uint8_t*>(request->extacl_passwd.rawBuf()));
+ resultLen += base64_encode_final(&ctx, base64buf+resultLen);
+ buf.appendf("Proxy-Authorization: Basic %.*s\r\n", (int)resultLen, base64buf);
+ }
+diff --git a/src/http.cc b/src/http.cc
+index df67fbbd2..3cf776ae8 100644
+--- a/src/http.cc
++++ b/src/http.cc
+@@ -1853,8 +1853,12 @@ httpFixupAuthentication(HttpRequest * request, const HttpHeader * hdr_in, HttpHe
+ username = request->auth_user_request->username();
+ #endif
+
+- blen = base64_encode_update(&ctx, loginbuf, strlen(username), reinterpret_cast<const uint8_t*>(username));
+- blen += base64_encode_update(&ctx, loginbuf+blen, strlen(request->peer_login +1), reinterpret_cast<const uint8_t*>(request->peer_login +1));
++ const auto usernameLen = strlen(username);
++ const auto suffixLen = strlen(request->peer_login + 1);
++ if (usernameLen + suffixLen > MAX_LOGIN_SZ)
++ throw TextException("peer login credentials too long", Here());
++ blen = base64_encode_update(&ctx, loginbuf, usernameLen, reinterpret_cast<const uint8_t*>(username));
++ blen += base64_encode_update(&ctx, loginbuf+blen, suffixLen, reinterpret_cast<const uint8_t*>(request->peer_login +1));
+ blen += base64_encode_final(&ctx, loginbuf+blen);
+ httpHeaderPutStrf(hdr_out, header, "Basic %.*s", (int)blen, loginbuf);
+ return;
+@@ -1865,9 +1869,14 @@ httpFixupAuthentication(HttpRequest * request, const HttpHeader * hdr_in, HttpHe
+ (strcmp(request->peer_login, "PASS") == 0 ||
+ strcmp(request->peer_login, "PROXYPASS") == 0)) {
+
+- blen = base64_encode_update(&ctx, loginbuf, request->extacl_user.size(), reinterpret_cast<const uint8_t*>(request->extacl_user.rawBuf()));
++ const auto userLen = request->extacl_user.size();
++ const auto passwdLen = request->extacl_passwd.size();
++ // +1 for the ':' separator between user and passwd
++ if (userLen + 1 + passwdLen > MAX_LOGIN_SZ)
++ throw TextException("extacl credentials too long for peer login", Here());
++ blen = base64_encode_update(&ctx, loginbuf, userLen, reinterpret_cast<const uint8_t*>(request->extacl_user.rawBuf()));
+ blen += base64_encode_update(&ctx, loginbuf+blen, 1, reinterpret_cast<const uint8_t*>(":"));
+- blen += base64_encode_update(&ctx, loginbuf+blen, request->extacl_passwd.size(), reinterpret_cast<const uint8_t*>(request->extacl_passwd.rawBuf()));
++ blen += base64_encode_update(&ctx, loginbuf+blen, passwdLen, reinterpret_cast<const uint8_t*>(request->extacl_passwd.rawBuf()));
+ blen += base64_encode_final(&ctx, loginbuf+blen);
+ httpHeaderPutStrf(hdr_out, header, "Basic %.*s", (int)blen, loginbuf);
+ return;
+@@ -1897,7 +1906,10 @@ httpFixupAuthentication(HttpRequest * request, const HttpHeader * hdr_in, HttpHe
+ }
+ #endif /* HAVE_KRB5 && HAVE_GSSAPI */
+
+- blen = base64_encode_update(&ctx, loginbuf, strlen(request->peer_login), reinterpret_cast<const uint8_t*>(request->peer_login));
++ const auto loginLen = strlen(request->peer_login);
++ if (loginLen > MAX_LOGIN_SZ)
++ throw TextException("peer_login too long", Here());
++ blen = base64_encode_update(&ctx, loginbuf, loginLen, reinterpret_cast<const uint8_t*>(request->peer_login));
+ blen += base64_encode_final(&ctx, loginbuf+blen);
+ httpHeaderPutStrf(hdr_out, header, "Basic %.*s", (int)blen, loginbuf);
+ return;
+@@ -2024,6 +2036,7 @@ HttpStateData::httpBuildRequestHeader(HttpRequest * request,
+ /* append Authorization if known in URL, not in header and going direct */
+ if (!hdr_out->has(Http::HdrType::AUTHORIZATION)) {
+ if (flags.toOrigin && !request->url.userInfo().isEmpty()) {
++ Assure(request->url.userInfo().length() < MAX_URL*2);
+ static char result[base64_encode_len(MAX_URL*2)]; // should be big enough for a single URI segment
+ struct base64_encode_ctx ctx;
+ base64_encode_init(&ctx);
+diff --git a/src/peer_proxy_negotiate_auth.cc b/src/peer_proxy_negotiate_auth.cc
+index d0f36477e..2fcad6ebb 100644
+--- a/src/peer_proxy_negotiate_auth.cc
++++ b/src/peer_proxy_negotiate_auth.cc
+@@ -17,6 +17,7 @@
+ #define GSSKRB_APPLE_DEPRECATED(x)
+ #endif
+
++#include "base/Assure.h"
+ #include "base64.h"
+ #include "compat/krb5.h"
+ #include "debug/Stream.h"
+@@ -549,6 +550,7 @@ char *peer_proxy_negotiate_auth(char *principal_name, char *proxy, int flags) {
+ static char b64buf[8192]; // XXX: 8KB only because base64_encode_bin() used to.
+ struct base64_encode_ctx ctx;
+ base64_encode_init(&ctx);
++ Assure(base64_encode_len(output_token.length) < sizeof(b64buf));
+ size_t blen = base64_encode_update(&ctx, b64buf, output_token.length, reinterpret_cast<const uint8_t*>(output_token.value));
+ blen += base64_encode_final(&ctx, b64buf+blen);
+ b64buf[blen] = '\0';
@@ -29,6 +29,7 @@ SRC_URI = "https://github.com/squid-cache/${BPN}/releases/download/SQUID_${PV_U}
file://CVE-2026-50012-01.patch \
file://CVE-2026-50012-02.patch \
file://CVE-2026-61642.patch \
+ file://CVE-2026-104786.patch \
"
SRC_URI[sha256sum] = "9eafe06f58a199b918e79d33d8aa03afb9ae0c11d18974dca0b44c2669cab6dd"