diff mbox series

[wrynose,6/8] curl: fix CVE-2026-11856

Message ID 20260805083103.2633995-7-deeratho@cisco.com
State New
Headers show
Series curl: Security fixes | expand

Commit Message

From: Deepak Rathore <deeratho@cisco.com>

This patch applies the upstream curl security backports for
CVE-2026-11856. The main fix commit is referenced in [1], the
Digest proxy-state follow-up is referenced in [2], and the public
curl advisory is referenced in [3]. The individual backported commit
links are recorded in the embedded patch headers.

[1] https://github.com/curl/curl/commit/5c6b4880357ab3e72967c1c45cae0f96ffabc535
[2] https://github.com/curl/curl/commit/7ec25148c06b049d3252172ff17fae85b19c54c9
[3] https://curl.se/docs/CVE-2026-11856.html

Signed-off-by: Deepak Rathore <deeratho@cisco.com>
---
 .../curl/curl/CVE-2026-11856_p1.patch         | 372 ++++++++++++++++++
 .../curl/curl/CVE-2026-11856_p2.patch         |  72 ++++
 meta/recipes-support/curl/curl_8.19.0.bb      |   2 +
 3 files changed, 446 insertions(+)
 create mode 100644 meta/recipes-support/curl/curl/CVE-2026-11856_p1.patch
 create mode 100644 meta/recipes-support/curl/curl/CVE-2026-11856_p2.patch
diff mbox series

Patch

