diff mbox series

[wrynose,8/9] curl: fix CVE-2026-107929

Message ID 20261106071932.490299-9-dkelaiya@cisco.com
State New
Headers show
Series curl: Security fixes | expand

Commit Message

From: Darsh Kelaiya <dkelaiya@cisco.com>

This patch applies the upstream fix as referenced in [2],
using the commit shown in [1].

Keep the complete CURLOPT_INTERFACE option in the connection and
compare it when choosing a connection to reuse. This includes if!,
host! and ifhost! prefixes, so a transfer cannot silently inherit an
older source binding. Parse that owned option again when binding the
socket and release temporary strings on success and failure.

Adapt the old string helpers, setopt parameters and nested device-bind
check while preserving the complete upstream matching and cleanup.

[1] https://github.com/curl/curl/commit/d37d79df1e01066417aff0a9e6bc675edad3edba
[2] https://curl.se/docs/CVE-2026-107929.html

Signed-off-by: Darsh Kelaiya <dkelaiya@cisco.com>
---
 .../curl/curl/CVE-2026-107929.patch           | 470 ++++++++++++++++++
 meta/recipes-support/curl/curl_8.19.0.bb      |   1 +
 2 files changed, 471 insertions(+)
 create mode 100644 meta/recipes-support/curl/curl/CVE-2026-107929.patch
diff mbox series

Patch

