Add c-ares TCP UAF calc PoC
This commit is contained in:
217
c-ares-tcp-uaf-calc-poc/README.md
Normal file
217
c-ares-tcp-uaf-calc-poc/README.md
Normal file
@@ -0,0 +1,217 @@
|
||||
# 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
|
||||
|
||||
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:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
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 and CTF environments.
|
||||
Reference in New Issue
Block a user