[PATCH net] mpls: reload header after pskb_may_pull()
Qing Ming <[email protected]>
| Newsgroups | org.kernel.vger.netdev,org.kernel.vger.linux-kernel,org.kernel.vger.stable |
|---|---|
| Message-ID | <[email protected]> |
mpls_select_multipath() calls mpls_multipath_hash() to choose a nexthop
when an MPLS route has multiple nexthops. While walking the MPLS label
stack, the hash routine caches hdr for the current label. After finding
the bottom-of-stack label, it calls pskb_may_pull() before reading the
inner IP header.
If an skb is constructed with the inner IP header in nonlinear data and
insufficient tailroom in the linear head, pskb_may_pull() calls
pskb_expand_head() to replace the skb head and free the old one. This
leaves hdr pointing to freed memory. The IPv6 path can invalidate hdr
again when it performs a second pull for the larger header.
The issue was found through static analysis. A reproducer sending a legal
Geneve packet through a bareudp/MPLS multipath setup triggered the same
KASAN report in 2 of 2 unpatched runs:
BUG: KASAN: slab-use-after-free in mpls_select_multipath
Read of size 1 at addr ffff88800ecc6e20 by task ksoftirqd/1/23
Call Trace:
mpls_select_multipath
mpls_forward
__netif_receive_skb_list_core
netif_receive_skb_list_internal
napi_complete_done
gro_cell_poll
__napi_poll
net_rx_action
Freed by task 23:
kfree
pskb_expand_head
__pskb_pull_tail
mpls_select_multipath
Reload hdr from the current skb head after each successful pull before
deriving the inner IPv4 or IPv6 header pointer.
Fixes: 9f427a0e474a ("net: mpls: Fix multipath selection for LSR use case")
Cc: [email protected]
Signed-off-by: Qing Ming <[email protected]>
---
net/mpls/af_mpls.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/net/mpls/af_mpls.c b/net/mpls/af_mpls.c
index 961be5054a03..17b78dcbf8ab 100644
--- a/net/mpls/af_mpls.c
+++ b/net/mpls/af_mpls.c
@@ -221,6 +221,7 @@ static u32 mpls_multipath_hash(struct mpls_route *rt, struct sk_buff *skb)
if (pskb_may_pull(skb, mpls_hdr_len + sizeof(struct iphdr))) {
const struct iphdr *v4hdr;
+ hdr = mpls_hdr(skb) + label_index;
v4hdr = (const struct iphdr *)(hdr + 1);
if (v4hdr->version == 4) {
hash = jhash_3words(ntohl(v4hdr->saddr),
@@ -231,6 +232,7 @@ static u32 mpls_multipath_hash(struct mpls_route *rt, struct sk_buff *skb)
sizeof(struct ipv6hdr))) {
const struct ipv6hdr *v6hdr;
+ hdr = mpls_hdr(skb) + label_index;
v6hdr = (const struct ipv6hdr *)(hdr + 1);
hash = __ipv6_addr_jhash(&v6hdr->saddr, hash);
hash = __ipv6_addr_jhash(&v6hdr->daddr, hash);
base-commit: 24ef02f934eeb48830cff6b739abc3c62b1d107b
--
2.53.0