* [PATCH v3 net] net: watchdog: fix refcount tracking races
@ 2026-06-11 15:27 ` Eric Dumazet
2026-06-13 1:00 ` patchwork-bot+netdevbpf
2026-06-17 10:48 ` Marek Szyprowski
0 siblings, 2 replies; 5+ messages in thread
From: Eric Dumazet @ 2026-06-11 15:27 UTC (permalink / raw)
To: David S . Miller, Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, netdev, eric.dumazet, Eric Dumazet,
syzbot+381d82bbf0253710b35d, syzbot+3479efbc2821cb2a79f2
Blamed commit converted the untracked dev_hold()/dev_put() calls
in the watchdog code to use the tracked dev_hold_track()/dev_put_track()
(which were later renamed/interfaced to netdev_hold() and netdev_put()).
By introducing dev->watchdog_dev_tracker to store the
reference tracking information without adding synchronization
between netdev_watchdog_up() and dev_watchdog(), it enabled the
race condition where this pointer could be overwritten or freed
concurrently, leading to the list corruption crash syzbot reported:
list_del corruption, ffff888114a18c00->next is NULL
kernel BUG at lib/list_debug.c:52 !
Oops: invalid opcode: 0000 [#1] SMP KASAN PTI
CPU: 1 UID: 0 PID: 91 Comm: kworker/u8:5 Not tainted syzkaller #0 PREEMPT(lazy)
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 05/09/2026
Workqueue: events_unbound linkwatch_event
RIP: 0010:__list_del_entry_valid_or_report.cold+0x22/0x2a lib/list_debug.c:52
Call Trace:
<TASK>
__list_del_entry_valid include/linux/list.h:132 [inline]
__list_del_entry include/linux/list.h:246 [inline]
list_move_tail include/linux/list.h:341 [inline]
ref_tracker_free+0x1a7/0x6c0 lib/ref_tracker.c:329
netdev_tracker_free include/linux/netdevice.h:4491 [inline]
netdev_put include/linux/netdevice.h:4508 [inline]
netdev_put include/linux/netdevice.h:4504 [inline]
netdev_watchdog_down net/sched/sch_generic.c:600 [inline]
dev_deactivate_many+0x28c/0xfe0 net/sched/sch_generic.c:1363
dev_deactivate+0x109/0x1d0 net/sched/sch_generic.c:1397
linkwatch_do_dev net/core/link_watch.c:184 [inline]
linkwatch_do_dev+0xd3/0x120 net/core/link_watch.c:166
__linkwatch_run_queue+0x3a5/0x810 net/core/link_watch.c:240
linkwatch_event+0x8f/0xc0 net/core/link_watch.c:314
process_one_work+0xa0e/0x1980 kernel/workqueue.c:3314
process_scheduled_works kernel/workqueue.c:3397 [inline]
worker_thread+0x5ef/0xe50 kernel/workqueue.c:3478
kthread+0x370/0x450 kernel/kthread.c:436
ret_from_fork+0x69a/0xc80 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
This patch has three coordinated parts:
1) Add dev->watchdog_lock and dev->watchdog_ref_held to serialize watchdog operations.
2) Remove netdev_watchdog_up() call from netif_carrier_on():
This ensures netdev_watchdog_up() is only called from process/BH context
(via linkwatch workqueue dev_activate()), allowing us to use
spin_lock_bh() for synchronization.
3) Synchronize watchdog up and watchdog timer:
Protect netdev_watchdog_up() with tx_global_lock and watchdog_lock.
Only allocate a new tracker in netdev_watchdog_up() if one is
not already present.
In dev_watchdog(), ensure we don't release the tracker if the
timer was rescheduled either by dev_watchdog() itself or concurrently
by netdev_watchdog_up().
Fixes: f12bf6f3f942 ("net: watchdog: add net device refcount tracker")
Reported-by: syzbot+381d82bbf0253710b35d@syzkaller.appspotmail.com
Closes: https://lore.kernel.org/netdev/6a26b751.c25708ab.1b19ef.0013.GAE@google.com/T/#u
Tested-by: syzbot+3479efbc2821cb2a79f2@syzkaller.appspotmail.com
Signed-off-by: Eric Dumazet <edumazet@google.com>
---
v3: added dev->watchdog_lock and dev->watchdog_ref_held instead of messing
with ref tracker infra. (after sashiko feedback on v2)
v2: fix compile error when CONFIG_NET_DEV_REFCNT_TRACKER is not set (Jakub and build bots)
include/linux/netdevice.h | 4 ++++
net/core/dev.c | 3 ++-
net/sched/sch_generic.c | 44 +++++++++++++++++++++++++++++----------
3 files changed, 39 insertions(+), 12 deletions(-)
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index 0e1e581efc5ac264259b2f0fdfe41c50a6f47239..4a0e83709f29e4bcf12f479e464e6bedecc61c69 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -1980,6 +1980,8 @@ enum netdev_reg_state {
* @qdisc_hash: qdisc hash table
* @watchdog_timeo: Represents the timeout that is used by
* the watchdog (see dev_watchdog())
+ * @watchdog_lock: protect watchdog_ref_held
+ * @watchdog_ref_held: True if the watchdog device ref is taken.
* @watchdog_timer: List of timers
*
* @proto_down_reason: reason a netdev interface is held down
@@ -2392,6 +2394,8 @@ struct net_device {
/* These may be needed for future network-power-down code. */
struct timer_list watchdog_timer;
int watchdog_timeo;
+ spinlock_t watchdog_lock;
+ bool watchdog_ref_held;
u32 proto_down_reason;
diff --git a/net/core/dev.c b/net/core/dev.c
index 0c6c270d9f7d115feb824f4ebe6be122c40d745f..731e661d7be6574d5eca4a600e0a5623be4c2485 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -11217,7 +11217,8 @@ static int netif_alloc_netdev_queues(struct net_device *dev)
netdev_for_each_tx_queue(dev, netdev_init_one_queue, NULL);
spin_lock_init(&dev->tx_global_lock);
-
+ spin_lock_init(&dev->watchdog_lock);
+ dev->watchdog_ref_held = false;
return 0;
}
diff --git a/net/sched/sch_generic.c b/net/sched/sch_generic.c
index a93321db8fd75d30c61e146c290bbc139c37c913..6cdf2ccfb0937e45271f8690a0c09d48a24ce769 100644
--- a/net/sched/sch_generic.c
+++ b/net/sched/sch_generic.c
@@ -568,16 +568,24 @@ static void dev_watchdog(struct timer_list *t)
dev->netdev_ops->ndo_tx_timeout(dev, i);
netif_unfreeze_queues(dev);
}
- if (!mod_timer(&dev->watchdog_timer,
- round_jiffies(oldest_start +
- dev->watchdog_timeo)))
- release = false;
+ spin_lock(&dev->watchdog_lock);
+ mod_timer(&dev->watchdog_timer,
+ round_jiffies(oldest_start +
+ dev->watchdog_timeo));
+ release = false;
+ spin_unlock(&dev->watchdog_lock);
}
}
spin_unlock(&dev->tx_global_lock);
- if (release)
+ spin_lock(&dev->watchdog_lock);
+ if (timer_pending(&dev->watchdog_timer))
+ release = false;
+ if (release && dev->watchdog_ref_held) {
netdev_put(dev, &dev->watchdog_dev_tracker);
+ dev->watchdog_ref_held = false;
+ }
+ spin_unlock(&dev->watchdog_lock);
}
void netdev_watchdog_up(struct net_device *dev)
@@ -586,18 +594,34 @@ void netdev_watchdog_up(struct net_device *dev)
return;
if (dev->watchdog_timeo <= 0)
dev->watchdog_timeo = 5*HZ;
+ spin_lock_bh(&dev->tx_global_lock);
+
+ spin_lock(&dev->watchdog_lock);
if (!mod_timer(&dev->watchdog_timer,
- round_jiffies(jiffies + dev->watchdog_timeo)))
- netdev_hold(dev, &dev->watchdog_dev_tracker,
- GFP_ATOMIC);
+ round_jiffies(jiffies + dev->watchdog_timeo))) {
+ if (!dev->watchdog_ref_held) {
+ netdev_hold(dev, &dev->watchdog_dev_tracker,
+ GFP_ATOMIC);
+ dev->watchdog_ref_held = true;
+ }
+ }
+ spin_unlock(&dev->watchdog_lock);
+
+ spin_unlock_bh(&dev->tx_global_lock);
}
EXPORT_SYMBOL_GPL(netdev_watchdog_up);
static void netdev_watchdog_down(struct net_device *dev)
{
netif_tx_lock_bh(dev);
- if (timer_delete(&dev->watchdog_timer))
+
+ spin_lock(&dev->watchdog_lock);
+ if (timer_delete(&dev->watchdog_timer)) {
netdev_put(dev, &dev->watchdog_dev_tracker);
+ dev->watchdog_ref_held = false;
+ }
+ spin_unlock(&dev->watchdog_lock);
+
netif_tx_unlock_bh(dev);
}
@@ -614,8 +638,6 @@ void netif_carrier_on(struct net_device *dev)
return;
atomic_inc(&dev->carrier_up_count);
linkwatch_fire_event(dev);
- if (netif_running(dev))
- netdev_watchdog_up(dev);
}
}
EXPORT_SYMBOL(netif_carrier_on);
--
2.54.0.1099.g489fc7bff1-goog
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH v3 net] net: watchdog: fix refcount tracking races
2026-06-11 15:27 ` [PATCH v3 net] net: watchdog: fix refcount tracking races Eric Dumazet
@ 2026-06-13 1:00 ` patchwork-bot+netdevbpf
2026-06-17 10:48 ` Marek Szyprowski
1 sibling, 0 replies; 5+ messages in thread
From: patchwork-bot+netdevbpf @ 2026-06-13 1:00 UTC (permalink / raw)
To: Eric Dumazet
Cc: davem, kuba, pabeni, horms, netdev, eric.dumazet,
syzbot+381d82bbf0253710b35d, syzbot+3479efbc2821cb2a79f2
Hello:
This patch was applied to netdev/net.git (main)
by Jakub Kicinski <kuba@kernel.org>:
On Thu, 11 Jun 2026 15:27:37 +0000 you wrote:
> Blamed commit converted the untracked dev_hold()/dev_put() calls
> in the watchdog code to use the tracked dev_hold_track()/dev_put_track()
> (which were later renamed/interfaced to netdev_hold() and netdev_put()).
>
> By introducing dev->watchdog_dev_tracker to store the
> reference tracking information without adding synchronization
> between netdev_watchdog_up() and dev_watchdog(), it enabled the
> race condition where this pointer could be overwritten or freed
> concurrently, leading to the list corruption crash syzbot reported:
>
> [...]
Here is the summary with links:
- [v3,net] net: watchdog: fix refcount tracking races
https://git.kernel.org/netdev/net/c/8eed5519e496
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v3 net] net: watchdog: fix refcount tracking races
2026-06-11 15:27 ` [PATCH v3 net] net: watchdog: fix refcount tracking races Eric Dumazet
2026-06-13 1:00 ` patchwork-bot+netdevbpf
@ 2026-06-17 10:48 ` Marek Szyprowski
2026-06-22 8:59 ` Eric Dumazet
1 sibling, 1 reply; 5+ messages in thread
From: Marek Szyprowski @ 2026-06-17 10:48 UTC (permalink / raw)
To: Eric Dumazet, David S . Miller, Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, netdev, eric.dumazet, syzbot+381d82bbf0253710b35d,
syzbot+3479efbc2821cb2a79f2
Dear All,
On 11.06.2026 17:27, Eric Dumazet wrote:
> Blamed commit converted the untracked dev_hold()/dev_put() calls
> in the watchdog code to use the tracked dev_hold_track()/dev_put_track()
> (which were later renamed/interfaced to netdev_hold() and netdev_put()).
>
> By introducing dev->watchdog_dev_tracker to store the
> reference tracking information without adding synchronization
> between netdev_watchdog_up() and dev_watchdog(), it enabled the
> race condition where this pointer could be overwritten or freed
> concurrently, leading to the list corruption crash syzbot reported:
>
> list_del corruption, ffff888114a18c00->next is NULL
> kernel BUG at lib/list_debug.c:52 !
> Oops: invalid opcode: 0000 [#1] SMP KASAN PTI
> CPU: 1 UID: 0 PID: 91 Comm: kworker/u8:5 Not tainted syzkaller #0 PREEMPT(lazy)
> Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 05/09/2026
> Workqueue: events_unbound linkwatch_event
> RIP: 0010:__list_del_entry_valid_or_report.cold+0x22/0x2a lib/list_debug.c:52
> Call Trace:
> <TASK>
> __list_del_entry_valid include/linux/list.h:132 [inline]
> __list_del_entry include/linux/list.h:246 [inline]
> list_move_tail include/linux/list.h:341 [inline]
> ref_tracker_free+0x1a7/0x6c0 lib/ref_tracker.c:329
> netdev_tracker_free include/linux/netdevice.h:4491 [inline]
> netdev_put include/linux/netdevice.h:4508 [inline]
> netdev_put include/linux/netdevice.h:4504 [inline]
> netdev_watchdog_down net/sched/sch_generic.c:600 [inline]
> dev_deactivate_many+0x28c/0xfe0 net/sched/sch_generic.c:1363
> dev_deactivate+0x109/0x1d0 net/sched/sch_generic.c:1397
> linkwatch_do_dev net/core/link_watch.c:184 [inline]
> linkwatch_do_dev+0xd3/0x120 net/core/link_watch.c:166
> __linkwatch_run_queue+0x3a5/0x810 net/core/link_watch.c:240
> linkwatch_event+0x8f/0xc0 net/core/link_watch.c:314
> process_one_work+0xa0e/0x1980 kernel/workqueue.c:3314
> process_scheduled_works kernel/workqueue.c:3397 [inline]
> worker_thread+0x5ef/0xe50 kernel/workqueue.c:3478
> kthread+0x370/0x450 kernel/kthread.c:436
> ret_from_fork+0x69a/0xc80 arch/x86/kernel/process.c:158
> ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
>
> This patch has three coordinated parts:
>
> 1) Add dev->watchdog_lock and dev->watchdog_ref_held to serialize watchdog operations.
>
> 2) Remove netdev_watchdog_up() call from netif_carrier_on():
> This ensures netdev_watchdog_up() is only called from process/BH context
> (via linkwatch workqueue dev_activate()), allowing us to use
> spin_lock_bh() for synchronization.
>
> 3) Synchronize watchdog up and watchdog timer:
> Protect netdev_watchdog_up() with tx_global_lock and watchdog_lock.
> Only allocate a new tracker in netdev_watchdog_up() if one is
> not already present.
> In dev_watchdog(), ensure we don't release the tracker if the
> timer was rescheduled either by dev_watchdog() itself or concurrently
> by netdev_watchdog_up().
>
> Fixes: f12bf6f3f942 ("net: watchdog: add net device refcount tracker")
> Reported-by: syzbot+381d82bbf0253710b35d@syzkaller.appspotmail.com
> Closes: https://lore.kernel.org/netdev/6a26b751.c25708ab.1b19ef.0013.GAE@google.com/T/#u
> Tested-by: syzbot+3479efbc2821cb2a79f2@syzkaller.appspotmail.com
> Signed-off-by: Eric Dumazet <edumazet@google.com>
This patch landed recently in linux-next as commit 8eed5519e496 ("net: watchdog:
fix refcount tracking races"). In my tests I found that it causes the following
deadlock during system suspend/resume on QEmu's ARM64bit 'virt' machine:
root@target:~# time rtcwake -s10 -mmem
rtcwake: assuming RTC uses UTC ...
rtcwake: wakeup from "mem" using /dev/rtc0 at Wed Jun 17 10:46:12 2026
PM: suspend entry (s2idle)
Filesystems sync: 0.055 seconds
Freezing user space processes
Freezing user space processes completed (elapsed 0.006 seconds)
OOM killer disabled.
Freezing remaining freezable tasks
Freezing remaining freezable tasks completed (elapsed 0.003 seconds)
============================================
WARNING: possible recursive locking detected
7.1.0-rc7+ #13003 Not tainted
--------------------------------------------
rtcwake/254 is trying to acquire lock:
ffff000006de64e8 (&dev->tx_global_lock){+.-.}-{3:3}, at: netdev_watchdog_up+0x40/0x108
but task is already holding lock:
ffff000006de64e8 (&dev->tx_global_lock){+.-.}-{3:3}, at: netif_tx_lock+0x1c/0x34
other info that might help us debug this:
Possible unsafe locking scenario:
CPU0
----
lock(&dev->tx_global_lock);
lock(&dev->tx_global_lock);
*** DEADLOCK ***
May be due to missing lock nesting notation
6 locks held by rtcwake/254:
#0: ffff0000071ab3e8 (sb_writers#5){.+.+}-{0:0}, at: vfs_write+0x1ec/0x35c
#1: ffff00000d22c480 (&of->mutex#2){+.+.}-{4:4}, at: kernfs_fop_write_iter+0xf0/0x1c4
#2: ffff0000049162c8 (kn->active#61){.+.+}-{0:0}, at: kernfs_fop_write_iter+0x100/0x1c4
#3: ffffaa79533c03b0 (system_transition_mutex){+.+.}-{4:4}, at: pm_suspend+0x98/0x608
#4: ffff000005e3a138 (&dev->mutex){....}-{4:4}, at: device_resume+0xb4/0x254
#5: ffff000006de64e8 (&dev->tx_global_lock){+.-.}-{3:3}, at: netif_tx_lock+0x1c/0x34
stack backtrace:
CPU: 1 UID: 0 PID: 254 Comm: rtcwake Not tainted 7.1.0-rc7+ #13003 PREEMPT
Hardware name: linux,dummy-virt (DT)
Call trace:
show_stack+0x18/0x24 (C)
dump_stack_lvl+0x90/0xd0
dump_stack+0x18/0x24
print_deadlock_bug+0x260/0x350
__lock_acquire+0x11b8/0x225c
lock_acquire+0x1c4/0x3f0
_raw_spin_lock_bh+0x50/0x68
netdev_watchdog_up+0x40/0x108
netif_device_attach+0x9c/0xb0
virtnet_restore+0x100/0x21c
virtio_device_restore_priv+0x11c/0x1d0
virtio_device_restore+0x14/0x20
virtio_mmio_restore+0x34/0x40
platform_pm_resume+0x2c/0x68
dpm_run_callback+0xa0/0x240
device_resume+0x120/0x254
dpm_resume+0x1f8/0x2ec
dpm_resume_end+0x18/0x34
suspend_devices_and_enter+0x1d0/0x990
pm_suspend+0x1ec/0x608
state_store+0x8c/0x110
kobj_attr_store+0x18/0x2c
sysfs_kf_write+0x50/0x7c
kernfs_fop_write_iter+0x130/0x1c4
vfs_write+0x2b8/0x35c
ksys_write+0x6c/0x104
__arm64_sys_write+0x1c/0x28
invoke_syscall+0x54/0x110
el0_svc_common.constprop.0+0x40/0xe8
do_el0_svc+0x20/0x2c
el0_svc+0x54/0x338
el0t_64_sync_handler+0xa0/0xe4
el0t_64_sync+0x198/0x19c
Reverting $subject on top of linux-next fixes this issue.
> ---
> v3: added dev->watchdog_lock and dev->watchdog_ref_held instead of messing
> with ref tracker infra. (after sashiko feedback on v2)
> v2: fix compile error when CONFIG_NET_DEV_REFCNT_TRACKER is not set (Jakub and build bots)
>
> include/linux/netdevice.h | 4 ++++
> net/core/dev.c | 3 ++-
> net/sched/sch_generic.c | 44 +++++++++++++++++++++++++++++----------
> 3 files changed, 39 insertions(+), 12 deletions(-)
>
> diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
> index 0e1e581efc5ac264259b2f0fdfe41c50a6f47239..4a0e83709f29e4bcf12f479e464e6bedecc61c69 100644
> --- a/include/linux/netdevice.h
> +++ b/include/linux/netdevice.h
> @@ -1980,6 +1980,8 @@ enum netdev_reg_state {
> * @qdisc_hash: qdisc hash table
> * @watchdog_timeo: Represents the timeout that is used by
> * the watchdog (see dev_watchdog())
> + * @watchdog_lock: protect watchdog_ref_held
> + * @watchdog_ref_held: True if the watchdog device ref is taken.
> * @watchdog_timer: List of timers
> *
> * @proto_down_reason: reason a netdev interface is held down
> @@ -2392,6 +2394,8 @@ struct net_device {
> /* These may be needed for future network-power-down code. */
> struct timer_list watchdog_timer;
> int watchdog_timeo;
> + spinlock_t watchdog_lock;
> + bool watchdog_ref_held;
>
> u32 proto_down_reason;
>
> diff --git a/net/core/dev.c b/net/core/dev.c
> index 0c6c270d9f7d115feb824f4ebe6be122c40d745f..731e661d7be6574d5eca4a600e0a5623be4c2485 100644
> --- a/net/core/dev.c
> +++ b/net/core/dev.c
> @@ -11217,7 +11217,8 @@ static int netif_alloc_netdev_queues(struct net_device *dev)
>
> netdev_for_each_tx_queue(dev, netdev_init_one_queue, NULL);
> spin_lock_init(&dev->tx_global_lock);
> -
> + spin_lock_init(&dev->watchdog_lock);
> + dev->watchdog_ref_held = false;
> return 0;
> }
>
> diff --git a/net/sched/sch_generic.c b/net/sched/sch_generic.c
> index a93321db8fd75d30c61e146c290bbc139c37c913..6cdf2ccfb0937e45271f8690a0c09d48a24ce769 100644
> --- a/net/sched/sch_generic.c
> +++ b/net/sched/sch_generic.c
> @@ -568,16 +568,24 @@ static void dev_watchdog(struct timer_list *t)
> dev->netdev_ops->ndo_tx_timeout(dev, i);
> netif_unfreeze_queues(dev);
> }
> - if (!mod_timer(&dev->watchdog_timer,
> - round_jiffies(oldest_start +
> - dev->watchdog_timeo)))
> - release = false;
> + spin_lock(&dev->watchdog_lock);
> + mod_timer(&dev->watchdog_timer,
> + round_jiffies(oldest_start +
> + dev->watchdog_timeo));
> + release = false;
> + spin_unlock(&dev->watchdog_lock);
> }
> }
> spin_unlock(&dev->tx_global_lock);
>
> - if (release)
> + spin_lock(&dev->watchdog_lock);
> + if (timer_pending(&dev->watchdog_timer))
> + release = false;
> + if (release && dev->watchdog_ref_held) {
> netdev_put(dev, &dev->watchdog_dev_tracker);
> + dev->watchdog_ref_held = false;
> + }
> + spin_unlock(&dev->watchdog_lock);
> }
>
> void netdev_watchdog_up(struct net_device *dev)
> @@ -586,18 +594,34 @@ void netdev_watchdog_up(struct net_device *dev)
> return;
> if (dev->watchdog_timeo <= 0)
> dev->watchdog_timeo = 5*HZ;
> + spin_lock_bh(&dev->tx_global_lock);
> +
> + spin_lock(&dev->watchdog_lock);
> if (!mod_timer(&dev->watchdog_timer,
> - round_jiffies(jiffies + dev->watchdog_timeo)))
> - netdev_hold(dev, &dev->watchdog_dev_tracker,
> - GFP_ATOMIC);
> + round_jiffies(jiffies + dev->watchdog_timeo))) {
> + if (!dev->watchdog_ref_held) {
> + netdev_hold(dev, &dev->watchdog_dev_tracker,
> + GFP_ATOMIC);
> + dev->watchdog_ref_held = true;
> + }
> + }
> + spin_unlock(&dev->watchdog_lock);
> +
> + spin_unlock_bh(&dev->tx_global_lock);
> }
> EXPORT_SYMBOL_GPL(netdev_watchdog_up);
>
> static void netdev_watchdog_down(struct net_device *dev)
> {
> netif_tx_lock_bh(dev);
> - if (timer_delete(&dev->watchdog_timer))
> +
> + spin_lock(&dev->watchdog_lock);
> + if (timer_delete(&dev->watchdog_timer)) {
> netdev_put(dev, &dev->watchdog_dev_tracker);
> + dev->watchdog_ref_held = false;
> + }
> + spin_unlock(&dev->watchdog_lock);
> +
> netif_tx_unlock_bh(dev);
> }
>
> @@ -614,8 +638,6 @@ void netif_carrier_on(struct net_device *dev)
> return;
> atomic_inc(&dev->carrier_up_count);
> linkwatch_fire_event(dev);
> - if (netif_running(dev))
> - netdev_watchdog_up(dev);
> }
> }
> EXPORT_SYMBOL(netif_carrier_on);
Best regards
--
Marek Szyprowski, PhD
Samsung R&D Institute Poland
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v3 net] net: watchdog: fix refcount tracking races
2026-06-17 10:48 ` Marek Szyprowski
@ 2026-06-22 8:59 ` Eric Dumazet
2026-06-22 10:22 ` Marek Szyprowski
0 siblings, 1 reply; 5+ messages in thread
From: Eric Dumazet @ 2026-06-22 8:59 UTC (permalink / raw)
To: Marek Szyprowski
Cc: David S . Miller, Jakub Kicinski, Paolo Abeni, Simon Horman,
netdev, eric.dumazet, syzbot+381d82bbf0253710b35d,
syzbot+3479efbc2821cb2a79f2
On Wed, Jun 17, 2026 at 3:48 AM Marek Szyprowski
<m.szyprowski@samsung.com> wrote:
>
> Dear All,
>
> On 11.06.2026 17:27, Eric Dumazet wrote:
> > Blamed commit converted the untracked dev_hold()/dev_put() calls
> > in the watchdog code to use the tracked dev_hold_track()/dev_put_track()
> > (which were later renamed/interfaced to netdev_hold() and netdev_put()).
> >
> > By introducing dev->watchdog_dev_tracker to store the
> > reference tracking information without adding synchronization
> > between netdev_watchdog_up() and dev_watchdog(), it enabled the
> > race condition where this pointer could be overwritten or freed
> > concurrently, leading to the list corruption crash syzbot reported:
> >
> > list_del corruption, ffff888114a18c00->next is NULL
> > kernel BUG at lib/list_debug.c:52 !
> > Oops: invalid opcode: 0000 [#1] SMP KASAN PTI
> > CPU: 1 UID: 0 PID: 91 Comm: kworker/u8:5 Not tainted syzkaller #0 PREEMPT(lazy)
> > Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 05/09/2026
> > Workqueue: events_unbound linkwatch_event
> > RIP: 0010:__list_del_entry_valid_or_report.cold+0x22/0x2a lib/list_debug.c:52
> > Call Trace:
> > <TASK>
> > __list_del_entry_valid include/linux/list.h:132 [inline]
> > __list_del_entry include/linux/list.h:246 [inline]
> > list_move_tail include/linux/list.h:341 [inline]
> > ref_tracker_free+0x1a7/0x6c0 lib/ref_tracker.c:329
> > netdev_tracker_free include/linux/netdevice.h:4491 [inline]
> > netdev_put include/linux/netdevice.h:4508 [inline]
> > netdev_put include/linux/netdevice.h:4504 [inline]
> > netdev_watchdog_down net/sched/sch_generic.c:600 [inline]
> > dev_deactivate_many+0x28c/0xfe0 net/sched/sch_generic.c:1363
> > dev_deactivate+0x109/0x1d0 net/sched/sch_generic.c:1397
> > linkwatch_do_dev net/core/link_watch.c:184 [inline]
> > linkwatch_do_dev+0xd3/0x120 net/core/link_watch.c:166
> > __linkwatch_run_queue+0x3a5/0x810 net/core/link_watch.c:240
> > linkwatch_event+0x8f/0xc0 net/core/link_watch.c:314
> > process_one_work+0xa0e/0x1980 kernel/workqueue.c:3314
> > process_scheduled_works kernel/workqueue.c:3397 [inline]
> > worker_thread+0x5ef/0xe50 kernel/workqueue.c:3478
> > kthread+0x370/0x450 kernel/kthread.c:436
> > ret_from_fork+0x69a/0xc80 arch/x86/kernel/process.c:158
> > ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
> >
> > This patch has three coordinated parts:
> >
> > 1) Add dev->watchdog_lock and dev->watchdog_ref_held to serialize watchdog operations.
> >
> > 2) Remove netdev_watchdog_up() call from netif_carrier_on():
> > This ensures netdev_watchdog_up() is only called from process/BH context
> > (via linkwatch workqueue dev_activate()), allowing us to use
> > spin_lock_bh() for synchronization.
> >
> > 3) Synchronize watchdog up and watchdog timer:
> > Protect netdev_watchdog_up() with tx_global_lock and watchdog_lock.
> > Only allocate a new tracker in netdev_watchdog_up() if one is
> > not already present.
> > In dev_watchdog(), ensure we don't release the tracker if the
> > timer was rescheduled either by dev_watchdog() itself or concurrently
> > by netdev_watchdog_up().
> >
> > Fixes: f12bf6f3f942 ("net: watchdog: add net device refcount tracker")
> > Reported-by: syzbot+381d82bbf0253710b35d@syzkaller.appspotmail.com
> > Closes: https://lore.kernel.org/netdev/6a26b751.c25708ab.1b19ef.0013.GAE@google.com/T/#u
> > Tested-by: syzbot+3479efbc2821cb2a79f2@syzkaller.appspotmail.com
> > Signed-off-by: Eric Dumazet <edumazet@google.com>
> This patch landed recently in linux-next as commit 8eed5519e496 ("net: watchdog:
> fix refcount tracking races"). In my tests I found that it causes the following
> deadlock during system suspend/resume on QEmu's ARM64bit 'virt' machine:
>
> root@target:~# time rtcwake -s10 -mmem
> rtcwake: assuming RTC uses UTC ...
> rtcwake: wakeup from "mem" using /dev/rtc0 at Wed Jun 17 10:46:12 2026
> PM: suspend entry (s2idle)
> Filesystems sync: 0.055 seconds
> Freezing user space processes
> Freezing user space processes completed (elapsed 0.006 seconds)
> OOM killer disabled.
> Freezing remaining freezable tasks
> Freezing remaining freezable tasks completed (elapsed 0.003 seconds)
>
> ============================================
> WARNING: possible recursive locking detected
> 7.1.0-rc7+ #13003 Not tainted
> --------------------------------------------
> rtcwake/254 is trying to acquire lock:
> ffff000006de64e8 (&dev->tx_global_lock){+.-.}-{3:3}, at: netdev_watchdog_up+0x40/0x108
>
> but task is already holding lock:
> ffff000006de64e8 (&dev->tx_global_lock){+.-.}-{3:3}, at: netif_tx_lock+0x1c/0x34
>
> other info that might help us debug this:
> Possible unsafe locking scenario:
>
> CPU0
> ----
> lock(&dev->tx_global_lock);
> lock(&dev->tx_global_lock);
>
> *** DEADLOCK ***
>
> May be due to missing lock nesting notation
>
> 6 locks held by rtcwake/254:
> #0: ffff0000071ab3e8 (sb_writers#5){.+.+}-{0:0}, at: vfs_write+0x1ec/0x35c
> #1: ffff00000d22c480 (&of->mutex#2){+.+.}-{4:4}, at: kernfs_fop_write_iter+0xf0/0x1c4
> #2: ffff0000049162c8 (kn->active#61){.+.+}-{0:0}, at: kernfs_fop_write_iter+0x100/0x1c4
> #3: ffffaa79533c03b0 (system_transition_mutex){+.+.}-{4:4}, at: pm_suspend+0x98/0x608
> #4: ffff000005e3a138 (&dev->mutex){....}-{4:4}, at: device_resume+0xb4/0x254
> #5: ffff000006de64e8 (&dev->tx_global_lock){+.-.}-{3:3}, at: netif_tx_lock+0x1c/0x34
>
> stack backtrace:
> CPU: 1 UID: 0 PID: 254 Comm: rtcwake Not tainted 7.1.0-rc7+ #13003 PREEMPT
> Hardware name: linux,dummy-virt (DT)
> Call trace:
> show_stack+0x18/0x24 (C)
> dump_stack_lvl+0x90/0xd0
> dump_stack+0x18/0x24
> print_deadlock_bug+0x260/0x350
> __lock_acquire+0x11b8/0x225c
> lock_acquire+0x1c4/0x3f0
> _raw_spin_lock_bh+0x50/0x68
> netdev_watchdog_up+0x40/0x108
> netif_device_attach+0x9c/0xb0
> virtnet_restore+0x100/0x21c
> virtio_device_restore_priv+0x11c/0x1d0
> virtio_device_restore+0x14/0x20
> virtio_mmio_restore+0x34/0x40
> platform_pm_resume+0x2c/0x68
> dpm_run_callback+0xa0/0x240
> device_resume+0x120/0x254
> dpm_resume+0x1f8/0x2ec
> dpm_resume_end+0x18/0x34
> suspend_devices_and_enter+0x1d0/0x990
> pm_suspend+0x1ec/0x608
> state_store+0x8c/0x110
> kobj_attr_store+0x18/0x2c
> sysfs_kf_write+0x50/0x7c
> kernfs_fop_write_iter+0x130/0x1c4
> vfs_write+0x2b8/0x35c
> ksys_write+0x6c/0x104
> __arm64_sys_write+0x1c/0x28
> invoke_syscall+0x54/0x110
> el0_svc_common.constprop.0+0x40/0xe8
> do_el0_svc+0x20/0x2c
> el0_svc+0x54/0x338
> el0t_64_sync_handler+0xa0/0xe4
> el0t_64_sync+0x198/0x19c
>
>
> Reverting $subject on top of linux-next fixes this issue.
Thanks for the report Marek!
Acquiring tx_global_lock in netdev_watchdog_up() appears unnecessary anyway
because the critical state (timer and refcount tracker) is already
protected by dev->watchdog_lock.
Could you try this patch?
diff --git a/net/sched/sch_generic.c b/net/sched/sch_generic.c
index 3f1c510df850dbdbaf10d483547c7b1f3a5d5482..ef2b4bf51564173751c74fefe17e3913ed2fa056
100644
--- a/net/sched/sch_generic.c
+++ b/net/sched/sch_generic.c
@@ -594,9 +594,8 @@ void netdev_watchdog_up(struct net_device *dev)
return;
if (dev->watchdog_timeo <= 0)
dev->watchdog_timeo = 5*HZ;
- spin_lock_bh(&dev->tx_global_lock);
- spin_lock(&dev->watchdog_lock);
+ spin_lock_bh(&dev->watchdog_lock);
if (!mod_timer(&dev->watchdog_timer,
round_jiffies(jiffies + dev->watchdog_timeo))) {
if (!dev->watchdog_ref_held) {
@@ -605,9 +604,7 @@ void netdev_watchdog_up(struct net_device *dev)
dev->watchdog_ref_held = true;
}
}
- spin_unlock(&dev->watchdog_lock);
-
- spin_unlock_bh(&dev->tx_global_lock);
+ spin_unlock_bh(&dev->watchdog_lock);
}
EXPORT_SYMBOL_GPL(netdev_watchdog_up);
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v3 net] net: watchdog: fix refcount tracking races
2026-06-22 8:59 ` Eric Dumazet
@ 2026-06-22 10:22 ` Marek Szyprowski
0 siblings, 0 replies; 5+ messages in thread
From: Marek Szyprowski @ 2026-06-22 10:22 UTC (permalink / raw)
To: Eric Dumazet
Cc: David S . Miller, Jakub Kicinski, Paolo Abeni, Simon Horman,
netdev, eric.dumazet, syzbot+381d82bbf0253710b35d,
syzbot+3479efbc2821cb2a79f2
On 22.06.2026 10:59, Eric Dumazet wrote:
> On Wed, Jun 17, 2026 at 3:48 AM Marek Szyprowski
> <m.szyprowski@samsung.com> wrote:
>> On 11.06.2026 17:27, Eric Dumazet wrote:
>>> Blamed commit converted the untracked dev_hold()/dev_put() calls
>>> in the watchdog code to use the tracked dev_hold_track()/dev_put_track()
>>> (which were later renamed/interfaced to netdev_hold() and netdev_put()).
>>>
>>> By introducing dev->watchdog_dev_tracker to store the
>>> reference tracking information without adding synchronization
>>> between netdev_watchdog_up() and dev_watchdog(), it enabled the
>>> race condition where this pointer could be overwritten or freed
>>> concurrently, leading to the list corruption crash syzbot reported:
>>>
>>> list_del corruption, ffff888114a18c00->next is NULL
>>> kernel BUG at lib/list_debug.c:52 !
>>> Oops: invalid opcode: 0000 [#1] SMP KASAN PTI
>>> CPU: 1 UID: 0 PID: 91 Comm: kworker/u8:5 Not tainted syzkaller #0 PREEMPT(lazy)
>>> Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 05/09/2026
>>> Workqueue: events_unbound linkwatch_event
>>> RIP: 0010:__list_del_entry_valid_or_report.cold+0x22/0x2a lib/list_debug.c:52
>>> Call Trace:
>>> <TASK>
>>> __list_del_entry_valid include/linux/list.h:132 [inline]
>>> __list_del_entry include/linux/list.h:246 [inline]
>>> list_move_tail include/linux/list.h:341 [inline]
>>> ref_tracker_free+0x1a7/0x6c0 lib/ref_tracker.c:329
>>> netdev_tracker_free include/linux/netdevice.h:4491 [inline]
>>> netdev_put include/linux/netdevice.h:4508 [inline]
>>> netdev_put include/linux/netdevice.h:4504 [inline]
>>> netdev_watchdog_down net/sched/sch_generic.c:600 [inline]
>>> dev_deactivate_many+0x28c/0xfe0 net/sched/sch_generic.c:1363
>>> dev_deactivate+0x109/0x1d0 net/sched/sch_generic.c:1397
>>> linkwatch_do_dev net/core/link_watch.c:184 [inline]
>>> linkwatch_do_dev+0xd3/0x120 net/core/link_watch.c:166
>>> __linkwatch_run_queue+0x3a5/0x810 net/core/link_watch.c:240
>>> linkwatch_event+0x8f/0xc0 net/core/link_watch.c:314
>>> process_one_work+0xa0e/0x1980 kernel/workqueue.c:3314
>>> process_scheduled_works kernel/workqueue.c:3397 [inline]
>>> worker_thread+0x5ef/0xe50 kernel/workqueue.c:3478
>>> kthread+0x370/0x450 kernel/kthread.c:436
>>> ret_from_fork+0x69a/0xc80 arch/x86/kernel/process.c:158
>>> ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
>>>
>>> This patch has three coordinated parts:
>>>
>>> 1) Add dev->watchdog_lock and dev->watchdog_ref_held to serialize watchdog operations.
>>>
>>> 2) Remove netdev_watchdog_up() call from netif_carrier_on():
>>> This ensures netdev_watchdog_up() is only called from process/BH context
>>> (via linkwatch workqueue dev_activate()), allowing us to use
>>> spin_lock_bh() for synchronization.
>>>
>>> 3) Synchronize watchdog up and watchdog timer:
>>> Protect netdev_watchdog_up() with tx_global_lock and watchdog_lock.
>>> Only allocate a new tracker in netdev_watchdog_up() if one is
>>> not already present.
>>> In dev_watchdog(), ensure we don't release the tracker if the
>>> timer was rescheduled either by dev_watchdog() itself or concurrently
>>> by netdev_watchdog_up().
>>>
>>> Fixes: f12bf6f3f942 ("net: watchdog: add net device refcount tracker")
>>> Reported-by: syzbot+381d82bbf0253710b35d@syzkaller.appspotmail.com
>>> Closes: https://lore.kernel.org/netdev/6a26b751.c25708ab.1b19ef.0013.GAE@google.com/T/#u
>>> Tested-by: syzbot+3479efbc2821cb2a79f2@syzkaller.appspotmail.com
>>> Signed-off-by: Eric Dumazet <edumazet@google.com>
>> This patch landed recently in linux-next as commit 8eed5519e496 ("net: watchdog:
>> fix refcount tracking races"). In my tests I found that it causes the following
>> deadlock during system suspend/resume on QEmu's ARM64bit 'virt' machine:
>>
>> root@target:~# time rtcwake -s10 -mmem
>> rtcwake: assuming RTC uses UTC ...
>> rtcwake: wakeup from "mem" using /dev/rtc0 at Wed Jun 17 10:46:12 2026
>> PM: suspend entry (s2idle)
>> Filesystems sync: 0.055 seconds
>> Freezing user space processes
>> Freezing user space processes completed (elapsed 0.006 seconds)
>> OOM killer disabled.
>> Freezing remaining freezable tasks
>> Freezing remaining freezable tasks completed (elapsed 0.003 seconds)
>>
>> ============================================
>> WARNING: possible recursive locking detected
>> 7.1.0-rc7+ #13003 Not tainted
>> --------------------------------------------
>> rtcwake/254 is trying to acquire lock:
>> ffff000006de64e8 (&dev->tx_global_lock){+.-.}-{3:3}, at: netdev_watchdog_up+0x40/0x108
>>
>> but task is already holding lock:
>> ffff000006de64e8 (&dev->tx_global_lock){+.-.}-{3:3}, at: netif_tx_lock+0x1c/0x34
>>
>> other info that might help us debug this:
>> Possible unsafe locking scenario:
>>
>> CPU0
>> ----
>> lock(&dev->tx_global_lock);
>> lock(&dev->tx_global_lock);
>>
>> *** DEADLOCK ***
>>
>> May be due to missing lock nesting notation
>>
>> 6 locks held by rtcwake/254:
>> #0: ffff0000071ab3e8 (sb_writers#5){.+.+}-{0:0}, at: vfs_write+0x1ec/0x35c
>> #1: ffff00000d22c480 (&of->mutex#2){+.+.}-{4:4}, at: kernfs_fop_write_iter+0xf0/0x1c4
>> #2: ffff0000049162c8 (kn->active#61){.+.+}-{0:0}, at: kernfs_fop_write_iter+0x100/0x1c4
>> #3: ffffaa79533c03b0 (system_transition_mutex){+.+.}-{4:4}, at: pm_suspend+0x98/0x608
>> #4: ffff000005e3a138 (&dev->mutex){....}-{4:4}, at: device_resume+0xb4/0x254
>> #5: ffff000006de64e8 (&dev->tx_global_lock){+.-.}-{3:3}, at: netif_tx_lock+0x1c/0x34
>>
>> stack backtrace:
>> CPU: 1 UID: 0 PID: 254 Comm: rtcwake Not tainted 7.1.0-rc7+ #13003 PREEMPT
>> Hardware name: linux,dummy-virt (DT)
>> Call trace:
>> show_stack+0x18/0x24 (C)
>> dump_stack_lvl+0x90/0xd0
>> dump_stack+0x18/0x24
>> print_deadlock_bug+0x260/0x350
>> __lock_acquire+0x11b8/0x225c
>> lock_acquire+0x1c4/0x3f0
>> _raw_spin_lock_bh+0x50/0x68
>> netdev_watchdog_up+0x40/0x108
>> netif_device_attach+0x9c/0xb0
>> virtnet_restore+0x100/0x21c
>> virtio_device_restore_priv+0x11c/0x1d0
>> virtio_device_restore+0x14/0x20
>> virtio_mmio_restore+0x34/0x40
>> platform_pm_resume+0x2c/0x68
>> dpm_run_callback+0xa0/0x240
>> device_resume+0x120/0x254
>> dpm_resume+0x1f8/0x2ec
>> dpm_resume_end+0x18/0x34
>> suspend_devices_and_enter+0x1d0/0x990
>> pm_suspend+0x1ec/0x608
>> state_store+0x8c/0x110
>> kobj_attr_store+0x18/0x2c
>> sysfs_kf_write+0x50/0x7c
>> kernfs_fop_write_iter+0x130/0x1c4
>> vfs_write+0x2b8/0x35c
>> ksys_write+0x6c/0x104
>> __arm64_sys_write+0x1c/0x28
>> invoke_syscall+0x54/0x110
>> el0_svc_common.constprop.0+0x40/0xe8
>> do_el0_svc+0x20/0x2c
>> el0_svc+0x54/0x338
>> el0t_64_sync_handler+0xa0/0xe4
>> el0t_64_sync+0x198/0x19c
>>
>>
>> Reverting $subject on top of linux-next fixes this issue.
> Thanks for the report Marek!
>
> Acquiring tx_global_lock in netdev_watchdog_up() appears unnecessary anyway
> because the critical state (timer and refcount tracker) is already
> protected by dev->watchdog_lock.
>
> Could you try this patch?
This fixes the observed issue. Thanks! Feel free to add:
Tested-by: Marek Szyprowski <m.szyprowski@samsung.com>
> diff --git a/net/sched/sch_generic.c b/net/sched/sch_generic.c
> index 3f1c510df850dbdbaf10d483547c7b1f3a5d5482..ef2b4bf51564173751c74fefe17e3913ed2fa056
> 100644
> --- a/net/sched/sch_generic.c
> +++ b/net/sched/sch_generic.c
> @@ -594,9 +594,8 @@ void netdev_watchdog_up(struct net_device *dev)
> return;
> if (dev->watchdog_timeo <= 0)
> dev->watchdog_timeo = 5*HZ;
> - spin_lock_bh(&dev->tx_global_lock);
>
> - spin_lock(&dev->watchdog_lock);
> + spin_lock_bh(&dev->watchdog_lock);
> if (!mod_timer(&dev->watchdog_timer,
> round_jiffies(jiffies + dev->watchdog_timeo))) {
> if (!dev->watchdog_ref_held) {
> @@ -605,9 +604,7 @@ void netdev_watchdog_up(struct net_device *dev)
> dev->watchdog_ref_held = true;
> }
> }
> - spin_unlock(&dev->watchdog_lock);
> -
> - spin_unlock_bh(&dev->tx_global_lock);
> + spin_unlock_bh(&dev->watchdog_lock);
> }
> EXPORT_SYMBOL_GPL(netdev_watchdog_up);
>
Best regards
--
Marek Szyprowski, PhD
Samsung R&D Institute Poland
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-06-22 10:22 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
[not found] <CGME20260617104823eucas1p2268fc45472e6acbcbe70ee7813b4e9dd@eucas1p2.samsung.com>
2026-06-11 15:27 ` [PATCH v3 net] net: watchdog: fix refcount tracking races Eric Dumazet
2026-06-13 1:00 ` patchwork-bot+netdevbpf
2026-06-17 10:48 ` Marek Szyprowski
2026-06-22 8:59 ` Eric Dumazet
2026-06-22 10:22 ` Marek Szyprowski
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox