From 82044c2fe9198ef0974a46636505684c6bdeb7e3 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Mon, 13 Jul 2026 14:39:47 +0100 Subject: [PATCH 1/3] resolved: publish browsed service after initialization dns_add_new_service() links a _cleanup_ service into the browser before copying its record and registering its maintenance timer has fully succeeded. A timer setup failure then frees the service while leaving the list head pointing at it. Follow-up for 8458b7fb91ea5e5109b6f3c94f8a781a120c798b --- src/resolve/resolved-dns-browse-services.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/resolve/resolved-dns-browse-services.c b/src/resolve/resolved-dns-browse-services.c index 6a48089a59..8b5544c01a 100644 --- a/src/resolve/resolved-dns-browse-services.c +++ b/src/resolve/resolved-dns-browse-services.c @@ -178,8 +178,8 @@ int dns_add_new_service(DnsServiceBrowser *sb, DnsResourceRecord *rr, int owner_ .query = NULL, .rr_ttl_state = DNS_RECORD_TTL_STATE_80_PERCENT, }; - - LIST_PREPEND(dns_services, sb->dns_services, s); + if (!s->rr) + return log_oom(); /* Schedule the first cache maintenance query at 80% of the record's * TTL. Subsequent queries issued at 5% increments until 100% of the @@ -219,6 +219,8 @@ int dns_add_new_service(DnsServiceBrowser *sb, DnsResourceRecord *rr, int owner_ r, "Failed to schedule mDNS maintenance query for DNS service: %m"); + LIST_PREPEND(dns_services, sb->dns_services, s); + TAKE_PTR(s); return 0; } From 3734b69f030a0f7c9056ad094f3d97d7a561799c Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Mon, 13 Jul 2026 14:56:34 +0100 Subject: [PATCH 2/3] resolved: roll back partial DNS zone publication dns_zone_link_item() inserts a new item into the by-key hashmap before publishing it by name. If the second hashmap insertion fails, _cleanup_ frees the item while by-key retains a dangling key and value. Follow-up for 623a4c97b9175f95c4b1c6fc34e36c56f1e4ddbf --- src/resolve/resolved-dns-zone.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/resolve/resolved-dns-zone.c b/src/resolve/resolved-dns-zone.c index c005342958..48dce9d742 100644 --- a/src/resolve/resolved-dns-zone.c +++ b/src/resolve/resolved-dns-zone.c @@ -161,8 +161,15 @@ static int dns_zone_link_item(DnsZone *z, DnsZoneItem *i) { assert_se(hashmap_replace(z->by_name, dns_resource_key_name(first->rr->key), first) >= 0); } else { r = hashmap_put(z->by_name, dns_resource_key_name(i->rr->key), i); - if (r < 0) + if (r < 0) { + first = hashmap_get(z->by_key, i->rr->key); + LIST_REMOVE(by_key, first, i); + if (first) + assert_se(hashmap_replace(z->by_key, first->rr->key, first) >= 0); + else + hashmap_remove(z->by_key, i->rr->key); return r; + } } return 0; From e509a6a4cb9846c9337f60b019b3d29a2bc8f7a3 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Mon, 13 Jul 2026 15:04:35 +0100 Subject: [PATCH 3/3] dns-answer: preserve shared aliases when removing records dns_answer_remove_by_rr() mutats the backing OrderedSet even when the answer has multiple references. Callers such as trust-anchor revocation could thus change snapshots held elsewhere in the resolver. Clone a shared answer before removal. Follow-up for 71aee23dba7faeef68e7232f444626267a6c90d7 --- src/resolve/test-dns-answer.c | 17 +++++++++++++++++ src/shared/dns-answer.c | 16 +++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/resolve/test-dns-answer.c b/src/resolve/test-dns-answer.c index 2454ca1c06..aea1ebcc3c 100644 --- a/src/resolve/test-dns-answer.c +++ b/src/resolve/test-dns-answer.c @@ -371,6 +371,23 @@ TEST(dns_answer_remove_by_rr) { ASSERT_NULL(answer); } +TEST(dns_answer_remove_shared) { + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = prepare_answer(), *alias = NULL; + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + + alias = dns_answer_ref(answer); + ASSERT_PTR_EQ(alias, answer); + + rr = ASSERT_NOT_NULL(dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_A, "b.example.com")); + rr->a.in_addr.s_addr = htobe32(0xc0a8017f); + + ASSERT_OK_POSITIVE(dns_answer_remove_by_rr(&answer, rr)); + ASSERT_EQ(dns_answer_size(answer), 2u); + ASSERT_EQ(dns_answer_size(alias), 3u); + ASSERT_FALSE(dns_answer_contains(answer, rr)); + ASSERT_TRUE(dns_answer_contains(alias, rr)); +} + TEST(dns_answer_remove_by_answer_keys) { _cleanup_(dns_answer_unrefp) DnsAnswer *a = prepare_answer(), *b = prepare_answer(); DnsResourceKey *key; diff --git a/src/shared/dns-answer.c b/src/shared/dns-answer.c index 24174d2bd2..037ad207ef 100644 --- a/src/shared/dns-answer.c +++ b/src/shared/dns-answer.c @@ -555,14 +555,28 @@ int dns_answer_remove_by_rr(DnsAnswer **a, DnsResourceRecord *rr) { if (r < 0) return r; if (r > 0) { - dns_answer_item_unref(ordered_set_remove((*a)->items, item)); found = true; + break; } } if (!found) return 0; + if ((*a)->n_ref > 1) { + r = dns_answer_reserve_or_clone(a, 1); + if (r < 0) + return r; + } + + DNS_ANSWER_FOREACH_ITEM(item, *a) { + r = dns_resource_record_equal(item->rr, rr); + if (r < 0) + return r; + if (r > 0) + dns_answer_item_unref(ordered_set_remove((*a)->items, item)); + } + if (dns_answer_isempty(*a)) *a = dns_answer_unref(*a); /* Return NULL for the empty answer */