The Linux Kernel Mailing List
 help / color / mirror / Atom feed
* [PATCH v6] serial: 8250: fix use-after-free in IRQ chain handling
@ 2026-07-07 14:06 Qiliang Yuan
  2026-07-08  6:03 ` Jiri Slaby
  0 siblings, 1 reply; 17+ messages in thread
From: Qiliang Yuan @ 2026-07-07 14:06 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Jiri Slaby, Anton Vorontsov, Alan Cox
  Cc: linux-kernel, linux-serial, Wang Zhaolong, Qiliang Yuan

serial_unlink_irq_chain() holds hash_mutex and calls free_irq() + kfree(i)
on an empty port list.  serial_link_irq_chain() used to release hash_mutex
after serial_get_or_create_irq_info() but before acquiring i->lock, so a
concurrent unlink could see list_empty() as true while a port was being
added, free i, and cause a use-after-free.

Dropping hash_mutex before request_irq() completes also lets another port
on the same IRQ join the chain and run the THRE test while IRQ startup is
still in progress, triggering "Unbalanced enable for IRQ" because
irq_shutdown() in the premature free_irq() path increments desc->depth and
breaks the disable_irq/enable_irq pairing in serial8250_THRE_test().

Hold hash_mutex across the whole first request_irq() completion (including
the error cleanup path) by taking it at the top of serial_link_irq_chain()
via guard(mutex)(&hash_mutex).  serial_unlink_irq_chain() already holds
hash_mutex throughout, so the race is closed.

With hash_mutex now taken by the caller, replace the guard in
serial_get_or_create_irq_info() with lockdep_assert_held() and add
__must_hold(&hash_mutex) for static analysis.

Fixes: 768aec0b5bcc ("serial: 8250: fix shared interrupts issues with SMP and RT kernels")
Reported-by: Wang Zhaolong <wangzhaolong@fnnas.com>
Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221579
Signed-off-by: Qiliang Yuan <realwujing@gmail.com>
---
V5 -> V6:
- Keep guard(mutex) and scoped_guard(spinlock_irq) style instead of
  manual locking; the lock lifecycle fits the scope-based model.

V4 -> V5:
- Add __must_hold(&hash_mutex) annotation to
  serial_get_or_create_irq_info() for static analysis.

V3 -> V4:
- Move cleanup under hash_mutex on request_irq() failure to prevent a
  second port from joining the chain before the irq_info is cleaned up.
- Fix inaccurate description of irq_shutdown() in commit message.

V2 -> V3:
- Hold hash_mutex across the first request_irq() completion to prevent a
  second port from joining the chain and running the shared-IRQ THRE test
  while IRQ startup is still in progress.

V1 -> V2:
- Add Reported-by tag from Wang Zhaolong.

v5: https://lore.kernel.org/r/20260624-bug-221579-8250-shared-irq-race-v5-1-15d841f89e1e@gmail.com
v4: https://lore.kernel.org/r/20260529-bug-221579-8250-shared-irq-race-v4-1-cfda63b4420f@gmail.com
v3: https://lore.kernel.org/r/20260529-bug-221579-8250-shared-irq-race-v3-1-fe4d430862a9@gmail.com
v2: https://lore.kernel.org/r/20260528-bug-221579-8250-shared-irq-race-v2-1-06531202e54d@gmail.com
v1: https://lore.kernel.org/r/20260528-bug-221579-8250-shared-irq-race-v1-1-30980cca02f3@gmail.com
---
 drivers/tty/serial/8250/8250_core.c | 32 +++++++++++++++++++++++++++-----
 1 file changed, 27 insertions(+), 5 deletions(-)

diff --git a/drivers/tty/serial/8250/8250_core.c b/drivers/tty/serial/8250/8250_core.c
index a428e88938eb7..6fd3bb2eee233 100644
--- a/drivers/tty/serial/8250/8250_core.c
+++ b/drivers/tty/serial/8250/8250_core.c
@@ -131,10 +131,11 @@ static void serial_do_unlink(struct irq_info *i, struct uart_8250_port *up)
  * - allocate a new one, add it to the hashtable and return it.
  */
 static struct irq_info *serial_get_or_create_irq_info(const struct uart_8250_port *up)
+	__must_hold(&hash_mutex)
 {
 	struct irq_info *i;
 
-	guard(mutex)(&hash_mutex);
+	lockdep_assert_held(&hash_mutex);
 
 	hash_for_each_possible(irq_lists, i, node, up->port.irq)
 		if (i->irq == up->port.irq)
@@ -151,19 +152,37 @@ static struct irq_info *serial_get_or_create_irq_info(const struct uart_8250_por
 	return i;
 }
 
+/*
+ * serial_link_irq_chain() hooks the given 8250 port into the IRQ chain.
+ *
+ * hash_mutex must be held from the hash lookup through the first
+ * request_irq() completion.  Dropping it earlier allows a concurrent
+ * serial_unlink_irq_chain() to race in after i->head is published but
+ * before the IRQ is fully set up — another port sharing the IRQ can then
+ * join the chain and run the shared-IRQ THRE test while IRQ startup is
+ * still in progress, triggering an "Unbalanced enable for IRQ" warning
+ * in kernel/irq/manage.c.
+ */
 static int serial_link_irq_chain(struct uart_8250_port *up)
 {
 	struct irq_info *i;
 	int ret;
 
+	guard(mutex)(&hash_mutex);
+
 	i = serial_get_or_create_irq_info(up);
 	if (IS_ERR(i))
 		return PTR_ERR(i);
 
+	/*
+	 * Serialise against the list manipulation in the interrupt handler
+	 * and in serial_unlink_irq_chain().  hash_mutex is still held which
+	 * prevents serial_unlink_irq_chain() from entering and freeing the
+	 * irq_info until the first request_irq() completes.
+	 */
 	scoped_guard(spinlock_irq, &i->lock) {
 		if (i->head) {
 			list_add(&up->list, i->head);
-
 			return 0;
 		}
 
@@ -171,11 +190,14 @@ static int serial_link_irq_chain(struct uart_8250_port *up)
 		i->head = &up->list;
 	}
 
-	ret = request_irq(up->port.irq, serial8250_interrupt, up->port.irqflags, up->port.name, i);
-	if (ret < 0)
+	ret = request_irq(up->port.irq, serial8250_interrupt,
+			  up->port.irqflags, up->port.name, i);
+	if (ret < 0) {
 		serial_do_unlink(i, up);
+		return ret;
+	}
 
-	return ret;
+	return 0;
 }
 
 static void serial_unlink_irq_chain(struct uart_8250_port *up)

---
base-commit: eb3f4b7426cfd2b79d65b7d37155480b32259a11
change-id: 20260528-bug-221579-8250-shared-irq-race-581e4900a178

Best regards,
-- 
Jing Wu <realwujing@gmail.com>


^ permalink raw reply related	[flat|nested] 17+ messages in thread
* [PATCH] serial: 8250: serialize shared IRQ startup
@ 2026-05-27  9:20 Wang Zhaolong
  2026-07-08  3:11 ` [PATCH v2] serial: 8250: fix shared IRQ startup race causing IRQ warning Wang Zhaolong
  0 siblings, 1 reply; 17+ messages in thread
