[PATCH] 6lowpan: do not compress headers that are not fully present

Farhad Alemi posted 1 patch 2 weeks ago
[PATCH] 6lowpan: do not compress headers that are not fully present
Posted by Farhad Alemi 2 weeks ago
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);
Re: [PATCH] 6lowpan: do not compress headers that are not fully present
Posted by Eric Dumazet 2 weeks ago
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.