* [PATCH v4 0/5] serial: fix console lifetime bugs on failed bind and removal
@ 2026-07-31 18:18 Karl Mehltretter
2026-07-31 18:18 ` [PATCH v4 1/5] serial: core: do fallible allocations before the console can be registered Karl Mehltretter
2026-07-31 18:18 ` [PATCH v4 5/5] serial: imx: serialize imx_uart_ports[] lifetime Karl Mehltretter
0 siblings, 2 replies; 4+ messages in thread
From: Karl Mehltretter @ 2026-07-31 18:18 UTC (permalink / raw)
To: Greg Kroah-Hartman, Jiri Slaby
Cc: Karl Mehltretter, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, linux-serial, linux-kernel, imx, linux-arm-kernel
This series fixes four serial/tty error-path bugs and an i.MX console
port-table lifetime bug. The fixes were found with failslab fail-nth sweeps
and sysfs bind/unbind sequences under KASAN on QEMU's raspi1ap and
mcimx6ul-evk machines.
Patches 1-4 fix shared serial/tty core code. They move fallible allocations
before console registration, clear pointers after uart_driver registration
fails, and make a non-NULL tty cdev slot mean that the cdev is live.
Patch 5 clears i.MX's devm-allocated port-table entry after failed probe and
removal. It keeps the entry valid through console unregistration, rejects
duplicate lines and serializes table updates against other port probes.
Validation includes fail-nth sweeps on both boards. The i.MX tests also
cover the sibling-probe race, sequential unbind/rebind and
CONFIG_PROVE_LOCKING=y.
Changes in v4:
- No code changes. v3 was accidentally sent as individual mails.
Resending as a single thread.
Changes in v3:
- Drop Cc: stable from patches 1-4.
- Patch 1: remove redundant uport->name clearing and credit Sashiko's report
with Reported-by and Closes trailers.
- Split v2 patch 3 into two patches with corresponding Fixes tags.
- Shorten the changelogs.
- Patch 5: use scoped guards, reject occupied slots, remove the unnecessary
line variable, document the console-callback locking constraint and add a
backport note.
Changes in v2:
- Patch 4: serialize port registration and removal with imx_uart_ports[]
updates to close the reported sibling-probe race.
- Patches 1-3 are unchanged.
v3: https://lore.kernel.org/r/20260731180343.10818-1-kmehltretter@gmail.com
v2: https://lore.kernel.org/r/20260719221014.44354-1-kmehltretter@gmail.com
v1: https://lore.kernel.org/r/20260719160812.35407-1-kmehltretter@gmail.com
Karl Mehltretter (5):
serial: core: do fallible allocations before the console can be
registered
serial: core: clear freed pointers on uart_register_driver() failure
tty: skip cdev_del() when no cdev is registered
tty: clear cdev pointer after cdev_add() failure
serial: imx: serialize imx_uart_ports[] lifetime
drivers/tty/serial/imx.c | 20 +++++++++++++++++---
drivers/tty/serial/serial_core.c | 32 ++++++++++++++++++--------------
drivers/tty/tty_io.c | 6 ++++--
3 files changed, 39 insertions(+), 19 deletions(-)
--
2.53.0
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH v4 1/5] serial: core: do fallible allocations before the console can be registered
2026-07-31 18:18 [PATCH v4 0/5] serial: fix console lifetime bugs on failed bind and removal Karl Mehltretter
@ 2026-07-31 18:18 ` Karl Mehltretter
2026-07-31 18:18 ` [PATCH v4 5/5] serial: imx: serialize imx_uart_ports[] lifetime Karl Mehltretter
1 sibling, 0 replies; 4+ messages in thread
From: Karl Mehltretter @ 2026-07-31 18:18 UTC (permalink / raw)
To: Greg Kroah-Hartman, Jiri Slaby
Cc: Karl Mehltretter, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, linux-serial, linux-kernel, imx, linux-arm-kernel,
Sashiko
serial_core_add_one_port() allocates uport->tty_groups after
uart_configure_port(), which may register the console. If the allocation
fails, the driver unwinds the port while its console remains registered.
The earlier uport->name allocation has a related failure path that leaves
state->uart_port linked to a port being freed.
Failslab reproduced a NULL dereference in PL011 console output and a KASAN
use-after-free in i.MX console output after failed binds.
Allocate the name and tty_groups before linking the port and configuring
it. Reserve space for the optional driver attribute group because
config_port() may populate uport->attr_group during configuration.
Fixes: 266dcff03eed ("Serial: allow port drivers to have a default attribute group")
Fixes: f7048b15900f ("tty: serial_core: Add name field to uart_port struct")
Reported-by: Sashiko <sashiko-bot@kernel.org>
Closes: https://lore.kernel.org/all/20260719070454.D6FA21F000E9@smtp.kernel.org/
Assisted-by: Claude:claude-fable-5
Signed-off-by: Karl Mehltretter <kmehltretter@gmail.com>
---
drivers/tty/serial/serial_core.c | 30 ++++++++++++++++--------------
1 file changed, 16 insertions(+), 14 deletions(-)
diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c
index a530ad372b43..03ee3d038f4e 100644
--- a/drivers/tty/serial/serial_core.c
+++ b/drivers/tty/serial/serial_core.c
@@ -3056,7 +3056,6 @@ static int serial_core_add_one_port(struct uart_driver *drv, struct uart_port *u
struct uart_state *state;
struct tty_port *port;
struct device *tty_dev;
- int num_groups;
if (uport->line >= drv->nr)
return -EINVAL;
@@ -3068,6 +3067,22 @@ static int serial_core_add_one_port(struct uart_driver *drv, struct uart_port *u
if (state->uart_port)
return -EINVAL;
+ uport->name = kasprintf(GFP_KERNEL, "%s%u", drv->dev_name,
+ drv->tty_driver->name_base + uport->line);
+ if (!uport->name)
+ return -ENOMEM;
+
+ /*
+ * uart_configure_port() may set uport->attr_group and register the
+ * console. Allocate room for both groups and a NULL terminator first.
+ */
+ uport->tty_groups = kzalloc_objs(*uport->tty_groups, 3);
+ if (!uport->tty_groups) {
+ kfree(uport->name);
+ return -ENOMEM;
+ }
+ uport->tty_groups[0] = &tty_dev_attr_group;
+
/* Link the port to the driver state table and vice versa */
atomic_set(&state->refcount, 1);
init_waitqueue_head(&state->remove_wait);
@@ -3084,10 +3099,6 @@ static int serial_core_add_one_port(struct uart_driver *drv, struct uart_port *u
state->pm_state = UART_PM_STATE_UNDEFINED;
uart_port_set_cons(uport, drv->cons);
uport->minor = drv->tty_driver->minor_start + uport->line;
- uport->name = kasprintf(GFP_KERNEL, "%s%u", drv->dev_name,
- drv->tty_driver->name_base + uport->line);
- if (!uport->name)
- return -ENOMEM;
if (uport->cons && uport->dev)
of_console_check(uport->dev->of_node, uport->cons->name, uport->line);
@@ -3102,15 +3113,6 @@ static int serial_core_add_one_port(struct uart_driver *drv, struct uart_port *u
port->console = uart_console(uport);
- num_groups = 2;
- if (uport->attr_group)
- num_groups++;
-
- uport->tty_groups = kzalloc_objs(*uport->tty_groups, num_groups);
- if (!uport->tty_groups)
- return -ENOMEM;
-
- uport->tty_groups[0] = &tty_dev_attr_group;
if (uport->attr_group)
uport->tty_groups[1] = uport->attr_group;
--
2.53.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH v4 5/5] serial: imx: serialize imx_uart_ports[] lifetime
2026-07-31 18:18 [PATCH v4 0/5] serial: fix console lifetime bugs on failed bind and removal Karl Mehltretter
2026-07-31 18:18 ` [PATCH v4 1/5] serial: core: do fallible allocations before the console can be registered Karl Mehltretter
@ 2026-07-31 18:18 ` Karl Mehltretter
2026-07-31 18:35 ` sashiko-bot
1 sibling, 1 reply; 4+ messages in thread
From: Karl Mehltretter @ 2026-07-31 18:18 UTC (permalink / raw)
To: Greg Kroah-Hartman, Jiri Slaby
Cc: Karl Mehltretter, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, linux-serial, linux-kernel, imx, linux-arm-kernel,
Sashiko, stable
imx_uart_probe() publishes its devm-allocated port in imx_uart_ports[]
before uart_add_one_port() because console setup uses the table. The entry
is not cleared when adding the port fails or after removal, leaving a
dangling pointer.
A sibling probe can register the shared console through that stale entry.
This was reproduced under KASAN on QEMU mcimx6ul-evk by unbinding a
sibling UART, unbinding the console UART and rebinding the sibling.
Keep the entry valid through uart_remove_one_port(), then clear it. Protect
port addition and removal together with their table updates so sibling
operations cannot interleave. Reject an occupied slot rather than
clobbering an active port during a duplicate-line probe.
Fixes: dbff4e9ea2e8 ("IMX UART: remove statically initialized tables")
Fixes: 9f322ad064f9 ("imx: serial: handle initialisation failure correctly")
Reported-by: Sashiko <sashiko-bot@kernel.org>
Link: https://lore.kernel.org/all/20260719162850.043B41F000E9@smtp.kernel.org
Link: https://lore.kernel.org/all/20260719222501.CB4CB1F000E9@smtp.kernel.org
Cc: stable@vger.kernel.org
Assisted-by: Claude:claude-fable-5
Signed-off-by: Karl Mehltretter <kmehltretter@gmail.com>
---
Backport note: the removal fix is self-contained. Complete probe-failure
coverage also requires patch 1. With patch 1, all fallible allocations
precede console registration, so rollback can safely clear the table.
Without it, a late allocation failure (only reachable with fault
injection) can leave the console registered after imx_uart_ports[] is
cleared, turning the pre-existing use-after-free into a NULL
dereference.
drivers/tty/serial/imx.c | 20 +++++++++++++++++---
1 file changed, 17 insertions(+), 3 deletions(-)
diff --git a/drivers/tty/serial/imx.c b/drivers/tty/serial/imx.c
index 251a50c8aa38..b0f34a6e7d4f 100644
--- a/drivers/tty/serial/imx.c
+++ b/drivers/tty/serial/imx.c
@@ -22,6 +22,7 @@
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/ktime.h>
+#include <linux/mutex.h>
#include <linux/pinctrl/consumer.h>
#include <linux/rational.h>
#include <linux/slab.h>
@@ -2080,6 +2081,9 @@ static const struct uart_ops imx_uart_pops = {
static struct imx_port *imx_uart_ports[UART_NR];
+/* Held across uart_add/remove_one_port(); console callbacks must not take it. */
+static DEFINE_MUTEX(imx_uart_ports_lock);
+
#if IS_ENABLED(CONFIG_SERIAL_IMX_CONSOLE)
static void imx_uart_console_putchar(struct uart_port *port, unsigned char ch)
{
@@ -2632,11 +2636,19 @@ static int imx_uart_probe(struct platform_device *pdev)
}
}
- imx_uart_ports[sport->port.line] = sport;
-
platform_set_drvdata(pdev, sport);
- ret = uart_add_one_port(&imx_uart_uart_driver, &sport->port);
+ scoped_guard(mutex, &imx_uart_ports_lock) {
+ if (imx_uart_ports[sport->port.line]) {
+ ret = -EBUSY;
+ } else {
+ imx_uart_ports[sport->port.line] = sport;
+ ret = uart_add_one_port(&imx_uart_uart_driver,
+ &sport->port);
+ if (ret)
+ imx_uart_ports[sport->port.line] = NULL;
+ }
+ }
err_clk:
clk_disable_unprepare(sport->clk_ipg);
@@ -2648,7 +2660,9 @@ static void imx_uart_remove(struct platform_device *pdev)
{
struct imx_port *sport = platform_get_drvdata(pdev);
+ guard(mutex)(&imx_uart_ports_lock);
uart_remove_one_port(&imx_uart_uart_driver, &sport->port);
+ imx_uart_ports[sport->port.line] = NULL;
}
static void imx_uart_restore_context(struct imx_port *sport)
--
2.53.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH v4 5/5] serial: imx: serialize imx_uart_ports[] lifetime
2026-07-31 18:18 ` [PATCH v4 5/5] serial: imx: serialize imx_uart_ports[] lifetime Karl Mehltretter
@ 2026-07-31 18:35 ` sashiko-bot
0 siblings, 0 replies; 4+ messages in thread
From: sashiko-bot @ 2026-07-31 18:35 UTC (permalink / raw)
To: Karl Mehltretter; +Cc: Frank.Li, imx
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
New issues:
- [Medium] Mixing goto-based error handling with scope-based cleanup helpers (`scoped_guard`) in the same function.
Pre-existing issues:
- [High] The `trigger_start_tx` and `trigger_stop_tx` hrtimers are never explicitly canceled, leading to a potential Use-After-Free.
- [High] The interrupt handlers are registered before the UART port's spinlock is initialized, exposing an uninitialized lock to concurrent execution.
--
--- Patch [5]: [PATCH v4 5/5] serial: imx: serialize imx_uart_ports[] lifetime ---
commit e68620832979b5e6ab71402a28efa1b401b7be4c
Author: Karl Mehltretter <kmehltretter@gmail.com>
serial: imx: serialize imx_uart_ports[] lifetime
This patch protects port addition and removal with a mutex to prevent sibling
operations from interleaving. It keeps the imx_uart_ports[] entry valid
through uart_remove_one_port() and clears it afterward, rejecting occupied
slots during a duplicate-line probe rather than clobbering an active port.
Link: https://lore.kernel.org/all/20260719162850.043B41F000E9@smtp.kernel.org
Link: https://lore.kernel.org/all/20260719222501.CB4CB1F000E9@smtp.kernel.org
> diff --git a/drivers/tty/serial/imx.c b/drivers/tty/serial/imx.c
> index 251a50c8aa387..b0f34a6e7d4f8 100644
> --- a/drivers/tty/serial/imx.c
> +++ b/drivers/tty/serial/imx.c
[ ... ]
> @@ -2632,11 +2636,19 @@ static int imx_uart_probe(struct platform_device *pdev)
> }
> }
>
> - imx_uart_ports[sport->port.line] = sport;
> -
> platform_set_drvdata(pdev, sport);
>
> - ret = uart_add_one_port(&imx_uart_uart_driver, &sport->port);
> + scoped_guard(mutex, &imx_uart_ports_lock) {
[Severity: Medium]
Does this mix goto-based error handling and scope-based cleanup helpers in
the same function?
Looking at imx_uart_probe(), the function heavily relies on goto labels like
goto err_clk for error cleanup. The cleanup subsystem guidelines expect that
usage of goto and cleanup helpers are never mixed in the same routine, and
that all resources should either be converted to scope-based cleanup or left
unconverted.
Should this function be converted entirely, or should this continue using
standard mutex_lock/unlock to match the rest of the error handling?
[Severity: High]
This isn't a bug introduced by this patch, but is there a race condition
with interrupt handlers being registered before the UART port's spinlock
is initialized?
Earlier in imx_uart_probe(), devm_request_irq() is called to request
interrupts before uart_add_one_port() is called here. The sport structure
is allocated via devm_kzalloc(), leaving sport->port.lock zero-initialized.
The lock is not formally initialized until uart_add_one_port() runs.
If a spurious interrupt or a pending interrupt from the bootloader triggers
between devm_request_irq() and uart_add_one_port(), would the IRQ handler
imx_uart_int() attempt to acquire an uninitialized lock, leading to a
panic or lockdep crash?
> + if (imx_uart_ports[sport->port.line]) {
> + ret = -EBUSY;
> + } else {
> + imx_uart_ports[sport->port.line] = sport;
> + ret = uart_add_one_port(&imx_uart_uart_driver,
> + &sport->port);
> + if (ret)
> + imx_uart_ports[sport->port.line] = NULL;
> + }
> + }
>
> err_clk:
> clk_disable_unprepare(sport->clk_ipg);
> @@ -2648,7 +2660,9 @@ static void imx_uart_remove(struct platform_device *pdev)
> {
> struct imx_port *sport = platform_get_drvdata(pdev);
>
> + guard(mutex)(&imx_uart_ports_lock);
> uart_remove_one_port(&imx_uart_uart_driver, &sport->port);
[Severity: High]
This is a pre-existing issue, but does driver removal leave active hrtimers
running that could cause a use-after-free?
During driver unbind, imx_uart_shutdown() waits for the hrtimer-driven state
machine to finish using a simple timeout loop, but it doesn't explicitly call
hrtimer_cancel() if it times out.
Shortly after uart_remove_one_port() completes here, devres will free the
sport memory. If either the trigger_start_tx or trigger_stop_tx hrtimers
are still active, could they fire and dereference the freed sport structure?
> + imx_uart_ports[sport->port.line] = NULL;
> }
>
> static void imx_uart_restore_context(struct imx_port *sport)
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260731181844.11330-1-kmehltretter@gmail.com?part=5
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-07-31 18:35 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-31 18:18 [PATCH v4 0/5] serial: fix console lifetime bugs on failed bind and removal Karl Mehltretter
2026-07-31 18:18 ` [PATCH v4 1/5] serial: core: do fallible allocations before the console can be registered Karl Mehltretter
2026-07-31 18:18 ` [PATCH v4 5/5] serial: imx: serialize imx_uart_ports[] lifetime Karl Mehltretter
2026-07-31 18:35 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox