From: Alejandro Pacheco <[email protected]>
parse_nsh() rejects an NSH header that does not fit in the packet, but
the flow keeps dl_type NSH, so a set(nsh) action can still be executed
for it.  odp_set_nsh() then rewrites the base header and the MD type 1
context without checking that they are in the packet.  pop_nsh() also
trusts the header length from the packet.

Add nsh_hdr_is_valid() with the checks of parse_nsh() and use it in
odp_set_nsh() and pop_nsh().  odp_set_nsh() now takes the MD type from
the md_type byte as parse_nsh() does, and zeroes the key and mask, as
the action may not carry the MD type 1 context.

Fixes: 3d2fbd70bda5 ("userspace: Add support for NSH MD1 match fields")
Fixes: 1fc11c5948cf ("Generic encap and decap support for NSH")
Fixes: 9a180f2c002a ("NSH: Adjust NSH wire format to the latest IETF draft")
Assisted-by: claude-opus-5-5, Claude Code
Signed-off-by: Alejandro Pacheco <[email protected]>
---
 lib/odp-execute.c | 13 ++++++++++---
 lib/packets.c     |  4 ++++
 lib/packets.h     | 27 +++++++++++++++++++++++++++
 tests/nsh.at      | 36 ++++++++++++++++++++++++++++++++++++
 4 files changed, 77 insertions(+), 3 deletions(-)

diff --git a/lib/odp-execute.c b/lib/odp-execute.c
index 3bec8ee00..a1f338271 100644
--- a/lib/odp-execute.c
+++ b/lib/odp-execute.c
@@ -390,16 +390,23 @@ odp_set_nd(struct dp_packet *packet, const struct 
ovs_key_nd *key,
     }
 }
 
-/* Set the NSH header. Assumes the NSH header is present and matches the
- * MD format of the key. The slow path must take case of that. */
+/* Set the NSH header.  Leaves the packet unchanged if it does not hold a valid
+ * NSH header.  Only the MD type 1 context is rewritten. */
 static void
 odp_set_nsh(struct dp_packet *packet, const struct nlattr *a, bool has_mask)
 {
     struct ovs_key_nsh key, mask;
     struct nsh_hdr *nsh = dp_packet_l3(packet);
-    uint8_t mdtype = nsh_md_type(nsh);
+    uint8_t mdtype;
     ovs_be32 path_hdr;
 
+    if (!nsh || !nsh_hdr_is_valid(nsh, dp_packet_l3_size(packet))) {
+        return;
+    }
+    mdtype = nsh->md_type;
+
+    memset(&key, 0, sizeof key);
+    memset(&mask, 0, sizeof mask);
     if (has_mask) {
         odp_nsh_key_from_attr(a, &key, &mask, NULL);
     } else {
diff --git a/lib/packets.c b/lib/packets.c
index 435aa8f9a..b0090fc44 100644
--- a/lib/packets.c
+++ b/lib/packets.c
@@ -530,6 +530,10 @@ pop_nsh(struct dp_packet *packet)
     uint32_t next_pt;
 
     if (packet->packet_type == htonl(PT_NSH) && nsh) {
+        if (!nsh_hdr_is_valid(nsh, dp_packet_l3_size(packet))) {
+            return false;
+        }
+
         switch (nsh->next_proto) {
             case NSH_P_ETHERNET:
                 next_pt = PT_ETH;
diff --git a/lib/packets.h b/lib/packets.h
index ea0bda840..189c5e283 100644
--- a/lib/packets.h
+++ b/lib/packets.h
@@ -453,6 +453,33 @@ void push_eth(struct dp_packet *packet, const struct 
eth_addr *dst,
               const struct eth_addr *src);
 void pop_eth(struct dp_packet *packet);
 
+/* Returns true if the 'size' bytes at 'nsh' start with an NSH header that
+ * flow parsing accepts: version 0, a length that fits within 'size' and, for
+ * MD type 1, the full fixed-length context. */
+static inline bool
+nsh_hdr_is_valid(const struct nsh_hdr *nsh, size_t size)
+{
+    size_t length;
+
+    if (size < NSH_BASE_HDR_LEN || nsh_get_ver(nsh) != 0) {
+        return false;
+    }
+
+    length = nsh_hdr_len(nsh);
+    if (length > size) {
+        return false;
+    }
+
+    switch (nsh->md_type) {
+    case NSH_M_TYPE1:
+        return length == NSH_M_TYPE1_LEN;
+    case NSH_M_TYPE2:
+        return length >= NSH_BASE_HDR_LEN;
+    default:
+        return true;
+    }
+}
+
 void push_nsh(struct dp_packet *packet, const struct nsh_hdr *nsh_hdr_src);
 bool pop_nsh(struct dp_packet *packet);
 
diff --git a/tests/nsh.at b/tests/nsh.at
index 2440addc5..4f689c456 100644
--- a/tests/nsh.at
+++ b/tests/nsh.at
@@ -86,6 +86,42 @@ Datapath actions: 
set(nsh(flags=2,ttl=63,mdtype=1,np=3,spi=0x123456,si=254,c1=0x
 OVS_VSWITCHD_STOP
 AT_CLEANUP
 
+AT_SETUP([nsh - set on truncated header])
+OVS_VSWITCHD_START
+add_of_ports --pcap br0 1 2
+
+AT_DATA([flows.txt], [dnl
+table=0,in_port=1,dl_type=0x894f,actions=set_field:0x123456->nsh_spi,2
+])
+AT_CHECK([ovs-ofctl -Oopenflow13 add-flows br0 flows.txt])
+
+dnl MD type 1 header with a length of 24 bytes, but only 12 bytes present.
+m4_define([NSH_SHORT_PKT], m4_join([],
+dnl eth(src=00:00:00:00:00:01,dst=00:00:00:00:00:02,type=0x894f)
+[000000000002000000000001894f],
+dnl nsh(ttl=63,len=6,mdtype=1,np=3,spi=0x1,si=255)
+[0fc60103000001ff],
+dnl First 4 of the 16 bytes of MD type 1 context.
+[11223344]))
+
+dnl Unknown MD type 0x11 with a length of 8 bytes.  Only the base header
+dnl is rewritten.
+m4_define([NSH_MD11_PKT], m4_join([],
+dnl eth(src=00:00:00:00:00:01,dst=00:00:00:00:00:02,type=0x894f)
+[000000000002000000000001894f],
+dnl nsh(ttl=63,len=2,mdtype=0x11,np=3,spi=0xa,si=255)
+[0fc2110300000aff]))
+
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 NSH_SHORT_PKT])
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 NSH_MD11_PKT])
+
+dnl The first header is not valid, so that packet is forwarded unmodified.
+OVS_WAIT_UNTIL_EQUAL([ovs-pcap p2-tx.pcap], [NSH_SHORT_PKT
+m4_join([], [000000000002000000000001894f], [0fc21103123456ff])])
+
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
 
 ### -----------------------------------------------------------------
 ###   NSH MD1 on Ethernet encapsulation over veth link
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to