Reproducing DEBIAN-CVE-2026-92390 (curl -J clobbered by 103 Early Hints)
curl / libcurl
| Version type | Vulnerable | Fixed |
|---|---|---|
| Releases | 7.20.0 - 8.22.0 | 8.23.0 |
| Tested builds | 8.22.1-DEV | 8.23.1-DEV |
| Catalog | Fixed in fork | Fork branch | Merged |
|---|---|---|---|
| Severity | Low | Updated |
| Advisory | |
|---|---|
| Fixes | |
| Code | |
| Discussion |
Setup
curl -J can overwrite an existing file when a 103 Early Hints response arrives before the final response.
| Vulnerable | upstream at b31c352, reports 8.22.1-DEV |
|---|---|
| Fixed | our fork at ce57339, reports 8.23.1-DEV |
| Build | same flags both sides |
| Sandbox | no network, loopback only |
With -J, an interim 103 response or an ETag under --etag-save could open the output file before curl decided the filename, truncating a pre-existing file. Upstream fix c24a69d8, included in the fork, buffers headers until the filename decision is complete and then refuses to overwrite (exit 23).
This reproduction starts a loopback server that answers /victim92390 with 103 Early Hints, followed by a final 200 with Content-Disposition: attachment. The client creates that file with the contents "original" and runs curl -O -J -i. If the contents change, the file was clobbered.
Reproduce (vulnerable build)
docker run --rm --network none --pids-limit 64 --memory 512m --read-only --tmpfs /tmp:rw,noexec,nosuid,size=64m cve-92390-demo python3 /task/repro.py /opt/tree-vuln/bin/curl$ LD_LIBRARY_PATH=/opt/tree-vuln/lib /opt/tree-vuln/bin/curl --version | head -1
curl 8.22.1-DEV (x86_64-pc-linux-gnu) libcurl/8.22.1-DEV OpenSSL/3.5.7
$ LD_LIBRARY_PATH=/opt/tree-vuln/lib /opt/tree-vuln/bin/curl -O -J -i --output-dir /tmp/cj92390 --max-time 20 http://127.0.0.1:8000/victim92390
b'HTTP/1.1 103 Early Hints\r\nLink: </style.css>; rel=preload\r\n\r\n'The pre-existing file now holds the 103 headers instead of "original". The 103 path opened the output before the filename decision.
The regression tests show the same failure.
docker run --rm --network none --pids-limit 64 --memory 1g cve-92390-demo sh -c 'cp /src/fixed/tests/data/test3037 /src/fixed/tests/data/test3038 /src/vuln/tests/data/ && cd /src/vuln && make -C tests/server >/tmp/srv.log 2>&1 && cd tests && perl runtests.pl 3037 3038 2>&1 | tail -n 25'* Perl: v5.40.1 (/usr/bin/perl)
* Args: 3037 3038
* Seed: 266361
* Servers: HTTP-IPv6 HTTP-unix FTP-IPv6
*****************************************
Warning: test3037 not present in tests/data/Makefile.am
Warning: test3038 not present in tests/data/Makefile.am
test 3037 [Do not open output for 103 Early Hints with -J and --show-headers]
3037: output (log/victim3037) FAILED:
@@ -1 +1,2 @@
-original[LF]
+HTTP/1.1 103 Early Hints[LF]
+[LF]
- abort tests
TESTDONE: 1 tests were considered during 1 seconds.
TESTDONE: 0 tests out of 1 reported OK: 0%
FAIL 3037: 'Do not open output for 103 Early Hints with -J and --show-headers' HTTP, HTTP GET, 103, Early Hints, -J, --show-headers
TESTFAIL: These test cases failed: 3037The ETag test also overwrites the file.
docker run --rm --network none --pids-limit 64 --memory 1g cve-92390-demo sh -c 'cp /src/fixed/tests/data/test3037 /src/fixed/tests/data/test3038 /src/vuln/tests/data/ && cd /src/vuln && make -C tests/server >/tmp/srv.log 2>&1 && cd tests && perl runtests.pl 3038 2>&1 | tail -n 22'* Args: 3038
* Seed: 266361
* Servers: HTTP-IPv6 HTTP-unix FTP-IPv6
*****************************************
Warning: test3038 not present in tests/data/Makefile.am
test 3038 [Do not open output for ETag with -J, --show-headers, --etag-save]
3038: output (log/victim3038) FAILED:
@@ -1 +1 @@
-original[LF]
+ETag: "tag3038"[LF]
- abort tests
TESTDONE: 1 tests were considered during 1 seconds.
TESTDONE: 0 tests out of 1 reported OK: 0%
FAIL 3038: 'Do not open output for ETag with -J, --show-headers, --etag-save' HTTP, HTTP GET, ETag, --etag-save, -J, --show-headers
TESTFAIL: These test cases failed: 3038Verify (fork-fixed build)
Run the same repro against the fork build.
docker run --rm --network none --pids-limit 64 --memory 512m --read-only --tmpfs /tmp:rw,noexec,nosuid,size=64m cve-92390-demo python3 /task/repro.py /opt/tree-fixed/bin/curl$ LD_LIBRARY_PATH=/opt/tree-fixed/lib /opt/tree-fixed/bin/curl --version | head -1
curl 8.23.1-DEV (x86_64-pc-linux-gnu) libcurl/8.23.1-DEV OpenSSL/3.5.7
$ LD_LIBRARY_PATH=/opt/tree-fixed/lib /opt/tree-fixed/bin/curl -O -J -i --output-dir /tmp/cj92390 --max-time 20 http://127.0.0.1:8000/victim92390
b'original'The file keeps its bytes. Exit 23 is the documented refusal to overwrite, not a crash. No download completes, and nothing is written.
Both regression tests pass on the fixed tree.
docker run --rm --network none --pids-limit 64 --memory 1g cve-92390-demo sh -c 'cd /src/fixed && make -C tests/server >/tmp/srv.log 2>&1 && cd tests && perl runtests.pl 3037 3038 2>&1 | tail -n 10'* Servers: HTTP-IPv6 HTTP-unix FTP-IPv6
*****************************************
test 3037 [Do not open output for 103 Early Hints with -J and --show-headers]
--p---oe--- OK (1 out of 2 , remaining: 00:00, took 1.023s, duration: 00:01)
test 3038 [Do not open output for ETag with -J, --show-headers, --etag-save]
--p---oe--- OK (2 out of 2 , remaining: 00:00, took 0.037s, duration: 00:01)
TESTDONE: 2 tests were considered during 1 seconds.
TESTDONE: 2 tests out of 2 reported OK: 100%Limits
The clobber needs -O -J -i (or --etag-save for the ETag path) plus a server that sends 103 Early Hints or a pre-CD ETag header. Without -i the header path differs and the file handling changes. The fixed build still exits 23 and downloads nothing when the target file exists. That is the intended no-overwrite behavior, kept from before.
Attack flow
Agent runbook
docker run --rm --network none --pids-limit 64 --memory 512m --read-only --tmpfs /tmp:rw,noexec,nosuid,size=64m cve-92390-demo python3 /task/repro.py /opt/tree-vuln/bin/curl
docker run --rm --network none --pids-limit 64 --memory 1g cve-92390-demo sh -c 'cp /src/fixed/tests/data/test3037 /src/fixed/tests/data/test3038 /src/vuln/tests/data/ && cd /src/vuln && make -C tests/server >/tmp/srv.log 2>&1 && cd tests && perl runtests.pl 3037 3038 2>&1 | tail -n 25'
docker run --rm --network none --pids-limit 64 --memory 1g cve-92390-demo sh -c 'cp /src/fixed/tests/data/test3037 /src/fixed/tests/data/test3038 /src/vuln/tests/data/ && cd /src/vuln && make -C tests/server >/tmp/srv.log 2>&1 && cd tests && perl runtests.pl 3038 2>&1 | tail -n 22'
docker run --rm --network none --pids-limit 64 --memory 512m --read-only --tmpfs /tmp:rw,noexec,nosuid,size=64m cve-92390-demo python3 /task/repro.py /opt/tree-fixed/bin/curl
docker run --rm --network none --pids-limit 64 --memory 1g cve-92390-demo sh -c 'cd /src/fixed && make -C tests/server >/tmp/srv.log 2>&1 && cd tests && perl runtests.pl 3037 3038 2>&1 | tail -n 10'Release information
| Source | Official advisory |
|---|---|
| Checked | |
| Severity | Low |
| Weakness | CWE-73 |
| Other fixed branches |
|
Recorded evidence
| upstream fix present in fork base | |
| regression test tests/data/test5033 executed RED then GREEN |
Status history
| Date | Status | Record |
|---|---|---|
| Fixed in fork | merged to fork master aab434c |