c-ares 1.16.0: ares_destroy() with pending ares_getaddrinfo() leads to use-after-free The following code was introduced in c-ares commit dbd4c441 (first released in 1.16.0, which was published on 2020-03-13), as part of the new ares_getaddrinfo() feature: ========================================================= static void end_hquery(struct host_query *hquery, int status) { [...] hquery->callback(hquery->arg, status, hquery->timeouts, hquery->ai); ares_free(hquery->name); ares_free(hquery); } static void host_callback(void *arg, int status, int timeouts, unsigned char *abuf, int alen) { struct host_query *hquery = (struct host_query*)arg; int addinfostatus = ARES_SUCCESS; [...] if (status == ARES_SUCCESS) [...] else if (status == ARES_EDESTRUCTION) { end_hquery(hquery, status); } if (!hquery->remaining) { if (addinfostatus != ARES_SUCCESS) [...] else if (hquery->ai->nodes) { /* at least one query ended with ARES_SUCCESS */ end_hquery(hquery, ARES_SUCCESS); } else if (status == ARES_ENOTFOUND) [...] else { end_hquery(hquery, status); } } /* at this point we keep on waiting for the next query to finish */ } ========================================================= In the ARES_EDESTRUCTION case, host_callback() ends up calling end_hquery() twice (unless it crashes before the second call), and the second call will, among other things, call a function pointer from freed memory and free the memory a second time. Here's a reproducer: ========================================================= #include #include #include #include static void gai_cb(void *arg, int status, int timeouts, struct ares_addrinfo *result) { printf(\"gai_cb(): %s\ \", ares_strerror(status)); } int main(void) { if (ares_library_init(ARES_LIB_INIT_ALL)) errx(1, \"ares_library_init\"); ares_channel chan; if (ares_init(&chan)) errx(1, \"ares_init\"); ares_getaddrinfo(chan, \"blah\", NULL, NULL, gai_cb, NULL); ares_destroy(chan); return 0; } ========================================================= Output (from a test against c-ares from Debian testing): ========================================================= user@vm:~/test/cares-gai-destroy$ valgrind ./cares-gai-destroy-uaf ==5248== Memcheck, a memory error detector ==5248== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al. ==5248== Using Valgrind-3.14.0 and LibVEX; rerun with -h for copyright info ==5248== Command: ./cares-gai-destroy-uaf ==5248== gai_cb(): Channel is being destroyed ==5248== Invalid read of size 4 ==5248== at 0x485F56A: host_callback (ares_getaddrinfo.c:553) ==5248== by 0x486869F: qcallback (ares_query.c:183) ==5248== by 0x485E8D0: ares_destroy (ares_destroy.c:58) ==5248== by 0x109253: main (cares-gai-destroy-uaf.c:18) ==5248== Address 0x4a4ae00 is 80 bytes inside a block of size 88 free'd ==5248== at 0x48369AB: free (vg_replace_malloc.c:530) ==5248== by 0x485F126: end_hquery (ares_getaddrinfo.c:429) ==5248== by 0x485F569: host_callback (ares_getaddrinfo.c:550) ==5248== by 0x486869F: qcallback (ares_query.c:183) ==5248== by 0x485E8D0: ares_destroy (ares_destroy.c:58) ==5248== by 0x109253: main (cares-gai-destroy-uaf.c:18) ==5248== Block was alloc'd at ==5248== at 0x483577F: malloc (vg_replace_malloc.c:299) ==5248== by 0x485F951: ares_getaddrinfo (ares_getaddrinfo.c:650) ==5248== by 0x109247: main (cares-gai-destroy-uaf.c:17) ==5248== ==5248== Invalid read of size 4 ==5248== at 0x485F4CD: host_callback (ares_getaddrinfo.c:542) ==5248== by 0x486869F: qcallback (ares_query.c:183) ==5248== by 0x485E8D0: ares_destroy (ares_destroy.c:58) ==5248== by 0x109253: main (cares-gai-destroy-uaf.c:18) ==5248== Address 0x4a4ae00 is 80 bytes inside a block of size 88 free'd ==5248== at 0x48369AB: free (vg_replace_malloc.c:530) ==5248== by 0x485F126: end_hquery (ares_getaddrinfo.c:429) ==5248== by 0x485F569: host_callback (ares_getaddrinfo.c:550) ==5248== by 0x486869F: qcallback (ares_query.c:183) ==5248== by 0x485E8D0: ares_destroy (ares_destroy.c:58) ==5248== by 0x109253: main (cares-gai-destroy-uaf.c:18) ==5248== Block was alloc'd at ==5248== at 0x483577F: malloc (vg_replace_malloc.c:299) ==5248== by 0x485F951: ares_getaddrinfo (ares_getaddrinfo.c:650) ==5248== by 0x109247: main (cares-gai-destroy-uaf.c:17) ==5248== ==5248== Invalid read of size 4 ==5248== at 0x485F4D0: host_callback (ares_getaddrinfo.c:541) ==5248== by 0x486869F: qcallback (ares_query.c:183) ==5248== by 0x485E8D0: ares_destroy (ares_destroy.c:58) ==5248== by 0x109253: main (cares-gai-destroy-uaf.c:18) ==5248== Address 0x4a4adec is 60 bytes inside a block of size 88 free'd ==5248== at 0x48369AB: free (vg_replace_malloc.c:530) ==5248== by 0x485F126: end_hquery (ares_getaddrinfo.c:429) ==5248== by 0x485F569: host_callback (ares_getaddrinfo.c:550) ==5248== by 0x486869F: qcallback (ares_query.c:183) ==5248== by 0x485E8D0: ares_destroy (ares_destroy.c:58) ==5248== by 0x109253: main (cares-gai-destroy-uaf.c:18) ==5248== Block was alloc'd at ==5248== at 0x483577F: malloc (vg_replace_malloc.c:299) ==5248== by 0x485F951: ares_getaddrinfo (ares_getaddrinfo.c:650) ==5248== by 0x109247: main (cares-gai-destroy-uaf.c:17) ==5248== ==5248== Invalid write of size 4 ==5248== at 0x485F4D6: host_callback (ares_getaddrinfo.c:542) ==5248== by 0x486869F: qcallback (ares_query.c:183) ==5248== by 0x485E8D0: ares_destroy (ares_destroy.c:58) ==5248== by 0x109253: main (cares-gai-destroy-uaf.c:18) ==5248== Address 0x4a4ae00 is 80 bytes inside a block of size 88 free'd ==5248== at 0x48369AB: free (vg_replace_malloc.c:530) ==5248== by 0x485F126: end_hquery (ares_getaddrinfo.c:429) ==5248== by 0x485F569: host_callback (ares_getaddrinfo.c:550) ==5248== by 0x486869F: qcallback (ares_query.c:183) ==5248== by 0x485E8D0: ares_destroy (ares_destroy.c:58) ==5248== by 0x109253: main (cares-gai-destroy-uaf.c:18) ==5248== Block was alloc'd at ==5248== at 0x483577F: malloc (vg_replace_malloc.c:299) ==5248== by 0x485F951: ares_getaddrinfo (ares_getaddrinfo.c:650) ==5248== by 0x109247: main (cares-gai-destroy-uaf.c:17) ==5248== ==5248== Invalid read of size 8 ==5248== at 0x485F0BD: end_hquery (ares_getaddrinfo.c:394) ==5248== by 0x485F569: host_callback (ares_getaddrinfo.c:550) ==5248== by 0x486869F: qcallback (ares_query.c:183) ==5248== by 0x485E8D0: ares_destroy (ares_destroy.c:58) ==5248== by 0x109253: main (cares-gai-destroy-uaf.c:18) ==5248== Address 0x4a4adf8 is 72 bytes inside a block of size 88 free'd ==5248== at 0x48369AB: free (vg_replace_malloc.c:530) ==5248== by 0x485F126: end_hquery (ares_getaddrinfo.c:429) ==5248== by 0x485F569: host_callback (ares_getaddrinfo.c:550) ==5248== by 0x486869F: qcallback (ares_query.c:183) ==5248== by 0x485E8D0: ares_destroy (ares_destroy.c:58) ==5248== by 0x109253: main (cares-gai-destroy-uaf.c:18) ==5248== Block was alloc'd at ==5248== at 0x483577F: malloc (vg_replace_malloc.c:299) ==5248== by 0x485F951: ares_getaddrinfo (ares_getaddrinfo.c:650) ==5248== by 0x109247: main (cares-gai-destroy-uaf.c:17) ==5248== ==5248== Invalid read of size 8 ==5248== at 0x485F05D: ares_freeaddrinfo (ares_freeaddrinfo.c:54) ==5248== by 0x485F167: end_hquery (ares_getaddrinfo.c:423) ==5248== by 0x485F569: host_callback (ares_getaddrinfo.c:550) ==5248== by 0x486869F: qcallback (ares_query.c:183) ==5248== by 0x485E8D0: ares_destroy (ares_destroy.c:58) ==5248== by 0x109253: main (cares-gai-destroy-uaf.c:18) ==5248== Address 0x0 is not stack'd, malloc'd or (recently) free'd ==5248== ==5248== ==5248== Process terminating with default action of signal 11 (SIGSEGV): dumping core ==5248== Access not within mapped region at address 0x0 ==5248== at 0x485F05D: ares_freeaddrinfo (ares_freeaddrinfo.c:54) ==5248== by 0x485F167: end_hquery (ares_getaddrinfo.c:423) ==5248== by 0x485F569: host_callback (ares_getaddrinfo.c:550) ==5248== by 0x486869F: qcallback (ares_query.c:183) ==5248== by 0x485E8D0: ares_destroy (ares_destroy.c:58) ==5248== by 0x109253: main (cares-gai-destroy-uaf.c:18) ==5248== If you believe this happened as a result of a stack ==5248== overflow in your program's main thread (unlikely but ==5248== possible), you can try to increase the size of the ==5248== main thread stack using the --main-stacksize= flag. ==5248== The main thread stack size used in this run was 8388608. ==5248== ==5248== HEAP SUMMARY: ==5248== in use at exit: 74,643 bytes in 7 blocks ==5248== total heap usage: 29 allocs, 22 frees, 95,011 bytes allocated ==5248== ==5248== LEAK SUMMARY: ==5248== definitely lost: 0 bytes in 0 blocks ==5248== indirectly lost: 0 bytes in 0 blocks ==5248== possibly lost: 0 bytes in 0 blocks ==5248== still reachable: 74,643 bytes in 7 blocks ==5248== suppressed: 0 bytes in 0 blocks ==5248== Rerun with --leak-check=full to see details of leaked memory ==5248== ==5248== For counts of detected and suppressed errors, rerun with: -v ==5248== ERROR SUMMARY: 7 errors from 6 contexts (suppressed: 0 from 0) Segmentation fault ========================================================= It seems like there are already some users of ares_getaddrinfo() out there: seems to use ares_getaddrinfo(), and also uses ares_destroy() - not just on program exit, but also e.g. when handling ARES_ECONNREFUSED. Luckily for them, they pin some random commit between releases (which doesn't include the bug yet) in . But there are also some other hits on github for ares_getaddrinfo(), and there seems to be at least one library that has shipped a release that uses ares_getaddrinfo(). This bug is subject to a 90 day disclosure deadline. After 90 days elapse, the bug report will become visible to the public. The scheduled disclosure date is 2020-08-02. Disclosure at an earlier date is possible if agreed upon by all parties. (To clarify: This deadline only applies to when we publish this bug report in our own bugtracker, nothing else.) Found by: jannh@google.com