Version in base suite: 1.34.5-1 Base version: c-ares_1.34.5-1 Target version: c-ares_1.34.5-1+deb13u1 Base file: /srv/ftp-master.debian.org/ftp/pool/main/c/c-ares/c-ares_1.34.5-1.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/c/c-ares/c-ares_1.34.5-1+deb13u1.dsc changelog | 6 gbp.conf | 2 patches/CVE-2025-62408.diff | 379 ++++++++++++++++++++++++++++++++++++++++++++ patches/series | 1 4 files changed, 387 insertions(+), 1 deletion(-) diff -Nru c-ares-1.34.5/debian/changelog c-ares-1.34.5/debian/changelog --- c-ares-1.34.5/debian/changelog 2025-04-08 16:30:43.000000000 +0000 +++ c-ares-1.34.5/debian/changelog 2025-12-14 14:24:14.000000000 +0000 @@ -1,3 +1,9 @@ +c-ares (1.34.5-1+deb13u1) trixie-security; urgency=medium + + * Apply patch to fix use-after-free (fixes CVE-2025-62408) + + -- Gregor Jasny Sun, 14 Dec 2025 15:24:14 +0100 + c-ares (1.34.5-1) unstable; urgency=high * Imported Upstream version 1.34.5 (fixes CVE-2025-31498) diff -Nru c-ares-1.34.5/debian/gbp.conf c-ares-1.34.5/debian/gbp.conf --- c-ares-1.34.5/debian/gbp.conf 2023-05-22 15:13:13.000000000 +0000 +++ c-ares-1.34.5/debian/gbp.conf 2025-12-14 14:24:14.000000000 +0000 @@ -1,6 +1,6 @@ [DEFAULT] upstream-branch = upstream -debian-branch = master +debian-branch = debian/13-trixie upstream-tag = upstream/%(version)s debian-tag = debian/%(version)s pristine-tar = True diff -Nru c-ares-1.34.5/debian/patches/CVE-2025-62408.diff c-ares-1.34.5/debian/patches/CVE-2025-62408.diff --- c-ares-1.34.5/debian/patches/CVE-2025-62408.diff 1970-01-01 00:00:00.000000000 +0000 +++ c-ares-1.34.5/debian/patches/CVE-2025-62408.diff 2025-12-14 14:24:14.000000000 +0000 @@ -0,0 +1,379 @@ +From: Brad House +Date: Mon, 8 Dec 2025 10:12:08 -0500 +Subject: Fix use-after-free (CVE-2025-62408) +Origin: upstream, https://github.com/c-ares/c-ares/commit/714bf5675c541bd1e668a8db8e67ce012651e618 +Forwarded: not-needed +Bug: https://github.com/c-ares/c-ares/security/advisories/GHSA-jq53-42q6-pqr5 + +--- + src/lib/ares_private.h | 10 ++-- + src/lib/ares_process.c | 114 +++++++++++++++++++++++++++++++++------------- + src/lib/ares_qcache.c | 20 ++++++-- + test/ares-test-mock-ai.cc | 32 +++++++++++++ + 4 files changed, 135 insertions(+), 41 deletions(-) + +diff --git a/src/lib/ares_private.h b/src/lib/ares_private.h +index 3d7cea3..8be2580 100644 +--- a/src/lib/ares_private.h ++++ b/src/lib/ares_private.h +@@ -321,7 +321,7 @@ ares_status_t ares_send_query(ares_server_t *requested_server /* Optional */, + ares_status_t ares_requeue_query(ares_query_t *query, const ares_timeval_t *now, + ares_status_t status, + ares_bool_t inc_try_count, +- const ares_dns_record_t *dnsrec, ++ ares_dns_record_t *dnsrec, + ares_array_t **requeue); + + /*! Count the number of labels (dots+1) in a domain */ +@@ -594,10 +594,10 @@ ares_status_t ares_qcache_create(ares_rand_state *rand_state, + unsigned int max_ttl, + ares_qcache_t **cache_out); + void ares_qcache_flush(ares_qcache_t *cache); +-ares_status_t ares_qcache_insert(ares_channel_t *channel, +- const ares_timeval_t *now, +- const ares_query_t *query, +- ares_dns_record_t *dnsrec); ++ares_status_t ares_qcache_insert(ares_channel_t *channel, ++ const ares_timeval_t *now, ++ const ares_query_t *query, ++ const ares_dns_record_t *dnsrec); + ares_status_t ares_qcache_fetch(ares_channel_t *channel, + const ares_timeval_t *now, + const ares_dns_record_t *dnsrec, +diff --git a/src/lib/ares_process.c b/src/lib/ares_process.c +index c5834fa..3de75db 100644 +--- a/src/lib/ares_process.c ++++ b/src/lib/ares_process.c +@@ -64,7 +64,8 @@ static ares_bool_t same_questions(const ares_query_t *query, + const ares_dns_record_t *arec); + static void end_query(ares_channel_t *channel, ares_server_t *server, + ares_query_t *query, ares_status_t status, +- const ares_dns_record_t *dnsrec); ++ ares_dns_record_t *dnsrec, ++ ares_array_t **requeue); + + static void ares_query_remove_from_conn(ares_query_t *query) + { +@@ -511,16 +512,27 @@ static ares_status_t read_conn_packets(ares_conn_t *conn) + return ARES_SUCCESS; + } + ++typedef enum { ++ REQUEUE_REQUEUE = 1, ++ REQUEUE_ENDQUERY = 2 ++} requeue_type_t; ++ + /* Simple data structure to store a query that needs to be requeued with + * optional server */ + typedef struct { +- unsigned short qid; +- ares_server_t *server; /* optional */ ++ requeue_type_t type; /* type of entry, requeue or endquery */ ++ unsigned short qid; /* query id */ ++ ares_server_t *server; /* requeue only: optional */ ++ ares_status_t status; /* endquery only */ ++ ares_dns_record_t *dnsrec; /* endquery only: optional */ + } ares_requeue_t; + +-static ares_status_t ares_append_requeue(ares_array_t **requeue, +- ares_query_t *query, +- ares_server_t *server) ++static ares_status_t ares_append_requeue_int(ares_array_t **requeue, ++ requeue_type_t type, ++ ares_query_t *query, ++ ares_server_t *server, ++ ares_status_t status, ++ ares_dns_record_t *dnsrec) + { + ares_requeue_t entry; + +@@ -533,16 +545,36 @@ static ares_status_t ares_append_requeue(ares_array_t **requeue, + + ares_query_remove_from_conn(query); + ++ entry.type = type; + entry.qid = query->qid; + entry.server = server; ++ entry.status = status; ++ entry.dnsrec = dnsrec; + return ares_array_insertdata_last(*requeue, &entry); + } + ++static ares_status_t ares_append_requeue(ares_array_t **requeue, ++ ares_query_t *query, ++ ares_server_t *server) ++{ ++ return ares_append_requeue_int(requeue, REQUEUE_REQUEUE, query, server, 0, ++ NULL); ++} ++ ++static ares_status_t ares_append_endqueue(ares_array_t **requeue, ++ ares_query_t *query, ++ ares_status_t status, ++ ares_dns_record_t *dnsrec) ++{ ++ return ares_append_requeue_int(requeue, REQUEUE_ENDQUERY, query, NULL, status, ++ dnsrec); ++} ++ + static ares_status_t read_answers(ares_conn_t *conn, const ares_timeval_t *now) + { + ares_status_t status; +- ares_channel_t *channel = conn->server->channel; +- ares_array_t *requeue = NULL; ++ ares_channel_t *channel = conn->server->channel; ++ ares_array_t *requeue = NULL; + + /* Process all queued answers */ + while (1) { +@@ -602,18 +634,30 @@ cleanup: + break; + } + +- /* Query disappeared */ + query = ares_htable_szvp_get_direct(channel->queries_by_qid, entry.qid); +- if (query == NULL) { +- continue; +- } + +- internal_status = ares_send_query(entry.server, query, now); +- /* We only care about ARES_ENOMEM */ +- if (internal_status == ARES_ENOMEM) { +- status = ARES_ENOMEM; ++ if (entry.type == REQUEUE_REQUEUE) { ++ /* query disappeared */ ++ if (query == NULL) { ++ continue; ++ } ++ internal_status = ares_send_query(entry.server, query, now); ++ /* We only care about ARES_ENOMEM */ ++ if (internal_status == ARES_ENOMEM) { ++ status = ARES_ENOMEM; ++ } ++ } else { /* REQUEUE_ENDQUERY */ ++ if (query != NULL) { ++ query->callback(query->arg, entry.status, query->timeouts, entry.dnsrec); ++ ares_free_query(query); ++ } ++ ares_dns_record_destroy(entry.dnsrec); + } + } ++ /* Don't forget to send notification if queue emptied */ ++ if (requeue != NULL) { ++ ares_queue_notify_empty(channel); ++ } + ares_array_destroy(requeue); + + return status; +@@ -669,7 +713,7 @@ static ares_status_t process_timeouts(ares_channel_t *channel, + conn = query->conn; + server_increment_failures(conn->server, query->using_tcp); + status = ares_requeue_query(query, now, ARES_ETIMEOUT, ARES_TRUE, NULL, +- NULL); ++ NULL); + if (status == ARES_ENOMEM) { + goto done; + } +@@ -824,7 +868,7 @@ static ares_status_t process_answer(ares_channel_t *channel, + if (issue_might_be_edns(query->query, rdnsrec)) { + status = rewrite_without_edns(query); + if (status != ARES_SUCCESS) { +- end_query(channel, server, query, status, NULL); ++ end_query(channel, server, query, status, NULL, NULL); + goto cleanup; + } + +@@ -869,7 +913,9 @@ static ares_status_t process_answer(ares_channel_t *channel, + } + + server_increment_failures(server, query->using_tcp); +- status = ares_requeue_query(query, now, status, ARES_TRUE, rdnsrec, requeue); ++ status = ares_requeue_query(query, now, status, ARES_TRUE, rdnsrec, ++ requeue); ++ rdnsrec = NULL; /* Free'd by ares_requeue_query() */ + + if (status != ARES_ENOMEM) { + /* Should any of these cause a connection termination? +@@ -882,12 +928,11 @@ static ares_status_t process_answer(ares_channel_t *channel, + + /* If cache insertion was successful, it took ownership. We ignore + * other cache insertion failures. */ +- if (ares_qcache_insert(channel, now, query, rdnsrec) == ARES_SUCCESS) { +- is_cached = ARES_TRUE; +- } ++ ares_qcache_insert(channel, now, query, rdnsrec); + + server_set_good(server, query->using_tcp); +- end_query(channel, server, query, ARES_SUCCESS, rdnsrec); ++ end_query(channel, server, query, ARES_SUCCESS, rdnsrec, requeue); ++ rdnsrec = NULL; /* Free'd by the requeue */ + + status = ARES_SUCCESS; + +@@ -922,7 +967,7 @@ static void handle_conn_error(ares_conn_t *conn, ares_bool_t critical_failure, + ares_status_t ares_requeue_query(ares_query_t *query, const ares_timeval_t *now, + ares_status_t status, + ares_bool_t inc_try_count, +- const ares_dns_record_t *dnsrec, ++ ares_dns_record_t *dnsrec, + ares_array_t **requeue) + { + ares_channel_t *channel = query->channel; +@@ -939,6 +984,7 @@ ares_status_t ares_requeue_query(ares_query_t *query, const ares_timeval_t *now, + } + + if (query->try_count < max_tries && !query->no_retries) { ++ ares_dns_record_destroy(dnsrec); + if (requeue != NULL) { + return ares_append_requeue(requeue, query, NULL); + } +@@ -950,7 +996,7 @@ ares_status_t ares_requeue_query(ares_query_t *query, const ares_timeval_t *now, + query->error_status = ARES_ETIMEOUT; + } + +- end_query(channel, NULL, query, query->error_status, dnsrec); ++ end_query(channel, NULL, query, query->error_status, dnsrec, requeue); + return ARES_ETIMEOUT; + } + +@@ -1232,7 +1278,7 @@ ares_status_t ares_send_query(ares_server_t *requested_server, + } + + if (server == NULL) { +- end_query(channel, server, query, ARES_ENOSERVER /* ? */, NULL); ++ end_query(channel, server, query, ARES_ENOSERVER /* ? */, NULL, NULL); + return ARES_ENOSERVER; + } + +@@ -1260,7 +1306,7 @@ ares_status_t ares_send_query(ares_server_t *requested_server, + + /* Anything else is not retryable, likely ENOMEM */ + default: +- end_query(channel, server, query, status, NULL); ++ end_query(channel, server, query, status, NULL, NULL); + return status; + } + } +@@ -1274,7 +1320,7 @@ ares_status_t ares_send_query(ares_server_t *requested_server, + + case ARES_ENOMEM: + /* Not retryable */ +- end_query(channel, server, query, status, NULL); ++ end_query(channel, server, query, status, NULL, NULL); + return status; + + /* These conditions are retryable as they are server-specific +@@ -1306,7 +1352,7 @@ ares_status_t ares_send_query(ares_server_t *requested_server, + ares_slist_insert(channel->queries_by_timeout, query); + if (!query->node_queries_by_timeout) { + /* LCOV_EXCL_START: OutOfMemory */ +- end_query(channel, server, query, ARES_ENOMEM, NULL); ++ end_query(channel, server, query, ARES_ENOMEM, NULL, NULL); + return ARES_ENOMEM; + /* LCOV_EXCL_STOP */ + } +@@ -1319,7 +1365,7 @@ ares_status_t ares_send_query(ares_server_t *requested_server, + + if (query->node_queries_to_conn == NULL) { + /* LCOV_EXCL_START: OutOfMemory */ +- end_query(channel, server, query, ARES_ENOMEM, NULL); ++ end_query(channel, server, query, ARES_ENOMEM, NULL, NULL); + return ARES_ENOMEM; + /* LCOV_EXCL_STOP */ + } +@@ -1407,7 +1453,7 @@ static void ares_detach_query(ares_query_t *query) + + static void end_query(ares_channel_t *channel, ares_server_t *server, + ares_query_t *query, ares_status_t status, +- const ares_dns_record_t *dnsrec) ++ ares_dns_record_t *dnsrec, ares_array_t **requeue) + { + /* If we were probing for the server to come back online, lets mark it as + * no longer being probed */ +@@ -1417,6 +1463,12 @@ static void end_query(ares_channel_t *channel, ares_server_t *server, + + ares_metrics_record(query, server, status, dnsrec); + ++ /* Delay calling the query callback */ ++ if (requeue != NULL) { ++ ares_append_endqueue(requeue, query, status, dnsrec); ++ return; ++ } ++ + /* Invoke the callback. */ + query->callback(query->arg, status, query->timeouts, dnsrec); + ares_free_query(query); +diff --git a/src/lib/ares_qcache.c b/src/lib/ares_qcache.c +index 97c0a91..4050ff5 100644 +--- a/src/lib/ares_qcache.c ++++ b/src/lib/ares_qcache.c +@@ -421,10 +421,20 @@ done: + return status; + } + +-ares_status_t ares_qcache_insert(ares_channel_t *channel, +- const ares_timeval_t *now, +- const ares_query_t *query, +- ares_dns_record_t *dnsrec) ++ares_status_t ares_qcache_insert(ares_channel_t *channel, ++ const ares_timeval_t *now, ++ const ares_query_t *query, ++ const ares_dns_record_t *dnsrec) + { +- return ares_qcache_insert_int(channel->qcache, dnsrec, query->query, now); ++ ares_dns_record_t *dupdns = ares_dns_record_duplicate(dnsrec); ++ ares_status_t status; ++ ++ if (dupdns == NULL) { ++ return ARES_ENOMEM; ++ } ++ status = ares_qcache_insert_int(channel->qcache, dupdns, query->query, now); ++ if (status != ARES_SUCCESS) { ++ ares_dns_record_destroy(dupdns); ++ } ++ return status; + } +diff --git a/test/ares-test-mock-ai.cc b/test/ares-test-mock-ai.cc +index 812976c..ae45c45 100644 +--- a/test/ares-test-mock-ai.cc ++++ b/test/ares-test-mock-ai.cc +@@ -805,7 +805,39 @@ TEST_P(MockUDPChannelTestAI, TriggerResendThenConnFailEDNS) { + EXPECT_THAT(result.ai_, IncludesV6Address("2121:0000:0000:0000:0000:0000:0000:0303")); + } + ++TEST_P(MockUDPChannelTestAI, ConnectionRefusedOnSearchDomainRetry) { ++ DNSPacket badrsp4; ++ badrsp4.set_response().set_aa() ++ .add_question(new DNSQuestion("www.google.com", T_A)) ++ .set_rcode(NXDOMAIN); ++ ++ EXPECT_CALL(server_, OnRequest("www.google.com", T_A)) ++ .WillOnce(SetReplyAndFailSend(&server_, &badrsp4)); ++ ++ DNSPacket goodrsp4; ++ goodrsp4.set_response().set_aa() ++ .add_question(new DNSQuestion("www.google.com.first.com", T_A)) ++ .add_answer(new DNSARR("www.google.com.first.com", 0x0100, {0x01, 0x02, 0x03, 0x04})); ++ ++ EXPECT_CALL(server_, OnRequest("www.google.com.first.com", T_A)) ++ .WillOnce(SetReply(&server_, &goodrsp4)); ++ ++ ares_socket_functions sock_funcs; ++ memset(&sock_funcs, 0, sizeof(sock_funcs)); + ++ sock_funcs.asendv = ares_sendv_fail; ++ ++ ares_set_socket_functions(channel_, &sock_funcs, NULL); ++ ++ AddrInfoResult result; ++ struct ares_addrinfo_hints hints = {0, 0, 0, 0}; ++ hints.ai_family = AF_INET; ++ hints.ai_flags = ARES_AI_NOSORT; ++ ares_getaddrinfo(channel_, "www.google.com", NULL, &hints, ++ AddrInfoCallback, &result); ++ ++ Process(); ++} + + class MockEDNSChannelTestAI : public MockFlagsChannelOptsTestAI { + public: diff -Nru c-ares-1.34.5/debian/patches/series c-ares-1.34.5/debian/patches/series --- c-ares-1.34.5/debian/patches/series 2024-08-02 19:06:55.000000000 +0000 +++ c-ares-1.34.5/debian/patches/series 2025-12-14 14:24:14.000000000 +0000 @@ -1,2 +1,3 @@ use-debian-googletest.diff remove-shields-from-markdown.diff +CVE-2025-62408.diff