[PATCH net] ieee802154: reject ARPHRD_IEEE802154 devices without ieee802154_ptr

From: Joas Antonio dos Santos

Date: Fri Oct 30 2026 - 15:26:41 EST


TUNSETLINK lets a TUN device change its link type to ARPHRD_IEEE802154
without it becoming a WPAN device: dev->ieee802154_ptr and dev->ml_priv
stay NULL. The legacy 802.15.4 netlink code and the AF_IEEE802154
socket code only check dev->type before dereferencing them:

- ieee802154_llsec_dump_table() calls ieee802154_mlme_ops(dev)->llsec
(the syzbot report below);
- ieee802154_dump_iface() and ieee802154_nl_get_dev() users such as
IEEE802154_LIST_IFACE, which needs no privileges, go through
ieee802154_nl_fill_iface() and dev->ieee802154_ptr->wpan_phy;
- ieee802154_del_iface() reads dev->ieee802154_ptr->wpan_phy;
- ieee802154_get_dev(), used by bind() and sendmsg() on any
AF_IEEE802154 datagram socket, reads tmp->ieee802154_ptr->pan_id when
looking up a short address, and dgram_sendmsg() and
ieee802154_dgram_deliver() use dev->ieee802154_ptr of the device they
picked by type.

Once such a TUN device exists in init_net, an unprivileged user can
crash the kernel with an IEEE802154_LIST_IFACE dump or by binding an
AF_IEEE802154 datagram socket to a short address:

Unable to handle kernel NULL pointer dereference at virtual address 0000000000000000
pc : ieee802154_nl_fill_iface.isra.0+0xf0/0x64c

KASAN: null-ptr-deref in range [0x0000000000000038-0x000000000000003f]
pc : ieee802154_get_dev+0x2b8/0x478

Reject devices without ieee802154_ptr wherever a device is picked by
its type, as was done for lowpan_newlink() in commit bf79662bc85e
("ieee802154: 6lowpan: fix NULL dereference in lowpan_newlink").

The bug was reported by syzbot; the other paths were found by
LLM-assisted review of the same pattern and confirmed with a test that
sets up the TUN device as root and then triggers them as uid 65534.

Fixes: 3e9c156e2c21 ("ieee802154: add netlink interfaces for llsec")
Reported-by: syzbot+9ea34da5ca6100593c90@xxxxxxxxxxxxxxxxxxxxxxxxx
Closes: https://syzkaller.appspot.com/bug?extid=9ea34da5ca6100593c90
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Joas Antonio dos Santos <joasantonio108@xxxxxxxxx>
Assisted-by: Yaga Agent HackerSec Claude:claude-opus-5-5
---
Tested on arm64 QEMU with KASAN (net/main 94cea342e891, mac802154_hwsim
loaded): a TUN device set to ARPHRD_IEEE802154 by root, then
- LLSEC_LIST_KEY dump as root: NULL deref in ieee802154_llsec_dump_table()
without the patch, normal dump with it;
- LIST_IFACE dump as uid 65534: NULL deref in ieee802154_nl_fill_iface()
without the patch, lists the hwsim interfaces with it;
- bind() of an AF_IEEE802154 datagram socket to a short address as uid
65534: NULL deref in ieee802154_get_dev() without the patch, -ENODEV
with it;
- sendto() on an unbound datagram socket keeps working (it picks a hwsim
interface).

net/ieee802154/nl-mac.c | 8 +++++---
net/ieee802154/nl-phy.c | 2 +-
net/ieee802154/socket.c | 18 ++++++++++++++++--
3 files changed, 22 insertions(+), 6 deletions(-)

diff --git a/net/ieee802154/nl-mac.c b/net/ieee802154/nl-mac.c
index 74ef0a310..5bf6725eb 100644
--- a/net/ieee802154/nl-mac.c
+++ b/net/ieee802154/nl-mac.c
@@ -162,7 +162,7 @@ static struct net_device *ieee802154_nl_get_dev(struct genl_info *info)
if (!dev)
return NULL;

- if (dev->type != ARPHRD_IEEE802154) {
+ if (dev->type != ARPHRD_IEEE802154 || !dev->ieee802154_ptr) {
dev_put(dev);
return NULL;
}
@@ -437,7 +437,8 @@ int ieee802154_dump_iface(struct sk_buff *skb, struct netlink_callback *cb)

idx = 0;
for_each_netdev(net, dev) {
- if (idx < s_idx || dev->type != ARPHRD_IEEE802154)
+ if (idx < s_idx || dev->type != ARPHRD_IEEE802154 ||
+ !dev->ieee802154_ptr)
goto cont;

if (ieee802154_nl_fill_iface(skb, NETLINK_CB(cb->skb).portid,
@@ -771,7 +772,8 @@ ieee802154_llsec_dump_table(struct sk_buff *skb, struct netlink_callback *cb,
int rc;

for_each_netdev(net, dev) {
- if (idx < first_dev || dev->type != ARPHRD_IEEE802154)
+ if (idx < first_dev || dev->type != ARPHRD_IEEE802154 ||
+ !dev->ieee802154_ptr)
goto skip;

data.ops = ieee802154_mlme_ops(dev);
diff --git a/net/ieee802154/nl-phy.c b/net/ieee802154/nl-phy.c
index 4c07a475c..39aaa9745 100644
--- a/net/ieee802154/nl-phy.c
+++ b/net/ieee802154/nl-phy.c
@@ -284,7 +284,7 @@ int ieee802154_del_iface(struct sk_buff *skb, struct genl_info *info)
dev = dev_get_by_name(genl_info_net(info), name);
if (!dev)
return rc;
- if (dev->type != ARPHRD_IEEE802154)
+ if (dev->type != ARPHRD_IEEE802154 || !dev->ieee802154_ptr)
goto out;

phy = dev->ieee802154_ptr->wpan_phy;
diff --git a/net/ieee802154/socket.c b/net/ieee802154/socket.c
index be8ca1f05..f87c9ccbc 100644
--- a/net/ieee802154/socket.c
+++ b/net/ieee802154/socket.c
@@ -53,7 +53,8 @@ ieee802154_get_dev(struct net *net, const struct ieee802154_addr *addr)
rtnl_lock();

for_each_netdev(net, tmp) {
- if (tmp->type != ARPHRD_IEEE802154)
+ if (tmp->type != ARPHRD_IEEE802154 ||
+ !tmp->ieee802154_ptr)
continue;

pan_id = tmp->ieee802154_ptr->pan_id;
@@ -74,6 +75,14 @@ ieee802154_get_dev(struct net *net, const struct ieee802154_addr *addr)
break;
}

+ /* A TUN device can have its type set to ARPHRD_IEEE802154 without
+ * being a WPAN device.
+ */
+ if (dev && !dev->ieee802154_ptr) {
+ dev_put(dev);
+ dev = NULL;
+ }
+
return dev;
}

@@ -641,6 +650,11 @@ static int dgram_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)
else
dev = ieee802154_get_dev(sock_net(sk), &ro->src_addr);

+ if (dev && !dev->ieee802154_ptr) {
+ dev_put(dev);
+ dev = NULL;
+ }
+
if (!dev) {
pr_debug("no dev\n");
err = -ENXIO;
@@ -1097,7 +1111,7 @@ static int ieee802154_rcv(struct sk_buff *skb, struct net_device *dev,

ieee802154_raw_deliver(dev, skb);

- if (dev->type != ARPHRD_IEEE802154)
+ if (dev->type != ARPHRD_IEEE802154 || !dev->ieee802154_ptr)
goto drop;

if (skb->pkt_type != PACKET_OTHERHOST)
--
2.43.0