Package: release.debian.org Severity: normal Tags: trixie X-Debbugs-Cc: [email protected] Control: affects -1 + src:ovn User: [email protected] Usertags: pu
Hi, [ Reason ] I'd like to upload OVN to Trixie p-u to address CVE-2026-5265 and CVE-2026-5367. [ Impact ] - CVE-2026-5265: Heap Over-Read in ICMP Error Response Generation. - CVE-2026-5367: Heap over-read in OVN DHCPv6 Client ID processing. [ Tests ] Both patches (for each CVE) contain unit tests. [ Risks ] Patches aren't so big, should be ok, especially with the included tests. [ Checklist ] [x] *all* changes are documented in the d/changelog [x] I reviewed all changes and I approve them [x] attach debdiff against the package in (old)stable [x] the issue is verified as fixed in unstable [ Changes ] 2 patches, plus refresh of 2, no other change. Please allow me to uplaod ovn/25.03.0-1+deb13u1 to Trixie p-u. Cheers, Thomas Goirand (zigo)
diff -Nru ovn-25.03.0/debian/changelog ovn-25.03.0/debian/changelog --- ovn-25.03.0/debian/changelog 2025-03-31 17:35:50.000000000 +0200 +++ ovn-25.03.0/debian/changelog 2026-05-11 15:27:17.000000000 +0200 @@ -1,3 +1,14 @@ +ovn (25.03.0-1+deb13u1) trixie; urgency=medium + + * Fix security issues (Closes: #1134486): + - CVE-2026-5265: Heap Over-Read in ICMP Error Response Generation. Add + upstream patch: pinctrl: Limit the IP packet size to buffer size for + ICMP Need Frag. + - CVE-2026-5367: Heap over-read in OVN DHCPv6 Client ID processing. Add + upstream patch: Unify handling of DHCPv6 options. + + -- Thomas Goirand <[email protected]> Mon, 11 May 2026 15:27:17 +0200 + ovn (25.03.0-1) unstable; urgency=medium * Team upload. diff -Nru ovn-25.03.0/debian/patches/CVE-2026-5265_pinctrl_Limit_the_IP_packet_size_to_buffer_size_for_ICMP_Need_Frag.patch ovn-25.03.0/debian/patches/CVE-2026-5265_pinctrl_Limit_the_IP_packet_size_to_buffer_size_for_ICMP_Need_Frag.patch --- ovn-25.03.0/debian/patches/CVE-2026-5265_pinctrl_Limit_the_IP_packet_size_to_buffer_size_for_ICMP_Need_Frag.patch 1970-01-01 01:00:00.000000000 +0100 +++ ovn-25.03.0/debian/patches/CVE-2026-5265_pinctrl_Limit_the_IP_packet_size_to_buffer_size_for_ICMP_Need_Frag.patch 2026-05-11 15:27:17.000000000 +0200 @@ -0,0 +1,141 @@ +From 609ef5e7cc81c1d01eed1b0b743bc3f2b1ba92c8 Mon Sep 17 00:00:00 2001 +From: Ales Musil <[email protected]> +Date: Fri, 20 Mar 2026 10:13:02 +0100 +Subject: [PATCH] pinctrl: Limit the IP packet size to buffer size for ICMP + Need Frag. + +The ICMP need frag copies part of the IP packet, which is limited by +the space after ICMP header. However the packet size would be taken +from the IP header itself. That is problematic because we could +receive empty packet with the IP header packet size set to arbitrary +number. To prevent that limit the size to the buffer size so we will +never copy more than what is in the packet data. + +Fixes: c2339d87268d ("ovn: Add a new OVN action 'icmp4_error'") +Reported-by: Seiji Sakurai <[email protected]> +Co-authored-by: Seiji Sakurai <[email protected]> +Acked-by: Dumitru Ceara <[email protected]> +Signed-off-by: Seiji Sakurai <[email protected]> +Signed-off-by: Ales Musil <[email protected]> +(cherry picked from commit 2c063b508fc7b15fd931fb9d057106951b85a74e) +--- + controller/pinctrl.c | 7 ++-- + tests/system-ovn.at | 83 ++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 88 insertions(+), 2 deletions(-) + +Index: ovn/controller/pinctrl.c +=================================================================== +--- ovn.orig/controller/pinctrl.c ++++ ovn/controller/pinctrl.c +@@ -1676,7 +1676,8 @@ pinctrl_handle_icmp(struct rconn *swconn + + if (get_dl_type(ip_flow) == htons(ETH_TYPE_IP)) { + struct ip_header *in_ip = dp_packet_l3(pkt_in); +- uint16_t in_ip_len = ntohs(in_ip->ip_tot_len); ++ uint16_t in_ip_len = ++ MIN(ntohs(in_ip->ip_tot_len), dp_packet_l3_size(pkt_in)); + if (in_ip_len < IP_HEADER_LEN) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); + VLOG_WARN_RL(&rl, +@@ -1736,7 +1737,9 @@ pinctrl_handle_icmp(struct rconn *swconn + ih->icmp_csum = csum(ih, sizeof *ih + in_ip_len); + } else { + struct ovs_16aligned_ip6_hdr *in_ip = dp_packet_l3(pkt_in); +- uint16_t in_ip_len = (uint16_t) sizeof *in_ip + ntohs(in_ip->ip6_plen); ++ uint16_t pkt_in_ip_len = ++ (uint16_t) sizeof *in_ip + ntohs(in_ip->ip6_plen); ++ uint16_t in_ip_len = MIN(pkt_in_ip_len, dp_packet_l3_size(pkt_in)); + + const struct in6_addr *ip6_src = + loopback ? &ip_flow->ipv6_dst : &ip_flow->ipv6_src; +Index: ovn/tests/system-ovn.at +=================================================================== +--- ovn.orig/tests/system-ovn.at ++++ ovn/tests/system-ovn.at +@@ -17412,3 +17412,86 @@ OVS_TRAFFIC_VSWITCHD_STOP() + + AT_CLEANUP + ]) ++ ++OVN_FOR_EACH_NORTHD([ ++AT_SETUP([ACL - ICMP unreachable heap overread]) ++AT_SKIP_IF([test $HAVE_SCAPY = no]) ++ ++ovn_start ++ ++OVS_TRAFFIC_VSWITCHD_START() ++ADD_BR([br-int]) ++ ++# Set external-ids in br-int needed for ovn-controller. ++check ovs-vsctl \ ++ -- set Open_vSwitch . external-ids:system-id=hv1 \ ++ -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \ ++ -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \ ++ -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \ ++ -- set bridge br-int fail-mode=secure other-config:disable-in-band=true ++ ++start_daemon ovn-controller ++ ++# Create a logical switch with a port and a reject ACL. ++check ovn-nbctl ls-add ls1 ++check ovn-nbctl lsp-add ls1 ls1-lp1 \ ++ -- lsp-set-addresses ls1-lp1 "f0:00:00:00:00:01 10.0.0.4 fd10::4" ++ ++# Add a reject ACL: any traffic to ls1-lp1 gets ICMP Destination Unreachable. ++check ovn-nbctl acl-add ls1 to-lport 1000 "outport == \"ls1-lp1\"" reject ++ ++# We need a second port as the "sender". ++check ovn-nbctl lsp-add ls1 ls1-lp2 \ ++ -- lsp-set-addresses ls1-lp2 "f0:00:00:00:00:02 10.0.0.5 fd10::5" ++ ++ADD_NAMESPACES(ls1-lp1) ++ADD_VETH(ls1-lp1, ls1-lp1, br-int, "fd10::4/96", "f0:00:00:00:00:01", \ ++ "fd10::1", "nodad", "10.0.0.4/24", "10.0.0.1") ++ ++ADD_NAMESPACES(ls1-lp2) ++ADD_VETH(ls1-lp2, ls1-lp2, br-int, "fd10::5/96", "f0:00:00:00:00:02", \ ++ "fd10::1", "nodad", "10.0.0.5/24", "10.0.0.1") ++ ++NETNS_START_TCPDUMP([ls1-lp2], [-nnne -i ls1-lp2 icmp or icmp6], [ls1-lp2]) ++ ++OVN_POPULATE_ARP ++wait_for_ports_up ++check ovn-nbctl --wait=hv sync ++ ++# UDP but IP length claims 508 bytes while actual packet is smaller. ++ip netns exec ls1-lp2 scapy -H <<-EOF ++p = Ether(dst='f0:00:00:00:00:01', src='f0:00:00:00:00:02') / \ ++ IP(src='10.0.0.5', dst='10.0.0.4', ttl=64, len=508) / \ ++ UDP(sport=12345, dport=5050) / \ ++ Raw(load=b'AAAA') ++sendp (p, iface='ls1-lp2', loop = 0, verbose = 0, count = 1) ++EOF ++ ++# UDP but IPv6 length claims 508 bytes while actual packet is smaller. ++ip netns exec ls1-lp2 scapy -H <<-EOF ++p = Ether(dst='f0:00:00:00:00:01', src='f0:00:00:00:00:02') / \ ++ IPv6(src='fd10::5', dst='fd10::4', plen=508) / \ ++ UDP(sport=12345, dport=5050) / \ ++ Raw(load=b'AAAA') ++sendp (p, iface='ls1-lp2', loop = 0, verbose = 0, count = 1) ++EOF ++ ++OVS_WAIT_UNTIL([ ++ test "$(grep 'unreachable' -c ls1-lp2.tcpdump)" = "2" ++]) ++ ++ip4_length=$(grep "ICMP " ls1-lp2.tcpdump | grep "unreachable" | rev | cut -d " " -f1 | rev) ++ip6_length=$(grep "ICMP6" ls1-lp2.tcpdump | grep "unreachable" | rev | cut -d " " -f1 | rev) ++ ++check test $ip4_length -eq 40 ++check test $ip6_length -eq 60 ++ ++OVN_CLEANUP_CONTROLLER([hv1]) ++OVN_CLEANUP_NORTHD ++ ++as ++OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d ++/connection dropped.*/d"]) ++ ++AT_CLEANUP ++]) diff -Nru ovn-25.03.0/debian/patches/CVE-2026-5367_pinctrl_Unify_handling_of_DHCPv6_options.patch ovn-25.03.0/debian/patches/CVE-2026-5367_pinctrl_Unify_handling_of_DHCPv6_options.patch --- ovn-25.03.0/debian/patches/CVE-2026-5367_pinctrl_Unify_handling_of_DHCPv6_options.patch 1970-01-01 01:00:00.000000000 +0100 +++ ovn-25.03.0/debian/patches/CVE-2026-5367_pinctrl_Unify_handling_of_DHCPv6_options.patch 2026-05-11 15:27:17.000000000 +0200 @@ -0,0 +1,605 @@ +From f2f840df0f2fc52cacd96c928587c3c1a603c306 Mon Sep 17 00:00:00 2001 +From: Ales Musil <[email protected]> +Date: Fri, 20 Mar 2026 12:42:43 +0100 +Subject: [PATCH] pinctrl: Unify handling of DHCPv6 options. + +Unify the handling of DHCPv6 options. This is addressing several +problems that were present in the DHCPv6 handling: + +1) There were inconsistent length checks for the packet length. It + would be possible to craft a packet that had an option header + without any data which could lead to a crash because we would + attempt to deref data after the packet buffer. + +2) We could end up reading data after the packet buffer. This could + happen when the option header would lie about the data length in + the option. + +3) Unbounded strcmp for a string created by user. + +4) The parsing was inconsistent and very hard to read. + +Make sure the parsing is done using helpers that should prevent +the mentioned issues. + +Fixes: e3a398e9146e ("controller: Add ipv6 prefix delegation state machine") +Fixes: 32fc42fdbb20 ("ovn-controller: Add 'put_dhcpv6_opts' action in ovn-controller") +Fixes: c5fd51bd1541 ("Introduce IPv6 iPXE chainload support") +Fixes: b3ae86a15e81 ("northd, controller: Add support for DHCPv6 FQDN option") +Reported-by: Seiji Sakurai <[email protected]> +Co-authored-by: Seiji Sakurai <[email protected]> +Acked-by: Dumitru Ceara <[email protected]> +Signed-off-by: Seiji Sakurai <[email protected]> +Signed-off-by: Ales Musil <[email protected]> +(cherry picked from commit 0e3d1d908b765ca29c6744f22005c60a6f72b76e) +--- + controller/pinctrl.c | 303 ++++++++++++++++++++++++++----------------- + lib/ovn-l7.h | 10 ++ + tests/system-ovn.at | 66 ++++++++++ + 3 files changed, 262 insertions(+), 117 deletions(-) + +Index: ovn/controller/pinctrl.c +=================================================================== +--- ovn.orig/controller/pinctrl.c ++++ ovn/controller/pinctrl.c +@@ -822,32 +822,100 @@ pinctrl_find_prefixd_state(const struct + return NULL; + } + ++static const struct dhcpv6_opt_header * ++next_dhcpv6_opt(const uint8_t *data, size_t opts_len, ++ size_t *len, size_t *opt_len) ++{ ++ size_t len_inner = *len + sizeof(struct dhcpv6_opt_header); ++ if (len_inner > opts_len) { ++ return NULL; ++ } ++ ++ const struct dhcpv6_opt_header *hdr = ++ (const struct dhcpv6_opt_header *) (data + *len); ++ len_inner += ntohs(hdr->len); ++ if (len_inner > opts_len) { ++ return NULL; ++ } ++ ++ *len = len_inner; ++ *opt_len = sizeof *hdr + ntohs(hdr->len); ++ return hdr; ++} ++ + static void +-pinctrl_parse_dhcpv6_advt(struct rconn *swconn, const struct flow *ip_flow, +- struct dp_packet *pkt_in, const struct match *md) ++dhcpv6_opt_ia_na_parse_inner(const struct dhcpv6_opt_ia_na *ia_na, ++ size_t opts_len, ++ struct dhcpv6_opt_ia_prefix **ia_prefix, ++ struct dhcpv6_opt_status **status) + { +- struct udp_header *udp_in = dp_packet_l4(pkt_in); +- size_t dlen = MIN(ntohs(udp_in->udp_len), dp_packet_l4_size(pkt_in)); +- unsigned char *in_dhcpv6_data = (unsigned char *)(udp_in + 1); +- uint8_t *data, *end = (uint8_t *)udp_in + dlen; +- int len = 0, aid = 0; +- +- data = xmalloc(dlen); +- /* skip DHCPv6 common header */ +- in_dhcpv6_data += 4; +- while (in_dhcpv6_data < end) { +- struct dhcpv6_opt_header *in_opt = +- (struct dhcpv6_opt_header *)in_dhcpv6_data; +- int opt_len = sizeof *in_opt + ntohs(in_opt->len); ++ /* Check if there are at least some data. */ ++ opts_len -= sizeof *ia_na; ++ if (!opts_len) { ++ return; ++ } ++ ++ const uint8_t *opts_data = (uint8_t *) ia_na + sizeof *ia_na; ++ size_t len = 0, opt_len = 0; ++ ++ const struct dhcpv6_opt_header *in_opt; ++ for (in_opt = next_dhcpv6_opt(opts_data, opts_len, &len, &opt_len); ++ in_opt; ++ in_opt = next_dhcpv6_opt(opts_data, opts_len, ++ &len, &opt_len)) { ++ switch (ntohs(in_opt->code)) { ++ case DHCPV6_OPT_IA_PREFIX: { ++ /* Consider only the first found option. */ ++ if (*ia_prefix) { ++ break; ++ } + +- if (dlen < opt_len + len) { +- goto out; ++ if (opt_len < sizeof(struct dhcpv6_opt_ia_prefix)) { ++ break; ++ } ++ ++ *ia_prefix = (struct dhcpv6_opt_ia_prefix *) in_opt; ++ break; + } ++ case DHCPV6_OPT_STATUS_CODE: { ++ /* Consider only the first found option. */ ++ if (*status) { ++ break; ++ } ++ ++ if (opt_len < sizeof(struct dhcpv6_opt_status)) { ++ break; ++ } ++ ++ *status = (struct dhcpv6_opt_status *) in_opt; ++ break; ++ } ++ default: ++ break; ++ } ++ } ++} ++ ++static void ++pinctrl_parse_dhcpv6_advt(struct rconn *swconn, const struct flow *ip_flow, ++ const void *opts_data, size_t opts_len, ++ const struct match *md) ++{ ++ size_t len = 0, opt_len = 0, data_len = 0; ++ int aid = 0; + ++ uint8_t *data = xmalloc(opts_len); ++ ++ const struct dhcpv6_opt_header *in_opt; ++ for (in_opt = next_dhcpv6_opt(opts_data, opts_len, &len, &opt_len); ++ in_opt; ++ in_opt = next_dhcpv6_opt(opts_data, opts_len, &len, &opt_len)) { + switch (ntohs(in_opt->code)) { + case DHCPV6_OPT_IA_PD: { ++ if (opt_len < sizeof(struct dhcpv6_opt_ia_na)) { ++ break; ++ } + struct dhcpv6_opt_ia_na *ia_na = (struct dhcpv6_opt_ia_na *)in_opt; +- int orig_len = len, hdr_len = 0, size = sizeof *in_opt + 12; + uint32_t t1 = ntohl(ia_na->t1), t2 = ntohl(ia_na->t2); + + if (t1 > t2 && t2 > 0) { +@@ -855,55 +923,49 @@ pinctrl_parse_dhcpv6_advt(struct rconn * + } + + aid = ntohl(ia_na->iaid); +- memcpy(&data[len], in_opt, size); +- in_opt = (struct dhcpv6_opt_header *)(in_dhcpv6_data + size); +- len += size; + +- while (size < opt_len) { +- int flen = sizeof *in_opt + ntohs(in_opt->len); ++ memcpy(data + data_len, in_opt, sizeof *ia_na); ++ size_t ia_na_offset = data_len; ++ data_len += sizeof *ia_na; ++ ++ struct dhcpv6_opt_ia_prefix *ia_prefix = NULL; ++ struct dhcpv6_opt_status *status = NULL; ++ dhcpv6_opt_ia_na_parse_inner(ia_na, opt_len, &ia_prefix, &status); ++ ++ if (ia_prefix) { ++ uint32_t plife_time = ntohl(ia_prefix->plife_time); ++ uint32_t vlife_time = ntohl(ia_prefix->vlife_time); + +- if (dlen < flen + len) { ++ if (plife_time > vlife_time) { + goto out; + } + +- if (ntohs(in_opt->code) == DHCPV6_OPT_IA_PREFIX) { +- struct dhcpv6_opt_ia_prefix *ia_hdr = +- (struct dhcpv6_opt_ia_prefix *)in_opt; +- uint32_t plife_time = ntohl(ia_hdr->plife_time); +- uint32_t vlife_time = ntohl(ia_hdr->vlife_time); +- +- if (plife_time > vlife_time) { +- goto out; +- } +- +- memcpy(&data[len], in_opt, flen); +- hdr_len += flen; +- len += flen; +- } +- if (ntohs(in_opt->code) == DHCPV6_OPT_STATUS_CODE) { +- struct dhcpv6_opt_status *status; ++ memcpy(data + data_len, ia_prefix, sizeof *ia_prefix); ++ data_len += sizeof *ia_prefix; ++ } + +- status = (struct dhcpv6_opt_status *)in_opt; +- if (ntohs(status->status_code)) { +- goto out; +- } ++ if (status) { ++ if (ntohs(status->status_code)) { ++ goto out; + } +- size += flen; +- in_opt = (struct dhcpv6_opt_header *)(in_dhcpv6_data + size); + } +- in_opt = (struct dhcpv6_opt_header *)&data[orig_len]; +- in_opt->len = htons(hdr_len + 12); ++ ++ /* Adjust the copied IA NA option header len. */ ++ struct dhcpv6_opt_header *copied_hdr = ++ (struct dhcpv6_opt_header *) (data + ia_na_offset); ++ copied_hdr->len = htons(sizeof *ia_na - sizeof *copied_hdr + ++ (ia_prefix ? sizeof *ia_prefix : 0)); ++ + break; + } + case DHCPV6_OPT_SERVER_ID_CODE: + case DHCPV6_OPT_CLIENT_ID_CODE: +- memcpy(&data[len], in_opt, opt_len); +- len += opt_len; ++ memcpy(data + data_len, in_opt, opt_len); ++ data_len += opt_len; + break; + default: + break; + } +- in_dhcpv6_data += opt_len; + } + + struct ipv6_prefixd_state *pfd = pinctrl_find_prefixd_state(ip_flow, aid); +@@ -931,14 +993,14 @@ pinctrl_parse_dhcpv6_advt(struct rconn * + &ip_flow->ipv6_dst, + &in6addr_all_dhcp_agents, + 0, 0, 255, +- len + UDP_HEADER_LEN + 4); +- udp_h->udp_len = htons(len + UDP_HEADER_LEN + 4); ++ data_len + UDP_HEADER_LEN + 4); ++ udp_h->udp_len = htons(data_len + UDP_HEADER_LEN + 4); + udp_h->udp_csum = 0; + packet_set_udp_port(&packet, htons(546), htons(547)); + + unsigned char *dhcp_hdr = (unsigned char *)(udp_h + 1); + *dhcp_hdr = DHCPV6_MSG_TYPE_REQUEST; +- memcpy(dhcp_hdr + 4, data, len); ++ memcpy(dhcp_hdr + 4, data, data_len); + + uint32_t csum = packet_csum_pseudoheader6(dp_packet_l3(&packet)); + csum = csum_continue(csum, udp_h, dp_packet_size(&packet) - +@@ -1006,39 +1068,29 @@ pinctrl_prefixd_state_handler(const stru + } + + static void +-pinctrl_parse_dhcpv6_reply(struct dp_packet *pkt_in, ++pinctrl_parse_dhcpv6_reply(const void *opts_data, size_t opts_len, + const struct flow *ip_flow) + OVS_REQUIRES(pinctrl_mutex) + { +- struct udp_header *udp_in = dp_packet_l4(pkt_in); +- unsigned char *in_dhcpv6_data = (unsigned char *)(udp_in + 1); +- size_t dlen = MIN(ntohs(udp_in->udp_len), dp_packet_l4_size(pkt_in)); + unsigned t1 = 0, t2 = 0, vlife_time = 0, plife_time = 0; +- uint8_t *end = (uint8_t *) udp_in + dlen; + uint8_t prefix_len = 0, uuid_len = 0; + uint8_t uuid[DHCPV6_MAX_DUID_LEN]; + struct in6_addr ipv6 = in6addr_any; ++ size_t len = 0, opt_len = 0; + bool status = false; + unsigned aid = 0; + +- /* skip DHCPv6 common header */ +- in_dhcpv6_data += 4; +- +- while (in_dhcpv6_data < end) { +- struct dhcpv6_opt_header *in_opt = +- (struct dhcpv6_opt_header *)in_dhcpv6_data; +- int opt_len = sizeof *in_opt + ntohs(in_opt->len); +- +- if (in_dhcpv6_data + opt_len > end) { +- break; +- } +- ++ const struct dhcpv6_opt_header *in_opt; ++ for (in_opt = next_dhcpv6_opt(opts_data, opts_len, &len, &opt_len); ++ in_opt; ++ in_opt = next_dhcpv6_opt(opts_data, opts_len, &len, &opt_len)) { + switch (ntohs(in_opt->code)) { + case DHCPV6_OPT_IA_PD: { +- int size = sizeof *in_opt + 12; +- in_opt = (struct dhcpv6_opt_header *)(in_dhcpv6_data + size); ++ if (opt_len < sizeof(struct dhcpv6_opt_ia_na)) { ++ break; ++ } + struct dhcpv6_opt_ia_na *ia_na = +- (struct dhcpv6_opt_ia_na *)in_dhcpv6_data; ++ (struct dhcpv6_opt_ia_na *) in_opt; + + aid = ntohl(ia_na->iaid); + t1 = ntohl(ia_na->t1); +@@ -1047,30 +1099,25 @@ pinctrl_parse_dhcpv6_reply(struct dp_pac + break; + } + +- while (size < opt_len) { +- if (ntohs(in_opt->code) == DHCPV6_OPT_IA_PREFIX) { +- struct dhcpv6_opt_ia_prefix *ia_hdr = +- (struct dhcpv6_opt_ia_prefix *)(in_dhcpv6_data + size); +- +- plife_time = ntohl(ia_hdr->plife_time); +- vlife_time = ntohl(ia_hdr->vlife_time); +- if (plife_time > vlife_time) { +- break; +- } +- prefix_len = ia_hdr->plen; +- memcpy(&ipv6, &ia_hdr->ipv6, sizeof (struct in6_addr)); +- status = true; ++ struct dhcpv6_opt_ia_prefix *ia_hdr = NULL; ++ struct dhcpv6_opt_status *status_hdr = NULL; ++ dhcpv6_opt_ia_na_parse_inner(ia_na, opt_len, &ia_hdr, &status_hdr); ++ ++ if (ia_hdr) { ++ plife_time = ntohl(ia_hdr->plife_time); ++ vlife_time = ntohl(ia_hdr->vlife_time); ++ if (plife_time > vlife_time) { ++ break; + } +- if (ntohs(in_opt->code) == DHCPV6_OPT_STATUS_CODE) { +- struct dhcpv6_opt_status *status_hdr; ++ prefix_len = ia_hdr->plen; ++ memcpy(&ipv6, &ia_hdr->ipv6, sizeof (struct in6_addr)); ++ status = true; ++ } + +- status_hdr = (struct dhcpv6_opt_status *)in_opt; +- if (ntohs(status_hdr->status_code)) { +- status = false; +- } ++ if (status_hdr) { ++ if (ntohs(status_hdr->status_code)) { ++ status = false; + } +- size += sizeof *in_opt + ntohs(in_opt->len); +- in_opt = (struct dhcpv6_opt_header *)(in_dhcpv6_data + size); + } + break; + } +@@ -1081,7 +1128,6 @@ pinctrl_parse_dhcpv6_reply(struct dp_pac + default: + break; + } +- in_dhcpv6_data += opt_len; + } + if (status) { + char prefix[INET6_ADDRSTRLEN + 1]; +@@ -1108,14 +1154,20 @@ pinctrl_handle_dhcp6_server(struct rconn + } + + struct udp_header *udp_in = dp_packet_l4(pkt_in); +- unsigned char *dhcp_hdr = (unsigned char *)(udp_in + 1); ++ size_t dlen = MIN(ntohs(udp_in->udp_len), dp_packet_l4_size(pkt_in)); ++ if (dlen < UDP_HEADER_LEN + DHCPV6_HEADER_LEN) { ++ return; ++ } + +- switch (*dhcp_hdr) { ++ size_t opts_len = dlen - UDP_HEADER_LEN - DHCPV6_HEADER_LEN; ++ const struct dhcpv6_header *hdr = dp_packet_get_udp_payload(pkt_in); ++ switch (hdr->msg_type) { + case DHCPV6_MSG_TYPE_ADVT: +- pinctrl_parse_dhcpv6_advt(swconn, ip_flow, pkt_in, md); ++ pinctrl_parse_dhcpv6_advt(swconn, ip_flow, DHCPV6_PAYLOAD(hdr), ++ opts_len, md); + break; + case DHCPV6_MSG_TYPE_REPLY: +- pinctrl_parse_dhcpv6_reply(pkt_in, ip_flow); ++ pinctrl_parse_dhcpv6_reply(DHCPV6_PAYLOAD(hdr), opts_len, ip_flow); + break; + default: + break; +@@ -3075,6 +3127,8 @@ compose_dhcpv6_status(struct ofpbuf *use + return true; + } + ++#define DHCPV6_UC_PXE_OFFSET 2 ++ + /* Called with in the pinctrl_handler thread context. */ + static void + pinctrl_handle_put_dhcpv6_opts( +@@ -3117,16 +3171,17 @@ pinctrl_handle_put_dhcpv6_opts( + } + + struct udp_header *in_udp = dp_packet_l4(pkt_in); +- const uint8_t *in_dhcpv6_data = dp_packet_get_udp_payload(pkt_in); +- if (!in_udp || !in_dhcpv6_data) { ++ size_t dlen = MIN(ntohs(in_udp->udp_len), dp_packet_l4_size(pkt_in)); ++ if (dlen < UDP_HEADER_LEN + DHCPV6_HEADER_LEN) { + VLOG_WARN_RL(&rl, "truncated dhcpv6 packet"); + goto exit; + } + ++ const struct dhcpv6_header *hdr = dp_packet_get_udp_payload(pkt_in); ++ + uint8_t out_dhcpv6_msg_type; +- uint8_t in_dhcpv6_msg_type = *in_dhcpv6_data; + bool status_only = false; +- switch (in_dhcpv6_msg_type) { ++ switch (hdr->msg_type) { + case DHCPV6_MSG_TYPE_SOLICIT: + out_dhcpv6_msg_type = DHCPV6_MSG_TYPE_ADVT; + break; +@@ -3147,8 +3202,6 @@ pinctrl_handle_put_dhcpv6_opts( + /* Invalid or unsupported DHCPv6 message type */ + goto exit; + } +- /* Skip 4 bytes (message type (1 byte) + transaction ID (3 bytes). */ +- in_dhcpv6_data += 4; + /* We need to extract IAID from the IA-NA option of the client's DHCPv6 + * solicit/request/confirm packet and copy the same IAID in the Server's + * response. +@@ -3157,17 +3210,24 @@ pinctrl_handle_put_dhcpv6_opts( + * */ + ovs_be32 iaid = 0; + struct dhcpv6_opt_header const *in_opt_client_id = NULL; +- size_t udp_len = ntohs(in_udp->udp_len); +- size_t l4_len = dp_packet_l4_size(pkt_in); +- uint8_t *end = (uint8_t *)in_udp + MIN(udp_len, l4_len); + bool ipxe_req = false; + uint8_t fqdn_flags = DHCPV6_FQDN_FLAGS_UNDEFINED; +- while (in_dhcpv6_data < end) { +- struct dhcpv6_opt_header const *in_opt = +- (struct dhcpv6_opt_header *)in_dhcpv6_data; +- switch(ntohs(in_opt->code)) { ++ size_t len = 0, opt_len = 0; ++ size_t opts_len = dlen - UDP_HEADER_LEN - DHCPV6_HEADER_LEN; ++ ++ const struct dhcpv6_opt_header *in_opt; ++ for (in_opt = next_dhcpv6_opt(DHCPV6_PAYLOAD(hdr), opts_len, ++ &len, &opt_len); ++ in_opt; ++ in_opt = next_dhcpv6_opt(DHCPV6_PAYLOAD(hdr), opts_len, ++ &len, &opt_len)) { ++ switch (ntohs(in_opt->code)) { + case DHCPV6_OPT_IA_NA_CODE: + { ++ if (opt_len < sizeof(struct dhcpv6_opt_ia_na)) { ++ break; ++ } ++ + struct dhcpv6_opt_ia_na *opt_ia_na = ( + struct dhcpv6_opt_ia_na *)in_opt; + iaid = opt_ia_na->iaid; +@@ -3179,21 +3239,30 @@ pinctrl_handle_put_dhcpv6_opts( + break; + + case DHCPV6_OPT_USER_CLASS: { +- char *user_class = (char *)(in_opt + 1); +- if (!strcmp(user_class + 2, "iPXE")) { ++ if (opt_len < ++ sizeof(struct dhcpv6_opt_header) + DHCPV6_UC_PXE_OFFSET + 4) { ++ break; ++ } ++ ++ const char *user_class = DHCPV6_OPT_PAYLOAD(in_opt); ++ if (!strncmp(user_class + DHCPV6_UC_PXE_OFFSET, "iPXE", 4)) { + ipxe_req = true; + } + break; + } + + case DHCPV6_OPT_FQDN_CODE: +- fqdn_flags = *(in_dhcpv6_data + sizeof *in_opt); ++ if (opt_len < ++ sizeof(struct dhcpv6_opt_header) + sizeof(uint8_t)) { ++ break; ++ } ++ ++ fqdn_flags = *(uint8_t *) DHCPV6_OPT_PAYLOAD(in_opt); + break; + + default: + break; + } +- in_dhcpv6_data += sizeof *in_opt + ntohs(in_opt->len); + } + + if (!in_opt_client_id) { +@@ -3202,7 +3271,7 @@ pinctrl_handle_put_dhcpv6_opts( + goto exit; + } + +- if (!iaid && in_dhcpv6_msg_type != DHCPV6_MSG_TYPE_INFO_REQ) { ++ if (!iaid && hdr->msg_type != DHCPV6_MSG_TYPE_INFO_REQ) { + VLOG_WARN_RL(&rl, "DHCPv6 option - IA NA not present in the " + "DHCPv6 packet"); + goto exit; +Index: ovn/lib/ovn-l7.h +=================================================================== +--- ovn.orig/lib/ovn-l7.h ++++ ovn/lib/ovn-l7.h +@@ -312,6 +312,16 @@ extern const struct in6_addr in6addr_all + 0x00,0x00,0x00,0x00,0x00,0x00, \ + 0x00,0x01,0x00,0x02 } } } + ++#define DHCPV6_HEADER_LEN 4 ++OVS_PACKED( ++struct dhcpv6_header { ++ uint8_t msg_type; ++ uint8_t transaction_id[3]; ++}); ++BUILD_ASSERT_DECL(DHCPV6_HEADER_LEN == sizeof(struct dhcpv6_header)); ++ ++#define DHCPV6_PAYLOAD(hdr) \ ++ (const void *)((uint8_t *) (hdr) + sizeof(struct dhcpv6_header)) + + #define DHCP6_OPT_HEADER_LEN 4 + OVS_PACKED( +Index: ovn/tests/system-ovn.at +=================================================================== +--- ovn.orig/tests/system-ovn.at ++++ ovn/tests/system-ovn.at +@@ -17495,3 +17495,69 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/failed to q + + AT_CLEANUP + ]) ++ ++OVN_FOR_EACH_NORTHD([ ++AT_SETUP([DHCPv6 - Options heap overread]) ++AT_SKIP_IF([test $HAVE_SCAPY = no]) ++ ++ovn_start ++ ++OVS_TRAFFIC_VSWITCHD_START() ++ADD_BR([br-int]) ++ ++# Set external-ids in br-int needed for ovn-controller. ++check ovs-vsctl \ ++ -- set Open_vSwitch . external-ids:system-id=hv1 \ ++ -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \ ++ -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \ ++ -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \ ++ -- set bridge br-int fail-mode=secure other-config:disable-in-band=true ++ ++start_daemon ovn-controller ++ ++check ovn-nbctl ls-add ls1 ++check ovn-nbctl lsp-add ls1 ls1-lp1 \ ++ -- lsp-set-addresses ls1-lp1 "f0:00:00:00:00:01 fd10::4" ++ ++check_uuid ovn-nbctl -- --id=@opt create DHCP_Options cidr="fd10\:\:/64" \ ++ options="\"server_id\"=\"00:00:00:10:00:01\"" \ ++ -- set Logical_Switch_Port ls1-lp1 dhcpv6_options=[@opt] ++ ++ADD_NAMESPACES(ls1-lp1) ++ADD_VETH(ls1-lp1, ls1-lp1, br-int, "fd10::4/96", "f0:00:00:00:00:01", \ ++ "fd10::1", "nodad") ++ ++NETNS_START_TCPDUMP([ls1-lp1], [-nnne -i ls1-lp1 udp port 546 and udp port 547], [ls1-lp1]) ++ ++OVN_POPULATE_ARP ++wait_for_ports_up ++check ovn-nbctl --wait=hv sync ++ ++ip netns exec ls1-lp1 scapy -H <<-EOF ++p = Ether(dst='33:33:00:01:00:02', src='f0:00:00:00:00:01') / \ ++ IPv6(dst='ff02::1:2', src='fe80::f200:ff:fe00:1') / \ ++ UDP(sport=546, dport=547) / \ ++ Raw(load=bytes.fromhex( \ ++ '01' '010203' \ ++ '0003' '000c' '01020304' '00000000' '00000000' \ ++ '0001' '0200' \ ++ '0003' '0001' \ ++ 'f00000000001' ++ )) ++sendp (p, iface='ls1-lp1', loop = 0, verbose = 0, count = 1) ++EOF ++ ++# ovn-contorller should report a warning that the packet didn't contain valid Client ID. ++OVS_WAIT_UNTIL([grep -q "DHCPv6 option - Client id not present" ovn-controller.log]) ++AT_CHECK([grep -q "advertise" ls1-lp1.tcpdump], [1]) ++ ++OVN_CLEANUP_CONTROLLER([hv1]) ++OVN_CLEANUP_NORTHD ++ ++as ++OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d ++/connection dropped.*/d ++/DHCPv6 option - Client id not present.*/d"]) ++ ++AT_CLEANUP ++]) diff -Nru ovn-25.03.0/debian/patches/lp-2066194-tests-Ignore-ovs-vswitchd-received-packet-on-unknown.patch ovn-25.03.0/debian/patches/lp-2066194-tests-Ignore-ovs-vswitchd-received-packet-on-unknown.patch --- ovn-25.03.0/debian/patches/lp-2066194-tests-Ignore-ovs-vswitchd-received-packet-on-unknown.patch 2025-03-31 17:35:50.000000000 +0200 +++ ovn-25.03.0/debian/patches/lp-2066194-tests-Ignore-ovs-vswitchd-received-packet-on-unknown.patch 2026-05-11 15:27:17.000000000 +0200 @@ -15,11 +15,11 @@ tests/ovn-macros.at | 1 + 1 file changed, 1 insertion(+) -diff --git a/tests/ovn-macros.at b/tests/ovn-macros.at -index 32ab3b69f..3606b5fe3 100644 ---- a/tests/ovn-macros.at -+++ b/tests/ovn-macros.at -@@ -101,6 +101,7 @@ m4_define([OVN_CLEANUP_SBOX],[ +Index: ovn/tests/ovn-macros.at +=================================================================== +--- ovn.orig/tests/ovn-macros.at ++++ ovn/tests/ovn-macros.at +@@ -360,6 +360,7 @@ m4_define([OVN_CLEANUP_SBOX],[ /receive tunnel port not found*/d /Failed to locate tunnel to reach main chassis/d /Transaction causes multiple rows.*MAC_Binding/d @@ -27,6 +27,3 @@ " $sbox]) ]) --- -2.43.0 - diff -Nru ovn-25.03.0/debian/patches/lp-2104222-tests-Use-scapy-contrib-BFD-implementation.patch ovn-25.03.0/debian/patches/lp-2104222-tests-Use-scapy-contrib-BFD-implementation.patch --- ovn-25.03.0/debian/patches/lp-2104222-tests-Use-scapy-contrib-BFD-implementation.patch 2025-03-31 17:35:50.000000000 +0200 +++ ovn-25.03.0/debian/patches/lp-2104222-tests-Use-scapy-contrib-BFD-implementation.patch 2026-05-11 15:27:17.000000000 +0200 @@ -23,11 +23,11 @@ utilities/containers/py-requirements.txt | 2 +- 2 files changed, 12 insertions(+), 11 deletions(-) -diff --git a/tests/system-ovn.at b/tests/system-ovn.at -index 66deeb30e..07cc31ba3 100644 ---- a/tests/system-ovn.at -+++ b/tests/system-ovn.at -@@ -7867,19 +7867,20 @@ bfd: bfd-meter +Index: ovn/tests/system-ovn.at +=================================================================== +--- ovn.orig/tests/system-ovn.at ++++ ovn/tests/system-ovn.at +@@ -7863,19 +7863,20 @@ bfd: bfd-meter ]) check ovn-nbctl --wait=hv --bfd lr-route-add R1 240.0.0.0/8 172.16.1.50 rp-public @@ -58,10 +58,10 @@ # 1pps OVS_WAIT_UNTIL([ -diff --git a/utilities/containers/py-requirements.txt b/utilities/containers/py-requirements.txt -index 1b55042c8..f308cc331 100644 ---- a/utilities/containers/py-requirements.txt -+++ b/utilities/containers/py-requirements.txt +Index: ovn/utilities/containers/py-requirements.txt +=================================================================== +--- ovn.orig/utilities/containers/py-requirements.txt ++++ ovn/utilities/containers/py-requirements.txt @@ -1,6 +1,6 @@ flake8>=6.1.0 meson>=1.4,<1.5 @@ -70,6 +70,3 @@ sphinx<8.0 # https://github.com/sphinx-doc/sphinx/issues/12711 setuptools pyelftools --- -2.43.0 - diff -Nru ovn-25.03.0/debian/patches/series ovn-25.03.0/debian/patches/series --- ovn-25.03.0/debian/patches/series 2025-03-31 17:35:50.000000000 +0200 +++ ovn-25.03.0/debian/patches/series 2026-05-11 15:27:17.000000000 +0200 @@ -1,2 +1,4 @@ lp-2066194-tests-Ignore-ovs-vswitchd-received-packet-on-unknown.patch lp-2104222-tests-Use-scapy-contrib-BFD-implementation.patch +CVE-2026-5265_pinctrl_Limit_the_IP_packet_size_to_buffer_size_for_ICMP_Need_Frag.patch +CVE-2026-5367_pinctrl_Unify_handling_of_DHCPv6_options.patch

