From: Chengfeng Ye <nicoyip.dev@gmail.com>
To: Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
Jiri Slaby <jirislaby@kernel.org>,
Richard Watts <rrw@kynesim.co.uk>
Cc: linux-kernel@vger.kernel.org, linux-serial@vger.kernel.org,
Chengfeng Ye <nicoyip.dev@gmail.com>,
stable@vger.kernel.org
Subject: [PATCH] tty: serialize device registration and removal
Date: Fri, 31 Jul 2026 22:22:20 +0800 [thread overview]
Message-ID: <20260731142220.3000214-1-nicoyip.dev@gmail.com> (raw)
tty_cdev_add() stores a newly allocated cdev in driver->cdevs[index]
before initializing and registering it. tty_unregister_device() reads and
deletes the same pointer without serialization.
The race can proceed as follows:
registration removal
------------ -------
driver->cdevs[index] = cdev
cdev_del(driver->cdevs[index])
kobject_put() frees cdev
driver->cdevs[index]->ops = &tty_fops
The final write accesses freed memory. Concurrent registration can also
replace the slot while another caller is still using its cdev.
KASAN reported:
BUG: KASAN: slab-use-after-free in tty_cdev_add+0x5c7/0x670
Write of size 8 at addr ffff88810b298c48
Call Trace:
tty_cdev_add+0x5c7/0x670
tty_register_device_attr+0x458/0x810
gsm_activate_mux+0x105/0x2e0
gsmld_ioctl+0x92f/0x14d0
Allocated by task 111:
cdev_alloc+0x99/0x130
tty_cdev_add+0x93/0x670
Freed by task 110:
cdev_dynamic_release+0x38/0x50
kobject_put+0x14d/0x280
tty_unregister_device+0x1b7/0x3c0
Add a per-driver mutex and hold it across complete device registration and
removal. This prevents either transition from replacing or freeing a cdev
until the other has finished.
Fixes: a3a10ce3429e ("Avoid usb reset crashes by making tty_io cdevs truly dynamic")
Cc: stable@vger.kernel.org
Signed-off-by: Chengfeng Ye <nicoyip.dev@gmail.com>
---
drivers/tty/tty_io.c | 13 ++++++++++---
include/linux/tty_driver.h | 3 +++
2 files changed, 13 insertions(+), 3 deletions(-)
diff --git a/drivers/tty/tty_io.c b/drivers/tty/tty_io.c
index 6b283fd03ff8..926a9b586c7d 100644
--- a/drivers/tty/tty_io.c
+++ b/drivers/tty/tty_io.c
@@ -3185,7 +3185,8 @@ static int tty_cdev_add(struct tty_driver *driver, dev_t dev,
* that bit is not set, this function should not be called by a tty
* driver.
*
- * Locking: ??
+ * Locking: device registration and removal are serialized by
+ * @driver->device_mutex.
*
* Return: A pointer to the struct device for this tty device (or
* ERR_PTR(-EFOO) on error).
@@ -3217,7 +3218,8 @@ static void tty_device_create_release(struct device *dev)
* tty driver's flags have the %TTY_DRIVER_DYNAMIC_DEV bit set. If that bit is
* not set, this function should not be called by a tty driver.
*
- * Locking: ??
+ * Locking: device registration and removal are serialized by
+ * @driver->device_mutex.
*
* Return: A pointer to the struct device for this tty device (or
* ERR_PTR(-EFOO) on error).
@@ -3257,6 +3259,7 @@ struct device *tty_register_device_attr(struct tty_driver *driver,
dev_set_drvdata(dev, drvdata);
dev_set_uevent_suppress(dev, 1);
+ guard(mutex)(&driver->device_mutex);
retval = device_register(dev);
if (retval)
@@ -3300,10 +3303,13 @@ EXPORT_SYMBOL_GPL(tty_register_device_attr);
* If a tty device is registered with a call to tty_register_device() then
* this function must be called when the tty device is gone.
*
- * Locking: ??
+ * Locking: device registration and removal are serialized by
+ * @driver->device_mutex.
*/
void tty_unregister_device(struct tty_driver *driver, unsigned index)
{
+ guard(mutex)(&driver->device_mutex);
+
device_destroy(&tty_class, MKDEV(driver->major, driver->minor_start) + index);
if (!(driver->flags & TTY_DRIVER_DYNAMIC_ALLOC)) {
cdev_del(driver->cdevs[index]);
@@ -3338,6 +3344,7 @@ struct tty_driver *__tty_alloc_driver(unsigned int lines, struct module *owner,
return ERR_PTR(-ENOMEM);
kref_init(&driver->kref);
+ mutex_init(&driver->device_mutex);
driver->num = lines;
driver->owner = owner;
driver->flags = flags;
diff --git a/include/linux/tty_driver.h b/include/linux/tty_driver.h
index 1f2896e56e77..bf8c9d8ec698 100644
--- a/include/linux/tty_driver.h
+++ b/include/linux/tty_driver.h
@@ -7,6 +7,7 @@
#include <linux/kref.h>
#include <linux/list.h>
#include <linux/cdev.h>
+#include <linux/mutex.h>
#include <linux/uaccess.h>
#include <linux/termios.h>
#include <linux/seq_file.h>
@@ -497,6 +498,7 @@ struct tty_operations {
* @kref: reference counting. Reaching zero frees all the internals and the
* driver.
* @cdevs: allocated/registered character /dev devices
+ * @device_mutex: serializes device registration and removal
* @owner: modules owning this driver. Used drivers cannot be rmmod'ed.
* Automatically set by tty_alloc_driver().
* @driver_name: name of the driver used in /proc/tty
@@ -532,6 +534,7 @@ struct tty_operations {
struct tty_driver {
struct kref kref;
struct cdev **cdevs;
+ struct mutex device_mutex;
struct module *owner;
const char *driver_name;
const char *name;
--
2.43.0
reply other threads:[~2026-07-31 14:22 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260731142220.3000214-1-nicoyip.dev@gmail.com \
--to=nicoyip.dev@gmail.com \
--cc=gregkh@linuxfoundation.org \
--cc=jirislaby@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-serial@vger.kernel.org \
--cc=rrw@kynesim.co.uk \
--cc=stable@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.