diff --git a/meta/recipes-support/curl/curl/CVE-2026-107929.patch b/meta/recipes-support/curl/curl/CVE-2026-107929.patch
new file mode 100644
index 0000000000..873b03fd5b
--- /dev/null
+++ b/meta/recipes-support/curl/curl/CVE-2026-107929.patch
@@ -0,0 +1,470 @@ 
+From d37d79df1e01066417aff0a9e6bc675edad3edba Mon Sep 17 00:00:00 2001
+From: Stefan Eissing <stefan@eissing.org>
+Date: Fri, 9 Oct 2026 09:51:12 +0200
+Subject: [PATCH] url: CURLOPT_INTERFACE fix connection reuse match
+
+Remember the full option string at the connection for use in reuse
+matching and cf-socket binding.
+
+Parse the string when set for syntax check, but remember the unparsed
+version and use that in connection matching.
+
+Parse that string again when the socket is set up.
+
+Closes #23326
+
+CVE: CVE-2026-107929
+Upstream-Status: Backport [https://github.com/curl/curl/commit/d37d79df1e01066417aff0a9e6bc675edad3edba]
+
+Backport Changes:
+- Use curl 8.19.0's data->set.str[], Curl_setstropt() and
+  Curl_safefree() instead of the newer string and free helpers.
+- Change the older setstropt_interface() parameters and its caller
+  to pass the easy handle; remove the obsolete output assertions.
+- Keep bindlocal()'s older nested SO_BINDTODEVICE test. Route its
+  success path through cleanup and keep two blank separators around
+  the new option parsing and binding checks.
+- Replace the older braced local-bind comparison and its split
+  comment ending with upstream's new matching expression.
+
+Signed-off-by: Darsh Kelaiya <dkelaiya@cisco.com>
+---
+ lib/cf-socket.c | 135 +++++++++++++++++++++++++++++++-----------------
+ lib/setopt.c    |  34 +++---------
+ lib/url.c       |  48 +++++++++--------
+ lib/urldata.h   |   6 +--
+ 4 files changed, 126 insertions(+), 97 deletions(-)
+
+diff --git a/lib/cf-socket.c b/lib/cf-socket.c
+index 2ebbe4d..f3978e2 100644
+--- a/lib/cf-socket.c
++++ b/lib/cf-socket.c
+@@ -464,11 +464,13 @@ int Curl_socket_close(struct Curl_easy *data, struct connectdata *conn,
+  * len    [in]     - length of the input string.
+  * dev    [in/out] - address where a pointer to newly allocated memory
+  *                   holding the interface-or-host will be stored upon
+- *                   completion.
++ *                   completion or NULL if the caller is not interested.
+  * iface  [in/out] - address where a pointer to newly allocated memory
+- *                   holding the interface will be stored upon completion.
++ *                   holding the interface will be stored upon completion
++ *                   or NULL if the caller is not interested.
+  * host   [in/out] - address where a pointer to newly allocated memory
+- *                   holding the host will be stored upon completion.
++ *                   holding the host will be stored upon completion
++ *                   or NULL if the caller is not interested.
+  *
+  * Returns CURLE_OK on success.
+  */
+@@ -480,27 +482,36 @@ CURLcode Curl_parse_interface(const char *input,
+   static const char if_host_prefix[] = "ifhost!";
+   size_t len;
+ 
+-  DEBUGASSERT(dev);
+-  DEBUGASSERT(iface);
+-  DEBUGASSERT(host);
+-
+   len = strlen(input);
+   if(len > 512)
+     return CURLE_BAD_FUNCTION_ARGUMENT;
+ 
++  if(dev)
++    *dev = NULL;
++  if(iface)
++    *iface = NULL;
++  if(host)
++    *host = NULL;
++
+   if(!strncmp(if_prefix, input, strlen(if_prefix))) {
+     input += strlen(if_prefix);
+     if(!*input)
+       return CURLE_BAD_FUNCTION_ARGUMENT;
+-    *iface = curlx_memdup0(input, len - strlen(if_prefix));
+-    return *iface ? CURLE_OK : CURLE_OUT_OF_MEMORY;
++    if(iface) {
++      *iface = curlx_memdup0(input, len - strlen(if_prefix));
++      return *iface ? CURLE_OK : CURLE_OUT_OF_MEMORY;
++    }
++    return CURLE_OK;
+   }
+   else if(!strncmp(host_prefix, input, strlen(host_prefix))) {
+     input += strlen(host_prefix);
+     if(!*input)
+       return CURLE_BAD_FUNCTION_ARGUMENT;
+-    *host = curlx_memdup0(input, len - strlen(host_prefix));
+-    return *host ? CURLE_OK : CURLE_OUT_OF_MEMORY;
++    if(host) {
++      *host = curlx_memdup0(input, len - strlen(host_prefix));
++      return *host ? CURLE_OK : CURLE_OUT_OF_MEMORY;
++    }
++    return CURLE_OK;
+   }
+   else if(!strncmp(if_host_prefix, input, strlen(if_host_prefix))) {
+     const char *host_part;
+@@ -509,23 +520,30 @@ CURLcode Curl_parse_interface(const char *input,
+     host_part = memchr(input, '!', len);
+     if(!host_part || !*(host_part + 1))
+       return CURLE_BAD_FUNCTION_ARGUMENT;
+-    *iface = curlx_memdup0(input, host_part - input);
+-    if(!*iface)
+-      return CURLE_OUT_OF_MEMORY;
++    if(iface) {
++      *iface = curlx_memdup0(input, host_part - input);
++      if(!*iface)
++        return CURLE_OUT_OF_MEMORY;
++    }
+     ++host_part;
+-    *host = curlx_memdup0(host_part, len - (host_part - input));
+-    if(!*host) {
+-      curlx_free(*iface);
+-      *iface = NULL;
+-      return CURLE_OUT_OF_MEMORY;
++    if(host) {
++      *host = curlx_memdup0(host_part, len - (host_part - input));
++      if(!*host) {
++        if(iface)
++          Curl_safefree(*iface);
++        return CURLE_OUT_OF_MEMORY;
++      }
+     }
+     return CURLE_OK;
+   }
+ 
+   if(!*input)
+     return CURLE_BAD_FUNCTION_ARGUMENT;
+-  *dev = curlx_memdup0(input, len);
+-  return *dev ? CURLE_OK : CURLE_OUT_OF_MEMORY;
++  if(dev) {
++    *dev = curlx_memdup0(input, len);
++    return *dev ? CURLE_OK : CURLE_OUT_OF_MEMORY;
++  }
++  return CURLE_OK;
+ }
+ 
+ #ifndef CURL_DISABLE_BINDLOCAL
+@@ -545,27 +563,38 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn,
+                                                 "random" */
+   /* how many port numbers to try to bind to, increasing one at a time */
+   int portnum = data->set.localportrange;
+-  const char *dev = data->set.str[STRING_DEVICE];
+-  const char *iface_input = data->set.str[STRING_INTERFACE];
+-  const char *host_input = data->set.str[STRING_BINDHOST];
+-  const char *iface = iface_input ? iface_input : dev;
+-  const char *host = host_input ? host_input : dev;
++  char *dev_in = NULL, *iface_in = NULL, *host_in = NULL;
++  const char *iface = NULL, *host = NULL;
+   int error;
+ #ifdef IP_BIND_ADDRESS_NO_PORT
+   int on = 1;
+ #endif
++  CURLcode result = CURLE_OK;
++
+ #ifndef USE_IPV6
+   (void)scope;
+ #endif
+ 
+-  /*************************************************************
+-   * Select device to bind socket to
+-   *************************************************************/
++  if(conn->interface_opts) {
++    /*************************************************************
++     * Select device to bind socket to
++     *************************************************************/
++    result = Curl_parse_interface(conn->interface_opts,
++                                  &dev_in, &iface_in, &host_in);
++    if(result)
++      goto out;
++    /* This is how we use the values */
++    iface = iface_in ? iface_in : dev_in;
++    host = host_in ? host_in : dev_in;
++  }
++
+   if(!iface && !host && !port)
+-    /* no local kind of binding was requested */
+-    return CURLE_OK;
+-  else if(iface && (strlen(iface) >= 255))
+-    return CURLE_BAD_FUNCTION_ARGUMENT;
++    goto out;
++
++  if(iface && (strlen(iface) >= 255)) {
++    result = CURLE_BAD_FUNCTION_ARGUMENT;
++    goto out;
++  }
+ 
+   memset(&sa, 0, sizeof(struct Curl_sockaddr_storage));
+ 
+@@ -593,14 +622,15 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn,
+          * succeeds it means the parameter was a valid interface and not an IP
+          * address. Return immediately.
+          */
+-        if(!host_input) {
++        if(!host_in) {
+           infof(data, "socket successfully bound to interface '%s'", iface);
+-          return CURLE_OK;
++          result = CURLE_OK;
++          goto out;
+         }
+       }
+     }
+ #endif
+-    if(!host_input) {
++    if(!host_in) {
+       /* Discover IP from input device, then bind to it */
+       if2ip_result = Curl_if2ip(af,
+ #ifdef USE_IPV6
+@@ -610,18 +640,20 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn,
+     }
+     switch(if2ip_result) {
+     case IF2IP_NOT_FOUND:
+-      if(iface_input && !host_input) {
++      if(iface_in && !host_in) {
+         /* Do not fall back to treating it as a hostname */
+         char buffer[STRERROR_LEN];
+         data->state.os_errno = error = SOCKERRNO;
+         failf(data, "Could not bind to interface '%s' with errno %d: %s",
+               iface, error, curlx_strerror(error, buffer, sizeof(buffer)));
+-        return CURLE_INTERFACE_FAILED;
++        result = CURLE_INTERFACE_FAILED;
++        goto out;
+       }
+       break;
+     case IF2IP_AF_NOT_SUPPORTED:
+       /* Signal the caller to try another address family if available */
+-      return CURLE_UNSUPPORTED_PROTOCOL;
++      result = CURLE_UNSUPPORTED_PROTOCOL;
++      goto out;
+     case IF2IP_FOUND:
+       /*
+        * We now have the numerical IP address in the 'myhost' buffer
+@@ -632,7 +664,7 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn,
+       done = 1;
+       break;
+     }
+-    if(!iface_input || host_input) {
++    if(!iface_in || host_in) {
+       /*
+        * This was not an interface, resolve the name as a hostname
+        * or IP number
+@@ -659,7 +691,8 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn,
+         if(af != h_af) {
+           /* bad IP version combo, signal the caller to try another address
+              family if available */
+-          return CURLE_UNSUPPORTED_PROTOCOL;
++          result = CURLE_UNSUPPORTED_PROTOCOL;
++          goto out;
+         }
+         done = 1;
+       }
+@@ -692,8 +725,10 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn,
+                present, is known to be numeric */
+             curl_off_t scope_id;
+             if(curlx_str_number((const char **)CURL_UNCONST(&scope_ptr),
+-                                &scope_id, UINT_MAX))
+-              return CURLE_UNSUPPORTED_PROTOCOL;
++                                &scope_id, UINT_MAX)) {
++              result = CURLE_UNSUPPORTED_PROTOCOL;
++              goto out;
++            }
+             si6->sin6_scope_id = (unsigned int)scope_id;
+           }
+ #endif
+@@ -720,7 +755,8 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn,
+       data->state.os_errno = error = SOCKERRNO;
+       failf(data, "Could not bind to '%s' with errno %d: %s", host,
+             error, curlx_strerror(error, buffer, sizeof(buffer)));
+-      return CURLE_INTERFACE_FAILED;
++      result = CURLE_INTERFACE_FAILED;
++      goto out;
+     }
+   }
+   else {
+@@ -747,7 +783,8 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn,
+       /* we succeeded to bind */
+       infof(data, "Local port: %hu", port);
+       conn->bits.bound = TRUE;
+-      return CURLE_OK;
++      result = CURLE_OK;
++      goto out;
+     }
+ 
+     if(--portnum > 0) {
+@@ -773,7 +810,13 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn,
+           error, curlx_strerror(error, buffer, sizeof(buffer)));
+   }
+ 
+-  return CURLE_INTERFACE_FAILED;
++  result = CURLE_INTERFACE_FAILED;
++
++out:
++  curlx_free(dev_in);
++  curlx_free(iface_in);
++  curlx_free(host_in);
++  return result;
+ }
+ #endif
+ 
+diff --git a/lib/setopt.c b/lib/setopt.c
+index ae77ae8..4c634a8 100644
+--- a/lib/setopt.c
++++ b/lib/setopt.c
+@@ -160,33 +160,18 @@ static CURLcode setstropt_userpwd(const char *option, char **userp,
+   return CURLE_OK;
+ }
+ 
+-static CURLcode setstropt_interface(char *option, char **devp,
+-                                    char **ifacep, char **hostp)
++static CURLcode setstropt_interface(struct Curl_easy *data, char *option)
+ {
+-  char *dev = NULL;
+-  char *iface = NULL;
+-  char *host = NULL;
+-  CURLcode result;
+-
+-  DEBUGASSERT(devp);
+-  DEBUGASSERT(ifacep);
+-  DEBUGASSERT(hostp);
+-
++  if(option && !option[0])
++    option = NULL;
+   if(option) {
+-    /* Parse the interface details if set, otherwise clear them all */
+-    result = Curl_parse_interface(option, &dev, &iface, &host);
++    /* Parse for checking syntax */
++    CURLcode result = Curl_parse_interface(option, NULL, NULL, NULL);
+     if(result)
+       return result;
++    return Curl_setstropt(&data->set.str[STRING_INTERFACE_OPT], option);
+   }
+-  curlx_free(*devp);
+-  *devp = dev;
+-
+-  curlx_free(*ifacep);
+-  *ifacep = iface;
+-
+-  curlx_free(*hostp);
+-  *hostp = host;
+-
++  Curl_safefree(data->set.str[STRING_INTERFACE_OPT]);
+   return CURLE_OK;
+ }
+ 
+@@ -2267,10 +2252,7 @@ static CURLcode setopt_cptr(struct Curl_easy *data, CURLoption option,
+      * Set what interface or address/hostname to bind the socket to when
+      * performing an operation and thus what from-IP your connection will use.
+      */
+-    return setstropt_interface(ptr,
+-                               &s->str[STRING_DEVICE],
+-                               &s->str[STRING_INTERFACE],
+-                               &s->str[STRING_BINDHOST]);
++    return setstropt_interface(data, ptr);
+   case CURLOPT_ISSUERCERT:
+     /*
+      * Set Issuer certificate file
+diff --git a/lib/url.c b/lib/url.c
+index 4a01baf..1b6e9e3 100644
+--- a/lib/url.c
++++ b/lib/url.c
+@@ -550,7 +550,7 @@ void Curl_conn_free(struct Curl_easy *data, struct connectdata *conn)
+   Curl_safefree(conn->host.rawalloc); /* hostname buffer */
+   Curl_safefree(conn->conn_to_host.rawalloc); /* hostname buffer */
+   Curl_safefree(conn->secondaryhostname);
+-  Curl_safefree(conn->localdev);
++  Curl_safefree(conn->interface_opts);
+   Curl_ssl_conn_config_cleanup(conn);
+ 
+ #ifdef USE_UNIX_SOCKETS
+@@ -773,24 +773,29 @@ static bool url_match_connect_config(struct connectdata *conn,
+      m->data->set.ipver != conn->ip_version)
+     return FALSE;
+ 
+-  if(m->needle->localdev || m->needle->localport) {
++  /* When needle has specific local bind settings, those have to match.
++   * If it does not, any other connection might do when it comes to ports. */
++  if(m->needle->localport &&
++     ((conn->localport != m->needle->localport) ||
++      (conn->localportrange != m->needle->localportrange)))
++    return FALSE;
++  /* For interface related bind options, we require a full match
++   * as connection might go totally different ways (VPNs, for example). */
++  if((m->needle->interface_opts != conn->interface_opts) &&
++     (!m->needle->interface_opts || !conn->interface_opts ||
++      strcmp(conn->interface_opts, m->needle->interface_opts)))
++    return FALSE;
++
++  if((m->needle->interface_opts || m->needle->localport) &&
+     /* If we are bound to a specific local end (IP+port), we must not reuse a
+        random other one, although if we did not ask for a particular one we
+-       can reuse one that was bound.
+-
+-       This comparison is a bit rough and too strict. Since the input
+-       parameters can be specified in numerous ways and still end up the same
+-       it would take a lot of processing to make it really accurate. Instead,
+-       this matching will assume that reuses of bound connections will most
+-       likely also reuse the exact same binding parameters and missing out a
+-       few edge cases should not hurt anyone much.
+-    */
+-    if((conn->localport != m->needle->localport) ||
+-       (conn->localportrange != m->needle->localportrange) ||
+-       (m->needle->localdev &&
+-        (!conn->localdev || strcmp(conn->localdev, m->needle->localdev))))
+-      return FALSE;
+-  }
++       can reuse one that was bound. */
++    ((conn->localport != m->needle->localport) ||
++     (conn->localportrange != m->needle->localportrange) ||
++     (m->needle->interface_opts &&
++      (!conn->interface_opts ||
++       strcmp(conn->interface_opts, m->needle->interface_opts)))))
++    return FALSE;
+ 
+   if(m->needle->bits.conn_to_host != conn->bits.conn_to_host)
+     /* do not mix connections that use the "connect to host" feature and
+@@ -1443,9 +1448,10 @@ static struct connectdata *allocate_conn(struct Curl_easy *data)
+   conn->transport_wanted = TRNSPRT_TCP; /* most of them are TCP streams */
+ 
+   /* Store the local bind parameters that will be used for this connection */
+-  if(data->set.str[STRING_DEVICE]) {
+-    conn->localdev = curlx_strdup(data->set.str[STRING_DEVICE]);
+-    if(!conn->localdev)
++  if(data->set.str[STRING_INTERFACE_OPT]) {
++    conn->interface_opts =
++      curlx_strdup(data->set.str[STRING_INTERFACE_OPT]);
++    if(!conn->interface_opts)
+       goto error;
+   }
+ #ifndef CURL_DISABLE_BINDLOCAL
+@@ -1465,7 +1471,7 @@ static struct connectdata *allocate_conn(struct Curl_easy *data)
+   return conn;
+ error:
+ 
+-  curlx_free(conn->localdev);
++  curlx_free(conn->interface_opts);
+   curlx_free(conn);
+   return NULL;
+ }
+diff --git a/lib/urldata.h b/lib/urldata.h
+index 0bc9ade..327c9f4 100644
+--- a/lib/urldata.h
++++ b/lib/urldata.h
+@@ -694,7 +694,7 @@ struct connectdata {
+      made and will serve the purpose of being used for comparison reasons so
+      that subsequent bound-requested connections are not accidentally reusing
+      wrong connections. */
+-  char *localdev;
++  char *interface_opts;
+   uint16_t localportrange;
+ #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)
+   int socks5_gssapi_enctype;
+@@ -1167,9 +1167,7 @@ enum dupstring {
+ #endif
+   STRING_CUSTOMREQUEST,   /* HTTP/FTP/RTSP request/method to use */
+   STRING_DEFAULT_PROTOCOL, /* Protocol to use when the URL does not specify */
+-  STRING_DEVICE,          /* local network interface/address to use */
+-  STRING_INTERFACE,       /* local network interface to use */
+-  STRING_BINDHOST,        /* local address to use */
++  STRING_INTERFACE_OPT,   /* CURLOPT_INTERFACE config */
+   STRING_ENCODING,        /* Accept-Encoding string */
+ #ifndef CURL_DISABLE_FTP
+   STRING_FTP_ACCOUNT,     /* ftp account data */
+-- 
+2.35.6
diff --git a/meta/recipes-support/curl/curl_8.19.0.bb b/meta/recipes-support/curl/curl_8.19.0.bb
index a4f7b466c3..026b5a3638 100644
--- a/meta/recipes-support/curl/curl_8.19.0.bb
+++ b/meta/recipes-support/curl/curl_8.19.0.bb
@@ -70,6 +70,7 @@  SRC_URI = "https://curl.se/download/${BP}.tar.xz \
            file://CVE-2026-92392.patch \
            file://CVE-2026-94266_p1.patch \
            file://CVE-2026-94266_p2.patch \
+           file://CVE-2026-107929.patch \
            "
 
 SRC_URI:append:class-nativesdk = " \