[PATCH net] xfrm: enforce hard byte lifetime in xfrm_input()

Yuxiang Yang posted 1 patch 10 hours ago
net/xfrm/xfrm_input.c | 20 +++++++++++++++++++-
1 file changed, 19 insertions(+), 1 deletion(-)
[PATCH net] xfrm: enforce hard byte lifetime in xfrm_input()
Posted by Yuxiang Yang 10 hours ago
xfrm_input() checks an inbound state's hard lifetime before processing
the current packet, but accounts the packet only after the transform
succeeds. The check therefore sees only the previous byte count.

If the current packet makes the new count exceed the hard byte limit,
that packet is still delivered. The state is not expired until the next
matching packet arrives. The effect is limited to the packet that crosses
the configured limit.

This affects only states configured with a finite hard byte limit. The
default XFRM_INF lifetime is unaffected. IPComp makes the issue especially
visible because decompression can increase skb->len before it is accounted.

Account the actual post-transform length, then enforce the finite hard byte
limit before delivering the packet. Use a strict comparison so a packet
that brings the count exactly to the limit remains accepted.

Saturate the byte counter at XFRM_INF - 1 because XFRM_INF is also the
unlimited-lifetime sentinel. Carry a separate saturation flag so a finite
lifetime still expires if the addition overflows or reaches the sentinel.

The transform and replay update have already completed when an overshooting
packet is dropped. Checking the compressed length before the transform
would not enforce the correct limit because IPComp can increase skb->len
during decompression.

With this change applied to net at 78f75d632f74, a local QEMU/TCG test
dropped a 136-byte packet at a hard limit of 135 and accepted the same
packet at limits of 136 and 100000. XfrmInStateExpired increased by 1, 0,
and 0, respectively. A characterization-only kprobe module seeded
curlft.bytes at U64_MAX - 100 for an unlimited lifetime; two packets were
delivered, the counter saturated at U64_MAX - 1, and XfrmInStateExpired
did not change.

This change is limited to hard byte lifetime handling in xfrm_input().
xfrm6_input_addr() and the output path use separate byte accounting and
are not changed here.

The faulty ordering predates the available Git development history, so
there is no accurate introducing commit to reference with a Fixes tag.

Cc: stable@vger.kernel.org
Signed-off-by: Yuxiang Yang <yangyx22@mails.tsinghua.edu.cn>
---
 net/xfrm/xfrm_input.c | 20 +++++++++++++++++++-
 1 file changed, 19 insertions(+), 1 deletion(-)

diff --git a/net/xfrm/xfrm_input.c b/net/xfrm/xfrm_input.c
index eecab337b..6846ccb39 100644
--- a/net/xfrm/xfrm_input.c
+++ b/net/xfrm/xfrm_input.c
@@ -14,6 +14,7 @@
 #include <linux/slab.h>
 #include <linux/module.h>
 #include <linux/netdevice.h>
+#include <linux/overflow.h>
 #include <linux/percpu.h>
 #include <net/dst.h>
 #include <net/ip.h>
@@ -582,6 +583,9 @@ int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type)
 	daddr = (xfrm_address_t *)(skb_network_header(skb) +
 				   XFRM_SPI_SKB_CB(skb)->daddroff);
 	do {
+		bool saturated;
+		u64 bytes, len;
+
 		sp = skb_sec_path(skb);
 
 		if (sp->len == XFRM_MAX_DEPTH) {
@@ -689,10 +693,24 @@ int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type)
 
 		xfrm_replay_advance(x, seq);
 
-		x->curlft.bytes += skb->len;
+		len = skb->len;
+		saturated = check_add_overflow(x->curlft.bytes, len, &bytes);
+		if (saturated || bytes == XFRM_INF) {
+			bytes = XFRM_INF - 1;
+			saturated = true;
+		}
+		x->curlft.bytes = bytes;
 		x->curlft.packets++;
 		x->lastused = ktime_get_real_seconds();
 
+		/* IPComp may expand skb->len during the input transform. */
+		if (x->lft.hard_byte_limit != XFRM_INF &&
+		    (saturated || bytes > x->lft.hard_byte_limit)) {
+			xfrm_state_check_expire(x);
+			XFRM_INC_STATS(net, LINUX_MIB_XFRMINSTATEEXPIRED);
+			goto drop_unlock;
+		}
+
 		spin_unlock(&x->lock);
 
 		XFRM_MODE_SKB_CB(skb)->protocol = nexthdr;
-- 
2.34.1