On Thu, Feb 26, 2026 at 5:59 AM Eelco Chaudron via dev <
[email protected]> wrote:

> Coverity reports multiple untrusted loop bound and buffer access issues
> (CID 278410, and related) in format_odp_tnl_push_header() when processing
> tunnel headers. The function casts parts of ovs_action_push_tnl->header
> to various tunnel protocol structures and uses length fields from those
> structures without validating they stay within buffer bounds.
>
> The ovs_action_push_tnl->header buffer is fixed at 512 bytes
> (TNL_PUSH_HEADER_SIZE), but the function was parsing variable-length
> structures without checking that accesses remain within header_len:
>
> - Geneve options: opt_len * 4 bytes could exceed buffer
> - SRv6 segments: (last_entry + 1) * 16 bytes could exceed buffer
> - GRE options: checksum, key, sequence fields parsed without validation
> - ERSPAN metadata: version-specific fields accessed without bounds check
> - GTPU headers: no validation before dereferencing
>
> Fixes: f5796d539cdb ("Format and commit the encap action tunnel header.")
>

The code change looks fine, however, I cannot find this commit.

Cheers,
M


> Signed-off-by: Eelco Chaudron <[email protected]>
> ---
>  lib/odp-util.c | 130 ++++++++++++++++++++++++++++++++++++++++++++++---
>  1 file changed, 122 insertions(+), 8 deletions(-)
>
> diff --git a/lib/odp-util.c b/lib/odp-util.c
> index e293388d2..809fdb5fd 100644
> --- a/lib/odp-util.c
> +++ b/lib/odp-util.c
> @@ -631,8 +631,14 @@ format_odp_hash_action(struct ds *ds, const struct
> ovs_action_hash *hash_act)
>  }
>
>  static const void *
> -format_udp_tnl_push_header(struct ds *ds, const struct udp_header *udp)
> +format_udp_tnl_push_header(struct ds *ds, const struct udp_header *udp,
> +                           uint32_t bytes_left)
>  {
> +    if (bytes_left < sizeof *udp) {
> +        ds_put_cstr(ds, "udp(truncated header))");
> +        return NULL;
> +    }
> +
>      ds_put_format(ds,
> "udp(src=%"PRIu16",dst=%"PRIu16",csum=0x%"PRIx16"),",
>                    ntohs(udp->udp_src), ntohs(udp->udp_dst),
>                    ntohs(udp->udp_csum));
> @@ -644,13 +650,23 @@ static void
>  format_odp_tnl_push_header(struct ds *ds, struct ovs_action_push_tnl
> *data)
>  {
>      const struct eth_header *eth;
> +    const struct udp_header *udp;
> +    uint32_t bytes_left;
>      const void *l3;
>      const void *l4;
> -    const struct udp_header *udp;
>
>      eth = (const struct eth_header *)data->header;
>
> +    if (data->header_len < sizeof *eth) {
> +        ds_put_format(ds,
> +                      "header(size=%"PRIu32",type=%"PRIu32
> +                      ", eth(truncated header))",
> +                      data->header_len, data->tnl_type);
> +        return;
> +    }
> +
>      l3 = eth + 1;
> +    bytes_left = data->header_len - sizeof *eth;
>
>      /* Ethernet */
>      ds_put_format(ds, "header(size=%"PRIu32",type=%"PRIu32",eth(dst=",
> @@ -663,6 +679,12 @@ format_odp_tnl_push_header(struct ds *ds, struct
> ovs_action_push_tnl *data)
>      if (eth->eth_type == htons(ETH_TYPE_IP)) {
>          /* IPv4 */
>          const struct ip_header *ip = l3;
> +
> +        if (bytes_left < sizeof *ip) {
> +            ds_put_cstr(ds, "ipv4(truncated header))");
> +            return;
> +        }
> +
>          ds_put_format(ds, "ipv4(src="IP_FMT",dst="IP_FMT",proto=%"PRIu8
>                        ",tos=%#"PRIx8",ttl=%"PRIu8",frag=0x%"PRIx16"),",
>                        IP_ARGS(get_16aligned_be32(&ip->ip_src)),
> @@ -671,6 +693,7 @@ format_odp_tnl_push_header(struct ds *ds, struct
> ovs_action_push_tnl *data)
>                        ip->ip_ttl,
>                        ntohs(ip->ip_frag_off));
>          l4 = (ip + 1);
> +        bytes_left -= sizeof *ip;
>      } else {
>          const struct ovs_16aligned_ip6_hdr *ip6 = l3;
>          struct in6_addr src, dst;
> @@ -678,6 +701,11 @@ format_odp_tnl_push_header(struct ds *ds, struct
> ovs_action_push_tnl *data)
>          memcpy(&dst, &ip6->ip6_dst, sizeof dst);
>          uint32_t ipv6_flow = ntohl(get_16aligned_be32(&ip6->ip6_flow));
>
> +        if (bytes_left < sizeof *ip6) {
> +            ds_put_cstr(ds, "ipv6(truncated header))");
> +            return;
> +        }
> +
>          ds_put_format(ds, "ipv6(src=");
>          ipv6_format_addr(&src, ds);
>          ds_put_format(ds, ",dst=");
> @@ -687,6 +715,7 @@ format_odp_tnl_push_header(struct ds *ds, struct
> ovs_action_push_tnl *data)
>                        ipv6_flow & IPV6_LABEL_MASK, ip6->ip6_nxt,
>                        (ipv6_flow >> 20) & 0xff, ip6->ip6_hlim);
>          l4 = (ip6 + 1);
> +        bytes_left -= sizeof *ip6;
>      }
>
>      udp = (const struct udp_header *) l4;
> @@ -694,7 +723,16 @@ format_odp_tnl_push_header(struct ds *ds, struct
> ovs_action_push_tnl *data)
>      if (data->tnl_type == OVS_VPORT_TYPE_VXLAN) {
>          const struct vxlanhdr *vxh;
>
> -        vxh = format_udp_tnl_push_header(ds, udp);
> +        vxh = format_udp_tnl_push_header(ds, udp, bytes_left);
> +        if (!vxh) {
> +            return;
> +        }
> +
> +        bytes_left -= sizeof *udp;
> +        if (bytes_left < sizeof *vxh) {
> +            ds_put_cstr(ds, "vxlan(truncated header))");
> +            return;
> +        }
>
>          ds_put_format(ds, "vxlan(flags=0x%"PRIx32",vni=0x%"PRIx32")",
>                        ntohl(get_16aligned_be32(&vxh->vx_flags)),
> @@ -702,14 +740,29 @@ format_odp_tnl_push_header(struct ds *ds, struct
> ovs_action_push_tnl *data)
>      } else if (data->tnl_type == OVS_VPORT_TYPE_GENEVE) {
>          const struct genevehdr *gnh;
>
> -        gnh = format_udp_tnl_push_header(ds, udp);
> +        gnh = format_udp_tnl_push_header(ds, udp, bytes_left);
> +        if (!gnh) {
> +            return;
> +        }
> +
> +        bytes_left -= sizeof *udp;
> +        if (bytes_left < sizeof *gnh) {
> +            ds_put_cstr(ds, "geneve(truncated header))");
> +            return;
> +        }
>
>          ds_put_format(ds, "geneve(%s%svni=0x%"PRIx32,
>                        gnh->oam ? "oam," : "",
>                        gnh->critical ? "crit," : "",
>                        ntohl(get_16aligned_be32(&gnh->vni)) >> 8);
>
> +        bytes_left -= sizeof *gnh;
>          if (gnh->opt_len) {
> +            if (bytes_left < gnh->opt_len * sizeof(uint32_t)) {
> +                ds_put_cstr(ds, ",options(invalid length)))");
> +                return;
> +            }
> +
>              ds_put_cstr(ds, ",options(");
>              format_geneve_opts(gnh->options, NULL, gnh->opt_len * 4,
>                                 ds, false);
> @@ -724,12 +777,23 @@ format_odp_tnl_push_header(struct ds *ds, struct
> ovs_action_push_tnl *data)
>          int i;
>
>          srh = (const struct srv6_base_hdr *) l4;
> +        if (bytes_left < sizeof *srh) {
> +            ds_put_cstr(ds, "srv6(truncated header))");
> +            return;
> +        }
> +
>          segs = ALIGNED_CAST(struct in6_addr *, srh + 1);
>          nr_segs = srh->last_entry + 1;
> +        bytes_left -= sizeof *srh;
>
>          ds_put_format(ds, "srv6(");
>          ds_put_format(ds, "segments_left=%d", srh->rt_hdr.segments_left);
>          ds_put_format(ds, ",segs(");
> +        if (bytes_left < nr_segs * sizeof *segs) {
> +            ds_put_cstr(ds, "truncated segs)))");
> +            return;
> +        }
> +
>          for (i = 0; i < nr_segs; i++) {
>              ds_put_format(ds, i > 0 ? "," : "");
>              ipv6_format_addr(&segs[nr_segs - i - 1], ds);
> @@ -741,20 +805,45 @@ format_odp_tnl_push_header(struct ds *ds, struct
> ovs_action_push_tnl *data)
>          ovs_16aligned_be32 *options;
>
>          greh = (const struct gre_base_hdr *) l4;
> +        if (bytes_left < sizeof *greh) {
> +            ds_put_cstr(ds, "gre(truncated header))");
> +            return;
> +        }
>
>          ds_put_format(ds, "gre((flags=0x%"PRIx16",proto=0x%"PRIx16")",
>                             ntohs(greh->flags), ntohs(greh->protocol));
> +        bytes_left -= sizeof *greh;
>          options = (ovs_16aligned_be32 *)(greh + 1);
>          if (greh->flags & htons(GRE_CSUM)) {
> -            ds_put_format(ds, ",csum=0x%"PRIx16, ntohs(*((ovs_be16
> *)options)));
> +            if (bytes_left < sizeof(uint16_t)) {
> +                ds_put_cstr(ds, ",csum=<truncated>))");
> +                return;
> +            }
> +
> +            bytes_left -= sizeof(uint16_t);
> +            ds_put_format(ds, ",csum=0x%"PRIx16,
> +                          ntohs(*((ovs_be16 *) options)));
>              options++;
>          }
>          if (greh->flags & htons(GRE_KEY)) {
> -            ds_put_format(ds, ",key=0x%"PRIx32,
> ntohl(get_16aligned_be32(options)));
> +            if (bytes_left < sizeof(uint32_t)) {
> +                ds_put_cstr(ds, ",key=<truncated>))");
> +                return;
> +            }
> +
> +            bytes_left -= sizeof(uint32_t);
> +            ds_put_format(ds, ",key=0x%"PRIx32,
> +                          ntohl(get_16aligned_be32(options)));
>              options++;
>          }
>          if (greh->flags & htons(GRE_SEQ)) {
> -            ds_put_format(ds, ",seq=0x%"PRIx32,
> ntohl(get_16aligned_be32(options)));
> +            if (bytes_left < sizeof(uint32_t)) {
> +                ds_put_cstr(ds, ",seq=<truncated>))");
> +                return;
> +            }
> +
> +            ds_put_format(ds, ",seq=0x%"PRIx32,
> +                          ntohl(get_16aligned_be32(options)));
>              options++;
>          }
>          ds_put_format(ds, ")");
> @@ -764,16 +853,32 @@ format_odp_tnl_push_header(struct ds *ds, struct
> ovs_action_push_tnl *data)
>          const struct erspan_base_hdr *ersh;
>
>          greh = (const struct gre_base_hdr *) l4;
> +        if (bytes_left < ERSPAN_GREHDR_LEN + sizeof *ersh) {
> +            ds_put_cstr(ds, "erspan(truncated header))");
> +            return;
> +        }
> +
>          ersh = ERSPAN_HDR(greh);
> +        bytes_left -= ERSPAN_GREHDR_LEN + sizeof *ersh;
>
>          if (ersh->ver == 1) {
>              ovs_16aligned_be32 *index = ALIGNED_CAST(ovs_16aligned_be32 *,
>                                                       ersh + 1);
> +
> +            if (bytes_left < sizeof *index) {
> +                ds_put_cstr(ds, "erspan(ver=1,truncated header))");
> +                return;
> +            }
>              ds_put_format(ds,
> "erspan(ver=1,sid=0x%"PRIx16",idx=0x%"PRIx32")",
>                            get_sid(ersh),
> ntohl(get_16aligned_be32(index)));
>          } else if (ersh->ver == 2) {
>              struct erspan_md2 *md2 = ALIGNED_CAST(struct erspan_md2 *,
>                                                    ersh + 1);
> +
> +            if (bytes_left < sizeof *md2) {
> +                ds_put_cstr(ds, "erspan(ver=2, truncated header))");
> +                return;
> +            }
>              ds_put_format(ds, "erspan(ver=2,sid=0x%"PRIx16
>                            ",dir=%"PRIu8",hwid=0x%"PRIx8")",
>                            get_sid(ersh), md2->dir, get_hwid(md2));
> @@ -783,7 +888,16 @@ format_odp_tnl_push_header(struct ds *ds, struct
> ovs_action_push_tnl *data)
>      } else if (data->tnl_type == OVS_VPORT_TYPE_GTPU) {
>          const struct gtpuhdr *gtph;
>
> -        gtph = format_udp_tnl_push_header(ds, udp);
> +        gtph = format_udp_tnl_push_header(ds, udp, bytes_left);
> +        if (!gtph) {
> +            return;
> +        }
> +
> +        bytes_left -= sizeof *udp;
> +         if (bytes_left < sizeof gtph) {
> +            ds_put_cstr(ds, "gtpu(truncated header))");
> +            return;
> +        }
>
>          ds_put_format(ds, "gtpu(flags=0x%"PRIx8
>                            ",msgtype=%"PRIu8",teid=0x%"PRIx32")",
> --
> 2.52.0
>
> _______________________________________________
> dev mailing list
> [email protected]
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>
>
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to