netdev_gtpu_pop_header() only checks that the packet holds the UDP and
the mandatory GTP-U headers. For a G-PDU with the S flag set it then
adds the 4-byte optional field to the header length and strips that
many bytes without checking them against the packet size.
It also locates the inner IP header at 'gtph + gtpu_hlen', but
'gtpu_hlen' already includes the UDP header and 'gtph' points past it,
so the version is read 8 bytes into the inner packet, which can be past
the end of the packet data.
Compute the full header length first, drop the packet if it does not
fit, and read the inner IP version at the offset that is actually
popped, only when there is a payload.
Fixes: 3c6d05a02e0f ("userspace: Add GTP-U support.")
Assisted-by: claude-opus-5-5, Claude Code
Signed-off-by: Alejandro Pacheco <[email protected]>
---
lib/netdev-native-tnl.c | 22 ++++++++++-----
tests/tunnel-push-pop.at | 60 ++++++++++++++++++++++++++++++++++++++++
2 files changed, 75 insertions(+), 7 deletions(-)
diff --git a/lib/netdev-native-tnl.c b/lib/netdev-native-tnl.c
index f27b8b692..dc14542ec 100644
--- a/lib/netdev-native-tnl.c
+++ b/lib/netdev-native-tnl.c
@@ -838,18 +838,26 @@ netdev_gtpu_pop_header(struct dp_packet *packet)
tnl->tun_id = be32_to_be64(get_16aligned_be32(>ph->teid));
if (tnl->gtpu_msgtype == GTPU_MSGTYPE_GPDU) {
- struct ip_header *ip;
+ uint8_t ip_ver = 0;
+ gtpu_hlen = GTPU_HLEN;
if (gtph->md.flags & GTPU_S_MASK) {
- gtpu_hlen = GTPU_HLEN + sizeof(struct gtpuhdr_opt);
- } else {
- gtpu_hlen = GTPU_HLEN;
+ gtpu_hlen += sizeof(struct gtpuhdr_opt);
+ }
+ if (gtpu_hlen > dp_packet_l4_size(packet)) {
+ goto err;
+ }
+ if (gtpu_hlen < dp_packet_l4_size(packet)) {
+ const struct ip_header *ip;
+
+ ip = ALIGNED_CAST(const struct ip_header *,
+ (char *) dp_packet_l4(packet) + gtpu_hlen);
+ ip_ver = IP_VER(ip->ip_ihl_ver);
}
- ip = ALIGNED_CAST(struct ip_header *, (char *)gtph + gtpu_hlen);
- if (IP_VER(ip->ip_ihl_ver) == 4) {
+ if (ip_ver == 4) {
packet->packet_type = htonl(PT_IPV4);
- } else if (IP_VER(ip->ip_ihl_ver) == 6) {
+ } else if (ip_ver == 6) {
packet->packet_type = htonl(PT_IPV6);
} else {
VLOG_WARN_RL(&err_rl, "GTP-U: Receive non-IP packet.");
diff --git a/tests/tunnel-push-pop.at b/tests/tunnel-push-pop.at
index ab393cfc7..74e43fc82 100644
--- a/tests/tunnel-push-pop.at
+++ b/tests/tunnel-push-pop.at
@@ -193,6 +193,66 @@ OVS_WAIT_UNTIL([test `ovs-pcap p0.pcap | grep
100022eb0000000120000237 | wc -l`
OVS_VSWITCHD_STOP
AT_CLEANUP
+AT_SETUP([tunnel_push_pop - gtpu decap with sequence number])
+OVS_VSWITCHD_START([add-port br0 p0 -- set Interface p0 type=dummy
ofport_request=1 other-config:hwaddr=aa:55:aa:55:00:00])
+AT_CHECK([ovs-vsctl add-br int-br -- set bridge int-br
datapath_type=dummy], [0])
+AT_CHECK([ovs-vsctl add-port int-br t1 -- set Interface t1 type=gtpu \
+ options:remote_ip=1.1.2.92 options:key=123
ofport_request=2])
+
+AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 1.1.2.88/24], [0], [OK
+])
+AT_CHECK([ovs-ofctl add-flow br0 action=normal])
+AT_CHECK([ovs-ofctl add-flow int-br "cookie=0x1,in_port=2,ip,actions=drop"])
+AT_CHECK([ovs-appctl netdev-dummy/receive p0
'recirc_id(0),in_port(100),eth(src=f8:bc:12:44:34:b6,dst=ff:ff:ff:ff:ff:ff),eth_type(0x0806),arp(sip=1.1.2.92,tip=1.1.2.88,op=2,sha=f8:bc:12:44:34:b6,tha=00:00:00:00:00:00)'])
+AT_CHECK([ovs-appctl time/warp 1000], [0], [ignore])
+AT_CHECK([ovs-appctl revalidator/wait])
+
+dnl G-PDU with the S flag set is decapsulated as an IPv4 packet.
+m4_define([GTPU_SEQ_PKT], m4_join([],
+dnl eth(src=00:1b:21:3c:ab:64,dst=aa:55:aa:55:00:00,type=0x0800)
+[aa55aa550000001b213cab640800],
+dnl ipv4(src=1.1.2.92,dst=1.1.2.88,proto=17,tot_len=68)
+[4500004400010000401173f30101025c01010258],
+dnl udp(src=4789,dst=2152,len=48)
+[12b5086800300000],
+dnl gtpu(flags=0x32,msgtype=255,len=32,teid=123)
+[32ff00200000007b],
+dnl gtpu optional field (sequence number, N-PDU, next extension).
+[00000000],
+dnl ipv4(src=30.0.0.1,dst=30.0.0.2,proto=1,tot_len=28)
+[4500001c000100008001fedd1e0000011e000002],
+dnl icmp(type=0,code=0)
+[0000ffff00000000]))
+
+AT_CHECK([ovs-appctl netdev-dummy/receive p0 GTPU_SEQ_PKT])
+AT_CHECK([ovs-appctl time/warp 1000], [0], [ignore])
+AT_CHECK([ovs-ofctl dump-ports int-br | grep 'port 2'], [0], [dnl
+ port 2: rx pkts=1, bytes=28, drop=?, errs=?, frame=?, over=?, crc=?
+])
+AT_CHECK([ovs-ofctl dump-flows int-br | grep 'cookie=0x1,' | grep -c
'n_packets=1,'], [0], [1
+])
+
+dnl G-PDU with the S flag set, but without the optional field, is dropped.
+m4_define([GTPU_SHORT_PKT], m4_join([],
+dnl eth(src=00:1b:21:3c:ab:64,dst=aa:55:aa:55:00:00,type=0x0800)
+[aa55aa550000001b213cab640800],
+dnl ipv4(src=1.1.2.92,dst=1.1.2.88,proto=17,tot_len=36)
+[4500002400010000401174130101025c01010258],
+dnl udp(src=4789,dst=2152,len=16)
+[12b5086800100000],
+dnl gtpu(flags=0x32,msgtype=255,len=0,teid=123)
+[32ff00000000007b]))
+
+AT_CHECK([ovs-appctl netdev-dummy/receive p0 GTPU_SHORT_PKT])
+AT_CHECK([ovs-appctl time/warp 1000], [0], [ignore])
+AT_CHECK([ovs-ofctl dump-ports int-br | grep 'port 2'], [0], [dnl
+ port 2: rx pkts=1, bytes=28, drop=?, errs=?, frame=?, over=?, crc=?
+])
+CHECK_COVERAGE([datapath_drop_tunnel_pop_error], [1])
+
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
AT_SETUP([tunnel_push_pop - action])
OVS_VSWITCHD_START([add-port br0 p0 -- set Interface p0 type=dummy
ofport_request=1 other-config:hwaddr=aa:55:aa:55:00:00])
--
2.53.0
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev