Skip to content

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.

REPRO-2026-00278 curl/libcurl · github Use-After-Free Variant found Jul 9, 2026 CVE entry ↗ .txt
Severity
CRITICAL
CVSS
9.8
Confidence
HIGH
Reproduced in
22m 30s
Tool calls
194
Spend
$1.93
01 · Overview

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).

02 · Severity & CVSS

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 threat level
9.8 / 10 CVSS base
Weakness CWE-416 Use After Free — Use After Free

Critical — the most severe class — typically remotely exploitable with severe impact. Treat as an emergency.

03 · Affected Versions

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
or 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
Run in a VM or disposable container. This exploits a real vulnerability.
06 · Proof of Reproduction

Proof of Reproduction for CVE-2026-10536

Memory corruption — reproduced
  • reached the target end-to-end
  • crash observed
  • high confidence
Trigger

CURLOPT_STREAM_DEPENDS set on an easy handle, followed by curl_easy_reset and curl_easy_cleanup order

Attack chain
  1. curl_easy_setopt(CURLOPT_STREAM_DEPENDS)
  2. curl_easy_reset(child)
  3. curl_easy_cleanup(child)
  4. curl_easy_cleanup(parent)
Runnable proof: reproduction_steps.sh
Captured evidence: reproducer fixed duphandle
Variants tested

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 509 events · 194 tool calls · 23 min
23 minDuration
194Tool calls
154Reasoning steps
509Events
Agent activity over 23 min
Support
18
Hypothesis
2
Repro
131
Judge
17
Variant
155
Coding
181
0:0022:30

Root Cause and Exploit Chain for CVE-2026-10536

Versions: curl 7.88.0 through 8.20.0 inclusive (per upstream advisory); introduced by commit 71b7e0161032927cdfb and fixed by commit bfbff7852f050232edd3e5ca5c6bf2021c340f5a.

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 71b7e0161032927cdfb and fixed by commit bfbff7852f050232edd3e5ca5c6bf2021c340f5a.
  • 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-free report in priority_remove_child at lib/url.c:3516 when the parent handle is cleaned up after the child handle has been reset and freed.
  • Parity: full for 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:

  1. curl_easy_setopt(child, CURLOPT_STREAM_DEPENDS, parent) adds a Curl_data_prio_node to parent->set.priority.children; that node stores a raw pointer to child. It also sets child->set.priority.parent = parent.

  2. curl_easy_reset(child) executes memset(&data->set, 0, sizeof(struct UserDefined)), which zeros child->set.priority and therefore clears child->set.priority.parent. The child handle now "forgets" that it is a dependent of parent, but parent->set.priority.children still contains the node pointing at child.

  3. curl_easy_cleanup(child) calls data_priority_cleanup(child). Because child->set.priority.parent is now zero, the code does not remove the child node from its parent list. The Curl_easy struct for child is then freed.

  4. curl_easy_cleanup(parent) calls data_priority_cleanup(parent), which loops over parent->set.priority.children. The first node still points to the freed child struct, so priority_remove_child(parent, child) reads child->set.priority.parent (offset 2120 inside the freed Curl_easy) and crashes under AddressSanitizer.

Reproduction Steps

  1. 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 (vulnerable curl-8_20_0 and fixed bfbff7852f), compiles a small C reproducer, and runs it against both.
  2. The reproducer creates two easy handles, sets CURLOPT_STREAM_DEPENDS on the child so it depends on the parent, calls curl_easy_reset(child) and curl_easy_cleanup(child), then calls curl_easy_cleanup(parent).
  3. Expected evidence:
    • Vulnerable build: AddressSanitizer reports heap-use-after-free in priority_remove_child (lib/url.c:3516).
    • Fixed build: reproducer exits cleanly with no sanitizer report.

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_DEPENDS or CURLOPT_STREAM_DEPENDS_E should 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 by curl_easy_reset and curl_easy_cleanup no 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

Versions: versions tested: curl-8_20_0 (6e3f8dc1f173b47de9a68516ce4b95bf25598c2f). The upstream advisory identifies affected range 7.88.0 through 8.20.0.Fixed: version tested: bfbff7852f050232edd3e5ca5c6bf2021c340f5a (fix commit) and release curl-8_21_0 (3f00a2f6fa97f7721b65606954aac979dcb6caac).

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_DEPENDS and CURLOPT_STREAM_DEPENDS_E become no-ops.
  • Curl_data_priority_add_child(), priority_remove_child(), and data_priority_cleanup() are deleted from lib/url.c.
  • The only remaining priority code is Curl_data_priority_clear_state() and a memset of set->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:

  1. parent = curl_easy_init()
  2. child = curl_easy_init()
  3. curl_easy_setopt(child, CURLOPT_STREAM_DEPENDS, parent) — creates the dependency node on parent and sets child->set.priority.parent = parent.
  4. parent_clone = curl_easy_duphandle(parent)dupset() does a shallow copy of src->set into dst->set, so parent_clone->set.priority.children points to the same heap-allocated Curl_data_prio_node as parent.
  5. curl_easy_cleanup(child) — child is freed; the node still lives on parent.
  6. curl_easy_cleanup(parent) — frees the shared Curl_data_prio_node.
  7. curl_easy_cleanup(parent_clone) — dereferences the freed node in data_priority_cleanup(), triggering the heap-use-after-free.

