Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package curl for openSUSE:Factory checked in at 2025-11-21 16:54:21 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/curl (Old) and /work/SRC/openSUSE:Factory/.curl.new.2061 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "curl" Fri Nov 21 16:54:21 2025 rev:220 rq:1318709 version:8.17.0 Changes: -------- --- /work/SRC/openSUSE:Factory/curl/curl.changes 2025-10-01 18:55:32.618853662 +0200 +++ /work/SRC/openSUSE:Factory/.curl.new.2061/curl.changes 2025-11-21 16:54:59.122079825 +0100 @@ -1,0 +2,83 @@ +Wed Nov 19 13:07:46 UTC 2025 - Pedro Monreal <[email protected]> + +- Fix a regression in curl 8.17.0: [bsc#1253116] + * Builds with no CURL_CA_PATH ignore CURLOPT_CAPATH + * vtls: fix CURLOPT_CAPATH use [gh#curl/curl#19401] + * Add upstream curl-vtls-fix-CURLOPT_CAPATH-use.patch + +------------------------------------------------------------------- +Wed Nov 5 08:45:52 UTC 2025 - Pedro Monreal <[email protected]> + +- Update to 8.17.0: + * Security fixes: + - [bsc#1252859, CVE-2025-10966] curl: missing SFTP host + verification with wolfSSH + - [bsc#1253757, CVE-2025-11563] curl: wcurl path traversal with + percent-encoded slashes + * Changes: + - krb5: drop support for Kerberos FTP + - multi: add notifications API + - ssl: support Apple SecTrust configurations + - tool_getparam: add --knownhosts + - vssh: drop support for wolfSSH + - wcurl: import v2025.11.04 + * Bugfixes: + - ares: fix leak in tracing + - base64: accept zero length argument to base64_encode + - c-ares: when resolving failed, persist error + - cf-socket: set FD_CLOEXEC on all sockets opened + - cf-socket: use the right byte order for ports in bindlocal + - conn: fix hostname move on connection reuse + - conncache: prevent integer overflow in maxconnects calculation + - cookie: avoid saving a cookie file if no transfer was done + - curl_easy_getinfo: error code on NULL arg + - curl_path: make sure just whitespace is illegal + - digest_sspi: fix two memory leaks in error branches + - ftp: add extra buffer length check + - ftp: check errors on remote ip for data connection + - gnutls: check conversion of peer cert chain + - gnutls: fix re-handshake comments + - gssapi: make channel binding conditional on GSS_C_CHANNEL_BOUND_FLAG + - gtls: check the return value of gnutls_pubkey_init() + - hmac: free memory properly on errors + - HTTP3: clarify the status for "old" OpenSSL, not current + - kerberos: bump minimum to 1.3 (2003-07-08), drop legacy logic + - krb5_gssapi: fix memory leak on error path + - krb5_sspi: the chlg argument is NOT optional + - ldap: avoid null ptr deref on failure + - ldap: do not base64 encode zero length string + - lib: SSL connection reuse + - libssh/libssh2: reject quote command lines with too much data + - libssh/sftp: fix resume corruption by avoiding O_APPEND with rresume + - libssh: acknowledge SSH_AGAIN in the SFTP state machine + - nghttp3: return NGHTTP3_ERR_CALLBACK_FAILURE from recv_header + - ngtcp2: close just-opened QUIC stream when submit_request fails + - ngtcp2: compare idle timeout in ms to avoid overflow + - noproxy: fix the IPV6 network mask pattern match + - NTLM: disable if DES support missing from OpenSSL or mbedTLS + - openldap: limit max incoming size + - openssl: call SSL_get_error() with proper error + - openssl: check CURL_SSLVERSION_MAX_DEFAULT properly + - openssl: fail if more than MAX_ALLOWED_CERT_AMOUNT certs + - openssl: fail the transfer if ossl_certchain() fails + - openssl: fix peer certificate leak in channel binding + - openssl: fix resource leak in provider error path + - openssl: free UI_METHOD on exit path + - openssl: only try engine/provider if a cert file/name is provided + - openssl: set io_need always + - openssl: skip session resumption when verifystatus is set + - pop3: fix CAPA response termination detection + - quic: fix min TLS version handling + - quic: ignore EMSGSIZE on receive + - schannel: properly close the certfile on error + - schannel_verify: fix mem-leak in Curl_verify_host + - socks: avoid UAF risk in error path + - socks: deny server basic-auth if not configured + - socks_gssapi: reject too long tokens + - socks_gssapi: remove the forced "no protection" + - thread: errno on thread creation + - ws: reject curl_ws_recv called with NULL buffer with a buflen + * Rebase libcurl-ocloexec.patch + * Remove curl-handle_user-defined_connection_headers.patch upstream + +------------------------------------------------------------------- Old: ---- curl-8.16.0.tar.xz curl-8.16.0.tar.xz.asc curl-handle_user-defined_connection_headers.patch New: ---- curl-8.17.0.tar.xz curl-8.17.0.tar.xz.asc curl-vtls-fix-CURLOPT_CAPATH-use.patch ----------(Old B)---------- Old: * Rebase libcurl-ocloexec.patch * Remove curl-handle_user-defined_connection_headers.patch upstream ----------(Old E)---------- ----------(New B)---------- New: * vtls: fix CURLOPT_CAPATH use [gh#curl/curl#19401] * Add upstream curl-vtls-fix-CURLOPT_CAPATH-use.patch ----------(New E)---------- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ curl.spec ++++++ --- /var/tmp/diff_new_pack.w3P9Qv/_old 2025-11-21 16:55:00.342131237 +0100 +++ /var/tmp/diff_new_pack.w3P9Qv/_new 2025-11-21 16:55:00.346131406 +0100 @@ -36,7 +36,7 @@ %endif Name: curl%{?psuffix} -Version: 8.16.0 +Version: 8.17.0 Release: 0 Summary: A Tool for Transferring Data from URLs License: curl @@ -51,8 +51,8 @@ Patch2: curl-secure-getenv.patch # PATCH-FIX-OPENSUSE bsc#1076446 protocol redirection not supported or disabled Patch3: curl-disabled-redirect-protocol-message.patch -# PATCH-FIX-UPSTREAM bsc#1249448 http: handle user-defined connection headers -Patch4: curl-handle_user-defined_connection_headers.patch +# PATCH-FIX-UPSTREAM vtls: fix CURLOPT_CAPATH use +Patch4: curl-vtls-fix-CURLOPT_CAPATH-use.patch BuildRequires: groff BuildRequires: libtool BuildRequires: pkgconfig ++++++ curl-8.16.0.tar.xz -> curl-8.17.0.tar.xz ++++++ ++++ 160226 lines of diff (skipped) ++++++ curl-vtls-fix-CURLOPT_CAPATH-use.patch ++++++ >From f55974c139d88582a9c503c9a35840f3b9fae458 Mon Sep 17 00:00:00 2001 From: Stefan Eissing <[email protected]> Date: Sat, 8 Nov 2025 14:28:38 +0100 Subject: [PATCH 326/500] vtls: fix CURLOPT_CAPATH use A regression in curl 8.17.0 led to a customer CAPATH set by the application (or the curl command) to be ignored unless licurl was built with a default CAPATH. Add test cases using `--capath` on the custom pytest CA, generated with the help of the openssl command when available. Fixes #19401 Reported-by: Brad King Closes #19308 diff --git a/lib/vtls/vtls.c b/lib/vtls/vtls.c index 3b7a095c8b..3858cad983 100644 --- a/lib/vtls/vtls.c +++ b/lib/vtls/vtls.c @@ -310,7 +310,6 @@ CURLcode Curl_ssl_easy_config_complete(struct Curl_easy *data) if(result) return result; } - sslc->primary.CApath = data->set.str[STRING_SSL_CAPATH]; #endif #ifdef CURL_CA_BUNDLE if(!sslc->custom_cafile && !set->str[STRING_SSL_CAFILE]) { @@ -322,6 +321,7 @@ CURLcode Curl_ssl_easy_config_complete(struct Curl_easy *data) } sslc->primary.CAfile = data->set.str[STRING_SSL_CAFILE]; sslc->primary.CRLfile = data->set.str[STRING_SSL_CRLFILE]; + sslc->primary.CApath = data->set.str[STRING_SSL_CAPATH]; sslc->primary.issuercert = data->set.str[STRING_SSL_ISSUERCERT]; sslc->primary.issuercert_blob = data->set.blobs[BLOB_SSL_ISSUERCERT]; sslc->primary.cipher_list = data->set.str[STRING_SSL_CIPHER_LIST]; @@ -358,7 +358,6 @@ CURLcode Curl_ssl_easy_config_complete(struct Curl_easy *data) if(result) return result; } - sslc->primary.CApath = data->set.str[STRING_SSL_CAPATH_PROXY]; #endif #ifdef CURL_CA_BUNDLE if(!sslc->custom_cafile && !set->str[STRING_SSL_CAFILE_PROXY]) { @@ -370,6 +369,7 @@ CURLcode Curl_ssl_easy_config_complete(struct Curl_easy *data) #endif } sslc->primary.CAfile = data->set.str[STRING_SSL_CAFILE_PROXY]; + sslc->primary.CApath = data->set.str[STRING_SSL_CAPATH_PROXY]; sslc->primary.cipher_list = data->set.str[STRING_SSL_CIPHER_LIST_PROXY]; sslc->primary.cipher_list13 = data->set.str[STRING_SSL_CIPHER13_LIST_PROXY]; sslc->primary.pinned_key = data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY]; diff --git a/tests/http/test_17_ssl_use.py b/tests/http/test_17_ssl_use.py index 57e1c01404..615658f06c 100644 --- a/tests/http/test_17_ssl_use.py +++ b/tests/http/test_17_ssl_use.py @@ -597,3 +597,29 @@ class TestSSLUse: ]) # expect NOT_IMPLEMENTED or OK assert r.exit_code in [0, 2], f'{r.dump_logs()}' + + @pytest.mark.skipif(condition=not Env.have_openssl(), reason="needs openssl command") + def test_17_21_capath_valid(self, env: Env, httpd): + if env.curl_uses_lib('rustls-ffi'): + pytest.skip('rustls does not support CURLOPT_CAPATH') + proto = 'http/1.1' + curl = CurlClient(env=env) + url = f'https://{env.authority_for(env.domain1, proto)}/curltest/sslinfo' + r = curl.http_get(url=url, alpn_proto=proto, extra_args=[ + '--capath', env.ca.hashdir + ]) + assert r.exit_code == 0, f'{r.dump_logs()}' + assert r.json['HTTPS'] == 'on', f'{r.json}' + + @pytest.mark.skipif(condition=not Env.have_openssl(), reason="needs openssl command") + def test_17_22_capath_invalid(self, env: Env, httpd): + # we can test all TLS backends here. the ones not supporting CAPATH + # need to fail as well as the ones which do, but get an invalid path. + proto = 'http/1.1' + curl = CurlClient(env=env) + url = f'https://{env.authority_for(env.domain1, proto)}/curltest/sslinfo' + r = curl.http_get(url=url, alpn_proto=proto, extra_args=[ + '--capath', os.path.join(env.gen_dir, 'ca/invalid') + ]) + # CURLE_PEER_FAILED_VERIFICATION or CURLE_SSL_CACERT_BADFILE + assert r.exit_code in [60, 77], f'{r.dump_logs()}' diff --git a/tests/http/testenv/certs.py b/tests/http/testenv/certs.py index e59b1ea147..c9a30aaac0 100644 --- a/tests/http/testenv/certs.py +++ b/tests/http/testenv/certs.py @@ -28,6 +28,8 @@ import base64 import ipaddress import os import re +import shutil +import subprocess from datetime import timedelta, datetime, timezone from typing import List, Any, Optional @@ -200,6 +202,10 @@ class Credentials: def combined_file(self) -> Optional[str]: return self._combined_file + @property + def hashdir(self) -> Optional[str]: + return os.path.join(self._store.path, 'hashdir') + def get_first(self, name) -> Optional['Credentials']: creds = self._store.get_credentials_for_name(name) if self._store else [] return creds[0] if len(creds) else None @@ -236,6 +242,16 @@ class Credentials: creds.issue_certs(spec.sub_specs, chain=subchain) return creds + def create_hashdir(self, openssl): + os.makedirs(self.hashdir, exist_ok=True) + p = subprocess.run(args=[ + openssl, 'x509', '-hash', '-noout', '-in', self.cert_file + ], capture_output=True, text=True) + if p.returncode != 0: + raise Exception(f'openssl failed to compute cert hash: {p}') + cert_hname = f'{p.stdout.strip()}.0' + shutil.copy(self.cert_file, os.path.join(self.hashdir, cert_hname)) + class CertStore: diff --git a/tests/http/testenv/curl.py b/tests/http/testenv/curl.py index dc885ab8cb..a92e4f681f 100644 --- a/tests/http/testenv/curl.py +++ b/tests/http/testenv/curl.py @@ -987,7 +987,8 @@ class CurlClient: pass elif insecure: args.append('--insecure') - elif active_options and "--cacert" in active_options: + elif active_options and ("--cacert" in active_options or \ + "--capath" in active_options): pass elif u.hostname: args.extend(["--cacert", self.env.ca.cert_file]) diff --git a/tests/http/testenv/env.py b/tests/http/testenv/env.py index ff8741530b..859b704a35 100644 --- a/tests/http/testenv/env.py +++ b/tests/http/testenv/env.py @@ -199,6 +199,16 @@ class EnvConfig: ]), ] + self.openssl = 'openssl' + p = subprocess.run(args=[self.openssl, 'version'], + capture_output=True, text=True) + if p.returncode != 0: + # no openssl in path + self.openssl = None + self.openssl_version = None + else: + self.openssl_version = p.stdout.strip() + self.nghttpx = self.config['nghttpx']['nghttpx'] if len(self.nghttpx.strip()) == 0: self.nghttpx = None @@ -372,6 +382,10 @@ class Env: def incomplete_reason() -> Optional[str]: return Env.CONFIG.get_incomplete_reason() + @staticmethod + def have_openssl() -> bool: + return Env.CONFIG.openssl is not None + @staticmethod def have_nghttpx() -> bool: return Env.CONFIG.nghttpx is not None @@ -548,6 +562,8 @@ class Env: store_dir=ca_dir, key_type="rsa2048") self._ca.issue_certs(self.CONFIG.cert_specs) + if self.have_openssl(): + self._ca.create_hashdir(self.openssl) def setup(self): os.makedirs(self.gen_dir, exist_ok=True) @@ -703,6 +719,10 @@ class Env: def curl(self) -> str: return self.CONFIG.curl + @property + def openssl(self) -> Optional[str]: + return self.CONFIG.openssl + @property def httpd(self) -> str: return self.CONFIG.httpd -- 2.51.1 ++++++ libcurl-ocloexec.patch ++++++ --- /var/tmp/diff_new_pack.w3P9Qv/_old 2025-11-21 16:55:04.098289520 +0100 +++ /var/tmp/diff_new_pack.w3P9Qv/_new 2025-11-21 16:55:04.098289520 +0100 @@ -7,49 +7,49 @@ compile time is not enough. -Index: curl-8.16.0/lib/file.c +Index: curl-8.17.0/lib/file.c =================================================================== ---- curl-8.16.0.orig/lib/file.c -+++ curl-8.16.0/lib/file.c -@@ -270,7 +270,7 @@ static CURLcode file_connect(struct Curl +--- curl-8.17.0.orig/lib/file.c ++++ curl-8.17.0/lib/file.c +@@ -266,7 +266,7 @@ static CURLcode file_connect(struct Curl } } #else -- fd = open(real_path, O_RDONLY); -+ fd = open(real_path, O_RDONLY|O_CLOEXEC); +- fd = curlx_open(real_path, O_RDONLY); ++ fd = curlx_open(real_path, O_RDONLY|O_CLOEXEC); file->path = real_path; #endif #endif -@@ -349,9 +349,9 @@ static CURLcode file_upload(struct Curl_ +@@ -345,9 +345,9 @@ static CURLcode file_upload(struct Curl_ #if (defined(ANDROID) || defined(__ANDROID__)) && \ (defined(__i386__) || defined(__arm__)) -- fd = open(file->path, mode, (mode_t)data->set.new_file_perms); -+ fd = open(file->path, mode|O_CLOEXEC, (mode_t)data->set.new_file_perms); +- fd = curlx_open(file->path, mode, (mode_t)data->set.new_file_perms); ++ fd = curlx_open(file->path, mode|O_CLOEXEC, (mode_t)data->set.new_file_perms); #else -- fd = open(file->path, mode, data->set.new_file_perms); -+ fd = open(file->path, mode|O_CLOEXEC, data->set.new_file_perms); +- fd = curlx_open(file->path, mode, data->set.new_file_perms); ++ fd = curlx_open(file->path, mode|O_CLOEXEC, data->set.new_file_perms); #endif if(fd < 0) { failf(data, "cannot open %s for writing", file->path); -Index: curl-8.16.0/lib/if2ip.c +Index: curl-8.17.0/lib/if2ip.c =================================================================== ---- curl-8.16.0.orig/lib/if2ip.c -+++ curl-8.16.0/lib/if2ip.c +--- curl-8.17.0.orig/lib/if2ip.c ++++ curl-8.17.0/lib/if2ip.c @@ -208,7 +208,7 @@ if2ip_result_t Curl_if2ip(int af, if(len >= sizeof(req.ifr_name)) return IF2IP_NOT_FOUND; -- dummy = socket(AF_INET, SOCK_STREAM, 0); -+ dummy = socket(AF_INET, SOCK_STREAM|SOCK_CLOEXEC, 0); +- dummy = CURL_SOCKET(AF_INET, SOCK_STREAM, 0); ++ dummy = CURL_SOCKET(AF_INET, SOCK_STREAM|SOCK_CLOEXEC, 0); if(CURL_SOCKET_BAD == dummy) return IF2IP_NOT_FOUND; -Index: curl-8.16.0/configure.ac +Index: curl-8.17.0/configure.ac =================================================================== ---- curl-8.16.0.orig/configure.ac -+++ curl-8.16.0/configure.ac -@@ -456,6 +456,8 @@ AC_DEFINE_UNQUOTED(CURL_OS, "${host}", [ +--- curl-8.17.0.orig/configure.ac ++++ curl-8.17.0/configure.ac +@@ -459,6 +459,8 @@ AC_DEFINE_UNQUOTED(CURL_OS, "${host}", [ # Silence warning: ar: 'u' modifier ignored since 'D' is the default AC_SUBST(AR_FLAGS, [cr]) @@ -58,10 +58,10 @@ dnl This defines _ALL_SOURCE for AIX CURL_CHECK_AIX_ALL_SOURCE -Index: curl-8.16.0/lib/hostip.c +Index: curl-8.17.0/lib/hostip.c =================================================================== ---- curl-8.16.0.orig/lib/hostip.c -+++ curl-8.16.0/lib/hostip.c +--- curl-8.17.0.orig/lib/hostip.c ++++ curl-8.17.0/lib/hostip.c @@ -46,6 +46,7 @@ #include <signal.h> #endif @@ -70,28 +70,27 @@ #include "urldata.h" #include "sendf.h" #include "connect.h" -@@ -709,7 +710,7 @@ bool Curl_ipv6works(struct Curl_easy *da +@@ -704,7 +705,7 @@ bool Curl_ipv6works(struct Curl_easy *da else { int ipv6_works = -1; /* probe to see if we have a working IPv6 stack */ -- curl_socket_t s = socket(PF_INET6, SOCK_DGRAM, 0); -+ curl_socket_t s = socket(PF_INET6, SOCK_DGRAM|SOCK_CLOEXEC, 0); +- curl_socket_t s = CURL_SOCKET(PF_INET6, SOCK_DGRAM, 0); ++ curl_socket_t s = CURL_SOCKET(PF_INET6, SOCK_DGRAM|SOCK_CLOEXEC, 0); if(s == CURL_SOCKET_BAD) /* an IPv6 address was requested but we cannot get/use one */ ipv6_works = 0; -Index: curl-8.16.0/lib/cf-socket.c +Index: curl-8.17.0/lib/cf-socket.c =================================================================== ---- curl-8.16.0.orig/lib/cf-socket.c -+++ curl-8.16.0/lib/cf-socket.c -@@ -369,7 +369,9 @@ static CURLcode socket_open(struct Curl_ +--- curl-8.17.0.orig/lib/cf-socket.c ++++ curl-8.17.0/lib/cf-socket.c +@@ -366,7 +366,8 @@ static CURLcode socket_open(struct Curl_ } else { /* opensocket callback not set, so simply create the socket now */ -- *sockfd = socket(addr->family, addr->socktype, addr->protocol); -+ *sockfd = socket(addr->family, -+ addr->socktype|SOCK_CLOEXEC, -+ addr->protocol); +- *sockfd = CURL_SOCKET(addr->family, addr->socktype, addr->protocol); ++ *sockfd = CURL_SOCKET(addr->family, addr->socktype|SOCK_CLOEXEC, ++ addr->protocol); } - if(*sockfd == CURL_SOCKET_BAD) + if(*sockfd == CURL_SOCKET_BAD) {