diff --git a/meta/recipes-support/curl/curl/CVE-2026-11856_p1.patch b/meta/recipes-support/curl/curl/CVE-2026-11856_p1.patch
new file mode 100644
index 0000000000..80fb18a7c1
--- /dev/null
+++ b/meta/recipes-support/curl/curl/CVE-2026-11856_p1.patch
@@ -0,0 +1,372 @@ 
+From 3349fbcb7876456cadc3cc3a8d3e8aff29406906 Mon Sep 17 00:00:00 2001
+From: Daniel Stenberg <daniel@haxx.se>
+Date: Mon, 13 Jul 2026 23:17:23 -0700
+Subject: [PATCH] digest: flush state on origin or credential change
+
+Verified by test 1686
+
+Closes #21944
+
+CVE: CVE-2026-11856
+Upstream-Status: Backport [https://github.com/curl/curl/commit/5c6b4880357ab3e72967c1c45cae0f96ffabc535]
+
+Backport Changes:
+- Wrynose curl 8.19.0 does not have upstream Curl_peer or
+  struct Curl_creds. This backport stores the host Digest origin
+  and user/password on digestdata, then flushes stale host Digest
+  state before reuse when either value changes.
+- Kept Wrynose test-list ordering and added only the upstream
+  test1686/lib1686 regression coverage.
+
+(cherry picked from commit 5c6b4880357ab3e72967c1c45cae0f96ffabc535)
+Signed-off-by: Deepak Rathore <deeratho@cisco.com>
+---
+ lib/http_digest.c          | 50 ++++++++++++++++++++
+ lib/urldata.h              |  3 ++
+ lib/vauth/digest.c         |  3 ++
+ lib/vauth/digest_sspi.c    |  4 ++
+ tests/data/Makefile.am     |  2 +-
+ tests/data/test1686        | 84 +++++++++++++++++++++++++++++++++
+ tests/libtest/Makefile.inc |  1 +
+ tests/libtest/lib1686.c    | 96 ++++++++++++++++++++++++++++++++++++++
+ 8 files changed, 242 insertions(+), 1 deletion(-)
+ create mode 100644 tests/data/test1686
+ create mode 100644 tests/libtest/lib1686.c
+
+diff --git a/lib/http_digest.c b/lib/http_digest.c
+index f5b20c5e9d..29bfa3b0b3 100644
+--- a/lib/http_digest.c
++++ b/lib/http_digest.c
+@@ -29,6 +29,7 @@
+ #include "strcase.h"
+ #include "vauth/vauth.h"
+ #include "http_digest.h"
++#include "curlx/strdup.h"
+ #include "curlx/strparse.h"
+ 
+ /* Test example headers:
+@@ -38,6 +39,49 @@ Proxy-Authenticate: Digest realm="testrealm", nonce="1053604598"
+ 
+ */
+ 
++static CURLcode digest_update_origin(struct Curl_easy *data,
++                                     struct digestdata *digest,
++                                     const char *userp,
++                                     const char *passwdp)
++{
++  char *origin;
++  char *authuser;
++  char *authpasswd;
++  bool flush = FALSE;
++
++  origin = curl_maprintf("%s:%d:%s", data->conn->scheme->name,
++                         data->conn->remote_port, data->conn->host.name);
++  if(!origin)
++    return CURLE_OUT_OF_MEMORY;
++
++  authuser = curlx_strdup(userp);
++  authpasswd = curlx_strdup(passwdp);
++  if(!authuser || !authpasswd) {
++    curlx_free(origin);
++    curlx_free(authuser);
++    curlx_free(authpasswd);
++    return CURLE_OUT_OF_MEMORY;
++  }
++
++  if(digest->authorigin && Curl_timestrcmp(digest->authorigin, origin))
++    flush = TRUE;
++  else if(digest->authuser && Curl_timestrcmp(digest->authuser, userp))
++    flush = TRUE;
++  else if(digest->authpasswd && Curl_timestrcmp(digest->authpasswd, passwdp))
++    flush = TRUE;
++
++  if(flush)
++    Curl_auth_digest_cleanup(digest);
++
++  Curl_safefree(digest->authorigin);
++  Curl_safefree(digest->authuser);
++  Curl_safefree(digest->authpasswd);
++  digest->authorigin = origin;
++  digest->authuser = authuser;
++  digest->authpasswd = authpasswd;
++  return CURLE_OK;
++}
++
+ CURLcode Curl_input_digest(struct Curl_easy *data,
+                            bool proxy,
+                            const char *header) /* rest of the *-authenticate:
+@@ -114,6 +158,12 @@ CURLcode Curl_output_digest(struct Curl_easy *data,
+   if(!passwdp)
+     passwdp = "";
+ 
++  if(!proxy) {
++    result = digest_update_origin(data, digest, userp, passwdp);
++    if(result)
++      return result;
++  }
++
+ #ifdef USE_WINDOWS_SSPI
+   have_chlg = !!digest->input_token;
+ #else
+diff --git a/lib/urldata.h b/lib/urldata.h
+index 455ed7f996..113f6e7102 100644
+--- a/lib/urldata.h
++++ b/lib/urldata.h
+@@ -283,6 +283,9 @@ struct ssl_general_config {
+ #ifndef CURL_DISABLE_DIGEST_AUTH
+ /* Struct used for Digest challenge-response authentication */
+ struct digestdata {
++  char *authorigin; /* origin used for the host Digest state */
++  char *authuser;   /* user used for the host Digest state */
++  char *authpasswd; /* password used for the host Digest state */
+ #ifdef USE_WINDOWS_SSPI
+   BYTE *input_token;
+   size_t input_token_len;
+diff --git a/lib/vauth/digest.c b/lib/vauth/digest.c
+index 9609390387..1feee07764 100644
+--- a/lib/vauth/digest.c
++++ b/lib/vauth/digest.c
+@@ -1028,6 +1028,9 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data,
+  */
+ void Curl_auth_digest_cleanup(struct digestdata *digest)
+ {
++  Curl_safefree(digest->authorigin);
++  Curl_safefree(digest->authuser);
++  Curl_safefree(digest->authpasswd);
+   Curl_safefree(digest->nonce);
+   Curl_safefree(digest->cnonce);
+   Curl_safefree(digest->realm);
+diff --git a/lib/vauth/digest_sspi.c b/lib/vauth/digest_sspi.c
+index f29e569cd1..4688a7b5c5 100644
+--- a/lib/vauth/digest_sspi.c
++++ b/lib/vauth/digest_sspi.c
+@@ -636,6 +636,10 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data,
+  */
+ void Curl_auth_digest_cleanup(struct digestdata *digest)
+ {
++  Curl_safefree(digest->authorigin);
++  Curl_safefree(digest->authuser);
++  Curl_safefree(digest->authpasswd);
++
+   /* Free the input token */
+   Curl_safefree(digest->input_token);
+ 
+diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am
+index f9d20a9cc8..89eae33ca0 100644
+--- a/tests/data/Makefile.am
++++ b/tests/data/Makefile.am
+@@ -226,7 +226,7 @@ test1660 test1661 test1662 test1663 test1664 test1665 \
+ \
+ test1670 test1671 \
+ \
+-test1680 test1681 test1682 test1683 \
++test1680 test1681 test1682 test1683 test1686 \
+ \
+ test1700 test1701 test1702 test1703 test1704 test1705 test1706 test1707 \
+ test1708 test1709 test1710 test1711 test1712 \
+diff --git a/tests/data/test1686 b/tests/data/test1686
+new file mode 100644
+index 0000000000..2d419ad608
+--- /dev/null
++++ b/tests/data/test1686
+@@ -0,0 +1,84 @@
++<?xml version="1.0" encoding="US-ASCII"?>
++<testcase>
++<info>
++<keywords>
++HTTP
++Digest
++</keywords>
++</info>
++
++<reply>
++<data crlf="headers" nocheck="yes">
++HTTP/1.1 401 Authorization Required
++Server: Apache/1.3.27 (Darwin) PHP/4.1.2
++WWW-Authenticate: Digest realm="my-backyard", nonce="314156295"
++Content-Length: 26
++
++This is not the real page
++</data>
++
++# This is supposed to be returned when the server gets a
++# Authorization: Digest line passed-in from the client
++<data1000 crlf="headers">
++HTTP/1.1 200 OK
++Server: Apache/1.3.27 (Darwin) PHP/4.1.2
++Content-Type: text/html; charset=iso-8859-1
++Content-Length: 23
++
++This IS the real page!
++</data1000>
++
++</reply>
++
++<client>
++<features>
++!SSPI
++crypto
++digest
++</features>
++<server>
++http
++</server>
++<name>
++HTTP Digest to different origins and switching credentials
++</name>
++<tool>
++lib%TESTNUMBER
++</tool>
++<command>
++%HOSTIP %HTTPPORT
++</command>
++</client>
++
++<verify>
++<protocol crlf="headers">
++GET /api HTTP/1.1
++Host: first.test:%HTTPPORT
++Accept: */*
++
++GET /api HTTP/1.1
++Host: first.test:%HTTPPORT
++Authorization: Digest username="alice", realm="my-backyard", nonce="314156295", uri="/api", response="4ecc00e567c37a9d537727890c2e5b32"
++Accept: */*
++
++GET /hook HTTP/1.1
++Host: second.test:%HTTPPORT
++Accept: */*
++
++GET /hook HTTP/1.1
++Host: second.test:%HTTPPORT
++Authorization: Digest username="alice", realm="my-backyard", nonce="314156295", uri="/hook", response="d3a7738fb6a23f5543fb8dacc0f0f253"
++Accept: */*
++
++GET /hook HTTP/1.1
++Host: second.test:%HTTPPORT
++Accept: */*
++
++GET /hook HTTP/1.1
++Host: second.test:%HTTPPORT
++Authorization: Digest username="bob", realm="my-backyard", nonce="314156295", uri="/hook", response="777e68eddb77294d9cbd6134973cbbab"
++Accept: */*
++
++</protocol>
++</verify>
++</testcase>
+diff --git a/tests/libtest/Makefile.inc b/tests/libtest/Makefile.inc
+index e938b87bc5..ef23ca4e28 100644
+--- a/tests/libtest/Makefile.inc
++++ b/tests/libtest/Makefile.inc
+@@ -102,6 +102,7 @@ TESTS_C = \
+   lib1598.c lib1599.c \
+   lib1647.c \
+   lib1662.c \
++  lib1686.c \
+   lib1900.c lib1901.c lib1902.c lib1903.c lib1905.c lib1906.c lib1907.c \
+   lib1908.c           lib1910.c lib1911.c lib1912.c lib1913.c \
+   lib1915.c lib1916.c           lib1918.c lib1919.c lib1920.c \
+diff --git a/tests/libtest/lib1686.c b/tests/libtest/lib1686.c
+new file mode 100644
+index 0000000000..e457012bb9
+--- /dev/null
++++ b/tests/libtest/lib1686.c
+@@ -0,0 +1,96 @@
++/***************************************************************************
++ *                                  _   _ ____  _
++ *  Project                     ___| | | |  _ \| |
++ *                             / __| | | | |_) | |
++ *                            | (__| |_| |  _ <| |___
++ *                             \___|\___/|_| \_\_____|
++ *
++ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
++ *
++ * This software is licensed as described in the file COPYING, which
++ * you should have received as part of this distribution. The terms
++ * are also available at https://curl.se/docs/copyright.html.
++ *
++ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
++ * copies of the Software, and permit persons to whom the Software is
++ * furnished to do so, under the terms of the COPYING file.
++ *
++ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
++ * KIND, either express or implied.
++ *
++ * SPDX-License-Identifier: curl
++ *
++ ***************************************************************************/
++#include "first.h"
++
++static size_t devnull_1686(char *p, size_t s, size_t n, void *u)
++{
++  (void)p;
++  (void)u;
++  return s * n;
++}
++
++#define FIRSTHOST "first.test"
++#define SECONDHOST "second.test"
++
++static CURLcode test_lib1686(const char *hostip)
++{
++  CURL *curl = NULL;
++  CURLcode result = CURLE_OK;
++  const char *httpport = libtest_arg2;
++  char firsturl[100];
++  char secondurl[100];
++  char firstres[100];
++  char secondres[100];
++  struct curl_slist *host = NULL;
++  struct curl_slist *host2 = NULL;
++
++  if(curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) {
++    curl_mfprintf(stderr, "curl_global_init() failed\n");
++    return TEST_ERR_MAJOR_BAD;
++  }
++
++  /* create strings for CURLOPT_RESOLVE */
++  curl_msnprintf(firstres, sizeof(firstres), "%s:%s:%s",
++                 FIRSTHOST, httpport, hostip);
++  curl_msnprintf(secondres, sizeof(secondres), "%s:%s:%s",
++                 SECONDHOST, httpport, hostip);
++
++  /* create URLs */
++  curl_msnprintf(firsturl, sizeof(firsturl), "http://%s:%s/api",
++                 FIRSTHOST, httpport);
++  curl_msnprintf(secondurl, sizeof(secondurl), "http://%s:%s/hook",
++                 SECONDHOST, httpport);
++
++  host = curl_slist_append(NULL, firstres);
++  if(!host)
++    goto test_cleanup;
++  host2 = curl_slist_append(host, secondres);
++  if(!host2)
++    goto test_cleanup;
++  host = host2;
++
++  curl = curl_easy_init();
++  if(curl) {
++    easy_setopt(curl, CURLOPT_RESOLVE, host);
++    easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);
++    easy_setopt(curl, CURLOPT_USERPWD, "alice:bond");
++    easy_setopt(curl, CURLOPT_WRITEFUNCTION, devnull_1686);
++
++    easy_setopt(curl, CURLOPT_URL, firsturl);
++    result = curl_easy_perform(curl);
++
++    easy_setopt(curl, CURLOPT_URL, secondurl);
++    result = curl_easy_perform(curl);
++
++    easy_setopt(curl, CURLOPT_USERPWD, "bob:secret");
++    easy_setopt(curl, CURLOPT_URL, secondurl);
++    result = curl_easy_perform(curl);
++  }
++
++test_cleanup:
++  curl_easy_cleanup(curl);
++  curl_global_cleanup();
++  curl_slist_free_all(host);
++  return result;
++}
+-- 
+2.35.6
diff --git a/meta/recipes-support/curl/curl/CVE-2026-11856_p2.patch b/meta/recipes-support/curl/curl/CVE-2026-11856_p2.patch
new file mode 100644
index 0000000000..4e3dc45186
--- /dev/null
+++ b/meta/recipes-support/curl/curl/CVE-2026-11856_p2.patch
@@ -0,0 +1,72 @@ 
+From 0d7f5b5037e2878ee68ca8317445df1240d117bf Mon Sep 17 00:00:00 2001
+From: alhudz <al.hudz.k@gmail.com>
+Date: Thu, 30 Jul 2026 22:18:13 -0700
+Subject: [PATCH] digest: flush proxy state on proxy or credential change
+
+Closes #21951
+
+CVE: CVE-2026-11856
+Upstream-Status: Backport [https://github.com/curl/curl/commit/7ec25148c06b049d3252172ff17fae85b19c54c9]
+
+Backport Changes:
+- Wrynose curl 8.19.0 does not have upstream Curl_peer or Curl_creds.
+  This backport extends the string-based Digest origin and credential
+  tracking introduced by upstream commit [1] to the proxy path, so
+  proxy Digest state is flushed when the proxy destination or proxy
+  credentials change.
+
+[1] https://github.com/curl/curl/commit/5c6b4880357ab3e72967c1c45cae0f96ffabc535
+
+(cherry picked from commit 7ec25148c06b049d3252172ff17fae85b19c54c9)
+Signed-off-by: Deepak Rathore <deeratho@cisco.com>
+---
+ lib/http_digest.c | 20 +++++++++++++-------
+ 1 file changed, 13 insertions(+), 7 deletions(-)
+
+diff --git a/lib/http_digest.c b/lib/http_digest.c
+index 29bfa3b0b3..06e4028f02 100644
+--- a/lib/http_digest.c
++++ b/lib/http_digest.c
+@@ -41,6 +41,7 @@ Proxy-Authenticate: Digest realm="testrealm", nonce="1053604598"
+ 
+ static CURLcode digest_update_origin(struct Curl_easy *data,
+                                      struct digestdata *digest,
++                                     bool proxy,
+                                      const char *userp,
+                                      const char *passwdp)
+ {
+@@ -49,8 +50,15 @@ static CURLcode digest_update_origin(struct Curl_easy *data,
+   char *authpasswd;
+   bool flush = FALSE;
+ 
+-  origin = curl_maprintf("%s:%d:%s", data->conn->scheme->name,
+-                         data->conn->remote_port, data->conn->host.name);
++#ifndef CURL_DISABLE_PROXY
++  if(proxy)
++    origin = curl_maprintf("%d:%d:%s", data->conn->http_proxy.proxytype,
++                           data->conn->http_proxy.port,
++                           data->conn->http_proxy.host.name);
++  else
++#endif
++    origin = curl_maprintf("%s:%d:%s", data->conn->scheme->name,
++                           data->conn->remote_port, data->conn->host.name);
+   if(!origin)
+     return CURLE_OUT_OF_MEMORY;
+ 
+@@ -158,11 +166,9 @@ CURLcode Curl_output_digest(struct Curl_easy *data,
+   if(!passwdp)
+     passwdp = "";
+ 
+-  if(!proxy) {
+-    result = digest_update_origin(data, digest, userp, passwdp);
+-    if(result)
+-      return result;
+-  }
++  result = digest_update_origin(data, digest, proxy, userp, passwdp);
++  if(result)
++    return result;
+ 
+ #ifdef USE_WINDOWS_SSPI
+   have_chlg = !!digest->input_token;
+-- 
+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 33ccb73eb3..b0edc5b960 100644
--- a/meta/recipes-support/curl/curl_8.19.0.bb
+++ b/meta/recipes-support/curl/curl_8.19.0.bb
@@ -28,6 +28,8 @@  SRC_URI = " \
     file://CVE-2026-8932-dependent.patch \
     file://CVE-2026-8932.patch \
     file://CVE-2026-8458.patch \
+    file://CVE-2026-11856_p1.patch \
+    file://CVE-2026-11856_p2.patch \
 "
 
 SRC_URI:append:class-nativesdk = " \