]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
dns-answer: preserve shared aliases when removing records 43011/head
authorLuca Boccassi <luca.boccassi@gmail.com>
Mon, 13 Jul 2026 14:04:35 +0000 (15:04 +0100)
committerLuca Boccassi <luca.boccassi@gmail.com>
Tue, 14 Jul 2026 09:09:31 +0000 (10:09 +0100)
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
src/shared/dns-answer.c

index 2454ca1c06bb79cd6bb96d0dc933275df99fc1a9..aea1ebcc3c34742effe08abab833ff415f783ebe 100644 (file)
@@ -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;
index 24174d2bd2c07d39a462d269b0605b1018507cd1..037ad207effffcfa7781ad529c6e06eea5d554d0 100644 (file)
@@ -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 */