Skip to content

CVE-2026-33630: Verified Repro With Script Download

CVE-2026-33630: c-ares CVE-2026-33630 RCE primitive construction from confirmed remote UAF

CVE-2026-33630 is verified against c-ares/c-ares · c. Affected versions: >1.32.3 through 1.34.6. Fixed in 1.34.7. Vulnerability class: RCE. This high reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00263.

REPRO-2026-00263 c-ares/c-ares · c RCE Variant found Jul 7, 2026 CVE entry ↗ .txt
Severity
HIGH
Confidence
HIGH
Reproduced in
54m 27s
Tool calls
252
Spend
$14.17
01 · Overview

What Is CVE-2026-33630?

CVE-2026-33630 is a high-severity, remotely reachable use-after-free/double-free (CWE-416) in c-ares's query-completion handling, triggerable through the public ares_getaddrinfo() API over TCP DNS. Pruva reproduced it as a vulnerable-only control-flow hijack marker (reproduction REPRO-2026-00263).

02 · Severity & CVSS

CVE-2026-33630 Severity

CVE-2026-33630 is rated high severity.

HIGH threat level
Weakness CWE-416 — Use After Free

High — serious impact or readily exploitable. Prioritize remediation.

03 · Affected Versions

Affected c-ares/c-ares Versions

c-ares/c-ares · c versions >1.32.3 through 1.34.6 are affected.

How to Reproduce CVE-2026-33630

$ pruva-verify REPRO-2026-00263
or curl -O https://pruva.dev/api/v1/reproductions/REPRO-2026-00263/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-33630

Remote code execution — reproduced
  • reached the target end-to-end
  • full exploit chain demonstrated
  • on the real production code path
  • high confidence
  • the upstream fix blocks the same trigger
Trigger

malicious TCP DNS server responses (FORMERR without OPT, duplicate same-qid NXDOMAIN, TCP reset) plus deterministic heap-reclaim payload supplied through c-ares public allocator API to model attacker-controlled reclaimed heap content

Attack chain
  1. ares_getaddrinfo(AF_INET) over TCP
  2. read_answers() deferred ENDQUERY flush
  3. host_callback() reentrant next_lookup/end_hquery after TCP reset
  4. hquery_free()
  5. stale host_callback consumes forged host_query
  6. callback/arg and executes sentinel
Runnable proof: reproduction_steps.sh
Captured evidence: rce reclaim fixed attempt1rce reclaim fixed attempt2
Variants tested

Distinct vulnerable-version alternate trigger for CVE-2026-33630: a real UDP DNS peer withholds a response, driving process_timeouts(); the timeout callback reenters c-ares via public ares_cancel(), causing a vulnerable-only double free in v1.34.6. The candidate is not a bypass because v1.34.7 blocks it with ares_flus…

How the agent worked 529 events · 252 tool calls · 54 min
54 minDuration
252Tool calls
103Reasoning steps
529Events
1Dead-ends
Agent activity over 54 min
Support
16
Hypothesis
2
Repro
311
Judge
76
Variant
119
0:0054:27

Root Cause and Exploit Chain for CVE-2026-33630

Versions: reproduced on c-ares v1.34.6 (3ac47ee46edd8ea40370222f91613fc16c434853).Fixed: version/control: c-ares v1.34.7 (d63b7de3a3568d14ab1351374b205365aed51cca), which contains fix commit d823199b688052dcdc1646f2ab4cb8c16b1c644a.