Code path involved:

  • lib/easy.c:dupset() — shallow copy of set.priority.
  • lib/url.c:data_priority_cleanup() — iterates over stale children list.
  • lib/url.c:priority_remove_child() — reads freed Curl_data_prio_node.
Secondary variant: reset/cleanup parent before child

Entry point: curl_easy_reset() and curl_easy_cleanup() order.

Steps:

  1. Set CURLOPT_STREAM_DEPENDS on child so child->set.priority.parent = parent.
  2. curl_easy_reset(parent) — zeroes parent->set.priority, but does not clear child->set.priority.parent.
  3. curl_easy_cleanup(parent) — frees the parent Curl_easy struct.
  4. curl_easy_cleanup(child)data_priority_cleanup() reads child->set.priority.parent, now a freed pointer, and UAFs in priority_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 range 7.88.0 through 8.20.0.
  • Fixed version tested: bfbff7852f050232edd3e5ca5c6bf2021c340f5a (fix commit) and release curl-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-free in data_priority_cleanup() on the vulnerable build for both the duphandle and reset_parent variants.
  • Parity: full for 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:

  1. 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 in bundle/vuln_variant/artifacts/install-latest.
  2. Compiles two small harnesses:
    • bundle/vuln_variant/artifacts/variant_duphandle.c
    • bundle/vuln_variant/artifacts/variant_reset_parent.c
  3. Runs each harness against the vulnerable, fixed, and latest builds.
  4. Checks each run's log for ERROR: AddressSanitizer: heap-use-after-free.
  5. Logs are written to bundle/logs/vuln_variant/.
  6. Exits 1 because 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.c
    • bundle/vuln_variant/artifacts/variant_reset_parent.c
  • Runtime logs:
    • bundle/logs/vuln_variant/reproducer_vuln_duphandle.log — UAF
    • bundle/logs/vuln_variant/reproducer_vuln_reset_parent.log — UAF
    • bundle/logs/vuln_variant/reproducer_fixed_duphandle.log — clean
    • bundle/logs/vuln_variant/reproducer_fixed_reset_parent.log — clean
    • bundle/logs/vuln_variant/reproducer_latest_duphandle.log — clean
    • bundle/logs/vuln_variant/reproducer_latest_reset_parent.log — clean
    • bundle/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.parent on curl_easy_reset()), the curl_easy_duphandle() variant would remain a concern because the clone shares the children pointer. 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_DEPENDS and CURLOPT_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.

Event 1/40
0:002:36
0:00
session startedaccounts/fireworks/models/kimi-k2p7-code · CVE-2026-10536 · REPRO-20
0:03
0:05
web search
0:06
0:07
0:11
0:12
web search
0:23
0:24
0:36
0:36
extract_facts
no facts extracted
0:38
0:38
0:38
supportrepro
2:11
2:16
2:16
2:21
2:21
2:21
2:27
2:27
2:28
2:28
2:31
2:33
web search
2:36
08 · How to Fix

How to Fix CVE-2026-10536

Upgrade curl/libcurl · github to curl 8.21.0 or later.

Coming soon

Step-by-step mitigation and hardening guidance for CVE-2026-10536 — configuration checks, workarounds where no patch exists, and how to verify you're protected — is on the way.

10 · FAQ

FAQ: CVE-2026-10536

What conditions are required to trigger CVE-2026-10536?

The build must have HTTP/2 support (nghttp2), and the application must use the now-deprecated CURLOPT_STREAM_DEPENDS / CURLOPT_STREAM_DEPENDS_E options followed by curl_easy_reset() and curl_easy_cleanup(). It is an application-usage-dependent memory-safety bug rather than a purely remote one.

Which versions of libcurl are affected by CVE-2026-10536, and where is it fixed?

libcurl versions 7.88.0 through 8.20.0 inclusive are affected. It is fixed in curl 8.21.0 — upgrade to 8.21.0 or later.

How can I reproduce CVE-2026-10536?

Download the verified script from this page and run it in an isolated environment against libcurl 7.88.0-8.20.0 built with nghttp2. It sets the stream-dependency options, calls curl_easy_reset() then curl_easy_cleanup(), and shows the use-after-free that curl 8.21.0 no longer triggers.
11 · References

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.