## Summary

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.

## Impact

- 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:

```text
[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:

```text
[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:

```text
[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.
