File curl-CVE-2026-3784.patch of Package curl.43131
From 5f13a7645e565c5c1a06f3ef86e97afb856fb364 Mon Sep 17 00:00:00 2001
From: Stefan Eissing <stefan@eissing.org>
Date: Fri, 6 Mar 2026 14:54:09 +0100
Subject: [PATCH] proxy-auth: additional tests
Also eliminate the special handling for socks proxy match.
Closes #20837
---
lib/url.c | 28 +++++++---------------------
tests/http/test_13_proxy_auth.py | 20 ++++++++++++++++++++
tests/http/testenv/curl.py | 18 +++++++++++++++---
3 files changed, 42 insertions(+), 24 deletions(-)
Index: curl-8.14.1/lib/url.c
===================================================================
--- curl-8.14.1.orig/lib/url.c
+++ curl-8.14.1/lib/url.c
@@ -640,30 +640,17 @@ proxy_info_matches(const struct proxy_in
{
if((data->proxytype == needle->proxytype) &&
(data->port == needle->port) &&
- strcasecompare(data->host.name, needle->host.name))
+ curl_strequal(data->host.name, needle->host.name)) {
+
+ if(Curl_timestrcmp(data->user, needle->user) ||
+ Curl_timestrcmp(data->passwd, needle->passwd))
+ return FALSE;
return TRUE;
+ }
return FALSE;
}
-static bool
-socks_proxy_info_matches(const struct proxy_info *data,
- const struct proxy_info *needle)
-{
- if(!proxy_info_matches(data, needle))
- return FALSE;
-
- /* the user information is case-sensitive
- or at least it is not defined as case-insensitive
- see https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.1 */
-
- /* curl_strequal does a case insensitive comparison,
- so do not use it here! */
- if(Curl_timestrcmp(data->user, needle->user) ||
- Curl_timestrcmp(data->passwd, needle->passwd))
- return FALSE;
- return TRUE;
-}
#else
/* disabled, will not get called */
#define proxy_info_matches(x,y) FALSE
@@ -982,8 +969,7 @@ static bool url_match_proxy_use(struct c
return FALSE;
if(m->needle->bits.socksproxy &&
- !socks_proxy_info_matches(&m->needle->socks_proxy,
- &conn->socks_proxy))
+ !proxy_info_matches(&m->needle->socks_proxy, &conn->socks_proxy))
return FALSE;
if(m->needle->bits.httpproxy) {
Index: curl-8.14.1/tests/http/test_13_proxy_auth.py
===================================================================
--- curl-8.14.1.orig/tests/http/test_13_proxy_auth.py
+++ curl-8.14.1/tests/http/test_13_proxy_auth.py
@@ -167,3 +167,23 @@ class TestProxyAuth:
'--negotiate', '--proxy-user', 'proxy:proxy'
])
r1.check_response(count=1, http_status=200)
+
+ def test_13_10_tunnels_mixed_auth(self, env: Env, httpd, configures_httpd):
+ self.httpd_configure(env, httpd)
+ curl = CurlClient(env=env)
+ url1 = f'http://localhost:{env.http_port}/data.json?1'
+ url2 = f'http://localhost:{env.http_port}/data.json?2'
+ url3 = f'http://localhost:{env.http_port}/data.json?3'
+ xargs1 = curl.get_proxy_args(proxys=False, tunnel=True)
+ xargs1.extend(['--proxy-user', 'proxy:proxy']) # good auth
+ xargs2 = curl.get_proxy_args(proxys=False, tunnel=True)
+ xargs2.extend(['--proxy-user', 'ungood:ungood']) # bad auth
+ xargs3 = curl.get_proxy_args(proxys=False, tunnel=True)
+ # no auth
+ r = curl.http_download(urls=[url1, url2, url3], alpn_proto='http/1.1', with_stats=True,
+ url_options={url1: xargs1, url2: xargs2, url3: xargs3})
+ # only url1 succeeds, others fail, no connection reuse
+ assert r.stats[0]['http_code'] == 200, f'{r.dump_logs()}'
+ assert r.stats[1]['http_code'] == 0, f'{r.dump_logs()}'
+ assert r.stats[2]['http_code'] == 0, f'{r.dump_logs()}'
+ assert r.total_connects == 3, f'{r.dump_logs()}'
Index: curl-8.14.1/tests/http/testenv/curl.py
===================================================================
--- curl-8.14.1.orig/tests/http/testenv/curl.py
+++ curl-8.14.1/tests/http/testenv/curl.py
@@ -543,7 +543,8 @@ class CurlClient:
with_profile: bool = False,
with_tcpdump: bool = False,
no_save: bool = False,
- extra_args: Optional[List[str]] = None):
+ extra_args: Optional[List[str]] = None,
+ url_options: Optional[Dict[str,List[str]]] = None):
if extra_args is None:
extra_args = []
if no_save:
@@ -585,6 +586,7 @@ class CurlClient:
])
return self._raw(urls, alpn_proto=alpn_proto, options=extra_args,
with_stats=with_stats,
+ url_options=url_options,
with_headers=with_headers,
with_profile=with_profile,
with_tcpdump=with_tcpdump)
@@ -824,6 +826,7 @@ class CurlClient:
def _raw(self, urls, intext='', timeout=None, options=None, insecure=False,
alpn_proto: Optional[str] = None,
+ url_options=None,
force_resolve=True,
with_stats=False,
with_headers=True,
@@ -833,7 +836,8 @@ class CurlClient:
args = self._complete_args(
urls=urls, timeout=timeout, options=options, insecure=insecure,
alpn_proto=alpn_proto, force_resolve=force_resolve,
- with_headers=with_headers, def_tracing=def_tracing)
+ with_headers=with_headers, def_tracing=def_tracing,
+ url_options=url_options)
r = self._run(args, intext=intext, with_stats=with_stats,
with_profile=with_profile, with_tcpdump=with_tcpdump)
if r.exit_code == 0 and with_headers:
@@ -843,8 +847,10 @@ class CurlClient:
def _complete_args(self, urls, timeout=None, options=None,
insecure=False, force_resolve=True,
alpn_proto: Optional[str] = None,
+ url_options=None,
with_headers: bool = True,
def_tracing: bool = True):
+ url_sep = []
if not isinstance(urls, list):
urls = [urls]
@@ -864,7 +870,13 @@ class CurlClient:
active_options = options[options.index('--next') + 1:]
for url in urls:
- u = urlparse(urls[0])
+ args.extend(url_sep)
+ if url_options is not None:
+ url_sep = ['--next']
+
+ u = urlparse(url)
+ if url_options is not None and url in url_options:
+ args.extend(url_options[url])
if options:
args.extend(options)
if alpn_proto is not None: