Files
exploitarium/c-ares-tcp-uaf-calc-poc
2026-06-24 02:37:01 -05:00
..
2026-06-24 02:37:01 -05:00
2026-06-24 02:37:01 -05:00
2026-06-24 02:37:01 -05:00
2026-06-24 02:37:01 -05:00

c-ares TCP ares_getaddrinfo() UAF calc PoC

This directory contains a local, benign calculator proof for a c-ares TCP resolver use-after-free in the ares_getaddrinfo() path.

The PoC drives c-ares through a loopback DNS-over-TCP server, shapes the freed c-ares allocation through the public ares_library_init_mem() allocator hook, and demonstrates that cleanup reaches an attacker-shaped indirect call:

ares_slist_node_destroy(node)
  -> node->parent->destruct(node->data)
  -> proof_marker()

The marker writes a proof file under /tmp and attempts to start a local calculator. On WSL it starts Windows Calculator. On Linux desktops it tries common calculator binaries.

Status

Disclosure status: privately reported upstream; publication permitted while a fix is being staged.

Verified targets:

Target Commit Result
c-ares upstream main c93e50f3ebc0373fe57677523ec960f6c1cb0e15 calculator proof reached
c-ares latest official release v1.34.6 3ac47ee46edd8ea40370222f91613fc16c434853 calculator proof reached

This is a local proof harness, not a universal exploit for every application that links c-ares. It demonstrates controlled code execution in the harness when the affected c-ares path, response sequence, allocator shaping, and cleanup path are present.

Files

  • poc/cares_tcp_uaf_calc_poc.c - standalone C proof harness and benign calc payload.
  • scripts/build_from_checkout.sh - builds c-ares from a supplied checkout and links the PoC against the resulting static library.
  • scripts/run_until_hit.sh - retries the probabilistic heap-layout proof until the marker path lands.
  • evidence/main-c93e50f3-gdb.txt - sanitized GDB stack for upstream main.
  • evidence/v1.34.6-gdb.txt - sanitized GDB stack for v1.34.6.
  • evidence/local-verification.txt - local calculator and marker verification transcript.

Vulnerability Shape

The reproduced path uses:

  • API: ares_getaddrinfo()
  • Transport: DNS over TCP
  • Channel flags: ARES_FLAG_EDNS | ARES_FLAG_USEVC
  • Lookup mode: DNS only (opts.lookups = "b")
  • Response behavior:
    • accept a TCP DNS query;
    • return two responses for the same query id in one read;
    • first response is FORMERR without OPT data, causing EDNS retry handling;
    • second response is a successful empty response for the same query id;
    • accept the retry write;
    • reset the TCP connection before the next internal lookup write.

The stale state is later consumed during query cleanup. In the control-flow proof, the stale ares_query_t.node_queries_by_timeout pointer is made to reference a shaped skip-list node. c-ares then calls the node parent list destructor, reaching the local marker function.

The GDB evidence shows the useful call chain:

proof_marker()
ares_slist_node_destroy()
ares_query_remove_from_conn()
ares_detach_query()
ares_free_query()
read_answers()
process_read()
ares_process()
main()

Build

Build against current upstream main:

git clone --depth 1 https://github.com/c-ares/c-ares.git /tmp/c-ares-main
./scripts/build_from_checkout.sh /tmp/c-ares-main /tmp/c-ares-main-build ./cares_tcp_uaf_calc_poc

Build against the latest release tag:

git clone --depth 1 --branch v1.34.6 https://github.com/c-ares/c-ares.git /tmp/c-ares-v1.34.6
./scripts/build_from_checkout.sh /tmp/c-ares-v1.34.6 /tmp/c-ares-v1.34.6-build ./cares_tcp_uaf_calc_poc

Dependencies:

  • Linux or WSL
  • gcc
  • cmake
  • git
  • POSIX sockets and pthreads

The build script links a static PoC binary against the static libcares.a produced from the supplied checkout.

Run

chmod +x ./cares_tcp_uaf_calc_poc
./scripts/run_until_hit.sh ./cares_tcp_uaf_calc_poc

Expected success:

CARES_RCE_PAYLOAD_TRIGGERED
run=N rc=77
c-ares control-flow payload reached pid=...

The proof writes:

/tmp/cares_rce_proof_latest

Calculator launch order:

  1. WSL Windows Calculator through /mnt/c/Windows/System32/calc.exe
  2. WSL Windows Calculator through cmd.exe /c start "" calc.exe
  3. xcalc
  4. gnome-calculator
  5. kcalc