CVE-2026-33630 in c-ares is a remotely reachable use-after-free/double-free in query-completion handling that can be triggered through the public ares_getaddrinfo() API over TCP DNS. In c-ares v1.34.6, deferred ENDQUERY processing invokes a query callback while the query remains linked in the channel lookup tables. A malicious TCP DNS peer can drive a reentrant completion path that frees the associated host_query; subsequent stale use can consume reclaimed attacker-shaped fields. This run demonstrates a vulnerable-only benign control-flow hijack marker: under the real TCP DNS trigger and a deterministic public allocator-hook heap reclaim, v1.34.6 executes a sentinel callback from a forged reclaimed host_query, while v1.34.7 never executes the marker.

  • Package/component affected: c-ares, specifically src/lib/ares_process.c deferred requeue/endquery handling and src/lib/ares_getaddrinfo.c host_query callback completion state.
  • Affected versions: reproduced on c-ares v1.34.6 (3ac47ee46edd8ea40370222f91613fc16c434853).
  • Fixed version/control: c-ares v1.34.7 (d63b7de3a3568d14ab1351374b205365aed51cca), which contains fix commit d823199b688052dcdc1646f2ab4cb8c16b1c644a.
  • Risk level and consequences: high. A malicious DNS/TCP peer can drive the vulnerable completion timing through a real resolver API. This run demonstrates that reclaimed host_query callback fields can be consumed as control flow in the vulnerable build, which is equivalent to a code-execution primitive in-process when heap content is controlled.

Impact Parity

  • Disclosed/claimed maximum impact: code execution/RCE primitive construction from the confirmed remote c-ares UAF/double-free.
  • Reproduced impact from this run: vulnerable-only sentinel function execution (SENTINEL EXECUTION MARKER FIRED) through a forged reclaimed host_query object after a real malicious TCP DNS peer drives ares_getaddrinfo().
  • Parity: full.
  • Not demonstrated: no shell command execution or external payload execution was attempted. The proof uses a benign in-process sentinel callback as the code-execution/control-flow marker, which the ticket explicitly allowed as an acceptable safe proof form.

Root Cause

In v1.34.6, read_answers() accumulates deferred ENDQUERY entries in a requeue array and later flushes them by looking up the query by QID, invoking query->callback(query->arg, ...), and only then freeing/detaching the query with ares_free_query(query). Because the query remains visible in the channel's queries_by_qid/all_queries structures while its callback is running, callback-driven reentrant resolver activity can find and complete/free related state before the outer completion path has finished. For the ares_getaddrinfo() path, host_callback() may call end_hquery()/hquery_free() during this reentrant sequence, then later stale code continues to operate on the freed host_query pointer.

The fixed code introduces centralized ares_flush_requeue() handling and detaches the query before invoking the callback. In the fixed implementation, the ENDQUERY drain performs ares_detach_query(query) before query->callback(...), so reentrant cancellation/completion cannot rediscover and free the same query or drive the stale host_query consumption window. The reproduction anchors the negative control to v1.34.7, which contains fix commit d823199b688052dcdc1646f2ab4cb8c16b1c644a.

Reproduction Steps

  1. Use bundle/repro/reproduction_steps.sh.
  2. The script reuses or clones c-ares into the prepared project cache, checks out v1.34.6 and v1.34.7, verifies that only the fixed checkout contains ares_flush_requeue, builds static non-sanitized libcares.a for both, and compiles bundle/repro/rce_reclaim_harness.c against each build.
  3. The harness calls public ares_getaddrinfo(AF_INET) and sends traffic through a real localhost TCP DNS socket to a malicious peer. The peer sends FORMERR without OPT, a duplicate same-QID NXDOMAIN, observes the follow-on search-domain DNS query, and resets the TCP connection.
  4. The harness uses the public ares_library_init_mem() allocator hooks to deterministically retain and rewrite freed 144-byte host_query chunks with a forged callback/arg/addrinfo layout. Success is counted only when c-ares later consumes the forged callback and fires the sentinel.
  5. The script runs 30 vulnerable attempts and 10 fixed attempts. Expected evidence is at least two vulnerable sentinel executions and zero fixed sentinel executions, with TCP DNS boundary evidence in both builds.

Evidence

Key artifacts:

  • bundle/repro/reproduction_steps.sh — self-contained reproducer.
  • bundle/repro/rce_reclaim_harness.c — C harness using real c-ares public APIs and real TCP DNS peer.
  • bundle/logs/reproduction_steps.log — aggregate build/run/proof log.
  • bundle/logs/rce_reclaim_vuln_attempt*.log — vulnerable attempt logs.
  • bundle/logs/rce_reclaim_fixed_attempt*.log — fixed negative-control logs.
  • bundle/repro/runtime_manifest.json — runtime evidence manifest.
  • bundle/repro/validation_verdict.json — structured verdict.

