esp6_input_done2() computes the transport header offset with ipv6_skip_exthdr(), which walks the extension header chain using the packet's self-reported header lengths without ever comparing the running offset against skb->len, so it can return an offset past the end of the packet. esp6_input_done2() then reads the 2-byte source port at skb->data + offset out of bounds. esp4's esp_input_done2() has the same class of problem from a different direction: ihl is taken from the decrypted inner packet and is not re-validated after esp_remove_trailer() shrank the skb, so the 2-byte source port read at skb_network_header + ihl can land past the new end of the packet. Reject the packet with -EINVAL when there is no room for the port at the computed offset. This issue was discovered by Tencent CodeBuddy Security. Fixes: e27cca96cd68 ("xfrm: add espintcp (RFC 8229)") Signed-off-by: Henry Martin --- net/ipv4/esp4.c | 12 ++++++++++++ net/ipv6/esp6.c | 11 +++++++++++ 2 files changed, 23 insertions(+) diff --git a/net/ipv4/esp4.c b/net/ipv4/esp4.c index e76db5817e78e..aaff5a2b659db 100644 --- a/net/ipv4/esp4.c +++ b/net/ipv4/esp4.c @@ -744,6 +744,18 @@ int esp_input_done2(struct sk_buff *skb, int err) struct udphdr *uh = (void *)(skb_network_header(skb) + ihl); __be16 source; + /* + * ihl is taken from the decrypted inner packet and is not + * re-validated after esp_remove_trailer() shrank the skb; + * make sure the 2-byte source port we read below is within + * the packet. + */ + if (skb_network_offset(skb) + ihl + sizeof(__be16) > + skb->len) { + err = -EINVAL; + goto out; + } + switch (x->encap->encap_type) { case TCP_ENCAP_ESPINTCP: source = th->source; diff --git a/net/ipv6/esp6.c b/net/ipv6/esp6.c index b1c9b36f76dc4..89e8cdf5a26b2 100644 --- a/net/ipv6/esp6.c +++ b/net/ipv6/esp6.c @@ -782,6 +782,17 @@ int esp6_input_done2(struct sk_buff *skb, int err) goto out; } + /* + * ipv6_skip_exthdr() can advance past the packet end when the + * last extension header claims an oversized length; the + * transport header below would then be read out of bounds. + * Make sure there is room for the 2-byte source port we read. + */ + if (offset + sizeof(__be16) > skb->len) { + err = -EINVAL; + goto out; + } + uh = (void *)(skb->data + offset); th = (void *)(skb->data + offset); hdr_len += offset; -- 2.43.7