Source-Changes-HG archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

[src/trunk]: src/sys/kern callout(9): Fix panic() in callout_destroy() (kern/...



details:   https://anonhg.NetBSD.org/src/rev/fdbee7f6df9e
branches:  trunk
changeset: 376640:fdbee7f6df9e
user:      pho <pho%NetBSD.org@localhost>
date:      Tue Jun 27 01:15:22 2023 +0000

description:
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:

 sys/kern/kern_timeout.c |  14 +++++++-------
 1 files changed, 7 insertions(+), 7 deletions(-)

diffs (43 lines):

diff -r bf0216e7eed6 -r fdbee7f6df9e sys/kern/kern_timeout.c
--- a/sys/kern/kern_timeout.c   Mon Jun 26 22:04:18 2023 +0000
+++ b/sys/kern/kern_timeout.c   Tue Jun 27 01:15:22 2023 +0000
@@ -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 *interl
 {
        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 *interl
        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;
        }



Home | Main Index | Thread Index | Old Index