The latest verified run ended with:

[repro] counts: vuln_boundary_count=30 fixed_boundary_count=10 vuln_forge_count=30 rce_marker_count=4 fixed_marker_count=0
[repro] wrote validation_verdict.json: claim_outcome=confirmed observed_impact_class=code_execution
[repro] proof summary: rce_marker_fired=1 rce_marker_count=4 fixed_marker=0 fixed_marker_count=0 vuln_boundary=1 fixed_boundary=1 controlled_reclaim=1 vuln_runs_ok=1 fixed_runs_ok=1
[repro] SUCCESS: vulnerable-only sentinel code execution marker fired through reclaimed host_query over real TCP DNS path

Representative vulnerable marker evidence:

[server] accepted TCP DNS connection on 127.0.0.1:40863
[server] sending FORMERR without OPT for qid=22298
[server] sending second response with duplicate qid=22298 (NXDOMAIN, no OPT)
[server] observed follow-on search-domain query qid=22298 qtype=1 length=33
[server] resetting TCP connection with SO_LINGER zero
[allocator] FORGED_RECLAIM host_query ptr=0x5588b747d020 callback@24=0x55888b4cbcf9 arg@32=0x7ffeddcb4bc8 ai@112=0x5588b7490e30 remaining@128=1
[RCE] *** SENTINEL EXECUTION MARKER FIRED *** arg=0x7ffeddcb4bc8 status=11 timeouts=0 callback=0x55888b4cbcf9
[result] done=1 status=11 accepted=1 follow_on_seen=1 normal_callbacks=1 forged_host_query=1 forged_callback_consumed=1 rce_marker_fired=1

Representative fixed negative-control evidence shows the same DNS boundary and allocator-forge opportunity but no stale consumption:

[server] accepted TCP DNS connection on 127.0.0.1:42041
[server] sending FORMERR without OPT for qid=24413
[server] sending second response with duplicate qid=24413 (NXDOMAIN, no OPT)
[server] observed follow-on search-domain query qid=24413 qtype=1 length=33
[server] resetting TCP connection with SO_LINGER zero
[allocator] FORGED_RECLAIM host_query ptr=0x55d50d23f020 callback@24=0x55d4d3286cb9 arg@32=0x7fff28da4f38 ai@112=0x55d50d2536f0 remaining@128=1
[result] done=1 status=11 accepted=1 follow_on_seen=1 normal_callbacks=1 forged_host_query=1 forged_callback_consumed=0 rce_marker_fired=0

Environment and source identity captured by the script:

vulnerable checkout v1.34.6 -> 3ac47ee46edd8ea40370222f91613fc16c434853
fixed checkout v1.34.7 -> d63b7de3a3568d14ab1351374b205365aed51cca
vulnerable ares_flush_requeue count: 0
fixed ares_flush_requeue count: 5

Recommendations / Next Steps

  • Upgrade c-ares deployments to v1.34.7 or later.
  • Preserve the fix pattern: detach queries from all lookup structures before invoking user callbacks, and centralize deferred requeue/endquery draining so callback reentrancy cannot rediscover pending query objects.
  • Add regression tests that exercise malicious TCP DNS peers, duplicate-QID/deferred ENDQUERY flows, TCP reset after follow-on query, and callback reentrancy.
  • Add allocator-assisted and sanitizer-backed tests that verify no stale host_query or ares_query fields are consumed after callback-driven reentrant cleanup.

Additional Notes

  • Idempotency confirmation: bundle/repro/reproduction_steps.sh was run successfully twice consecutively. The first successful full run observed 7 vulnerable marker firings out of 30 and 0 fixed marker firings out of 10. The second successful full run observed 4 vulnerable marker firings out of 30 and 0 fixed marker firings out of 10.
  • The vulnerable marker is probabilistic because it depends on the exact reentrant stale-use timing, so the script runs multiple bounded attempts and requires at least two vulnerable marker firings while ensuring the fixed build has zero marker firings.
  • The proof intentionally uses a benign in-process sentinel callback instead of executing an external command. No sanitizer is used for the primary code-execution proof.