From: Wang Zhaolong @ 2026-05-27  9:20 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Jiri Slaby, Ilpo Järvinen, Xin Zhao,
	Andy Shevchenko, Kees Cook, Ingo Molnar, Bing Fan, Guanbing Huang,
	linux-kernel, linux-serial
  Cc: Wang Zhaolong, stable

Concurrent startup of two 8250 ports sharing the same IRQ can trigger an
IRQ core warning:

  Unbalanced enable for IRQ 3
  WARNING: CPU: 0 PID: 580 at kernel/irq/manage.c:774 __enable_irq+0x3b/0x60
  Call Trace:
   enable_irq+0x8d/0x120
   serial8250_do_startup+0x80d/0xa80
   uart_port_startup+0x13d/0x440
   uart_port_activate+0x5b/0xb0
   tty_port_open+0xa1/0x120
   uart_open+0x1e/0x30
   tty_open+0x140/0x7a0

The second port can then run the shared-IRQ startup test while the IRQ core
is still enabling the line for the first port.  The local
disable_irq_nosync()/enable_irq() pair is balanced, but the interleaving can
still unbalance the IRQ core disable depth.

That makes the QEMU legacy serial ports enter the shared-IRQ THRE test path:

  serial8250_do_startup()
    if (port->irqflags & IRQF_SHARED)
      disable_irq_nosync(port->irq)
    ...
    if (port->irqflags & IRQF_SHARED)
      enable_irq(port->irq)

One possible interleaving is:

  CPU0, ttyS1                         CPU1, ttyS3

  serial_link_irq_chain()
    hash_add(i)
    i->head = &ttyS1
    request_irq()
                                        serial_link_irq_chain()
                                          find i in irq_lists
                                          list_add(&ttyS3, i->head)
                                        serial8250_do_startup()
                                          disable_irq_nosync(irq)
    irq_startup()
      desc->depth = 0
                                          enable_irq(irq)
                                            WARN: Unbalanced enable for IRQ 3

