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.
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).
CVE-2026-33630 Severity
CVE-2026-33630 is rated high severity.
High — serious impact or readily exploitable. Prioritize remediation.
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 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 Proof of Reproduction for CVE-2026-33630
- 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
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
- ares_getaddrinfo(AF_INET) over TCP
- read_answers() deferred ENDQUERY flush
- host_callback() reentrant next_lookup/end_hquery after TCP reset
- hquery_free()
- stale host_callback consumes forged host_query
- callback/arg and executes sentinel
reproduction_steps.sh 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
Root Cause and Exploit Chain for CVE-2026-33630
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.cdeferred requeue/endquery handling andsrc/lib/ares_getaddrinfo.chost_querycallback completion state. - Affected versions: reproduced on c-ares v1.34.6 (
3ac47ee46edd8ea40370222f91613fc16c434853). - Fixed version/control: c-ares v1.34.7 (
d63b7de3a3568d14ab1351374b205365aed51cca), which contains fix commitd823199b688052dcdc1646f2ab4cb8c16b1c644a. - 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_querycallback 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 reclaimedhost_queryobject after a real malicious TCP DNS peer drivesares_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
- Use
bundle/repro/reproduction_steps.sh. - 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-sanitizedlibcares.afor both, and compilesbundle/repro/rce_reclaim_harness.cagainst each build. - 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. - The harness uses the public
ares_library_init_mem()allocator hooks to deterministically retain and rewrite freed 144-bytehost_querychunks with a forged callback/arg/addrinfo layout. Success is counted only when c-ares later consumes the forged callback and fires the sentinel. - 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_queryorares_queryfields are consumed after callback-driven reentrant cleanup.
Additional Notes
- Idempotency confirmation:
bundle/repro/reproduction_steps.shwas 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
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 introducingares_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()insrc/lib/ares_process.cdetects the timeout and callsares_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:
process_timeouts()reentrantares_cancel()self-cancel path: confirmed on vulnerable v1.34.6, blocked on fixed v1.34.7.read_answers()duplicate-QID TCP ENDQUERY path: parent path, not distinct; blocked on v1.34.7.- 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.candsrc/lib/ares_cancel.c. - Affected version tested: c-ares v1.34.6 (
3ac47ee46edd8ea40370222f91613fc16c434853). - Fixed version tested: c-ares v1.34.7 (
d63b7de3a3568d14ab1351374b205365aed51cca) containing fix commitd823199b688052dcdc1646f2ab4cb8c16b1c644a. - 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
- Run
bundle/vuln_variant/reproduction_steps.sh. - 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-sanitizedlibcares.afor both, compilesbundle/vuln_variant/timeout_cancel_probe.c, and runs both binaries side by side. - Expected evidence:
- Vulnerable v1.34.6 log shows UDP DNS query receipt, timeout callback entry, reentrant
ares_cancel(), a second cancellation callback, andfree(): 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
1because no fixed-version bypass is confirmed; this is expected for a fixed-covered alternate trigger.
- Vulnerable v1.34.6 log shows UDP DNS query receipt, timeout callback entry, reentrant
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_queriesimmediately before invoking completion callbacks.
Additional Notes
- Idempotency confirmed:
bundle/vuln_variant/reproduction_steps.shwas 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.
Artifacts and Evidence for CVE-2026-33630
Scripts, logs, diffs, and output captured during the reproduction.
How to Fix CVE-2026-33630
Upgrade c-ares/c-ares · c to 1.34.7 or later.
FAQ: CVE-2026-33630
How does the c-ares exploitability demonstration work?
Which c-ares versions are affected by CVE-2026-33630, and where is it fixed?
How severe is CVE-2026-33630, and is it a confirmed RCE?
How can I reproduce CVE-2026-33630?
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.