[PATCH v2] serial: core: fix NULL pointer dereference in serial_core_unregister_port()

From: Ruslan Valiyev

Date: Wed Aug 26 2026 - 04:50:40 EST


port->port_dev is NULL when no port device is installed: it is cleared
on teardown, and never set if registration failed before
serial_core_port_device_add(). serial_core_unregister_port() passes it
straight to serial_core_get_ctrl_dev(), which dereferences it:

KASAN: null-ptr-deref in range [0x0000000000000040-0x0000000000000047]
RIP: serial_core_unregister_port
Call Trace:
serial8250_unregister_port
serial8250_remove
unbind_store

Return early when there is no port device, and read port->port_dev
under port_mutex.

Also clear port->port_dev on the serial_core_register_port() error
path, where the port device has already been removed.

Fixes: 84a9582fd203 ("serial: core: Start managing serial controllers to enable runtime PM")
Reported-by: syzbot+9f57c1b2792029198fcf@xxxxxxxxxxxxxxxxxxxxxxxxx
Closes: https://syzkaller.appspot.com/bug?extid=9f57c1b2792029198fcf
Cc: stable@xxxxxxxxxxxxxxx
Assisted-by: Claude:claude-opus-5
Assisted-by: Codex:gpt-5.6-sol
Signed-off-by: Ruslan Valiyev <linuxoid@xxxxxxxxx>
---
Reproduced on 8d3ae59288f1 with syzbot's config under QEMU/KVM x86_64:
6/6 runs oops on stock, 0/6 patched. checkpatch clean, no new W=1
warnings.

The reproducer still does not run to completion on a patched kernel. It
goes on to hit two pre-existing problems in the tty layer that this patch
does not touch: tty_cdev_add() leaves driver->cdevs[index] pointing at a
freed cdev when cdev_add() fails, and tty_unregister_device() deletes that
entry unconditionally when it is NULL. Mentioning it so the remaining
crashes are not mistaken for this fix failing.

v1: https://lore.kernel.org/all/20260826073237.1377668-1-linuxoid@xxxxxxxxx/
v2: trimmed the commit message and backtrace per Andy Shevchenko's
review, added the Assisted-by tags.
drivers/tty/serial/serial_core.c | 17 +++++++++++++++--
1 file changed, 15 insertions(+), 2 deletions(-)

diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c
index a530ad372b434..5bf71d7bbd223 100644
--- a/drivers/tty/serial/serial_core.c
+++ b/drivers/tty/serial/serial_core.c
@@ -3327,6 +3327,7 @@ int serial_core_register_port(struct uart_driver *drv, struct uart_port *port)

err_unregister_port_dev:
serial_base_port_device_remove(port->port_dev);
+ port->port_dev = NULL;

err_unregister_ctrl_dev:
serial_base_ctrl_device_remove(new_ctrl_dev);
@@ -3341,12 +3342,24 @@ int serial_core_register_port(struct uart_driver *drv, struct uart_port *port)
void serial_core_unregister_port(struct uart_driver *drv, struct uart_port *port)
{
struct device *phys_dev = port->dev;
- struct serial_port_device *port_dev = port->port_dev;
- struct serial_ctrl_device *ctrl_dev = serial_core_get_ctrl_dev(port_dev);
+ struct serial_port_device *port_dev;
+ struct serial_ctrl_device *ctrl_dev;
int ctrl_id = port->ctrl_id;

guard(mutex)(&port_mutex);

+ /*
+ * A NULL port device means there is no registered port device to
+ * remove: serial_core_remove_one_port() clears port_dev on
+ * teardown, and it is never set if registration failed before
+ * serial_core_port_device_add().
+ */
+ port_dev = port->port_dev;
+ if (!port_dev)
+ return;
+
+ ctrl_dev = serial_core_get_ctrl_dev(port_dev);
+
port->flags |= UPF_DEAD;

serial_core_remove_one_port(drv, port);

base-commit: 8d3ae59288f1e7d58d76558a6ee96d533bc5019f
--
2.43.0