Keep hash_mutex held in serial_link_irq_chain() until the first request_irq()
has completed.  This prevents another 8250 port sharing the IRQ from joining
the chain and running the THRE test while the IRQ core is still starting the
interrupt.

This was reproduced in QEMU with ttyS1 and ttyS3 sharing IRQ 3.  With this
change, 100000 synchronized open/close iterations on /dev/ttyS1 and /dev/ttyS3
completed without the warning.

Fixes: 64c79dfbc458 ("serial: 8250_pnp: Support configurable reg shift property")
Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221579
Cc: stable@vger.kernel.org # 6.10+
Assisted-by: Codex:gpt-5
Signed-off-by: Wang Zhaolong <wangzhaolong@fnnas.com>
---
 drivers/tty/serial/8250/8250_core.c | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/drivers/tty/serial/8250/8250_core.c b/drivers/tty/serial/8250/8250_core.c
index a428e88938eb..64eed4dc343f 100644
--- a/drivers/tty/serial/8250/8250_core.c
+++ b/drivers/tty/serial/8250/8250_core.c
@@ -132,12 +132,10 @@ static void serial_do_unlink(struct irq_info *i, struct uart_8250_port *up)
  */
 static struct irq_info *serial_get_or_create_irq_info(const struct uart_8250_port *up)
 {
 	struct irq_info *i;
 
-	guard(mutex)(&hash_mutex);
-
 	hash_for_each_possible(irq_lists, i, node, up->port.irq)
 		if (i->irq == up->port.irq)
 			return i;
 
 	i = kzalloc_obj(*i);
@@ -154,10 +152,12 @@ static struct irq_info *serial_get_or_create_irq_info(const struct uart_8250_por
 static int serial_link_irq_chain(struct uart_8250_port *up)
 {
 	struct irq_info *i;
 	int ret;
 
+	guard(mutex)(&hash_mutex);
+
 	i = serial_get_or_create_irq_info(up);
 	if (IS_ERR(i))
 		return PTR_ERR(i);
 
 	scoped_guard(spinlock_irq, &i->lock) {
@@ -169,10 +169,15 @@ static int serial_link_irq_chain(struct uart_8250_port *up)
 
 		INIT_LIST_HEAD(&up->list);
 		i->head = &up->list;
 	}
 
+	/*
+	 * Keep the shared-IRQ chain locked until the first handler is installed.
+	 * Otherwise another UART can join early and run startup IRQ masking while
+	 * the IRQ core is still enabling the line, unbalancing the disable depth.
+	 */
 	ret = request_irq(up->port.irq, serial8250_interrupt, up->port.irqflags, up->port.name, i);
 	if (ret < 0)
 		serial_do_unlink(i, up);
 
 	return ret;
-- 
2.54.0

^ permalink raw reply related	[flat|nested] 17+ messages in thread

end of thread, other threads:[~2026-07-10 13:28 UTC | newest]

Thread overview: 17+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-07 14:06 [PATCH v6] serial: 8250: fix use-after-free in IRQ chain handling Qiliang Yuan
2026-07-08  6:03 ` Jiri Slaby
2026-07-08  6:34   ` Jing Wu
2026-07-08  7:11   ` Jing Wu
2026-07-08  7:33   ` Wang Zhaolong
2026-07-08  7:57     ` Jing Wu
2026-07-08  8:20       ` Wang Zhaolong
2026-07-08  7:39   ` Jing Wu
2026-07-08  7:53     ` Wang Zhaolong
2026-07-08  8:08       ` Jing Wu
2026-07-08  8:32         ` Wang Zhaolong
     [not found]         ` <5cf37150673ea4d5c28f94db91cdf68504b50522.9be37cbd.7a04.4d7e.b20e.d3f90675af6e@feishu.cn>
2026-07-10 12:17           ` Greg KH
2026-07-10 13:15             ` Wang Zhaolong
2026-07-10 13:28               ` Greg KH
2026-07-08  8:43       ` Jing Wu
2026-07-08  8:45       ` Jing Wu
  -- strict thread matches above, loose matches on Subject: below --
2026-05-27  9:20 [PATCH] serial: 8250: serialize shared IRQ startup Wang Zhaolong
2026-07-08  3:11 ` [PATCH v2] serial: 8250: fix shared IRQ startup race causing IRQ warning Wang Zhaolong
2026-07-08  7:23   ` [PATCH v3] " Wang Zhaolong
2026-07-08  8:47     ` [PATCH v6] serial: 8250: fix use-after-free in IRQ chain handling Jing Wu

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox