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.

This change ensures we never read beyond the data->header buffer when
formatting tunnel push actions.

Fixes: a36de779d739 ("openvswitch: Userspace tunneling.")
Signed-off-by: Eelco Chaudron <[email protected]>
---
v2:
  - Updated Fixes: tag.
  - Updated commit message.
---
 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

Reply via email to