# Root Cause Analysis: CVE-2026-10536

## Summary

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.

## Impact

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

- **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_DEPENDS` and `CURLOPT_STREAM_DEPENDS_E` no-ops, which eliminates the faulty bookkeeping.

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