From 218ac81f85d58b200c5a870fd61aa3b4f661976c Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 24 Jun 2026 13:54:05 +0100 Subject: [PATCH 1/4] resolved: avoid dangling hashmap entry on RegisterService failure bus_method_register_service() inserted the DnssdRegisteredService into m->dnssd_registered_services before assigning service->manager and before the sd_bus_track_new()/sd_bus_track_add_sender() calls, so if either failed, the destructor ran with service->manager still NULL, so its guarded hashmap_remove() was skipped and the freed service was left in the hashmap. --- src/resolve/resolved-bus.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/resolve/resolved-bus.c b/src/resolve/resolved-bus.c index e04caa7898..53092bd6d3 100644 --- a/src/resolve/resolved-bus.c +++ b/src/resolve/resolved-bus.c @@ -2055,10 +2055,6 @@ static int bus_method_register_service(sd_bus_message *message, void *userdata, if (r == 0) return 1; /* Polkit will call us back */ - r = hashmap_ensure_put(&m->dnssd_registered_services, &string_hash_ops, service->id, service); - if (r < 0) - return r; - r = sd_bus_track_new(sd_bus_message_get_bus(message), &bus_track, dnssd_registered_service_on_bus_track, service); if (r < 0) return r; @@ -2069,6 +2065,10 @@ static int bus_method_register_service(sd_bus_message *message, void *userdata, service->manager = m; + r = hashmap_ensure_put(&m->dnssd_registered_services, &string_hash_ops, service->id, service); + if (r < 0) + return r; + service = NULL; manager_refresh_rrs(m); From a6e2bfe533a80e3ddc03312fd8c2d87614f7a02f Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 24 Jun 2026 13:43:14 +0100 Subject: [PATCH 2/4] resolved: fix potential use-after-free when freeing DNS extra stub listeners dns_stub_listener_extra_free() frees the listener while DnsQuery and DnsStream objects still keep pointers to it. On a reload the extra listeners are freed before dns_stream_disconnect_all() and dns_query_free() run, and dns_query_free() then dereferences those pointers. --- src/resolve/resolved-dns-stub.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/resolve/resolved-dns-stub.c b/src/resolve/resolved-dns-stub.c index 91ea30e0e2..4a4c3f0015 100644 --- a/src/resolve/resolved-dns-stub.c +++ b/src/resolve/resolved-dns-stub.c @@ -102,6 +102,19 @@ DnsStubListenerExtra *dns_stub_listener_extra_free(DnsStubListenerExtra *p) { p->udp_event_source = sd_event_source_disable_unref(p->udp_event_source); p->tcp_event_source = sd_event_source_disable_unref(p->tcp_event_source); + assert(p->manager); + + /* Detach the raw back-pointers that DnsStream and DnsQuery objects keep to this listener before we + * free it. Otherwise a later dns_stream_complete()/dns_query_free() during the same reload would + * dereference the freed slab: dns_query_free() reads q->stub_listener_extra->queries_by_packet. */ + LIST_FOREACH(streams, s, p->manager->dns_streams) + if (s->stub_listener_extra == p) + s->stub_listener_extra = NULL; + + LIST_FOREACH(queries, q, p->manager->dns_queries) + if (q->stub_listener_extra == p) + q->stub_listener_extra = NULL; + hashmap_free(p->queries_by_packet); return mfree(p); From a762c92a7c68e94a131209e9a01b2c51d82416b2 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 24 Jun 2026 13:53:07 +0100 Subject: [PATCH 3/4] sd-lldp-rx: keep object ref around event callbacks If the user callback set via sd_lldp_rx_set_callback() drops the last reference to the sd_lldp_rx object, trying to use it later does not go well. Take a ref to keep the objects alive as long as they are needed. --- src/libsystemd-network/sd-lldp-rx.c | 6 +++++ src/libsystemd-network/test-lldp-rx.c | 39 +++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/src/libsystemd-network/sd-lldp-rx.c b/src/libsystemd-network/sd-lldp-rx.c index 288ed2ee96..30c616448b 100644 --- a/src/libsystemd-network/sd-lldp-rx.c +++ b/src/libsystemd-network/sd-lldp-rx.c @@ -199,6 +199,9 @@ static int lldp_rx_receive_datagram(sd_event_source *s, int fd, uint32_t revents sd_lldp_rx *lldp_rx = ASSERT_PTR(userdata); struct timespec ts; + /* Keep ref in case the callback drops the last reference, so we can use it below */ + _unused_ _cleanup_(sd_lldp_rx_unrefp) sd_lldp_rx *ref = sd_lldp_rx_ref(lldp_rx); + assert(fd >= 0); space = next_datagram_size_fd(fd); @@ -421,6 +424,9 @@ static int on_timer_event(sd_event_source *s, uint64_t usec, void *userdata) { sd_lldp_rx *lldp_rx = userdata; int r; + /* Keep ref in case the callback drops the last reference, so we can use it below */ + _unused_ _cleanup_(sd_lldp_rx_unrefp) sd_lldp_rx *ref = sd_lldp_rx_ref(lldp_rx); + r = lldp_rx_make_space(lldp_rx, 0); if (r < 0) { log_lldp_rx_errno(lldp_rx, r, "Failed to make space, ignoring: %m"); diff --git a/src/libsystemd-network/test-lldp-rx.c b/src/libsystemd-network/test-lldp-rx.c index 09c916db51..75a40871a5 100644 --- a/src/libsystemd-network/test-lldp-rx.c +++ b/src/libsystemd-network/test-lldp-rx.c @@ -417,6 +417,44 @@ static void test_receive_oui_vlanid_packet(sd_event *e) { ASSERT_OK(stop_lldp_rx(lldp_rx)); } +static int reentrant_unref_calls; + +static void lldp_rx_reentrant_unref_handler(sd_lldp_rx *lldp_rx, sd_lldp_rx_event_t event, sd_lldp_neighbor *n, void *userdata) { + /* Drop the last external reference from inside the callback. The event-source callback holds a + * transient self-ref, so lldp_rx must survive until the dispatch stack unwinds. */ + reentrant_unref_calls++; + sd_lldp_rx_unref(lldp_rx); +} + +static void test_receive_reentrant_unref(sd_event *e) { + /* Regression: a user callback dropping the last lldp_rx reference used to free the object while + * inner library frames still dereferenced it (use-after-free). The event-source callbacks now take + * a transient self-ref, so teardown is deferred until the dispatch stack unwinds. */ + static const uint8_t frame[] = { + /* Ethernet header */ + 0x01, 0x80, 0xc2, 0x00, 0x00, 0x03, /* Destination MAC */ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, /* Source MAC */ + 0x88, 0xcc, /* Ethertype */ + /* LLDP mandatory TLVs */ + 0x02, 0x07, 0x04, 0x00, 0x01, 0x02, /* Chassis: MAC, 00:01:02:03:04:05 */ + 0x03, 0x04, 0x05, + 0x04, 0x04, 0x05, 0x31, 0x2f, 0x33, /* Port: interface name, "1/3" */ + 0x06, 0x02, 0x00, 0x78, /* TTL: 120 seconds */ + 0x00, 0x00 /* End Of LLDPDU */ + }; + sd_lldp_rx *lldp_rx; + + reentrant_unref_calls = 0; + assert_se(start_lldp_rx(&lldp_rx, e, lldp_rx_reentrant_unref_handler, NULL) == 0); + + assert_se(write(test_fd[1], frame, sizeof(frame)) == sizeof(frame)); + sd_event_run(e, 0); + assert_se(reentrant_unref_calls == 1); + + /* lldp_rx was freed by the callback's unref once the dispatch stack unwound; do not touch it. */ + safe_close(test_fd[1]); +} + int main(int argc, char *argv[]) { _cleanup_(sd_event_unrefp) sd_event *e = NULL; @@ -429,6 +467,7 @@ int main(int argc, char *argv[]) { test_receive_oui_packet(e); test_multiple_neighbors_sorted(e); test_receive_oui_vlanid_packet(e); + test_receive_reentrant_unref(e); return 0; } From f98629421b673f5d4cb8206a9dc14b6f1855a9f3 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 24 Jun 2026 13:20:56 +0100 Subject: [PATCH 4/4] dhcp6: reject IA_PD_PREFIX with invalid prefix length dhcp6_option_parse_ia_pdprefix() validates the lifetimes but never the prefixlen byte, so a delegated prefix with prefixlen == 0 or > 128 is stored in the lease and handed over. RFC 8415 defines the prefix length as 1 to 128, and the send-side option_append_pd_prefix() already rejects 0, so reject the out-of-range values on the receive path too. Follow-up for f8ad4dd45d761d839de49ddff9d7fbf46892c148 --- src/libsystemd-network/dhcp6-option.c | 7 +++++++ src/libsystemd-network/test-dhcp6-client.c | 19 +++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/libsystemd-network/dhcp6-option.c b/src/libsystemd-network/dhcp6-option.c index d62fd70588..1c6b29b8cb 100644 --- a/src/libsystemd-network/dhcp6-option.c +++ b/src/libsystemd-network/dhcp6-option.c @@ -672,6 +672,13 @@ static int dhcp6_option_parse_ia_pdprefix(sd_dhcp6_client *client, DHCP6IA *ia, FORMAT_TIMESPAN(lt_pref, USEC_PER_SEC), FORMAT_TIMESPAN(lt_valid, USEC_PER_SEC)); + /* RFC 8415 defines the prefix length as 1…128; reject the out-of-range values the sender-side + * counterpart option_append_pd_prefix() also refuses. */ + if (a->iapdprefix.prefixlen == 0 || a->iapdprefix.prefixlen > 128) + return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), + "Received a PD prefix with invalid prefix length %u, ignoring.", + a->iapdprefix.prefixlen); + if (len > sizeof(struct iapdprefix)) { r = dhcp6_option_parse_ia_options(client, data + sizeof(struct iapdprefix), len - sizeof(struct iapdprefix)); if (r < 0) diff --git a/src/libsystemd-network/test-dhcp6-client.c b/src/libsystemd-network/test-dhcp6-client.c index f2b6ce8598..08dda7ce18 100644 --- a/src/libsystemd-network/test-dhcp6-client.c +++ b/src/libsystemd-network/test-dhcp6-client.c @@ -324,6 +324,17 @@ TEST(option_status) { /* PD prefix status option */ 0x00, 0x0d, 0x00, 0x02, 0x00, 0x00, }; + static const uint8_t option6[] = { + /* IA PD */ + 0x00, 0x19, 0x00, 0x29, 0x1a, 0x1d, 0x1a, 0x1d, + 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02, + /* IA PD Prefix, with an invalid prefix length of 0 */ + 0x00, 0x1a, 0x00, 0x19, + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x00, 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, + }; _cleanup_(dhcp6_ia_freep) DHCP6IA *ia = NULL; DHCP6Option *option; be32_t iaid; @@ -379,6 +390,14 @@ TEST(option_status) { assert_se(ia); assert_se(ia->addresses); ia = dhcp6_ia_free(ia); + + /* An IA_PD whose only prefix carries an invalid (zero) prefix length must be refused, leaving no + * valid prefix behind. */ + option = (DHCP6Option*) option6; + assert_se(sizeof(option6) == sizeof(DHCP6Option) + be16toh(option->len)); + r = dhcp6_option_parse_ia(NULL, iaid, be16toh(option->code), be16toh(option->len), option->data, &ia); + assert_se(r == -ENODATA); + assert_se(!ia); } TEST(client_parse_message_issue_22099) {