syzbot |
sign-in | mailing list | source | docs |
| ID | Workflow | Result | Correct | Bug | Created | Started | Finished | Revision | Error |
|---|---|---|---|---|---|---|---|---|---|
| 3be354da-bc29-4298-ae02-642fc700f114 | moderation | Actionable: ✅ Confident: ✅ | ❓ | KASAN: slab-use-after-free Read in uart_write (3) | 2026/02/25 00:59 | 2026/02/25 00:59 | 2026/02/25 01:12 | 305c0ec5cd886e2d13738e28e1b2df9b0ec20fc9 |
The bug report is consistent and actionable. It describes a slab-use-after-free on a `struct tty_struct` object.
### Analysis of the report:
1. **Object Type**: The allocation and free stacks clearly identify the object as a `struct tty_struct`. The allocation happens in `alloc_tty_struct` (called via `tty_open`), and the free happens via `queue_release_one_tty` (called via `tty_release` when the last reference to the tty is dropped).
2. **Access**: The crash occurs in `uart_write+0x9fd/0xb20` at `drivers/tty/serial/serial_core.c:612`.
* Line 612 is: `struct uart_state *state = tty->driver_data;`.
* The access is a read of size 8 at offset 1528 (0x5f8) from the start of the object.
* The layout of `struct tty_struct` confirms that `driver_data` is indeed at offset 1528.
3. **Context**: The access stack shows that the tty is being accessed by the `aoe_tx0` task, which is sending a packet through a CAIF serial network device (`caif_xmit` -> `handle_tx` -> `uart_write`).
4. **Root Cause**: The CAIF serial line discipline (`drivers/net/caif/caif_serial.c`) manages a `net_device` that uses the tty to transmit data. When the line discipline is closed (`ldisc_close`), it drops its reference to the tty using `tty_kref_put(ser->tty)`. However, it schedules the unregistration of the network device asynchronously in `ser_release_work`. There is a race window where the network device is still registered and can attempt to transmit data (via `caif_xmit`) after the tty's reference count has dropped to zero and the tty has been freed, but before the network device is unregistered.
### Conclusion:
The report is self-consistent. The three stack traces (access, allocation, and free) all point to the same `tty_struct` object and describe a logical race condition between the TTY layer's release process and the CAIF serial driver's network device lifecycle.
The report is **actionable**. A fix would involve ensuring that the `tty_struct` reference is held until the network device is fully unregistered and no further transmissions can occur.================================================================== BUG: KASAN: slab-use-after-free in uart_write+0x9fd/0xb20 drivers/tty/serial/serial_core.c:612 Read of size 8 at addr ffff888056df35f8 by task aoe_tx0/1422 CPU: 0 UID: 0 PID: 1422 Comm: aoe_tx0 Not tainted syzkaller #0 PREEMPT(full) Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 Call Trace: <TASK> __dump_stack lib/dump_stack.c:94 [inline] dump_stack_lvl+0x100/0x190 lib/dump_stack.c:120 print_address_description mm/kasan/report.c:378 [inline] print_report+0x156/0x4c9 mm/kasan/report.c:482 kasan_report+0xdf/0x1a0 mm/kasan/report.c:595 uart_write+0x9fd/0xb20 drivers/tty/serial/serial_core.c:612 handle_tx+0x1ff/0x620 drivers/net/caif/caif_serial.c:222 __netdev_start_xmit include/linux/netdevice.h:5273 [inline] netdev_start_xmit include/linux/netdevice.h:5282 [inline] xmit_one net/core/dev.c:3866 [inline] dev_hard_start_xmit+0x127/0x6c0 net/core/dev.c:3882 __dev_queue_xmit+0x6dd/0x46f0 net/core/dev.c:4832 dev_queue_xmit include/linux/netdevice.h:3381 [inline] tx+0xc4/0x130 drivers/block/aoe/aoenet.c:62 kthread+0x1d8/0x3c0 drivers/block/aoe/aoecmd.c:1241 kthread+0x3b3/0x730 kernel/kthread.c:463 ret_from_fork+0x754/0xaf0 arch/x86/kernel/process.c:158 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:246 </TASK> Allocated by task 6127: kasan_save_stack+0x30/0x50 mm/kasan/common.c:57 kasan_save_track+0x14/0x30 mm/kasan/common.c:78 poison_kmalloc_redzone mm/kasan/common.c:398 [inline] __kasan_kmalloc+0xaa/0xb0 mm/kasan/common.c:415 kmalloc_noprof include/linux/slab.h:957 [inline] kzalloc_noprof include/linux/slab.h:1094 [inline] alloc_tty_struct+0x96/0x8c0 drivers/tty/tty_io.c:3102 tty_init_dev.part.0+0x20/0x470 drivers/tty/tty_io.c:1400 tty_init_dev include/linux/err.h:78 [inline] tty_open_by_driver drivers/tty/tty_io.c:2073 [inline] tty_open+0xa63/0xfa0 drivers/tty/tty_io.c:2120 chrdev_open+0x234/0x6a0 fs/char_dev.c:414 do_dentry_open+0x73e/0x1570 fs/open.c:962 vfs_open+0x82/0x3f0 fs/open.c:1094 do_open fs/namei.c:4637 [inline] path_openat+0x21dc/0x3120 fs/namei.c:4796 do_filp_open+0x1f7/0x420 fs/namei.c:4823 do_sys_openat2+0x12e/0x220 fs/open.c:1430 do_sys_open fs/open.c:1436 [inline] __do_sys_openat fs/open.c:1452 [inline] __se_sys_openat fs/open.c:1447 [inline] __x64_sys_openat+0x12d/0x210 fs/open.c:1447 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0xc9/0xf80 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f Freed by task 53: kasan_save_stack+0x30/0x50 mm/kasan/common.c:57 kasan_save_track+0x14/0x30 mm/kasan/common.c:78 kasan_save_free_info+0x3b/0x70 mm/kasan/generic.c:584 poison_slab_object mm/kasan/common.c:253 [inline] __kasan_slab_free+0x5f/0x80 mm/kasan/common.c:285 kasan_slab_free include/linux/kasan.h:235 [inline] slab_free_hook mm/slub.c:2540 [inline] slab_free mm/slub.c:6674 [inline] kfree+0x1c7/0x690 mm/slub.c:6882 process_one_work+0x9c2/0x1840 kernel/workqueue.c:3257 process_scheduled_works kernel/workqueue.c:3340 [inline] worker_thread+0x5da/0xe40 kernel/workqueue.c:3421 kthread+0x3b3/0x730 kernel/kthread.c:463 ret_from_fork+0x754/0xaf0 arch/x86/kernel/process.c:158 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:246 Last potentially related work creation: kasan_save_stack+0x30/0x50 mm/kasan/common.c:57 kasan_record_aux_stack+0xa7/0xc0 mm/kasan/generic.c:556 insert_work+0x36/0x230 kernel/workqueue.c:2180 __queue_work+0x96f/0x10f0 kernel/workqueue.c:2335 queue_work_on+0x180/0x1e0 kernel/workqueue.c:2386 kref_put include/linux/kref.h:65 [inline] tty_kref_put drivers/tty/tty_io.c:1557 [inline] tty_kref_put drivers/tty/tty_io.c:1554 [inline] release_tty+0x4f3/0x5f0 drivers/tty/tty_io.c:1593 tty_release_struct+0xb7/0xe0 drivers/tty/tty_io.c:1692 tty_release+0xd7a/0x1300 drivers/tty/tty_io.c:1852 __fput+0x3ff/0xb40 fs/file_table.c:468 task_work_run+0x150/0x240 kernel/task_work.c:233 exit_task_work include/linux/task_work.h:40 [inline] do_exit+0x829/0x2a30 kernel/exit.c:971 do_group_exit+0xd5/0x2a0 kernel/exit.c:1112 get_signal+0x1ec7/0x21e0 kernel/signal.c:3034 arch_do_signal_or_restart+0x91/0x7a0 arch/x86/kernel/signal.c:337 __exit_to_user_mode_loop kernel/entry/common.c:41 [inline] exit_to_user_mode_loop+0x86/0x4b0 kernel/entry/common.c:75 __exit_to_user_mode_prepare include/linux/irq-entry-common.h:226 [inline] syscall_exit_to_user_mode_prepare include/linux/irq-entry-common.h:256 [inline] syscall_exit_to_user_mode_work include/linux/entry-common.h:159 [inline] syscall_exit_to_user_mode include/linux/entry-common.h:194 [inline] do_syscall_64+0x4fe/0xf80 arch/x86/entry/syscall_64.c:100 entry_SYSCALL_64_after_hwframe+0x77/0x7f The buggy address belongs to the object at ffff888056df3000 which belongs to the cache kmalloc-cg-2k of size 2048 The buggy address is located 1528 bytes inside of freed 2048-byte region [ffff888056df3000, ffff888056df3800) The buggy address belongs to the physical page: page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x56df0 head: order:3 mapcount:0 entire_mapcount:0 nr_pages_mapped:0 pincount:0 memcg:ffff88805609e981 anon flags: 0xfff00000000040(head|node=0|zone=1|lastcpupid=0x7ff) page_type: f5(slab) raw: 00fff00000000040 ffff88801b84c140 0000000000000000 dead000000000001 raw: 0000000000000000 0000000000080008 00000000f5000000 ffff88805609e981 head: 00fff00000000040 ffff88801b84c140 0000000000000000 dead000000000001 head: 0000000000000000 0000000000080008 00000000f5000000 ffff88805609e981 head: 00fff00000000003 ffffea00015b7c01 00000000ffffffff 00000000ffffffff head: ffffffffffffffff 0000000000000000 00000000ffffffff 0000000000000008 page dumped because: kasan: bad access detected page_owner tracks the page as allocated page last allocated via order 3, migratetype Unmovable, gfp_mask 0xd20c0(__GFP_IO|__GFP_FS|__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP|__GFP_NOMEMALLOC), pid 6032, tgid 6031 (syz.2.6), ts 65779837095, free_ts 0 set_page_owner include/linux/page_owner.h:32 [inline] post_alloc_hook+0x1e1/0x250 mm/page_alloc.c:1884 prep_new_page mm/page_alloc.c:1892 [inline] get_page_from_freelist+0xe3d/0x2e10 mm/page_alloc.c:3945 __alloc_frozen_pages_noprof+0x26c/0x2410 mm/page_alloc.c:5240 alloc_pages_mpol+0x1fb/0x550 mm/mempolicy.c:2486 alloc_slab_page mm/slub.c:3075 [inline] allocate_slab mm/slub.c:3248 [inline] new_slab+0x2c4/0x440 mm/slub.c:3302 ___slab_alloc+0xda3/0x1ca0 mm/slub.c:4656 __slab_alloc.isra.0+0x63/0x110 mm/slub.c:4779 __slab_alloc_node mm/slub.c:4855 [inline] slab_alloc_node mm/slub.c:5251 [inline] __do_kmalloc_node mm/slub.c:5656 [inline] __kmalloc_node_track_caller_noprof+0x629/0x9d0 mm/slub.c:5768 kmemdup_noprof+0x29/0x60 mm/util.c:138 kmemdup_noprof include/linux/fortify-string.h:765 [inline] __devinet_sysctl_register+0xbc/0x360 net/ipv4/devinet.c:2665 devinet_sysctl_register net/ipv4/devinet.c:2717 [inline] devinet_sysctl_register+0x17b/0x210 net/ipv4/devinet.c:2707 inetdev_init+0x2b8/0x570 net/ipv4/devinet.c:291 inetdev_event+0x7fa/0x17f0 net/ipv4/devinet.c:1590 notifier_call_chain+0x99/0x3b0 kernel/notifier.c:85 call_netdevice_notifiers_info+0xbe/0x110 net/core/dev.c:2243 call_netdevice_notifiers_extack net/core/dev.c:2281 [inline] call_netdevice_notifiers net/core/dev.c:2295 [inline] register_netdevice+0x16b9/0x21d0 net/core/dev.c:11444 page_owner free stack trace missing Memory state around the buggy address: ffff888056df3480: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb ffff888056df3500: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb >ffff888056df3580: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb ^ ffff888056df3600: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb ffff888056df3680: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb ==================================================================
| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/02/25 00:59 | flow | moderation |
12mResults:map[Actionable:true Confident:true Explanation:The bug report is consistent and actionable. It describes a slab-use-after-free on a `struct tty_struct` object.
### Analysis of the report:
1. **Object Type**: The allocation and free stacks clearly identify the object as a `struct tty_struct`. The allocation happens in `alloc_tty_struct` (called via `tty_open`), and the free happens via `queue_release_one_tty` (called via `tty_release` when the last reference to the tty is dropped).
2. **Access**: The crash occurs in `uart_write+0x9fd/0xb20` at `drivers/tty/serial/serial_core.c:612`.
* Line 612 is: `struct uart_state *state = tty->driver_data;`.
* The access is a read of size 8 at offset 1528 (0x5f8) from the start of the object.
* The layout of `struct tty_struct` confirms that `driver_data` is indeed at offset 1528.
3. **Context**: The access stack shows that the tty is being accessed by the `aoe_tx0` task, which is sending a packet through a CAIF serial network device (`caif_xmit` -> `handle_tx` -> `uart_write`).
4. **Root Cause**: The CAIF serial line discipline (`drivers/net/caif/caif_serial.c`) manages a `net_device` that uses the tty to transmit data. When the line discipline is closed (`ldisc_close`), it drops its reference to the tty using `tty_kref_put(ser->tty)`. However, it schedules the unregistration of the network device asynchronously in `ser_release_work`. There is a race window where the network device is still registered and can attempt to transmit data (via `caif_xmit`) after the tty's reference count has dropped to zero and the tty has been freed, but before the network device is unregistered.
### Conclusion:
The report is self-consistent. The three stack traces (access, allocation, and free) all point to the same `tty_struct` object and describe a logical race condition between the TTY layer's release process and the CAIF serial driver's network device lifecycle.
The report is **actionable**. A fix would involve ensuring that the `tty_struct` reference is held until the network device is fully unregistered and no further transmissions can occur.] |
| 1/1 | 2026/02/25 00:59 | action | kernel-checkouter |
0mResults:map[KernelSrc:/usr/local/google/home/dvyukov/syzkaller/agent/workdir/cache/src/442656ac8be766ac08d507e9d55d7e172d7ca2ec] |
| 2/1 | 2026/02/25 00:59 | action | kernel-builder |
6mResults:map[KernelObj:/usr/local/google/home/dvyukov/syzkaller/agent/workdir/cache/build/571ee61fe28a6420639fa80c13a2c03759e164fb] |
| 3/1 | 2026/02/25 01:06 | action | codesearch-prepare |
4mResults:map[Index:codesearch-index] |
| 4/1 | 2026/02/25 01:10 | agent | expert |
2mModel:gemini-3-flash-preview Results: map[Actionable:true Confident:true] Instruction: You are an experienced Linux kernel developer tasked with determining if the given kernel bug report is actionable or not. Actionable means that it contains enough info to root cause the underlying bug, and that the report is self-consistent and makes sense, rather than e.g. a one-off nonsensical crash induced by a previous memory corruption. The bug report is about a use-after-free bug generated by KASAN tool. It should contain 3 stack traces: the bad memory access stack, the heap block allocation stack, and the heap block free stack. If the report does not contain 3 stacks, it's not actionable. All 3 stack traces should be related to the same object type, and usually be in the same kernel subsystem (at least leaf stack frames). An example of an actionable and consistent report would be: first access stack relates to an access to a field of struct Foo, allocation/free stacks relate to allocation/free of the struct Foo. In inconsistent/nonsensical reports an access may be to a struct Foo, but allocation stack allocates a different structure in a different subsystem. Look for other suspicious signals/inconsistencies that can make this report hard to debug/understand. In the final reply explain why you think the report is self-consistent and actionable, or why it's inconsistent and/or not actionable. Use the provided tools to confirm any assumptions, variables/fields being accessed, etc. In particular, don't make assumptions about the kernel source code, use codesearch tools to read the actual source code. Prefer calling several tools at the same time to save round-trips. Use set-results tool to provide results of the analysis. It must be called exactly once before the final reply. Ignore results of this tool. Prompt:
The bug report is:
==================================================================
BUG: KASAN: slab-use-after-free in uart_write+0x9fd/0xb20 drivers/tty/serial/serial_core.c:612
Read of size 8 at addr ffff888056df35f8 by task aoe_tx0/1422
CPU: 0 UID: 0 PID: 1422 Comm: aoe_tx0 Not tainted syzkaller #0 PREEMPT(full)
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
Call Trace:
<TASK>
__dump_stack lib/dump_stack.c:94 [inline]
dump_stack_lvl+0x100/0x190 lib/dump_stack.c:120
print_address_description mm/kasan/report.c:378 [inline]
print_report+0x156/0x4c9 mm/kasan/report.c:482
kasan_report+0xdf/0x1a0 mm/kasan/report.c:595
uart_write+0x9fd/0xb20 drivers/tty/serial/serial_core.c:612
handle_tx+0x1ff/0x620 drivers/net/caif/caif_serial.c:222
__netdev_start_xmit include/linux/netdevice.h:5273 [inline]
netdev_start_xmit include/linux/netdevice.h:5282 [inline]
xmit_one net/core/dev.c:3866 [inline]
dev_hard_start_xmit+0x127/0x6c0 net/core/dev.c:3882
__dev_queue_xmit+0x6dd/0x46f0 net/core/dev.c:4832
dev_queue_xmit include/linux/netdevice.h:3381 [inline]
tx+0xc4/0x130 drivers/block/aoe/aoenet.c:62
kthread+0x1d8/0x3c0 drivers/block/aoe/aoecmd.c:1241
kthread+0x3b3/0x730 kernel/kthread.c:463
ret_from_fork+0x754/0xaf0 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:246
</TASK>
Allocated by task 6127:
kasan_save_stack+0x30/0x50 mm/kasan/common.c:57
kasan_save_track+0x14/0x30 mm/kasan/common.c:78
poison_kmalloc_redzone mm/kasan/common.c:398 [inline]
__kasan_kmalloc+0xaa/0xb0 mm/kasan/common.c:415
kmalloc_noprof include/linux/slab.h:957 [inline]
kzalloc_noprof include/linux/slab.h:1094 [inline]
alloc_tty_struct+0x96/0x8c0 drivers/tty/tty_io.c:3102
tty_init_dev.part.0+0x20/0x470 drivers/tty/tty_io.c:1400
tty_init_dev include/linux/err.h:78 [inline]
tty_open_by_driver drivers/tty/tty_io.c:2073 [inline]
tty_open+0xa63/0xfa0 drivers/tty/tty_io.c:2120
chrdev_open+0x234/0x6a0 fs/char_dev.c:414
do_dentry_open+0x73e/0x1570 fs/open.c:962
vfs_open+0x82/0x3f0 fs/open.c:1094
do_open fs/namei.c:4637 [inline]
path_openat+0x21dc/0x3120 fs/namei.c:4796
do_filp_open+0x1f7/0x420 fs/namei.c:4823
do_sys_openat2+0x12e/0x220 fs/open.c:1430
do_sys_open fs/open.c:1436 [inline]
__do_sys_openat fs/open.c:1452 [inline]
__se_sys_openat fs/open.c:1447 [inline]
__x64_sys_openat+0x12d/0x210 fs/open.c:1447
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0xc9/0xf80 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
Freed by task 53:
kasan_save_stack+0x30/0x50 mm/kasan/common.c:57
kasan_save_track+0x14/0x30 mm/kasan/common.c:78
kasan_save_free_info+0x3b/0x70 mm/kasan/generic.c:584
poison_slab_object mm/kasan/common.c:253 [inline]
__kasan_slab_free+0x5f/0x80 mm/kasan/common.c:285
kasan_slab_free include/linux/kasan.h:235 [inline]
slab_free_hook mm/slub.c:2540 [inline]
slab_free mm/slub.c:6674 [inline]
kfree+0x1c7/0x690 mm/slub.c:6882
process_one_work+0x9c2/0x1840 kernel/workqueue.c:3257
process_scheduled_works kernel/workqueue.c:3340 [inline]
worker_thread+0x5da/0xe40 kernel/workqueue.c:3421
kthread+0x3b3/0x730 kernel/kthread.c:463
ret_from_fork+0x754/0xaf0 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:246
Last potentially related work creation:
kasan_save_stack+0x30/0x50 mm/kasan/common.c:57
kasan_record_aux_stack+0xa7/0xc0 mm/kasan/generic.c:556
insert_work+0x36/0x230 kernel/workqueue.c:2180
__queue_work+0x96f/0x10f0 kernel/workqueue.c:2335
queue_work_on+0x180/0x1e0 kernel/workqueue.c:2386
kref_put include/linux/kref.h:65 [inline]
tty_kref_put drivers/tty/tty_io.c:1557 [inline]
tty_kref_put drivers/tty/tty_io.c:1554 [inline]
release_tty+0x4f3/0x5f0 drivers/tty/tty_io.c:1593
tty_release_struct+0xb7/0xe0 drivers/tty/tty_io.c:1692
tty_release+0xd7a/0x1300 drivers/tty/tty_io.c:1852
__fput+0x3ff/0xb40 fs/file_table.c:468
task_work_run+0x150/0x240 kernel/task_work.c:233
exit_task_work include/linux/task_work.h:40 [inline]
do_exit+0x829/0x2a30 kernel/exit.c:971
do_group_exit+0xd5/0x2a0 kernel/exit.c:1112
get_signal+0x1ec7/0x21e0 kernel/signal.c:3034
arch_do_signal_or_restart+0x91/0x7a0 arch/x86/kernel/signal.c:337
__exit_to_user_mode_loop kernel/entry/common.c:41 [inline]
exit_to_user_mode_loop+0x86/0x4b0 kernel/entry/common.c:75
__exit_to_user_mode_prepare include/linux/irq-entry-common.h:226 [inline]
syscall_exit_to_user_mode_prepare include/linux/irq-entry-common.h:256 [inline]
syscall_exit_to_user_mode_work include/linux/entry-common.h:159 [inline]
syscall_exit_to_user_mode include/linux/entry-common.h:194 [inline]
do_syscall_64+0x4fe/0xf80 arch/x86/entry/syscall_64.c:100
entry_SYSCALL_64_after_hwframe+0x77/0x7f
The buggy address belongs to the object at ffff888056df3000
which belongs to the cache kmalloc-cg-2k of size 2048
The buggy address is located 1528 bytes inside of
freed 2048-byte region [ffff888056df3000, ffff888056df3800)
The buggy address belongs to the physical page:
page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x56df0
head: order:3 mapcount:0 entire_mapcount:0 nr_pages_mapped:0 pincount:0
memcg:ffff88805609e981
anon flags: 0xfff00000000040(head|node=0|zone=1|lastcpupid=0x7ff)
page_type: f5(slab)
raw: 00fff00000000040 ffff88801b84c140 0000000000000000 dead000000000001
raw: 0000000000000000 0000000000080008 00000000f5000000 ffff88805609e981
head: 00fff00000000040 ffff88801b84c140 0000000000000000 dead000000000001
head: 0000000000000000 0000000000080008 00000000f5000000 ffff88805609e981
head: 00fff00000000003 ffffea00015b7c01 00000000ffffffff 00000000ffffffff
head: ffffffffffffffff 0000000000000000 00000000ffffffff 0000000000000008
page dumped because: kasan: bad access detected
page_owner tracks the page as allocated
page last allocated via order 3, migratetype Unmovable, gfp_mask 0xd20c0(__GFP_IO|__GFP_FS|__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP|__GFP_NOMEMALLOC), pid 6032, tgid 6031 (syz.2.6), ts 65779837095, free_ts 0
set_page_owner include/linux/page_owner.h:32 [inline]
post_alloc_hook+0x1e1/0x250 mm/page_alloc.c:1884
prep_new_page mm/page_alloc.c:1892 [inline]
get_page_from_freelist+0xe3d/0x2e10 mm/page_alloc.c:3945
__alloc_frozen_pages_noprof+0x26c/0x2410 mm/page_alloc.c:5240
alloc_pages_mpol+0x1fb/0x550 mm/mempolicy.c:2486
alloc_slab_page mm/slub.c:3075 [inline]
allocate_slab mm/slub.c:3248 [inline]
new_slab+0x2c4/0x440 mm/slub.c:3302
___slab_alloc+0xda3/0x1ca0 mm/slub.c:4656
__slab_alloc.isra.0+0x63/0x110 mm/slub.c:4779
__slab_alloc_node mm/slub.c:4855 [inline]
slab_alloc_node mm/slub.c:5251 [inline]
__do_kmalloc_node mm/slub.c:5656 [inline]
__kmalloc_node_track_caller_noprof+0x629/0x9d0 mm/slub.c:5768
kmemdup_noprof+0x29/0x60 mm/util.c:138
kmemdup_noprof include/linux/fortify-string.h:765 [inline]
__devinet_sysctl_register+0xbc/0x360 net/ipv4/devinet.c:2665
devinet_sysctl_register net/ipv4/devinet.c:2717 [inline]
devinet_sysctl_register+0x17b/0x210 net/ipv4/devinet.c:2707
inetdev_init+0x2b8/0x570 net/ipv4/devinet.c:291
inetdev_event+0x7fa/0x17f0 net/ipv4/devinet.c:1590
notifier_call_chain+0x99/0x3b0 kernel/notifier.c:85
call_netdevice_notifiers_info+0xbe/0x110 net/core/dev.c:2243
call_netdevice_notifiers_extack net/core/dev.c:2281 [inline]
call_netdevice_notifiers net/core/dev.c:2295 [inline]
register_netdevice+0x16b9/0x21d0 net/core/dev.c:11444
page_owner free stack trace missing
Memory state around the buggy address:
ffff888056df3480: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
ffff888056df3500: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
>ffff888056df3580: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
^
ffff888056df3600: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
ffff888056df3680: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
==================================================================
Reply: The bug report is consistent and actionable. It describes a slab-use-after-free on a `struct tty_struct` object.
### Analysis of the report:
1. **Object Type**: The allocation and free stacks clearly identify the object as a `struct tty_struct`. The allocation happens in `alloc_tty_struct` (called via `tty_open`), and the free happens via `queue_release_one_tty` (called via `tty_release` when the last reference to the tty is dropped).
2. **Access**: The crash occurs in `uart_write+0x9fd/0xb20` at `drivers/tty/serial/serial_core.c:612`.
* Line 612 is: `struct uart_state *state = tty->driver_data;`.
* The access is a read of size 8 at offset 1528 (0x5f8) from the start of the object.
* The layout of `struct tty_struct` confirms that `driver_data` is indeed at offset 1528.
3. **Context**: The access stack shows that the tty is being accessed by the `aoe_tx0` task, which is sending a packet through a CAIF serial network device (`caif_xmit` -> `handle_tx` -> `uart_write`).
4. **Root Cause**: The CAIF serial line discipline (`drivers/net/caif/caif_serial.c`) manages a `net_device` that uses the tty to transmit data. When the line discipline is closed (`ldisc_close`), it drops its reference to the tty using `tty_kref_put(ser->tty)`. However, it schedules the unregistration of the network device asynchronously in `ser_release_work`. There is a race window where the network device is still registered and can attempt to transmit data (via `caif_xmit`) after the tty's reference count has dropped to zero and the tty has been freed, but before the network device is unregistered.
### Conclusion:
The report is self-consistent. The three stack traces (access, allocation, and free) all point to the same `tty_struct` object and describe a logical race condition between the TTY layer's release process and the CAIF serial driver's network device lifecycle.
The report is **actionable**. A fix would involve ensuring that the `tty_struct` reference is held until the network device is fully unregistered and no further transmissions can occur. |
| 5/2 | 2026/02/25 01:10 | llm | expert |
0mModel:gemini-3-flash-preview Tokens: input: 7041 output: 103 thoughts: 865 Thoughts: **Analyzing a KASAN Slab-Use-After-Free Bug** Okay, I need to dive into this KASAN report, a slab-use-after-free, specifically. The access is a read of size 8 at `ffff888056df35f8` within `uart_write` in `serial_core.c:612`. The offending task is `aoe_tx0/1422`. The object itself is `ffff888056df3000`, a 2048-byte allocation from the `kmalloc-cg-2k` slab. Right, I see the allocation stack leading up to this: `alloc_tty_struct`, `tty_init_dev`, and then `tty_open`. This makes me think we are dealing with tty structures. And the free stack, which is also helpful, goes through `kfree`, then the workqueue and the related calls through `tty_kref_put`, `release_tty`, `tty_release_struct`, to the `tty_release`. Clearly, this `tty_struct` is being allocated and released. Now, my next step is to pinpoint the object, and as indicated by both the allocation and free stacks, this *has* to be a `struct tty_struct`. `alloc_tty_struct` allocates it, and `tty_release` leads to its deallocation. That seems definitive, and that's good information. Now for the access itself. The crash occurs inside `uart_write` at line 612 of `serial_core.c`. I have to confirm this and examine that specific line of code. To understand the context, the call stack shows `uart_write` being called from `handle_tx` within `caif_serial`, which in turn is called from within the `dev_hard_start_xmit` pathway, coming from `tx` function within `aoe`. So, it appears that ATA over Ethernet (aoe) is sending a packet which is then processed through the `caif_serial` driver, and eventually, the `uart_write` is called. Now, for the critical verification. The buggy address is `ffff888056df35f8`, and the object's base is `ffff888056df3000`. So, the offset is `0x5f8` (1528 bytes). That's key. I need to understand what's at this offset within the `struct tty_struct`. I'll need to check the size and layout of this structure. Time to dive into the code. |
| 6/2 | 2026/02/25 01:10 | tool | codesearch-dir-index |
0mArgs:map[Dir:drivers/tty/serial/] Results: map[Files:[21285.c altera_jtaguart.c altera_uart.c amba-pl010.c amba-pl011.c apbuart.c apbuart.h ar933x_uart.c arc_uart.c atmel_serial.c atmel_serial.h bcm63xx_uart.c clps711x.c cpm_uart.c cpm_uart.h digicolor-usart.c dz.c dz.h earlycon-riscv-sbi.c earlycon-semihost.c earlycon.c esp32_acm.c esp32_uart.c fsl_linflexuart.c fsl_lpuart.c icom.c imx.c imx_earlycon.c ip22zilog.c ip22zilog.h kgdboc.c lantiq.c liteuart.c lpc32xx_hs.c ma35d1_serial.c max3100.c max310x.c mcf.c men_z135_uart.c meson_uart.c milbeaut_usio.c mpc52xx_uart.c mps2-uart.c msm_serial.c mux.c mvebu-uart.c mxs-auart.c omap-serial.c owl-uart.c pch_uart.c pic32_uart.c pmac_zilog.c pmac_zilog.h pxa.c qcom_geni_serial.c rda-uart.c rp2.c rsci.c rsci.h sa1100.c samsung_tty.c sb1250-duart.c sc16is7xx.c sc16is7xx.h sc16is7xx_i2c.c sc16is7xx_spi.c sccnxp.c serial-tegra.c serial_base.h serial_base_bus.c serial_core.c serial_ctrl.c serial_mctrl_gpio.c serial_mctrl_gpio.h serial_port.c serial_txx9.c sh-sci-common.h sh-sci.c sifive.c sprd_serial.c st-asc.c stm32-usart.c stm32-usart.h suncore.c sunhv.c sunplus-uart.c sunsab.c sunsab.h sunsu.c sunzilog.c sunzilog.h tegra-tcu.c tegra-utc.c timbuart.c timbuart.h uartlite.c ucc_uart.c vt8500_serial.c xilinx_uartps.c zs.c zs.h] Subdirs:[8250 jsm]] |
| 7/2 | 2026/02/25 01:10 | tool | codesearch-definition-source |
0mArgs:map[ContextFile:drivers/tty/serial/serial_core.c IncludeLines:true Name:uart_write] Results: map[SourceCode: 610: static ssize_t uart_write(struct tty_struct *tty, const u8 *buf, size_t count)
611: {
612: struct uart_state *state = tty->driver_data;
613: struct uart_port *port;
614: unsigned long flags;
615: int ret = 0;
616:
617: /*
618: * This means you called this function _after_ the port was
619: * closed. No cookie for you.
620: */
621: if (WARN_ON(!state))
622: return -EL3HLT;
623:
624: port = uart_port_ref_lock(state, &flags);
625: if (!state->port.xmit_buf) {
626: uart_port_unlock_deref(port, flags);
627: return 0;
628: }
629:
630: if (port)
631: ret = kfifo_in(&state->port.xmit_fifo, buf, count);
632:
633: __uart_start(state);
634: uart_port_unlock_deref(port, flags);
635: return ret;
636: }
SourceFile:drivers/tty/serial/serial_core.c] |
| 8/2 | 2026/02/25 01:10 | tool | codesearch-struct-layout |
0mArgs:map[ContextFile:drivers/tty/tty_io.c Name:tty_struct] Results: map[Fields:[map[Name:kref OffsetBits:0 SizeBits:32] map[Name:index OffsetBits:32 SizeBits:32] map[Name:dev OffsetBits:64 SizeBits:64] map[Name:driver OffsetBits:128 SizeBits:64] map[Name:port OffsetBits:192 SizeBits:64] map[Name:ops OffsetBits:256 SizeBits:64] map[Name:ldisc OffsetBits:320 SizeBits:64] map[Name:ldisc_sem OffsetBits:384 SizeBits:1216] map[Name:atomic_write_lock OffsetBits:1600 SizeBits:1152] map[Name:legacy_mutex OffsetBits:2752 SizeBits:1152] map[Name:throttle_mutex OffsetBits:3904 SizeBits:1152] map[Name:termios_rwsem OffsetBits:5056 SizeBits:1216] map[Name:winsize_mutex OffsetBits:6272 SizeBits:1152] map[Name:termios OffsetBits:7424 SizeBits:352] map[Name:termios_locked OffsetBits:7776 SizeBits:352] map[Name:name OffsetBits:8128 SizeBits:512] map[Name:flags OffsetBits:8640 SizeBits:64] map[Name:count OffsetBits:8704 SizeBits:32] map[Name:receive_room OffsetBits:8736 SizeBits:32] map[Name:winsize OffsetBits:8768 SizeBits:64] map[Name:flow OffsetBits:8832 SizeBits:576] map[Name:ctrl OffsetBits:9408 SizeBits:704] map[Name:hw_stopped OffsetBits:10112 SizeBits:8] map[Name:closing OffsetBits:10120 SizeBits:8] map[Name:flow_change OffsetBits:10144 SizeBits:32] map[Name:link OffsetBits:10176 SizeBits:64] map[Name:fasync OffsetBits:10240 SizeBits:64] map[Name:write_wait OffsetBits:10304 SizeBits:640] map[Name:read_wait OffsetBits:10944 SizeBits:640] map[Name:hangup_work OffsetBits:11584 SizeBits:576] map[Name:disc_data OffsetBits:12160 SizeBits:64] map[Name:driver_data OffsetBits:12224 SizeBits:64] map[Name:files_lock OffsetBits:12288 SizeBits:512] map[Name:write_cnt OffsetBits:12800 SizeBits:32] map[Name:write_buf OffsetBits:12864 SizeBits:64] map[Name:tty_files OffsetBits:12928 SizeBits:128] map[Name:SAK_work OffsetBits:13056 SizeBits:576]]] |
| 9/2 | 2026/02/25 01:10 | llm | expert |
0mModel:gemini-3-flash-preview Tokens: input: 10793 output: 85 thoughts: 421 Thoughts: **KASAN Report Analysis: A Freed tty_struct** Okay, so the KASAN report is screaming a use-after-free. It says there's a read of size 8 at address `ffff888056df35f8` by task `aoe_tx0/1422`, and the function involved is `uart_write` at line 612 in `serial_core.c`. `uart_write` looks like it's taking a `tty` struct as its first argument and it looks like a read is happening at offset 0x5f8. Let's quickly check the structure layout with my trusty `codesearch-struct-layout`. Yup, offset 1528 (0x5f8) from the `tty_struct` start matches the offset of the `driver_data` field exactly. 12224 bits / 8 bits per byte = 1528 bytes. The sizes match. So, the `tty` pointer passed to `uart_write` is definitely pointing at a freed or corrupted `tty_struct`. That's the problem. Now, let's understand the allocation and deallocation paths. The standard allocation of `tty_struct` appears to be through `alloc_tty_struct`, called from `tty_init_dev`, which itself is called by `tty_open`. That's how things are *supposed* to work. Now, the KASAN report shows it was freed via `kfree` called from `process_one_work`. Digging through the "Last potentially related work creation" entry, it seems that there's a free stack that looks like `tty_kref_put` -> `release_tty` -> `tty_release_struct` -> `tty_release` -> `__fput`. Ah, `tty_release` is usually called when the last file descriptor is closed. So `release_tty` likely queues some work and calls `tty_kref_put` to free the tty later. I need to dig into `release_tty` in `tty_io.c` to see how a `tty` might be used after it's deallocated, and what could be missing from the reference counting. That's the next step to fixing this. |
| 10/2 | 2026/02/25 01:10 | tool | codesearch-definition-source |
0mArgs:map[ContextFile:drivers/tty/tty_io.c IncludeLines:true Name:release_tty] Results: map[SourceCode:1561: /**
1562: * release_tty - release tty structure memory
1563: * @tty: tty device release
1564: * @idx: index of the tty device release
1565: *
1566: * Release both @tty and a possible linked partner (think pty pair),
1567: * and decrement the refcount of the backing module.
1568: *
1569: * Locking:
1570: * tty_mutex
1571: * takes the file list lock internally when working on the list of ttys
1572: * that the driver keeps.
1573: */
1574: static void release_tty(struct tty_struct *tty, int idx)
1575: {
1576: /* This should always be true but check for the moment */
1577: WARN_ON(tty->index != idx);
1578: WARN_ON(!mutex_is_locked(&tty_mutex));
1579: if (tty->ops->shutdown)
1580: tty->ops->shutdown(tty);
1581: tty_save_termios(tty);
1582: tty_driver_remove_tty(tty->driver, tty);
1583: if (tty->port)
1584: tty->port->itty = NULL;
1585: if (tty->link)
1586: tty->link->port->itty = NULL;
1587: if (tty->port)
1588: tty_buffer_cancel_work(tty->port);
1589: if (tty->link)
1590: tty_buffer_cancel_work(tty->link->port);
1591:
1592: tty_kref_put(tty->link);
1593: tty_kref_put(tty);
1594: }
SourceFile:drivers/tty/tty_io.c] |
| 11/2 | 2026/02/25 01:10 | tool | codesearch-definition-source |
0mArgs:map[ContextFile:drivers/tty/tty_io.c IncludeLines:true Name:tty_kref_put] Results: map[SourceCode:1547: /**
1548: * tty_kref_put - release a tty kref
1549: * @tty: tty device
1550: *
1551: * Release a reference to the @tty device and if need be let the kref layer
1552: * destruct the object for us.
1553: */
1554: void tty_kref_put(struct tty_struct *tty)
1555: {
1556: if (tty)
1557: kref_put(&tty->kref, queue_release_one_tty);
1558: }
SourceFile:drivers/tty/tty_io.c] |
| 12/2 | 2026/02/25 01:10 | llm | expert |
0mModel:gemini-3-flash-preview Tokens: input: 12551 output: 77 thoughts: 149 Thoughts: **Analyzing TTY Release in the CAIF Serial Driver** Okay, so `tty_kref_put` is ultimately calling `kref_put`, and the release function in this scenario is `queue_release_one_tty`. My intuition is that this is queueing up a delayed work item to actually free the tty structure – standard practice for managing resources in the kernel, especially when dealing with asynchronous operations like device release. Now, I need to understand the path to this release. I've got the access trace: `aoe_tx0` task -> `tx` -> `dev_queue_xmit` -> `dev_hard_start_xmit` -> `handle_tx` -> `uart_write`. This looks like a typical network transmit path, working its way down to the UART driver level. The crucial point is `handle_tx`, which, according to the trace, resides in `drivers/net/caif/caif_serial.c`. Now I need to see exactly how `caif_serial` is interacting with the tty structure. Is it taking a direct reference? Or, perhaps, using the tty within a larger CAIF framework? Identifying the reference acquisition and usage patterns here will be key to understanding the potential for race conditions or memory leaks that this kref management is trying to prevent. My next step is a deep dive into the code around `handle_tx` to see how that tty pointer is being used. |
| 13/2 | 2026/02/25 01:10 | tool | codesearch-definition-source |
0mArgs:map[ContextFile:drivers/net/caif/caif_serial.c IncludeLines:true Name:handle_tx] Results: map[SourceCode: 194: static int handle_tx(struct ser_device *ser)
195: {
196: struct tty_struct *tty;
197: struct sk_buff *skb;
198: int tty_wr, len, room;
199:
200: tty = ser->tty;
201: ser->tx_started = true;
202:
203: /* Enter critical section */
204: if (test_and_set_bit(CAIF_SENDING, &ser->state))
205: return 0;
206:
207: /* skb_peek is safe because handle_tx is called after skb_queue_tail */
208: while ((skb = skb_peek(&ser->head)) != NULL) {
209:
210: /* Make sure you don't write too much */
211: len = skb->len;
212: room = tty_write_room(tty);
213: if (!room)
214: break;
215: if (room > ser_write_chunk)
216: room = ser_write_chunk;
217: if (len > room)
218: len = room;
219:
220: /* Write to tty or loopback */
221: if (!ser_loop) {
222: tty_wr = tty->ops->write(tty, skb->data, len);
223: update_tty_status(ser);
224: } else {
225: tty_wr = len;
226: ldisc_receive(tty, skb->data, NULL, len);
227: }
228: ser->dev->stats.tx_packets++;
229: ser->dev->stats.tx_bytes += tty_wr;
230:
231: /* Error on TTY ?! */
232: if (tty_wr < 0)
233: goto error;
234: /* Reduce buffer written, and discard if empty */
235: skb_pull(skb, tty_wr);
236: if (skb->len == 0) {
237: struct sk_buff *tmp = skb_dequeue(&ser->head);
238: WARN_ON(tmp != skb);
239: dev_consume_skb_any(skb);
240: }
241: }
242: /* Send flow off if queue is empty */
243: if (ser->head.qlen <= SEND_QUEUE_LOW &&
244: test_and_clear_bit(CAIF_FLOW_OFF_SENT, &ser->state) &&
245: ser->common.flowctrl != NULL)
246: ser->common.flowctrl(ser->dev, ON);
247: clear_bit(CAIF_SENDING, &ser->state);
248: return 0;
249: error:
250: clear_bit(CAIF_SENDING, &ser->state);
251: return tty_wr;
252: }
SourceFile:drivers/net/caif/caif_serial.c] |
| 14/2 | 2026/02/25 01:10 | tool | codesearch-file-index |
0mArgs:map[SourceFile:drivers/net/caif/caif_serial.c] Results: map[Entities:[map[Kind:function Name:__check_ser_loop] map[Kind:function Name:__check_ser_use_fcs] map[Kind:function Name:__check_ser_use_stx] map[Kind:function Name:__check_ser_write_chunk] map[Kind:function Name:caif_net_close] map[Kind:function Name:caif_net_open] map[Kind:function Name:caif_ser_exit] map[Kind:function Name:caif_ser_init] map[Kind:function Name:caif_xmit] map[Kind:function Name:caifdev_setup] map[Kind:function Name:debugfs_deinit] map[Kind:function Name:debugfs_init] map[Kind:function Name:debugfs_rx] map[Kind:function Name:handle_tx] map[Kind:function Name:ldisc_close] map[Kind:function Name:ldisc_open] map[Kind:function Name:ldisc_receive] map[Kind:function Name:ldisc_tx_wakeup] map[Kind:function Name:ser_release] map[Kind:function Name:update_tty_status] map[Kind:struct Name:ser_device] map[Kind:global_variable Name:__UNIQUE_ID_addressable_caif_ser_init_1404] map[Kind:global_variable Name:__UNIQUE_ID_modinfo_1381] map[Kind:global_variable Name:__UNIQUE_ID_modinfo_1382] map[Kind:global_variable Name:__UNIQUE_ID_modinfo_1383] map[Kind:global_variable Name:__UNIQUE_ID_modinfo_1384] map[Kind:global_variable Name:__UNIQUE_ID_modinfo_1385] map[Kind:global_variable Name:__UNIQUE_ID_modinfo_1386] map[Kind:global_variable Name:__UNIQUE_ID_modinfo_1387] map[Kind:global_variable Name:__UNIQUE_ID_modinfo_1388] map[Kind:global_variable Name:__UNIQUE_ID_modinfo_1389] map[Kind:global_variable Name:__UNIQUE_ID_modinfo_1390] map[Kind:global_variable Name:__UNIQUE_ID_modinfo_1391] map[Kind:global_variable Name:__UNIQUE_ID_modinfo_1392] map[Kind:global_variable Name:__UNIQUE_ID_modinfo_1393] map[Kind:global_variable Name:__UNIQUE_ID_modinfo_1394] map[Kind:global_variable Name:__UNIQUE_ID_modinfo_1395] map[Kind:global_variable Name:__exitcall_caif_ser_exit] map[Kind:global_variable Name:__param_ser_loop] map[Kind:global_variable Name:__param_ser_use_fcs] map[Kind:global_variable Name:__param_ser_use_stx] map[Kind:global_variable Name:__param_ser_write_chunk] map[Kind:global_variable Name:__param_str_ser_loop] map[Kind:global_variable Name:__param_str_ser_use_fcs] map[Kind:global_variable Name:__param_str_ser_use_stx] map[Kind:global_variable Name:__param_str_ser_write_chunk] map[Kind:global_variable Name:caif_ldisc] map[Kind:global_variable Name:netdev_ops] map[Kind:global_variable Name:ser_list] map[Kind:global_variable Name:ser_lock] map[Kind:global_variable Name:ser_release_list] map[Kind:global_variable Name:ser_release_work] map[Kind:global_variable Name:ser_use_fcs] map[Kind:global_variable Name:ser_use_stx] map[Kind:global_variable Name:ser_write_chunk]]] |
| 15/2 | 2026/02/25 01:10 | llm | expert |
0mModel:gemini-3-flash-preview Tokens: input: 15140 output: 41 thoughts: 716 Thoughts: **Analyzing a Potential Use-After-Free in Serial Port Transmission** Okay, so I'm looking at this KASAN report related to a crash within `uart_write`, specifically line 612, which is trying to access `tty->driver_data`. The code in question is within `handle_tx` where we have the line `tty_wr = tty->ops->write(tty, skb->data, len);`. My initial thought was, if `tty` is freed, then accessing `tty->ops->write` directly should cause a crash, right? `tty->ops` becomes garbage. But then, the KASAN report pointed to a location *inside* `uart_write`, which suggests that `tty->ops->write` was at least partially valid. My first thought was that perhaps `tty->ops` was still pointing to the old, valid `uart_ops` if the memory hasn't been overwritten yet. If `uart_ops` is in a constant data section, KASAN might not have complained at *that* point. However, `tty->driver_data` *is* actually within the `tty_struct` itself. The discrepancy here is bothering me. `tty->ops` is offset 256 from the beginning of the `tty_struct` while `tty->driver_data` is offset 1528. A `tty` struct that's been freed would result in both of these accesses causing KASAN errors, right? Then it hit me. Accessing a function pointer is a multi-step process. First we're going to read `tty->ops` (UAF). Then, we'll read `tty->ops->write` (this *wouldn't* be a UAF because `tty->ops` still points to `uart_ops`, which is global). Finally, `uart_write` would be called. Inside `uart_write` is where `tty->driver_data` is accessed (and that *is* a UAF). So, the crash is consistent with that access sequence! KASAN *should* have caught the read of `tty->ops` at line 222 of `caif_serial.c`, but it didn't. This is interesting. Let me go back and re-examine the `handle_tx` function, specifically that line again: `tty_wr = tty->ops->write(tty, skb->data, len);`. Ah, wait... before the call to `tty->ops->write`, `tty_write_room(tty)` is called at line 212. Let's dig into `tty_write_room`. |
| 16/2 | 2026/02/25 01:10 | tool | codesearch-definition-source |
0mArgs:map[ContextFile:include/linux/tty.h IncludeLines:true Name:tty_write_room] Results: map[SourceCode: 56: /**
57: * tty_write_room - write queue space
58: * @tty: terminal
59: *
60: * Returns: the number of bytes that can be queued to this device at the present
61: * time. The result should be treated as a guarantee and the driver cannot
62: * offer a value it later shrinks by more than the number of bytes written. If
63: * no method is provided, 2K is always returned and data may be lost as there
64: * will be no flow control.
65: */
66: unsigned int tty_write_room(struct tty_struct *tty)
67: {
68: if (tty->ops->write_room)
69: return tty->ops->write_room(tty);
70: return 2048;
71: }
SourceFile:drivers/tty/tty_ioctl.c] |
| 17/2 | 2026/02/25 01:10 | llm | expert |
0mModel:gemini-3-flash-preview Tokens: input: 16745 output: 92 thoughts: 1015 Thoughts: **Analyzing a KASAN UAF Report and Tracing TTY Pointer Management in caif_serial** Okay, so `tty_write_room` is accessing `tty->ops`. I'm trying to understand why KASAN is reporting a Use-After-Free (UAF) on `tty->ops`. Initially, I thought maybe it wasn't poisoned yet because it's at the beginning of the struct, but then I remember the whole object is freed, 2048 bytes and marked as freed (`fb`). Wait, let me re-examine the KASAN report. It says the buggy address is 1528 bytes into a freed 2048-byte region. The region starts at `ffff888056df3000`. `tty->ops` is at offset 32. Okay, so `tty->ops` is at address `ffff888056df3020`. The memory state dump shows that the entire region is filled with `fb` (freed bytes). But there's a pointer `^` after `ffff888056df3580`... hmm, each line shows 16 bytes. `ffff888056df3580 + 15` would be `ffff888056df358f`. Hold on, the actual buggy address is `ffff888056df35f8`. It's actually in the line starting at `ffff888056df35f0`. Wait, that's wrong. The `>` points to `ffff888056df3580`. Okay, so `ffff888056df35f8` is 0x78 bytes after the start of that line. Looking again at the memory state, `ffff888056df3580` is correct and it is filled with fb. The `^` is under the last `fb` in that line, which is at `ffff888056df358f`. Aha! `ffff888056df35f8` is just *before* `ffff888056df3600`. It means it is in the line ending at `ffff888056df35ff`. So it's the last 8 bytes of that block. Yes, that makes sense. So, the point remains: the `tty` object is freed. Now I need to figure out *how* `caif_serial` is managing that `tty` pointer. It's a line discipline, so `ldisc_open` and `ldisc_close` likely are the responsible functions. `ldisc_open` should set `ser->tty = tty`. I need to confirm that `ldisc_close` properly clears that pointer to prevent a UAF. Time to check `drivers/net/caif/caif_serial.c` and verify how `ldisc_open` and `ldisc_close` are implemented. |
| 18/2 | 2026/02/25 01:10 | tool | codesearch-definition-source |
0mArgs:map[ContextFile:drivers/net/caif/caif_serial.c IncludeLines:true Name:ldisc_open] Results: map[SourceCode: 305: static int ldisc_open(struct tty_struct *tty)
306: {
307: struct ser_device *ser;
308: struct net_device *dev;
309: char name[64];
310: int result;
311:
312: /* No write no play */
313: if (tty->ops->write == NULL)
314: return -EOPNOTSUPP;
315: if (!capable(CAP_SYS_ADMIN) && !capable(CAP_SYS_TTY_CONFIG))
316: return -EPERM;
317:
318: /* release devices to avoid name collision */
319: ser_release(NULL);
320:
321: result = snprintf(name, sizeof(name), "cf%s", tty->name);
322: if (result >= IFNAMSIZ)
323: return -EINVAL;
324: dev = alloc_netdev(sizeof(*ser), name, NET_NAME_UNKNOWN,
325: caifdev_setup);
326: if (!dev)
327: return -ENOMEM;
328:
329: ser = netdev_priv(dev);
330: ser->tty = tty_kref_get(tty);
331: ser->dev = dev;
332: debugfs_init(ser, tty);
333: tty->receive_room = 4096;
334: tty->disc_data = ser;
335: set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
336: rtnl_lock();
337: result = register_netdevice(dev);
338: if (result) {
339: tty_kref_put(tty);
340: rtnl_unlock();
341: free_netdev(dev);
342: return -ENODEV;
343: }
344:
345: spin_lock(&ser_lock);
346: list_add(&ser->node, &ser_list);
347: spin_unlock(&ser_lock);
348: rtnl_unlock();
349: netif_stop_queue(dev);
350: update_tty_status(ser);
351: return 0;
352: }
SourceFile:drivers/net/caif/caif_serial.c] |
| 19/2 | 2026/02/25 01:10 | tool | codesearch-definition-source |
0mArgs:map[ContextFile:drivers/net/caif/caif_serial.c IncludeLines:true Name:ldisc_close] Results: map[SourceCode: 354: static void ldisc_close(struct tty_struct *tty)
355: {
356: struct ser_device *ser = tty->disc_data;
357:
358: tty_kref_put(ser->tty);
359:
360: spin_lock(&ser_lock);
361: list_move(&ser->node, &ser_release_list);
362: spin_unlock(&ser_lock);
363: schedule_work(&ser_release_work);
364: }
SourceFile:drivers/net/caif/caif_serial.c] |
| 20/2 | 2026/02/25 01:10 | llm | expert |
0mModel:gemini-3-flash-preview Tokens: input: 19454 output: 93 thoughts: 228 Thoughts: **Analyzing Potential Reference Leak in Serial Line Discipline** Okay, so I'm looking at this serial line discipline code and immediately see a potential issue with reference counting. In `ldisc_open`, the `tty_kref_get(tty)` call is taking a reference to the `tty` structure, which is standard practice. Good. Now, in the `ldisc_close` function, we've got `tty_kref_put(ser->tty)`. It *should* be releasing the reference, which is great. However, here's the kicker: `ser->tty` isn't set to NULL in `ldisc_close` after the `put`. That feels wrong. What's more, `ldisc_close` schedules `ser_release_work`. This means `ldisc_close` could be called, and then the netdevice could *still* be active, potentially sending packets before the `ser_release_work` actually runs and cleans things up. Now, I'm tracing back a packet transmission. The stack trace leads me to `caif_xmit` through `tx` -> `dev_queue_xmit` -> `dev_hard_start_xmit` -> `handle_tx`. Since this is a serial port, `caif_serial`'s `netdev_ops` must be handling the actual transmission, so there ought to be an `ndo_start_xmit` defined. Time to check the `netdev_ops` implementation in `caif_serial.c` and make sure my instincts are correct. |
| 21/2 | 2026/02/25 01:10 | tool | codesearch-definition-source |
0mArgs:map[ContextFile:drivers/net/caif/caif_serial.c IncludeLines:true Name:netdev_ops] Results: map[SourceCode: 377: static const struct net_device_ops netdev_ops = {
378: .ndo_open = caif_net_open,
379: .ndo_stop = caif_net_close,
380: .ndo_start_xmit = caif_xmit
381: };
SourceFile:drivers/net/caif/caif_serial.c] |
| 22/2 | 2026/02/25 01:10 | tool | codesearch-definition-source |
0mArgs:map[ContextFile:drivers/net/caif/caif_serial.c IncludeLines:true Name:caif_xmit] Results: map[SourceCode: 254: static netdev_tx_t caif_xmit(struct sk_buff *skb, struct net_device *dev)
255: {
256: struct ser_device *ser;
257:
258: ser = netdev_priv(dev);
259:
260: /* Send flow off once, on high water mark */
261: if (ser->head.qlen > SEND_QUEUE_HIGH &&
262: !test_and_set_bit(CAIF_FLOW_OFF_SENT, &ser->state) &&
263: ser->common.flowctrl != NULL)
264:
265: ser->common.flowctrl(ser->dev, OFF);
266:
267: skb_queue_tail(&ser->head, skb);
268: return handle_tx(ser);
269: }
SourceFile:drivers/net/caif/caif_serial.c] |
| 23/2 | 2026/02/25 01:10 | llm | expert |
0mModel:gemini-3-flash-preview Tokens: input: 20522 output: 64 thoughts: 142 Thoughts: **My Analysis of the Serial Driver's Shutdown Sequence** Okay, let's break down this serial driver's cleanup process. It starts with a transmission request, initiated by `caif_xmit`, which in turn calls `handle_tx`. `handle_tx` seems to rely on `ser->tty`. That's the core serial port structure. Now, here's where it gets interesting: When the line discipline is closed via `ldisc_close`, the code calls `tty_kref_put(ser->tty)`. This is a reference counting mechanism for the `tty_struct`. If this `put` operation decrements the counter to zero, the `tty_struct` itself gets freed! That’s important to remember. However, a key point of concern emerges: even if the tty is freed, the associated network device might still be registered, and its `ndo_start_xmit` function could potentially still be invoked. That's a textbook race condition if we're not careful. I see, `ldisc_close` doesn't immediately unregister the network device; instead, it schedules `ser_release_work`. This must be the key to safely detaching the serial port. Let's dig into `ser_release_work` and eventually `ser_release` and see what they actually do. Those functions are the key to understanding how the driver manages to release the serial port. |
| 24/2 | 2026/02/25 01:11 | tool | codesearch-definition-source |
0mArgs:map[ContextFile:drivers/net/caif/caif_serial.c IncludeLines:true Name:ser_release] Results: map[SourceCode: 283: static void ser_release(struct work_struct *work)
284: {
285: struct list_head list;
286: struct ser_device *ser, *tmp;
287:
288: spin_lock(&ser_lock);
289: list_replace_init(&ser_release_list, &list);
290: spin_unlock(&ser_lock);
291:
292: if (!list_empty(&list)) {
293: rtnl_lock();
294: list_for_each_entry_safe(ser, tmp, &list, node) {
295: dev_close(ser->dev);
296: unregister_netdevice(ser->dev);
297: debugfs_deinit(ser);
298: }
299: rtnl_unlock();
300: }
301: }
SourceFile:drivers/net/caif/caif_serial.c] |
| 25/2 | 2026/02/25 01:11 | tool | grepper |
0mArgs:map[Expression:ser_release_work] Results: map[Output:drivers/infiniband/ulp/iser/iscsi_iser.c=873=static void iscsi_iser_ep_disconnect(struct iscsi_endpoint *ep)
--
drivers/infiniband/ulp/iser/iscsi_iser.c-888- if (iser_conn->iscsi_conn) {
drivers/infiniband/ulp/iser/iscsi_iser.c:889: INIT_WORK(&iser_conn->release_work, iser_release_work);
drivers/infiniband/ulp/iser/iscsi_iser.c-890- queue_work(release_wq, &iser_conn->release_work);
--
drivers/infiniband/ulp/iser/iscsi_iser.h=492=int iser_conn_terminate(struct iser_conn *iser_conn);
drivers/infiniband/ulp/iser/iscsi_iser.h-493-
drivers/infiniband/ulp/iser/iscsi_iser.h:494:void iser_release_work(struct work_struct *work);
drivers/infiniband/ulp/iser/iscsi_iser.h-495-
--
drivers/infiniband/ulp/iser/iser_verbs.c=330=static void iser_device_try_release(struct iser_device *device)
--
drivers/infiniband/ulp/iser/iser_verbs.c-342-
drivers/infiniband/ulp/iser/iser_verbs.c:343:void iser_release_work(struct work_struct *work)
drivers/infiniband/ulp/iser/iser_verbs.c-344-{
--
drivers/net/caif/caif_serial.c=283=static void ser_release(struct work_struct *work)
--
drivers/net/caif/caif_serial.c-302-
drivers/net/caif/caif_serial.c:303:static DECLARE_WORK(ser_release_work, ser_release);
drivers/net/caif/caif_serial.c-304-
--
drivers/net/caif/caif_serial.c=354=static void ldisc_close(struct tty_struct *tty)
--
drivers/net/caif/caif_serial.c-362- spin_unlock(&ser_lock);
drivers/net/caif/caif_serial.c:363: schedule_work(&ser_release_work);
drivers/net/caif/caif_serial.c-364-}
--
drivers/net/caif/caif_serial.c=427=static void __exit caif_ser_exit(void)
--
drivers/net/caif/caif_serial.c-432- ser_release(NULL);
drivers/net/caif/caif_serial.c:433: cancel_work_sync(&ser_release_work);
drivers/net/caif/caif_serial.c-434- tty_unregister_ldisc(&caif_ldisc);
] |
| 26/2 | 2026/02/25 01:11 | llm | expert |
0mModel:gemini-3-flash-preview Tokens: input: 22119 output: 41 thoughts: 316 Thoughts: **My Analysis of the Dangling Pointer Issue** Okay, so here's what I'm seeing. It looks like we have a race condition leading to a use-after-free issue, and the KASAN report appears to be confirming my suspicions. The crux of the problem seems to be the lifecycle of the `tty_struct` associated with the serial device. Here's how I'm piecing it together: 1. `ldisc_close` is the initial culprit. This is where things start to unravel. It calls `tty_kref_put` on the `ser->tty` pointer. If that was the *last* reference to the `tty_struct`, then the `tty_struct` will be either freed immediately, or queued for later freeing. 2. `ldisc_close` then schedules `ser_release_work`. This is where the device unregistration happens. 3. The race is this: Before `ser_release_work` actually runs and calls `unregister_netdevice`, another task – perhaps `aoe_tx0` as an example – tries to transmit data and calls `caif_xmit` on the netdevice. 4. `caif_xmit` then calls `handle_tx`. 5. *Here's the problem*: `handle_tx` is trying to use `ser->tty`. But the critical issue is that `ser->tty` is now a dangling pointer. The `tty_struct` it was pointing to has been freed (or is about to be freed) because of the `tty_kref_put` call. The KASAN report just confirms this: the allocation stack shows `tty_open` -> `alloc_tty_struct`, so we know the struct was allocated to begin with. Then the free stack is `tty_release` -> `release_tty` -> `tty_kref_put` -> `queue_release_one_tty` -> `process_one_work` -> `kfree`. The trace seems to be right. `tty_release` is called, which happens when the tty is closed. And yes, when a tty is closed, its line discipline also gets closed, and `tty_release` in turn calls `tty_ldisc_release`, which then calls `tty_ldisc_close`. It looks like it's a standard pattern with ttys, but we need to verify where exactly it is freed. To nail down the specifics and confirm my theory, I'm going to take a closer look at the `tty_release` function in `drivers/tty/tty_io.c` to understand the exact sequence of events during a tty's closure. This should confirm my understanding. |
| 27/2 | 2026/02/25 01:11 | tool | codesearch-definition-source |
0mArgs:map[ContextFile:drivers/tty/tty_io.c IncludeLines:true Name:tty_release] Results: map[SourceCode:1697: /**
1698: * tty_release - vfs callback for close
1699: * @inode: inode of tty
1700: * @filp: file pointer for handle to tty
1701: *
1702: * Called the last time each file handle is closed that references this tty.
1703: * There may however be several such references.
1704: *
1705: * Locking:
1706: * Takes BKL. See tty_release_dev().
1707: *
1708: * Even releasing the tty structures is a tricky business. We have to be very
1709: * careful that the structures are all released at the same time, as interrupts
1710: * might otherwise get the wrong pointers.
1711: *
1712: * WSH 09/09/97: rewritten to avoid some nasty race conditions that could
1713: * lead to double frees or releasing memory still in use.
1714: */
1715: int tty_release(struct inode *inode, struct file *filp)
1716: {
1717: struct tty_struct *tty = file_tty(filp);
1718: struct tty_struct *o_tty = NULL;
1719: int do_sleep, final;
1720: int idx;
1721: long timeout = 0;
1722: int once = 1;
1723:
1724: if (tty_paranoia_check(tty, inode, __func__))
1725: return 0;
1726:
1727: tty_lock(tty);
1728: check_tty_count(tty, __func__);
1729:
1730: __tty_fasync(-1, filp, 0);
1731:
1732: idx = tty->index;
1733: if (tty->driver->type == TTY_DRIVER_TYPE_PTY &&
1734: tty->driver->subtype == PTY_TYPE_MASTER)
1735: o_tty = tty->link;
1736:
1737: if (tty_release_checks(tty, idx)) {
1738: tty_unlock(tty);
1739: return 0;
1740: }
1741:
1742: tty_debug_hangup(tty, "releasing (count=%d)\n", tty->count);
1743:
1744: if (tty->ops->close)
1745: tty->ops->close(tty, filp);
1746:
1747: /* If tty is pty master, lock the slave pty (stable lock order) */
1748: tty_lock_slave(o_tty);
1749:
1750: /*
1751: * Sanity check: if tty->count is going to zero, there shouldn't be
1752: * any waiters on tty->read_wait or tty->write_wait. We test the
1753: * wait queues and kick everyone out _before_ actually starting to
1754: * close. This ensures that we won't block while releasing the tty
1755: * structure.
1756: *
1757: * The test for the o_tty closing is necessary, since the master and
1758: * slave sides may close in any order. If the slave side closes out
1759: * first, its count will be one, since the master side holds an open.
1760: * Thus this test wouldn't be triggered at the time the slave closed,
1761: * so we do it now.
1762: */
1763: while (1) {
1764: do_sleep = 0;
1765:
1766: if (tty->count <= 1) {
1767: if (waitqueue_active(&tty->read_wait)) {
1768: wake_up_poll(&tty->read_wait, EPOLLIN);
1769: do_sleep++;
1770: }
1771: if (waitqueue_active(&tty->write_wait)) {
1772: wake_up_poll(&tty->write_wait, EPOLLOUT);
1773: do_sleep++;
1774: }
1775: }
1776: if (o_tty && o_tty->count <= 1) {
1777: if (waitqueue_active(&o_tty->read_wait)) {
1778: wake_up_poll(&o_tty->read_wait, EPOLLIN);
1779: do_sleep++;
1780: }
1781: if (waitqueue_active(&o_tty->write_wait)) {
1782: wake_up_poll(&o_tty->write_wait, EPOLLOUT);
1783: do_sleep++;
1784: }
1785: }
1786: if (!do_sleep)
1787: break;
1788:
1789: if (once) {
1790: once = 0;
1791: tty_warn(tty, "read/write wait queue active!\n");
1792: }
1793: schedule_timeout_killable(timeout);
1794: if (timeout < 120 * HZ)
1795: timeout = 2 * timeout + 1;
1796: else
1797: timeout = MAX_SCHEDULE_TIMEOUT;
1798: }
1799:
1800: if (o_tty) {
1801: if (--o_tty->count < 0) {
1802: tty_warn(tty, "bad slave count (%d)\n", o_tty->count);
1803: o_tty->count = 0;
1804: }
1805: }
1806: if (--tty->count < 0) {
1807: tty_warn(tty, "bad tty->count (%d)\n", tty->count);
1808: tty->count = 0;
1809: }
1810:
1811: /*
1812: * We've decremented tty->count, so we need to remove this file
1813: * descriptor off the tty->tty_files list; this serves two
1814: * purposes:
1815: * - check_tty_count sees the correct number of file descriptors
1816: * associated with this tty.
1817: * - do_tty_hangup no longer sees this file descriptor as
1818: * something that needs to be handled for hangups.
1819: */
1820: tty_del_file(filp);
1821:
1822: /*
1823: * Perform some housekeeping before deciding whether to return.
1824: *
1825: * If _either_ side is closing, make sure there aren't any
1826: * processes that still think tty or o_tty is their controlling
1827: * tty.
1828: */
1829: if (!tty->count) {
1830: read_lock(&tasklist_lock);
1831: session_clear_tty(tty->ctrl.session);
1832: if (o_tty)
1833: session_clear_tty(o_tty->ctrl.session);
1834: read_unlock(&tasklist_lock);
1835: }
1836:
1837: /* check whether both sides are closing ... */
1838: final = !tty->count && !(o_tty && o_tty->count);
1839:
1840: tty_unlock_slave(o_tty);
1841: tty_unlock(tty);
1842:
1843: /* At this point, the tty->count == 0 should ensure a dead tty
1844: * cannot be re-opened by a racing opener.
1845: */
1846:
1847: if (!final)
1848: return 0;
1849:
1850: tty_debug_hangup(tty, "final close\n");
1851:
1852: tty_release_struct(tty, idx);
1853: return 0;
1854: }
SourceFile:drivers/tty/tty_io.c] |
| 28/2 | 2026/02/25 01:11 | llm | expert |
0mModel:gemini-3-flash-preview Tokens: input: 25444 output: 47 thoughts: 846 Thoughts: **Analyzing the Serial Device Release Sequence**
Okay, let's break this down. `tty_release` kicks off a chain: `tty_release` -> `tty_release_struct` -> `tty_ldisc_release` -> `tty_ldisc_close` -> `ldisc_close`. The critical part here is that `ldisc_close` calls the `caif_serial` ldisc's `close` method, which is `ldisc_close`. In that function, there's a key operation: `tty_kref_put(ser->tty)`. This is where the reference to the `tty` is released.
Following the sequence, after the ldisc releases its reference, we get back to `tty_release_struct` which then calls `release_tty`. That's where the *final* reference to the tty gets released, through `tty_kref_put(tty)`. So if the refcount started at 2 (one for the file, one for the ldisc), after `ldisc_close`, it's down to 1, and `release_tty` takes it to 0, which means the tty is freed.
The bug is that the `caif_serial` driver is not ensuring that all pending packets are handled before freeing the `tty`, or not managing the `tty`'s reference correctly for the duration of the netdevice's lifecycle. Actually, it *does* hold a reference in `ser->tty`, but releases it in `ldisc_close`. I think the reference should be released in `ser_release` *after* `unregister_netdevice`.
Let's double-check `ldisc_close`:
```c
354: static void ldisc_close(struct tty_struct *tty)
355: {
356: struct ser_device *ser = tty->disc_data;
357:
358: tty_kref_put(ser->tty);
359:
360: spin_lock(&ser_lock);
361: list_move(&ser->node, &ser_release_list);
362: spin_unlock(&ser_lock);
363: schedule_work(&ser_release_work);
364: }
```
Yep, line 358 is the culprit. Releasing the kref here seems premature. If we move that `tty_kref_put` to `ser_release`, it should be much safer.
But...wait. What does `ser_release` *actually* do?
```c
283: static void ser_release(struct work_struct *work)
284: {
...
294: list_for_each_entry_safe(ser, tmp, &list, node) {
295: dev_close(ser->dev);
296: unregister_netdevice(ser->dev);
297: debugfs_deinit(ser);
298: }
...
300: }
301: }
```
Hold on... it calls `unregister_netdevice`, but it doesn't *free* `ser`! Where is `ser` freed? Ah, it is `netdev_priv(dev)`. That means it's freed when `free_netdev(dev)` is called. But, `unregister_netdevice` *doesn't* call `free_netdev`. Usually, the netdevice itself is freed in the `destructor` of `net_device` or explicitly *after* `unregister_netdev`.
Hmm... let's see, `caifdev_setup` might set a destructor, right? Let me check `caifdev_setup`.
|
| 29/2 | 2026/02/25 01:11 | tool | codesearch-definition-source |
0mArgs:map[ContextFile:drivers/net/caif/caif_serial.c IncludeLines:true Name:caifdev_setup] Results: map[SourceCode: 383: static void caifdev_setup(struct net_device *dev)
384: {
385: struct ser_device *serdev = netdev_priv(dev);
386:
387: dev->features = 0;
388: dev->netdev_ops = &netdev_ops;
389: dev->type = ARPHRD_CAIF;
390: dev->flags = IFF_POINTOPOINT | IFF_NOARP;
391: dev->mtu = CAIF_MAX_MTU;
392: dev->priv_flags |= IFF_NO_QUEUE;
393: dev->needs_free_netdev = true;
394: skb_queue_head_init(&serdev->head);
395: serdev->common.link_select = CAIF_LINK_LOW_LATENCY;
396: serdev->common.use_frag = true;
397: serdev->common.use_stx = ser_use_stx;
398: serdev->common.use_fcs = ser_use_fcs;
399: serdev->dev = dev;
400: }
SourceFile:drivers/net/caif/caif_serial.c] |
| 30/2 | 2026/02/25 01:11 | llm | expert |
0mModel:gemini-3-flash-preview Tokens: input: 27579 output: 40 thoughts: 2249 Thoughts: **Actionable UAF in caif_serial Driver** Okay, so I've been digging into this KASAN report and I think I've got a handle on the issue. Basically, it looks like there's a Use-After-Free (UAF) vulnerability within the `caif_serial` driver, specifically related to how the TTY structure (`ser->tty`) is handled in relation to the network device's operation. The report is clear; we're accessing freed memory via `uart_write`, and it's all happening during packet transmission through `caif_serial`. The heart of the problem seems to be the timing of `ldisc_close` and the netdevice's continued operation. `free_netdev` is automatically called after the last reference to `net_device` is gone, triggered by `unregister_netdevice`, which is scheduled by `ser_release_work`. We are able to get a hold of the net_device via `caif_serial`'s `ndo_start_xmit` as long as the netdevice is registered and up. When `ldisc_close` is triggered, either through detachment of the line discipline or closing the TTY, the `ser->tty` pointer is released. The race condition stems from the fact that `ldisc_close` drops the reference to `ser->tty` *before* scheduling `ser_release_work`, which calls `unregister_netdevice`. Meanwhile, the network device can still be receiving packets and calling `caif_xmit`, which in turn calls `handle_tx`. `handle_tx` uses the `ser->tty` to handle the transmission of packets. Thus, we have a UAF. I initially thought we might be able to fix this by moving the `tty_kref_put(ser->tty)` call to the end of `ser_release`, but `ser_release` is also called by `ldisc_open`. This would be complicated and wouldn't solve the core issue. Then I realized that the core problem is that `ldisc_close` drops the tty kref before scheduling the work to unregister the netdevice. The KASAN report nailed the stacks, showing the UAF in `uart_write`, which is called through the TTY operations. The allocation and free stacks are all there, and the timing looks perfect. The report is very actionable. `handle_tx` is the key function. It's the one using `ser->tty`. If `ser->tty` was set to NULL in `ldisc_close`, then `handle_tx` might be crashing with a null pointer dereference instead of a UAF. If `caif_xmit` is running on a different CPU (which is likely the case here), it can read `ser->tty` after `ldisc_close` has finished `tty_kref_put`, potentially releasing the `tty` structure and scheduling the free. Then, when `handle_tx` runs, it uses the freed memory. The sequence is: 1. `tty_release` -> `ldisc_close(tty)`. 2. `ldisc_close` calls `tty_kref_put(ser->tty)`. 3. `ldisc_close` schedules `ser_release_work`. 4. `tty_release_struct` calls `release_tty(tty)`. 5. `release_tty` calls `tty_kref_put(tty)`. 6. `queue_release_one_tty` is called, it queues a work to free `tty`. 7. `aoe_tx0` calls `caif_xmit(ser->dev)`. 8. `caif_xmit` calls `handle_tx(ser)`. 9. `handle_tx` reads `tty = ser->tty`. 10. The free work runs and `kfree(tty)` is called. 11. `handle_tx` calls `tty->ops->write(tty, ...)`. UAF. Even though `handle_tx` calls `tty_write_room(tty)` before `tty->ops->write(tty,...)`, this still accesses the freed memory. The KASAN report is very consistent. This is definitely actionable. I need to make sure that the net device is unregistered *before* we drop the reference to the TTY. |
| 31/2 | 2026/02/25 01:11 | tool | codesearch-struct-layout |
0mArgs:map[ContextFile:drivers/net/caif/caif_serial.c Name:ser_device] Results: map[Fields:[map[Name:common OffsetBits:0 SizeBits:192] map[Name:node OffsetBits:192 SizeBits:128] map[Name:dev OffsetBits:320 SizeBits:64] map[Name:head OffsetBits:384 SizeBits:704] map[Name:tty OffsetBits:1088 SizeBits:64] map[Name:tx_started OffsetBits:1152 SizeBits:8] map[Name:state OffsetBits:1216 SizeBits:64] map[Name:debugfs_tty_dir OffsetBits:1280 SizeBits:64] map[Name:tx_blob OffsetBits:1344 SizeBits:128] map[Name:rx_blob OffsetBits:1472 SizeBits:128] map[Name:rx_data OffsetBits:1600 SizeBits:1024] map[Name:tx_data OffsetBits:2624 SizeBits:1024] map[Name:tty_status OffsetBits:3648 SizeBits:8]]] |
| 32/2 | 2026/02/25 01:11 | llm | expert |
0mModel:gemini-3-flash-preview Tokens: input: 31128 output: 19 thoughts: 1070 Thoughts: **UAF Bug in `caif_serial` Line Discipline** Okay, here's the breakdown of what I'm seeing. This is a classic Use-After-Free (UAF) bug, and the report is pretty solid. The root cause is a race condition between the `ldisc_close` function, which handles closing the line discipline, and the `caif_xmit` function, which handles data transmission. Essentially, `ldisc_close` prematurely drops the reference count (using `tty_kref_put`) to a `struct tty_struct` before the associated network device (`ser->dev`) is unregistered. This network device is still registered and actively involved in sending data via `caif_xmit`, which eventually calls `handle_tx`. This `handle_tx` function then, critically, *uses* that tty reference, specifically accessing `tty->ops->write`. Because the reference count has already been decremented, the tty can be freed before `handle_tx` gets a chance to use it. The report specifically mentions the offset of the `tty` field within `ser_device` – 136 bytes – and that the code is accessing `tty->driver_data` at offset 1528 which, with the pointer arithmetic, is indeed `tty->driver_data` from the report. So, the report is technically sound. The report shows `tty = ser->tty;` followed by the problematic `tty->ops->write(tty, ...)` and is a clear indicator that `tty` is the subject of the UAF. The `net_device` unregistration is asynchronous; it's scheduled as a work item (`ser_release_work`) which is triggered after `ldisc_close`. The tty reference is dropped *before* the work is scheduled and before the net device is unregistered, which is where the race happens. Now, looking at the stacks, it's consistent. The access to the freed memory is in `uart_write`, which comes from the serial core, and is called within `handle_tx`. The allocation is from the tty core – `alloc_tty_struct` – and the free occurs from the tty core as well, but triggered from `kfree` in the workqueue by `tty_release`. All three are directly related to `tty_struct`, so the involved subsystems, *tty*, *serial*, and *caif* are all involved and consistent with the observed behavior. Also, the free stack is from `process_one_work` and the "Last potentially related work creation" points to `tty_kref_put` -> `queue_release_one_tty` which fits perfectly with the analysis. And that the process doing the access is `aoe_tx0` seems reasonable, as ATA over Ethernet uses a network device. If that device is the `caif_serial` one, then it makes perfect sense. The proposed fix is to move `tty_kref_put(ser->tty)` to `ser_release` *after* the `unregister_netdevice(ser->dev)` call. This will ensure that the tty's reference count isn't decremented until the netdevice is definitely gone. However, `ser_release` is also called from `ldisc_open` which is important to remember because `ser_release_list` might be empty. It could also contain objects that were added to the list by *previous* calls to `ldisc_close`. Looking at `caif_xmit` and `handle_tx`, they *don't* take any explicit references, so the current problem is indeed the premature kref drop. `dev_close` and `unregister_netdevice` called in `ser_release` are the definitive ways to stop the queue. And, though not ideal, one potential improvement is to call `netif_device_detach` or similar from `ldisc_close`. In short, the key issue is that `caif_serial` drops the kref *before* unregistering the netdevice. This needs to be fixed. I am confident in this analysis. |
| 33/2 | 2026/02/25 01:11 | tool | set-results |
0mArgs:map[Actionable:true Confident:true] Results: map[Actionable:true Confident:true] |
| 34/2 | 2026/02/25 01:11 | llm | expert |
0mModel:gemini-3-flash-preview Tokens: input: 33124 output: 564 thoughts: 302 Thoughts: **Actionable Bug Summary** Okay, here's the rundown of what I've got. I need to make sure this is clear, and, more importantly, why it's actionable. This is a use-after-free issue with a `tty_struct`. Let's break it down: * I've got the allocation stack; it's right there in the `tty_open` call, which is perfectly expected. I can see the `tty_struct` being created when the tty is opened. That checks out. * Then, the free stack shows the `tty_struct` getting freed through a workqueue after a `tty_release` - makes sense, this is the closing path. This is likely how we end up with the free in the first place. * Now, the critical part. The access stack. `uart_write` from the serial core is called from within `handle_tx` inside `caif_serial.c`. That's where it's being *used* after being freed. We have the proof! * Digging deeper, the root cause is with `caif_serial`'s line discipline's `close` method, `ldisc_close`. It calls `tty_kref_put(ser->tty)` - dropping the reference to the `tty_struct`. This happens *before* the associated network device is unregistered. Hmm. * That net device unregistration is happening asynchronously in `ser_release_work`. A workqueue. That explains the timing issue. * So, here's the race: after the reference is dropped but before the network device is unregistered, a packet can be sent. That means `caif_xmit` will be called, which leads to `handle_tx`, which tries to use the now-freed `tty_struct`. BOOM. Use-after-free. * The report is solid; all the stacks are there, the offsets are consistent with the structure. This is all *consistent*. The data looks complete. Therefore, this is actionable because it's a clear race condition with a specific window for the bug to occur, and the impact will manifest as a memory-corruption issue. It should be possible to patch this by ensuring the network device is unregistered *before* the `tty_struct` reference is dropped in `ldisc_close`. |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|