Linux Serial subsystem development
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Ruslan Valiyev" <linuxoid@gmail.com>
Cc: linux-serial@vger.kernel.org
Subject: Re: [PATCH] serial: core: fix NULL pointer dereference in serial_core_unregister_port()
Date: Wed, 26 Aug 2026 07:50:31 +0000	[thread overview]
Message-ID: <20260826075031.D06451F000E9@smtp.kernel.org> (raw)
In-Reply-To: <20260826073237.1377668-1-linuxoid@gmail.com>

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:

Pre-existing issues:
- [High] `tty_unregister_device()` unconditionally deregisters a `cdev`, causing UAF or NULL pointer dereference if `tty_cdev_add()` previously failed.
- [High] `uart_resume_port()` dereferences a NULL `tty_dev` in `device_may_wakeup()` if port registration failed and the driver kept the port active.
- [High] Concurrent read-modify-write of `port->flags` without proper locking causes `UPF_DEAD` to be lost, undermining port teardown safety.
--

commit a9b5deb58407eb5cb3253b4b3646215bfcb86ab0
Author: Ruslan Valiyev <linuxoid@gmail.com>

serial: core: fix NULL pointer dereference in serial_core_unregister_port()

This commit fixes a NULL pointer dereference in serial_core_unregister_port()
that occurs when unregistering a port with no port device installed. It adds
an early return if port_dev is NULL and explicitly clears port->port_dev
on the error path of serial_core_register_port().

> 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;

[Severity: High]
This is a pre-existing issue, but does uart_resume_port() dereference a
NULL tty_dev in device_may_wakeup() if port registration failed and the
driver kept the port active?

If serial_core_register_port() fails, port_dev is set to NULL. If this port
is configured as a console, during system suspend uart_suspend_port() will
return early (leaving uport->suspended = 0).

On system resume, uart_resume_port() calls device_find_child(NULL, ...):

drivers/tty/serial/serial_core.c:uart_resume_port() {
	tty_dev = device_find_child(&uport->port_dev->dev, &match, serial_match_port);
	if (!uport->suspended && device_may_wakeup(tty_dev)) {
		...
	}
}

It safely returns NULL for tty_dev. Since uport->suspended is 0, the
short-circuit && does not prevent device_may_wakeup(tty_dev) from being
called.

device_may_wakeup(NULL) unconditionally dereferences NULL->power.can_wakeup:

include/linux/pm_wakeup.h:device_may_wakeup() {
	return dev->power.can_wakeup && !!dev->power.wakeup;
}

>  
>  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;

[Severity: High]
This is a pre-existing issue, but can a concurrent read-modify-write of
port->flags without proper locking cause UPF_DEAD to be lost, undermining
port teardown safety?

serial_core_unregister_port() sets the UPF_DEAD bit in port->flags while
holding the global port_mutex, but without holding the TTY &port->mutex.

Concurrently, uart_set_info() modifies port->flags using a read-modify-write
operation while holding the TTY &port->mutex:

drivers/tty/serial/serial_core.c:uart_set_info() {
	uport->flags = (uport->flags & ~UPF_CHANGE_MASK) |
				 (new_flags & UPF_CHANGE_MASK);
}

If the threads interleave, uart_set_info() can overwrite and clear the
UPF_DEAD bit set by the teardown path. If UPF_DEAD is lost,
uart_port_activate() will incorrectly allow an unbound/removed port to be
opened and activated.

>  
>  	serial_core_remove_one_port(drv, port);

[Severity: High]
This is a pre-existing issue, but does tty_unregister_device() unconditionally
deregister a cdev, causing UAF or NULL pointer dereference if tty_cdev_add()
previously failed?

If cdev_alloc() or cdev_add() fails in tty_cdev_add(), it leaves
driver->cdevs[index] pointing to NULL or freed memory:

drivers/tty/tty_io.c:tty_cdev_add() {
	err = cdev_add(driver->cdevs[index], dev, count);
	if (err)
		kobject_put(&driver->cdevs[index]->kobj);
	return err;
}

Because serial_core_add_one_port() ignores this error, treating TTY
registration failure as non-fatal and returning success, the port gets
registered:

drivers/tty/serial/serial_core.c:serial_core_add_one_port() {
	if (!IS_ERR(tty_dev)) {
		device_set_wakeup_capable(tty_dev, 1);
	} else {
		uport->flags |= UPF_DEAD;
		dev_err(uport->dev, "Cannot register tty device on line %u\n",
		       uport->line);
	}
	return 0;
}

On teardown, serial_core_remove_one_port() calls
tty_port_unregister_device() which invokes tty_unregister_device().
tty_unregister_device() calls cdev_del(driver->cdevs[index]) unconditionally
since serial_core does not use the TTY_DRIVER_DYNAMIC_ALLOC flag:

drivers/tty/tty_io.c:tty_unregister_device() {
	if (!(driver->flags & TTY_DRIVER_DYNAMIC_ALLOC)) {
		cdev_del(driver->cdevs[index]);
		driver->cdevs[index] = NULL;
	}
}

cdev_del() does not check for NULL and dereferences the pointer, causing a
direct kernel panic.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260826073237.1377668-1-linuxoid@gmail.com?part=1

  reply	other threads:[~2026-08-26  7:50 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-26  7:32 [PATCH] serial: core: fix NULL pointer dereference in serial_core_unregister_port() Ruslan Valiyev
2026-08-26  7:50 ` sashiko-bot [this message]
2026-08-26  7:53 ` Greg Kroah-Hartman
2026-08-26  8:06   ` Ruslan Valiyev
2026-08-26  8:20     ` Greg Kroah-Hartman
2026-08-26  8:13 ` Andy Shevchenko

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=20260826075031.D06451F000E9@smtp.kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=linux-serial@vger.kernel.org \
    --cc=linuxoid@gmail.com \
    --cc=sashiko-reviews@lists.linux.dev \
    /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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox