| 0/0 |
2026/07/29 00:48 |
flow |
moderation |
47m
Results: map[Actionable:true Explanation:An analysis of the KASAN use-after-free bug report reveals that it is **fully
consistent, self-contained, and highly actionable**.
### Root Cause Analysis
The bug is caused by incomplete error-handling cleanup in the serial core port
registration path (`serial_core_register_port`), which leaves dangling pointers
in the global driver state (`state->uart_port`) and the port structure
(`port->port_dev`).
Here is the step-by-step sequence of events that leads to the use-after-free:
1. **Port Registration Attempt:**
When a serial port is registered (e.g., during `serial8250_unregister_port`
re-adding a port via `uart_add_one_port`), `serial_core_register_port()` is
called.
2. **Device Allocation:**
`serial_core_register_port()` calls `serial_core_port_device_add()`, which
allocates a `struct serial_port_device` via `serial_base_port_add()` and
assigns it to `port->port_dev`.
3. **Linking to Driver State:**
`serial_core_register_port()` then calls `serial_core_add_one_port()`, which
links the port to the driver's state:
```c
state->uart_port = uport;
uport->state = state;
```
4. **Fault Injection / Allocation Failure:**
Inside `serial_core_add_one_port()`, a memory allocation fails (specifically
`kzalloc_objs` for `tty_groups` at line 3109, triggered by the `failslab`
fault injection). The function returns `-ENOMEM`.
* **Crucially**, `serial_core_add_one_port()` does **not** clear
`state->uart_port` or `uport->state` on this failure path.
5. **Incomplete Cleanup on Error Path:**
Upon receiving the error, `serial_core_register_port()` jumps to the
`err_unregister_port_dev` error label:
```c
err_unregister_port_dev:
serial_base_port_device_remove(port->port_dev);
```
`serial_base_port_device_remove()` deletes the device and calls
`put_device(&port_dev->dev)`, which triggers `serial_base_port_release()` and
**frees** the `serial_port_device` structure.
* **Crucially**, `serial_core_register_port()` does **not** clear
`port->port_dev` or `state->uart_port` on this failure path. They are left as
dangling pointers pointing to the freed memory.
6. **Subsequent Dereference (Use-After-Free):**
Later, when a port is unregistered (e.g., during driver unbinding/removal),
`serial_core_unregister_port()` is called. It attempts to find if any other
ports are using the controller via `serial_core_ctrl_find()`:
```c
for (i = 0; i < drv->nr; i++) {
state = drv->state + i;
if (!state->uart_port || !state->uart_port->port_dev)
continue;
...
return serial_core_get_ctrl_dev(state->uart_port->port_dev);
}
```
Since `state->uart_port` and `state->uart_port->port_dev` were never cleared
during the previous registration failure, they are both non-NULL. The code
attempts to dereference the dangling `port_dev` pointer in
`serial_core_get_ctrl_dev()`:
```c
static struct serial_ctrl_device *
serial_core_get_ctrl_dev(struct serial_port_device *port_dev)
{
struct device *dev = &port_dev->dev;
return to_serial_base_ctrl_device(dev->parent);
}
```
This reads `dev->parent` (located at offset **64 bytes** inside the freed
`struct serial_port_device` structure), triggering the KASAN
slab-use-after-free read of size 8.
---
### Conclusion
The report is **actionable**. The root cause is fully understood, and the fix
involves ensuring that `state->uart_port`, `port->state`, and `port->port_dev`
are properly set to `NULL` on the error paths of `serial_core_add_one_port()`
and `serial_core_register_port()`.]
|
| 1/1 |
2026/07/29 00:48 |
action |
kernel-checkouter |
0m
Results: map[KernelSrc:/app/workdir/cache/src/c41e592f23836ae254abe21615b950b8294f138e]
|
| 2/1 |
2026/07/29 00:48 |
action |
kernel-builder |
14m
Results: map[KernelObj:/app/workdir/cache/build/41ef09eb338654268308dfd88c84113cb5473b2d]
|
| 3/1 |
2026/07/29 01:02 |
action |
codesearch-prepare |
19m
Results: map[Index:codesearch-index]
|
| 4/1 |
2026/07/29 01:21 |
agent |
expert |
14m
Model:
Results:
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.
Don't make assumptions about the kernel source code (it may be different from what you assume it is).
Extensively use the provided code access tools (codesearch-*, git-*, grepper, etc)
to examine the actual source code, and confirm any assumptions.
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:
FAULT_INJECTION: forcing a failure.
name failslab, interval 1, probability 0, space 0, times 1
CPU: 0 UID: 0 PID: 5333 Comm: syz.0.0 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_lvl+0xe8/0x150 lib/dump_stack.c:120
fail_dump lib/fault-inject.c:73 [inline]
should_fail_ex+0x40c/0x560 lib/fault-inject.c:174
should_failslab+0xa8/0x100 mm/failslab.c:46
slab_pre_alloc_hook mm/slub.c:4539 [inline]
slab_alloc_node mm/slub.c:4897 [inline]
__do_kmalloc_node mm/slub.c:5333 [inline]
__kmalloc_noprof+0xfe/0x720 mm/slub.c:5359
_kmalloc_noprof include/linux/slab.h:992 [inline]
_kzalloc_noprof include/linux/slab.h:1309 [inline]
serial_core_add_one_port drivers/tty/serial/serial_core.c:3109 [inline]
serial_core_register_port+0xe73/0x2840 drivers/tty/serial/serial_core.c:3322
serial8250_unregister_port+0x39c/0x720 drivers/tty/serial/8250/8250_core.c:892
serial8250_remove+0x87/0xc0 drivers/tty/serial/8250/8250_platform.c:241
device_remove drivers/base/dd.c:616 [inline]
__device_release_driver drivers/base/dd.c:1349 [inline]
device_release_driver_internal+0x48b/0x880 drivers/base/dd.c:1372
unbind_store+0x1a1/0x1d0 drivers/base/bus.c:244
kernfs_fop_write_iter+0x3a4/0x540 fs/kernfs/file.c:345
new_sync_write fs/read_write.c:595 [inline]
vfs_write+0x612/0xba0 fs/read_write.c:687
ksys_write+0x150/0x270 fs/read_write.c:739
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7fde8d99de99
Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 e8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007fde8e8aafe8 EFLAGS: 00000246 ORIG_RAX: 0000000000000001
RAX: ffffffffffffffda RBX: 00007fde8dc25fa0 RCX: 00007fde8d99de99
RDX: 000000000000000a RSI: 0000200000000600 RDI: 0000000000000003
RBP: 00007fde8e8ab050 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000002
R13: 00007fde8dc26038 R14: 00007fde8dc25fa0 R15: 00007ffee26ea9b8
</TASK>
==================================================================
BUG: KASAN: slab-use-after-free in serial_core_get_ctrl_dev drivers/tty/serial/serial_core.c:3236 [inline]
BUG: KASAN: slab-use-after-free in serial_core_ctrl_find drivers/tty/serial/serial_core.c:3259 [inline]
BUG: KASAN: slab-use-after-free in serial_core_unregister_port+0x93f/0xb40 drivers/tty/serial/serial_core.c:3358
Read of size 8 at addr ffff88801f578040 by task syz.0.0/5333
CPU: 0 UID: 0 PID: 5333 Comm: syz.0.0 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_lvl+0xe8/0x150 lib/dump_stack.c:120
print_address_description+0x55/0x1e0 mm/kasan/report.c:378
print_report+0x58/0x70 mm/kasan/report.c:482
kasan_report+0x117/0x150 mm/kasan/report.c:595
serial_core_get_ctrl_dev drivers/tty/serial/serial_core.c:3236 [inline]
serial_core_ctrl_find drivers/tty/serial/serial_core.c:3259 [inline]
serial_core_unregister_port+0x93f/0xb40 drivers/tty/serial/serial_core.c:3358
serial8250_unregister_port+0x27d/0x720 drivers/tty/serial/8250/8250_core.c:883
serial8250_remove+0x87/0xc0 drivers/tty/serial/8250/8250_platform.c:241
device_remove drivers/base/dd.c:616 [inline]
__device_release_driver drivers/base/dd.c:1349 [inline]
device_release_driver_internal+0x48b/0x880 drivers/base/dd.c:1372
unbind_store+0x1a1/0x1d0 drivers/base/bus.c:244
kernfs_fop_write_iter+0x3a4/0x540 fs/kernfs/file.c:345
new_sync_write fs/read_write.c:595 [inline]
vfs_write+0x612/0xba0 fs/read_write.c:687
ksys_write+0x150/0x270 fs/read_write.c:739
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7fde8d99de99
Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 e8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007fde8e8aafe8 EFLAGS: 00000246 ORIG_RAX: 0000000000000001
RAX: ffffffffffffffda RBX: 00007fde8dc25fa0 RCX: 00007fde8d99de99
RDX: 000000000000000a RSI: 0000200000000600 RDI: 0000000000000003
RBP: 00007fde8e8ab050 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000002
R13: 00007fde8dc26038 R14: 00007fde8dc25fa0 R15: 00007ffee26ea9b8
</TASK>
Allocated by task 5333:
kasan_save_stack mm/kasan/common.c:57 [inline]
kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
poison_kmalloc_redzone mm/kasan/common.c:398 [inline]
__kasan_kmalloc+0x93/0xb0 mm/kasan/common.c:415
kasan_kmalloc include/linux/kasan.h:263 [inline]
__kmalloc_cache_noprof+0x32d/0x660 mm/slub.c:5489
_kmalloc_noprof include/linux/slab.h:988 [inline]
_kzalloc_noprof include/linux/slab.h:1309 [inline]
serial_base_port_add+0x5b/0x270 drivers/tty/serial/serial_base_bus.c:159
serial_core_port_device_add drivers/tty/serial/serial_core.c:3275 [inline]
serial_core_register_port+0x37f/0x2840 drivers/tty/serial/serial_core.c:3314
serial8250_unregister_port+0x39c/0x720 drivers/tty/serial/8250/8250_core.c:892
serial8250_remove+0x87/0xc0 drivers/tty/serial/8250/8250_platform.c:241
device_remove drivers/base/dd.c:616 [inline]
__device_release_driver drivers/base/dd.c:1349 [inline]
device_release_driver_internal+0x48b/0x880 drivers/base/dd.c:1372
unbind_store+0x1a1/0x1d0 drivers/base/bus.c:244
kernfs_fop_write_iter+0x3a4/0x540 fs/kernfs/file.c:345
new_sync_write fs/read_write.c:595 [inline]
vfs_write+0x612/0xba0 fs/read_write.c:687
ksys_write+0x150/0x270 fs/read_write.c:739
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
Freed by task 5333:
kasan_save_stack mm/kasan/common.c:57 [inline]
kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
kasan_save_free_info+0x40/0x50 mm/kasan/generic.c:584
poison_slab_object mm/kasan/common.c:253 [inline]
__kasan_slab_free+0x5c/0x80 mm/kasan/common.c:285
kasan_slab_free include/linux/kasan.h:235 [inline]
slab_free_hook mm/slub.c:2677 [inline]
slab_free mm/slub.c:6377 [inline]
kfree+0x1c5/0x640 mm/slub.c:6692
device_release+0xc4/0x1f0 drivers/base/core.c:-1
kobject_cleanup lib/kobject.c:689 [inline]
kobject_release lib/kobject.c:720 [inline]
kref_put include/linux/kref.h:65 [inline]
kobject_put+0x222/0x550 lib/kobject.c:737
serial_core_register_port+0x55b/0x2840 drivers/tty/serial/serial_core.c:3329
serial8250_unregister_port+0x39c/0x720 drivers/tty/serial/8250/8250_core.c:892
serial8250_remove+0x87/0xc0 drivers/tty/serial/8250/8250_platform.c:241
device_remove drivers/base/dd.c:616 [inline]
__device_release_driver drivers/base/dd.c:1349 [inline]
device_release_driver_internal+0x48b/0x880 drivers/base/dd.c:1372
unbind_store+0x1a1/0x1d0 drivers/base/bus.c:244
kernfs_fop_write_iter+0x3a4/0x540 fs/kernfs/file.c:345
new_sync_write fs/read_write.c:595 [inline]
vfs_write+0x612/0xba0 fs/read_write.c:687
ksys_write+0x150/0x270 fs/read_write.c:739
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
The buggy address belongs to the object at ffff88801f578000
which belongs to the cache kmalloc-2k of size 2048
The buggy address is located 64 bytes inside of
freed 2048-byte region [ffff88801f578000, ffff88801f578800)
The buggy address belongs to the physical page:
page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x1f578
head: order:3 mapcount:0 entire_mapcount:0 nr_pages_mapped:0 pincount:0
flags: 0xfff00000000040(head|node=0|zone=1|lastcpupid=0x7ff)
page_type: f5(slab)
raw: 00fff00000000040 ffff88801b042000 dead000000000100 dead000000000122
raw: 0000000000000000 0000000800080008 00000000f5000000 0000000000000000
head: 00fff00000000040 ffff88801b042000 dead000000000100 dead000000000122
head: 0000000000000000 0000000800080008 00000000f5000000 0000000000000000
head: 00fff00000000003 fffffffffffffe01 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 12, tgid 12 (kworker/u4:0), ts 100787266514, free_ts 99036531444
set_page_owner include/linux/page_owner.h:32 [inline]
post_alloc_hook+0x1f9/0x250 mm/page_alloc.c:1859
prep_new_page mm/page_alloc.c:1867 [inline]
get_page_from_freelist+0x21fa/0x2270 mm/page_alloc.c:3946
__alloc_frozen_pages_noprof+0x18d/0x380 mm/page_alloc.c:5304
alloc_slab_page mm/slub.c:3266 [inline]
allocate_slab+0x79/0x5e0 mm/slub.c:3380
new_slab mm/slub.c:3426 [inline]
refill_objects+0x2d5/0x350 mm/slub.c:7310
refill_sheaf mm/slub.c:2804 [inline]
__pcs_replace_empty_main+0x2bf/0x6b0 mm/slub.c:4675
alloc_from_pcs mm/slub.c:4773 [inline]
slab_alloc_node mm/slub.c:4905 [inline]
__do_kmalloc_node mm/slub.c:5333 [inline]
__kmalloc_node_track_caller_noprof+0x557/0x730 mm/slub.c:5471
kmalloc_reserve net/core/skbuff.c:637 [inline]
__alloc_skb+0x2bd/0x7a0 net/core/skbuff.c:715
alloc_skb include/linux/skbuff.h:1384 [inline]
mld_newpack+0x165/0xcb0 net/ipv6/mcast.c:1789
add_grhead net/ipv6/mcast.c:1900 [inline]
add_grec+0x119f/0x19d0 net/ipv6/mcast.c:2039
mld_send_initial_cr+0x288/0x550 net/ipv6/mcast.c:2284
ipv6_mc_dad_complete+0x87/0x410 net/ipv6/mcast.c:2295
addrconf_dad_completed+0x8af/0xe60 net/ipv6/addrconf.c:4375
addrconf_dad_work+0xcf8/0x15c0 net/ipv6/addrconf.c:-1
process_one_work kernel/workqueue.c:3322 [inline]
process_scheduled_works+0xa8e/0x14e0 kernel/workqueue.c:3405
worker_thread+0xa47/0xfb0 kernel/workqueue.c:3486
page last free pid 75 tgid 75 stack trace:
reset_page_owner include/linux/page_owner.h:25 [inline]
__free_pages_prepare mm/page_alloc.c:1406 [inline]
free_unref_folios+0xd84/0x14a0 mm/page_alloc.c:3011
shrink_folio_list+0x2b7d/0x5330 mm/vmscan.c:1513
evict_folios+0x3821/0x4b40 mm/vmscan.c:4835
try_to_shrink_lruvec+0xb4f/0xed0 mm/vmscan.c:4983
shrink_one+0x233/0x730 mm/vmscan.c:5024
shrink_many mm/vmscan.c:5087 [inline]
lru_gen_shrink_node mm/vmscan.c:5165 [inline]
shrink_node+0x3303/0x3b60 mm/vmscan.c:6154
kswapd_shrink_node mm/vmscan.c:7008 [inline]
balance_pgdat mm/vmscan.c:7186 [inline]
kswapd+0x17b6/0x31c0 mm/vmscan.c:7462
kthread+0x388/0x470 kernel/kthread.c:436
ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
Memory state around the buggy address:
ffff88801f577f00: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
ffff88801f577f80: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
>ffff88801f578000: fa fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
^
ffff88801f578080: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
ffff88801f578100: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
==================================================================
Reply: An analysis of the KASAN use-after-free bug report reveals that it is **fully consistent, self-contained, and highly actionable**.
### Root Cause Analysis
The bug is caused by incomplete error-handling cleanup in the serial core port registration path (`serial_core_register_port`), which leaves dangling pointers in the global driver state (`state->uart_port`) and the port structure (`port->port_dev`).
Here is the step-by-step sequence of events that leads to the use-after-free:
1. **Port Registration Attempt:**
When a serial port is registered (e.g., during `serial8250_unregister_port` re-adding a port via `uart_add_one_port`), `serial_core_register_port()` is called.
2. **Device Allocation:**
`serial_core_register_port()` calls `serial_core_port_device_add()`, which allocates a `struct serial_port_device` via `serial_base_port_add()` and assigns it to `port->port_dev`.
3. **Linking to Driver State:**
`serial_core_register_port()` then calls `serial_core_add_one_port()`, which links the port to the driver's state:
```c
state->uart_port = uport;
uport->state = state;
```
4. **Fault Injection / Allocation Failure:**
Inside `serial_core_add_one_port()`, a memory allocation fails (specifically `kzalloc_objs` for `tty_groups` at line 3109, triggered by the `failslab` fault injection). The function returns `-ENOMEM`.
* **Crucially**, `serial_core_add_one_port()` does **not** clear `state->uart_port` or `uport->state` on this failure path.
5. **Incomplete Cleanup on Error Path:**
Upon receiving the error, `serial_core_register_port()` jumps to the `err_unregister_port_dev` error label:
```c
err_unregister_port_dev:
serial_base_port_device_remove(port->port_dev);
```
`serial_base_port_device_remove()` deletes the device and calls `put_device(&port_dev->dev)`, which triggers `serial_base_port_release()` and **frees** the `serial_port_device` structure.
* **Crucially**, `serial_core_register_port()` does **not** clear `port->port_dev` or `state->uart_port` on this failure path. They are left as dangling pointers pointing to the freed memory.
6. **Subsequent Dereference (Use-After-Free):**
Later, when a port is unregistered (e.g., during driver unbinding/removal), `serial_core_unregister_port()` is called. It attempts to find if any other ports are using the controller via `serial_core_ctrl_find()`:
```c
for (i = 0; i < drv->nr; i++) {
state = drv->state + i;
if (!state->uart_port || !state->uart_port->port_dev)
continue;
...
return serial_core_get_ctrl_dev(state->uart_port->port_dev);
}
```
Since `state->uart_port` and `state->uart_port->port_dev` were never cleared during the previous registration failure, they are both non-NULL. The code attempts to dereference the dangling `port_dev` pointer in `serial_core_get_ctrl_dev()`:
```c
static struct serial_ctrl_device *
serial_core_get_ctrl_dev(struct serial_port_device *port_dev)
{
struct device *dev = &port_dev->dev;
return to_serial_base_ctrl_device(dev->parent);
}
```
This reads `dev->parent` (located at offset **64 bytes** inside the freed `struct serial_port_device` structure), triggering the KASAN slab-use-after-free read of size 8.
---
### Conclusion
The report is **actionable**. The root cause is fully understood, and the fix involves ensuring that `state->uart_port`, `port->state`, and `port->port_dev` are properly set to `NULL` on the error paths of `serial_core_add_one_port()` and `serial_core_register_port()`.
|
| 5/2 |
2026/07/29 01:21 |
llm |
expert |
0m
Model:
Tokens:
input: 10511
output: 23
thoughts: 847
Thoughts: **Analyzing the KASAN Use-After-Free Bug Report**
Alright, let's dive into this KASAN report and determine if it's something we can actually act on. The report flags a slab use-after-free, triggered by a `FAULT_INJECTION` point causing a failure in `failslab` during `serial_core_register_port`. That's the setup, now let's break down the stacks.
The crucial part is understanding the three stacks provided. First, the "**Bad memory access stack**" is key; it shows the error occurring within `serial_core_get_ctrl_dev`, indirectly called from within the `serial_core_unregister_port` function, called from within `serial8250_remove`. We need to understand *why* this access is bad.
Next, the "**Allocation stack**" is interesting because it helps trace where this memory was originally allocated. It shows the allocation originating from `serial_base_port_add` through the calls to `serial_core_register_port`.
Finally, and perhaps most importantly, we have the "**Free stack**". This should show us *when* and *where* the memory was freed. This one's interesting, there's a reference to the `serial_core_register_port` function. Let's make sure that that is correct (Wait, the stack says: `serial_core_register_port+0x55b/0x2840 drivers/tty/serial/serial_core.c:3329`). The free appears to be happening during the same function as the register, in the `serial_core_register_port` function.
I need to immediately check the source code for `drivers/tty/serial/serial_core.c` and `drivers/tty/serial/serial_base_bus.c`. I will utilize my `codesearch-definition-source` or `read-file` or `grepper` tools to quickly get these files and relevant lines. I need to understand the logic of allocation, unregistration, and access to pinpoint the root cause here. I'm going to start with a quick search to ensure I have the files in the right place.
|
| 6/2 |
2026/07/29 01:22 |
tool |
codesearch-dir-index |
0m
Args: 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 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/07/29 01:22 |
llm |
expert |
0m
Model:
Tokens:
input: 12802
output: 41
thoughts: 102
Thoughts: **Analyzing Serial Core Driver Code**
Okay, let's dive into this. Based on my understanding of this kind of serial communication driver, and after reviewing the potential areas of interest, I'm going to focus my attention on the `serial_core.c` file. I'm thinking lines 3236, 3259, 3275, 3314, 3322, 3329, and 3358 specifically. I suspect the core logic for [insert specific suspected area based on your knowledge - e.g. flow control, baud rate setting, interrupt handling] might be in there.
Actually, scratch that, let's not cherry-pick. A more thorough approach, given the complexity of these serial drivers, is to get a broader view of this section of the source code. Let's read from line 3200 to 3400 using `read-file` to give me some context before I zoom in on any specific lines. This range should encompass the key areas I initially identified, plus any adjacent related code that might be relevant. This should give me a good overview of the relevant function or block of functions.
|
| 8/2 |
2026/07/29 01:22 |
tool |
read-file |
0m
Args: map[File:drivers/tty/serial/serial_core.c FirstLine:3200 LineCount:100]
Results: map[Contents:3200: * @port1: first port
3201: * @port2: second port
3202: *
3203: * This utility function can be used to determine whether two uart_port
3204: * structures describe the same port.
3205: */
3206: bool uart_match_port(const struct uart_port *port1,
3207: const struct uart_port *port2)
3208: {
3209: if (port1->iotype != port2->iotype)
3210: return false;
3211:
3212: switch (port1->iotype) {
3213: case UPIO_PORT:
3214: return port1->iobase == port2->iobase;
3215: case UPIO_HUB6:
3216: return port1->iobase == port2->iobase &&
3217: port1->hub6 == port2->hub6;
3218: case UPIO_MEM:
3219: case UPIO_MEM16:
3220: case UPIO_MEM32:
3221: case UPIO_MEM32BE:
3222: case UPIO_AU:
3223: case UPIO_TSI:
3224: return port1->mapbase == port2->mapbase;
3225: default:
3226: return false;
3227: }
3228: }
3229: EXPORT_SYMBOL(uart_match_port);
3230:
3231: static struct serial_ctrl_device *
3232: serial_core_get_ctrl_dev(struct serial_port_device *port_dev)
3233: {
3234: struct device *dev = &port_dev->dev;
3235:
3236: return to_serial_base_ctrl_device(dev->parent);
3237: }
3238:
3239: /*
3240: * Find a registered serial core controller device if one exists. Returns
3241: * the first device matching the ctrl_id. Caller must hold port_mutex.
3242: */
3243: static struct serial_ctrl_device *serial_core_ctrl_find(struct uart_driver *drv,
3244: struct device *phys_dev,
3245: int ctrl_id)
3246: {
3247: struct uart_state *state;
3248: int i;
3249:
3250: lockdep_assert_held(&port_mutex);
3251:
3252: for (i = 0; i < drv->nr; i++) {
3253: state = drv->state + i;
3254: if (!state->uart_port || !state->uart_port->port_dev)
3255: continue;
3256:
3257: if (state->uart_port->dev == phys_dev &&
3258: state->uart_port->ctrl_id == ctrl_id)
3259: return serial_core_get_ctrl_dev(state->uart_port->port_dev);
3260: }
3261:
3262: return NULL;
3263: }
3264:
3265: static struct serial_ctrl_device *serial_core_ctrl_device_add(struct uart_port *port)
3266: {
3267: return serial_base_ctrl_add(port, port->dev);
3268: }
3269:
3270: static int serial_core_port_device_add(struct serial_ctrl_device *ctrl_dev,
3271: struct uart_port *port)
3272: {
3273: struct serial_port_device *port_dev;
3274:
3275: port_dev = serial_base_port_add(port, ctrl_dev);
3276: if (IS_ERR(port_dev))
3277: return PTR_ERR(port_dev);
3278:
3279: port->port_dev = port_dev;
3280:
3281: return 0;
3282: }
3283:
3284: /*
3285: * Initialize a serial core port device, and a controller device if needed.
3286: */
3287: int serial_core_register_port(struct uart_driver *drv, struct uart_port *port)
3288: {
3289: struct serial_ctrl_device *ctrl_dev, *new_ctrl_dev = NULL;
3290: int ret;
3291:
3292: guard(mutex)(&port_mutex);
3293:
3294: /*
3295: * Prevent serial_port_runtime_resume() from trying to use the port
3296: * until serial_core_add_one_port() has completed
3297: */
3298: port->flags |= UPF_DEAD;
3299:
]
|
| 9/2 |
2026/07/29 01:22 |
llm |
expert |
0m
Model:
Tokens:
input: 14630
output: 41
thoughts: 29
Thoughts: Okay, let's dive into this. Here's my take on those thoughts, as if they're my own:
**Deep Dive into `serial_core_register_port`**
Alright, time to get my hands dirty. I'm thinking, "Let's crack open the code for `serial_core_register_port` right now." I need to understand it better, and since it resides in `drivers/tty/serial/serial_core.c`, that's where I'll focus. I'm hoping to gain some fresh insight into the intricacies of how this registration process works within the serial core. I want to really grasp the mechanics of this critical function. Specifically, I'm aiming to identify how new serial ports are integrated into the system, how the kernel handles the low-level details, and what data structures are in play. I'm keen on how this registration interplays with the other serial core components. Let's see what we can uncover!
|
| 10/2 |
2026/07/29 01:22 |
tool |
read-file |
0m
Args: map[File:drivers/tty/serial/serial_core.c FirstLine:3300 LineCount:100]
Results: map[Contents:3300: /* Inititalize a serial core controller device if needed */
3301: ctrl_dev = serial_core_ctrl_find(drv, port->dev, port->ctrl_id);
3302: if (!ctrl_dev) {
3303: new_ctrl_dev = serial_core_ctrl_device_add(port);
3304: if (IS_ERR(new_ctrl_dev))
3305: return PTR_ERR(new_ctrl_dev);
3306: ctrl_dev = new_ctrl_dev;
3307: }
3308:
3309: /*
3310: * Initialize a serial core port device. Tag the port dead to prevent
3311: * serial_port_runtime_resume() trying to do anything until port has
3312: * been registered. It gets cleared by serial_core_add_one_port().
3313: */
3314: ret = serial_core_port_device_add(ctrl_dev, port);
3315: if (ret)
3316: goto err_unregister_ctrl_dev;
3317:
3318: ret = serial_base_match_and_update_preferred_console(drv, port);
3319: if (ret)
3320: goto err_unregister_port_dev;
3321:
3322: ret = serial_core_add_one_port(drv, port);
3323: if (ret)
3324: goto err_unregister_port_dev;
3325:
3326: return 0;
3327:
3328: err_unregister_port_dev:
3329: serial_base_port_device_remove(port->port_dev);
3330:
3331: err_unregister_ctrl_dev:
3332: serial_base_ctrl_device_remove(new_ctrl_dev);
3333:
3334: return ret;
3335: }
3336:
3337: /*
3338: * Removes a serial core port device, and the related serial core controller
3339: * device if the last instance.
3340: */
3341: void serial_core_unregister_port(struct uart_driver *drv, struct uart_port *port)
3342: {
3343: struct device *phys_dev = port->dev;
3344: struct serial_port_device *port_dev = port->port_dev;
3345: struct serial_ctrl_device *ctrl_dev = serial_core_get_ctrl_dev(port_dev);
3346: int ctrl_id = port->ctrl_id;
3347:
3348: guard(mutex)(&port_mutex);
3349:
3350: port->flags |= UPF_DEAD;
3351:
3352: serial_core_remove_one_port(drv, port);
3353:
3354: /* Note that struct uart_port *port is no longer valid at this point */
3355: serial_base_port_device_remove(port_dev);
3356:
3357: /* Drop the serial core controller device if no ports are using it */
3358: if (!serial_core_ctrl_find(drv, phys_dev, ctrl_id))
3359: serial_base_ctrl_device_remove(ctrl_dev);
3360: }
3361:
3362: /**
3363: * uart_handle_dcd_change - handle a change of carrier detect state
3364: * @uport: uart_port structure for the open port
3365: * @active: new carrier detect status
3366: *
3367: * Caller must hold uport->lock.
3368: */
3369: void uart_handle_dcd_change(struct uart_port *uport, bool active)
3370: {
3371: struct tty_port *port = &uport->state->port;
3372: struct tty_struct *tty = port->tty;
3373: struct tty_ldisc *ld;
3374:
3375: lockdep_assert_held_once(&uport->lock);
3376:
3377: if (tty) {
3378: ld = tty_ldisc_ref(tty);
3379: if (ld) {
3380: if (ld->ops->dcd_change)
3381: ld->ops->dcd_change(tty, active);
3382: tty_ldisc_deref(ld);
3383: }
3384: }
3385:
3386: uport->icount.dcd++;
3387:
3388: if (uart_dcd_enabled(uport)) {
3389: if (active)
3390: wake_up_interruptible(&port->open_wait);
3391: else if (tty)
3392: tty_hangup(tty);
3393: }
3394: }
3395: EXPORT_SYMBOL_GPL(uart_handle_dcd_change);
3396:
3397: /**
3398: * uart_handle_cts_change - handle a change of clear-to-send state
3399: * @uport: uart_port structure for the open port
]
|
| 11/2 |
2026/07/29 01:22 |
llm |
expert |
0m
Model:
Tokens:
input: 16421
output: 52
thoughts: 55
Thoughts: **Analyzing the `serial8250_unregister_port` Function**
Okay, let's break this down systematically. My initial thought is to trace the sequence of events. The primary action we're dealing with is the call to `serial8250_unregister_port`. The next logical step, from an engineering perspective, is to understand its behavior. Specifically, I need to know *what* `serial8250_unregister_port` actually *does*. Therefore, I'm going to find the definition of the `serial8250_unregister_port` function and thoroughly examine its implementation. Understanding the functionality of `serial8250_unregister_port` is paramount to grasping the complete context.
|
| 12/2 |
2026/07/29 01:22 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/tty/serial/8250/8250_core.c Name:serial8250_unregister_port]
Results: map[SourceCode: 865: /**
866: * serial8250_unregister_port - remove a 16x50 serial port at runtime
867: * @line: serial line number
868: *
869: * Remove one serial port. This may not be called from interrupt
870: * context. We hand the port back to the our control.
871: */
872: void serial8250_unregister_port(int line)
873: {
874: struct uart_8250_port *uart = &serial8250_ports[line];
875:
876: guard(mutex)(&serial_mutex);
877:
878: if (uart->em485) {
879: guard(uart_port_lock_irqsave)(&uart->port);
880: serial8250_em485_destroy(uart);
881: }
882:
883: uart_remove_one_port(&serial8250_reg, &uart->port);
884: if (serial8250_isa_devs) {
885: uart->port.flags &= ~UPF_BOOT_AUTOCONF;
886: uart->port.type = PORT_UNKNOWN;
887: uart->port.dev = &serial8250_isa_devs->dev;
888: uart->port.port_id = line;
889: uart->capabilities = 0;
890: serial8250_init_port(uart);
891: serial8250_apply_quirks(uart);
892: uart_add_one_port(&serial8250_reg, &uart->port);
893: } else {
894: uart->port.dev = NULL;
895: }
896: }
SourceFile:drivers/tty/serial/8250/8250_core.c]
|
| 13/2 |
2026/07/29 01:22 |
llm |
expert |
0m
Model:
Tokens:
input: 17260
output: 69
thoughts: 276
Thoughts: **Analyzing the 8250 Serial Port Unregistration**
Okay, let's break down this `serial8250_unregister_port` function. My initial thought is to examine how it handles port removal. Here's what jumps out: it calls `uart_remove_one_port` first. Now, `uart_remove_one_port` and, by association, `uart_add_one_port`, have to be pretty fundamental. I'm noting that after removing the port, there's a conditional block that deals with the `serial8250_isa_devs` variable. Inside, it seems to be resetting some port flags (`UPF_BOOT_AUTOCONF`), setting the port type to `PORT_UNKNOWN`, assigning the device pointer, setting the port ID (which is the line), clearing capabilities, and re-initializing and reapplying quirks to the UART port. Then, it calls `uart_add_one_port`. If `serial8250_isa_devs` is not present, it simply sets the device pointer to `NULL`.
Aha! Now I'm recalling that `uart_remove_one_port` and `uart_add_one_port` are just wrappers. It's time to confirm and check if they wrap `serial_core_unregister_port` and `serial_core_register_port`. I'm going to quickly check their definitions.
|
| 14/2 |
2026/07/29 01:22 |
tool |
codesearch-find-references |
0m
Args: map[ContextFile:drivers/tty/serial/8250/8250_core.c IncludeSnippetLines:5 Name:uart_remove_one_port SourceTreePrefix:drivers/tty/serial/]
Results: map[References:[map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:serial8250_register_8250_port SourceFile:drivers/tty/serial/8250/8250_core.c SourceLine:724 SourceSnippet: 719:
720: /* Preserve specified console flow control. */
721: cons_flow = uart_cons_flow_enabled(&uart->port);
722:
723: if (uart->port.dev)
724: uart_remove_one_port(&serial8250_reg, &uart->port);
725:
726: uart->port.ctrl_id = up->port.ctrl_id;
727: uart->port.port_id = up->port.port_id;
728: uart->port.iobase = up->port.iobase;
729: uart->port.membase = up->port.membase;
] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:serial8250_unregister_port SourceFile:drivers/tty/serial/8250/8250_core.c SourceLine:883 SourceSnippet: 878: if (uart->em485) {
879: guard(uart_port_lock_irqsave)(&uart->port);
880: serial8250_em485_destroy(uart);
881: }
882:
883: uart_remove_one_port(&serial8250_reg, &uart->port);
884: if (serial8250_isa_devs) {
885: uart->port.flags &= ~UPF_BOOT_AUTOCONF;
886: uart->port.type = PORT_UNKNOWN;
887: uart->port.dev = &serial8250_isa_devs->dev;
888: uart->port.port_id = line;
] map[ReferenceKind:takes-address-of ReferencingEntityKind:global_variable ReferencingEntityName:__UNIQUE_ID_addressable_uart_remove_one_port_762 SourceFile:drivers/tty/serial/serial_port.c SourceLine:151 SourceSnippet: 151: EXPORT_SYMBOL(uart_remove_one_port);
] map[ReferenceKind:takes-address-of ReferencingEntityKind:global_variable ReferencingEntityName:__gendwarfksyms_ptr_uart_remove_one_port SourceFile:drivers/tty/serial/serial_port.c SourceLine:151 SourceSnippet: 151: EXPORT_SYMBOL(uart_remove_one_port);
] map[ReferenceKind:takes-address-of ReferencingEntityKind:global_variable ReferencingEntityName:__gendwarfksyms_ptr_uart_remove_one_port SourceFile:drivers/tty/serial/serial_port.c SourceLine:151 SourceSnippet: 151: EXPORT_SYMBOL(uart_remove_one_port);
]] TruncatedOutput:false]
|
| 15/2 |
2026/07/29 01:22 |
llm |
expert |
0m
Model:
Tokens:
input: 18773
output: 38
thoughts: 39
Thoughts: Okay, let's dive into this. I need to understand how these serial ports are managed within the kernel. My initial focus is on the core functions responsible for adding and removing them. I'm going to start by examining `drivers/tty/serial/serial_port.c`. This seems like the most logical place to begin, given the file path. Specifically, I'm targeting the functions `uart_remove_one_port` and `uart_add_one_port`. These names are pretty self-explanatory, but I'll confirm their exact behavior within the context of the larger serial driver framework. I'll need to dissect these functions' implementations, paying close attention to any resource allocation, deallocation, device registration/deregistration, and interrupt handling involved. Understanding the flow from the point of port detection/initialization to the point of removal is key. I'm expecting to find some intricate locking mechanisms and potential interactions with other subsystems. I'll be looking for potential race conditions or resource leaks. Given the critical nature of serial communication, robustness and reliability are paramount, so I'll be keeping a keen eye out for how these aspects are addressed. This initial investigation should provide a solid foundation for understanding the entire serial port management lifecycle.
|
| 16/2 |
2026/07/29 01:22 |
tool |
read-file |
0m
Args: map[File:drivers/tty/serial/serial_port.c FirstLine:1 LineCount:100]
Results: map[Contents: 1: // SPDX-License-Identifier: GPL-2.0+
2: /*
3: * Serial core port device driver
4: *
5: * Copyright (C) 2023 Texas Instruments Incorporated - https://www.ti.com/
6: * Author: Tony Lindgren <tony@atomide.com>
7: */
8:
9: #include <linux/device.h>
10: #include <linux/module.h>
11: #include <linux/of.h>
12: #include <linux/platform_device.h>
13: #include <linux/pm_runtime.h>
14: #include <linux/pnp.h>
15: #include <linux/property.h>
16: #include <linux/serial_core.h>
17: #include <linux/spinlock.h>
18:
19: #include "serial_base.h"
20:
21: #define SERIAL_PORT_AUTOSUSPEND_DELAY_MS 500
22:
23: /* Only considers pending TX for now. Caller must take care of locking */
24: static int __serial_port_busy(struct uart_port *port)
25: {
26: return !uart_tx_stopped(port) &&
27: !kfifo_is_empty(&port->state->port.xmit_fifo);
28: }
29:
30: static int serial_port_runtime_resume(struct device *dev)
31: {
32: struct serial_port_device *port_dev = to_serial_base_port_device(dev);
33: struct uart_port *port;
34: unsigned long flags;
35:
36: port = port_dev->port;
37:
38: if (port->flags & UPF_DEAD)
39: goto out;
40:
41: /* Flush any pending TX for the port */
42: uart_port_lock_irqsave(port, &flags);
43: if (!port_dev->tx_enabled)
44: goto unlock;
45: if (__serial_port_busy(port))
46: port->ops->start_tx(port);
47:
48: unlock:
49: uart_port_unlock_irqrestore(port, flags);
50:
51: out:
52: pm_runtime_mark_last_busy(dev);
53:
54: return 0;
55: }
56:
57: static int serial_port_runtime_suspend(struct device *dev)
58: {
59: struct serial_port_device *port_dev = to_serial_base_port_device(dev);
60: struct uart_port *port = port_dev->port;
61: unsigned long flags;
62: bool busy;
63:
64: if (port->flags & UPF_DEAD)
65: return 0;
66:
67: /*
68: * Nothing to do on pm_runtime_force_suspend(), see
69: * DEFINE_RUNTIME_DEV_PM_OPS.
70: */
71: if (!pm_runtime_enabled(dev))
72: return 0;
73:
74: uart_port_lock_irqsave(port, &flags);
75: if (!port_dev->tx_enabled) {
76: uart_port_unlock_irqrestore(port, flags);
77: return 0;
78: }
79:
80: busy = __serial_port_busy(port);
81: if (busy)
82: port->ops->start_tx(port);
83: uart_port_unlock_irqrestore(port, flags);
84:
85: if (busy)
86: pm_runtime_mark_last_busy(dev);
87:
88: return busy ? -EBUSY : 0;
89: }
90:
91: static void serial_base_port_set_tx(struct uart_port *port,
92: struct serial_port_device *port_dev,
93: bool enabled)
94: {
95: unsigned long flags;
96:
97: uart_port_lock_irqsave(port, &flags);
98: port_dev->tx_enabled = enabled;
99: uart_port_unlock_irqrestore(port, flags);
100: }
]
|
| 17/2 |
2026/07/29 01:22 |
llm |
expert |
0m
Model:
Tokens:
input: 20426
output: 40
thoughts: 19
Thoughts: Okay, here's my interpretation of that thought process, tailored for an expert audience, and written in the first person:
**Diving into the Serial Port Driver**
Right, let's get into the nitty-gritty. I'm thinking we need a deeper dive into `drivers/tty/serial/serial_port.c`. That file is where the core serial port driver functionality resides. I know the general architecture, of course, but I need to refresh my memory on the implementation details ā specifically, how they're handling things like flow control, interrupt handling, and character buffering within this specific serial port driver. I want to see if there are any obvious performance bottlenecks or potential race conditions that might be lurking. Time to get familiar with the low-level interactions and algorithms used in the driver code. Let's see how they've handled the typical challenges of serial communication within this kernel environment. Specifically, I'm interested in how they're managing baud rates, parity, stop bits, and all the low-level details that actually make the communication happen. This is going to be good.
|
| 18/2 |
2026/07/29 01:22 |
tool |
read-file |
0m
Args: map[File:drivers/tty/serial/serial_port.c FirstLine:101 LineCount:100]
Results: map[Contents: 101:
102: void serial_base_port_startup(struct uart_port *port)
103: {
104: struct serial_port_device *port_dev = port->port_dev;
105:
106: serial_base_port_set_tx(port, port_dev, true);
107: }
108:
109: void serial_base_port_shutdown(struct uart_port *port)
110: {
111: struct serial_port_device *port_dev = port->port_dev;
112:
113: serial_base_port_set_tx(port, port_dev, false);
114: }
115:
116: static DEFINE_RUNTIME_DEV_PM_OPS(serial_port_pm,
117: serial_port_runtime_suspend,
118: serial_port_runtime_resume, NULL);
119:
120: static int serial_port_probe(struct device *dev)
121: {
122: pm_runtime_enable(dev);
123: pm_runtime_set_autosuspend_delay(dev, SERIAL_PORT_AUTOSUSPEND_DELAY_MS);
124: pm_runtime_use_autosuspend(dev);
125:
126: return 0;
127: }
128:
129: static int serial_port_remove(struct device *dev)
130: {
131: pm_runtime_dont_use_autosuspend(dev);
132: pm_runtime_disable(dev);
133:
134: return 0;
135: }
136:
137: /*
138: * Serial core port device init functions. Note that the physical serial
139: * port device driver may not have completed probe at this point.
140: */
141: int uart_add_one_port(struct uart_driver *drv, struct uart_port *port)
142: {
143: return serial_ctrl_register_port(drv, port);
144: }
145: EXPORT_SYMBOL(uart_add_one_port);
146:
147: void uart_remove_one_port(struct uart_driver *drv, struct uart_port *port)
148: {
149: serial_ctrl_unregister_port(drv, port);
150: }
151: EXPORT_SYMBOL(uart_remove_one_port);
152:
153: /**
154: * __uart_read_properties - read firmware properties of the given UART port
155: * @port: corresponding port
156: * @use_defaults: apply defaults (when %true) or validate the values (when %false)
157: *
158: * The following device properties are supported:
159: * - clock-frequency (optional)
160: * - fifo-size (optional)
161: * - no-loopback-test (optional)
162: * - reg-shift (defaults may apply)
163: * - reg-offset (value may be validated)
164: * - reg-io-width (defaults may apply or value may be validated)
165: * - interrupts (OF only)
166: * - serial [alias ID] (OF only)
167: *
168: * If the port->dev is of struct platform_device type the interrupt line
169: * will be retrieved via platform_get_irq() call against that device.
170: * Otherwise it will be assigned by fwnode_irq_get() call. In both cases
171: * the index 0 of the resource is used.
172: *
173: * The caller is responsible to initialize the following fields of the @port
174: * ->dev (must be valid)
175: * ->flags
176: * ->iobase
177: * ->mapbase
178: * ->mapsize
179: * ->regshift (if @use_defaults is false)
180: * before calling this function. Alternatively the above mentioned fields
181: * may be zeroed, in such case the only ones, that have associated properties
182: * found, will be set to the respective values.
183: *
184: * If no error happened, the ->irq, ->mapbase, ->mapsize will be altered.
185: * The ->iotype is always altered.
186: *
187: * When @use_defaults is true and the respective property is not found
188: * the following values will be applied:
189: * ->regshift = 0
190: * In this case IRQ must be provided, otherwise an error will be returned.
191: *
192: * When @use_defaults is false and the respective property is found
193: * the following values will be validated:
194: * - reg-io-width (->iotype)
195: * - reg-offset (->mapsize against ->mapbase)
196: *
197: * Returns: 0 on success or negative errno on failure
198: */
199: static int __uart_read_properties(struct uart_port *port, bool use_defaults)
200: {
]
|
| 19/2 |
2026/07/29 01:22 |
llm |
expert |
0m
Model:
Tokens:
input: 22281
output: 21
thoughts: 50
Thoughts: Okay, here's my interpretation of that thought process, from an expert's perspective:
**Diving into the Serial Controller Code**
Alright, so `serial_ctrl_register_port` and `serial_ctrl_unregister_port` are being called, eh? My immediate thought is, "Right, let's trace those function calls." First, I need to know where these functions are defined. No point in trying to debug a call if I can't look at the source.
Okay, instead of manually searching through headers and source files (though I know where to *start* looking based on my experience with this architecture), I'll reach for `grep`. It's quicker and more reliable to find every single instance of `serial_ctrl_register_port`. Then I can follow the breadcrumbs, see where it's declared, check the arguments, and see how its used. That will tell me a lot.
|
| 20/2 |
2026/07/29 01:22 |
tool |
grepper |
0m
Args: map[Expression:serial_ctrl_register_port]
Results: map[Output:drivers/tty/serial/serial_base.h=45=void serial_base_port_device_remove(struct serial_port_device *port_dev);
drivers/tty/serial/serial_base.h-46-
drivers/tty/serial/serial_base.h:47:int serial_ctrl_register_port(struct uart_driver *drv, struct uart_port *port);
drivers/tty/serial/serial_base.h-48-void serial_ctrl_unregister_port(struct uart_driver *drv, struct uart_port *port);
--
drivers/tty/serial/serial_ctrl.c=28=static int serial_ctrl_remove(struct device *dev)
--
drivers/tty/serial/serial_ctrl.c-38- */
drivers/tty/serial/serial_ctrl.c:39:int serial_ctrl_register_port(struct uart_driver *drv, struct uart_port *port)
drivers/tty/serial/serial_ctrl.c-40-{
--
drivers/tty/serial/serial_port.c=141=int uart_add_one_port(struct uart_driver *drv, struct uart_port *port)
drivers/tty/serial/serial_port.c-142-{
drivers/tty/serial/serial_port.c:143: return serial_ctrl_register_port(drv, port);
drivers/tty/serial/serial_port.c-144-}
]
|
| 21/2 |
2026/07/29 01:22 |
llm |
expert |
0m
Model:
Tokens:
input: 22905
output: 38
thoughts: 23
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Diving into `serial_ctrl.c` - Line 39**
Okay, time to get my hands dirty. I'm going to jump into the `drivers/tty/serial/serial_ctrl.c` file. Specifically, I'm aiming for around line 39. That usually signifies a critical part of the initialization, interrupt setup, or perhaps a core data structure definition within a serial driver. Line 39 could be defining the `struct` for the serial port, or it could be where the main control logic begins. I'm expecting to find the key function calls and variable initializations for the UART driver. Let's see what we can dig up.
|
| 22/2 |
2026/07/29 01:22 |
tool |
read-file |
0m
Args: map[File:drivers/tty/serial/serial_ctrl.c FirstLine:30 LineCount:50]
Results: map[Contents: 30: pm_runtime_disable(dev);
31:
32: return 0;
33: }
34:
35: /*
36: * Serial core controller device init functions. Note that the physical
37: * serial port device driver may not have completed probe at this point.
38: */
39: int serial_ctrl_register_port(struct uart_driver *drv, struct uart_port *port)
40: {
41: return serial_core_register_port(drv, port);
42: }
43:
44: void serial_ctrl_unregister_port(struct uart_driver *drv, struct uart_port *port)
45: {
46: serial_core_unregister_port(drv, port);
47: }
48:
49: static struct device_driver serial_ctrl_driver = {
50: .name = "ctrl",
51: .suppress_bind_attrs = true,
52: .probe = serial_ctrl_probe,
53: .remove = serial_ctrl_remove,
54: };
55:
56: int serial_base_ctrl_init(void)
57: {
58: return serial_base_driver_register(&serial_ctrl_driver);
59: }
60:
61: void serial_base_ctrl_exit(void)
62: {
63: serial_base_driver_unregister(&serial_ctrl_driver);
64: }
65:
66: MODULE_AUTHOR("Tony Lindgren <tony@atomide.com>");
67: MODULE_DESCRIPTION("Serial core controller driver");
68: MODULE_LICENSE("GPL");
]
|
| 23/2 |
2026/07/29 01:22 |
llm |
expert |
0m
Model:
Tokens:
input: 23626
output: 41
thoughts: 186
Thoughts: **Serial Port Register/Unregister Flow**
Okay, so I'm looking at how serial ports get registered and unregistered. It's pretty straightforward, really. `serial_ctrl_register_port` is just a wrapper, calling `serial_core_register_port` under the hood. Similarly, `serial_ctrl_unregister_port` calls `serial_core_unregister_port`. Seems like a standard abstraction.
Now, let's trace the allocation path. I see it goes something like this: starting with `serial_base_port_add`, which then calls the inlined `serial_core_port_device_add`, and finally winds up at `serial_core_register_port`.
Alright, I'll go ahead and take a look at the code in `drivers/tty/serial/serial_base_bus.c`, focusing on the vicinity of line 159. That should clarify the role of `serial_base_port_add` in this whole process. It's likely initializing some of the base port data structures before handing it off further down the stack. I'll get the context there.
|
| 24/2 |
2026/07/29 01:22 |
tool |
read-file |
0m
Args: map[File:drivers/tty/serial/serial_base_bus.c FirstLine:140 LineCount:50]
Results: map[Contents: 140:
141: return ERR_PTR(err);
142: }
143:
144: static void serial_base_port_release(struct device *dev)
145: {
146: struct serial_port_device *port_dev = to_serial_base_port_device(dev);
147:
148: fwnode_handle_put(dev_fwnode(dev));
149: kfree(port_dev);
150: }
151:
152: struct serial_port_device *serial_base_port_add(struct uart_port *port,
153: struct serial_ctrl_device *ctrl_dev)
154: {
155: struct serial_port_device *port_dev;
156: int min = 0, max = -1; /* Use -1 for max to apply IDA defaults */
157: int err;
158:
159: port_dev = kzalloc_obj(*port_dev);
160: if (!port_dev)
161: return ERR_PTR(-ENOMEM);
162:
163: /* Device driver specified port_id vs automatic assignment? */
164: if (port->port_id) {
165: min = port->port_id;
166: max = port->port_id;
167: }
168:
169: err = ida_alloc_range(&ctrl_dev->port_ida, min, max, GFP_KERNEL);
170: if (err < 0) {
171: kfree(port_dev);
172: return ERR_PTR(err);
173: }
174:
175: port->port_id = err;
176:
177: err = serial_base_device_init(port, &port_dev->dev,
178: &ctrl_dev->dev, &serial_port_type,
179: serial_base_port_release,
180: port->ctrl_id, port->port_id);
181: if (err)
182: goto err_put_device;
183:
184: port_dev->port = port;
185:
186: err = device_add(&port_dev->dev);
187: if (err)
188: goto err_put_device;
189:
]
|
| 25/2 |
2026/07/29 01:22 |
llm |
expert |
0m
Model:
Tokens:
input: 24819
output: 46
thoughts: 287
Thoughts: **Analyzing the Serial Port Device Registration and Removal**
Okay, so I'm looking at `serial_base_port_add`, which, as I see it, allocates a `port_dev` of type `struct serial_port_device` using `kzalloc_obj(*port_dev)`. Standard memory allocation. But hold on, let's trace this and see if there are any potential leaks or other issues. I need to understand the lifecycle of this `port_dev` within the registration and unregistration process.
Let me examine the current stack, it's:
```
device_release+0xc4/0x1f0 drivers/base/core.c:-1
kobject_cleanup lib/kobject.c:689 [inline]
kobject_release lib/kobject.c:720 [inline]
kref_put include/linux/kref.h:65 [inline]
kobject_put+0x222/0x550 lib/kobject.c:737
serial_core_register_port+0x55b/0x2840 drivers/tty/serial/serial_core.c:3329
```
Right, so the important part is in `serial_core_register_port`. Line 3329 is the key place to look in `serial_core_register_port`
```c
err_unregister_port_dev:
serial_base_port_device_remove(port->port_dev);
```
Ah, it seems in case of a problem, it calls `serial_base_port_device_remove` to clean up. Now I need to know what `serial_base_port_device_remove` does. Let's find its definition, and dig into that function. This is critical to ensure that memory is properly deallocated when ports are unregistered or errors occur during registration.
|
| 26/2 |
2026/07/29 01:22 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/tty/serial/serial_base_bus.c Name:serial_base_port_device_remove]
Results: map[SourceCode: 199: void serial_base_port_device_remove(struct serial_port_device *port_dev)
200: {
201: struct serial_ctrl_device *ctrl_dev;
202: struct device *parent;
203:
204: if (!port_dev)
205: return;
206:
207: parent = port_dev->dev.parent;
208: ctrl_dev = to_serial_base_ctrl_device(parent);
209:
210: device_del(&port_dev->dev);
211: ida_free(&ctrl_dev->port_ida, port_dev->port->port_id);
212: put_device(&port_dev->dev);
213: }
SourceFile:drivers/tty/serial/serial_base_bus.c]
|
| 27/2 |
2026/07/29 01:22 |
llm |
expert |
0m
Model:
Tokens:
input: 25832
output: 44
thoughts: 902
Thoughts: **Analyzing a Potential Use-After-Free Vulnerability**
Okay, so I'm digging into this code, specifically around `serial_core_unregister_port`. My initial thought was: `serial_base_port_device_remove` calls `put_device`, and that's going to trigger `serial_base_port_release`, which then calls `kfree(port_dev)`. So, `serial_base_port_device_remove` is definitely freeing `port_dev`. Good.
Now, let's look at this stack trace for the bad memory access: `serial_core_get_ctrl_dev` -> `serial_core_ctrl_find` -> `serial_core_unregister_port`. I see the call to `serial_core_unregister_port` and, let's trace it. It seems we're looking at line 3358.
The crucial bit, I think, is this: `serial_base_port_device_remove(port_dev)`. Then, there's a check with `serial_core_ctrl_find`. Let's peek at `serial_core_ctrl_find` itself: I see it iterates through a `drv->state` array, and crucially checks `state->uart_port->port_dev`.
This is where it gets interesting. I know `serial_base_port_device_remove(port_dev)` is freeing `port_dev`, which is the same as `port->port_dev`. The question is, does `state->uart_port->port_dev` still point to this freed memory? That is a definite use-after-free scenario.
The crucial link is `serial_core_remove_one_port`. Does that function or `serial_base_port_device_remove` nullify the pointer to `port->port_dev` to avoid this problem? Need to check the definition of `serial_core_remove_one_port` to be sure.
|
| 28/2 |
2026/07/29 01:22 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/tty/serial/serial_core.c Name:serial_core_remove_one_port]
Results: map[SourceCode:3138: /**
3139: * serial_core_remove_one_port - detach a driver defined port structure
3140: * @drv: pointer to the uart low level driver structure for this port
3141: * @uport: uart port structure for this port
3142: *
3143: * Context: task context, might sleep
3144: *
3145: * This unhooks (and hangs up) the specified port structure from the core
3146: * driver. No further calls will be made to the low-level code for this port.
3147: * Caller must hold port_mutex.
3148: */
3149: static void serial_core_remove_one_port(struct uart_driver *drv,
3150: struct uart_port *uport)
3151: {
3152: struct uart_state *state = drv->state + uport->line;
3153: struct tty_port *port = &state->port;
3154: struct uart_port *uart_port;
3155:
3156: scoped_guard(mutex, &port->mutex) {
3157: uart_port = uart_port_check(state);
3158: if (uart_port != uport)
3159: dev_alert(uport->dev, "Removing wrong port: %p != %p\n", uart_port, uport);
3160:
3161: if (!uart_port)
3162: return;
3163: }
3164:
3165: /*
3166: * Remove the devices from the tty layer
3167: */
3168: tty_port_unregister_device(port, drv->tty_driver, uport->line);
3169:
3170: tty_port_tty_vhangup(port);
3171:
3172: /*
3173: * If the port is used as a console, unregister it
3174: */
3175: if (uart_console(uport))
3176: unregister_console(uport->cons);
3177:
3178: /*
3179: * Free the port IO and memory resources, if any.
3180: */
3181: if (uport->type != PORT_UNKNOWN && uport->ops->release_port)
3182: uport->ops->release_port(uport);
3183: kfree(uport->tty_groups);
3184: kfree(uport->name);
3185:
3186: /*
3187: * Indicate that there isn't a port here anymore.
3188: */
3189: uport->type = PORT_UNKNOWN;
3190: uport->port_dev = NULL;
3191:
3192: guard(mutex)(&port->mutex);
3193: WARN_ON(atomic_dec_return(&state->refcount) < 0);
3194: wait_event(state->remove_wait, !atomic_read(&state->refcount));
3195: state->uart_port = NULL;
3196: }
SourceFile:drivers/tty/serial/serial_core.c]
|
| 29/2 |
2026/07/29 01:22 |
llm |
expert |
0m
Model:
Tokens:
input: 28118
output: 44
thoughts: 731
Thoughts: **Dissecting a Serial Port Unregistration Bug**
Okay, so I'm looking at this `serial_core_unregister_port` function, and my initial thought is, "Aha! `serial_core_remove_one_port` is clearing `uport->port_dev` and `state->uart_port`. That's important."
But then I circle back to `serial_core_unregister_port`. The code removes the port. It sets the `UPF_DEAD` flag, calls `serial_core_remove_one_port`, and *then* calls `serial_base_port_device_remove` which is fine. But then it calls `serial_core_ctrl_find`. And that's where things get interesting. If `serial_core_remove_one_port` is setting `state->uart_port = NULL`, how is `serial_core_ctrl_find` supposed to find the port? It shouldn't be able to, right? The `port` should be gone!
Hold on, there's another possibility. What if there's *another* port registered to the same `drv`? Maybe it's using the same controller. That's a good thing to think about and worth looking into.
Now I need to remember the allocation and free stacks and see if anything jumps out from that angle. Hmm, something's still not completely clear.
Okay, now, the code I'm really trying to understand is why `serial_core_register_port` failed. Let's check the FAULT_INJECTION stack trace for more details. Okay, so it failed during memory allocation. It looks like it was triggered at `__kmalloc_noprof` and it went through `serial_core_add_one_port` and ultimately ended up at `serial_core_register_port`. I remember the line of code that it failed on!
`serial_core_register_port` failed at line 3322:
```c
ret = serial_core_add_one_port(drv, port);
if (ret)
goto err_unregister_port_dev;
```
Right, it's failing when trying to add a port. This means `serial_core_add_one_port` returned an error.
Let's look at `serial_core_add_one_port` definition.
|
| 30/2 |
2026/07/29 01:22 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/tty/serial/serial_core.c Name:serial_core_add_one_port]
Results: map[SourceCode:3042: /**
3043: * serial_core_add_one_port - attach a driver-defined port structure
3044: * @drv: pointer to the uart low level driver structure for this port
3045: * @uport: uart port structure to use for this port.
3046: *
3047: * Context: task context, might sleep
3048: *
3049: * This allows the driver @drv to register its own uart_port structure with the
3050: * core driver. The main purpose is to allow the low level uart drivers to
3051: * expand uart_port, rather than having yet more levels of structures.
3052: * Caller must hold port_mutex.
3053: */
3054: static int serial_core_add_one_port(struct uart_driver *drv, struct uart_port *uport)
3055: {
3056: struct uart_state *state;
3057: struct tty_port *port;
3058: struct device *tty_dev;
3059: int num_groups;
3060:
3061: if (uport->line >= drv->nr)
3062: return -EINVAL;
3063:
3064: state = drv->state + uport->line;
3065: port = &state->port;
3066:
3067: guard(mutex)(&port->mutex);
3068: if (state->uart_port)
3069: return -EINVAL;
3070:
3071: /* Link the port to the driver state table and vice versa */
3072: atomic_set(&state->refcount, 1);
3073: init_waitqueue_head(&state->remove_wait);
3074: state->uart_port = uport;
3075: uport->state = state;
3076:
3077: /*
3078: * If this port is in use as a console then the spinlock is already
3079: * initialised.
3080: */
3081: if (!uart_console_registered(uport))
3082: uart_port_spin_lock_init(uport);
3083:
3084: state->pm_state = UART_PM_STATE_UNDEFINED;
3085: uart_port_set_cons(uport, drv->cons);
3086: uport->minor = drv->tty_driver->minor_start + uport->line;
3087: uport->name = kasprintf(GFP_KERNEL, "%s%u", drv->dev_name,
3088: drv->tty_driver->name_base + uport->line);
3089: if (!uport->name)
3090: return -ENOMEM;
3091:
3092: if (uport->cons && uport->dev)
3093: of_console_check(uport->dev->of_node, uport->cons->name, uport->line);
3094:
3095: /*
3096: * TTY port has to be linked with the driver before register_console()
3097: * in uart_configure_port(), because user-space could open the console
3098: * immediately after.
3099: */
3100: tty_port_link_device(port, drv->tty_driver, uport->line);
3101: uart_configure_port(drv, state, uport);
3102:
3103: port->console = uart_console(uport);
3104:
3105: num_groups = 2;
3106: if (uport->attr_group)
3107: num_groups++;
3108:
3109: uport->tty_groups = kzalloc_objs(*uport->tty_groups, num_groups);
3110: if (!uport->tty_groups)
3111: return -ENOMEM;
3112:
3113: uport->tty_groups[0] = &tty_dev_attr_group;
3114: if (uport->attr_group)
3115: uport->tty_groups[1] = uport->attr_group;
3116:
3117: /* Ensure serdev drivers can call serdev_device_open() right away */
3118: uport->flags &= ~UPF_DEAD;
3119:
3120: /*
3121: * Register the port whether it's detected or not. This allows
3122: * setserial to be used to alter this port's parameters.
3123: */
3124: tty_dev = tty_port_register_device_attr_serdev(port, drv->tty_driver,
3125: uport->line, uport->dev, &uport->port_dev->dev, port,
3126: uport->tty_groups);
3127: if (!IS_ERR(tty_dev)) {
3128: device_set_wakeup_capable(tty_dev, 1);
3129: } else {
3130: uport->flags |= UPF_DEAD;
3131: dev_err(uport->dev, "Cannot register tty device on line %u\n",
3132: uport->line);
3133: }
3134:
3135: return 0;
3136: }
SourceFile:drivers/tty/serial/serial_core.c]
|
| 31/2 |
2026/07/29 01:22 |
llm |
expert |
0m
Model:
Tokens:
input: 30962
output: 42
thoughts: 6065
Thoughts: **Analysis of a Serial Port Driver Use-After-Free Vulnerability**
Okay, I've got this figured out. The crux of the issue lies within how the serial port driver handles registration and unregistration, specifically when a registration attempt fails due to a memory allocation problem (like a failslab). I started by noticing that the initial `kzalloc_objs` call within `serial_core_add_one_port` fails, leading to `-ENOMEM` and a subsequent failure in `serial_core_register_port`. I then traced the error handling and realized that if `serial_core_add_one_port` returns an error, it doesn't correctly clean up the `state->uart_port` or `port->state` pointers. This means that a failed allocation does not result in the expected cleanup.
Specifically, the problem revolves around the `serial8250_unregister_port` function. It first calls `uart_remove_one_port`, which triggers a call to `serial_core_unregister_port`. However, if a previous registration attempt failed within `serial8250_unregister_port` (due to the `kzalloc_objs` failure), crucial pointers were not set to NULL.
During this failed registration, the code calls `serial_core_port_device_add` which, in turn, allocates and assigns `port->port_dev`. If `serial_core_add_one_port` fails, `port->port_dev` is freed in the `err_unregister_port_dev` section of `serial_core_register_port`. HOWEVER, `state->uart_port` is left pointing to this now-freed memory!
Later, when `serial8250_unregister_port` gets called again, `uart_remove_one_port` is invoked. Inside this call, `serial_core_unregister_port` gets called, and the `port->port_dev` pointer still points to the freed memory.
Within `serial_core_unregister_port`, it calls `serial_core_ctrl_find`. Critically, `serial_core_ctrl_find` iterates through the serial driver's state and checks for registered ports. Since `state->uart_port` was not correctly cleared after the failed registration, the code incorrectly accesses freed memory within `serial_core_ctrl_find`. Specifically, inside `serial_core_ctrl_find`, the code accesses `port_dev->dev->parent` when `port_dev` is a freed pointer. This is the source of the use-after-free, a read on freed memory.
This use-after-free occurs because the registration failure's cleanup path does not set relevant pointers to NULL, thus leading to the vulnerable code path. The stack traces confirm this, showing that the crash happens during access to the `port_dev` within `serial_core_get_ctrl_dev`. Essentially, the driver's state is corrupted due to improper cleanup after a failed registration, making the next access of the port to be pointing at freed memory.
|
| 32/2 |
2026/07/29 01:23 |
tool |
codesearch-struct-layout |
0m
Args: map[ContextFile:drivers/tty/serial/serial_base_bus.c Name:serial_port_device]
Results: map[Fields:[map[Name:dev OffsetBits:0 SizeBits:9792] map[Name:port OffsetBits:9792 SizeBits:64] map[Name:tx_enabled OffsetBits:9856 SizeBits:1]]]
|
| 33/2 |
2026/07/29 01:23 |
llm |
expert |
0m
Model:
Tokens:
input: 37798
output: 38
thoughts: 31
Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience:
**Analyzing the `struct device` Layout**
Right, so `dev` starts at offset zero ā that's a given. Now, my immediate thought is to pinpoint the location of the `parent` field within the `struct device` structure. We need to quickly understand the memory layout to navigate device hierarchies effectively. Understanding the offset of `parent` is crucial for traversing the device tree and managing relationships within the kernel. Iām thinking about quickly consulting the header files or the relevant documentation to determine the offset. It's time to refresh my memory on the exact ordering, as it varies depending on the kernel version and configuration, but I can't think of anything else.
|
| 34/2 |
2026/07/29 01:23 |
tool |
codesearch-struct-layout |
0m
Args: map[ContextFile:drivers/tty/serial/serial_base_bus.c Name:device]
Results: map[Fields:[map[Name:kobj OffsetBits:0 SizeBits:512] map[Name:parent OffsetBits:512 SizeBits:64] map[Name:p OffsetBits:576 SizeBits:64] map[Name:init_name OffsetBits:640 SizeBits:64] map[Name:type OffsetBits:704 SizeBits:64] map[Name:bus OffsetBits:768 SizeBits:64] map[Name:driver OffsetBits:832 SizeBits:64] map[Name:platform_data OffsetBits:896 SizeBits:64] map[Name:driver_data OffsetBits:960 SizeBits:64] map[Name:driver_override OffsetBits:1024 SizeBits:576] map[Name:mutex OffsetBits:1600 SizeBits:1088] map[Name:links OffsetBits:2688 SizeBits:448] map[Name:power OffsetBits:3136 SizeBits:4416] map[Name:pm_domain OffsetBits:7552 SizeBits:64] map[Name:msi OffsetBits:7616 SizeBits:128] map[Name:dma_mask OffsetBits:7744 SizeBits:64] map[Name:coherent_dma_mask OffsetBits:7808 SizeBits:64] map[Name:bus_dma_limit OffsetBits:7872 SizeBits:64] map[Name:dma_range_map OffsetBits:7936 SizeBits:64] map[Name:dma_parms OffsetBits:8000 SizeBits:64] map[Name:dma_pools OffsetBits:8064 SizeBits:128] map[Name:dma_mem OffsetBits:8192 SizeBits:64] map[Name:cma_area OffsetBits:8256 SizeBits:64] map[Name:dma_io_tlb_mem OffsetBits:8320 SizeBits:64] map[Name:archdata OffsetBits:8384 SizeBits:0] map[Name:of_node OffsetBits:8384 SizeBits:64] map[Name:fwnode OffsetBits:8448 SizeBits:64] map[Name:numa_node OffsetBits:8512 SizeBits:32] map[Name:devt OffsetBits:8544 SizeBits:32] map[Name:id OffsetBits:8576 SizeBits:32] map[Name:devres_lock OffsetBits:8640 SizeBits:512] map[Name:devres_head OffsetBits:9152 SizeBits:128] map[Name:class OffsetBits:9280 SizeBits:64] map[Name:groups OffsetBits:9344 SizeBits:64] map[Name:release OffsetBits:9408 SizeBits:64] map[Name:iommu_group OffsetBits:9472 SizeBits:64] map[Name:iommu OffsetBits:9536 SizeBits:64] map[Name:physical_location OffsetBits:9600 SizeBits:64] map[Name:removable OffsetBits:9664 SizeBits:32] map[Name:flags OffsetBits:9728 SizeBits:64]]]
|
| 35/2 |
2026/07/29 01:23 |
llm |
expert |
0m
Model:
Tokens:
input: 38913
output: 36
thoughts: 1409
Thoughts: **The Eureka Moment and the Dangling Pointers**
Okay, so the KASAN report is a gold mine! The "parent" field in the `serial_port_device` object is located at an offset of 64 bytes. The KASAN report pinpointed a read exactly 64 bytes into a freed `struct serial_port_device` object. Bingo! This read corresponds directly to `dev.parent`, which is precisely what I was suspecting!
Now, let's trace this back and double-check those error paths to see where the dangling pointers are introduced. First, let's revisit `serial_core_register_port`. I see that if `serial_core_add_one_port` fails, we jump to `err_unregister_port_dev`. The code calls `serial_base_port_device_remove` to clean up. But here's the kicker: `port->port_dev` isn't set to NULL before the jump!
I had to double-check `serial_base_port_device_remove` itself. The code *deletes* the device and frees some associated IDs, but it doesn't touch the `port->port_dev` pointer itself. It takes `port_dev` as an argument and doesn't have access to the original `port` to clear it. That's a classic mistake.
And what about `state->uart_port`? It turns out in `serial_core_add_one_port` there is another dangling pointer. On failure (e.g., in `kzalloc_objs` for `tty_groups`), the function returns `-ENOMEM`, *without* clearing `state->uart_port` or `uport->state`.
So, in the error case, both `state->uart_port` and `port->port_dev` end up pointing to freed memory, leading to a classic use-after-free vulnerability! The global driver state (`drv->state`) and the port structure (`port`) both end up with dangling pointers. When subsequent operations try to interact with them, boom!
Now, let's see if this has been caught before. Let's fire up `git log` and see if anyone has touched these code paths recently. I'll search for commits related to `serial_core_register_port`, `serial_core_add_one_port`, or `serial_base_port_device_remove` in the relevant files. Hopefully, this is a fresh find!
|
| 36/2 |
2026/07/29 01:23 |
tool |
git-log |
1m
Args: map[CodeRegexp:serial_core_register_port Count:10 Since:1 year]
Results:
|
| 37/2 |
2026/07/29 01:25 |
llm |
expert |
0m
Model:
Tokens:
input: 40908
output: 29
thoughts: 18
Thoughts: Okay, here's the summary, rendered as if I am the expert thinking it:
**Considering Search Constraints**
Right, I'm thinking I should loosen the reins on my search. The "Since" parameter, or perhaps even the existing limit in general, might be unduly restricting the scope of the results. It's possible I'm missing valuable information by being too narrow with the search parameters. I need to get a broader view of the subject, so I should try a search without the `Since` constraint, or at least significantly widen the temporal scope or the total limit of items returned. Let's see what more I can uncover.
|
| 38/2 |
2026/07/29 01:25 |
tool |
git-log |
10m
Error:
Args: map[CodeRegexp:serial_core_register_port Count:10]
Results:
|
| 39/2 |
2026/07/29 01:35 |
llm |
expert |
0m
Model:
Tokens:
input: 41125
output: 41
thoughts: 15
Thoughts: Okay, here's the summary, rendered as a first-person thought process, keeping in mind the user is an expert:
**Refining the Scope**
Right, thinking about this... I'm going to want to make this operation as precise as possible, minimizing any unintended consequences. The overall strategy is sound, but a blanket application isn't ideal. I need to be *sure* I'm targeting the correct endpoints. So, to ensure I'm limiting the impact to only the relevant parts of the system, I'm going to start by nailing down the path restriction. Let's make this actionable by specifying `PathPrefix`. That should do the trick.
|
| 40/2 |
2026/07/29 01:35 |
tool |
git-log |
0m
Args: map[CodeRegexp:serial_core_register_port Count:10 PathPrefix:drivers/tty/serial/]
Results: map[Output:84a9582fd203 serial: core: Start managing serial controllers to enable runtime PM
]
|
| 41/2 |
2026/07/29 01:35 |
llm |
expert |
0m
Model:
Tokens:
input: 41359
output: 26
thoughts: 19
Thoughts: Okay, here's my interpretation of what's going on, framed as a thought process I might have:
**Examining Commit 84a9582fd203**
Alright, let's take a look at commit `84a9582fd203`. I want to dive into this one specifically. What's the context here? I'm curious what changes were made. I'm already looking for the usual suspects: bug fixes, feature additions, refactoring, performance improvements, or maybe just some documentation updates. I'll need to use the tools to quickly understand the diff ā what files were modified, and by how much? I'm not just going to read it line-by-line; I'm going to look for the *key* changes, the structural shifts, the core logic updates. I need to understand the intent behind the change. Was there a specific issue being addressed? What was the rationale that led to this commit? And most importantly, is this change well-tested? I hope there are clear test cases associated with it. I'll be looking for any potential side effects or regressions that may have been introduced as well. Depending on the size of the commit, this will be a quick skim or a detailed analysis.
|
| 42/2 |
2026/07/29 01:35 |
tool |
git-show |
0m
Args:
Results: map[Output:commit 84a9582fd203063cd4d301204971ff2cd8327f1a
Author: Tony Lindgren <tony@atomide.com>
Date: Thu May 25 14:30:30 2023 +0300
serial: core: Start managing serial controllers to enable runtime PM
We want to enable runtime PM for serial port device drivers in a generic
way. To do this, we want to have the serial core layer manage the
registered physical serial controller devices.
To manage serial controllers, let's set up a struct bus and struct device
for the serial core controller as suggested by Greg and Jiri. The serial
core controller devices are children of the physical serial port device.
The serial core controller device is needed to support multiple different
kind of ports connected to single physical serial port device.
Let's also set up a struct device for the serial core port. The serial
core port instances are children of the serial core controller device.
With the serial core port device we can now flush pending TX on the
runtime PM resume as suggested by Johan.
Suggested-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Suggested-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Suggested-by: Jiri Slaby <jirislaby@kernel.org>
Suggested-by: Johan Hovold <johan@kernel.org>
Signed-off-by: Tony Lindgren <tony@atomide.com>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Link: https://lore.kernel.org/r/20230525113034.46880-1-tony@atomide.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
diff --git a/drivers/tty/serial/8250/8250_core.c b/drivers/tty/serial/8250/8250_core.c
index 4434c3256a92..914e0e6251bf 100644
--- a/drivers/tty/serial/8250/8250_core.c
+++ b/drivers/tty/serial/8250/8250_core.c
@@ -1039,6 +1039,7 @@ int serial8250_register_8250_port(const struct uart_8250_port *up)
if (uart->port.dev)
uart_remove_one_port(&serial8250_reg, &uart->port);
+ uart->port.ctrl_id = up->port.ctrl_id;
uart->port.iobase = up->port.iobase;
uart->port.membase = up->port.membase;
uart->port.irq = up->port.irq;
diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c
index 1321bb42a074..dfb51a854e77 100644
--- a/drivers/tty/serial/8250/8250_port.c
+++ b/drivers/tty/serial/8250/8250_port.c
@@ -3282,6 +3282,7 @@ void serial8250_init_port(struct uart_8250_port *up)
struct uart_port *port = &up->port;
spin_lock_init(&port->lock);
+ port->ctrl_id = 0;
port->ops = &serial8250_pops;
port->has_sysrq = IS_ENABLED(CONFIG_SERIAL_8250_CONSOLE);
diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile
index 531ec3a19dae..4f7ab4150ec5 100644
--- a/drivers/tty/serial/Makefile
+++ b/drivers/tty/serial/Makefile
@@ -3,7 +3,8 @@
# Makefile for the kernel serial device drivers.
#
-obj-$(CONFIG_SERIAL_CORE) += serial_core.o
+obj-$(CONFIG_SERIAL_CORE) += serial_base.o
+serial_base-y := serial_core.o serial_base_bus.o serial_ctrl.o serial_port.o
obj-$(CONFIG_SERIAL_EARLYCON) += earlycon.o
obj-$(CONFIG_SERIAL_EARLYCON_SEMIHOST) += earlycon-semihost.o
diff --git a/drivers/tty/serial/serial_base.h b/drivers/tty/serial/serial_base.h
new file mode 100644
index 000000000000..9faac0ff6b89
--- /dev/null
+++ b/drivers/tty/serial/serial_base.h
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Serial core related functions, serial port device drivers do not need this.
+ *
+ * Copyright (C) 2023 Texas Instruments Incorporated - https://www.ti.com/
+ * Author: Tony Lindgren <tony@atomide.com>
+ */
+
+#define to_serial_base_ctrl_device(d) container_of((d), struct serial_ctrl_device, dev)
+#define to_serial_base_port_device(d) container_of((d), struct serial_port_device, dev)
+
+struct uart_driver;
+struct uart_port;
+struct device_driver;
+struct device;
+
+struct serial_ctrl_device {
+ struct device dev;
+};
+
+struct serial_port_device {
+ struct device dev;
+ struct uart_port *port;
+};
+
+int serial_base_ctrl_init(void);
+void serial_base_ctrl_exit(void);
+
+int serial_base_port_init(void);
+void serial_base_port_exit(void);
+
+int serial_base_driver_register(struct device_driver *driver);
+void serial_base_driver_unregister(struct device_driver *driver);
+
+struct serial_ctrl_device *serial_base_ctrl_add(struct uart_port *port,
+ struct device *parent);
+struct serial_port_device *serial_base_port_add(struct uart_port *port,
+ struct serial_ctrl_device *parent);
+void serial_base_ctrl_device_remove(struct serial_ctrl_device *ctrl_dev);
+void serial_base_port_device_remove(struct serial_port_device *port_dev);
+
+int serial_ctrl_register_port(struct uart_driver *drv, struct uart_port *port);
+void serial_ctrl_unregister_port(struct uart_driver *drv, struct uart_port *port);
+
+int serial_core_register_port(struct uart_driver *drv, struct uart_port *port);
+void serial_core_unregister_port(struct uart_driver *drv, struct uart_port *port);
diff --git a/drivers/tty/serial/serial_base_bus.c b/drivers/tty/serial/serial_base_bus.c
new file mode 100644
index 000000000000..1b37833b8f66
--- /dev/null
+++ b/drivers/tty/serial/serial_base_bus.c
@@ -0,0 +1,200 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Serial base bus layer for controllers
+ *
+ * Copyright (C) 2023 Texas Instruments Incorporated - https://www.ti.com/
+ * Author: Tony Lindgren <tony@atomide.com>
+ *
+ * The serial core bus manages the serial core controller instances.
+ */
+
+#include <linux/container_of.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/serial_core.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+#include "serial_base.h"
+
+static int serial_base_match(struct device *dev, struct device_driver *drv)
+{
+ int len = strlen(drv->name);
+
+ return !strncmp(dev_name(dev), drv->name, len);
+}
+
+static struct bus_type serial_base_bus_type = {
+ .name = "serial-base",
+ .match = serial_base_match,
+};
+
+int serial_base_driver_register(struct device_driver *driver)
+{
+ driver->bus = &serial_base_bus_type;
+
+ return driver_register(driver);
+}
+
+void serial_base_driver_unregister(struct device_driver *driver)
+{
+ driver_unregister(driver);
+}
+
+static int serial_base_device_init(struct uart_port *port,
+ struct device *dev,
+ struct device *parent_dev,
+ const struct device_type *type,
+ void (*release)(struct device *dev),
+ int id)
+{
+ device_initialize(dev);
+ dev->type = type;
+ dev->parent = parent_dev;
+ dev->bus = &serial_base_bus_type;
+ dev->release = release;
+
+ return dev_set_name(dev, "%s.%s.%d", type->name, dev_name(port->dev), id);
+}
+
+static const struct device_type serial_ctrl_type = {
+ .name = "ctrl",
+};
+
+static void serial_base_ctrl_release(struct device *dev)
+{
+ struct serial_ctrl_device *ctrl_dev = to_serial_base_ctrl_device(dev);
+
+ kfree(ctrl_dev);
+}
+
+void serial_base_ctrl_device_remove(struct serial_ctrl_device *ctrl_dev)
+{
+ if (!ctrl_dev)
+ return;
+
+ device_del(&ctrl_dev->dev);
+}
+
+struct serial_ctrl_device *serial_base_ctrl_add(struct uart_port *port,
+ struct device *parent)
+{
+ struct serial_ctrl_device *ctrl_dev;
+ int err;
+
+ ctrl_dev = kzalloc(sizeof(*ctrl_dev), GFP_KERNEL);
+ if (!ctrl_dev)
+ return ERR_PTR(-ENOMEM);
+
+ err = serial_base_device_init(port, &ctrl_dev->dev,
+ parent, &serial_ctrl_type,
+ serial_base_ctrl_release,
+ port->ctrl_id);
+ if (err)
+ goto err_free_ctrl_dev;
+
+ err = device_add(&ctrl_dev->dev);
+ if (err)
+ goto err_put_device;
+
+ return ctrl_dev;
+
+err_put_device:
+ put_device(&ctrl_dev->dev);
+err_free_ctrl_dev:
+ kfree(ctrl_dev);
+
+ return ERR_PTR(err);
+}
+
+static const struct device_type serial_port_type = {
+ .name = "port",
+};
+
+static void serial_base_port_release(struct device *dev)
+{
+ struct serial_port_device *port_dev = to_serial_base_port_device(dev);
+
+ kfree(port_dev);
+}
+
+struct serial_port_device *serial_base_port_add(struct uart_port *port,
+ struct serial_ctrl_device *ctrl_dev)
+{
+ struct serial_port_device *port_dev;
+ int err;
+
+ port_dev = kzalloc(sizeof(*port_dev), GFP_KERNEL);
+ if (!port_dev)
+ return ERR_PTR(-ENOMEM);
+
+ err = serial_base_device_init(port, &port_dev->dev,
+ &ctrl_dev->dev, &serial_port_type,
+ serial_base_port_release,
+ port->line);
+ if (err)
+ goto err_free_port_dev;
+
+ port_dev->port = port;
+
+ err = device_add(&port_dev->dev);
+ if (err)
+ goto err_put_device;
+
+ return port_dev;
+
+err_put_device:
+ put_device(&port_dev->dev);
+err_free_port_dev:
+ kfree(port_dev);
+
+ return ERR_PTR(err);
+}
+
+void serial_base_port_device_remove(struct serial_port_device *port_dev)
+{
+ if (!port_dev)
+ return;
+
+ device_del(&port_dev->dev);
+}
+
+static int serial_base_init(void)
+{
+ int ret;
+
+ ret = bus_register(&serial_base_bus_type);
+ if (ret)
+ return ret;
+
+ ret = serial_base_ctrl_init();
+ if (ret)
+ goto err_bus_unregister;
+
+ ret = serial_base_port_init();
+ if (ret)
+ goto err_ctrl_exit;
+
+ return 0;
+
+err_ctrl_exit:
+ serial_base_ctrl_exit();
+
+err_bus_unregister:
+ bus_unregister(&serial_base_bus_type);
+
+ return ret;
+}
+module_init(serial_base_init);
+
+static void serial_base_exit(void)
+{
+ serial_base_port_exit();
+ serial_base_ctrl_exit();
+ bus_unregister(&serial_base_bus_type);
+}
+module_exit(serial_base_exit);
+
+MODULE_AUTHOR("Tony Lindgren <tony@atomide.com>");
+MODULE_DESCRIPTION("Serial core bus");
+MODULE_LICENSE("GPL");
diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c
index f856c7fae2fd..29bd5ede0b25 100644
--- a/drivers/tty/serial/serial_core.c
+++ b/drivers/tty/serial/serial_core.c
@@ -17,6 +17,7 @@
#include <linux/gpio/consumer.h>
#include <linux/kernel.h>
#include <linux/of.h>
+#include <linux/pm_runtime.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/device.h>
@@ -31,6 +32,8 @@
#include <linux/irq.h>
#include <linux/uaccess.h>
+#include "serial_base.h"
+
/*
* This is used to lock changes in serial line configuration.
*/
@@ -134,9 +137,30 @@ static void __uart_start(struct tty_struct *tty)
{
struct uart_state *state = tty->driver_data;
struct uart_port *port = state->uart_port;
+ struct serial_port_device *port_dev;
+ int err;
+
+ if (!port || port->flags & UPF_DEAD || uart_tx_stopped(port))
+ return;
+
+ port_dev = port->port_dev;
+
+ /* Increment the runtime PM usage count for the active check below */
+ err = pm_runtime_get(&port_dev->dev);
+ if (err < 0) {
+ pm_runtime_put_noidle(&port_dev->dev);
+ return;
+ }
- if (port && !(port->flags & UPF_DEAD) && !uart_tx_stopped(port))
+ /*
+ * Start TX if enabled, and kick runtime PM. If the device is not
+ * enabled, serial_port_runtime_resume() calls start_tx() again
+ * after enabling the device.
+ */
+ if (pm_runtime_active(&port_dev->dev))
port->ops->start_tx(port);
+ pm_runtime_mark_last_busy(&port_dev->dev);
+ pm_runtime_put_autosuspend(&port_dev->dev);
}
static void uart_start(struct tty_struct *tty)
@@ -3048,7 +3072,7 @@ static const struct attribute_group tty_dev_attr_group = {
};
/**
- * uart_add_one_port - attach a driver-defined port structure
+ * serial_core_add_one_port - attach a driver-defined port structure
* @drv: pointer to the uart low level driver structure for this port
* @uport: uart port structure to use for this port.
*
@@ -3057,8 +3081,9 @@ static const struct attribute_group tty_dev_attr_group = {
* This allows the driver @drv to register its own uart_port structure with the
* core driver. The main purpose is to allow the low level uart drivers to
* expand uart_port, rather than having yet more levels of structures.
+ * Caller must hold port_mutex.
*/
-int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)
+static int serial_core_add_one_port(struct uart_driver *drv, struct uart_port *uport)
{
struct uart_state *state;
struct tty_port *port;
@@ -3072,7 +3097,6 @@ int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)
state = drv->state + uport->line;
port = &state->port;
- mutex_lock(&port_mutex);
mutex_lock(&port->mutex);
if (state->uart_port) {
ret = -EINVAL;
@@ -3137,21 +3161,14 @@ int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)
uport->line);
}
- /*
- * Ensure UPF_DEAD is not set.
- */
- uport->flags &= ~UPF_DEAD;
-
out:
mutex_unlock(&port->mutex);
- mutex_unlock(&port_mutex);
return ret;
}
-EXPORT_SYMBOL(uart_add_one_port);
/**
- * uart_remove_one_port - detach a driver defined port structure
+ * serial_core_remove_one_port - detach a driver defined port structure
* @drv: pointer to the uart low level driver structure for this port
* @uport: uart port structure for this port
*
@@ -3159,20 +3176,16 @@ EXPORT_SYMBOL(uart_add_one_port);
*
* This unhooks (and hangs up) the specified port structure from the core
* driver. No further calls will be made to the low-level code for this port.
+ * Caller must hold port_mutex.
*/
-void uart_remove_one_port(struct uart_driver *drv, struct uart_port *uport)
+static void serial_core_remove_one_port(struct uart_driver *drv,
+ struct uart_port *uport)
{
struct uart_state *state = drv->state + uport->line;
struct tty_port *port = &state->port;
struct uart_port *uart_port;
struct tty_struct *tty;
- mutex_lock(&port_mutex);
-
- /*
- * Mark the port "dead" - this prevents any opens from
- * succeeding while we shut down the port.
- */
mutex_lock(&port->mutex);
uart_port = uart_port_check(state);
if (uart_port != uport)
@@ -3183,7 +3196,6 @@ void uart_remove_one_port(struct uart_driver *drv, struct uart_port *uport)
mutex_unlock(&port->mutex);
goto out;
}
- uport->flags |= UPF_DEAD;
mutex_unlock(&port->mutex);
/*
@@ -3215,6 +3227,7 @@ void uart_remove_one_port(struct uart_driver *drv, struct uart_port *uport)
* Indicate that there isn't a port here anymore.
*/
uport->type = PORT_UNKNOWN;
+ uport->port_dev = NULL;
mutex_lock(&port->mutex);
WARN_ON(atomic_dec_return(&state->refcount) < 0);
@@ -3224,7 +3237,6 @@ void uart_remove_one_port(struct uart_driver *drv, struct uart_port *uport)
out:
mutex_unlock(&port_mutex);
}
-EXPORT_SYMBOL(uart_remove_one_port);
/**
* uart_match_port - are the two ports equivalent?
@@ -3259,6 +3271,144 @@ bool uart_match_port(const struct uart_port *port1,
}
EXPORT_SYMBOL(uart_match_port);
+static struct serial_ctrl_device *
+serial_core_get_ctrl_dev(struct serial_port_device *port_dev)
+{
+ struct device *dev = &port_dev->dev;
+
+ return to_serial_base_ctrl_device(dev->parent);
+}
+
+/*
+ * Find a registered serial core controller device if one exists. Returns
+ * the first device matching the ctrl_id. Caller must hold port_mutex.
+ */
+static struct serial_ctrl_device *serial_core_ctrl_find(struct uart_driver *drv,
+ struct device *phys_dev,
+ int ctrl_id)
+{
+ struct uart_state *state;
+ int i;
+
+ lockdep_assert_held(&port_mutex);
+
+ for (i = 0; i < drv->nr; i++) {
+ state = drv->state + i;
+ if (!state->uart_port || !state->uart_port->port_dev)
+ continue;
+
+ if (state->uart_port->dev == phys_dev &&
+ state->uart_port->ctrl_id == ctrl_id)
+ return serial_core_get_ctrl_dev(state->uart_port->port_dev);
+ }
+
+ return NULL;
+}
+
+static struct serial_ctrl_device *serial_core_ctrl_device_add(struct uart_port *port)
+{
+ return serial_base_ctrl_add(port, port->dev);
+}
+
+static int serial_core_port_device_add(struct serial_ctrl_device *ctrl_dev,
+ struct uart_port *port)
+{
+ struct serial_port_device *port_dev;
+
+ port_dev = serial_base_port_add(port, ctrl_dev);
+ if (IS_ERR(port_dev))
+ return PTR_ERR(port_dev);
+
+ port->port_dev = port_dev;
+
+ return 0;
+}
+
+/*
+ * Initialize a serial core port device, and a controller device if needed.
+ */
+int serial_core_register_port(struct uart_driver *drv, struct uart_port *port)
+{
+ struct serial_ctrl_device *ctrl_dev, *new_ctrl_dev = NULL;
+ int ret;
+
+ mutex_lock(&port_mutex);
+
+ /*
+ * Prevent serial_port_runtime_resume() from trying to use the port
+ * until serial_core_add_one_port() has completed
+ */
+ port->flags |= UPF_DEAD;
+
+ /* Inititalize a serial core controller device if needed */
+ ctrl_dev = serial_core_ctrl_find(drv, port->dev, port->ctrl_id);
+ if (!ctrl_dev) {
+ new_ctrl_dev = serial_core_ctrl_device_add(port);
+ if (!new_ctrl_dev) {
+ ret = -ENODEV;
+ goto err_unlock;
+ }
+ ctrl_dev = new_ctrl_dev;
+ }
+
+ /*
+ * Initialize a serial core port device. Tag the port dead to prevent
+ * serial_port_runtime_resume() trying to do anything until port has
+ * been registered. It gets cleared by serial_core_add_one_port().
+ */
+ ret = serial_core_port_device_add(ctrl_dev, port);
+ if (ret)
+ goto err_unregister_ctrl_dev;
+
+ ret = serial_core_add_one_port(drv, port);
+ if (ret)
+ goto err_unregister_port_dev;
+
+ port->flags &= ~UPF_DEAD;
+
+ mutex_unlock(&port_mutex);
+
+ return 0;
+
+err_unregister_port_dev:
+ serial_base_port_device_remove(port->port_dev);
+
+err_unregister_ctrl_dev:
+ serial_base_ctrl_device_remove(new_ctrl_dev);
+
+err_unlock:
+ mutex_unlock(&port_mutex);
+
+ return ret;
+}
+
+/*
+ * Removes a serial core port device, and the related serial core controller
+ * device if the last instance.
+ */
+void serial_core_unregister_port(struct uart_driver *drv, struct uart_port *port)
+{
+ struct device *phys_dev = port->dev;
+ struct serial_port_device *port_dev = port->port_dev;
+ struct serial_ctrl_device *ctrl_dev = serial_core_get_ctrl_dev(port_dev);
+ int ctrl_id = port->ctrl_id;
+
+ mutex_lock(&port_mutex);
+
+ port->flags |= UPF_DEAD;
+
+ serial_core_remove_one_port(drv, port);
+
+ /* Note that struct uart_port *port is no longer valid at this point */
+ serial_base_port_device_remove(port_dev);
+
+ /* Drop the serial core controller device if no ports are using it */
+ if (!serial_core_ctrl_find(drv, phys_dev, ctrl_id))
+ serial_base_ctrl_device_remove(ctrl_dev);
+
+ mutex_unlock(&port_mutex);
+}
+
/**
* uart_handle_dcd_change - handle a change of carrier detect state
* @uport: uart_port structure for the open port
diff --git a/drivers/tty/serial/serial_ctrl.c b/drivers/tty/serial/serial_ctrl.c
new file mode 100644
index 000000000000..6fcf634425dc
--- /dev/null
+++ b/drivers/tty/serial/serial_ctrl.c
@@ -0,0 +1,68 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Serial core controller driver
+ *
+ * Copyright (C) 2023 Texas Instruments Incorporated - https://www.ti.com/
+ * Author: Tony Lindgren <tony@atomide.com>
+ *
+ * This driver manages the serial core controller struct device instances.
+ * The serial core controller devices are children of the physical serial
+ * port device.
+ */
+
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/serial_core.h>
+#include <linux/spinlock.h>
+
+#include "serial_base.h"
+
+static int serial_ctrl_probe(struct device *dev)
+{
+ pm_runtime_enable(dev);
+
+ return 0;
+}
+
+static int serial_ctrl_remove(struct device *dev)
+{
+ pm_runtime_disable(dev);
+
+ return 0;
+}
+
+/*
+ * Serial core controller device init functions. Note that the physical
+ * serial port device driver may not have completed probe at this point.
+ */
+int serial_ctrl_register_port(struct uart_driver *drv, struct uart_port *port)
+{
+ return serial_core_register_port(drv, port);
+}
+
+void serial_ctrl_unregister_port(struct uart_driver *drv, struct uart_port *port)
+{
+ serial_core_unregister_port(drv, port);
+}
+
+static struct device_driver serial_ctrl_driver = {
+ .name = "ctrl",
+ .suppress_bind_attrs = true,
+ .probe = serial_ctrl_probe,
+ .remove = serial_ctrl_remove,
+};
+
+int serial_base_ctrl_init(void)
+{
+ return serial_base_driver_register(&serial_ctrl_driver);
+}
+
+void serial_base_ctrl_exit(void)
+{
+ serial_base_driver_unregister(&serial_ctrl_driver);
+}
+
+MODULE_AUTHOR("Tony Lindgren <tony@atomide.com>");
+MODULE_DESCRIPTION("Serial core controller driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/tty/serial/serial_port.c b/drivers/tty/serial/serial_port.c
new file mode 100644
index 000000000000..862423237007
--- /dev/null
+++ b/drivers/tty/serial/serial_port.c
@@ -0,0 +1,105 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Serial core port device driver
+ *
+ * Copyright (C) 2023 Texas Instruments Incorporated - https://www.ti.com/
+ * Author: Tony Lindgren <tony@atomide.com>
+ */
+
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/serial_core.h>
+#include <linux/spinlock.h>
+
+#include "serial_base.h"
+
+#define SERIAL_PORT_AUTOSUSPEND_DELAY_MS 500
+
+/* Only considers pending TX for now. Caller must take care of locking */
+static int __serial_port_busy(struct uart_port *port)
+{
+ return !uart_tx_stopped(port) &&
+ uart_circ_chars_pending(&port->state->xmit);
+}
+
+static int serial_port_runtime_resume(struct device *dev)
+{
+ struct serial_port_device *port_dev = to_serial_base_port_device(dev);
+ struct uart_port *port;
+ unsigned long flags;
+
+ port = port_dev->port;
+
+ if (port->flags & UPF_DEAD)
+ goto out;
+
+ /* Flush any pending TX for the port */
+ spin_lock_irqsave(&port->lock, flags);
+ if (__serial_port_busy(port))
+ port->ops->start_tx(port);
+ spin_unlock_irqrestore(&port->lock, flags);
+
+out:
+ pm_runtime_mark_last_busy(dev);
+
+ return 0;
+}
+
+static DEFINE_RUNTIME_DEV_PM_OPS(serial_port_pm,
+ NULL, serial_port_runtime_resume, NULL);
+
+static int serial_port_probe(struct device *dev)
+{
+ pm_runtime_enable(dev);
+ pm_runtime_set_autosuspend_delay(dev, SERIAL_PORT_AUTOSUSPEND_DELAY_MS);
+ pm_runtime_use_autosuspend(dev);
+
+ return 0;
+}
+
+static int serial_port_remove(struct device *dev)
+{
+ pm_runtime_dont_use_autosuspend(dev);
+ pm_runtime_disable(dev);
+
+ return 0;
+}
+
+/*
+ * Serial core port device init functions. Note that the physical serial
+ * port device driver may not have completed probe at this point.
+ */
+int uart_add_one_port(struct uart_driver *drv, struct uart_port *port)
+{
+ return serial_ctrl_register_port(drv, port);
+}
+EXPORT_SYMBOL(uart_add_one_port);
+
+void uart_remove_one_port(struct uart_driver *drv, struct uart_port *port)
+{
+ serial_ctrl_unregister_port(drv, port);
+}
+EXPORT_SYMBOL(uart_remove_one_port);
+
+static struct device_driver serial_port_driver = {
+ .name = "port",
+ .suppress_bind_attrs = true,
+ .probe = serial_port_probe,
+ .remove = serial_port_remove,
+ .pm = pm_ptr(&serial_port_pm),
+};
+
+int serial_base_port_init(void)
+{
+ return serial_base_driver_register(&serial_port_driver);
+}
+
+void serial_base_port_exit(void)
+{
+ serial_base_driver_unregister(&serial_port_driver);
+}
+
+MODULE_AUTHOR("Tony Lindgren <tony@atomide.com>");
+MODULE_DESCRIPTION("Serial controller port driver");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/serial_core.h b/include/linux/serial_core.h
index ddcdb5b8523e..6d58c57acdaa 100644
--- a/include/linux/serial_core.h
+++ b/include/linux/serial_core.h
@@ -28,6 +28,7 @@
struct uart_port;
struct serial_struct;
+struct serial_port_device;
struct device;
struct gpio_desc;
@@ -458,6 +459,7 @@ struct uart_port {
struct serial_rs485 *rs485);
int (*iso7816_config)(struct uart_port *,
struct serial_iso7816 *iso7816);
+ int ctrl_id; /* optional serial core controller id */
unsigned int irq; /* irq number */
unsigned long irqflags; /* irq flags */
unsigned int uartclk; /* base uart clock */
@@ -563,7 +565,8 @@ struct uart_port {
unsigned int minor;
resource_size_t mapbase; /* for ioremap */
resource_size_t mapsize;
- struct device *dev; /* parent device */
+ struct device *dev; /* serial port physical parent device */
+ struct serial_port_device *port_dev; /* serial core port device */
unsigned long sysrq; /* sysrq timeout */
unsigned int sysrq_ch; /* char for sysrq */
]
|
| 43/2 |
2026/07/29 01:35 |
llm |
expert |
0m
Model:
Tokens:
input: 50792
output: 15
thoughts: 831
Thoughts: **Bug Analysis: Serial Port Registration & Unregistration**
Okay, this is a clean and well-structured bug report. Let's break down my understanding: I see a classic use-after-free scenario stemming from an error during serial port registration. The core issue lies within the interaction between `serial_core_register_port()`, `serial_core_add_one_port()`, and the unregistration process.
First, I observe that the port device (`serial_port_device`) is allocated early in `serial_core_register_port()` via `serial_base_port_add()`, and the `port->port_dev` pointer is correctly set. Then, `serial_core_add_one_port()` links the port to the driver state, setting up `state->uart_port` and `uport->state`. This is where the vulnerability arises. If `serial_core_add_one_port()` fails (perhaps due to a memory allocation problem), it returns an error, specifically `-ENOMEM`.
Crucially, the problem lies in the fact that `serial_core_add_one_port()` fails to clean up the links it establishes *before* returning an error. `state->uart_port` and `uport->state` remain set, pointing to the intended, but now, ephemeral port and state. The code then jumps to `err_unregister_port_dev` within `serial_core_register_port()`. There the `serial_port_device` is correctly cleaned up by calling `serial_base_port_device_remove()`. This removes the device and releases the `serial_port_device` structure.
However, and this is the crux of the issue, neither `port->port_dev`, `state->uart_port` nor `uport->state` are explicitly cleared on this failure path! They become dangling pointers, pointing to a freed memory location.
Later, during port unregistration (e.g., driver unbinding), `serial_core_unregister_port()` is called. It attempts to determine if other ports are still using the controller. Because the dangling pointer `state->uart_port` still points to the (freed) `serial_port_device` structure and the `port_dev` member within that structure is non-null, `serial_core_ctrl_find()` skips the `continue` and proceeds with the problematic line of code. It attempts to access `state->uart_port->port_dev`, and, most critically, it dereferences `port_dev->dev.parent`. This results in a KASAN slab-use-after-free read.
The root cause here is crystal clear: incomplete cleanup on error in `serial_core_register_port()` and `serial_core_add_one_port()`. The fix is equally obvious. The dangling pointers need to be set to NULL on the error path to avoid the use-after-free condition. It's a relatively straightforward and completely actionable bug. I'd say this is ready to `set-results` with `Actionable: true`.
|
| 44/2 |
2026/07/29 01:36 |
tool |
set-results |
0m
Args:
Results:
|
| 45/2 |
2026/07/29 01:36 |
llm |
expert |
0m
Model:
Tokens:
input: 52314
output: 1037
thoughts: 41
Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience:
**Final Analysis: Bug Report - Time to Lay it Out**
Alright, the data's in, the analyses are complete. Time to craft a final, watertight explanation for this bug report. My focus now is on creating a definitive, professional reply that leaves no room for ambiguity. This isn't just about saying "yes, it's a bug"; it's about *demonstrating* that it's self-consistent, thoroughly actionable, and deserving of immediate attention. I'm going to meticulously lay out the precise sequence of events. No vague generalities. Every step will be dissected, from initial trigger to the observed failure, leaving no doubt about the path this took. Crucially, I'm going to pinpoint the root cause with as much precision as possible. I'll make sure to use all the relevant context, including any external factors. My goal is to make it easy to replicate (if possible) and straightforward to debug. This is more than a report; it's a complete diagnostic breakdown.
|
| 46/1 |
2026/07/29 01:36 |
action |
format-explanation |
0m
Results: map[Explanation:An analysis of the KASAN use-after-free bug report reveals that it is **fully
consistent, self-contained, and highly actionable**.
### Root Cause Analysis
The bug is caused by incomplete error-handling cleanup in the serial core port
registration path (`serial_core_register_port`), which leaves dangling pointers
in the global driver state (`state->uart_port`) and the port structure
(`port->port_dev`).
Here is the step-by-step sequence of events that leads to the use-after-free:
1. **Port Registration Attempt:**
When a serial port is registered (e.g., during `serial8250_unregister_port`
re-adding a port via `uart_add_one_port`), `serial_core_register_port()` is
called.
2. **Device Allocation:**
`serial_core_register_port()` calls `serial_core_port_device_add()`, which
allocates a `struct serial_port_device` via `serial_base_port_add()` and
assigns it to `port->port_dev`.
3. **Linking to Driver State:**
`serial_core_register_port()` then calls `serial_core_add_one_port()`, which
links the port to the driver's state:
```c
state->uart_port = uport;
uport->state = state;
```
4. **Fault Injection / Allocation Failure:**
Inside `serial_core_add_one_port()`, a memory allocation fails (specifically
`kzalloc_objs` for `tty_groups` at line 3109, triggered by the `failslab`
fault injection). The function returns `-ENOMEM`.
* **Crucially**, `serial_core_add_one_port()` does **not** clear
`state->uart_port` or `uport->state` on this failure path.
5. **Incomplete Cleanup on Error Path:**
Upon receiving the error, `serial_core_register_port()` jumps to the
`err_unregister_port_dev` error label:
```c
err_unregister_port_dev:
serial_base_port_device_remove(port->port_dev);
```
`serial_base_port_device_remove()` deletes the device and calls
`put_device(&port_dev->dev)`, which triggers `serial_base_port_release()` and
**frees** the `serial_port_device` structure.
* **Crucially**, `serial_core_register_port()` does **not** clear
`port->port_dev` or `state->uart_port` on this failure path. They are left as
dangling pointers pointing to the freed memory.
6. **Subsequent Dereference (Use-After-Free):**
Later, when a port is unregistered (e.g., during driver unbinding/removal),
`serial_core_unregister_port()` is called. It attempts to find if any other
ports are using the controller via `serial_core_ctrl_find()`:
```c
for (i = 0; i < drv->nr; i++) {
state = drv->state + i;
if (!state->uart_port || !state->uart_port->port_dev)
continue;
...
return serial_core_get_ctrl_dev(state->uart_port->port_dev);
}
```
Since `state->uart_port` and `state->uart_port->port_dev` were never cleared
during the previous registration failure, they are both non-NULL. The code
attempts to dereference the dangling `port_dev` pointer in
`serial_core_get_ctrl_dev()`:
```c
static struct serial_ctrl_device *
serial_core_get_ctrl_dev(struct serial_port_device *port_dev)
{
struct device *dev = &port_dev->dev;
return to_serial_base_ctrl_device(dev->parent);
}
```
This reads `dev->parent` (located at offset **64 bytes** inside the freed
`struct serial_port_device` structure), triggering the KASAN
slab-use-after-free read of size 8.
---
### Conclusion
The report is **actionable**. The root cause is fully understood, and the fix
involves ensuring that `state->uart_port`, `port->state`, and `port->port_dev`
are properly set to `NULL` on the error paths of `serial_core_add_one_port()`
and `serial_core_register_port()`.]
|