From 4309f68236d8b5d77d2e355783b3c10a4b68533e Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 8 Jul 2026 13:07:44 +0100 Subject: [PATCH 1/5] efivars: fix concurrent growth read accounting efi_get_variable() allocates one byte for probing whether efivarfs has grown since fstat(), then three more bytes for NUL termination. Account for both sizes separately so a full readv() result is treated as concurrent growth and retried before the terminators are written. Follow-up for ab69a04600fd34c152c44be6864eb3bc64568e17 --- src/basic/efivars.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/basic/efivars.c b/src/basic/efivars.c index 0eeee1038ef..b64b83664a8 100644 --- a/src/basic/efivars.c +++ b/src/basic/efivars.c @@ -87,14 +87,21 @@ int efi_get_variable( } /* We want +1 for the read call, and +3 for the additional terminating bytes added below. */ + size_t file_size = (size_t) st.st_size, payload_size, read_size, alloc_size, read_limit; + if (!SUB_SAFE(&payload_size, file_size, sizeof(attr)) || + !ADD_SAFE(&read_size, payload_size, 1) || + !ADD_SAFE(&alloc_size, read_size, 3) || + !ADD_SAFE(&read_limit, file_size, 1)) + return log_debug_errno(SYNTHETIC_ERRNO(EOVERFLOW), "EFI variable '%s' size calculation overflow, refusing.", p); + free(buf); - buf = malloc((size_t) st.st_size - sizeof(attr) + CONST_MAX(1, 3)); + buf = malloc(alloc_size); if (!buf) return -ENOMEM; struct iovec iov[] = { { &attr, sizeof(attr) }, - { buf, (size_t) st.st_size - sizeof(attr) + 1 }, + { buf, read_size }, }; n = readv(fd, iov, 2); @@ -103,11 +110,11 @@ int efi_get_variable( return log_debug_errno(errno, "Reading from '%s' failed: %m", p); log_debug("Reading from '%s' failed with EINTR, retrying.", p); - } else if ((size_t) n == sizeof(attr) + st.st_size + 1) + } else if ((size_t) n == read_limit) /* We need to try again with a bigger buffer, the variable was apparently changed concurrently? */ log_debug("EFI variable '%s' larger than expected, retrying.", p); else { - assert((size_t) n < sizeof(attr) + st.st_size + 1); + assert((size_t) n < read_limit); break; } From ad0996191d247a10ce37ff251d3d520aa9ea267c Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 8 Jul 2026 13:53:52 +0100 Subject: [PATCH 2/5] pull-oci: verify redirected manifest digest An OCI index redirect already carries the digest of the selected manifest. Store it in the expected checksum field so pull-job verifies the downloaded manifest instead of overwriting the digest with the computed checksum before comparison. Follow-up for a9f6ba04969d6eb2e629e30299fab7538ef42a57 --- src/import/pull-oci.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/import/pull-oci.c b/src/import/pull-oci.c index 90dfee9ed4a..c2d9bfeddf4 100644 --- a/src/import/pull-oci.c +++ b/src/import/pull-oci.c @@ -366,7 +366,7 @@ static int oci_pull_redirect_manifest(OciPull *i, const OciIndexEntry *entry) { j->on_finished = oci_pull_job_on_finished_manifest; j->calc_checksum = true; - if (!iovec_memdup(&entry->digest, &j->checksum)) + if (!iovec_memdup(&entry->digest, &j->expected_checksum)) return -ENOMEM; j->description = strjoin("Image Manifest (", url, ")"); From 8baed402984ddadfd51ca521f5759ea914f51370 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 8 Jul 2026 14:22:41 +0100 Subject: [PATCH 3/5] ndisc: reject non-zero ICMPv6 codes in parsers NDisc packets received through the socket path are filtered before parser dispatch, but parser entry points should still reject malformed packet bytes instead of asserting on them. Return EBADMSG for non-zero ICMPv6 code values in RA, NA, and Redirect messages. Follow-up for c34cb1d6451dd9fcd36e1c08c553ca7f25e9d83b Follow-up for 696eb2b8de980a2b79c1de7fbf12195936b00441 Follow-up for 44e8cf303b1e54752637725d55d01811e05ed482 --- src/libsystemd-network/sd-ndisc-neighbor.c | 4 +- src/libsystemd-network/sd-ndisc-redirect.c | 4 +- src/libsystemd-network/sd-ndisc-router.c | 4 +- src/libsystemd-network/test-ndisc-rs.c | 56 ++++++++++++++++++++++ 4 files changed, 65 insertions(+), 3 deletions(-) diff --git a/src/libsystemd-network/sd-ndisc-neighbor.c b/src/libsystemd-network/sd-ndisc-neighbor.c index 5ce1da06da3..a5de33db899 100644 --- a/src/libsystemd-network/sd-ndisc-neighbor.c +++ b/src/libsystemd-network/sd-ndisc-neighbor.c @@ -52,7 +52,9 @@ int ndisc_neighbor_parse(sd_ndisc *nd, sd_ndisc_neighbor *na) { /* Neighbor advertisement packets are neatly aligned to 64-bit boundaries, hence we can access them directly */ const struct nd_neighbor_advert *a = (const struct nd_neighbor_advert*) na->packet->raw_packet; assert(a->nd_na_type == ND_NEIGHBOR_ADVERT); - assert(a->nd_na_code == 0); + if (a->nd_na_code != 0) + return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG), + "Received Neighbor Advertisement with non-zero code, ignoring datagram."); na->flags = a->nd_na_flags_reserved; /* the first 3 bits */ na->target_address = a->nd_na_target; diff --git a/src/libsystemd-network/sd-ndisc-redirect.c b/src/libsystemd-network/sd-ndisc-redirect.c index 4845adbd7bb..fcfefc5ca5b 100644 --- a/src/libsystemd-network/sd-ndisc-redirect.c +++ b/src/libsystemd-network/sd-ndisc-redirect.c @@ -52,7 +52,9 @@ int ndisc_redirect_parse(sd_ndisc *nd, sd_ndisc_redirect *rd) { const struct nd_redirect *a = (const struct nd_redirect*) rd->packet->raw_packet; assert(a->nd_rd_type == ND_REDIRECT); - assert(a->nd_rd_code == 0); + if (a->nd_rd_code != 0) + return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG), + "Received Redirect message with non-zero code, ignoring datagram."); rd->target_address = a->nd_rd_target; rd->destination_address = a->nd_rd_dst; diff --git a/src/libsystemd-network/sd-ndisc-router.c b/src/libsystemd-network/sd-ndisc-router.c index 9d6181b3554..fcce89cc9b7 100644 --- a/src/libsystemd-network/sd-ndisc-router.c +++ b/src/libsystemd-network/sd-ndisc-router.c @@ -108,7 +108,9 @@ int ndisc_router_parse(sd_ndisc *nd, sd_ndisc_router *rt) { a = (const struct nd_router_advert*) rt->packet->raw_packet; assert(a->nd_ra_type == ND_ROUTER_ADVERT); - assert(a->nd_ra_code == 0); + if (a->nd_ra_code != 0) + return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG), + "Received Router Advertisement with non-zero code, ignoring."); rt->hop_limit = a->nd_ra_curhoplimit; rt->flags = a->nd_ra_flags_reserved; /* the first 8 bits */ diff --git a/src/libsystemd-network/test-ndisc-rs.c b/src/libsystemd-network/test-ndisc-rs.c index 9c1e6f03a4c..86b7c07bdbf 100644 --- a/src/libsystemd-network/test-ndisc-rs.c +++ b/src/libsystemd-network/test-ndisc-rs.c @@ -15,6 +15,9 @@ #include "icmp6-test-util.h" #include "in-addr-util.h" #include "ndisc-internal.h" +#include "ndisc-neighbor-internal.h" +#include "ndisc-redirect-internal.h" +#include "ndisc-router-internal.h" #include "strv.h" #include "tests.h" @@ -22,6 +25,59 @@ static struct ether_addr mac_addr = { .ether_addr_octet = {'A', 'B', 'C', '1', '2', '3'} }; +static ICMP6Packet* make_packet(const void *raw, size_t size) { + ICMP6Packet *packet; + + assert(raw); + + packet = malloc0(offsetof(ICMP6Packet, raw_packet) + size); + ASSERT_NOT_NULL(packet); + + packet->n_ref = 1; + packet->raw_size = size; + memcpy(packet->raw_packet, raw, size); + + return packet; +} + +TEST(parse_rejects_nonzero_code) { + static const struct nd_router_advert router_advertisement = { + .nd_ra_type = ND_ROUTER_ADVERT, + .nd_ra_code = 1, + }; + static const struct nd_neighbor_advert neighbor_advertisement = { + .nd_na_type = ND_NEIGHBOR_ADVERT, + .nd_na_code = 1, + }; + static const struct nd_redirect redirect = { + .nd_rd_type = ND_REDIRECT, + .nd_rd_code = 1, + }; + + _cleanup_(icmp6_packet_unrefp) ICMP6Packet *packet = + make_packet(&router_advertisement, sizeof(router_advertisement)); + _cleanup_(sd_ndisc_router_unrefp) sd_ndisc_router *router = + ASSERT_PTR(ndisc_router_new(packet)); + + ASSERT_ERROR(ndisc_router_parse(/* nd= */ NULL, router), EBADMSG); + router = sd_ndisc_router_unref(router); + packet = icmp6_packet_unref(packet); + + packet = make_packet(&neighbor_advertisement, sizeof(neighbor_advertisement)); + _cleanup_(sd_ndisc_neighbor_unrefp) sd_ndisc_neighbor *neighbor = + ASSERT_PTR(ndisc_neighbor_new(packet)); + + ASSERT_ERROR(ndisc_neighbor_parse(/* nd= */ NULL, neighbor), EBADMSG); + neighbor = sd_ndisc_neighbor_unref(neighbor); + packet = icmp6_packet_unref(packet); + + packet = make_packet(&redirect, sizeof(redirect)); + _cleanup_(sd_ndisc_redirect_unrefp) sd_ndisc_redirect *rd = + ASSERT_PTR(ndisc_redirect_new(packet)); + + ASSERT_ERROR(ndisc_redirect_parse(/* nd= */ NULL, rd), EBADMSG); +} + static void router_dump(sd_ndisc_router *rt) { struct in6_addr addr; uint8_t hop_limit; From 19c08f2dfa38e970fc95ad25258c9bfba7dbf7ad Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 8 Jul 2026 15:40:59 +0100 Subject: [PATCH 4/5] creds: tolerate TPM2 seal failure in auto mode Automatic key modes tolerate a failed TPM2 sealing attempt and fall back to a host or null key. Do not consume TPM2 blob output in that case, tpm2_seal() leaves it empty on failure, so the fallback path should continue without TPM2 metadata. Follow-up for 9e4379945b74ee7920fe375be0bcb04d8ef53873 --- src/shared/creds-util.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/shared/creds-util.c b/src/shared/creds-util.c index 58207d4d002..09bbf6bca89 100644 --- a/src/shared/creds-util.c +++ b/src/shared/creds-util.c @@ -1032,17 +1032,17 @@ int encrypt_credential_and_warn( return log_error_errno(r, "Failed to seal to TPM2: %m"); log_notice_errno(r, "TPM2 sealing didn't work, continuing without TPM2: %m"); + } else { + if (!iovec_memdup(&IOVEC_MAKE(tpm2_policy.buffer, tpm2_policy.size), &tpm2_policy_hash)) + return log_oom(); + + assert(n_blobs == 1); + tpm2_blob = TAKE_STRUCT(blobs[0]); + + assert(tpm2_blob.iov_len <= CREDENTIAL_FIELD_SIZE_MAX); + assert(tpm2_policy_hash.iov_len <= CREDENTIAL_FIELD_SIZE_MAX); + assert(tpm2_srk.iov_len <= CREDENTIAL_FIELD_SIZE_MAX); } - - if (!iovec_memdup(&IOVEC_MAKE(tpm2_policy.buffer, tpm2_policy.size), &tpm2_policy_hash)) - return log_oom(); - - assert(n_blobs == 1); - tpm2_blob = TAKE_STRUCT(blobs[0]); - - assert(tpm2_blob.iov_len <= CREDENTIAL_FIELD_SIZE_MAX); - assert(tpm2_policy_hash.iov_len <= CREDENTIAL_FIELD_SIZE_MAX); - assert(tpm2_srk.iov_len <= CREDENTIAL_FIELD_SIZE_MAX); } #endif From 2d85bba6b9ddd20b74992c740fab7f82c9c03f01 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 8 Jul 2026 15:58:45 +0100 Subject: [PATCH 5/5] nss-myhostname: keep IPv6 probe result stable Cache the IPv6 enabled state while sizing and filling NSS result buffers, so a transient sysctl read result cannot change the tuple or address layout after the scratch buffer size has been computed. Also zero-initialize gaih_addrtuple records before filling IPv4 addresses. Follow-up for e8a7a315391a6a07897122725cd707f4e9ce63d7 --- src/nss-myhostname/nss-myhostname.c | 60 +++++++++++++++++++---------- src/test/test-nss-hosts.c | 16 ++++++++ 2 files changed, 55 insertions(+), 21 deletions(-) diff --git a/src/nss-myhostname/nss-myhostname.c b/src/nss-myhostname/nss-myhostname.c index 83d968ff0b5..d902d305a7a 100644 --- a/src/nss-myhostname/nss-myhostname.c +++ b/src/nss-myhostname/nss-myhostname.c @@ -40,6 +40,7 @@ enum nss_status _nss_myhostname_gethostbyname4_r( const char *canonical = NULL; int n_addresses = 0; uint32_t local_address_ipv4 = 0; + bool ipv6_enabled; size_t l, idx, ms; char *r_name; @@ -96,7 +97,9 @@ enum nss_status _nss_myhostname_gethostbyname4_r( } l = strlen(canonical); - ms = ALIGN(l+1) + ALIGN(sizeof(struct gaih_addrtuple)) * (n_addresses > 0 ? n_addresses : 1 + socket_ipv6_is_enabled()); + ipv6_enabled = n_addresses == 0 && socket_ipv6_is_enabled(); + ms = ALIGN(l+1) + + ALIGN(sizeof(struct gaih_addrtuple)) * (n_addresses > 0 ? n_addresses : 1 + ipv6_enabled); if (buflen < ms) { UNPROTECT_ERRNO; *errnop = ERANGE; @@ -112,13 +115,14 @@ enum nss_status _nss_myhostname_gethostbyname4_r( assert(n_addresses >= 0); if (n_addresses == 0) { /* Second, fill in IPv6 tuple */ - if (socket_ipv6_is_enabled()) { + if (ipv6_enabled) { r_tuple = (struct gaih_addrtuple*) (buffer + idx); - r_tuple->next = r_tuple_prev; - r_tuple->name = r_name; - r_tuple->family = AF_INET6; + *r_tuple = (struct gaih_addrtuple) { + .next = r_tuple_prev, + .name = r_name, + .family = AF_INET6, + }; memcpy(r_tuple->addr, LOCALADDRESS_IPV6, FAMILY_ADDRESS_SIZE(AF_INET6)); - r_tuple->scopeid = 0; idx += ALIGN(sizeof(struct gaih_addrtuple)); r_tuple_prev = r_tuple; @@ -126,11 +130,12 @@ enum nss_status _nss_myhostname_gethostbyname4_r( /* Third, fill in IPv4 tuple */ r_tuple = (struct gaih_addrtuple*) (buffer + idx); - r_tuple->next = r_tuple_prev; - r_tuple->name = r_name; - r_tuple->family = AF_INET; - *(uint32_t*) r_tuple->addr = local_address_ipv4; - r_tuple->scopeid = 0; + *r_tuple = (struct gaih_addrtuple) { + .next = r_tuple_prev, + .name = r_name, + .family = AF_INET, + }; + memcpy(r_tuple->addr, &local_address_ipv4, FAMILY_ADDRESS_SIZE(AF_INET)); idx += ALIGN(sizeof(struct gaih_addrtuple)); r_tuple_prev = r_tuple; @@ -139,12 +144,18 @@ enum nss_status _nss_myhostname_gethostbyname4_r( /* Fourth, fill actual addresses in, but in backwards order */ for (int i = n_addresses; i > 0; i--) { struct local_address *a = addresses + i - 1; + uint32_t scopeid = 0; + + if (a->family == AF_INET6 && in6_addr_is_link_local(&a->address.in6)) + scopeid = a->ifindex; r_tuple = (struct gaih_addrtuple*) (buffer + idx); - r_tuple->next = r_tuple_prev; - r_tuple->name = r_name; - r_tuple->family = a->family; - r_tuple->scopeid = a->family == AF_INET6 && in6_addr_is_link_local(&a->address.in6) ? a->ifindex : 0; + *r_tuple = (struct gaih_addrtuple) { + .next = r_tuple_prev, + .name = r_name, + .family = a->family, + .scopeid = scopeid, + }; memcpy(r_tuple->addr, &a->address, FAMILY_ADDRESS_SIZE(a->family)); idx += ALIGN(sizeof(struct gaih_addrtuple)); @@ -180,6 +191,7 @@ static enum nss_status fill_in_hostent( int af, struct local_address *addresses, unsigned n_addresses, uint32_t local_address_ipv4, + bool ipv6_enabled, struct hostent *result, char *buffer, size_t buflen, int *errnop, int *h_errnop, @@ -212,8 +224,8 @@ static enum nss_status fill_in_hostent( (additional ? ALIGN(l_additional+1) : 0) + sizeof(char*) + (additional ? sizeof(char*) : 0) + - (c > 0 ? c : af == AF_INET ? 1 : socket_ipv6_is_enabled()) * ALIGN(alen) + - (c > 0 ? c+1 : af == AF_INET ? 2 : (unsigned) socket_ipv6_is_enabled() + 1) * sizeof(char*); + (c > 0 ? c : af == AF_INET ? 1 : ipv6_enabled) * ALIGN(alen) + + (c > 0 ? c+1 : af == AF_INET ? 2 : (unsigned) ipv6_enabled + 1) * sizeof(char*); if (buflen < ms) { UNPROTECT_ERRNO; @@ -263,7 +275,7 @@ static enum nss_status fill_in_hostent( } else if (af == AF_INET) { *(uint32_t*) r_addr = local_address_ipv4; assert_se(INC_SAFE(&idx, ALIGN(alen))); - } else if (socket_ipv6_is_enabled()) { + } else if (ipv6_enabled) { memcpy(r_addr, LOCALADDRESS_IPV6, FAMILY_ADDRESS_SIZE(AF_INET6)); assert_se(INC_SAFE(&idx, ALIGN(alen))); } @@ -279,7 +291,7 @@ static enum nss_status fill_in_hostent( ((char**) r_addr_list)[i] = NULL; assert_se(INC_SAFE(&idx, (c+1) * sizeof(char*))); - } else if (af == AF_INET || socket_ipv6_is_enabled()) { + } else if (af == AF_INET || ipv6_enabled) { ((char**) r_addr_list)[0] = r_addr; ((char**) r_addr_list)[1] = NULL; assert_se(INC_SAFE(&idx, 2 * sizeof(char*))); @@ -324,6 +336,7 @@ enum nss_status _nss_myhostname_gethostbyname3_r( const char *canonical, *additional = NULL; _cleanup_free_ char *hn = NULL; uint32_t local_address_ipv4 = 0; + bool ipv6_enabled; int n_addresses = 0; PROTECT_ERRNO; @@ -345,7 +358,8 @@ enum nss_status _nss_myhostname_gethostbyname3_r( return NSS_STATUS_UNAVAIL; } - if (af == AF_INET6 && !socket_ipv6_is_enabled()) + ipv6_enabled = af == AF_INET6 && socket_ipv6_is_enabled(); + if (af == AF_INET6 && !ipv6_enabled) goto not_found; if (is_localhost(name)) { @@ -397,6 +411,7 @@ enum nss_status _nss_myhostname_gethostbyname3_r( af, addresses, n_addresses, local_address_ipv4, + ipv6_enabled, host, buffer, buflen, errnop, h_errnop, @@ -423,6 +438,7 @@ enum nss_status _nss_myhostname_gethostbyaddr2_r( int n_addresses = 0; struct local_address *a; bool additional_from_hostname = false; + bool ipv6_enabled = false; unsigned n; PROTECT_ERRNO; @@ -461,7 +477,8 @@ enum nss_status _nss_myhostname_gethostbyaddr2_r( } else { assert(af == AF_INET6); - if (!socket_ipv6_is_enabled()) + ipv6_enabled = socket_ipv6_is_enabled(); + if (!ipv6_enabled) goto not_found; if (memcmp(addr, LOCALADDRESS_IPV6, FAMILY_ADDRESS_SIZE(AF_INET6)) == 0) { @@ -511,6 +528,7 @@ found: af, addresses, n_addresses, local_address_ipv4, + ipv6_enabled, host, buffer, buflen, errnop, h_errnop, diff --git a/src/test/test-nss-hosts.c b/src/test/test-nss-hosts.c index 20d151a4254..61f9b153f8c 100644 --- a/src/test/test-nss-hosts.c +++ b/src/test/test-nss-hosts.c @@ -15,6 +15,7 @@ #include "local-addresses.h" #include "log.h" #include "main-func.h" +#include "memory-util.h" #include "nss-test-util.h" #include "nss-util.h" #include "parse-util.h" @@ -67,6 +68,16 @@ static int print_gaih_addrtuples(const struct gaih_addrtuple *tuples) { return n; } +static void assert_myhostname_gaih_addrtuples(const struct gaih_addrtuple *tuples) { + assert(tuples); + + for (const struct gaih_addrtuple *it = tuples; it; it = it->next) + if (it->family == AF_INET) + assert_se(memeqzero( + (const uint8_t*) it->addr + FAMILY_ADDRESS_SIZE(AF_INET), + sizeof(it->addr) - FAMILY_ADDRESS_SIZE(AF_INET))); +} + static void print_struct_hostent(struct hostent *host, const char *canon) { log_info(" \"%s\"", host->h_name); STRV_FOREACH(s, host->h_aliases) @@ -112,6 +123,9 @@ static void test_gethostbyname4_r(void *handle, const char *module, const char * return; } + /* Poison the scratch buffer so uninitialized padding in NSS replies is observable. */ + memset(buffer, 0xa5, sizeof(buffer)); + status = f(name, &pat, buffer, sizeof buffer, &errno1, &errno2, &ttl); if (status == NSS_STATUS_SUCCESS) { log_info("%s(\"%s\") → status=%s%-20spat=buffer+0x%"PRIxPTR" errno=%d/%s h_errno=%d/%s ttl=%"PRIi32, @@ -122,6 +136,8 @@ static void test_gethostbyname4_r(void *handle, const char *module, const char * errno2, hstrerror(errno2), ttl); n = print_gaih_addrtuples(pat); + if (streq(module, "myhostname")) + assert_myhostname_gaih_addrtuples(pat); } else { log_info("%s(\"%s\") → status=%s%-20spat=0x%p errno=%d/%s h_errno=%d/%s", fname, name,