Variant Analysis & Alternative Triggers for CVE-2026-33630

Versions: version tested: c-ares v1.34.6 (3ac47ee46edd8ea40370222f91613fc16c434853).Fixed: version tested: c-ares v1.34.7 (d63b7de3a3568d14ab1351374b205365aed51cca) containing fix commit d823199b688052dcdc1646f2ab4cb8c16b1c644a.

This variant run found a distinct vulnerable-version alternate trigger for the same c-ares query lifetime root cause, but not a bypass of the v1.34.7 fix. The parent proof used a malicious TCP DNS peer and ares_getaddrinfo() duplicate-QID answer processing to reach stale callback consumption/code-execution marker behavior. The alternate trigger uses public ares_query_dnsrec() over UDP to a real DNS peer that receives the query and withholds a response, driving process_timeouts(). The timeout callback then reenters c-ares through public ares_cancel(). On c-ares v1.34.6 this causes a deterministic double free; on v1.34.7 the same runtime path completes without memory-safety evidence because process_timeouts() is covered by ares_flush_requeue() detach-before-callback handling.

Fix Coverage / Assumptions

The v1.34.7 fix relies on the invariant that a completing ares_query_t must be detached from c-ares lookup structures before invoking a user or wrapper callback. If a callback can run while the query remains visible in queries_by_qid or all_queries, callback reentrancy can rediscover and free the same query before the outer completion path frees it.

