net/bluetooth/bnep/core.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-)
`bnep_rx_frame()` pulls the first byte from the skb and immediately reads
the control type from the remaining data. Short control packets can leave
no bytes in the skb at that point.
The later control-message pull logic also reads `skb->data + 1` before
proving that the length byte or 16-bit filter length is actually present.
Validate the required control-header bytes before each dereference and
drop malformed frames through the existing bad-frame path.
Signed-off-by: Pengpeng Hou <pengpeng@iscas.ac.cn>
---
net/bluetooth/bnep/core.c | 17 +++++++++++++++--
1 file changed, 15 insertions(+), 2 deletions(-)
diff --git a/net/bluetooth/bnep/core.c b/net/bluetooth/bnep/core.c
index d44987d4515c..0e7a7fb758c9 100644
--- a/net/bluetooth/bnep/core.c
+++ b/net/bluetooth/bnep/core.c
@@ -299,18 +299,27 @@ static int bnep_rx_frame(struct bnep_session *s, struct sk_buff *skb)
{
struct net_device *dev = s->dev;
struct sk_buff *nskb;
- u8 type, ctrl_type;
+ u8 type;
dev->stats.rx_bytes += skb->len;
+ if (!skb->len)
+ goto badframe;
+
type = *(u8 *) skb->data;
skb_pull(skb, 1);
- ctrl_type = *(u8 *)skb->data;
if ((type & BNEP_TYPE_MASK) >= sizeof(__bnep_rx_hlen))
goto badframe;
if ((type & BNEP_TYPE_MASK) == BNEP_CONTROL) {
+ u8 ctrl_type;
+
+ if (!skb->len)
+ goto badframe;
+
+ ctrl_type = *(u8 *)skb->data;
+
if (bnep_rx_control(s, skb->data, skb->len) < 0) {
dev->stats.tx_errors++;
kfree_skb(skb);
@@ -326,12 +335,16 @@ static int bnep_rx_frame(struct bnep_session *s, struct sk_buff *skb)
switch (ctrl_type) {
case BNEP_SETUP_CONN_REQ:
/* Pull: ctrl type (1 b), len (1 b), data (len bytes) */
+ if (skb->len < 2)
+ goto badframe;
if (!skb_pull(skb, 2 + *(u8 *)(skb->data + 1) * 2))
goto badframe;
break;
case BNEP_FILTER_MULTI_ADDR_SET:
case BNEP_FILTER_NET_TYPE_SET:
/* Pull: ctrl type (1 b), len (2 b), data (len bytes) */
+ if (skb->len < 3)
+ goto badframe;
if (!skb_pull(skb, 3 + *(u16 *)(skb->data + 1) * 2))
goto badframe;
break;
--
2.50.1 (Apple Git-155)
Hi Paul,
Thanks for the review.
The Date/Received mismatch is on me. I resent from a locally generated
patch file and preserved its original Date header, so the SMTP receive
time ended up being newer than the message Date. I will correct that on
the next resend.
I do not have a polished userspace reproducer prepared yet, but the bug
can be triggered with short malformed BNEP payloads passed to
bnep_rx_frame().
The minimal cases are:
1. 0x01 (or 0x81)
This is a 1-byte BNEP_CONTROL frame. After the initial skb_pull(skb, 1),
skb->len becomes 0, but the current code still does
ctrl_type = *(u8 *)skb->data;
2. 0x81 0x01
This is BNEP_CONTROL | BNEP_EXT_HEADER with ctrl_type
BNEP_SETUP_CONN_REQ. Later, in the control-message pull path,
the code reads
*(u8 *)(skb->data + 1)
for the length byte, even though only the ctrl byte is present.
3. 0x81 0x03 or 0x81 0x05
These are BNEP_FILTER_NET_TYPE_SET / BNEP_FILTER_MULTI_ADDR_SET.
Later the code reads
*(u16 *)(skb->data + 1)
for the 16-bit length field, even though that field is absent.
So the issue is reproducible with truncated control skb payloads before
any valid length byte / 16-bit length field is available.
If useful, I can also turn one of the above cases into a small standalone
reproducer for the receive path.
Thanks,
Pengpeng
Dear Pengpeng,
Thank you for your patch. Just a note, that the date/time mismataches:
Received: from 0002-bnep.eml (unknown [111.196.245.197])
by APP-05 (Coremail) with SMTP id zQCowACHFwsG0dBp_7RzDA--.49444S2;
Sat, 04 Apr 2026 16:51:18 +0800 (CST)
From: Pengpeng Hou <pengpeng@iscas.ac.cn>
Date: Fri, 3 Apr 2026 16:56:12 +0800
Am 03.04.26 um 10:56 schrieb Pengpeng Hou:
> `bnep_rx_frame()` pulls the first byte from the skb and immediately reads
> the control type from the remaining data. Short control packets can leave
> no bytes in the skb at that point.
>
> The later control-message pull logic also reads `skb->data + 1` before
> proving that the length byte or 16-bit filter length is actually present.
>
> Validate the required control-header bytes before each dereference and
> drop malformed frames through the existing bad-frame path.
Do you have by chance a reproducer?
> Signed-off-by: Pengpeng Hou <pengpeng@iscas.ac.cn>
> ---
> net/bluetooth/bnep/core.c | 17 +++++++++++++++--
> 1 file changed, 15 insertions(+), 2 deletions(-)
>
> diff --git a/net/bluetooth/bnep/core.c b/net/bluetooth/bnep/core.c
> index d44987d4515c..0e7a7fb758c9 100644
> --- a/net/bluetooth/bnep/core.c
> +++ b/net/bluetooth/bnep/core.c
> @@ -299,18 +299,27 @@ static int bnep_rx_frame(struct bnep_session *s, struct sk_buff *skb)
> {
> struct net_device *dev = s->dev;
> struct sk_buff *nskb;
> - u8 type, ctrl_type;
> + u8 type;
>
> dev->stats.rx_bytes += skb->len;
>
> + if (!skb->len)
> + goto badframe;
> +
> type = *(u8 *) skb->data;
> skb_pull(skb, 1);
> - ctrl_type = *(u8 *)skb->data;
>
> if ((type & BNEP_TYPE_MASK) >= sizeof(__bnep_rx_hlen))
> goto badframe;
>
> if ((type & BNEP_TYPE_MASK) == BNEP_CONTROL) {
> + u8 ctrl_type;
> +
> + if (!skb->len)
> + goto badframe;
> +
> + ctrl_type = *(u8 *)skb->data;
> +
> if (bnep_rx_control(s, skb->data, skb->len) < 0) {
> dev->stats.tx_errors++;
> kfree_skb(skb);
> @@ -326,12 +335,16 @@ static int bnep_rx_frame(struct bnep_session *s, struct sk_buff *skb)
> switch (ctrl_type) {
> case BNEP_SETUP_CONN_REQ:
> /* Pull: ctrl type (1 b), len (1 b), data (len bytes) */
> + if (skb->len < 2)
> + goto badframe;
gemini/gemini-3.1-pro-preview commented on this hunk.
https://sashiko.dev/#/patchset/20260404101002.2-bnep-pengpeng%40iscas.ac.cn
> if (!skb_pull(skb, 2 + *(u8 *)(skb->data + 1) * 2))
> goto badframe;
> break;
> case BNEP_FILTER_MULTI_ADDR_SET:
> case BNEP_FILTER_NET_TYPE_SET:
> /* Pull: ctrl type (1 b), len (2 b), data (len bytes) */
> + if (skb->len < 3)
> + goto badframe;
> if (!skb_pull(skb, 3 + *(u16 *)(skb->data + 1) * 2))
> goto badframe;
> break;
Kind regards,
Paul
© 2016 - 2026 Red Hat, Inc.