xfrm4_transport_output() reads the IPv4 header length (ihl) from the
packet and uses it for __skb_pull() and memmove() without validating it,
so a frame with ihl * 4 larger than the linear header underflows skb->len
to a huge value or trips BUG() in __skb_pull().
Packets injected into an xfrm interface (e.g. AF_PACKET on an xfrmi
device) reach xfrm output without the header validation that
ip_rcv_core() applies to received traffic. The underflowed length then
flows into esp_output() and the crypto scatterlist setup, where every
length in the underflow window ends in a fatal fault (BUG_ON in
__skb_to_sgvec()). This is a deterministic local denial of service
reachable by an unprivileged user through a user and network namespace.
Reject ihl < 5, matching ip_rcv_core(), so a complete IPv4 header is
present. Then use pskb_may_pull() to make the header linear: this
rejects ihl > skb->len, linearizes a header that currently lives in
fragments, and guarantees __skb_pull()'s precondition that the pull must
not drive skb->len below skb->data_len, and it keeps the memmove() source
in bounds. ip_hdr() is re-read afterwards because pskb_may_pull() may
reallocate the skb head. xfrm6_transport_output() already bounds its
header length through xfrm6_hdr_offset().
Fixes: b59f45d0b2878 ("[IPSEC] xfrm: Abstract out encapsulation modes")
Cc: stable@vger.kernel.org
Signed-off-by: Qihang <redacted>
---
net/xfrm/xfrm_output.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/net/xfrm/xfrm_output.c b/net/xfrm/xfrm_output.c
index e305ba3..86943e2 100644
--- a/net/xfrm/xfrm_output.c
+++ b/net/xfrm/xfrm_output.c
@@ -66,6 +66,11 @@ static int xfrm4_transport_output(struct xfrm_state *x, struct sk_buff *skb)
struct iphdr *iph = ip_hdr(skb);
int ihl = iph->ihl * 4;
+ if (iph->ihl < 5 || !pskb_may_pull(skb, ihl))
+ return -EINVAL;
+
+ iph = ip_hdr(skb);
+
if (!skb->inner_protocol)
skb_set_inner_transport_header(skb,
skb_transport_offset(skb));
--
2.46.0