The fix commit is d823199b688052dcdc1646f2ab4cb8c16b1c644a ([Backport v1.34] Fix double-free in process_timeouts() and consolidate requeue handling (#1237)), included in fixed tag v1.34.7 commit d63b7de3a3568d14ab1351374b205365aed51cca. It explicitly covers:

  • read_answers() deferred ENDQUERY flushing, which was the parent TCP duplicate-QID path.
  • process_timeouts(), the alternate trigger validated in this run.
  • ares_send_query() retry/end handling, by introducing ares_send_query_int() plus an iterative flush.

The main new helper is ares_flush_requeue() in src/lib/ares_process.c. For ENDQUERY entries it calls ares_detach_query(query) before query->callback(...), then frees the query. Remaining direct callback/free paths such as ares_cancel() and ares_destroy() are application lifecycle APIs, not independent remote DNS peer entry points under this ticket's trust boundary.

Variant / Alternate Trigger

Validated alternate path:

  • Entrypoint: public ares_query_dnsrec().
  • Network behavior: localhost UDP DNS peer receives the DNS query and intentionally does not reply.
  • Completion path: process_timeouts() in src/lib/ares_process.c detects the timeout and calls ares_requeue_query().
  • Reentrancy: the user callback invokes public ares_cancel(channel).
  • Vulnerable sink: in v1.34.6, timeout completion reaches callback-before-free while the query remains discoverable; ares_cancel() invokes a second callback and frees it, then the outer timeout path frees it again.

Code anchors:

  • src/lib/ares_process.c: process_timeouts() around lines 692-724 in v1.34.6.
  • src/lib/ares_process.c: ares_requeue_query() around lines 970-1002 in v1.34.6.
  • src/lib/ares_process.c: end_query() around lines 1474-1486 in v1.34.6.
  • src/lib/ares_cancel.c: cancellation loop around lines 58-74.
  • Fixed src/lib/ares_process.c: ares_flush_requeue() around lines 604-676 in v1.34.7.

Tested/rule-out candidates:

  1. process_timeouts() reentrant ares_cancel() self-cancel path: confirmed on vulnerable v1.34.6, blocked on fixed v1.34.7.
  2. read_answers() duplicate-QID TCP ENDQUERY path: parent path, not distinct; blocked on v1.34.7.
  3. Direct ares_cancel() / ares_destroy() lifecycle APIs: not remote DNS peer entry points by themselves, so not valid variants under the ticket trust boundary.
  • Package/component affected: c-ares query completion and cancellation handling in src/lib/ares_process.c and src/lib/ares_cancel.c.
  • Affected version tested: c-ares v1.34.6 (3ac47ee46edd8ea40370222f91613fc16c434853).
  • Fixed version tested: c-ares v1.34.7 (d63b7de3a3568d14ab1351374b205365aed51cca) containing fix commit d823199b688052dcdc1646f2ab4cb8c16b1c644a.
  • Risk level and consequences: memory corruption in vulnerable builds when callback reentrancy is present. The runtime proof shows a glibc-detected double free on the vulnerable build. The fixed build reaches the same high-level timeout and callback reentrancy path without double free.

Impact Parity

  • Disclosed/claimed maximum impact for the parent: code execution/RCE primitive from a remote DNS peer, demonstrated in the parent reproduction by a vulnerable-only in-process sentinel callback marker.
  • Reproduced impact from this variant run: vulnerable-only memory corruption/double free in process_timeouts() with a real DNS peer and public c-ares APIs.
  • Parity: partial.
  • Not demonstrated: no code-execution marker was demonstrated for the timeout alternate trigger. The fixed version did not reproduce memory corruption, so this is not a bypass.

Root Cause

The same underlying bug is callback-before-detach/free in query completion. In v1.34.6, process_timeouts() called ares_requeue_query(..., requeue=NULL). When retries were exhausted, ares_requeue_query() called end_query(), which invoked query->callback(query->arg, ...) before ares_free_query(query). During that callback, the query was still reachable to public reentrant APIs. The harness reentered with ares_cancel(), which found the same query, invoked the cancellation callback, and freed it. Control then returned to the outer timeout completion, which freed the same query again.

The fixed commit d823199b688052dcdc1646f2ab4cb8c16b1c644a closes this by routing process_timeouts() through a requeue list and ares_flush_requeue(), where ENDQUERY processing detaches the query from all lookup lists before invoking the callback.

Reproduction Steps

  1. Run bundle/vuln_variant/reproduction_steps.sh.
  2. The script reuses or clones c-ares, checks out v1.34.6 and v1.34.7, verifies v1.34.7 contains fix commit d823199b688052dcdc1646f2ab4cb8c16b1c644a, builds static non-sanitized libcares.a for both, compiles bundle/vuln_variant/timeout_cancel_probe.c, and runs both binaries side by side.
  3. Expected evidence:
    • Vulnerable v1.34.6 log shows UDP DNS query receipt, timeout callback entry, reentrant ares_cancel(), a second cancellation callback, and free(): double free detected in tcache 2.
    • Fixed v1.34.7 log shows the same UDP query receipt and reentrant ares_cancel(), but only one callback and a clean [result] line.
    • The script exits 1 because no fixed-version bypass is confirmed; this is expected for a fixed-covered alternate trigger.

Evidence

Primary artifacts:

  • bundle/vuln_variant/reproduction_steps.sh — idempotent variant reproducer.
  • bundle/vuln_variant/timeout_cancel_probe.c — public-API C harness.
  • bundle/logs/vuln_variant_reproduction_steps.log — aggregate build/run log.
  • bundle/logs/vuln_variant_timeout_vuln.log — vulnerable runtime log.
  • bundle/logs/vuln_variant_timeout_fixed.log — fixed runtime log.
  • bundle/logs/vuln_variant_fixed_version.txt — fixed source identity and fix-commit ancestry.
  • bundle/vuln_variant/runtime_manifest.json — runtime manifest.
  • bundle/vuln_variant/validation_verdict.json — structured verdict.
  • bundle/vuln_variant/source_identity.json — tested source identity.

Representative vulnerable evidence:

[server] received UDP DNS query bytes=64 qid=1930; intentionally withholding response to drive process_timeouts()
[callback] entry #1 status=12 (Timeout while contacting DNS servers) timeouts=1 cancel_called=0
[callback] reentering public ares_cancel(channel) from timeout callback
[callback] entry #2 status=24 (DNS query cancelled) timeouts=0 cancel_called=1
[callback] returned from reentrant ares_cancel(channel)
free(): double free detected in tcache 2

Representative fixed evidence:

[server] received UDP DNS query bytes=64 qid=21529; intentionally withholding response to drive process_timeouts()
[callback] entry #1 status=12 (Timeout while contacting DNS servers) timeouts=1 cancel_called=0
[callback] reentering public ares_cancel(channel) from timeout callback
[callback] returned from reentrant ares_cancel(channel)
[result] received_query=1 done=1 callback_count=1 cancel_called=1 first_status=12

The final script summary from repeated validation was:

[variant] FINAL setup_ok=1 vuln_rc=134 fixed_rc=0 vuln_repro=1 fixed_repro=1 vuln_memory_safety=1 fixed_memory_safety=0 alternate_trigger=1 bypass_confirmed=0

Recommendations / Next Steps

  • Keep the v1.34.7 fix pattern: all query completion paths that can invoke callbacks should detach the query from lookup/list structures before callback invocation.
  • Add a regression test for timeout callback self-cancellation: issue a query that times out, call ares_cancel() from the timeout callback, and assert only one callback/free occurs.
  • Add regression tests for callback reentrancy across read_answers(), process_timeouts(), and send-path immediate failures to prevent future callback-before-detach regressions.
  • Consider internal assertions or debug instrumentation that verify a query is not still in queries_by_qid/all_queries immediately before invoking completion callbacks.

Additional Notes

  • Idempotency confirmed: bundle/vuln_variant/reproduction_steps.sh was run twice consecutively and produced the same outcome each time.
  • This is an alternate trigger on the vulnerable version, not a bypass. The fixed v1.34.7 build reaches the same timeout and callback reentrancy scenario but does not exhibit memory corruption.
  • The attacker-controlled network component in this variant is DNS response timing/withholding. The required ares_cancel() reentrancy is modeled as application callback behavior through public API, so exploitability is lower than the parent TCP duplicate-QID RCE marker path.

CVE-2026-33630 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:003:35
0:00
session startedaccounts/fireworks/models/kimi-k2p7-code · CVE-2026-33630 · REPRO-20
0:03
0:05
web search
0:15
0:16
0:17
web search
0:27
0:28
1:18
1:18
extract_facts
no facts extracted
1:19
1:19
1:19
supportrepro
3:23
3:25
3:25
3:25
3:27
3:27
3:27
3:27
3:29
3:29
3:29
3:29
3:35
3:35
3:35
3:35
3:35
08 · How to Fix

How to Fix CVE-2026-33630

Upgrade c-ares/c-ares · c to 1.34.7 or later.

Coming soon

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

10 · FAQ

FAQ: CVE-2026-33630

How does the c-ares exploitability demonstration work?

Under a real TCP DNS trigger combined with a deterministic public allocator-hook heap reclaim, the reproduction shows v1.34.6 executing a sentinel callback from a forged, reclaimed host_query structure - demonstrating that reclaimed callback fields can be consumed as control flow - while v1.34.7 never executes that marker.

Which c-ares versions are affected by CVE-2026-33630, and where is it fixed?

c-ares versions greater than 1.32.3 through 1.34.6 are affected; it is fixed in 1.34.7.

How severe is CVE-2026-33630, and is it a confirmed RCE?

It is rated high severity. The reproduction demonstrates that the use-after-free lets reclaimed host_query callback fields drive control flow in the vulnerable build - equivalent to a code-execution primitive when heap content is attacker-controlled - rather than shipping a fully weaponized, end-to-end RCE exploit.

How can I reproduce CVE-2026-33630?

Download the verified script from this page and run it in an isolated environment against c-ares v1.34.6; it drives the vulnerable ares_getaddrinfo() completion timing over TCP DNS with a deterministic allocator-hook heap reclaim and shows the forged host_query callback executing, then confirms v1.34.7 does not execute it.
11 · References

References for CVE-2026-33630

Authoritative sources for CVE-2026-33630 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.