From: Xion Wang xion.wang@mediatek.com
[ Upstream commit e28022873c0d051e980c4145f1965cab5504b498 ]
Currently, misc_deregister() uses list_del() to remove the device from the list. After list_del(), the list pointers are set to LIST_POISON1 and LIST_POISON2, which may help catch use-after-free bugs, but does not reset the list head. If misc_deregister() is called more than once on the same device, list_empty() will not return true, and list_del() may be called again, leading to undefined behavior.
Replace list_del() with list_del_init() to reinitialize the list head after deletion. This makes the code more robust against double deregistration and allows safe usage of list_empty() on the miscdevice after deregistration.
[ Note, this seems to keep broken out-of-tree drivers from doing foolish things. While this does not matter for any in-kernel drivers, external drivers could use a bit of help to show them they shouldn't be doing stuff like re-registering misc devices - gregkh ]
Signed-off-by: Xion Wang xion.wang@mediatek.com Link: https://lore.kernel.org/r/20250904063714.28925-2-xion.wang@mediatek.com Signed-off-by: Greg Kroah-Hartman gregkh@linuxfoundation.org Signed-off-by: Sasha Levin sashal@kernel.org ---
LLM Generated explanations, may be completely bogus:
YES
- What changed: In drivers/char/misc.c, misc_deregister() replaces list_del(&misc->list) with list_del_init(&misc->list) (drivers/char/misc.c: list_del → list_del_init). - Bug addressed: After list_del(), the entry’s next/prev are poisoned, so list_empty(&misc->list) is false and a second misc_deregister() can call list_del() on poisoned pointers, causing undefined behavior. With list_del_init(), the list node is reinitialized to point to itself, so list_empty(&misc->list) returns true after deregistration and a second call cleanly becomes a no-op for the list operation. - Scope and impact: The fix is a one-line, localized change confined to the misc core. It doesn’t alter the normal registration/deregistration flow. The surrounding teardown remains unchanged and safe on repeated calls: device_destroy(&misc_class, MKDEV(MISC_MAJOR, misc->minor)) and misc_minor_free(misc->minor) are tolerant when the device is already gone, and the code resets the minor to MISC_DYNAMIC_MINOR afterward (drivers/char/misc.c: the nearby if (misc->minor > MISC_DYNAMIC_MINOR) misc->minor = MISC_DYNAMIC_MINOR;), further reducing risk on subsequent calls. - Risk assessment: Minimal. The only behavior change is reinitializing the list node instead of poisoning it, which improves idempotency and enables correct membership checks via list_empty() after deregistration. Concurrency is guarded by misc_mtx as before. The main trade-off is slightly less aggressive poisoning for this node, but it materially reduces the chance of crashes from double deregistration paths. - Stable criteria: This is a defensive bug fix that prevents potential crashes/oopses when drivers—particularly in error paths or out-of-tree modules—call misc_deregister() more than once. It is small, contained, and non-architectural, with no new features or API changes. The commit message explicitly notes the robustness improvement against double deregistration and has maintainer sign-off.
Conclusion: This is a low-risk, robustness-improving bug fix suitable for stable backporting.
drivers/char/misc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/char/misc.c b/drivers/char/misc.c index 4c276b8066ff8..ea5b4975347a0 100644 --- a/drivers/char/misc.c +++ b/drivers/char/misc.c @@ -281,7 +281,7 @@ void misc_deregister(struct miscdevice *misc) return;
mutex_lock(&misc_mtx); - list_del(&misc->list); + list_del_init(&misc->list); device_destroy(&misc_class, MKDEV(MISC_MAJOR, misc->minor)); misc_minor_free(misc->minor); if (misc->minor > MISC_DYNAMIC_MINOR)