DF-0628 / fix.diff
diff --git a/sys/netproto/smb/smb_conn.h b/sys/netproto/smb/smb_conn.h --- a/sys/netproto/smb/smb_conn.h +++ b/sys/netproto/smb/smb_conn.h @@ -432,6 +432,7 @@ }; #define SMBIOD_SHUTDOWN 0x0001 +#define SMBIOD_EXITED 0x0002 /* DF-0628: iod kthread has exited */ struct smbiod { int iod_id; diff --git a/sys/netproto/smb/smb_iod.c b/sys/netproto/smb/smb_iod.c --- a/sys/netproto/smb/smb_iod.c +++ b/sys/netproto/smb/smb_iod.c @@ -679,6 +679,11 @@ break; tsleep(&iod->iod_flags, 0, "90idle", iod->iod_sleeptimo); } + /* DF-0628: signal exit to smb_iod_destroy() which is waiting before + * freeing iod. Without this barrier, the destroyer can kfree(iod) + * while this thread is still executing between wakeup(evp) and here. */ + iod->iod_flags |= SMBIOD_EXITED; + wakeup(iod); kthread_exit_compat(); } @@ -717,6 +722,15 @@ smb_iod_destroy(struct smbiod *iod) { smb_iod_request(iod, SMBIOD_EV_SHUTDOWN | SMBIOD_EV_SYNC, NULL); + /* + * DF-0628: the SYNC handshake above guarantees the SHUTDOWN event was + * *processed* (SMBIOD_SHUTDOWN set in iod_flags), not that the iod + * kthread has exited. Wait for the kthread to actually exit before + * reclaiming iod; otherwise the kthread re-reads iod->iod_flags + * (smb_iod_thread:675,678) after the free, a use-after-free. + */ + while ((iod->iod_flags & SMBIOD_EXITED) == 0) + tsleep(iod, 0, "90iodx", hz); smb_sl_destroy(&iod->iod_rqlock); smb_sl_destroy(&iod->iod_evlock); kfree(iod, M_SMBIOD); |