diff mbox series

[wrynose,2/9] curl: fix CVE-2026-87863

Message ID 20261106071932.490299-3-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].

Cap HSTS max-age at two years and keep saving valid entries if one
date cannot be serialized. Adapt the older parser so oversized quoted
ages are capped while preserving unquoted suffix handling. Retain
upstream test1862.

[1] https://github.com/curl/curl/commit/08d50a791b3197a3d0a4fc2f778e243f5501c67e
[2] https://curl.se/docs/CVE-2026-87863.html

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

Patch

diff --git a/meta/recipes-support/curl/curl/CVE-2026-87863.patch b/meta/recipes-support/curl/curl/CVE-2026-87863.patch
new file mode 100644
index 0000000000..d1f489e47e
--- /dev/null
+++ b/meta/recipes-support/curl/curl/CVE-2026-87863.patch
@@ -0,0 +1,203 @@ 
+From 08d50a791b3197a3d0a4fc2f778e243f5501c67e Mon Sep 17 00:00:00 2001
+From: Daniel Stenberg <daniel@haxx.se>
+Date: Tue, 8 Sep 2026 16:04:42 +0200
+Subject: [PATCH] hsts: cap max-age to two years
+
+And ignore problems saving individual HSTS entries - a single entry
+problem should not affect storing other entries.
+
+Added test 1862 to verify.
+
+Closes #22878
+
+CVE: CVE-2026-87863
+Upstream-Status: Backport [https://github.com/curl/curl/commit/08d50a791b3197a3d0a4fc2f778e243f5501c67e]
+
+Backport Changes:
+- Keep curl 8.19.0's max-age parser, which reads directly from the
+  header using TIME_T_MAX as its numeric limit. Upstream first
+  extracts the value and parses it with a two-year numeric limit.
+  Cap parsed ages and numeric overflows at two years.
+- After overflow in a quoted age, skip its digits before checking
+  the closing quote. The older number reader leaves its position
+  unchanged on overflow. For unquoted overflow, keep that position
+  so trailing text is handled as before.
+- Register test1862 in the older grouped tests/data/Makefile.am list.
+
+Signed-off-by: Darsh Kelaiya <dkelaiya@cisco.com>
+---
+ lib/hsts.c             | 32 ++++++++++--------
+ tests/data/Makefile.am |  2 +-
+ tests/data/test1862    | 73 ++++++++++++++++++++++++++++++++++++++++++
+ 3 files changed, 93 insertions(+), 14 deletions(-)
+ create mode 100644 tests/data/test1862
+
+diff --git a/lib/hsts.c b/lib/hsts.c
+index 5ad5bd4..9e5b456 100644
+--- a/lib/hsts.c
++++ b/lib/hsts.c
+@@ -43,6 +43,8 @@
+ #define MAX_HSTS_DATELEN 256
+ #define UNLIMITED        "unlimited"
+ 
++#define CAP_HSTS_MAX_AGE (2*365*24*3600) /* two years cap */
++
+ #if defined(DEBUGBUILD) || defined(UNITTESTS)
+ /* to play well with debug builds, we can *set* a fixed time this will
+    return */
+@@ -152,11 +154,18 @@ CURLcode Curl_hsts_parse(struct hsts *h, const char *hostname,
+         quoted = TRUE;
+ 
+       rc = curlx_str_number(&p, &expires, TIME_T_MAX);
+-      if(rc == STRE_OVERFLOW)
+-        expires = CURL_OFF_T_MAX;
++      if(rc == STRE_OVERFLOW) {
++        expires = CAP_HSTS_MAX_AGE;
++        if(quoted) {
++          while(ISDIGIT(*p))
++            p++;
++        }
++      }
+       else if(rc)
+         /* invalid max-age */
+         return CURLE_BAD_FUNCTION_ARGUMENT;
++      else if(expires > CAP_HSTS_MAX_AGE)
++        expires = CAP_HSTS_MAX_AGE;
+ 
+       if(quoted) {
+         if(*p != '\"')
+@@ -304,22 +313,21 @@ static CURLcode hsts_push(struct Curl_easy *data,
+ /*
+  * Write this single hsts entry to a single output line
+  */
+-static CURLcode hsts_out(struct stsentry *sts, FILE *fp)
++static void hsts_out(struct stsentry *sts, FILE *fp)
+ {
+   struct tm stamp;
+   if(sts->expires != TIME_T_MAX) {
+     CURLcode result = curlx_gmtime((time_t)sts->expires, &stamp);
+-    if(result)
+-      return result;
+-    curl_mfprintf(fp, "%s%s \"%d%02d%02d %02d:%02d:%02d\"\n",
+-                  sts->includeSubDomains ? "." : "", sts->host,
+-                  stamp.tm_year + 1900, stamp.tm_mon + 1, stamp.tm_mday,
+-                  stamp.tm_hour, stamp.tm_min, stamp.tm_sec);
++    if(!result)
++      /* skip the entry if the date function fails */
++      curl_mfprintf(fp, "%s%s \"%d%02d%02d %02d:%02d:%02d\"\n",
++                    sts->includeSubDomains ? "." : "", sts->host,
++                    stamp.tm_year + 1900, stamp.tm_mon + 1, stamp.tm_mday,
++                    stamp.tm_hour, stamp.tm_min, stamp.tm_sec);
+   }
+   else
+     curl_mfprintf(fp, "%s%s \"%s\"\n",
+                   sts->includeSubDomains ? "." : "", sts->host, UNLIMITED);
+-  return CURLE_OK;
+ }
+ 
+ /*
+@@ -354,9 +362,7 @@ CURLcode Curl_hsts_save(struct Curl_easy *data, struct hsts *h,
+     for(e = Curl_llist_head(&h->list); e; e = n) {
+       struct stsentry *sts = Curl_node_elem(e);
+       n = Curl_node_next(e);
+-      result = hsts_out(sts, out);
+-      if(result)
+-        break;
++      hsts_out(sts, out);
+     }
+     curlx_fclose(out);
+     if(!result && tempstore && curlx_rename(tempstore, file))
+diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am
+index 7a9b8d0..e678290 100644
+--- a/tests/data/Makefile.am
++++ b/tests/data/Makefile.am
+@@ -231,7 +231,7 @@ test1680 test1681 test1682 test1683 test1685 \
+ test1700 test1701 test1702 test1703 test1704 test1705 test1706 test1707 \
+ test1708 test1709 test1710 test1711 test1712 \
+ \
+-test1800 test1801 test1802 test1847 test1848 test1849 test1850 \
++test1800 test1801 test1802 test1847 test1848 test1849 test1850 test1862 \
+ \
+ test1900 test1901 test1902 test1903 test1904 test1905 test1906 test1907 \
+ test1908 test1909 test1910 test1911 test1912 test1913 test1914 test1915 \
+diff --git a/tests/data/test1862 b/tests/data/test1862
+new file mode 100644
+index 0000000..b9a29f6
+--- /dev/null
++++ b/tests/data/test1862
+@@ -0,0 +1,73 @@
++<?xml version="1.0" encoding="US-ASCII"?>
++<testcase>
++<info>
++<keywords>
++HTTP
++HSTS
++</keywords>
++</info>
++
++<reply>
++
++<data crlf="headers" nocheck="yes">
++HTTP/1.1 200 OK
++Date: Tue, 09 Nov 2010 14:49:00 GMT
++Content-Length: 6
++Connection: close
++Strict-Transport-Security: max-age=1152921504606846976
++
++-foo-
++</data>
++</reply>
++
++<client>
++<server>
++http
++</server>
++<features>
++HSTS
++Debug
++</features>
++
++# November 11, 2030
++<setenv>
++CURL_HSTS_HTTP=yes
++CURL_TIME=1920582000
++</setenv>
++
++<name>
++HSTS with extreme max-age
++</name>
++<command>
++http://this.hsts.example:%HTTPPORT/%TESTNUMBER --hsts %LOGDIR/input%TESTNUMBER --resolve this.hsts.example:%HTTPPORT:%HOSTIP
++</command>
++<file name="%LOGDIR/input%TESTNUMBER">
++# Your HSTS cache. https://curl.se/docs/hsts.html
++# This file was generated by libcurl! Edit at your own risk.
++somehere.example "20330525 03:33:20"
++elsewhere.example.com "20330727 03:33:20"
++</file>
++<disable>
++test-duphandle
++</disable>
++</client>
++
++<verify>
++# we let it CONNECT to the server to confirm HSTS but deny from there
++<protocol crlf="headers">
++GET /1862 HTTP/1.1
++Host: this.hsts.example:%HTTPPORT
++User-Agent: curl/%VERSION
++Accept: */*
++
++</protocol>
++<file name="%LOGDIR/input%TESTNUMBER" mode="text">
++# Your HSTS cache. https://curl.se/docs/hsts.html
++# This file was generated by libcurl! Edit at your own risk.
++somehere.example "20330525 03:33:20"
++elsewhere.example.com "20330727 03:33:20"
++this.hsts.example "20321109 23:00:00"
++</file>
++
++</verify>
++</testcase>
+-- 
+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 20dbeb9dfd..9fd78a0fd8 100644
--- a/meta/recipes-support/curl/curl_8.19.0.bb
+++ b/meta/recipes-support/curl/curl_8.19.0.bb
@@ -61,6 +61,7 @@  SRC_URI = "https://curl.se/download/${BP}.tar.xz \
            file://CVE-2026-87865_p1.patch \
            file://CVE-2026-87865_p2.patch \
            file://CVE-2026-92391.patch \
+           file://CVE-2026-87863.patch \
            "
 
 SRC_URI:append:class-nativesdk = " \