DF-5065 / fix.diff
diff --git a/sys/dev/disk/dm/dm_target.c b/sys/dev/disk/dm/dm_target.c --- a/sys/dev/disk/dm/dm_target.c +++ b/sys/dev/disk/dm/dm_target.c @@ -277,15 +277,21 @@ int dm_target_uninit(void) { - dm_target_t *dm_target; - lockmgr(&dm_target_mutex, LK_EXCLUSIVE); - while ((dm_target = TAILQ_FIRST(&dm_target_list)) != NULL) { - TAILQ_REMOVE(&dm_target_list, dm_target, dm_target_next); - dm_target_free(dm_target); + /* + * A non-empty registry means a target module is still loaded and + * registered -- it calls dm_target_remove() from its own MOD_UNLOAD + * and would run through freed dm module text and a destroyed + * registry lock -- or a table still holds a reference (ref_cnt > 0), + * and freeing the entry leaves a dangling struct dm_target pointer + * in live dm_table_entry_t's. Refuse; the caller must veto the + * module unload. + */ + if (!TAILQ_EMPTY(&dm_target_list)) { + lockmgr(&dm_target_mutex, LK_RELEASE); + return EBUSY; } - KKASSERT(TAILQ_EMPTY(&dm_target_list)); lockmgr(&dm_target_mutex, LK_RELEASE); diff --git a/sys/dev/disk/dm/device-mapper.c b/sys/dev/disk/dm/device-mapper.c --- a/sys/dev/disk/dm/device-mapper.c +++ b/sys/dev/disk/dm/device-mapper.c @@ -187,11 +187,28 @@ static int dmdestroy(void) { + int error; + + /* + * Unregister the built-in targets explicitly: their SYSUNINITs + * (DM_TARGET_BUILTIN, dm.h) only run after this MOD_UNLOAD handler. + */ + dm_target_remove("error"); + dm_target_remove("zero"); + + /* + * Refuse to unload while any target module is still registered or + * any table still holds a target reference: dm_target_uninit() + * must not free the registry (or destroy its lock) underneath a + * still-loaded target module's dm_target_remove() call. + */ + if ((error = dm_target_uninit()) != 0) + return error; + destroy_dev(dmcdev); dm_dev_uninit(); dm_pdev_uninit(); - dm_target_uninit(); return 0; } |