serial: caif: fix use-after-free in caif_serial ldisc_close()
Subsystems (2)
PATCH v2 1/1 serial: caif: fix use-after-free in caif_serial ldisc_close()
From: Jiayuan Chen <[email protected]>
There is a use-after-free bug in caif_serial where handle_tx() may access ser->tty after the tty has been freed.
The race condition occurs between ldisc_close() and packet transmission:
CPU 0 (close) CPU 1 (xmit)
------------- ------------
ldisc_close()
tty_kref_put(ser->tty)
[tty may be freed here]
<-- race window -->
caif_xmit()
handle_tx()
tty = ser->tty // dangling ptr
tty->ops->write() // UAF!
schedule_work()
ser_release()
unregister_netdevice()The root cause is that tty_kref_put() is called in ldisc_close() while the network device is still active and can receive packets.
Since ser and tty have a 1:1 binding relationship with consistent lifecycles (ser is allocated in ldisc_open and freed in ser_release via unregister_netdevice, and each ser binds exactly one tty), we can safely defer the tty reference release to ser_release() where the network device is unregistered.
Fix this by moving tty_kref_put() from ldisc_close() to ser_release(), after unregister_netdevice(). This ensures the tty reference is held as long as the network device exists, preventing the UAF.
Note: We save ser->tty before unregister_netdevice() because ser is embedded in netdev's private data and will be freed along with netdev (needs_free_netdev = true).
How to reproduce: Add mdelay(500) at the beginning of ldisc_close() to widen the race window, then run the reproducer program [1].
Note: There is a separate deadloop issue in handle_tx() when using PORT_UNKNOWN serial ports (e.g., /dev/ttyS3 in QEMU without proper serial backend). This deadloop exists even without this patch, and is likely caused by inconsistency between uart_write_room() and uart_write() in serial core. It has been addressed in a separate patch [2].
KASAN report:
==================================================================
BUG: KASAN: slab-use-after-free in handle_tx+0x5d1/0x620 Read of size 1 at addr ffff8881131e1490 by task caif_uaf_trigge/9929 Call Trace: <TASK> dump_stack_lvl+0x10e/0x1f0 print_report+0xd0/0x630 kasan_report+0xe4/0x120 handle_tx+0x5d1/0x620 dev_hard_start_xmit+0x9d/0x6c0 __dev_queue_xmit+0x6e2/0x4410 packet_xmit+0x243/0x360 packet_sendmsg+0x26cf/0x5500 __sys_sendto+0x4a3/0x520 __x64_sys_sendto+0xe0/0x1c0 do_syscall_64+0xc9/0xf80 entry_SYSCALL_64_after_hwframe+0x77/0x7f RIP: 0033:0x7f615df2c0d7 Allocated by task 9930: Freed by task 64: Last potentially related work creation: The buggy address belongs to the object at ffff8881131e1000 which belongs to the cache kmalloc-cg-2k of size 2048
The buggy address is located 1168 bytes inside of
freed 2048-byte region [ffff8881131e1000, ffff8881131e1800)
The buggy address belongs to the physical page:
page_owner tracks the page as allocated
page last free pid 9778 tgid 9778 stack trace:
Memory state around the buggy address:
ffff8881131e1380: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
ffff8881131e1400: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
>ffff8881131e1480: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
^
ffff8881131e1500: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
ffff8881131e1580: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
==================================================================[1]: https://gist.github.com/mrpre/f683f244544f7b11e7fa87df9e6c2eeb
[2]: https://lore.kernel.org/linux-serial/[email protected]/T/#u
---
v1 -> v2:
Add Reviewed-by tag.
Use correct Fixes tag.
v1: https://lore.kernel.org/netdev/[email protected]/
--- drivers/net/caif/caif_serial.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-)
drivers/net/caif/caif_serial.c +3 -2
@@ -284,6 +284,7 @@ static void ser_release(struct work_struct *work) {struct list_head list;struct ser_device *ser, *tmp; +struct tty_struct *tty;spin_lock (&ser_lock);list_replace_init (&ser_release_list, &list);
@@ -292,9 +293,11 @@ static void ser_release(struct work_struct *work)if (!list_empty (&list)) {rtnl_lock ();list_for_each_entry_safe (ser, tmp, &list, node) { + tty = ser->tty;dev_close (ser->dev);unregister_netdevice (ser->dev);debugfs_deinit (ser); +tty_kref_put (tty); }rtnl_unlock (); }
@@ -355,8 +358,6 @@ static void ldisc_close(struct tty_struct *tty) {struct ser_device *ser = tty->disc_data; -tty_kref_put (ser->tty); -spin_lock (&ser_lock);list_move (&ser->node, &ser_release_list);spin_unlock (&ser_lock);
Signature · 1 line
2.43.0
Quoted 124 lines · Jiayuan Chen
From: Jiayuan Chen <[email protected]>
There is a use-after-free bug in
caif_serialwherehandle_tx()may accessser->ttyafter the tty has been freed.The race condition occurs between
ldisc_close()and packet transmission:CPU 0 (close) CPU 1 (xmit) ------------- ------------ ldisc_close() tty_kref_put(ser->tty) [tty may be freed here] <-- race window --> caif_xmit() handle_tx() tty = ser->tty // dangling ptr tty->ops->write() // UAF! schedule_work() ser_release() unregister_netdevice()The root cause is that
tty_kref_put()is called inldisc_close()while the network device is still active and can receive packets.Since ser and tty have a 1:1 binding relationship with consistent lifecycles (ser is allocated in
ldisc_openand freed inser_releaseviaunregister_netdevice, and each ser binds exactly one tty), we can safely defer the tty reference release toser_release()where the network device is unregistered.Fix this by moving
tty_kref_put()fromldisc_close()toser_release(), afterunregister_netdevice(). This ensures the tty reference is held as long as the network device exists, preventing the UAF.Note: We save
ser->ttybeforeunregister_netdevice()because ser is embedded in netdev's private data and will be freed along with netdev (needs_free_netdev= true).How to reproduce: Add mdelay(500) at the beginning of
ldisc_close()to widen the race window, then run the reproducer program [1].Note: There is a separate deadloop issue in
handle_tx()when usingPORT_UNKNOWNserial ports (e.g.,/dev/ttyS3in QEMU without proper serial backend). This deadloop exists even without this patch, and is likely caused by inconsistency betweenuart_write_room()anduart_write()in serial core. It has been addressed in a separate patch [2].KASAN report:
==================================================================
BUG: KASAN: slab-use-after-free in handle_tx+0x5d1/0x620 Read of size 1 at addr ffff8881131e1490 by task caif_uaf_trigge/9929 Call Trace: <TASK> dump_stack_lvl+0x10e/0x1f0 print_report+0xd0/0x630 kasan_report+0xe4/0x120 handle_tx+0x5d1/0x620 dev_hard_start_xmit+0x9d/0x6c0 __dev_queue_xmit+0x6e2/0x4410 packet_xmit+0x243/0x360 packet_sendmsg+0x26cf/0x5500 __sys_sendto+0x4a3/0x520 __x64_sys_sendto+0xe0/0x1c0 do_syscall_64+0xc9/0xf80 entry_SYSCALL_64_after_hwframe+0x77/0x7f RIP: 0033:0x7f615df2c0d7 Allocated by task 9930: Freed by task 64: Last potentially related work creation: The buggy address belongs to the object at ffff8881131e1000 which belongs to the cache kmalloc-cg-2k of size 2048The buggy address is located 1168 bytes inside of
freed 2048-byte region [ffff8881131e1000,ffff8881131e1800)The buggy address belongs to the physical page: page_owner tracks the page as allocated page last free pid 9778 tgid 9778 stack trace: Memory state around the buggy address: ffff8881131e1380: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb ffff8881131e1400: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fbQuoted 1 line
ffff8881131e1480: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb^
ffff8881131e1500: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
ffff8881131e1580: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
==================================================================
[1]: https://gist.github.com/mrpre/f683f244544f7b11e7fa87df9e6c2eeb
[2]: https://lore.kernel.org/linux-serial/[email protected]/T/#uReported-by [email protected]Fixes 56e0ef527b18 ("drivers/net: caif: fix wrong rtnl_is_locked() usage")Reviewed-by Greg Kroah-Hartman <[email protected]>Signed-off-by Jiayuan Chen <[email protected]>---
v1 -> v2:
Add Reviewed-by tag.
Use correct Fixes tag.
v1: https://lore.kernel.org/netdev/[email protected]/--- drivers/net/caif/caif_serial.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-)drivers/net/caif/caif_serial.c +1
-0@@ -284,6 +284,7 @@ static void ser_release(struct work_struct *work) {struct list_head list;struct ser_device *ser, *tmp; +struct tty_struct *tty;spin_lock (&ser_lock);list_replace_init (&ser_release_list, &list);
drivers/net/caif/caif_serial.c static void ser_release(struct work_struct *work) · line 299
Quoted 7 lines
drivers/net/caif/caif_serial.c +1
-0@@ -292,9 +293,11 @@ static void ser_release(struct work_struct *work)if (!list_empty (&list)) {rtnl_lock ();list_for_each_entry_safe (ser, tmp, &list, node) { + tty = ser->tty;dev_close (ser->dev);unregister_netdevice (ser->dev);debugfs_deinit (ser);
from log message:
Note: We save ser->tty before unregister_netdevice() because ser is embedded in netdev's private data and will be freed along with netdev (needs_free_netdev = true).
...
Therefore, since "ser" has already been released, "debugfs_deinit(ser)" should not be at this location. Of course, this issue is not related to this patch itself.
...
Also, after calling "dev_close(ser->dev)", can we directly call tty_kref_put()?
At this point, there should be no more packets to receive, so you don't need to save "ser->tty" anymore.
Of course, the current changes also look good to me. so,
Quoted 13 lines
drivers/net/caif/caif_serial.c +1
-2+tty_kref_put (tty); }rtnl_unlock (); }@@ -355,8 +358,6 @@ static void ldisc_close(struct tty_struct *tty) {struct ser_device *ser = tty->disc_data; -tty_kref_put (ser->tty); -spin_lock (&ser_lock);list_move (&ser->node, &ser_release_list);spin_unlock (&ser_lock);
This patch was applied to netdev/net.git (main) by Paolo Abeni <[email protected]>:
Hello:
This patch was applied to netdev/net.git (main)
by Paolo Abeni <[email protected]>:
Quoted 8 lines · On Fri, 6 Feb 2026 15:44:44 +0800 you wrote:
From: Jiayuan Chen <[email protected]>
There is a use-after-free bug in
caif_serialwherehandle_tx()may accessser->ttyafter the tty has been freed.The race condition occurs between
ldisc_close()and packet transmission:[...]
Here is the summary with links:
- [net,v2] serial: caif: fix use-after-free in caif_serial ldisc_close()
https://git.kernel.org/netdev/net/c/308e7e4d0a84
You are awesome, thank you!
Signature · 2 lines
Deet-doot-dot, I am a bot. https://korg.docs.kernel.org/patchwork/pwbot.html