[PATCH net 1/1] net: fix linkwatch UAF on uninitialized netdev

From: Zihan Xi

Date: Fri Sep 04 2026 - 02:53:31 EST


linkwatch_fire_event() queues a net_device onto lweventlist and takes a
netdev_hold() even when the device has never been registered.

netif_carrier_on(), netif_carrier_off() and netif_carrier_event() already
skip NETREG_UNINITIALIZED. netif_dormant_*() and netif_testing_*() do
not. Pre-registration operstate changes are meant to stay local and be
consumed later by linkwatch_init_dev() in register_netdevice().

If a driver fires a dormant or testing event before register_netdevice()
and registration then fails, rtnl_newlink_create() calls free_netdev().
For NETREG_UNINITIALIZED devices, free_netdev() immediately kvfree()s
the object without draining linkwatch. The later linkwatch worker
dereferences the stale list entry and use-after-frees the device.

The same path is reachable from an unprivileged user who can unshare
user and net namespaces, because only netns-local CAP_NET_ADMIN is
required.

linkwatch_fire_event() has never rejected NETREG_UNINITIALIZED devices.
That goes back to commit 1da177e4c3f4 ("Linux-2.6.12-rc2"), the root of
this tree. Later changes only altered callers or were reverted.

commit b47300168e77 ("net: Do not fire linkwatch events
until the device is registered.")
skipped uninitialized devices in the carrier helpers only.

commit 22604c866889 ("net: Fix for initial link state in 2.6.28")
briefly added a NETREG_UNINITIALIZED return in the helper after calling
rfc2863_policy().

commit c276e098d3ee ("Revert "net: Fix for initial link state in 2.6.28"")
removed it again because that rfc2863_policy() call could take
dev_base_lock in software-interrupt context.

Fix this in linkwatch_fire_event() by ignoring NETREG_UNINITIALIZED
devices so they cannot be queued, without calling rfc2863_policy().
Pre-registration flags remain on the device and are still applied by
linkwatch_init_dev() if registration succeeds.

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Cc: stable@xxxxxxxxxxxxxxx
Reported-by: Vega <vega@xxxxxxxxxx>
Assisted-by: LLM
Signed-off-by: Zihan Xi <zihanx@xxxxxxxxxx>
---
net/core/link_watch.c | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/net/core/link_watch.c b/net/core/link_watch.c
index 9c35aac8b2e98..1caa786df2c71 100644
--- a/net/core/link_watch.c
+++ b/net/core/link_watch.c
@@ -318,7 +318,12 @@ static void linkwatch_event(struct work_struct *dummy)

void linkwatch_fire_event(struct net_device *dev)
{
- bool urgent = linkwatch_urgent_event(dev);
+ bool urgent;
+
+ if (dev->reg_state == NETREG_UNINITIALIZED)
+ return;
+
+ urgent = linkwatch_urgent_event(dev);

if (!test_and_set_bit(__LINK_STATE_LINKWATCH_PENDING, &dev->state)) {
linkwatch_add_event(dev);
--
2.43.0