diff --git a/c-ares-tcp-uaf-calc-poc/README.md b/c-ares-tcp-uaf-calc-poc/README.md new file mode 100644 index 0000000..eaf829d --- /dev/null +++ b/c-ares-tcp-uaf-calc-poc/README.md @@ -0,0 +1,229 @@ +# 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: + +```text +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 + +Unpatched + +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. + +The current `main` head and latest official release tag were both verified through the same resolver I/O path. The PoC is not an offline packet parser: it starts a loopback DNS-over-TCP server, lets c-ares issue real TCP DNS queries, sends the two-response EDNS retry sequence, and then resets the connection before cleanup consumes the stale state. + +## 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: + +```text +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`: + +```bash +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: + +```bash +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 + +```bash +chmod +x ./cares_tcp_uaf_calc_poc +./scripts/run_until_hit.sh ./cares_tcp_uaf_calc_poc +``` + +Expected success: + +```text +CARES_RCE_PAYLOAD_TRIGGERED +run=N rc=77 +c-ares control-flow payload reached pid=... +``` + +The proof writes: + +```text +/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: + +```text +upstream main c93e50f3: run=1 rc=77 +v1.34.6 release: run=1 rc=77 +``` + +Additional local repeat testing against the release build reached the control-flow marker in consecutive runs: + +```text +run=1 rc=77 hit=true +run=2 rc=77 hit=true +run=3 rc=77 hit=true +run=4 rc=77 hit=true +run=5 rc=77 hit=true +``` + +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: + +```text +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: + +```bash +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: + +```bash +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 + +- c-ares project: https://c-ares.org/ +- c-ares releases: https://c-ares.org/download/ +- c-ares GitHub: https://github.com/c-ares/c-ares +- gRPC resolver docs: https://grpc.github.io/grpc/cpp/md_doc_environment_variables.html +- Envoy DNS resolution docs: https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/upstream/dns_resolution +- Envoy c-ares resolver API: https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/network/dns_resolver/cares/v3/cares_dns_resolver.proto +- Tornado c-ares resolver docs: https://www.tornadoweb.org/en/stable/caresresolver.html +- Wireshark 3.4.0 release notes: https://www.wireshark.org/docs/relnotes/wireshark-3.4.0.html +- Node.js c-ares security update example: https://nodejs.org/en/blog/vulnerability/july-2017-security-releases + +## Responsible Use + +Run this PoC only against local research targets, owned systems, or explicitly authorized lab environments.