summaryrefslogtreecommitdiff
path: root/sys
diff options
context:
space:
mode:
authorpho <pho@NetBSD.org>2023-06-27 01:15:22 +0000
committerpho <pho@NetBSD.org>2023-06-27 01:15:22 +0000
commit34e8508d21d6b11b3f08cd6d621bda3ad80a66f8 (patch)
tree8374c220037f865c0c35368b958530a012c3ba29 /sys
parent8e39f005c1a069e0fad7ac39c87bfbfdb9c896bf (diff)
callout(9): Fix panic() in callout_destroy() (kern/57226)
The culprit was callout_halt(). "(c->c_flags & CALLOUT_FIRED) != 0" wasn't the correct way to check if a callout is running. It failed to wait for a running callout to finish in the following scenario: 1. cpu0 initializes a callout and schedules it. 2. cpu0 invokes callout_softlock() and fires the callout, setting the flag CALLOUT_FIRED. 3. The callout invokes callout_schedule() to re-schedule itself. 4. callout_schedule_locked() clears the flag CALLOUT_FIRED, and releases the lock. 5. Before the lock is re-acquired by callout_softlock(), cpu1 decides to destroy the callout. It first invokes callout_halt() to make sure the callout finishes running. 6. But since CALLOUT_FIRED has been cleared, callout_halt() thinks it's not running and therefore returns without invoking callout_wait(). 7. cpu1 proceeds to invoke callout_destroy() while it's still running on cpu0. callout_destroy() detects that and panics.
Diffstat (limited to 'sys')
-rw-r--r--sys/kern/kern_timeout.c14
1 files changed, 7 insertions, 7 deletions
diff --git a/sys/kern/kern_timeout.c b/sys/kern/kern_timeout.c
index 3f3338a6787..5a7912c3d08 100644
--- a/sys/kern/kern_timeout.c
+++ b/sys/kern/kern_timeout.c
@@ -1,4 +1,4 @@
-/* $NetBSD: kern_timeout.c,v 1.73 2022/10/29 00:19:21 riastradh Exp $ */
+/* $NetBSD: kern_timeout.c,v 1.74 2023/06/27 01:15:22 pho Exp $ */
/*-
* Copyright (c) 2003, 2006, 2007, 2008, 2009, 2019 The NetBSD Foundation, Inc.
@@ -59,7 +59,7 @@
*/
#include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: kern_timeout.c,v 1.73 2022/10/29 00:19:21 riastradh Exp $");
+__KERNEL_RCSID(0, "$NetBSD: kern_timeout.c,v 1.74 2023/06/27 01:15:22 pho Exp $");
/*
* Timeouts are kept in a hierarchical timing wheel. The c_time is the
@@ -542,7 +542,7 @@ callout_halt(callout_t *cs, void *interlock)
{
callout_impl_t *c = (callout_impl_t *)cs;
kmutex_t *lock;
- int flags;
+ struct callout_cpu *cc;
KASSERT(c->c_magic == CALLOUT_MAGIC);
KASSERT(!cpu_intr_p());
@@ -552,11 +552,11 @@ callout_halt(callout_t *cs, void *interlock)
lock = callout_lock(c);
SDT_PROBE4(sdt, kernel, callout, halt,
c, c->c_func, c->c_arg, c->c_flags);
- flags = c->c_flags;
- if ((flags & CALLOUT_PENDING) != 0)
+ if ((c->c_flags & CALLOUT_PENDING) != 0)
CIRCQ_REMOVE(&c->c_list);
- c->c_flags = flags & ~(CALLOUT_PENDING|CALLOUT_FIRED);
- if (__predict_false(flags & CALLOUT_FIRED)) {
+ c->c_flags &= ~(CALLOUT_PENDING|CALLOUT_FIRED);
+ cc = c->c_cpu;
+ if (__predict_false(cc->cc_active == c && cc->cc_lwp != curlwp)) {
callout_wait(c, interlock, lock);
return true;
}