{"id":2225017,"url":"http://patchwork.ozlabs.org/api/patches/2225017/?format=json","web_url":"http://patchwork.ozlabs.org/project/ovn/patch/20260420090026.1666597-2-amusil@redhat.com/","project":{"id":68,"url":"http://patchwork.ozlabs.org/api/projects/68/?format=json","name":"Open Virtual Network development","link_name":"ovn","list_id":"ovs-dev.openvswitch.org","list_email":"ovs-dev@openvswitch.org","web_url":"http://openvswitch.org/","scm_url":"","webscm_url":"","list_archive_url":"","list_archive_url_format":"","commit_url_format":""},"msgid":"<20260420090026.1666597-2-amusil@redhat.com>","list_archive_url":null,"date":"2026-04-20T09:00:25","name":"[ovs-dev,2/3] pinctrl: Unify handling of DHCPv6 options.","commit_ref":null,"pull_url":null,"state":"accepted","archived":false,"hash":"49611d04da4af299bab288d4eaaacc5eac80a4ac","submitter":{"id":83634,"url":"http://patchwork.ozlabs.org/api/people/83634/?format=json","name":"Ales Musil","email":"amusil@redhat.com"},"delegate":null,"mbox":"http://patchwork.ozlabs.org/project/ovn/patch/20260420090026.1666597-2-amusil@redhat.com/mbox/","series":[{"id":500572,"url":"http://patchwork.ozlabs.org/api/series/500572/?format=json","web_url":"http://patchwork.ozlabs.org/project/ovn/list/?series=500572","date":"2026-04-20T09:00:24","name":"[ovs-dev,1/3] pinctrl: Limit the IP packet size to buffer size for ICMP Need Frag.","version":1,"mbox":"http://patchwork.ozlabs.org/series/500572/mbox/"}],"comments":"http://patchwork.ozlabs.org/api/patches/2225017/comments/","check":"success","checks":"http://patchwork.ozlabs.org/api/patches/2225017/checks/","tags":{},"related":[],"headers":{"Return-Path":"<ovs-dev-bounces@openvswitch.org>","X-Original-To":["incoming@patchwork.ozlabs.org","dev@openvswitch.org"],"Delivered-To":["patchwork-incoming@legolas.ozlabs.org","ovs-dev@lists.linuxfoundation.org"],"Authentication-Results":["legolas.ozlabs.org;\n\tdkim=fail reason=\"signature verification failed\" (1024-bit key;\n unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256\n header.s=mimecast20190719 header.b=Ji0QBNLE;\n\tdkim-atps=neutral","legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org\n (client-ip=140.211.166.133; helo=smtp2.osuosl.org;\n envelope-from=ovs-dev-bounces@openvswitch.org; receiver=patchwork.ozlabs.org)","smtp2.osuosl.org;\n\tdkim=fail reason=\"signature verification failed\" (1024-bit key)\n header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256\n header.s=mimecast20190719 header.b=Ji0QBNLE","smtp4.osuosl.org; dmarc=pass (p=quarantine dis=none)\n header.from=redhat.com","smtp4.osuosl.org;\n dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com\n header.a=rsa-sha256 header.s=mimecast20190719 header.b=Ji0QBNLE"],"Received":["from smtp2.osuosl.org (smtp2.osuosl.org [140.211.166.133])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\t key-exchange x25519 server-signature ECDSA (secp384r1) server-digest SHA384)\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4fzfbM2WK4z1yGs\n\tfor <incoming@patchwork.ozlabs.org>; Mon, 20 Apr 2026 19:00:47 +1000 (AEST)","from localhost (localhost [127.0.0.1])\n\tby smtp2.osuosl.org (Postfix) with ESMTP id 3CC2B40833;\n\tMon, 20 Apr 2026 09:00:45 +0000 (UTC)","from smtp2.osuosl.org ([127.0.0.1])\n by localhost (smtp2.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP\n id l_7yEKCfmQKD; Mon, 20 Apr 2026 09:00:42 +0000 (UTC)","from lists.linuxfoundation.org (lf-lists.osuosl.org [140.211.9.56])\n\tby smtp2.osuosl.org (Postfix) with ESMTPS id 8EABF4080D;\n\tMon, 20 Apr 2026 09:00:41 +0000 (UTC)","from lf-lists.osuosl.org (localhost [127.0.0.1])\n\tby lists.linuxfoundation.org (Postfix) with ESMTP id 2F47BC058E;\n\tMon, 20 Apr 2026 09:00:41 +0000 (UTC)","from smtp4.osuosl.org (smtp4.osuosl.org [IPv6:2605:bc80:3010::137])\n by lists.linuxfoundation.org (Postfix) with ESMTP id 116ECC058D\n for <dev@openvswitch.org>; Mon, 20 Apr 2026 09:00:40 +0000 (UTC)","from localhost (localhost [127.0.0.1])\n by smtp4.osuosl.org (Postfix) with ESMTP id F02FD40F45\n for <dev@openvswitch.org>; Mon, 20 Apr 2026 09:00:39 +0000 (UTC)","from smtp4.osuosl.org ([127.0.0.1])\n by localhost (smtp4.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP\n id mOvDhK-Et4vi for <dev@openvswitch.org>;\n Mon, 20 Apr 2026 09:00:38 +0000 (UTC)","from us-smtp-delivery-124.mimecast.com\n (us-smtp-delivery-124.mimecast.com [170.10.133.124])\n by smtp4.osuosl.org (Postfix) with ESMTPS id 95A8E40EE9\n for <dev@openvswitch.org>; Mon, 20 Apr 2026 09:00:38 +0000 (UTC)","from mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com\n (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by\n relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3,\n cipher=TLS_AES_256_GCM_SHA384) id us-mta-146-Qu9Xi1lgOo66TpEaLE-7GA-1; Mon,\n 20 Apr 2026 05:00:33 -0400","from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com\n (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4])\n (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest\n SHA256)\n (No client certificate requested)\n by mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS\n id 577501955E9A; Mon, 20 Apr 2026 09:00:32 +0000 (UTC)","from amusil.redhat.com (unknown [10.44.33.221])\n by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP\n id 4BD393000C20; Mon, 20 Apr 2026 09:00:29 +0000 (UTC)"],"X-Virus-Scanned":["amavis at osuosl.org","amavis at osuosl.org"],"X-Comment":"SPF check N/A for local connections - client-ip=140.211.9.56;\n helo=lists.linuxfoundation.org;\n envelope-from=ovs-dev-bounces@openvswitch.org; receiver=<UNKNOWN> ","DKIM-Filter":["OpenDKIM Filter v2.11.0 smtp2.osuosl.org 8EABF4080D","OpenDKIM Filter v2.11.0 smtp4.osuosl.org 95A8E40EE9"],"Received-SPF":"Pass (mailfrom) identity=mailfrom; client-ip=170.10.133.124;\n helo=us-smtp-delivery-124.mimecast.com; envelope-from=amusil@redhat.com;\n receiver=<UNKNOWN>","DMARC-Filter":"OpenDMARC Filter v1.4.2 smtp4.osuosl.org 95A8E40EE9","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com;\n s=mimecast20190719; t=1776675637;\n h=from:from:reply-to:subject:subject:date:date:message-id:message-id:\n to:to:cc:cc:mime-version:mime-version:content-type:content-type:\n content-transfer-encoding:content-transfer-encoding:\n in-reply-to:in-reply-to:references:references;\n bh=Yjhl7/Qq2uNRl8z3TIChqMw5eJCqapSq9Rxf9fLapm4=;\n b=Ji0QBNLEiNX7YA4N6O/c8x7xCGkDk5hTOYH/nVG66QEBO/M2L5O8JOk4ux1zZ/fvccNb76\n 467DjK3Y5V+KaUuSawUOABM4gk07JJdpefSyodz/KSIfKTi0F/0RBX1OsIbkxMpNINP9fU\n DJuG+GgWo/r1HYUCmJgmPvLwUcTQBMA=","X-MC-Unique":"Qu9Xi1lgOo66TpEaLE-7GA-1","X-Mimecast-MFC-AGG-ID":"Qu9Xi1lgOo66TpEaLE-7GA_1776675632","To":"dev@openvswitch.org","Date":"Mon, 20 Apr 2026 11:00:25 +0200","Message-ID":"<20260420090026.1666597-2-amusil@redhat.com>","In-Reply-To":"<20260420090026.1666597-1-amusil@redhat.com>","References":"<20260420090026.1666597-1-amusil@redhat.com>","MIME-Version":"1.0","X-Scanned-By":"MIMEDefang 3.4.1 on 10.30.177.4","X-Mimecast-Spam-Score":"0","X-Mimecast-MFC-PROC-ID":"blJ44vDAQqWYjTPh_udP2T5AMVqowydx33iu6iUie8A_1776675632","X-Mimecast-Originator":"redhat.com","Subject":"[ovs-dev] [PATCH ovn 2/3] pinctrl: Unify handling of DHCPv6 options.","X-BeenThere":"ovs-dev@openvswitch.org","X-Mailman-Version":"2.1.30","Precedence":"list","List-Id":"<ovs-dev.openvswitch.org>","List-Unsubscribe":"<https://mail.openvswitch.org/mailman/options/ovs-dev>,\n <mailto:ovs-dev-request@openvswitch.org?subject=unsubscribe>","List-Archive":"<http://mail.openvswitch.org/pipermail/ovs-dev/>","List-Post":"<mailto:ovs-dev@openvswitch.org>","List-Help":"<mailto:ovs-dev-request@openvswitch.org?subject=help>","List-Subscribe":"<https://mail.openvswitch.org/mailman/listinfo/ovs-dev>,\n <mailto:ovs-dev-request@openvswitch.org?subject=subscribe>","From":"Ales Musil via dev <ovs-dev@openvswitch.org>","Reply-To":"Ales Musil <amusil@redhat.com>","Cc":"Seiji Sakurai <Seiji.Sakurai@outlook.com>,\n Dumitru Ceara <dceara@redhat.com>","Content-Type":"text/plain; charset=\"us-ascii\"","Content-Transfer-Encoding":"7bit","Errors-To":"ovs-dev-bounces@openvswitch.org","Sender":"\"dev\" <ovs-dev-bounces@openvswitch.org>"},"content":"Unify the handling of DHCPv6 options. This is addressing several\nproblems that were present in the DHCPv6 handling:\n\n1) There were inconsistent length checks for the packet length. It\n   would be possible to craft a packet that had an option header\n   without any data which could lead to a crash because we would\n   attempt to deref data after the packet buffer.\n\n2) We could end up reading data after the packet buffer. This could\n   happen when the option header would lie about the data length in\n   the option.\n\n3) Unbounded strcmp for a string created by user.\n\n4) The parsing was inconsistent and very hard to read.\n\nMake sure the parsing is done using helpers that should prevent\nthe mentioned issues.\n\nFixes: e3a398e9146e (\"controller: Add ipv6 prefix delegation state machine\")\nFixes: 32fc42fdbb20 (\"ovn-controller: Add 'put_dhcpv6_opts' action in ovn-controller\")\nFixes: c5fd51bd1541 (\"Introduce IPv6 iPXE chainload support\")\nFixes: b3ae86a15e81 (\"northd, controller: Add support for DHCPv6 FQDN option\")\nReported-by: Seiji Sakurai <Seiji.Sakurai@outlook.com>\nCo-authored-by: Seiji Sakurai <Seiji.Sakurai@outlook.com>\nAcked-by: Dumitru Ceara <dceara@redhat.com>\nSigned-off-by: Seiji Sakurai <Seiji.Sakurai@outlook.com>\nSigned-off-by: Ales Musil <amusil@redhat.com>\n---\n controller/pinctrl.c | 303 ++++++++++++++++++++++++++-----------------\n lib/ovn-l7.h         |  10 ++\n tests/system-ovn.at  |  66 ++++++++++\n 3 files changed, 262 insertions(+), 117 deletions(-)","diff":"diff --git a/controller/pinctrl.c b/controller/pinctrl.c\nindex 682b88b1a..de52ac87f 100644\n--- a/controller/pinctrl.c\n+++ b/controller/pinctrl.c\n@@ -822,32 +822,100 @@ pinctrl_find_prefixd_state(const struct flow *ip_flow, unsigned aid)\n     return NULL;\n }\n \n+static const struct dhcpv6_opt_header *\n+next_dhcpv6_opt(const uint8_t *data, size_t opts_len,\n+                size_t *len, size_t *opt_len)\n+{\n+    size_t len_inner = *len + sizeof(struct dhcpv6_opt_header);\n+    if (len_inner > opts_len) {\n+        return NULL;\n+    }\n+\n+    const struct dhcpv6_opt_header *hdr =\n+        (const struct dhcpv6_opt_header *) (data + *len);\n+    len_inner += ntohs(hdr->len);\n+    if (len_inner > opts_len) {\n+        return NULL;\n+    }\n+\n+    *len = len_inner;\n+    *opt_len = sizeof *hdr + ntohs(hdr->len);\n+    return hdr;\n+}\n+\n static void\n-pinctrl_parse_dhcpv6_advt(struct rconn *swconn, const struct flow *ip_flow,\n-                          struct dp_packet *pkt_in, const struct match *md)\n+dhcpv6_opt_ia_na_parse_inner(const struct dhcpv6_opt_ia_na *ia_na,\n+                             size_t opts_len,\n+                             struct dhcpv6_opt_ia_prefix **ia_prefix,\n+                             struct dhcpv6_opt_status **status)\n {\n-    struct udp_header *udp_in = dp_packet_l4(pkt_in);\n-    size_t dlen = MIN(ntohs(udp_in->udp_len), dp_packet_l4_size(pkt_in));\n-    unsigned char *in_dhcpv6_data = (unsigned char *)(udp_in + 1);\n-    uint8_t *data, *end = (uint8_t *)udp_in + dlen;\n-    int len = 0, aid = 0;\n-\n-    data = xmalloc(dlen);\n-    /* skip DHCPv6 common header */\n-    in_dhcpv6_data += 4;\n-    while (in_dhcpv6_data < end) {\n-        struct dhcpv6_opt_header *in_opt =\n-             (struct dhcpv6_opt_header *)in_dhcpv6_data;\n-        int opt_len = sizeof *in_opt + ntohs(in_opt->len);\n+    /* Check if there are at least some data. */\n+    opts_len -= sizeof *ia_na;\n+    if (!opts_len) {\n+        return;\n+    }\n+\n+    const uint8_t *opts_data = (uint8_t *) ia_na + sizeof *ia_na;\n+    size_t len = 0, opt_len = 0;\n \n-        if (dlen < opt_len + len) {\n-            goto out;\n+    const struct dhcpv6_opt_header *in_opt;\n+    for (in_opt = next_dhcpv6_opt(opts_data, opts_len, &len, &opt_len);\n+         in_opt;\n+         in_opt = next_dhcpv6_opt(opts_data, opts_len,\n+                                  &len, &opt_len)) {\n+        switch (ntohs(in_opt->code)) {\n+        case DHCPV6_OPT_IA_PREFIX: {\n+            /* Consider only the first found option. */\n+            if (*ia_prefix) {\n+                break;\n+            }\n+\n+            if (opt_len < sizeof(struct dhcpv6_opt_ia_prefix)) {\n+                break;\n+            }\n+\n+            *ia_prefix = (struct dhcpv6_opt_ia_prefix *) in_opt;\n+            break;\n         }\n+        case DHCPV6_OPT_STATUS_CODE: {\n+            /* Consider only the first found option. */\n+            if (*status) {\n+                break;\n+            }\n \n+            if (opt_len < sizeof(struct dhcpv6_opt_status)) {\n+                break;\n+            }\n+\n+            *status = (struct dhcpv6_opt_status *) in_opt;\n+            break;\n+        }\n+        default:\n+            break;\n+        }\n+    }\n+}\n+\n+static void\n+pinctrl_parse_dhcpv6_advt(struct rconn *swconn, const struct flow *ip_flow,\n+                          const void *opts_data, size_t opts_len,\n+                          const struct match *md)\n+{\n+    size_t len = 0, opt_len = 0, data_len = 0;\n+    int aid = 0;\n+\n+    uint8_t *data = xmalloc(opts_len);\n+\n+    const struct dhcpv6_opt_header *in_opt;\n+    for (in_opt = next_dhcpv6_opt(opts_data, opts_len, &len, &opt_len);\n+         in_opt;\n+         in_opt = next_dhcpv6_opt(opts_data, opts_len, &len, &opt_len)) {\n         switch (ntohs(in_opt->code)) {\n         case DHCPV6_OPT_IA_PD: {\n+            if (opt_len < sizeof(struct dhcpv6_opt_ia_na)) {\n+                break;\n+            }\n             struct dhcpv6_opt_ia_na *ia_na = (struct dhcpv6_opt_ia_na *)in_opt;\n-            int orig_len = len, hdr_len = 0, size = sizeof *in_opt + 12;\n             uint32_t t1 = ntohl(ia_na->t1), t2 = ntohl(ia_na->t2);\n \n             if (t1 > t2 && t2 > 0) {\n@@ -855,55 +923,49 @@ pinctrl_parse_dhcpv6_advt(struct rconn *swconn, const struct flow *ip_flow,\n             }\n \n             aid = ntohl(ia_na->iaid);\n-            memcpy(&data[len], in_opt, size);\n-            in_opt = (struct dhcpv6_opt_header *)(in_dhcpv6_data + size);\n-            len += size;\n-\n-            while (size < opt_len) {\n-                int flen = sizeof *in_opt + ntohs(in_opt->len);\n \n-                if (dlen < flen + len) {\n-                    goto out;\n-                }\n+            memcpy(data + data_len, in_opt, sizeof *ia_na);\n+            size_t ia_na_offset = data_len;\n+            data_len += sizeof *ia_na;\n \n-                if (ntohs(in_opt->code) == DHCPV6_OPT_IA_PREFIX) {\n-                    struct dhcpv6_opt_ia_prefix *ia_hdr =\n-                        (struct dhcpv6_opt_ia_prefix *)in_opt;\n-                    uint32_t plife_time = ntohl(ia_hdr->plife_time);\n-                    uint32_t vlife_time = ntohl(ia_hdr->vlife_time);\n+            struct dhcpv6_opt_ia_prefix *ia_prefix = NULL;\n+            struct dhcpv6_opt_status *status = NULL;\n+            dhcpv6_opt_ia_na_parse_inner(ia_na, opt_len, &ia_prefix, &status);\n \n-                    if (plife_time > vlife_time) {\n-                        goto out;\n-                    }\n+            if (ia_prefix) {\n+                uint32_t plife_time = ntohl(ia_prefix->plife_time);\n+                uint32_t vlife_time = ntohl(ia_prefix->vlife_time);\n \n-                    memcpy(&data[len], in_opt, flen);\n-                    hdr_len += flen;\n-                    len += flen;\n+                if (plife_time > vlife_time) {\n+                    goto out;\n                 }\n-                if (ntohs(in_opt->code) == DHCPV6_OPT_STATUS_CODE) {\n-                   struct dhcpv6_opt_status *status;\n \n-                   status = (struct dhcpv6_opt_status *)in_opt;\n-                   if (ntohs(status->status_code)) {\n-                       goto out;\n-                   }\n+                memcpy(data + data_len, ia_prefix, sizeof *ia_prefix);\n+                data_len += sizeof *ia_prefix;\n+            }\n+\n+            if (status) {\n+                if (ntohs(status->status_code)) {\n+                    goto out;\n                 }\n-                size += flen;\n-                in_opt = (struct dhcpv6_opt_header *)(in_dhcpv6_data + size);\n             }\n-            in_opt = (struct dhcpv6_opt_header *)&data[orig_len];\n-            in_opt->len = htons(hdr_len + 12);\n+\n+            /* Adjust the copied IA NA option header len. */\n+            struct dhcpv6_opt_header *copied_hdr =\n+                (struct dhcpv6_opt_header *) (data + ia_na_offset);\n+            copied_hdr->len = htons(sizeof *ia_na - sizeof *copied_hdr +\n+                                    (ia_prefix ? sizeof *ia_prefix : 0));\n+\n             break;\n         }\n         case DHCPV6_OPT_SERVER_ID_CODE:\n         case DHCPV6_OPT_CLIENT_ID_CODE:\n-            memcpy(&data[len], in_opt, opt_len);\n-            len += opt_len;\n+            memcpy(data + data_len, in_opt, opt_len);\n+            data_len += opt_len;\n             break;\n         default:\n             break;\n         }\n-        in_dhcpv6_data += opt_len;\n     }\n \n     struct ipv6_prefixd_state *pfd = pinctrl_find_prefixd_state(ip_flow, aid);\n@@ -931,14 +993,14 @@ pinctrl_parse_dhcpv6_advt(struct rconn *swconn, const struct flow *ip_flow,\n                                             &ip_flow->ipv6_dst,\n                                             &in6addr_all_dhcp_agents,\n                                             0, 0, 255,\n-                                            len + UDP_HEADER_LEN + 4);\n-    udp_h->udp_len = htons(len + UDP_HEADER_LEN + 4);\n+                                            data_len + UDP_HEADER_LEN + 4);\n+    udp_h->udp_len = htons(data_len + UDP_HEADER_LEN + 4);\n     udp_h->udp_csum = 0;\n     packet_set_udp_port(&packet, htons(546), htons(547));\n \n     unsigned char *dhcp_hdr = (unsigned char *)(udp_h + 1);\n     *dhcp_hdr = DHCPV6_MSG_TYPE_REQUEST;\n-    memcpy(dhcp_hdr + 4, data, len);\n+    memcpy(dhcp_hdr + 4, data, data_len);\n \n     uint32_t csum = packet_csum_pseudoheader6(dp_packet_l3(&packet));\n     csum = csum_continue(csum, udp_h, dp_packet_size(&packet) -\n@@ -1006,39 +1068,29 @@ pinctrl_prefixd_state_handler(const struct flow *ip_flow,\n }\n \n static void\n-pinctrl_parse_dhcpv6_reply(struct dp_packet *pkt_in,\n+pinctrl_parse_dhcpv6_reply(const void *opts_data, size_t opts_len,\n                            const struct flow *ip_flow)\n     OVS_REQUIRES(pinctrl_mutex)\n {\n-    struct udp_header *udp_in = dp_packet_l4(pkt_in);\n-    unsigned char *in_dhcpv6_data = (unsigned char *)(udp_in + 1);\n-    size_t dlen = MIN(ntohs(udp_in->udp_len), dp_packet_l4_size(pkt_in));\n     unsigned t1 = 0, t2 = 0, vlife_time = 0, plife_time = 0;\n-    uint8_t *end = (uint8_t *) udp_in + dlen;\n     uint8_t prefix_len = 0, uuid_len = 0;\n     uint8_t uuid[DHCPV6_MAX_DUID_LEN];\n     struct in6_addr ipv6 = in6addr_any;\n+    size_t len = 0, opt_len = 0;\n     bool status = false;\n     unsigned aid = 0;\n \n-    /* skip DHCPv6 common header */\n-    in_dhcpv6_data += 4;\n-\n-    while (in_dhcpv6_data < end) {\n-        struct dhcpv6_opt_header *in_opt =\n-             (struct dhcpv6_opt_header *)in_dhcpv6_data;\n-        int opt_len = sizeof *in_opt + ntohs(in_opt->len);\n-\n-        if (in_dhcpv6_data + opt_len > end) {\n-            break;\n-        }\n-\n+    const struct dhcpv6_opt_header *in_opt;\n+    for (in_opt = next_dhcpv6_opt(opts_data, opts_len, &len, &opt_len);\n+         in_opt;\n+         in_opt = next_dhcpv6_opt(opts_data, opts_len, &len, &opt_len)) {\n         switch (ntohs(in_opt->code)) {\n         case DHCPV6_OPT_IA_PD: {\n-            int size = sizeof *in_opt + 12;\n-            in_opt = (struct dhcpv6_opt_header *)(in_dhcpv6_data + size);\n+            if (opt_len < sizeof(struct dhcpv6_opt_ia_na)) {\n+                break;\n+            }\n             struct dhcpv6_opt_ia_na *ia_na =\n-                (struct dhcpv6_opt_ia_na *)in_dhcpv6_data;\n+                (struct dhcpv6_opt_ia_na *) in_opt;\n \n             aid = ntohl(ia_na->iaid);\n             t1 = ntohl(ia_na->t1);\n@@ -1047,30 +1099,25 @@ pinctrl_parse_dhcpv6_reply(struct dp_packet *pkt_in,\n                 break;\n             }\n \n-            while (size < opt_len) {\n-                if (ntohs(in_opt->code) == DHCPV6_OPT_IA_PREFIX) {\n-                    struct dhcpv6_opt_ia_prefix *ia_hdr =\n-                        (struct dhcpv6_opt_ia_prefix *)(in_dhcpv6_data + size);\n+            struct dhcpv6_opt_ia_prefix *ia_hdr = NULL;\n+            struct dhcpv6_opt_status *status_hdr = NULL;\n+            dhcpv6_opt_ia_na_parse_inner(ia_na, opt_len, &ia_hdr, &status_hdr);\n \n-                    plife_time = ntohl(ia_hdr->plife_time);\n-                    vlife_time = ntohl(ia_hdr->vlife_time);\n-                    if (plife_time > vlife_time) {\n-                        break;\n-                    }\n-                    prefix_len = ia_hdr->plen;\n-                    memcpy(&ipv6, &ia_hdr->ipv6, sizeof (struct in6_addr));\n-                    status = true;\n+            if (ia_hdr) {\n+                plife_time = ntohl(ia_hdr->plife_time);\n+                vlife_time = ntohl(ia_hdr->vlife_time);\n+                if (plife_time > vlife_time) {\n+                    break;\n                 }\n-                if (ntohs(in_opt->code) == DHCPV6_OPT_STATUS_CODE) {\n-                   struct dhcpv6_opt_status *status_hdr;\n+                prefix_len = ia_hdr->plen;\n+                memcpy(&ipv6, &ia_hdr->ipv6, sizeof (struct in6_addr));\n+                status = true;\n+            }\n \n-                   status_hdr = (struct dhcpv6_opt_status *)in_opt;\n-                   if (ntohs(status_hdr->status_code)) {\n-                       status = false;\n-                   }\n+            if (status_hdr) {\n+                if (ntohs(status_hdr->status_code)) {\n+                    status = false;\n                 }\n-                size += sizeof *in_opt + ntohs(in_opt->len);\n-                in_opt = (struct dhcpv6_opt_header *)(in_dhcpv6_data + size);\n             }\n             break;\n         }\n@@ -1081,7 +1128,6 @@ pinctrl_parse_dhcpv6_reply(struct dp_packet *pkt_in,\n         default:\n             break;\n         }\n-        in_dhcpv6_data += opt_len;\n     }\n     if (status) {\n         char prefix[INET6_ADDRSTRLEN + 1];\n@@ -1108,14 +1154,20 @@ pinctrl_handle_dhcp6_server(struct rconn *swconn, const struct flow *ip_flow,\n     }\n \n     struct udp_header *udp_in = dp_packet_l4(pkt_in);\n-    unsigned char *dhcp_hdr = (unsigned char *)(udp_in + 1);\n+    size_t dlen = MIN(ntohs(udp_in->udp_len), dp_packet_l4_size(pkt_in));\n+    if (dlen < UDP_HEADER_LEN + DHCPV6_HEADER_LEN) {\n+        return;\n+    }\n \n-    switch (*dhcp_hdr) {\n+    size_t opts_len = dlen - UDP_HEADER_LEN - DHCPV6_HEADER_LEN;\n+    const struct dhcpv6_header *hdr = dp_packet_get_udp_payload(pkt_in);\n+    switch (hdr->msg_type) {\n     case DHCPV6_MSG_TYPE_ADVT:\n-        pinctrl_parse_dhcpv6_advt(swconn, ip_flow, pkt_in, md);\n+        pinctrl_parse_dhcpv6_advt(swconn, ip_flow, DHCPV6_PAYLOAD(hdr),\n+                                  opts_len, md);\n         break;\n     case DHCPV6_MSG_TYPE_REPLY:\n-        pinctrl_parse_dhcpv6_reply(pkt_in, ip_flow);\n+        pinctrl_parse_dhcpv6_reply(DHCPV6_PAYLOAD(hdr), opts_len, ip_flow);\n         break;\n     default:\n         break;\n@@ -3073,6 +3125,8 @@ compose_dhcpv6_status(struct ofpbuf *userdata, struct ofpbuf *opts)\n     return true;\n }\n \n+#define DHCPV6_UC_PXE_OFFSET 2\n+\n /* Called with in the pinctrl_handler thread context. */\n static void\n pinctrl_handle_put_dhcpv6_opts(\n@@ -3115,16 +3169,17 @@ pinctrl_handle_put_dhcpv6_opts(\n     }\n \n     struct udp_header *in_udp = dp_packet_l4(pkt_in);\n-    const uint8_t *in_dhcpv6_data = dp_packet_get_udp_payload(pkt_in);\n-    if (!in_udp || !in_dhcpv6_data) {\n+    size_t dlen = MIN(ntohs(in_udp->udp_len), dp_packet_l4_size(pkt_in));\n+    if (dlen < UDP_HEADER_LEN + DHCPV6_HEADER_LEN) {\n         VLOG_WARN_RL(&rl, \"truncated dhcpv6 packet\");\n         goto exit;\n     }\n \n+    const struct dhcpv6_header *hdr = dp_packet_get_udp_payload(pkt_in);\n+\n     uint8_t out_dhcpv6_msg_type;\n-    uint8_t in_dhcpv6_msg_type = *in_dhcpv6_data;\n     bool status_only = false;\n-    switch (in_dhcpv6_msg_type) {\n+    switch (hdr->msg_type) {\n     case DHCPV6_MSG_TYPE_SOLICIT:\n         out_dhcpv6_msg_type = DHCPV6_MSG_TYPE_ADVT;\n         break;\n@@ -3145,8 +3200,6 @@ pinctrl_handle_put_dhcpv6_opts(\n         /* Invalid or unsupported DHCPv6 message type */\n         goto exit;\n     }\n-    /* Skip 4 bytes (message type (1 byte) + transaction ID (3 bytes). */\n-    in_dhcpv6_data += 4;\n     /* We need to extract IAID from the IA-NA option of the client's DHCPv6\n      * solicit/request/confirm packet and copy the same IAID in the Server's\n      * response.\n@@ -3155,17 +3208,24 @@ pinctrl_handle_put_dhcpv6_opts(\n      * */\n     ovs_be32 iaid = 0;\n     struct dhcpv6_opt_header const *in_opt_client_id = NULL;\n-    size_t udp_len = ntohs(in_udp->udp_len);\n-    size_t l4_len = dp_packet_l4_size(pkt_in);\n-    uint8_t *end = (uint8_t *)in_udp + MIN(udp_len, l4_len);\n     bool ipxe_req = false;\n     uint8_t fqdn_flags = DHCPV6_FQDN_FLAGS_UNDEFINED;\n-    while (in_dhcpv6_data < end) {\n-        struct dhcpv6_opt_header const *in_opt =\n-             (struct dhcpv6_opt_header *)in_dhcpv6_data;\n-        switch(ntohs(in_opt->code)) {\n+    size_t len = 0, opt_len = 0;\n+    size_t opts_len = dlen - UDP_HEADER_LEN - DHCPV6_HEADER_LEN;\n+\n+    const struct dhcpv6_opt_header *in_opt;\n+    for (in_opt = next_dhcpv6_opt(DHCPV6_PAYLOAD(hdr), opts_len,\n+                                  &len, &opt_len);\n+         in_opt;\n+         in_opt = next_dhcpv6_opt(DHCPV6_PAYLOAD(hdr), opts_len,\n+                                  &len, &opt_len)) {\n+        switch (ntohs(in_opt->code)) {\n         case DHCPV6_OPT_IA_NA_CODE:\n         {\n+            if (opt_len < sizeof(struct dhcpv6_opt_ia_na)) {\n+                break;\n+            }\n+\n             struct dhcpv6_opt_ia_na *opt_ia_na = (\n                 struct dhcpv6_opt_ia_na *)in_opt;\n             iaid = opt_ia_na->iaid;\n@@ -3177,21 +3237,30 @@ pinctrl_handle_put_dhcpv6_opts(\n             break;\n \n         case DHCPV6_OPT_USER_CLASS: {\n-            char *user_class = (char *)(in_opt + 1);\n-            if (!strcmp(user_class + 2, \"iPXE\")) {\n+            if (opt_len <\n+                sizeof(struct dhcpv6_opt_header) + DHCPV6_UC_PXE_OFFSET + 4) {\n+                break;\n+            }\n+\n+            const char *user_class = DHCPV6_OPT_PAYLOAD(in_opt);\n+            if (!strncmp(user_class + DHCPV6_UC_PXE_OFFSET, \"iPXE\", 4)) {\n                 ipxe_req = true;\n             }\n             break;\n         }\n \n         case DHCPV6_OPT_FQDN_CODE:\n-            fqdn_flags = *(in_dhcpv6_data + sizeof *in_opt);\n+            if (opt_len <\n+                sizeof(struct dhcpv6_opt_header) + sizeof(uint8_t)) {\n+                break;\n+            }\n+\n+            fqdn_flags = *(uint8_t *) DHCPV6_OPT_PAYLOAD(in_opt);\n             break;\n \n         default:\n             break;\n         }\n-        in_dhcpv6_data += sizeof *in_opt + ntohs(in_opt->len);\n     }\n \n     if (!in_opt_client_id) {\n@@ -3200,7 +3269,7 @@ pinctrl_handle_put_dhcpv6_opts(\n         goto exit;\n     }\n \n-    if (!iaid && in_dhcpv6_msg_type != DHCPV6_MSG_TYPE_INFO_REQ) {\n+    if (!iaid && hdr->msg_type != DHCPV6_MSG_TYPE_INFO_REQ) {\n         VLOG_WARN_RL(&rl, \"DHCPv6 option - IA NA not present in the \"\n                      \"DHCPv6 packet\");\n         goto exit;\ndiff --git a/lib/ovn-l7.h b/lib/ovn-l7.h\nindex 7f5673b12..5f52e1791 100644\n--- a/lib/ovn-l7.h\n+++ b/lib/ovn-l7.h\n@@ -312,6 +312,16 @@ extern const struct in6_addr in6addr_all_dhcp_agents;\n                                            0x00,0x00,0x00,0x00,0x00,0x00,     \\\n                                            0x00,0x01,0x00,0x02 } } }\n \n+#define DHCPV6_HEADER_LEN 4\n+OVS_PACKED(\n+struct dhcpv6_header {\n+    uint8_t msg_type;\n+    uint8_t transaction_id[3];\n+});\n+BUILD_ASSERT_DECL(DHCPV6_HEADER_LEN == sizeof(struct dhcpv6_header));\n+\n+#define DHCPV6_PAYLOAD(hdr) \\\n+    (const void *)((uint8_t *) (hdr) + sizeof(struct dhcpv6_header))\n \n #define DHCP6_OPT_HEADER_LEN 4\n OVS_PACKED(\ndiff --git a/tests/system-ovn.at b/tests/system-ovn.at\nindex 06c5c4b2c..582ed194b 100644\n--- a/tests/system-ovn.at\n+++ b/tests/system-ovn.at\n@@ -21748,3 +21748,69 @@ OVS_TRAFFIC_VSWITCHD_STOP([\"/failed to query port patch-.*/d\n \n AT_CLEANUP\n ])\n+\n+OVN_FOR_EACH_NORTHD([\n+AT_SETUP([DHCPv6 - Options heap overread])\n+AT_SKIP_IF([test $HAVE_SCAPY = no])\n+\n+ovn_start\n+\n+OVS_TRAFFIC_VSWITCHD_START()\n+ADD_BR([br-int])\n+\n+# Set external-ids in br-int needed for ovn-controller.\n+check ovs-vsctl \\\n+        -- set Open_vSwitch . external-ids:system-id=hv1 \\\n+        -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \\\n+        -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \\\n+        -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \\\n+        -- set bridge br-int fail-mode=secure other-config:disable-in-band=true\n+\n+start_daemon ovn-controller\n+\n+check ovn-nbctl ls-add ls1\n+check ovn-nbctl lsp-add ls1 ls1-lp1 \\\n+    -- lsp-set-addresses ls1-lp1 \"f0:00:00:00:00:01 fd10::4\"\n+\n+check_uuid ovn-nbctl -- --id=@opt create DHCP_Options cidr=\"fd10\\:\\:/64\" \\\n+    options=\"\\\"server_id\\\"=\\\"00:00:00:10:00:01\\\"\" \\\n+    -- set Logical_Switch_Port ls1-lp1 dhcpv6_options=[@opt]\n+\n+ADD_NAMESPACES(ls1-lp1)\n+ADD_VETH(ls1-lp1, ls1-lp1, br-int, \"fd10::4/96\", \"f0:00:00:00:00:01\", \\\n+         \"fd10::1\", \"nodad\")\n+\n+NETNS_START_TCPDUMP([ls1-lp1], [-nnne -i ls1-lp1 udp port 546 and udp port 547], [ls1-lp1])\n+\n+OVN_POPULATE_ARP\n+wait_for_ports_up\n+check ovn-nbctl --wait=hv sync\n+\n+ip netns exec ls1-lp1 scapy -H <<-EOF\n+p = Ether(dst='33:33:00:01:00:02', src='f0:00:00:00:00:01') / \\\n+    IPv6(dst='ff02::1:2', src='fe80::f200:ff:fe00:1') / \\\n+    UDP(sport=546, dport=547) / \\\n+    Raw(load=bytes.fromhex( \\\n+        '01' '010203' \\\n+        '0003' '000c' '01020304' '00000000' '00000000' \\\n+        '0001' '0200' \\\n+        '0003' '0001' \\\n+        'f00000000001'\n+    ))\n+sendp (p, iface='ls1-lp1', loop = 0, verbose = 0, count = 1)\n+EOF\n+\n+# ovn-contorller should report a warning that the packet didn't contain valid Client ID.\n+OVS_WAIT_UNTIL([grep -q \"DHCPv6 option - Client id not present\" ovn-controller.log])\n+AT_CHECK([grep -q \"advertise\" ls1-lp1.tcpdump], [1])\n+\n+OVN_CLEANUP_CONTROLLER([hv1])\n+OVN_CLEANUP_NORTHD\n+\n+as\n+OVS_TRAFFIC_VSWITCHD_STOP([\"/failed to query port patch-.*/d\n+/connection dropped.*/d\n+/DHCPv6 option - Client id not present.*/d\"])\n+\n+AT_CLEANUP\n+])\n","prefixes":["ovs-dev","2/3"]}