Re: [PATCH net v2 1/1] serial: caif: fix remaining ser->tty UAF in TX path

From: Shuangpeng

Date: Thu Feb 19 2026 - 16:48:11 EST


Hi all,

Thanks for the previous feedback and suggestions.

We explored several mitigations to address the remaining UAF in the
caif_serial TX path, including:
1. Taking a tty reference in handle_tx() using a new
tty_kref_get_unless_zero() helper.
2. Setting and checking ser->state to avoid internal races.

However, our reproducer still triggers a UAF.
Link: https://gist.github.com/shuangpengbai/c898debad6bdf170a84be7e6b3d8707f

After further debugging, the KASAN report consistently points to
the memory access in pty_write_room():

tty_buffer_space_avail(tty->link->port);

Two different tty objects are dereferenced here: 'tty' and 'tty->link'.
In debugging, we found the invalid access is specifically on 'tty->link',
rather than 'tty' itself, based on comparing the runtime addresses.
Therefore, our previous analysis for the race could be inaccurate.

We will continue investigating the root cause and we are happy to
provide any additional information for you to debug this bug.

Below is the report showing the fault location:

==================================================================
BUG: KASAN: slab-use-after-free in pty_write_room (drivers/tty/pty.c:131)
Read of size 8 at addr ffff888116df7018 by task a.out/8414

Call Trace:
<TASK>
dump_stack_lvl (lib/dump_stack.c:122)
print_report (mm/kasan/report.c:379 mm/kasan/report.c:482)
kasan_report (mm/kasan/report.c:597)
pty_write_room (drivers/tty/pty.c:131)
handle_tx (drivers/net/caif/caif_serial.c:213)
dev_hard_start_xmit (./include/linux/netdevice.h:5275
./include/linux/netdevice.h:5284 net/core/dev.c:3871 net/core/dev.c:3887)
__dev_queue_xmit (net/core/dev.c:?)
transmit (net/caif/caif_dev.c:237)
cfserl_transmit (net/caif/cfserl.c:185)
cffrml_transmit (net/caif/cffrml.c:?)
cfmuxl_transmit (net/caif/cfmuxl.c:240)
caif_connect_client (net/caif/cfcnfg.c:355)
caif_connect (net/caif/caif_socket.c:828)
__sys_connect (net/socket.c:2089 net/socket.c:2108)
__x64_sys_connect (net/socket.c:2114 net/socket.c:2111)
do_syscall_64 (arch/x86/entry/syscall_64.c:?)
entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:130)
</TASK>

Freed by task 4526:
kasan_save_track (mm/kasan/common.c:58 mm/kasan/common.c:78)
kasan_save_free_info (mm/kasan/generic.c:587)
__kasan_slab_free (mm/kasan/common.c:287)
kfree (mm/slub.c:6082 mm/slub.c:6399)
process_scheduled_works (kernel/workqueue.c:3280 kernel/workqueue.c:3358)
worker_thread (./include/linux/list.h:381 kernel/workqueue.c:3440)
kthread (kernel/kthread.c:468)
ret_from_fork (arch/x86/kernel/process.c:164)



> On Feb 18, 2026, at 09:25, Vadim Fedorenko <vadim.fedorenko@xxxxxxxxx> wrote:
>
> On 15/02/2026 02:51, Shuangpeng Bai wrote:
>> A reproducer exposes a KASAN use-after-free in caif_serial's TX path
>> (e.g., via tty_write_room() / tty->ops->write()) on top of commit
>> <308e7e4d0a84> ("serial: caif: fix use-after-free in caif_serial
>> ldisc_close()").
>> That commit moved tty_kref_put() to ser_release(). There is still a race
>> because the TX path may fetch ser->tty and use it while ser_release()
>> drops the last tty reference:
>> CPU 0 (ser_release worker) CPU 1 (xmit)
>> ------------------------- ------------
>> caif_xmit()
>> handle_tx()
>> tty = ser->tty
>> ser_release()
>> tty = ser->tty
>> dev_close(ser->dev)
>> unregister_netdevice(ser->dev)
>> debugfs_deinit(ser)
>> tty_kref_put(tty) // may drop the last ref
>> <-- race window -->
>> tty->ops->write(tty, ...) // UAF
>> Fix it by serializing accesses to ser->tty with a dedicated lock. The TX
>> path grabs a tty kref under the lock and drops it after the TX attempt,
>> while ser_release() clears ser->tty under the same lock before putting the
>> old tty reference. This prevents the TX path from observing a freed tty
>> object via ser->tty.
>> With this change applied, the reproducer no longer triggers the UAF in my
>> test.
>> One concern is that handle_tx() can be a hot path. This fix adds a short
>> lock-held section plus an extra tty kref get/put per TX run. Feedback on
>> the performance impact, or suggestions for a lower-overhead approach, are
>> welcome.
>
> I'm not quite sure we actually need spinlock here. It looks like just adding simple helper tty_kref_get_unless_zero() and using it in handle_xt() will solve the problem?
>
>