new file mode 100644
@@ -0,0 +1,270 @@
+From c24a69d8cd368de4e25e3540bf0b7b000a31d5a7 Mon Sep 17 00:00:00 2001
+From: Joshua Rogers <MegaManSec@users.noreply.github.com>
+Date: Sat, 12 Sep 2026 02:57:04 +0200
+Subject: [PATCH] tool: buffer headers before remote filename selection
+
+Verified by test 3037 and 3038.
+
+Closes #22977
+
+CVE: CVE-2026-92390
+Upstream-Status: Backport [https://github.com/curl/curl/commit/c24a69d8cd368de4e25e3540bf0b7b000a31d5a7]
+
+Backport Changes:
+- In content_disposition(), remove curl 8.19.0's curl_maprintf()
+ header-copy block; the upstream block uses curlx_memdup0().
+- Register test3037 and test3038 together in the older grouped
+ tests/data/Makefile.am list instead of one line per test.
+
+Signed-off-by: Darsh Kelaiya <dkelaiya@cisco.com>
+---
+ src/tool_cb_hdr.c | 53 +++++++++++++++++++++++++++--------------
+ tests/data/Makefile.am | 1 +
+ tests/data/test3037 | 62 ++++++++++++++++++++++++++++++++++++++++++++++++
+ tests/data/test3038 | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++
+ 4 files changed, 162 insertions(+), 18 deletions(-)
+
+diff --git a/src/tool_cb_hdr.c b/src/tool_cb_hdr.c
+index fc03cf1..15abb5b 100644
+--- a/src/tool_cb_hdr.c
++++ b/src/tool_cb_hdr.c
+@@ -248,6 +248,28 @@ fail:
+ return rc;
+ }
+
++/* Store a header until the output filename has been decided. */
++static size_t buffer_header(struct HdrCbData *hdrcbdata,
++ const char *str, size_t cb)
++{
++ char *clone = curlx_memdup0(str, cb);
++ if(clone) {
++ struct curl_slist *old = hdrcbdata->headlist;
++ hdrcbdata->headlist = curl_slist_append(old, clone);
++ curlx_free(clone);
++ if(!hdrcbdata->headlist) {
++ curl_slist_free_all(old);
++ return CURL_WRITEFUNC_ERROR;
++ }
++ }
++ else {
++ curl_slist_free_all(hdrcbdata->headlist);
++ hdrcbdata->headlist = NULL;
++ return CURL_WRITEFUNC_ERROR;
++ }
++ return cb;
++}
++
+ /*
+ * Write etag to file when --etag-save option is given.
+ */
+@@ -395,24 +417,8 @@ static size_t content_disposition(const char *str, const char *end,
+
+ if(hdrcbdata->honor_cd_filename &&
+ hdrcbdata->config->show_headers) {
+- /* still awaiting the Content-Disposition header, store the header in
+- memory. Since it is not null-terminated, we need an extra dance. */
+- char *clone = curl_maprintf("%.*s", (int)cb, str);
+- if(clone) {
+- struct curl_slist *old = hdrcbdata->headlist;
+- hdrcbdata->headlist = curl_slist_append(old, clone);
+- curlx_free(clone);
+- if(!hdrcbdata->headlist) {
+- curl_slist_free_all(old);
+- return CURL_WRITEFUNC_ERROR;
+- }
+- }
+- else {
+- curl_slist_free_all(hdrcbdata->headlist);
+- hdrcbdata->headlist = NULL;
+- return CURL_WRITEFUNC_ERROR;
+- }
+- return cb; /* done for now */
++ /* still awaiting the Content-Disposition header */
++ return buffer_header(hdrcbdata, str, cb);
+ }
+
+ return 0; /* ok */
+@@ -434,6 +440,7 @@ size_t tool_header_cb(char *ptr, size_t size, size_t nmemb, void *userdata)
+ const size_t cb = size * nmemb;
+ const char *end = ptr + cb;
+ const char *scheme = NULL;
++ bool cd_checked = FALSE;
+
+ if(!per->config)
+ return CURL_WRITEFUNC_ERROR;
+@@ -493,6 +500,9 @@ size_t tool_header_cb(char *ptr, size_t size, size_t nmemb, void *userdata)
+ size_t rc = content_disposition(str, end, cb, per, response);
+ if(rc)
+ return rc;
++ /* remember that this was checked to avoid duplicating it further
++ down */
++ cd_checked = TRUE;
+ }
+ }
+ if(hdrcbdata->config->writeout) {
+@@ -512,6 +522,13 @@ size_t tool_header_cb(char *ptr, size_t size, size_t nmemb, void *userdata)
+ /* bold headers only for selected protocols */
+ const char *value = NULL;
+
++ /* Informational responses, and ETag headers handled by --etag-save,
++ bypass the content-disposition handling above. Do not open the output
++ file until the filename decision is complete. Only for HTTP(S). */
++ if(hdrcbdata->honor_cd_filename && !cd_checked &&
++ (scheme == proto_http || scheme == proto_https))
++ return buffer_header(hdrcbdata, str, cb);
++
+ if(!outs->stream && !tool_create_output_file(outs, per->config))
+ return CURL_WRITEFUNC_ERROR;
+
+diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am
+index 7a9b8d0..6d12c7c 100644
+--- a/tests/data/Makefile.am
++++ b/tests/data/Makefile.am
+@@ -279,6 +279,7 @@ test3008 test3009 test3010 test3011 test3012 test3013 test3014 test3015 \
+ test3016 test3017 test3018 test3019 test3020 test3021 test3022 test3023 \
+ test3024 test3025 test3026 test3027 test3028 test3029 test3030 test3031 \
+ test3032 test3033 test3034 test3035 \
++test3037 test3038 \
+ \
+ test3100 test3101 test3102 test3103 test3104 test3105 test3109 \
+ \
+diff --git a/tests/data/test3037 b/tests/data/test3037
+new file mode 100644
+index 0000000..be29b99
+--- /dev/null
++++ b/tests/data/test3037
+@@ -0,0 +1,62 @@
++<?xml version="1.0" encoding="US-ASCII"?>
++<testcase>
++<info>
++<keywords>
++HTTP
++HTTP GET
++103
++Early Hints
++-J
++--show-headers
++</keywords>
++</info>
++
++# Server-side
++<reply>
++<data nocheck="yes">
++HTTP/1.1 103 Early Hints
++
++HTTP/1.1 200 OK
++Content-Length: 6
++Content-Disposition: attachment; filename="victim%TESTNUMBER"
++Connection: close
++
++12345
++</data>
++</reply>
++
++# Client-side
++<client>
++<server>
++http
++</server>
++<features>
++http
++</features>
++<name>
++Do not open output for 103 Early Hints with -J and --show-headers
++</name>
++<file name="%LOGDIR/victim%TESTNUMBER">
++original
++</file>
++<command option="no-output">
++http://%HOSTIP:%HTTPPORT/victim%TESTNUMBER -O -J -i --output-dir %LOGDIR
++</command>
++</client>
++
++<verify>
++<protocol crlf="headers">
++GET /victim%TESTNUMBER HTTP/1.1
++Host: %HOSTIP:%HTTPPORT
++User-Agent: curl/%VERSION
++Accept: */*
++
++</protocol>
++<errorcode>
++23
++</errorcode>
++<file name="%LOGDIR/victim%TESTNUMBER">
++original
++</file>
++</verify>
++</testcase>
+diff --git a/tests/data/test3038 b/tests/data/test3038
+new file mode 100644
+index 0000000..ff51458
+--- /dev/null
++++ b/tests/data/test3038
+@@ -0,0 +1,64 @@
++<?xml version="1.0" encoding="US-ASCII"?>
++<testcase>
++<info>
++<keywords>
++HTTP
++HTTP GET
++ETag
++--etag-save
++-J
++--show-headers
++</keywords>
++</info>
++
++# Server-side
++<reply>
++<data nocheck="yes">
++HTTP/1.1 200 OK
++ETag: "tag%TESTNUMBER"
++Content-Length: 6
++Content-Disposition: attachment; filename="victim%TESTNUMBER"
++Connection: close
++
++12345
++</data>
++</reply>
++
++# Client-side
++<client>
++<server>
++http
++</server>
++<features>
++http
++</features>
++<name>
++Do not open output for ETag with -J, --show-headers, --etag-save
++</name>
++<file name="%LOGDIR/victim%TESTNUMBER">
++original
++</file>
++<command option="no-output">
++http://%HOSTIP:%HTTPPORT/victim%TESTNUMBER -O -J -i --etag-save %LOGDIR/etag%TESTNUMBER --output-dir %LOGDIR
++</command>
++</client>
++
++<verify>
++<protocol crlf="headers">
++GET /victim%TESTNUMBER HTTP/1.1
++Host: %HOSTIP:%HTTPPORT
++User-Agent: curl/%VERSION
++Accept: */*
++
++</protocol>
++<errorcode>
++23
++</errorcode>
++<file1 name="%LOGDIR/victim%TESTNUMBER">
++original
++</file1>
++<file2 name="%LOGDIR/etag%TESTNUMBER">
++"tag%TESTNUMBER"
++</file2>
++</verify>
++</testcase>
+--
+2.35.6
new file mode 100644
@@ -0,0 +1,150 @@
+From 631ed306872956f86617df9b5664614e8a2dee54 Mon Sep 17 00:00:00 2001
+From: matty69v <matty69v@users.noreply.github.com>
+Date: Tue, 20 Oct 2026 10:28:31 +0200
+Subject: [PATCH] tool_cb_hdr: do not truncate existing file on filename-less
+ C-D header
+
+With -O -J, a Content-Disposition header without a usable filename
+(for example "inline") made curl create the URL-derived output file
+right away. When a later Content-Disposition header then named that
+same file, the no-clobber check refused it, but the file had already
+been truncated to zero bytes. The same happened when such a header came
+in a 3xx response before the Location: header with -L.
+
+Defer creating the output file until the body arrives (or the transfer
+ends), like for any other header. When the transfer ends without a body,
+also write the headers buffered for -i, which were lost before.
+
+Test 1718 verifies both.
+Closes #23501
+
+CVE: CVE-2026-92390
+Upstream-Status: Backport [https://github.com/curl/curl/commit/631ed306872956f86617df9b5664614e8a2dee54]
+
+Backport Changes:
+- Register test1718 without indentation to match curl 8.19.0's
+ grouped TESTCASES list; upstream indents the entry by two spaces.
+
+Signed-off-by: Darsh Kelaiya <dkelaiya@cisco.com>
+---
+ src/tool_cb_hdr.c | 10 +++++----
+ src/tool_operate.c | 4 ++++
+ tests/data/Makefile.am | 1 +
+ tests/data/test1718 | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++
+ 4 files changed, 70 insertions(+), 4 deletions(-)
+
+diff --git a/src/tool_cb_hdr.c b/src/tool_cb_hdr.c
+index 15abb5b..69c0227 100644
+--- a/src/tool_cb_hdr.c
++++ b/src/tool_cb_hdr.c
+@@ -409,10 +409,12 @@ static size_t content_disposition(const char *str, const char *end,
+ }
+ break;
+ }
+- if(!outs->stream && !tool_create_output_file(outs, per->config))
+- return CURL_WRITEFUNC_ERROR;
+- if(tool_write_headers(&per->hdrcbdata, outs->stream))
+- return CURL_WRITEFUNC_ERROR;
++ /* No usable filename in this header. Do not create the output file
++ yet: a later Content-Disposition header may still provide a name, and
++ opening the URL-derived name here would truncate an existing file
++ that -J is meant to leave alone. The file is created once the body
++ arrives (or at the end of the transfer) and any buffered headers are
++ written then. */
+ } /* content-disposition handling */
+
+ if(hdrcbdata->honor_cd_filename &&
+diff --git a/src/tool_operate.c b/src/tool_operate.c
+index 9a29b8e..1458322 100644
+--- a/src/tool_operate.c
++++ b/src/tool_operate.c
+@@ -649,6 +649,10 @@ static CURLcode post_output_handling(struct per_transfer *per,
+ curl_easy_getinfo(curl, CURLINFO_CONDITION_UNMET, &cond_unmet);
+ if(!cond_unmet && !tool_create_output_file(outs, config))
+ return CURLE_WRITE_ERROR;
++ /* write headers buffered while waiting for a -J filename */
++ if(outs->stream && per->hdrcbdata.headlist &&
++ tool_write_headers(&per->hdrcbdata, outs->stream))
++ return CURLE_WRITE_ERROR;
+ }
+
+ if(!outs->regular_file && outs->stream) {
+diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am
+index 6d12c7c..2528286 100644
+--- a/tests/data/Makefile.am
++++ b/tests/data/Makefile.am
+@@ -230,6 +230,7 @@ test1680 test1681 test1682 test1683 test1685 \
+ \
+ test1700 test1701 test1702 test1703 test1704 test1705 test1706 test1707 \
+ test1708 test1709 test1710 test1711 test1712 \
++test1718 \
+ \
+ test1800 test1801 test1802 test1847 test1848 test1849 test1850 test1862 \
+ \
+diff --git a/tests/data/test1718 b/tests/data/test1718
+new file mode 100644
+index 0000000..c4826f3
+--- /dev/null
++++ b/tests/data/test1718
+@@ -0,0 +1,59 @@
++<?xml version="1.0" encoding="US-ASCII"?>
++<testcase>
++<info>
++<keywords>
++HTTP
++HTTP GET
++-J
++</keywords>
++</info>
++
++<reply>
++<data nocheck="yes">
++HTTP/1.1 200 OK
++Content-Disposition: inline
++Content-Disposition: attachment; filename="name%TESTNUMBER"
++Content-Length: 8
++
++PAYLOAD
++</data>
++<data2 nocheck="yes">
++HTTP/1.1 200 OK
++Content-Disposition: inline
++Content-Length: 0
++
++</data2>
++</reply>
++
++# Client-side
++<client>
++<server>
++http
++</server>
++<name>
++HTTP GET -OJi, filename-less Content-Disposition first
++</name>
++<command option="no-output,no-include">
++http://%HOSTIP:%HTTPPORT/%TESTNUMBER0002 http://%HOSTIP:%HTTPPORT/name%TESTNUMBER -Ji -O -O --output-dir %LOGDIR
++</command>
++<file name="%LOGDIR/name%TESTNUMBER">
++initial content
++</file>
++</client>
++
++# Verify data after the test has been "shot"
++<verify>
++<errorcode>
++23
++</errorcode>
++<file name="%LOGDIR/name%TESTNUMBER">
++initial content
++</file>
++<file2 name="%LOGDIR/%TESTNUMBER0002">
++HTTP/1.1 200 OK
++Content-Disposition: inline
++Content-Length: 0
++
++</file2>
++</verify>
++</testcase>
+--
+2.35.6
@@ -63,6 +63,8 @@ SRC_URI = "https://curl.se/download/${BP}.tar.xz \
file://CVE-2026-92391.patch \
file://CVE-2026-87863.patch \
file://CVE-2026-86410.patch \
+ file://CVE-2026-92390_p1.patch \
+ file://CVE-2026-92390_p2.patch \
"
SRC_URI:append:class-nativesdk = " \