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

Reply via email to