DF-3524 / fix.diff
DF-3524 is fixed by restoring the callout_stop(9) contract in sys/kern/kern_timeout.c (the DF-3521-validated fix): an inline-completed armed stop must report CALLOUT_PREVENTED so ng_uncallout() returns non-zero and ng_l2cap_lp_untimeout()/ng_l2cap_command_untimeout() stop misreading successfully-cancelled timers as "already fired". Validated in-guest on a rebuilt kernel (kernel #1, make nativekernel RC=0) with STOCK ng7 modules: LP_ConnectCfm accepted (CON_OPEN), L2CAP echo round-trips, teardown panic gone. --- a/sys/kern/kern_timeout.c +++ b/sys/kern/kern_timeout.c @@ -885,10 +885,28 @@ * If the operation is still in-progress then re-acquire the spin-lock * and block if necessary. Also initiate the lock cancel. */ - if (sync == 0 || (c->flags & (CALLOUT_INPROG | CALLOUT_SET)) == 0) { + /* + * If the operation completed inline above (the armed callout was + * removed from the wheel and will never execute), report whether + * execution was PREVENTED. Returning 0 for a successfully + * cancelled pending callout violates the callout_stop(9) contract + * and makes conditional cleanup in consumers (e.g. netgraph + * ng_uncallout -> ng_l2cap_lp_untimeout, which reads 0 as + * "timer already fired" and returns ETIMEDOUT) misbehave: every + * L2CAP LP_ConnectCfm is discarded and the stuck-timeout con + * panics ng_l2cap_free_con at node teardown. + */ + if (sync == 0) { exis_drop_gd(gd); return 0; } + if ((c->flags & (CALLOUT_INPROG | CALLOUT_SET)) == 0) { + /* inline-completed stop/cancel: report PREVENTED, not 0 */ + res = ((c->flags & CALLOUT_PREVENTED) != 0); + exis_drop_gd(gd); + return (res); + } if (c->flags & CALLOUT_AUTOLOCK) lockmgr(c->lk, LK_CANCEL_BEG); spin_lock(&c->spin); |