lowpan_header_compress() pays for the IPHC header it pushes by first
calling skb_pull(skb, sizeof(struct ipv6hdr)), but that pull is a no-op
when skb->len is shorter than an IPv6 header, so the unpaid skb_push() can
drive skb->data below skb->head and into skb_under_panic().
lowpan_nhc_check_compression() has the same missing length check,
committing to the next-header compression path without requiring the
nhc->nexthdrlen transport bytes that nhc->compress() reads and
lowpan_nhc_do_compression() then pulls. Return -EINVAL from
lowpan_header_compress() when pskb_may_pull() cannot produce a full IPv6
header, and return -ENOENT from lowpan_nhc_check_compression() unless the
IPv6 header plus nhc->nexthdrlen bytes are present, so that the nexthdr
falls back to its inline encoding.
Closes: https://lore.kernel.org/all/CA+0ovCjTsygN76s2o=TZqPqW8v2gBhmRnz+q6G-NaB3Cq-YPqQ@mail.gmail.com/
Signed-off-by: Farhad Alemi <farhad.alemi@berkeley.edu>
---
--- a/net/6lowpan/iphc.c
+++ b/net/6lowpan/iphc.c
@@ -1140,6 +1140,10 @@ int lowpan_header_compress(struct sk_buff *skb,
const struct net_device *dev,
if (skb->protocol != htons(ETH_P_IPV6))
return -EINVAL;
+ /* The IPHC header pushed below is paid for by pulling this header. */
+ if (!pskb_may_pull(skb, sizeof(struct ipv6hdr)))
+ return -EINVAL;
+
hdr = ipv6_hdr(skb);
hc_ptr = head + 2;
--- a/net/6lowpan/nhc.c
+++ b/net/6lowpan/nhc.c
@@ -47,7 +47,9 @@ int lowpan_nhc_check_compression(struct sk_buff *skb,
spin_lock_bh(&lowpan_nhc_lock);
nhc = lowpan_nexthdr_nhcs[hdr->nexthdr];
- if (!(nhc && nhc->compress))
+ /* nhc->compress() reads and then pulls nexthdrlen transport bytes. */
+ if (!(nhc && nhc->compress) ||
+ !pskb_may_pull(skb, sizeof(struct ipv6hdr) + nhc->nexthdrlen))
ret = -ENOENT;
spin_unlock_bh(&lowpan_nhc_lock);
On Thu, Sep 10, 2026 at 1:31 PM Farhad Alemi <farhad.alemi@berkeley.edu> wrote: > > lowpan_header_compress() pays for the IPHC header it pushes by first > calling skb_pull(skb, sizeof(struct ipv6hdr)), but that pull is a no-op > when skb->len is shorter than an IPv6 header, so the unpaid skb_push() can > drive skb->data below skb->head and into skb_under_panic(). > lowpan_nhc_check_compression() has the same missing length check, > committing to the next-header compression path without requiring the > nhc->nexthdrlen transport bytes that nhc->compress() reads and > lowpan_nhc_do_compression() then pulls. Return -EINVAL from > lowpan_header_compress() when pskb_may_pull() cannot produce a full IPv6 > header, and return -ENOENT from lowpan_nhc_check_compression() unless the > IPv6 header plus nhc->nexthdrlen bytes are present, so that the nexthdr > falls back to its inline encoding. > > Closes: https://lore.kernel.org/all/CA+0ovCjTsygN76s2o=TZqPqW8v2gBhmRnz+q6G-NaB3Cq-YPqQ@mail.gmail.com/ > Signed-off-by: Farhad Alemi <farhad.alemi@berkeley.edu> Notes in a semi random order. 1) You forgot to tag the net tree in your patch. 2) You forgot the Fixes: tag Please look at Documentation/process/maintainer-netdev.rst for more details. 3) In net/6lowpan/nhc.c, calling pskb_may_pull() inside lowpan_nhc_check_compression() can reallocate skb->head (via pskb_expand_head()). When this happens, the 'hdr' pointer in lowpan_header_compress() becomes dangling. lowpan_header_compress() continues to dereference hdr throughout the rest of the function (hdr->nexthdr, hdr->hop_limit, hdr->saddr, hdr->daddr), leading to a use-after-free. 4) Callers of lowpan_header_compress() do not check its return value. In net/bluetooth/6lowpan.c (setup_header()) and net/ieee802154/6lowpan/tx.c (lowpan_header()), the return code of lowpan_header_compress() is completely ignored. If it returns -EINVAL, they proceed to transmit the malformed/uncompressed skb anyway. 5) In net/bluetooth/6lowpan.c:setup_header(), there is already an out-of-bounds read before lowpan_header_compress() is even called: hdr = ipv6_hdr(skb); ... memcpy(&ipv6_daddr, &hdr->daddr, sizeof(ipv6_daddr)); If skb->len is less than sizeof(struct ipv6hdr), reading hdr->daddr is already out of bounds. The driver's transmit path (bt_xmit / setup_header) needs to validate skb length / pskb_may_pull before touching the IPv6 header, and must check the return value of lowpan_header_compress(). Thanks.
© 2016 - 2026 Red Hat, Inc.