CVE-2026-10536: Verified Repro With Script Download
CVE-2026-10536: libcurl HTTP/2 stream-dependency tree use-after-free
CVE-2026-10536 is verified against curl/libcurl · github. Affected versions: 7.88.0 to 8.20.0 inclusive. Fixed in curl 8.21.0. Vulnerability class: Use-After-Free. This critical reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00278.
What Is CVE-2026-10536?
CVE-2026-10536 is a high-severity heap use-after-free (CWE-416) in libcurl's HTTP/2 stream-dependency bookkeeping. An application that uses the deprecated stream-dependency options can make libcurl dereference an already-freed dependency-tree node. Pruva reproduced it (reproduction REPRO-2026-00278).
CVE-2026-10536 Severity & CVSS Score
CVE-2026-10536 is rated critical severity, with a CVSS base score of 9.8 out of 10.
Critical — the most severe class — typically remotely exploitable with severe impact. Treat as an emergency.
Affected curl/libcurl Versions
curl/libcurl · github versions 7.88.0 to 8.20.0 inclusive are affected.
How to Reproduce CVE-2026-10536
pruva-verify REPRO-2026-00278 curl -O https://pruva.dev/api/v1/reproductions/REPRO-2026-00278/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh Proof of Reproduction for CVE-2026-10536
- reached the target end-to-end
- crash observed
- high confidence
CURLOPT_STREAM_DEPENDS set on an easy handle, followed by curl_easy_reset and curl_easy_cleanup order
- curl_easy_setopt(CURLOPT_STREAM_DEPENDS)
- curl_easy_reset(child)
- curl_easy_cleanup(child)
- curl_easy_cleanup(parent)
reproduction_steps.sh Alternate trigger for CVE-2026-10536 via curl_easy_duphandle(): cloning a parent handle after CURLOPT_STREAM_DEPENDS creates a second handle sharing the same dependency-tree node, which later cleanup dereferences after the original parent has freed it.
How the agent worked
Root Cause and Exploit Chain for CVE-2026-10536
CVE-2026-10536 is a heap use-after-free (UAF) in libcurl's HTTP/2 stream-dependency bookkeeping. When an application sets CURLOPT_STREAM_DEPENDS (or CURLOPT_STREAM_DEPENDS_E) on an easy handle, then calls curl_easy_reset() on that handle and later curl_easy_cleanup() on both handles, libcurl can dereference a dependency-tree node that was already freed. The vulnerability requires HTTP/2 support (nghttp2) and use of the now-deprecated dependency options. It is reachable through the public libcurl API (curl_easy_setopt, curl_easy_reset, curl_easy_cleanup) without any network traffic.
- Package/component affected: curl / libcurl, HTTP/2 stream priority/dependency code (
lib/url.c,priority_remove_child,data_priority_cleanup). - Affected versions: curl 7.88.0 through 8.20.0 inclusive (per upstream advisory); introduced by commit
71b7e0161032927cdfband fixed by commitbfbff7852f050232edd3e5ca5c6bf2021c340f5a. - Risk level and consequences: High / memory corruption. The UAF occurs during easy-handle cleanup, so a controlled memory layout could potentially lead to further memory corruption. The upstream advisory classifies the issue as a use-after-free with CVSS severity Low, but the demonstrated impact is a real memory-safety violation in a widely used library. It does not affect the curl command-line tool directly; only applications that programmatically use the deprecated dependency options are exposed.
Impact Parity
- Disclosed/claimed maximum impact: memory corruption (use-after-free).
- Reproduced impact from this run: A deterministic AddressSanitizer
heap-use-after-freereport inpriority_remove_childatlib/url.c:3516when the parent handle is cleaned up after the child handle has been reset and freed. - Parity:
fullfor the memory-safety claim. The reproduction proves the exact vulnerability class (UAF) and the exact API surface described in the advisory. - Not demonstrated: No remote code execution or full exploit chain was demonstrated; only the UAF crash/reachability is proven, which is sufficient for the claimed memory-corruption impact.
Root Cause
The bug is in the priority-child doubly-linked bookkeeping in lib/url.c:
curl_easy_setopt(child, CURLOPT_STREAM_DEPENDS, parent)adds aCurl_data_prio_nodetoparent->set.priority.children; that node stores a raw pointer tochild. It also setschild->set.priority.parent = parent.curl_easy_reset(child)executesmemset(&data->set, 0, sizeof(struct UserDefined)), which zeroschild->set.priorityand therefore clearschild->set.priority.parent. The child handle now "forgets" that it is a dependent ofparent, butparent->set.priority.childrenstill contains the node pointing atchild.curl_easy_cleanup(child)callsdata_priority_cleanup(child). Becausechild->set.priority.parentis now zero, the code does not remove the child node from its parent list. TheCurl_easystruct forchildis then freed.curl_easy_cleanup(parent)callsdata_priority_cleanup(parent), which loops overparent->set.priority.children. The first node still points to the freedchildstruct, sopriority_remove_child(parent, child)readschild->set.priority.parent(offset 2120 inside the freedCurl_easy) and crashes under AddressSanitizer.
- Fix commit: https://github.com/curl/curl/commit/bfbff7852f050232edd3e5ca5c6bf2021c340f5a ("http2: remove stream dependency tracking"). The upstream fix removes the entire HTTP/2 stream-dependency feature, making
CURLOPT_STREAM_DEPENDSandCURLOPT_STREAM_DEPENDS_Eno-ops, which eliminates the faulty bookkeeping.
Reproduction Steps
- Run
bundle/repro/reproduction_steps.sh. It is self-contained: it installs build dependencies, clones or reuses the curl repository, builds two static ASan-enabled libcurl installations (vulnerablecurl-8_20_0and fixedbfbff7852f), compiles a small C reproducer, and runs it against both. - The reproducer creates two easy handles, sets
CURLOPT_STREAM_DEPENDSon the child so it depends on the parent, callscurl_easy_reset(child)andcurl_easy_cleanup(child), then callscurl_easy_cleanup(parent). - Expected evidence:
- Vulnerable build: AddressSanitizer reports
heap-use-after-freeinpriority_remove_child(lib/url.c:3516). - Fixed build: reproducer exits cleanly with no sanitizer report.
- Vulnerable build: AddressSanitizer reports
Evidence
bundle/logs/reproduction_steps.log— full script output.bundle/logs/reproducer_vuln.log— ASan heap-use-after-free report for the vulnerable build.bundle/logs/reproducer_fixed.log— clean run for the fixed build.bundle/repro/reproducer.c— source of the minimal C harness.bundle/repro/runtime_manifest.json— structured runtime evidence manifest.
Key excerpt from the vulnerable run:
==ERROR: AddressSanitizer: heap-use-after-free on address ... at pc ... in priority_remove_child lib/url.c:3516
READ of size 8 at ... thread T0
#0 priority_remove_child /.../lib/url.c:3516
#1 data_priority_cleanup /.../lib/url.c:3588
#2 Curl_close /.../lib/url.c:280
#3 curl_easy_cleanup /.../lib/easy.c:844
The freed region was the Curl_easy struct allocated in Curl_open during curl_easy_init, and it was freed by curl_easy_cleanup(child) before curl_easy_cleanup(parent) accessed it through the stale dependency node.
Recommendations / Next Steps
- Upgrade: Upgrade libcurl to 8.21.0 or later, where the dependency feature has been removed and the option is a no-op.
- Workaround: Applications that currently use
CURLOPT_STREAM_DEPENDSorCURLOPT_STREAM_DEPENDS_Eshould stop using these deprecated options; they are no longer functional in 8.21.0+. - Testing: For any cherry-picked patch, verify that
curl_easy_setopt(..., CURLOPT_STREAM_DEPENDS, ...)followed bycurl_easy_resetandcurl_easy_cleanupno longer produces a use-after-free under ASan/Valgrind.
Additional Notes
- Idempotency: The reproduction script was run twice consecutively and produced the same confirmed result both times. Cached builds in the project cache are reused when the recorded commit matches, making subsequent runs fast.
- Limitations: The proof is a library/API harness that directly calls the affected libcurl functions. It does not exercise a remote network path because the advisory describes an application-level API misuse, not a network-triggered vulnerability.
Variant Analysis & Alternative Triggers for CVE-2026-10536
CVE-2026-10536 is a heap use-after-free in libcurl's HTTP/2 stream-dependency
bookkeeping. The original reproduction uses curl_easy_setopt(CURLOPT_STREAM_DEPENDS)
on a child handle, then curl_easy_reset(child) and a later cleanup order that
frees the child before the parent. This variant analysis identifies an
additional, distinct trigger: curl_easy_duphandle(parent) after a dependency
has been created. The clone copies the parent handle's set.priority.children
pointer, so both the original and the clone share the same Curl_data_prio_node.
When the original parent is cleaned up first, the node is freed; the clone's
later cleanup dereferences that freed node, producing the same heap-use-after-free
in data_priority_cleanup() / priority_remove_child(). A secondary variant
(resetting and cleaning up the parent before the child) also triggers the same
UAF. Both variants are confirmed on the vulnerable commit, and both are clean on
the fixed and latest releases because the upstream fix removes the dependency
tree entirely.
Fix Coverage / Assumptions
The upstream fix (bfbff7852f050232edd3e5ca5c6bf2021c340f5a) removes the
stream-dependency tracking feature:
CURLOPT_STREAM_DEPENDSandCURLOPT_STREAM_DEPENDS_Ebecome no-ops.Curl_data_priority_add_child(),priority_remove_child(), anddata_priority_cleanup()are deleted fromlib/url.c.- The only remaining priority code is
Curl_data_priority_clear_state()and amemsetofset->priority, both dealing with per-transfer state, not inter-handle links.
The fix assumes that the public CURLOPT_STREAM_DEPENDS / E options are the
sole source of dependency-tree links, and that removing those options and the
associated tree code closes every reachable UAF path. The variant tests below
confirm that assumption: no other tested API path can create a surviving
dependency link on the fixed or latest code.
Variant / Alternate Trigger
Primary variant: curl_easy_duphandle()
Entry point: curl_easy_duphandle() (public library API).
Steps:
parent = curl_easy_init()child = curl_easy_init()curl_easy_setopt(child, CURLOPT_STREAM_DEPENDS, parent)— creates the dependency node onparentand setschild->set.priority.parent = parent.parent_clone = curl_easy_duphandle(parent)—dupset()does a shallow copy ofsrc->setintodst->set, soparent_clone->set.priority.childrenpoints to the same heap-allocatedCurl_data_prio_nodeasparent.curl_easy_cleanup(child)— child is freed; the node still lives onparent.curl_easy_cleanup(parent)— frees the sharedCurl_data_prio_node.curl_easy_cleanup(parent_clone)— dereferences the freed node indata_priority_cleanup(), triggering the heap-use-after-free.
Code path involved:
lib/easy.c:dupset()— shallow copy ofset.priority.lib/url.c:data_priority_cleanup()— iterates over stale children list.lib/url.c:priority_remove_child()— reads freedCurl_data_prio_node.
Secondary variant: reset/cleanup parent before child
Entry point: curl_easy_reset() and curl_easy_cleanup() order.
Steps:
- Set
CURLOPT_STREAM_DEPENDSon child sochild->set.priority.parent = parent. curl_easy_reset(parent)— zeroesparent->set.priority, but does not clearchild->set.priority.parent.curl_easy_cleanup(parent)— frees the parentCurl_easystruct.curl_easy_cleanup(child)—data_priority_cleanup()readschild->set.priority.parent, now a freed pointer, and UAFs inpriority_remove_child().
This is the same root cause as the original report but with the reset/cleanup order reversed; it demonstrates that the bug is not tied to one specific sequence of API calls.
- Package/component affected: curl / libcurl, HTTP/2 stream priority/dependency code (
lib/url.c,lib/easy.c). - Affected versions tested:
curl-8_20_0(6e3f8dc1f173b47de9a68516ce4b95bf25598c2f). The upstream advisory identifies affected range7.88.0through8.20.0. - Fixed version tested:
bfbff7852f050232edd3e5ca5c6bf2021c340f5a(fix commit) and releasecurl-8_21_0(3f00a2f6fa97f7721b65606954aac979dcb6caac). - Risk level and consequences: Memory-safety violation (heap-use-after-free) during easy-handle cleanup. The demonstrated impact is a deterministic ASan crash; no exploit chain or read/write primitive was demonstrated.
Impact Parity
- Disclosed/claimed maximum impact: memory corruption (use-after-free).
- Reproduced impact from this variant run: Deterministic ASan
heap-use-after-freeindata_priority_cleanup()on the vulnerable build for both theduphandleandreset_parentvariants. - Parity:
fullfor the memory-safety claim. The alternate triggers prove the same vulnerability class and the same sink, just through a different API path or ordering. - Not demonstrated: Arbitrary code execution, information disclosure, or a bypass of the fixed code.
Root Cause
The vulnerable code maintains a doubly-linked (parent/children) dependency tree
of Curl_easy handles via set.priority.parent and set.priority.children.
The lifetime of a node is tied to the handle that owns it, but the pointers are
not invalidated when a related handle is reset or freed. The original trigger
frees the child handle while the parent still points to it; the duphandle
variant creates a second handle that points to the same node; the
reset_parent variant frees the parent while the child still points to it. In
all cases, a subsequent cleanup dereferences a freed dependency link.
The fix removes the tree entirely, so there is no node to share or free incorrectly. This closes all variants simultaneously.
Reproduction Steps
Run the variant script:
bash bundle/vuln_variant/reproduction_steps.sh
The script:
- Reuses the cached vulnerable (
curl-8_20_0) and fixed (bfbff7852f) builds from the project cache, plus the latest release (curl-8_21_0) built inbundle/vuln_variant/artifacts/install-latest. - Compiles two small harnesses:
bundle/vuln_variant/artifacts/variant_duphandle.cbundle/vuln_variant/artifacts/variant_reset_parent.c
- Runs each harness against the vulnerable, fixed, and latest builds.
- Checks each run's log for
ERROR: AddressSanitizer: heap-use-after-free. - Logs are written to
bundle/logs/vuln_variant/. - Exits
1because the variants only reproduce on the vulnerable version, not on the fixed or latest versions (i.e., no bypass).
Expected evidence on the vulnerable build:
ERROR: AddressSanitizer: heap-use-after-free ...
#0 data_priority_cleanup lib/url.c:3587
#1 Curl_close lib/url.c:280
#2 curl_easy_cleanup lib/easy.c:844
Expected evidence on the fixed and latest builds: clean exit, no ASan error,
possibly a deprecation warning for CURLOPT_STREAM_DEPENDS.
Evidence
- Variant script:
bundle/vuln_variant/reproduction_steps.sh - Harness sources:
bundle/vuln_variant/artifacts/variant_duphandle.cbundle/vuln_variant/artifacts/variant_reset_parent.c
- Runtime logs:
bundle/logs/vuln_variant/reproducer_vuln_duphandle.log— UAFbundle/logs/vuln_variant/reproducer_vuln_reset_parent.log— UAFbundle/logs/vuln_variant/reproducer_fixed_duphandle.log— cleanbundle/logs/vuln_variant/reproducer_fixed_reset_parent.log— cleanbundle/logs/vuln_variant/reproducer_latest_duphandle.log— cleanbundle/logs/vuln_variant/reproducer_latest_reset_parent.log— cleanbundle/logs/vuln_variant/variant_reproduction_steps.log— run summary
- Compiled binaries under
bundle/vuln_variant/artifacts/
Excerpt from reproducer_vuln_duphandle.log:
==...==ERROR: AddressSanitizer: heap-use-after-free on address ... at pc ...
READ of size 8 ...
#0 data_priority_cleanup /.../lib/url.c:3587
#1 Curl_close /.../lib/url.c:280
#2 curl_easy_cleanup /.../lib/easy.c:844
#3 main /.../variant_duphandle.c:33
...
freed by thread T0 here:
#0 free
#1 priority_remove_child /.../lib/url.c:3525
#2 data_priority_cleanup /.../lib/url.c:3594
...
previously allocated by thread T0 here:
#0 calloc
#1 Curl_data_priority_add_child /.../lib/url.c:3544
Recommendations / Next Steps
- The upstream fix is complete: removing the dependency tree eliminates both the original trigger and the alternate triggers identified here. No additional code change is required to close this specific bug class.
- For a hypothetical partial fix that tried to patch the tree lifetime (e.g.,
clearing
child->set.priority.parentoncurl_easy_reset()), thecurl_easy_duphandle()variant would remain a concern because the clone shares thechildrenpointer. Any partial fix must therefore cover every path that copies or shares the dependency tree, which is why the upstream removal approach is safer. - Applications should stop using
CURLOPT_STREAM_DEPENDSandCURLOPT_STREAM_DEPENDS_E; they are deprecated no-ops as of curl 8.21.0.
Additional Notes
- Idempotency: The variant script was run twice consecutively and produced the same results both times.
- Source state: The project cache repository was left at the vulnerable
commit (
curl-8_20_0) throughout. The fixed and latest builds used separate checkouts/worktrees, and the current repo state was not modified by the variant script. - No bypass found: The fixed and latest builds both exit cleanly, so the upstream removal-based fix is sufficient.
CVE-2026-10536 Reproduction Transcript
The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.
Full session Replay every step — scrub the timeline or play it back.
Artifacts and Evidence for CVE-2026-10536
Scripts, logs, diffs, and output captured during the reproduction.
How to Fix CVE-2026-10536
Upgrade curl/libcurl · github to curl 8.21.0 or later.
FAQ: CVE-2026-10536
What conditions are required to trigger CVE-2026-10536?
Which versions of libcurl are affected by CVE-2026-10536, and where is it fixed?
How can I reproduce CVE-2026-10536?
References for CVE-2026-10536
Authoritative sources for CVE-2026-10536 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.