If no GUI calculator is available, the marker file is still the reliable proof signal.

Reliability

The control-flow path is heap-layout sensitive. A clean run can exit normally with rc=0, so the helper script retries. Local verification landed quickly:

upstream main c93e50f3: run=1 rc=77
v1.34.6 release: run=1 rc=77

A miss does not necessarily mean the target is fixed. Use the GDB evidence mode or retry loop when validating.

Why this is code execution and not only a crash

The proof does not stop at a poisoned pointer crash. It shapes the stale pointer so that c-ares reaches a valid function pointer call through its own internal destructor mechanism. The payload is a benign local marker:

node->parent->destruct(node->data)

In the PoC, destruct points at proof_marker(). GDB confirms instruction pointer control at the marker and shows c-ares frames directly below it.

Limits

This PoC intentionally uses c-ares' supported allocator hook to make the control-flow condition reproducible in a local harness. Real application exploitability depends on:

  • whether the application uses the affected ares_getaddrinfo() path;
  • whether DNS over TCP and EDNS retry behavior are reachable;
  • whether attacker-controlled DNS responses can drive the required sequence;
  • allocator behavior and heap layout in the target process;
  • process mitigations, sandboxing, and restart model;
  • whether the target statically bundles c-ares or dynamically links the system library.

Do not treat this source as a drop-in exploit for every c-ares consumer. Treat it as proof that the affected c-ares state machine can be driven from stale object cleanup into a controlled indirect call under the demonstrated conditions.

Reach

c-ares is a low-level asynchronous DNS resolver, so the practical reach is larger than a single command-line tool. A vulnerable c-ares release may appear as a system shared library, a vendored static dependency, or a package-manager dependency. Whether a specific product is affected still requires confirming its bundled c-ares version and whether it exercises the vulnerable API/options.

Major consumers and ecosystems to check:

  • Node.js - Node has historically bundled c-ares under deps/cares and labels c-ares/c-ares-wrap work as part of its DNS dependency surface. Node security releases have previously patched bundled c-ares vulnerabilities. Check the specific Node release and DNS API path.
  • gRPC - gRPC documents the ares DNS resolver as the default on most platforms when built with c-ares support. Services using gRPC client-side DNS resolution should check whether their build enables the c-ares resolver.
  • Envoy Proxy - Envoy documents c-ares as its default DNS resolution library and exposes c-ares DNS resolver configuration. This matters for proxies, gateways, and service-mesh deployments with dynamic DNS clusters.
  • curl/libcurl - c-ares was originally created to provide asynchronous name resolution for curl. libcurl builds can use c-ares as one asynchronous DNS backend; whether a given curl build uses it depends on build flags and platform packaging.
  • Wireshark - Wireshark release notes and build files identify c-ares as a required dependency for asynchronous DNS name resolution in captures.
  • Python async DNS stacks - Tornado documents tornado.platform.caresresolver as a resolver using c-ares through pycares. Other Python packages such as pycares and aiodns may bring c-ares into applications.
  • Rust wrappers - crates such as c-ares and higher-level resolver wrappers expose c-ares to Rust applications.
  • C/C++ package managers - c-ares is packaged through Conan, vcpkg, Linux distributions, BSD ports, Homebrew-style package repositories, and similar dependency managers. Static consumers may remain vulnerable even after the system package is updated.

Operationally, good triage questions are:

ldd /path/to/binary | grep -i cares
readelf -d /path/to/binary | grep -i cares
strings /path/to/binary | grep -i 'c-ares\\|libcares\\|ares_getaddrinfo'

On macOS:

otool -L /path/to/binary | grep -i cares

On Windows, inspect loaded modules or imports for cares.dll, libcares.dll, or statically linked c-ares symbols.

Mitigation Notes

The correct mitigation is to apply the upstream c-ares fix once available and rebuild/redeploy all static consumers. Dynamic consumers need the patched shared library and a process restart. Static consumers need a product rebuild even if the operating system package has been fixed.

Short-term risk reducers, where compatible with the application, include:

  • avoid forcing DNS over TCP for untrusted resolver paths;
  • avoid resolver configurations where attacker-controlled DNS servers can drive application lookups;
  • sandbox processes that perform resolver work;
  • monitor for crashes or abnormal exits in DNS-heavy services;
  • inventory static c-ares copies in language runtimes and appliances.

References

Responsible Use

Run this PoC only against local research targets, owned systems, or explicitly authorized lab and CTF environments.