| 0/0 |
2026/07/28 00:49 |
flow |
assessment-security |
43m
Results: map[DenialOfService:true Explanation:**Bug Analysis:**
The bug is a Use-After-Free (UAF) in the 8250 serial driver subsystem,
specifically within `serial_core_unregister_port()`.
**Root Cause:**
When a serial port is registered via `uart_add_one_port()` (which calls
`serial_core_register_port()`), a `struct serial_port_device` is allocated and
assigned to `port->port_dev`. If the subsequent port registration steps fail
(e.g., due to a memory allocation failure in `serial_core_add_one_port()`), the
error handling path correctly frees the `port_dev` object. However, it fails to
clear the `port->port_dev` pointer, leaving it dangling.
In the 8250 driver, `serial8250_unregister_port()` is called when a device is
unbound or hot-unplugged. This function unregisters the port and then attempts
to re-register it as a fallback ISA port by calling `uart_add_one_port()`.
Crucially, it ignores the return value of `uart_add_one_port()`. If this
re-registration fails, the port remains in the global `serial8250_ports` array
with the dangling `port_dev` pointer.
If the device is unbound again, or if another device is hot-plugged and reuses
the same port slot, `uart_remove_one_port()` is called. This function eventually
calls `serial_core_unregister_port()`, which dereferences the dangling
`port->port_dev` pointer to retrieve the controller device, triggering the UAF.
**Exploitability:**
The bug is exploitable. The freed object is a `struct serial_port_device`
allocated from the `kmalloc-2k` cache. This structure contains an embedded
`struct device`. If an attacker can spray the heap and control the contents of
this freed 2048-byte object, they can control the `struct device` fields. When
`device_del()` and `put_device()` are called on this controlled object, the
attacker can achieve arbitrary code execution by hijacking function pointers
associated with the device (such as `dev->release`, `dev->bus`, or `dev->class`
callbacks).
**Accessibility:**
Despite being exploitable, the bug is **not accessible to unprivileged users**.
Triggering the bug requires one of the following actions:
1. Unbinding the serial driver via sysfs
(`/sys/bus/platform/drivers/serial8250/unbind`), which is restricted to `root`
(mode `0200`).
2. Hot-plugging and hot-unplugging a physical PCI, PNP, or LPSS serial device.
Neither of these actions can be performed by an unprivileged user, nor can they
be performed from within an unprivileged user namespace (sysfs `unbind` files
are not writable, and PCI hotplug is restricted).
The bug could theoretically be triggered via a malicious peripheral (e.g., a
Thunderbolt/PCIe device that rapidly hot-plugs and hot-unplugs itself), provided
the attacker can also induce a memory allocation failure at the exact right
moment during the driver's registration phase. Exploitable:true FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:true RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
|
| 1/1 |
2026/07/28 00:49 |
action |
syz-repro-to-c-repro |
0m
Results:
|
| 2/1 |
2026/07/28 00:49 |
action |
kernel-checkouter |
0m
Results: map[KernelSrc:/app/workdir/cache/src/c41e592f23836ae254abe21615b950b8294f138e]
|
| 3/1 |
2026/07/28 00:49 |
action |
kernel-builder |
21m
Results: map[KernelObj:/app/workdir/cache/build/41ef09eb338654268308dfd88c84113cb5473b2d]
|
| 4/1 |
2026/07/28 01:10 |
action |
codesearch-prepare |
17m
Results: map[Index:codesearch-index]
|
| 5/1 |
2026/07/28 01:28 |
agent |
expert |
5m
Model:
Results: map[DenialOfService:true Exploitable:true FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:true RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
Instruction: You are an experienced Linux kernel security engineer. Your task is to analyze given kernel bug report
and determine its security impact based on the following dimensions.
Use the provided tools to examine the source code, check for capability checks (e.g., capable(), ns_capable()),
and understand the nature of the bug. Analyze the given kernel build and configuration.
You can check the kernel config by grepping ".config" file; you can check kernel cmdline by grepping
".config" file for "CONFIG_CMDLINE=". Assume sysctl parameters have default values.
But analyze for the corresponding production build w/o debugging tools enabled (like KASAN, KMSAN, UBSAN).
Try different strategies when analyzing the bug:
- think of ways in which the vulnerable code is unreachable
- or the other way around: try to come up with different ideas of how an unprivileged user can reach the bug
If still unsure err on the side of the bug being non-exploitable/not-accessible.
In the final reply, provide a reasoning for your assessment.
Analysis dimensions:
* Exploitable:
Determine if the bug can result in memory corruption, elevated privileges, or an information leak.
Memory safety issues are almost always exploitable (KASAN or UBSAN reports for use-after-free, out-of-bounds;
refcounting issues, corrupted lists, etc). When kernel is crashing on a completely wild pointer access
(e.g. user-space address, or non-canonical address, but not on NULL or address corresponding to KASAN shadow
for NULL address), including both data accesses and control transfers, that also usually implies possibility
of exploitation. Such reports usually say "unable to handle kernel paging request".
Uses of uninitialized values detected by KMSAN may be exploitable b/c attacker frequently can affect uninit
values with spraying techniques. However, for these exploitability depends on how exactly the uninit value
is used in the code, and what it affects.
Information leaks are exploitable on their own and should be classified as such. A bug that copies kernel
memory contents to userspace (e.g. an out-of-bounds read whose result is returned to the caller, or
uninitialized stack/heap bytes written to a user buffer) is exploitable: it can reveal kernel pointer
values and defeat KASLR, expose sensitive data such as cryptographic keys or other processes' memory, and
serves as a necessary building block in most modern kernel privilege-escalation exploit chains. Do not classify
an information leak as non-exploitable solely because it does not directly cause a memory write or control-flow
hijack; the leak itself is the exploit primitive.
Think of what happens after the bug is triggered. Some bugs cause kernel panic and halt execution,
they are harder to exploit. For example, BUG reports halts the kernel. However, WARNING reports don't halt
execution in production builds. Debug bug detection tools (like KASAN, KMSAN, KCSAN, UBSAN) are also not enabled
in production builds, so attacker can freely exploit these bugs w/o being detected by these tools.
If you see an integer overflow, think how the overflowed value used later (if it's used as allocation size,
or an array index). If you see an out-of-bounds read, think if it's followed by an out-of-bounds write as well.
Some KCSAN data-races may be exploitable by skilled attackers as well. Think what data structures got corrupted
as the result of data races and how. However, note that kernel has lots of "benign" data races that don't lead
to any runtime misbehavior at all.
* Denial Of Service:
Determine if the bug can result in denial-of-service. Most bugs can, since they cause system crash,
hangs, deadlocks, or resource leaks. This is mostly applicable to WARNING bugs that won't cause system crash
in production. For these think what will be consequences of the violation of the kernel assumptions flagged
by the WARNING. In some cases the unexpected condition is also properly handled by the normal control flow
(e.g. with "if (WARN_ON(...))"), these won't cause denial-of-service. If the condition is not handled,
then it may or may not cause denial-of-service.
* Accessible From Unprivileged Processes:
Determine if the bug can be reached from a typical (non-root) user process that does NOT have any special capabilities
(like CAP_SYS_ADMIN, CAP_NET_ADMIN, CAP_NET_RAW, CAP_PERFMON) or access to device nodes restricted to root.
Assume that unprivileged_bpf_disabled=1, that is eBPF loading is not accessible. However, cBPF (classical BPF)
is still accessible to non-root processes.
Assume that user namespaces are not accessible, that is, the process cannot get the mentioned capabilities even
within a new user namespace (checked by ns_capable() function in the kernel sources).
* Accessible From User Namespaces:
Determine if the bug can be reached within a user-namespace where the process has all capabilities
(including CAP_SYS_ADMIN, CAP_NET_ADMIN, CAP_NET_RAW, CAP_PERFMON). Such capabilities are checked with ns_capable()
function in the kernel sources.
* VM Guest Trigger:
Determine if the bug can be triggered from the context of a typical KVM guest (e.g., set up by a QEMU VMM).
Consider accesses to standard Linux host paravirtualized features (virtio-blk, virtio-net, etc.),
and handling of VM exits in the KVM code.
* VM Host Trigger in The Confidential Computing Context:
Determine if the bug can be triggered in a confidential computing guest kernel from the context of a KVM host.
Consider access to standard Linux guest paravirtualized features (virtio-blk, virtio-net, etc.).
* Ethernet Network Trigger:
Determine if the bug can be triggered by processing ingress network Ethernet traffic, either directly (network stack)
or via drivers exposed to network data.
* Other Remote Trigger:
Determine if the bug can be triggered by processing remote traffic other than Ethernet (Wifi, Bluetooth, NFC, etc).
* Peripheral Trigger:
Determine if the bug can be triggered via an untrusted peripheral device that can be physically plugged
into a system, such as a USB device or a niche hardware driver handling external hardware inputs.
This is particularly important for mobile and desktop environments where users can plug in unknown devices.
* Malicious Filesystem Trigger:
Determine if the bug can be triggered by the kernel mounting and parsing a malicious filesystem image.
This is highly critical for Desktop and Mobile environments where external media or downloaded images
might be auto-mounted.
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 kernel 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: **Bug Analysis:**
The bug is a Use-After-Free (UAF) in the 8250 serial driver subsystem, specifically within `serial_core_unregister_port()`.
**Root Cause:**
When a serial port is registered via `uart_add_one_port()` (which calls `serial_core_register_port()`), a `struct serial_port_device` is allocated and assigned to `port->port_dev`. If the subsequent port registration steps fail (e.g., due to a memory allocation failure in `serial_core_add_one_port()`), the error handling path correctly frees the `port_dev` object. However, it fails to clear the `port->port_dev` pointer, leaving it dangling.
In the 8250 driver, `serial8250_unregister_port()` is called when a device is unbound or hot-unplugged. This function unregisters the port and then attempts to re-register it as a fallback ISA port by calling `uart_add_one_port()`. Crucially, it ignores the return value of `uart_add_one_port()`. If this re-registration fails, the port remains in the global `serial8250_ports` array with the dangling `port_dev` pointer.
If the device is unbound again, or if another device is hot-plugged and reuses the same port slot, `uart_remove_one_port()` is called. This function eventually calls `serial_core_unregister_port()`, which dereferences the dangling `port->port_dev` pointer to retrieve the controller device, triggering the UAF.
**Exploitability:**
The bug is exploitable. The freed object is a `struct serial_port_device` allocated from the `kmalloc-2k` cache. This structure contains an embedded `struct device`. If an attacker can spray the heap and control the contents of this freed 2048-byte object, they can control the `struct device` fields. When `device_del()` and `put_device()` are called on this controlled object, the attacker can achieve arbitrary code execution by hijacking function pointers associated with the device (such as `dev->release`, `dev->bus`, or `dev->class` callbacks).
**Accessibility:**
Despite being exploitable, the bug is **not accessible to unprivileged users**. Triggering the bug requires one of the following actions:
1. Unbinding the serial driver via sysfs (`/sys/bus/platform/drivers/serial8250/unbind`), which is restricted to `root` (mode `0200`).
2. Hot-plugging and hot-unplugging a physical PCI, PNP, or LPSS serial device.
Neither of these actions can be performed by an unprivileged user, nor can they be performed from within an unprivileged user namespace (sysfs `unbind` files are not writable, and PCI hotplug is restricted).
The bug could theoretically be triggered via a malicious peripheral (e.g., a Thunderbolt/PCIe device that rapidly hot-plugs and hot-unplugs itself), provided the attacker can also induce a memory allocation failure at the exact right moment during the driver's registration phase.
|
| 6/2 |
2026/07/28 01:28 |
llm |
expert |
0m
Model:
Tokens:
input: 12189
output: 33
thoughts: 509
Thoughts: **Analyzing a KASAN Slab-Use-After-Free Report**
Okay, so I've got a KASAN report, specifically a slab-use-after-free in `serial_core_get_ctrl_dev`. The access is a read, 8 bytes at `ffff88801f578040`. The object in question was allocated as a `kmalloc-2k` object within `serial_base_port_add` and freed, it seems, in `serial_core_register_port` – probably on an error path, or a similar situation. Let's delve into the allocation and free stack traces.
The allocation stack looks like: `serial_base_port_add` then `serial_core_port_device_add` then `serial_core_register_port` and, lastly, `serial8250_remove`. However, the freeing is a bit odd as the trace shows: `serial_core_register_port` called from `serial8250_unregister_port`. That can't be right; `serial8250_unregister_port` should be calling a de-register function, not a register function. So, is `serial8250_unregister_port` calling `serial_core_register_port`? It looks that way from the trace, but that doesn't feel right. My intuition says `serial8250_unregister_port` should call something like `uart_remove_one_port` which, perhaps, leads to `serial_core_unregister_port`.
Hmm, this is a bit confusing. Let me check the code in `drivers/tty/serial/8250/8250_core.c:892` to see what is really happening in `serial8250_unregister_port`.
Wait a minute, the trace shows a fault injection path, in `serial_core_register_port`. It looks like `serial_core_register_port` is being called from `serial_core_add_one_port` and then `serial8250_unregister_port`? No, that can't be correct. Let's look at the source again. I think `serial8250_unregister_port` calls `serial_core_unregister_port`. Let me double-check the code. I need to make sure I am tracing this properly.
|
| 7/2 |
2026/07/28 01:28 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile: 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]
|
| 8/2 |
2026/07/28 01:28 |
llm |
expert |
0m
Model:
Tokens:
input: 13833
output: 31
thoughts: 160
Thoughts: **Analyzing the Serial Port Unregistration/Registration Cycle**
Okay, so here's what's happening. I'm looking at `serial8250_unregister_port`. The call chain is pretty clear: it first calls `uart_remove_one_port` to, well, remove the port. Makes sense. Then, the kicker – if `serial8250_isa_devs` is true, it immediately calls `uart_add_one_port` to *re-add* the very port it just removed! Why are we doing this? I need to understand this better.
`uart_add_one_port` isn't doing the heavy lifting, though. It's calling `serial_core_add_one_port`, which in turn calls `serial_core_register_port`. Ah, here we go! `serial_core_register_port` is where the core allocation happens, specifically within the `serial_core_port_device_add` -> `serial_base_port_add` sequence.
Now, the trace indicates that something's failing after the allocation – specifically, a fault injection. This is critical because the object gets freed after the failure. So, the allocation is happening, and *then* the cleanup.
I need to dive deeper into `serial_core_register_port` to see why this allocation is happening in the first place, and what's going on during the failure injection. What's the context? What exactly are the fault injection points? Are we expecting this re-registration to succeed or fail? This whole thing seems a bit... convoluted. Time to analyze.
|
| 9/2 |
2026/07/28 01:28 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile: Name:serial_core_register_port]
Results: map[SourceCode: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:
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: }
SourceFile:drivers/tty/serial/serial_core.c]
|
| 10/2 |
2026/07/28 01:28 |
llm |
expert |
0m
Model:
Tokens:
input: 15180
output: 33
thoughts: 145
Thoughts: Okay, let's break this down.
**Understanding the Port Registration Flow**
So, `serial_core_register_port` is the starting point. It's calling `serial_core_port_device_add` – right, that's where the initial allocation happens, presumably for the port's device structure. My primary concern here is how this function handles error scenarios. If `serial_core_add_one_port` fails, and it *does* fail under the simulated fault conditions, the code jumps to `err_unregister_port_dev`. This path calls `serial_base_port_device_remove(port->port_dev)`. I need to verify that this correctly cleans up the `port->port_dev` in the failure case.
Then, after that cleanup, it also calls `serial_base_ctrl_device_remove(new_ctrl_dev)`. The critical thing here is to verify the order of operations, and what `new_ctrl_dev` refers to. Make sure no resources are leaked.
Wait a second... if `serial_core_port_device_add` *succeeds*, it *sets* `port->port_dev`. That means the `port->port_dev` is only valid after the successful call to `serial_core_port_device_add`. This is good. It means we don't need to check for a null pointer when unregistering. Let me go back and look at the body of the function `serial_core_port_device_add` to be completely certain of the memory management of that function.
|
| 11/2 |
2026/07/28 01:28 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile: Name:serial_core_port_device_add]
Results: map[SourceCode: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: }
SourceFile:drivers/tty/serial/serial_core.c]
|
| 12/2 |
2026/07/28 01:28 |
llm |
expert |
0m
Model:
Tokens:
input: 15922
output: 33
thoughts: 58
Thoughts: Okay, here's my take on those thoughts, framed as my own internal analysis of the situation:
**Potential Memory Leak in Serial Port Device Removal**
Right, so I'm seeing this code, and the key part is `port->port_dev`. I can see that this `port->port_dev` pointer *is* being assigned a valid device somewhere. Then, later, the `serial_base_port_device_remove` function is called, and it’s supposed to clean things up – presumably freeing that device. The problem is I just noticed, after the `serial_base_port_device_remove` routine completes, `port->port_dev` is *not* being explicitly cleared to NULL!
This is a red flag. If `serial_base_port_device_remove` frees the memory pointed to by `port->port_dev` but doesn't set the pointer itself to `NULL`, then we've got a classic use-after-free scenario waiting to happen. It means a subsequent access to `port->port_dev` could either result in a crash, or worse, use garbage data. I need to take a closer look at the `serial_base_port_device_remove` function to confirm my suspicion and see what's actually being freed. It's time to check what's going on in that routine.
|
| 13/2 |
2026/07/28 01:28 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile: 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]
|
| 14/2 |
2026/07/28 01:28 |
llm |
expert |
0m
Model:
Tokens:
input: 15689
output: 31
thoughts: 0
|
| 15/2 |
2026/07/28 01:28 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile: Name:serial_core_unregister_port]
Results: map[SourceCode: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: }
SourceFile:drivers/tty/serial/serial_core.c]
|
| 16/2 |
2026/07/28 01:28 |
llm |
expert |
0m
Model:
Tokens:
input: 17016
output: 31
thoughts: 254
Thoughts: **Dissecting the UART Unregistration Flow**
Okay, so I'm looking at this unregistration sequence. It seems like `serial_core_unregister_port` is calling `serial_core_get_ctrl_dev(port_dev)`. Standard operating procedure, I suppose, trying to grab the control device associated with this port. Now, the bug report... right, let's refresh my memory. It looks like `serial8250_unregister_port` is, in fact, calling `serial_core_unregister_port`.
Let's quickly double-check that trace from the bug report just to be absolutely sure I'm following the correct path. Okay, yes, there it is: `serial_core_get_ctrl_dev` is inlined, then `serial_core_ctrl_find`, and then `serial_core_unregister_port`. That's followed by `serial8250_unregister_port`. The stack trace confirms the call chain.
Wait a second... the bug report shows `serial8250_unregister_port` calling `uart_remove_one_port` at line 883. That's a critical point to check now. I need to understand the interaction between `serial8250_unregister_port` and `uart_remove_one_port` now. Let's dig into the implementation of `uart_remove_one_port`.
|
| 17/2 |
2026/07/28 01:28 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile: Name:uart_remove_one_port]
Results: map[SourceCode: 147: void uart_remove_one_port(struct uart_driver *drv, struct uart_port *port)
148: {
149: serial_ctrl_unregister_port(drv, port);
150: }
SourceFile:drivers/tty/serial/serial_port.c]
|
| 18/2 |
2026/07/28 01:28 |
llm |
expert |
0m
Model:
Tokens:
input: 17706
output: 31
thoughts: 18
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Unpacking That Function Call**
Alright, that function call… `serial_ctrl_unregister_port`. My initial reaction is to flag it for review. We're dealing with port unregistration here, which is a key process and, depending on the context, could be the origin of a host of issues, from dangling pointers to resource leaks. Let's see what's going on within that routine. Time to dig in and understand its role in this specific situation. I want to confirm that all resources associated with the serial port are being properly released.
|
| 19/2 |
2026/07/28 01:28 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile: Name:serial_ctrl_unregister_port]
Results: map[SourceCode: 44: void serial_ctrl_unregister_port(struct uart_driver *drv, struct uart_port *port)
45: {
46: serial_core_unregister_port(drv, port);
47: }
SourceFile:drivers/tty/serial/serial_ctrl.c]
|
| 20/2 |
2026/07/28 01:28 |
llm |
expert |
0m
Model:
Tokens:
input: 17984
output: 33
thoughts: 586
Thoughts: **Analyzing the UART Port Unregistration Process**
Okay, let's break this down. My initial observation is that `uart_remove_one_port` eventually leads to `serial_core_unregister_port` through `serial_ctrl_unregister_port`. So the expected flow is `serial8250_unregister_port` calls `uart_remove_one_port` and then on to `serial_core_unregister_port`. This then calls `serial_core_remove_one_port`, and ultimately invokes `serial_base_port_device_remove` with the `port_dev` in question.
However, I'm spotting a potential issue. If `serial_core_register_port` failed at some point, it *already* called `serial_base_port_device_remove` on `port->port_dev`. The concern is that `port->port_dev` wasn't *cleared* after that call. This means a stale pointer could exist. So, when `serial8250_unregister_port` is later called, and again calls the `uart_remove_one_port` path, we're likely to encounter a problem.
Now, why is `serial8250_unregister_port` called in the first place? Let me check the trace. Okay, the trace shows a clear path: `serial_core_add_one_port` -> `serial_core_register_port` -> `serial8250_unregister_port` -> `serial8250_remove`. Ah, got it. `serial8250_unregister_port` calls `uart_add_one_port` (which itself calls `serial_core_register_port`)!
Wait, this is interesting. `serial8250_unregister_port` *first* calls `uart_remove_one_port`. Then, after manipulating the `uart->port` structure, it calls `uart_add_one_port`. If `uart_add_one_port` fails, for instance due to a fault injection, `serial_core_register_port` *does* free `port->port_dev`. The problem is that it then leaves `port->port_dev` pointing to this freed memory.
So, `serial_core_register_port` is called. It *fails*. The question is, does `serial8250_unregister_port` properly handle this error from `uart_add_one_port`? That is where the bug lies. It likely does not.
|
| 21/2 |
2026/07/28 01:28 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile: 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]
|
| 22/2 |
2026/07/28 01:28 |
llm |
expert |
0m
Model:
Tokens:
input: 19731
output: 31
thoughts: 391
Thoughts: **Analyzing a Potential Use-After-Free Vulnerability**
Okay, so I'm looking at this code and something's not quite right. `uart_add_one_port` returns an `int`, a status code, but `serial8250_unregister_port` just blows right past it, ignoring the return value. That's a red flag. If `uart_add_one_port` fails, `port->port_dev` ends up as a dangling pointer – yikes! Now I need to trace what happens next, where this potential UAF manifests.
The second trace shows the use-after-free, and it's happening inside `serial_core_unregister_port`. Diving deeper, I see it's calling `serial8250_unregister_port` at `drivers/tty/serial/8250/8250_core.c:883`. Ah, there it is! But wait a second, line 883 is `uart_remove_one_port(&serial8250_reg, &uart->port);` within `serial8250_unregister_port`! This means `serial8250_unregister_port` is being called *again*!
Why is this happening? Something is re-triggering this function. I see that `serial8250_remove` is called, and that's the culprit. And, sure enough, I see that `serial8250_remove` calls `serial8250_unregister_port`. Okay, I need to see the implementation of `serial8250_remove`. Let's dig into that and figure out why it's re-triggering this unregistering process.
|
| 23/2 |
2026/07/28 01:29 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile: Name:serial8250_remove]
Results: map[SourceCode: 230: /*
231: * Remove serial ports registered against a platform device.
232: */
233: static void serial8250_remove(struct platform_device *dev)
234: {
235: int i;
236:
237: for (i = 0; i < nr_uarts; i++) {
238: struct uart_8250_port *up = serial8250_get_port(i);
239:
240: if (up->port.dev == &dev->dev)
241: serial8250_unregister_port(i);
242: }
243: }
SourceFile:drivers/tty/serial/8250/8250_platform.c]
|
| 24/2 |
2026/07/28 01:29 |
llm |
expert |
0m
Model:
Tokens:
input: 20772
output: 27
thoughts: 423
Thoughts: **Analyzing a Serial Port Unregistration/Registration Cycle**
Okay, so I'm digging into this `serial8250_remove` function, and it looks like it's the key to unregistering serial ports. I see it iterates through all the ports and unregisters them one by one. But here's where it gets interesting. I'm following the flow through `serial8250_unregister_port` which, if I'm reading this correctly, calls `uart_remove_one_port`. This function appears to free `port_dev` if valid, and then calls `uart_add_one_port`. Okay, that seems straightforward enough.
However, a failure in `uart_add_one_port` leaves a dangling pointer in `port->port_dev`. Now, the really concerning part is the possibility of `serial8250_unregister_port` getting called *again* on the *same* port. Why would that even happen? Tracing back, both times, it seems the calls originate from `serial8250_remove`. But there has to be more to it!
Ah, I've got it. `serial8250_remove` iterates through all ports, from `0` to `nr_uarts`, and it calls `serial8250_unregister_port(i)` for each port matching a specific device `up->port.dev == &dev->dev`. Okay, it makes sense if it's called for `i=0` it unregisters it, and then re-registers it with `uart->port.dev = &serial8250_isa_devs->dev`. So if `dev` is `serial8250_isa_devs`, then `up->port.dev` becomes `&serial8250_isa_devs->dev` again. That makes sense now. So if `serial8250_remove` is called for `serial8250_isa_devs`, it will unregister the port, and then re-register it with `dev = &serial8250_isa_devs->dev`.
Hang on a second. I remember `serial8250_remove` is called from `device_remove`, which in turn, is called by `unbind_store`. That means this all happens when the user unbinds the driver from sysfs, which is what likely explains what's happening. I'd better double-check `unbind_store`. This is starting to look like a subtle race condition in the unbinding process.
|
| 25/2 |
2026/07/28 01:29 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile: Name:unbind_store]
Results: map[SourceCode: 234: /* Manually detach a device from its associated driver. */
235: static ssize_t unbind_store(struct device_driver *drv, const char *buf,
236: size_t count)
237: {
238: const struct bus_type *bus = bus_get(drv->bus);
239: struct device *dev;
240: int err = -ENODEV;
241:
242: dev = bus_find_device_by_name(bus, NULL, buf);
243: if (dev && dev->driver == drv) {
244: device_driver_detach(dev);
245: err = count;
246: }
247: put_device(dev);
248: bus_put(bus);
249: return err;
250: }
SourceFile:drivers/base/bus.c]
|
| 26/2 |
2026/07/28 01:29 |
llm |
expert |
0m
Model:
Tokens:
input: 22056
output: 31
thoughts: 677
Thoughts: **Analyzing the 8250 Serial Driver Unbind Sequence**
Okay, so I see the user is writing to `/sys/bus/platform/drivers/serial8250/unbind`. This triggers `serial8250_remove`, which in turn calls `serial8250_unregister_port`. My initial thought is, "Standard unregistration, nothing unusual here." But then I see that `serial8250_unregister_port` unregisters the port and *then* re-registers it using `serial8250_isa_devs->dev`. That's a bit odd. If the device being unbound is indeed `serial8250_isa_devs`, then `serial8250_remove` is effectively unbinding it, but then immediately re-registering it with itself!
Okay, if it re-registers, then `uart_add_one_port` is called. Ah, there is the problem, if `uart_add_one_port` fails, then `port->port_dev` gets left dangling. The user *can* write `unbind` again? Because the device is still there, or maybe they are unbinding another device? That's a potential vulnerability.
Now, let's look at these traces. The first trace shows a failing `uart_add_one_port`. The second trace uses the dangling `port->port_dev` in `uart_remove_one_port`. So, the first time `unbind` is called, `uart_add_one_port` fails, leaving `port->port_dev` dangling. The second time `unbind` is called, `uart_remove_one_port` is called with that dangling pointer!
Hold on, if `uart_add_one_port` failed, is the port even considered registered? I should check `serial_core_unregister_port` to see if there is a problem there. This is a critical point to investigate – the state of the port after a failed registration, and whether the system accurately reflects this state. There's a clear potential for a use-after-free scenario here.
|
| 27/2 |
2026/07/28 01:29 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile: Name:serial_core_unregister_port]
Results: map[SourceCode: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: }
SourceFile:drivers/tty/serial/serial_core.c]
|
| 28/2 |
2026/07/28 01:29 |
llm |
expert |
0m
Model:
Tokens:
input: 23647
output: 33
thoughts: 113
Thoughts: **Dangling Pointer Alert: UART Port Registration**
Okay, so I'm looking at this code, and a red flag just went up. Specifically, if `uart_add_one_port` fails, that means the `port->port_dev` pointer is left dangling. Then, the code calls `serial_core_unregister_port`. Now here's where it gets interesting: inside that function, it does this: `struct serial_port_device *port_dev = port->port_dev;` and then immediately uses `port_dev` in `struct serial_ctrl_device *ctrl_dev = serial_core_get_ctrl_dev(port_dev);`. I see what they were trying to do here, but hold on - this *dereferences* `port_dev`! If that earlier `uart_add_one_port` call failed, that means we're trying to dereference a pointer to memory that's invalid, which sounds like trouble, right? Time to dig into `serial_core_get_ctrl_dev` and see what's happening in there. There has to be a defensive check somewhere!
|
| 29/2 |
2026/07/28 01:29 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile: Name:serial_core_get_ctrl_dev]
Results: map[SourceCode: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: }
SourceFile:drivers/tty/serial/serial_core.c]
|
| 30/2 |
2026/07/28 01:29 |
llm |
expert |
0m
Model:
Tokens:
input: 24180
output: 30
thoughts: 1023
Thoughts: **Analysis of a Serial Port Driver Use-After-Free Vulnerability**
Okay, let's break this down. First, I see an access to `dev->parent`, which sits at offset 64 within the `struct device` structure (or something similar). The bug report is clear: it's an 8-byte read at a specific address, and the object itself is 2048 bytes. Bingo, that looks exactly like `dev->parent` inside `struct serial_port_device`.
Here's the sequence of events that leads to this. `serial_core_register_port` calls `serial_core_port_device_add` to allocate `port_dev` and correctly assigns it: `port->port_dev = port_dev`. However, if `serial_core_add_one_port` fails, and it's called by `serial_core_register_port`, such as due to a memory allocation problem, the code jumps to `err_unregister_port_dev`. Here's the key: it calls `serial_base_port_device_remove(port->port_dev)`. This frees `port_dev`, that makes sense. However, `port->port_dev` is *not* set to `NULL` afterwards.
The caller ignores the error from `uart_add_one_port`. Then, later, `serial8250_unregister_port` is called again, maybe by another unbind, or module unload. It calls `uart_remove_one_port` which then calls `serial_core_unregister_port`. Now, *this* is the problem, `serial_core_unregister_port` uses that dangling `port->port_dev` pointer to retrieve `ctrl_dev` and crashes!
Hold on…is this exploitable? That dangling pointer is used like this within `serial_core_unregister_port`:
```c
struct serial_port_device *port_dev = port->port_dev;
struct serial_ctrl_device *ctrl_dev = serial_core_get_ctrl_dev(port_dev);
```
and `serial_core_get_ctrl_dev` looks like this:
```c
struct device *dev = &port_dev->dev;
return to_serial_base_ctrl_device(dev->parent);
```
Then `serial_core_unregister_port` does:
```c
serial_core_remove_one_port(drv, port);
serial_base_port_device_remove(port_dev);
```
and `serial_base_port_device_remove` does:
```c
parent = port_dev->dev.parent;
ctrl_dev = to_serial_base_ctrl_device(parent);
device_del(&port_dev->dev);
ida_free(&ctrl_dev->port_ida, port_dev->port->port_id);
put_device(&port_dev->dev);
```
Yes, this is a textbook use-after-free! If an attacker can spray the heap and control what's in the freed `port_dev` object (which is a 2048-byte object in `kmalloc-2k`), they can control `port_dev->dev.parent` and other members within `port_dev->dev`. Specifically, `device_del` will be called on `&port_dev->dev`. And `device_del` does quite a bit with that `struct device`, unlinking it from kobjects, sysfs, etc.
If the attacker controls the `struct device`, they can achieve arbitrary memory read/write or control flow hijack via function pointers in `dev->bus`, `dev->class`, `dev->release`, etc. For example, `put_device` will eventually call `dev->release` if the reference count drops to zero. That's a classic use-after-free leading to arbitrary code execution.
Now, how do we trigger this? The bug report suggests `unbind_store`. `unbind_store` is accessed through sysfs: `/sys/bus/platform/drivers/serial8250/unbind`. Writing to that file requires root privileges - specifically `CAP_SYS_ADMIN` capability, or the ability to own the sysfs file.
Let's quickly check the permissions of `unbind` files in sysfs to see if it's feasible to trigger. If they're typically `root:root` with `-rw-------` (0600) or `-rw-r--r--` (0644) permissions, this could be easily triggered by a privilege escalation.
|
| 31/2 |
2026/07/28 01:29 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile: Name:driver_sysfs_add]
Results: map[SourceCode: 483: static int driver_sysfs_add(struct device *dev)
484: {
485: int ret;
486:
487: bus_notify(dev, BUS_NOTIFY_BIND_DRIVER);
488:
489: ret = sysfs_create_link(&dev->driver->p->kobj, &dev->kobj,
490: kobject_name(&dev->kobj));
491: if (ret)
492: goto fail;
493:
494: ret = sysfs_create_link(&dev->kobj, &dev->driver->p->kobj,
495: "driver");
496: if (ret)
497: goto rm_dev;
498:
499: if (!IS_ENABLED(CONFIG_DEV_COREDUMP) || !dev->driver->coredump)
500: return 0;
501:
502: ret = device_create_file(dev, &dev_attr_coredump);
503: if (!ret)
504: return 0;
505:
506: sysfs_remove_link(&dev->kobj, "driver");
507:
508: rm_dev:
509: sysfs_remove_link(&dev->driver->p->kobj,
510: kobject_name(&dev->kobj));
511:
512: fail:
513: return ret;
514: }
SourceFile:drivers/base/dd.c]
|
| 32/2 |
2026/07/28 01:29 |
llm |
expert |
0m
Model:
Tokens:
input: 26707
output: 27
thoughts: 13
Thoughts: Here's a summary of my thinking process, based on the provided information, from an expert's perspective:
**Connecting the Dots: `unbind` and `driver_register`**
Okay, so I'm processing this. The key takeaway is the relationship between `unbind` and `driver_register`. Specifically, `unbind`'s creation or definition (or maybe initialization?) is happening *within* the context of `driver_register`. That's the core connection I need to understand better. Is `unbind` being assigned a function pointer during registration? Is it being dynamically allocated and initialized there? Is it a member of a structure defined in that scope? The location of `unbind`'s creation within `driver_register` immediately suggests that whatever `driver_register` is doing influences the nature and function of `unbind`, possibly even its very existence in a meaningful way. I need to now delve into the implementation of `driver_register` to see the exact mechanism. This is the critical clue to grasping the overall architecture.
|
| 33/2 |
2026/07/28 01:29 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile: Name:driver_register]
Results: map[SourceCode: 142: /**
143: * driver_register - register driver with bus
144: * @drv: driver to register
145: *
146: * We pass off most of the work to the bus_add_driver() call,
147: * since most of the things we have to do deal with the bus
148: * structures.
149: */
150: int driver_register(struct device_driver *drv)
151: {
152: int ret;
153: struct device_driver *other;
154:
155: if (!bus_is_registered(drv->bus)) {
156: pr_err("Driver '%s' was unable to register with bus_type '%s' because the bus was not initialized.\n",
157: drv->name, drv->bus->name);
158: return -EINVAL;
159: }
160:
161: if ((drv->bus->probe && drv->probe) ||
162: (drv->bus->remove && drv->remove) ||
163: (drv->bus->shutdown && drv->shutdown))
164: pr_warn("Driver '%s' needs updating - please use "
165: "bus_type methods\n", drv->name);
166:
167: other = driver_find(drv->name, drv->bus);
168: if (other) {
169: pr_err("Error: Driver '%s' is already registered, "
170: "aborting...\n", drv->name);
171: return -EBUSY;
172: }
173:
174: ret = bus_add_driver(drv);
175: if (ret)
176: return ret;
177: ret = driver_add_groups(drv, drv->groups);
178: if (ret) {
179: bus_remove_driver(drv);
180: return ret;
181: }
182: kobject_uevent(&drv->p->kobj, KOBJ_ADD);
183: deferred_probe_extend_timeout();
184:
185: return ret;
186: }
SourceFile:drivers/base/driver.c]
|
| 34/2 |
2026/07/28 01:29 |
llm |
expert |
0m
Model:
Tokens:
input: 27643
output: 29
thoughts: 11
Thoughts: Okay, let's dive into this. Here's my thought process on checking `bus_add_driver`, framed as my own mental journey:
**Examining `bus_add_driver`**
Alright, `bus_add_driver`. Time to dig in. I'm immediately thinking, "What's the context here?" Am I looking at a specific driver model, a particular bus architecture, a general framework? I need to understand the scope before I go too deep. Is this about adding a driver to a PCI bus, a USB bus, or something else entirely? The name suggests a generic bus concept, but the specifics are crucial.
My expert intuition tells me to mentally trace the control flow. What are the key data structures involved? I'm anticipating interaction with some sort of bus device tree or list, probably a driver structure describing the new device. I'll need to look at its input parameters: What information does it take? Is it passing a pointer to the driver itself, or is it expecting a registration structure of some kind?
I need to quickly assess error handling. What failures are possible during this process? Is there proper checking for memory allocation errors? Does it validate input parameters, such as the driver's ID, or the bus's capabilities? I want to see if the function performs any locking to protect shared resources, and what the lock hierarchy is. And what about error reporting? Does it return a meaningful error code, or does it log a message? A solid implementation has both. I will then have to evaluate its resource management. If it allocates resources, how are they freed? Is there a corresponding cleanup function? Does it handle device probes properly?
I'm mentally preparing to consider the potential for race conditions or deadlocks, especially if this function is called from an interrupt context. The concurrency aspects are critical. Does the code seem thread-safe? Does it handle bus hot-plug operations smoothly? I'll probably want to mentally walk through a few execution paths, simulating different scenarios, like attempting to add a driver to a bus that's already in a bad state, or a driver that attempts to bind to a device that's already in use. I will also have to keep in mind the potential for misuse, what happens if the inputs are not what they are supposed to be?
Ultimately, my goal is to assess the robustness, security, and correctness of this function. Does it meet all expected behavior in any potential use case?
|
| 35/2 |
2026/07/28 01:29 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile: Name:bus_add_driver]
Results: map[SourceCode: 728: /**
729: * bus_add_driver - Add a driver to the bus.
730: * @drv: driver.
731: */
732: int bus_add_driver(struct device_driver *drv)
733: {
734: struct subsys_private *sp = bus_to_subsys(drv->bus);
735: struct driver_private *priv;
736: int error = 0;
737:
738: if (!sp)
739: return -EINVAL;
740:
741: /*
742: * Reference in sp is now incremented and will be dropped when
743: * the driver is removed from the bus
744: */
745: pr_debug("bus: '%s': add driver %s\n", sp->bus->name, drv->name);
746:
747: priv = kzalloc_obj(*priv);
748: if (!priv) {
749: error = -ENOMEM;
750: goto out_put_bus;
751: }
752: klist_init(&priv->klist_devices, NULL, NULL);
753: priv->driver = drv;
754: drv->p = priv;
755: priv->kobj.kset = sp->drivers_kset;
756: error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,
757: "%s", drv->name);
758: if (error)
759: goto out_unregister;
760:
761: klist_add_tail(&priv->knode_bus, &sp->klist_drivers);
762: if (sp->drivers_autoprobe) {
763: error = driver_attach(drv);
764: if (error)
765: goto out_del_list;
766: }
767: error = module_add_driver(drv->owner, drv);
768: if (error) {
769: printk(KERN_ERR "%s: failed to create module links for %s\n",
770: __func__, drv->name);
771: goto out_detach;
772: }
773:
774: error = driver_create_file(drv, &driver_attr_uevent);
775: if (error) {
776: printk(KERN_ERR "%s: uevent attr (%s) failed\n",
777: __func__, drv->name);
778: }
779: error = driver_add_groups(drv, sp->bus->drv_groups);
780: if (error) {
781: /* How the hell do we get out of this pickle? Give up */
782: printk(KERN_ERR "%s: driver_add_groups(%s) failed\n",
783: __func__, drv->name);
784: }
785:
786: if (!drv->suppress_bind_attrs) {
787: error = add_bind_files(drv);
788: if (error) {
789: /* Ditto */
790: printk(KERN_ERR "%s: add_bind_files(%s) failed\n",
791: __func__, drv->name);
792: }
793: }
794:
795: return 0;
796:
797: out_detach:
798: driver_detach(drv);
799: out_del_list:
800: klist_del(&priv->knode_bus);
801: out_unregister:
802: kobject_put(&priv->kobj);
803: /* drv->p is freed in driver_release() */
804: drv->p = NULL;
805: out_put_bus:
806: subsys_put(sp);
807: return error;
808: }
SourceFile:drivers/base/bus.c]
|
| 36/2 |
2026/07/28 01:29 |
llm |
expert |
0m
Model:
Tokens:
input: 29401
output: 29
thoughts: 15
Thoughts: Okay, here's my interpretation of that thought process, tailored for an expert audience, and written in the first person:
**My Perspective on `add_bind_files`**
Alright, so I'm thinking about `add_bind_files`. It seems pretty straightforward on the surface, but like any good system, it's the implications that are interesting. What I'm really seeing here is the foundation for a stateful bind operation. We're creating the building blocks, namely `bind` and `unbind`. These functions, in my experience, will be the core actions for managing data or resources within a defined lifecycle. The exact nature of what's being bound or unbound is, of course, contextual, depending on the framework or system we're operating in. It could be memory, file handles, network connections, or even more abstract constructs. The significance here, from a design perspective, is that we're establishing a clear separation of concerns. The `add_bind_files` essentially creates the mechanisms; the "what" and "how" of the binding and unbinding likely reside elsewhere. I imagine this will be an essential piece in supporting the creation of various more abstract higher level components and system calls.
|
| 37/2 |
2026/07/28 01:29 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile: Name:add_bind_files]
Results: map[SourceCode: 675: static int __must_check add_bind_files(struct device_driver *drv)
676: {
677: int ret;
678:
679: ret = driver_create_file(drv, &driver_attr_unbind);
680: if (ret == 0) {
681: ret = driver_create_file(drv, &driver_attr_bind);
682: if (ret)
683: driver_remove_file(drv, &driver_attr_unbind);
684: }
685: return ret;
686: }
SourceFile:drivers/base/bus.c]
|
| 38/2 |
2026/07/28 01:29 |
llm |
expert |
0m
Model:
Tokens:
input: 29904
output: 29
thoughts: 11
Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience and presented in the first person:
**Deep Dive on `driver_attr_unbind`**
Right, let's see... the `driver_attr_unbind` function. I need to refresh my memory on the specific context here. What are we trying to achieve? Is this related to hotplug, device removal, or something else entirely? I know it's crucial for resource management and preventing dangling pointers, but I need to quickly recall the exact sequence of operations it's involved in.
I'm thinking: What are the common pitfalls in unbinding? Race conditions, obviously, are always a concern. Is there a potential for a double free if things aren't handled meticulously? And what about the interaction with other parts of the driver model? The dependencies on `device_unregister` and potentially other attribute cleanup functions - all that has to be nailed down.
I need to quickly visualize the data structures involved: the device, the driver, the attribute objects. How is the reference counting handled during the unbinding process? Are there any implicit dependencies I might be overlooking? Time to re-familiarize myself with the relevant code paths, specifically the error handling... that's where things usually get interesting.
|
| 39/2 |
2026/07/28 01:29 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile: Name:driver_attr_unbind]
Results: map[SourceCode: 251: static DRIVER_ATTR_IGNORE_LOCKDEP(unbind, 0200, NULL, unbind_store);
SourceFile:drivers/base/bus.c]
|
| 40/2 |
2026/07/28 01:29 |
llm |
expert |
0m
Model:
Tokens:
input: 30274
output: 31
thoughts: 477
Thoughts: **Analyzing Potential Triggers for `serial8250_remove` and `serial8250_unregister_port`**
Okay, it's 2 AM, time to dig into this. Let's see... the `unbind` file at this time shows the permissions: `--w-------`. That means it's writable only by root. Right, obvious. So, an unprivileged user can't directly write to `unbind`. My initial thought was: can this be triggered from within a user namespace? But then I remember sysfs is typically read-only in user namespaces, or at least the files owned by the host root are not writable unless explicitly changed, or if it is the init user namespace. Wait... Is this kernel compiled with `SB_I_USERNS_VISIBLE`? No, that's not right. Sysfs is *usually* mounted read-only inside unprivileged containers. Even if it *were* read-write, the files would be owned by the host's root (uid 0 in `init_user_ns`), which likely maps to something like `nobody` or `overflowuid` inside the user namespace, so the user namespace root still can't write to them. So, scratch that idea. It's not accessible from user namespaces.
Alright, back to the drawing board. What other pathways exist to trigger either `serial8250_remove` or `serial8250_unregister_port`? I know `serial8250_remove` is the platform driver's remove function. So, looking at the code, it looks like there are two main ways to trigger it:
1. Unbinding the driver via sysfs (which we've already discounted as it requires root).
2. Removing the device. This is where it gets interesting... How do you remove the device?
If it's a platform device, like the `serial8250_isa_devs` instances, it's usually registered at boot and *never* removed. That leaves hot-pluggable devices - PCI or USB, for example. If the device is physically removed, or removed via software, the remove function *should* be triggered. Thinking about that... for example, if a USB-to-serial adapter *somehow* used the 8250 driver (unlikely, I'd expect it to use `usb-serial`), or if we had a PCI serial card that was hot-unplugged. In the case of a hot-unplugged PCI serial card, `pciserial_remove_ports` would be called which *then* calls `serial8250_unregister_port`. Wait... double check that... Yep, `pciserial_remove_ports` *directly* calls `serial8250_unregister_port`. Alright, let's take a look at `pciserial_remove_ports`...
|
| 41/2 |
2026/07/28 01:30 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile: Name:pciserial_remove_ports]
Results: map[SourceCode:4291: void pciserial_remove_ports(struct serial_private *priv)
4292: {
4293: pciserial_detach_ports(priv);
4294: kfree(priv);
4295: }
SourceFile:drivers/tty/serial/8250/8250_pci.c]
|
| 42/2 |
2026/07/28 01:30 |
llm |
expert |
0m
Model:
Tokens:
input: 31514
output: 31
thoughts: 13
Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience:
**Analyzing `pciserial_detach_ports`**
Alright, let's dive into this `pciserial_detach_ports` function. I need to refresh my understanding of its role in the PCI serial driver and precisely how it handles port detachment. My initial mental checklist includes:
* What are the preconditions for calling this? Is it triggered by a specific event like a hot-unplug, or is it part of a shutdown sequence? Understanding the context will impact the potential for race conditions or error handling.
* How does this routine interact with the hardware registers? Does it directly manipulate the PCI configuration space, or does it call into lower-level routines for that task?
* Specifically, what resources are being released? Is it just the serial port itself, or does it handle interrupt deactivation and any associated DMA mappings? This is crucial for avoiding resource leaks.
* Are there any locking mechanisms involved? Concurrent access to PCI serial devices is common, so I want to know about any mutexes, spinlocks, or read-write locks employed to ensure data integrity during detach. I'll need to evaluate the potential for deadlocks or priority inversion.
* What about error handling? Does it gracefully handle cases where the device refuses to detach, perhaps due to ongoing activity? And what are the implications on any state variables?
* I also need to see how it interacts with the upper layers. Are there any callbacks or notifications that happen to the serial line discipline, or other parts of the system, that this routine might trigger?
Essentially, I'm forming a mental model of this function's logic. It's a quick assessment to identify potential problem areas or areas where I need to dig deeper.
|
| 43/2 |
2026/07/28 01:30 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile: Name:pciserial_detach_ports]
Results: map[SourceCode:4275: static void pciserial_detach_ports(struct serial_private *priv)
4276: {
4277: struct pci_serial_quirk *quirk;
4278: int i;
4279:
4280: for (i = 0; i < priv->nr; i++)
4281: serial8250_unregister_port(priv->line[i]);
4282:
4283: /*
4284: * Find the exit quirks.
4285: */
4286: quirk = find_quirk(priv->dev);
4287: if (quirk->exit)
4288: quirk->exit(priv->dev);
4289: }
SourceFile:drivers/tty/serial/8250/8250_pci.c]
|
| 44/2 |
2026/07/28 01:30 |
llm |
expert |
0m
Model:
Tokens:
input: 32185
output: 35
thoughts: 384
Thoughts: **Analyzing the Serial Port Detachment Bug**
Okay, so `pciserial_detach_ports` calls `serial8250_unregister_port`, that much is clear. But the bug report mentions `serial8250_unregister_port` being called *twice* on the same port, which is where things get interesting. Why? I need to understand that.
Right, I'm tracing it now: `serial8250_remove` iterates through all ports, and for each port where `up->port.dev` matches the device's device pointer (`&dev->dev`), it calls `serial8250_unregister_port`. Then, critically, `serial8250_unregister_port` then *re-registers* the port, but with `dev = &serial8250_isa_devs->dev`. If the device being removed *is* `serial8250_isa_devs`, then the port gets re-registered under the *same* device, which creates a loop! Consequently, on the next call to `serial8250_remove` (or if it's called again for the same device), it will match *again* because of the device loop.
Now, `serial8250_remove` gets called when the platform device is removed or unbound. If it's merely *unbound*, the device still exists in the system. Could the user theoretically unbind the device and then try to unbind it again? No, that won't work. If it's unbound, it's no longer bound to the driver. So, unbinding it again would return an `-ENODEV` error.
Hang on... if it's unbound, `serial8250_remove` is *still* called, which unregisters and re-registers the ports. And crucially, it's re-registering them with `serial8250_isa_devs->dev`. That's the key. But `serial8250_isa_devs` is a platform device. Is *it* even bound to a driver in the first place? I should investigate `serial8250_isa_init_ports` to confirm that. I need to understand the relationship of ISA serial devices in this model.
|
| 45/2 |
2026/07/28 01:30 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile: Name:serial8250_isa_init_ports]
Results: map[SourceCode: 100: void __init serial8250_isa_init_ports(void)
101: {
102: DO_ONCE(__serial8250_isa_init_ports);
103: }
SourceFile:drivers/tty/serial/8250/8250_platform.c]
|
| 46/2 |
2026/07/28 01:30 |
llm |
expert |
0m
Model:
Tokens:
input: 33206
output: 36
thoughts: 18
Thoughts: Here's a first-person summary, tailored for an expert audience, based on the provided thought:
**Checking `__serial8250_isa_init_ports`**
Alright, let's dive into this. The mental checklist here is pretty straightforward. I'm focusing on `__serial8250_isa_init_ports`, which I immediately recognize as the initialization function for ISA-based 8250 serial ports. My first thought is to recall the core logic within this function. I'm mentally parsing the code: what is its role in probing for, configuring, and registering these legacy serial devices? Are we talking about PCI or legacy ISA, as an extra step, I'd probably go and ensure I'm focused on the right hardware setup, just to be sure. It's a key piece in ensuring proper communication with serial devices during boot and runtime, so any issues here can be a real headache. I want to quickly evaluate if any recent changes or commits have touched this code and see how it fits into the broader serial driver architecture. Is it used only at the initial setup, or also during hotplug or resource management? I need to quickly recall potential problem areas related to interrupt handling, resource allocation, and DMA setup, since these functions tend to be intimately tied to these areas. I need to make sure I'm up to speed on any known issues or workarounds for specific chipsets or hardware revisions.
|
| 47/2 |
2026/07/28 01:30 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile: Name:__serial8250_isa_init_ports]
Results: map[SourceCode: 59: static void __init __serial8250_isa_init_ports(void)
60: {
61: int i;
62:
63: if (nr_uarts > UART_NR)
64: nr_uarts = UART_NR;
65:
66: /*
67: * Set up initial ISA ports based on nr_uart module param, or else
68: * default to CONFIG_SERIAL_8250_RUNTIME_UARTS. Note that we do not
69: * need to increase nr_uarts when setting up the initial ISA ports.
70: */
71: for (i = 0; i < nr_uarts; i++)
72: serial8250_setup_port(i);
73:
74: /* chain base port ops to support Remote Supervisor Adapter */
75: univ8250_port_ops = *univ8250_port_base_ops;
76: univ8250_rsa_support(&univ8250_port_ops, univ8250_port_base_ops);
77:
78: for (i = 0; i < ARRAY_SIZE(old_serial_port) && i < nr_uarts; i++) {
79: struct uart_8250_port *up = serial8250_get_port(i);
80: struct uart_port *port = &up->port;
81:
82: port->iobase = old_serial_port[i].port;
83: port->irq = irq_canonicalize(old_serial_port[i].irq);
84: port->irqflags = 0;
85: port->uartclk = old_serial_port[i].baud_base * 16;
86: port->flags = old_serial_port[i].flags;
87: port->hub6 = 0;
88: port->membase = old_serial_port[i].iomem_base;
89: port->iotype = old_serial_port[i].io_type;
90: port->regshift = old_serial_port[i].iomem_reg_shift;
91:
92: if (share_irqs)
93: port->irqflags |= IRQF_SHARED;
94:
95: if (serial8250_isa_config != NULL)
96: serial8250_isa_config(i, &up->port, &up->capabilities);
97: }
98: }
SourceFile:drivers/tty/serial/8250/8250_platform.c]
|
| 48/2 |
2026/07/28 01:30 |
llm |
expert |
0m
Model:
Tokens:
input: 34312
output: 31
thoughts: 18
Thoughts: Here's a summary of my thought process:
**Tracing the ISA 8250 Serial Driver Initialization**
Okay, so I'm digging into this `serial8250_isa_devs` variable. My immediate question is: where is this thing actually *created*? I need to understand its initialization to really grasp how the ISA-based 8250 serial driver is set up. Knowing where it's allocated, what data it holds initially, and how it’s populated is critical for debugging or, more likely, modifying this code. It's the lynchpin, the starting point for understanding how the system knows about and configures these serial ports on ISA. I'm going to have to track down its declaration and assignment. That's the next step.
|
| 49/2 |
2026/07/28 01:30 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile: Name:serial8250_init]
Results: map[SourceCode: 296: static int __init serial8250_init(void)
297: {
298: int ret;
299:
300: if (nr_uarts == 0)
301: return -ENODEV;
302:
303: serial8250_isa_init_ports();
304:
305: pr_info("Serial: 8250/16550 driver, %d ports, IRQ sharing %s\n",
306: nr_uarts, str_enabled_disabled(share_irqs));
307:
308: #ifdef CONFIG_SPARC
309: ret = sunserial_register_minors(&serial8250_reg, UART_NR);
310: #else
311: serial8250_reg.nr = UART_NR;
312: ret = uart_register_driver(&serial8250_reg);
313: #endif
314: if (ret)
315: goto out;
316:
317: ret = serial8250_pnp_init();
318: if (ret)
319: goto unreg_uart_drv;
320:
321: serial8250_isa_devs = platform_device_alloc("serial8250", PLAT8250_DEV_LEGACY);
322: if (!serial8250_isa_devs) {
323: ret = -ENOMEM;
324: goto unreg_pnp;
325: }
326:
327: ret = platform_device_add(serial8250_isa_devs);
328: if (ret)
329: goto put_dev;
330:
331: serial8250_register_ports(&serial8250_reg, &serial8250_isa_devs->dev);
332:
333: ret = platform_driver_register(&serial8250_isa_driver);
334: if (ret == 0)
335: goto out;
336:
337: platform_device_del(serial8250_isa_devs);
338: put_dev:
339: platform_device_put(serial8250_isa_devs);
340: unreg_pnp:
341: serial8250_pnp_exit();
342: unreg_uart_drv:
343: #ifdef CONFIG_SPARC
344: sunserial_unregister_minors(&serial8250_reg, UART_NR);
345: #else
346: uart_unregister_driver(&serial8250_reg);
347: #endif
348: out:
349: return ret;
350: }
SourceFile:drivers/tty/serial/8250/8250_platform.c]
|
| 50/2 |
2026/07/28 01:30 |
llm |
expert |
0m
Model:
Tokens:
input: 30089
output: 33
thoughts: 0
|
| 51/2 |
2026/07/28 01:30 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile: Name:serial8250_register_ports]
Results: map[SourceCode: 368: void __init serial8250_register_ports(struct uart_driver *drv, struct device *dev)
369: {
370: int i;
371:
372: for (i = 0; i < nr_uarts; i++) {
373: struct uart_8250_port *up = &serial8250_ports[i];
374:
375: if (up->port.type == PORT_8250_CIR)
376: continue;
377:
378: if (up->port.dev)
379: continue;
380:
381: up->port.dev = dev;
382:
383: if (uart_console_registered(&up->port))
384: pm_runtime_get_sync(up->port.dev);
385:
386: serial8250_apply_quirks(up);
387: uart_add_one_port(drv, &up->port);
388: }
389: }
SourceFile:drivers/tty/serial/8250/8250_core.c]
|
| 52/2 |
2026/07/28 01:30 |
llm |
expert |
0m
Model:
Tokens:
input: 35797
output: 31
thoughts: 219
Thoughts: **Unraveling This Serial Driver Detachment and Reattachment**
Okay, so I'm looking at how this `serial8250_isa_devs` platform device interacts with the driver. It's created at boot, which makes sense. The `serial8250_isa_driver` is registered, so far so good. Now, here's where things get interesting: when the user unbinds the driver from the device, the `serial8250_remove` function gets called.
And `serial8250_remove` iterates through the ports and calls `serial8250_unregister_port` for each one. This makes perfect sense on its own. It's the expected method to follow.
Wait a second, now I'm following `serial8250_unregister_port`, it seems to unregister the port, then immediately *re-registers* it with `serial8250_isa_devs`. Hold on. This is peculiar. If it's re-registering with the same device, then the port is *still* associated with `serial8250_isa_devs`! But, the driver is unbound, so, the port, now, exists in a sort of limbo. It isn't handled by any driver, but still exists as a device.
If the user then decides to rebind the driver, what happens? Will `serial8250_probe` get called again? Is this a potential bug? We need to investigate `serial8250_probe` to see what this does.
|
| 53/2 |
2026/07/28 01:30 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile: Name:serial8250_probe]
Results: map[SourceCode: 205: /*
206: * Register a set of serial devices attached to a platform device.
207: * The list is terminated with a zero flags entry, which means we expect
208: * all entries to have at least UPF_BOOT_AUTOCONF set.
209: */
210: static int serial8250_probe(struct platform_device *pdev)
211: {
212: struct device *dev = &pdev->dev;
213: struct plat_serial8250_port *p;
214:
215: p = dev_get_platdata(dev);
216: if (p)
217: return serial8250_probe_platform(pdev, p);
218:
219: /*
220: * Probe platform UART devices defined using standard hardware
221: * discovery mechanism like ACPI or DT. Support only ACPI based
222: * serial device for now.
223: */
224: if (has_acpi_companion(dev))
225: return serial8250_probe_acpi(pdev);
226:
227: return 0;
228: }
SourceFile:drivers/tty/serial/8250/8250_platform.c]
|
| 54/2 |
2026/07/28 01:30 |
llm |
expert |
0m
Model:
Tokens:
input: 36784
output: 38
thoughts: 917
Thoughts: **Analyzing a Potential Use-After-Free Vulnerability in the 8250 Serial Driver**
Okay, so I'm looking at this 8250 serial driver and I'm seeing something interesting. First, `serial8250_isa_devs` has no platform data, which means `serial8250_probe` just returns 0. So, binding and unbinding the `serial8250_isa_driver` to `serial8250_isa_devs` effectively does nothing except call `serial8250_remove` on unbind. And `serial8250_remove` calls `serial8250_unregister_port`, which...re-registers the port? That's weird.
So if a user unbinds the driver, it unregisters *and* re-registers the port. If that re-registration fails—say, because of a fault injection or memory allocation failure—`port->port_dev` is left dangling. Then, can the user unbind the driver *again*? Nope, if the driver's already unbound, they can't. But, they *can* bind it and then unbind it again! I'll check, I see I can write to `bind` and then `unbind`, which calls `serial8250_probe` (which does nothing and returns 0). Then they can write to `unbind` again, which calls `serial8250_remove` again! Which calls `serial8250_unregister_port` again. And it will use the dangling `port->port_dev` and trigger the UAF!
But wait, is this accessible to unprivileged users? No, `bind` and `unbind` are root-only (0200). Okay, so that's a limitation. But are there other ways to trigger `serial8250_remove` or `serial8250_unregister_port`?
What about PCI hotplugging? If a PCI serial device is hot-plugged, `pciserial_init_ports` gets called, which calls `serial8250_register_8250_port`. If it's hot-unplugged, `pciserial_remove_ports` gets called, which calls `serial8250_unregister_port`. Now, this is where it gets interesting. `serial8250_unregister_port` re-registers the port *only if* `serial8250_isa_devs` is not NULL. Right, because the code does:
```c
uart_remove_one_port(&serial8250_reg, &uart->port);
if (serial8250_isa_devs) {
uart->port.flags &= ~UPF_BOOT_AUTOCONF;
uart->port.type = PORT_UNKNOWN;
uart->port.dev = &serial8250_isa_devs->dev;
uart->port.port_id = line;
uart->capabilities = 0;
serial8250_init_port(uart);
serial8250_apply_quirks(uart);
uart_add_one_port(&serial8250_reg, &uart->port);
} else {
uart->port.dev = NULL;
}
```
So even if a PCI serial device is hot-unplugged, its port gets *re-registered as an ISA port*! I'm suspecting this may be intentional. In the 8250 driver, there's a fixed array of `serial8250_ports`. When a PCI device is removed, its slot in the array gets "returned" to the ISA device.
Therefore, if a PCI device is hot-unplugged, `serial8250_unregister_port` is called, which unregisters the PCI port and re-registers it as an ISA port. If the re-registration fails, again, `port->port_dev` is left dangling. Then, if the user hot-plugs the PCI device again, it will call `serial8250_register_8250_port` which will try to use the same slot again! Time to check `serial8250_register_8250_port`. This looks promising...
|
| 55/2 |
2026/07/28 01:30 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile: Name:serial8250_register_8250_port]
Results: map[SourceCode: 680: /**
681: * serial8250_register_8250_port - register a serial port
682: * @up: serial port template
683: *
684: * Configure the serial port specified by the request. If the
685: * port exists and is in use, it is hung up and unregistered
686: * first.
687: *
688: * The port is then probed and if necessary the IRQ is autodetected
689: * If this fails an error is returned.
690: *
691: * On success the port is ready to use and the line number is returned.
692: */
693: int serial8250_register_8250_port(const struct uart_8250_port *up)
694: {
695: struct uart_8250_port *uart;
696: bool cons_flow;
697: int ret;
698:
699: if (up->port.uartclk == 0)
700: return -EINVAL;
701:
702: guard(mutex)(&serial_mutex);
703:
704: uart = serial8250_find_match_or_unused(&up->port);
705: if (!uart) {
706: /*
707: * If the port is past the initial isa ports, initialize a new
708: * port and increment nr_uarts accordingly.
709: */
710: uart = serial8250_setup_port(nr_uarts);
711: if (!uart)
712: return -ENOSPC;
713: nr_uarts++;
714: }
715:
716: /* Check if it is CIR already. We check this below again, see there why. */
717: if (uart->port.type == PORT_8250_CIR)
718: return -ENODEV;
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;
730: uart->port.irq = up->port.irq;
731: uart->port.irqflags = up->port.irqflags;
732: uart->port.uartclk = up->port.uartclk;
733: uart->port.fifosize = up->port.fifosize;
734: uart->port.regshift = up->port.regshift;
735: uart->port.iotype = up->port.iotype;
736: uart->port.flags = up->port.flags | UPF_BOOT_AUTOCONF;
737: uart->bugs = up->bugs;
738: uart->port.mapbase = up->port.mapbase;
739: uart->port.mapsize = up->port.mapsize;
740: uart->port.private_data = up->port.private_data;
741: uart->tx_loadsz = up->tx_loadsz;
742: uart->capabilities = up->capabilities;
743: uart->port.throttle = up->port.throttle;
744: uart->port.unthrottle = up->port.unthrottle;
745: uart->port.rs485_config = up->port.rs485_config;
746: uart->port.rs485_supported = up->port.rs485_supported;
747: uart->port.rs485 = up->port.rs485;
748: uart->rs485_start_tx = up->rs485_start_tx;
749: uart->rs485_stop_tx = up->rs485_stop_tx;
750: uart->lsr_save_mask = up->lsr_save_mask;
751: uart->dma = up->dma;
752:
753: uart_set_cons_flow_enabled(&uart->port, uart_cons_flow_enabled(&up->port) | cons_flow);
754:
755: /* Take tx_loadsz from fifosize if it wasn't set separately */
756: if (uart->port.fifosize && !uart->tx_loadsz)
757: uart->tx_loadsz = uart->port.fifosize;
758:
759: if (up->port.dev) {
760: uart->port.dev = up->port.dev;
761: ret = uart_get_rs485_mode(&uart->port);
762: if (ret)
763: goto err;
764: }
765:
766: if (up->port.flags & UPF_FIXED_TYPE)
767: uart->port.type = up->port.type;
768:
769: /*
770: * Only call mctrl_gpio_init(), if the device has no ACPI
771: * companion device
772: */
773: if (uart->port.dev && !has_acpi_companion(uart->port.dev)) {
774: struct mctrl_gpios *gpios = mctrl_gpio_init(&uart->port, 0);
775: if (IS_ERR(gpios)) {
776: ret = PTR_ERR(gpios);
777: goto err;
778: } else {
779: uart->gpios = gpios;
780: }
781: }
782:
783: serial8250_set_defaults(uart);
784:
785: /* Possibly override default I/O functions. */
786: if (up->port.serial_in)
787: uart->port.serial_in = up->port.serial_in;
788: if (up->port.serial_out)
789: uart->port.serial_out = up->port.serial_out;
790: if (up->port.handle_irq)
791: uart->port.handle_irq = up->port.handle_irq;
792: /* Possibly override set_termios call */
793: if (up->port.set_termios)
794: uart->port.set_termios = up->port.set_termios;
795: if (up->port.set_ldisc)
796: uart->port.set_ldisc = up->port.set_ldisc;
797: if (up->port.get_mctrl)
798: uart->port.get_mctrl = up->port.get_mctrl;
799: if (up->port.set_mctrl)
800: uart->port.set_mctrl = up->port.set_mctrl;
801: if (up->port.get_divisor)
802: uart->port.get_divisor = up->port.get_divisor;
803: if (up->port.set_divisor)
804: uart->port.set_divisor = up->port.set_divisor;
805: if (up->port.startup)
806: uart->port.startup = up->port.startup;
807: if (up->port.shutdown)
808: uart->port.shutdown = up->port.shutdown;
809: if (up->port.pm)
810: uart->port.pm = up->port.pm;
811: if (up->port.handle_break)
812: uart->port.handle_break = up->port.handle_break;
813: if (up->dl_read)
814: uart->dl_read = up->dl_read;
815: if (up->dl_write)
816: uart->dl_write = up->dl_write;
817:
818: /* Check the type (again)! It might have changed by the port.type assignment above. */
819: if (uart->port.type != PORT_8250_CIR) {
820: if (uart_console_registered(&uart->port))
821: pm_runtime_get_sync(uart->port.dev);
822:
823: if (serial8250_isa_config != NULL)
824: serial8250_isa_config(0, &uart->port,
825: &uart->capabilities);
826:
827: serial8250_apply_quirks(uart);
828: ret = uart_add_one_port(&serial8250_reg,
829: &uart->port);
830: if (ret)
831: goto err;
832:
833: ret = uart->port.line;
834: } else {
835: dev_info(uart->port.dev,
836: "skipping CIR port at 0x%lx / 0x%llx, IRQ %d\n",
837: uart->port.iobase,
838: (unsigned long long)uart->port.mapbase,
839: uart->port.irq);
840:
841: ret = 0;
842: }
843:
844: if (!uart->lsr_save_mask)
845: uart->lsr_save_mask = LSR_SAVE_FLAGS; /* Use default LSR mask */
846:
847: /* Initialise interrupt backoff work if required */
848: if (up->overrun_backoff_time_ms > 0) {
849: uart->overrun_backoff_time_ms =
850: up->overrun_backoff_time_ms;
851: INIT_DELAYED_WORK(&uart->overrun_backoff,
852: serial_8250_overrun_backoff_work);
853: } else {
854: uart->overrun_backoff_time_ms = 0;
855: }
856:
857: return ret;
858:
859: err:
860: uart->port.dev = NULL;
861: return ret;
862: }
SourceFile:drivers/tty/serial/8250/8250_core.c]
|
| 56/2 |
2026/07/28 01:30 |
llm |
expert |
0m
Model:
Tokens:
input: 41860
output: 55
thoughts: 489
Thoughts: **UAF Vulnerability in serial8250 Driver: A Deep Dive**
Okay, so here's the scenario I've just walked through: `serial8250_register_8250_port` calls `uart_remove_one_port` when `uart->port.dev` is set. That's the key. This happens when a PCI device is hot-unplugged. The code goes into `serial8250_unregister_port` which handles the PCI port, unregisters it, and *then* re-registers it as an ISA port. The problem arises if that re-registration as ISA fails. At this point, `port->port_dev` is left dangling, pointing to freed memory.
Now, if that same PCI device is hot-plugged again, `serial8250_register_8250_port` is invoked. It finds the same slot (using `serial8250_find_match_or_unused`). Because `uart->port.dev` is still set (remember, it was set to `&serial8250_isa_devs->dev` during the ISA registration attempt), `uart_remove_one_port` gets called *again*. Boom, this time, it uses the dangling pointer `port->port_dev`, leading to a use-after-free (UAF) condition!
So, the trigger is a hot-unplug followed by a hot-plug of a PCI serial device (or anything else using `serial8250_register_8250_port`), *only* if the ISA re-registration fails.
Alright, the question of exploitability. Can an unprivileged user trigger a hot-plug/unplug? No, hot-plugging PCI devices requires either physical access or root privileges through the sysfs.
Hmm, what about USB serial devices? Do they use `serial8250_register_8250_port`? No, they use the `usb-serial` framework, which is different. Specifically, `drivers/usb/serial/ftdi_sio.c` and similar files don't utilize the 8250 driver. Are there any USB devices at all that would call `serial8250_register_8250_port`? I need to double check if any USB drivers directly interface with 8250 in their code. I'll need to look.
|
| 57/2 |
2026/07/28 01:30 |
tool |
codesearch-find-references |
0m
Args: map[ContextFile: IncludeSnippetLines:0 Name:serial8250_register_8250_port SourceTreePrefix:drivers/usb/]
Results: map[References:<nil> TruncatedOutput:false]
|
| 58/2 |
2026/07/28 01:30 |
llm |
expert |
0m
Model:
Tokens:
input: 42950
output: 56
thoughts: 16
Thoughts: Okay, here's my take on those thoughts, framed as an expert's internal monologue:
**USB and PCMCIA: A Quick Check**
Hmm, no obvious USB driver references in the `drivers/usb/` directory. That's not entirely surprising, but it's always worth a double-check. I wonder if there's any legacy stuff lingering around, or if something got refactored and moved elsewhere. The code organization is usually pretty good, but sometimes things slip through.
That does make me wonder about PCMCIA though. Is it still in use in any meaningful way, perhaps emulating USB in some unusual configuration or legacy system? Maybe it's buried in another section of the directory structure under a seemingly unrelated name or protocol driver. It's time for a more thorough search. I need to be sure before I move on.
|
| 59/2 |
2026/07/28 01:30 |
tool |
codesearch-find-references |
0m
Args: map[ContextFile: IncludeSnippetLines:0 Name:serial8250_register_8250_port SourceTreePrefix:drivers/pcmcia/]
Results: map[References:<nil> TruncatedOutput:false]
|
| 60/2 |
2026/07/28 01:30 |
llm |
expert |
0m
Model:
Tokens:
input: 43220
output: 62
thoughts: 58
Thoughts: Okay, here's my take on those thoughts, framed as an expert's internal monologue:
**Investigating PCMCIA Driver Locations**
Alright, let's see... I'm looking for PCMCIA-related drivers, and the first place I always check is usually the `drivers/pcmcia/` directory. Hmm, nothing directly there, which is a bit unusual. Okay, hold on, I should know this... where *else* would it make sense to put them? Ah, right, PCMCIA is often used for serial ports, isn't it? Let me think, where are the 8250 serial drivers? *click click* Got it, `drivers/tty/serial/8250/` is the correct spot. But, are there any files specific to PCMCIA? *scanning file list* Nope, it looks like the PCMCIA-specific code for serial ports has been integrated directly into the `drivers/tty/serial/8250/` architecture. Good. This is typical of a well-organized and modular kernel driver system.
|
| 61/2 |
2026/07/28 01:31 |
tool |
codesearch-find-references |
0m
Args: map[ContextFile: IncludeSnippetLines:0 Name:serial8250_register_8250_port SourceTreePrefix:drivers/tty/serial/8250/]
Results: map[References:[map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:lpss8250_probe SourceFile:drivers/tty/serial/8250/8250_lpss.c SourceLine:359] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:mid8250_probe SourceFile:drivers/tty/serial/8250/8250_mid.c SourceLine:331] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:pciserial_init_ports SourceFile:drivers/tty/serial/8250/8250_pci.c SourceLine:4254] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:pericom8250_probe SourceFile:drivers/tty/serial/8250/8250_pericom.c SourceLine:130] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:serial8250_probe_acpi SourceFile:drivers/tty/serial/8250/8250_platform.c SourceLine:148] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:serial8250_probe_platform SourceFile:drivers/tty/serial/8250/8250_platform.c SourceLine:194] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:serial_pnp_probe SourceFile:drivers/tty/serial/8250/8250_pnp.c SourceLine:480] map[ReferenceKind:takes-address-of ReferencingEntityKind:global_variable ReferencingEntityName:__UNIQUE_ID_addressable_serial8250_register_8250_port_877 SourceFile:drivers/tty/serial/8250/8250_core.c SourceLine:863] map[ReferenceKind:takes-address-of ReferencingEntityKind:global_variable ReferencingEntityName:__gendwarfksyms_ptr_serial8250_register_8250_port SourceFile:drivers/tty/serial/8250/8250_core.c SourceLine:863] map[ReferenceKind:takes-address-of ReferencingEntityKind:global_variable ReferencingEntityName:__gendwarfksyms_ptr_serial8250_register_8250_port SourceFile:drivers/tty/serial/8250/8250_core.c SourceLine:863]] TruncatedOutput:false]
|
| 62/2 |
2026/07/28 01:31 |
llm |
expert |
0m
Model:
Tokens:
input: 44302
output: 51
thoughts: 217
Thoughts: **Analyzing the Accessibility of a Serial Port Driver Bug**
Okay, so I'm looking at this potential bug and my mind immediately jumps to the likely culprits: `8250_pci.c`, `8250_pnp.c`, `8250_platform.c`, `8250_lpss.c`, `8250_mid.c`, and `8250_pericom.c`. These are all the classic 8250 serial port driver implementations I'd expect to see, but none of these are really hot-pluggable by a regular user. Generally, you need root privileges to mess with PCI or PNP devices, like writing to sysfs `unbind` or handling actual hot-plugging. So, the initial assessment is that this bug is likely *not* something an unprivileged process can exploit directly.
But, hold on... what about a more devious scenario? Could a rogue USB device masquerade as a PCI device somehow? That's a good thought, but, no, USB devices live on the USB bus; they aren't directly on the PCI bus. That's a dead end. Still, what if a malicious USB device used a driver that *registers* an 8250 port? That would be interesting. I'll need to check the USB drivers for that. Let me look through `drivers/usb/` and see if there are any mentions of `serial8250_register_8250_port`. It seems there aren't any, interesting. Time to widen the search - are there any *other* drivers that register 8250 ports?
|
| 63/2 |
2026/07/28 01:31 |
tool |
codesearch-find-references |
0m
Args: map[ContextFile: IncludeSnippetLines:0 Name:serial8250_register_8250_port SourceTreePrefix:]
Results: map[References:[map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:lpss8250_probe SourceFile:drivers/tty/serial/8250/8250_lpss.c SourceLine:359] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:mid8250_probe SourceFile:drivers/tty/serial/8250/8250_mid.c SourceLine:331] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:pciserial_init_ports SourceFile:drivers/tty/serial/8250/8250_pci.c SourceLine:4254] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:pericom8250_probe SourceFile:drivers/tty/serial/8250/8250_pericom.c SourceLine:130] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:serial8250_probe_acpi SourceFile:drivers/tty/serial/8250/8250_platform.c SourceLine:148] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:serial8250_probe_platform SourceFile:drivers/tty/serial/8250/8250_platform.c SourceLine:194] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:serial_pnp_probe SourceFile:drivers/tty/serial/8250/8250_pnp.c SourceLine:480] map[ReferenceKind:takes-address-of ReferencingEntityKind:global_variable ReferencingEntityName:__UNIQUE_ID_addressable_serial8250_register_8250_port_877 SourceFile:drivers/tty/serial/8250/8250_core.c SourceLine:863] map[ReferenceKind:takes-address-of ReferencingEntityKind:global_variable ReferencingEntityName:__gendwarfksyms_ptr_serial8250_register_8250_port SourceFile:drivers/tty/serial/8250/8250_core.c SourceLine:863] map[ReferenceKind:takes-address-of ReferencingEntityKind:global_variable ReferencingEntityName:__gendwarfksyms_ptr_serial8250_register_8250_port SourceFile:drivers/tty/serial/8250/8250_core.c SourceLine:863]] TruncatedOutput:false]
|
| 64/2 |
2026/07/28 01:31 |
llm |
expert |
0m
Model:
Tokens:
input: 45658
output: 48
thoughts: 92
Thoughts: **Examining the 8250 Serial Driver Registration**
Okay, so I'm focusing my attention solely on the `drivers/tty/serial/8250/` directory. That's where the heart of the 8250 serial driver logic resides, and that's where I need to look for registration mechanisms. My primary question is, how are these ports registered? I remember seeing a `serial8250_register_port` function floating around, but now I think I remember the *exported* function being `serial8250_register_8250_port`. That sounds correct, but I want to be certain.
Let's dig deeper. I need to comprehensively investigate this module and confirm if that's the only registration pathway, or if there are others. Time to switch gears. I'll open up `drivers/tty/serial/8250/8250_core.c` and search for other exported functions. I need to make sure I haven't missed anything crucial. I want to build a clear picture of how this driver is initialized and connected to the system.
|
| 65/2 |
2026/07/28 01:31 |
tool |
grepper |
0m
Args: map[Expression:EXPORT_SYMBOL.*serial8250 PathPrefix:drivers/tty/serial/8250/8250_core.c]
Results: map[Output:drivers/tty/serial/8250/8250_core.c=332=struct uart_8250_port *serial8250_get_port(int line)
--
drivers/tty/serial/8250/8250_core.c-335-}
drivers/tty/serial/8250/8250_core.c:336:EXPORT_SYMBOL_GPL(serial8250_get_port);
drivers/tty/serial/8250/8250_core.c-337-
--
drivers/tty/serial/8250/8250_core.c=582=void serial8250_suspend_port(int line)
--
drivers/tty/serial/8250/8250_core.c-597-}
drivers/tty/serial/8250/8250_core.c:598:EXPORT_SYMBOL(serial8250_suspend_port);
drivers/tty/serial/8250/8250_core.c-599-
--
drivers/tty/serial/8250/8250_core.c=606=void serial8250_resume_port(int line)
--
drivers/tty/serial/8250/8250_core.c-623-}
drivers/tty/serial/8250/8250_core.c:624:EXPORT_SYMBOL(serial8250_resume_port);
drivers/tty/serial/8250/8250_core.c-625-
--
drivers/tty/serial/8250/8250_core.c=693=int serial8250_register_8250_port(const struct uart_8250_port *up)
--
drivers/tty/serial/8250/8250_core.c-862-}
drivers/tty/serial/8250/8250_core.c:863:EXPORT_SYMBOL(serial8250_register_8250_port);
drivers/tty/serial/8250/8250_core.c-864-
--
drivers/tty/serial/8250/8250_core.c=872=void serial8250_unregister_port(int line)
--
drivers/tty/serial/8250/8250_core.c-896-}
drivers/tty/serial/8250/8250_core.c:897:EXPORT_SYMBOL(serial8250_unregister_port);
drivers/tty/serial/8250/8250_core.c-898-
]
|
| 66/2 |
2026/07/28 01:31 |
llm |
expert |
0m
Model:
Tokens:
input: 46745
output: 46
thoughts: 53
Thoughts: Okay, here's my take on those thoughts, framed as an expert's internal monologue:
**Investigating Serial8250 Port Management**
Right, so I'm thinking about `serial8250_register_8250_port` and `serial8250_unregister_port`. Those are the core functions for, well, registering and unregistering 8250 serial ports, obviously. My immediate concern is the unregistration side of things. Are those the only two functions involved? It’s common to use them in pairs, but I need to make sure I haven't missed a call. Are there any other drivers *actually* calling `serial8250_unregister_port` outside the expected usage? If something else is pulling the rug out from under it I need to find it and figure out the impact. Let's see...
|
| 67/2 |
2026/07/28 01:31 |
tool |
codesearch-find-references |
0m
Args: map[ContextFile: IncludeSnippetLines:0 Name:serial8250_unregister_port SourceTreePrefix:]
Results: map[References:[map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:lpss8250_remove SourceFile:drivers/tty/serial/8250/8250_lpss.c SourceLine:378] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:mid8250_remove SourceFile:drivers/tty/serial/8250/8250_mid.c SourceLine:350] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:pciserial_detach_ports SourceFile:drivers/tty/serial/8250/8250_pci.c SourceLine:4281] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:pericom8250_remove SourceFile:drivers/tty/serial/8250/8250_pericom.c SourceLine:151] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:serial8250_remove SourceFile:drivers/tty/serial/8250/8250_platform.c SourceLine:241] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:serial_pnp_remove SourceFile:drivers/tty/serial/8250/8250_pnp.c SourceLine:497] map[ReferenceKind:takes-address-of ReferencingEntityKind:global_variable ReferencingEntityName:__UNIQUE_ID_addressable_serial8250_unregister_port_881 SourceFile:drivers/tty/serial/8250/8250_core.c SourceLine:897] map[ReferenceKind:takes-address-of ReferencingEntityKind:global_variable ReferencingEntityName:__gendwarfksyms_ptr_serial8250_unregister_port SourceFile:drivers/tty/serial/8250/8250_core.c SourceLine:897] map[ReferenceKind:takes-address-of ReferencingEntityKind:global_variable ReferencingEntityName:__gendwarfksyms_ptr_serial8250_unregister_port SourceFile:drivers/tty/serial/8250/8250_core.c SourceLine:897]] TruncatedOutput:false]
|
| 68/2 |
2026/07/28 01:31 |
llm |
expert |
0m
Model:
Tokens:
input: 47680
output: 44
thoughts: 399
Thoughts: **Analyzing the 8250 Serial Driver Bug**
Okay, so this bug is definitely isolated within the `drivers/tty/serial/8250/` directory. That's a good starting point for limiting the scope. The trigger seems to be related to unbinding the driver or hot-unplugging a PCI/PNP/LPSS/MID/Pericom device. These actions are privileged, though, which means an unprivileged user can't exploit it directly. Let me consider... is there *another* way to call `serial8250_remove`? Ah, right, that's the remove function for `serial8250_isa_driver`. Can a user namespace root somehow trigger that? No, namespaces won't allow access to `unbind` or PCI hotplug.
Alright, what about triggering a `serial_core_register_port` failure? That's the key. This could happen if `serial_core_add_one_port` fails. And that in turn fails if `uart_add_one_port` fails. Right, and `uart_add_one_port` can fail for various reasons, like out of memory, or if the port is already in use, or if `tty_port_register_device` fails. But the *real* bug is that `serial_core_register_port` leaves `port->port_dev` dangling when it fails. The code then just *ignores* the error and calls `uart_remove_one_port` later. Essentially we're working with a use-after-free situation, but the vulnerability is tied to how the errors are handled.
Hmm, so we have a dangling pointer that can be triggered by failure in the register code. But the bug is that it is then used by the unregister code. So, the key is to see if any *other* places call `uart_add_one_port`, ignore a failure, and then later call `uart_remove_one_port`. That would identify other points of possible exploitation in the serial driver subsystem.
|
| 69/2 |
2026/07/28 01:31 |
tool |
codesearch-find-references |
0m
Args: map[ContextFile: IncludeSnippetLines:0 Name:uart_add_one_port SourceTreePrefix:]
Results: map[References:[map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:serial8250_register_8250_port SourceFile:drivers/tty/serial/8250/8250_core.c SourceLine:828] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:serial8250_register_ports SourceFile:drivers/tty/serial/8250/8250_core.c SourceLine:387] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:serial8250_unregister_port SourceFile:drivers/tty/serial/8250/8250_core.c SourceLine:892] map[ReferenceKind:takes-address-of ReferencingEntityKind:global_variable ReferencingEntityName:__UNIQUE_ID_addressable_uart_add_one_port_761 SourceFile:drivers/tty/serial/serial_port.c SourceLine:145] map[ReferenceKind:takes-address-of ReferencingEntityKind:global_variable ReferencingEntityName:__gendwarfksyms_ptr_uart_add_one_port SourceFile:drivers/tty/serial/serial_port.c SourceLine:145] map[ReferenceKind:takes-address-of ReferencingEntityKind:global_variable ReferencingEntityName:__gendwarfksyms_ptr_uart_add_one_port SourceFile:drivers/tty/serial/serial_port.c SourceLine:145]] TruncatedOutput:false]
|
| 70/2 |
2026/07/28 01:31 |
llm |
expert |
0m
Model:
Tokens:
input: 49000
output: 33
thoughts: 32
Thoughts: Here's my thought process, as if I were considering this code:
**Investigating a UART Driver Function**
Okay, so `uart_add_one_port` is exported. That's interesting; it suggests it might be used outside the immediate module. I need to figure out where else it's being called. My immediate thought is, are there other callers of this function?
My first instinct is to scan the common area, given the name, and start with the natural place to start: the `drivers/tty/serial/` directory. That's usually where you find UART-related drivers and support functions. Let's start there. I'll need to do a code search in the area. Let's see what turns up.
|
| 71/2 |
2026/07/28 01:31 |
tool |
grepper |
0m
Args: map[Expression:uart_add_one_port PathPrefix:drivers/tty/serial/]
Results: map[Output:
Full output is too long, showing 500 out of 507 lines.
Use more precise expression if possible.
drivers/tty/serial/21285.c=492=static int __init serial21285_init(void)
--
drivers/tty/serial/21285.c-501- if (ret == 0)
drivers/tty/serial/21285.c:502: uart_add_one_port(&serial21285_reg, &serial21285_port);
drivers/tty/serial/21285.c-503-
--
drivers/tty/serial/8250/8250_core.c=368=void __init serial8250_register_ports(struct uart_driver *drv, struct device *dev)
--
drivers/tty/serial/8250/8250_core.c-386- serial8250_apply_quirks(up);
drivers/tty/serial/8250/8250_core.c:387: uart_add_one_port(drv, &up->port);
drivers/tty/serial/8250/8250_core.c-388- }
--
drivers/tty/serial/8250/8250_core.c=693=int serial8250_register_8250_port(const struct uart_8250_port *up)
--
drivers/tty/serial/8250/8250_core.c-827- serial8250_apply_quirks(uart);
drivers/tty/serial/8250/8250_core.c:828: ret = uart_add_one_port(&serial8250_reg,
drivers/tty/serial/8250/8250_core.c-829- &uart->port);
--
drivers/tty/serial/8250/8250_core.c=872=void serial8250_unregister_port(int line)
--
drivers/tty/serial/8250/8250_core.c-891- serial8250_apply_quirks(uart);
drivers/tty/serial/8250/8250_core.c:892: uart_add_one_port(&serial8250_reg, &uart->port);
drivers/tty/serial/8250/8250_core.c-893- } else {
--
drivers/tty/serial/altera_jtaguart.c=374=static int altera_jtaguart_probe(struct platform_device *pdev)
--
drivers/tty/serial/altera_jtaguart.c-421-
drivers/tty/serial/altera_jtaguart.c:422: ret = uart_add_one_port(&altera_jtaguart_driver, port);
drivers/tty/serial/altera_jtaguart.c-423- if (ret) {
--
drivers/tty/serial/altera_uart.c=528=static int altera_uart_probe(struct platform_device *pdev)
--
drivers/tty/serial/altera_uart.c-591-
drivers/tty/serial/altera_uart.c:592: uart_add_one_port(&altera_uart_driver, port);
drivers/tty/serial/altera_uart.c-593-
--
drivers/tty/serial/amba-pl010.c=662=static int pl010_probe(struct amba_device *dev, const struct amba_id *id)
--
drivers/tty/serial/amba-pl010.c-717-
drivers/tty/serial/amba-pl010.c:718: ret = uart_add_one_port(&amba_reg, &uap->port);
drivers/tty/serial/amba-pl010.c-719- if (ret)
--
drivers/tty/serial/amba-pl011.c=2969=static int pl011_register_port(struct uart_amba_port *uap)
--
drivers/tty/serial/amba-pl011.c-2988-
drivers/tty/serial/amba-pl011.c:2989: ret = uart_add_one_port(&amba_reg, &uap->port);
drivers/tty/serial/amba-pl011.c-2990- if (ret)
--
drivers/tty/serial/apbuart.c=519=static int apbuart_probe(struct platform_device *op)
--
drivers/tty/serial/apbuart.c-532-
drivers/tty/serial/apbuart.c:533: uart_add_one_port(&grlib_apbuart_driver, (struct uart_port *) port);
drivers/tty/serial/apbuart.c-534-
--
drivers/tty/serial/ar933x_uart.c=761=static int ar933x_uart_probe(struct platform_device *pdev)
--
drivers/tty/serial/ar933x_uart.c-862-
drivers/tty/serial/ar933x_uart.c:863: ret = uart_add_one_port(&ar933x_uart_driver, &up->port);
drivers/tty/serial/ar933x_uart.c-864- if (ret)
--
drivers/tty/serial/arc_uart.c=568=static int arc_serial_probe(struct platform_device *pdev)
--
drivers/tty/serial/arc_uart.c-626-
drivers/tty/serial/arc_uart.c:627: return uart_add_one_port(&arc_uart_driver, &arc_uart_ports[dev_id].port);
drivers/tty/serial/arc_uart.c-628-}
--
drivers/tty/serial/atmel_serial.c=2852=static int atmel_serial_probe(struct platform_device *pdev)
--
drivers/tty/serial/atmel_serial.c-2931-
drivers/tty/serial/atmel_serial.c:2932: ret = uart_add_one_port(&atmel_uart, &atmel_port->uart);
drivers/tty/serial/atmel_serial.c-2933- if (ret)
--
drivers/tty/serial/bcm63xx_uart.c=808=static int bcm_uart_probe(struct platform_device *pdev)
--
drivers/tty/serial/bcm63xx_uart.c-856-
drivers/tty/serial/bcm63xx_uart.c:857: ret = uart_add_one_port(&bcm_uart_driver, port);
drivers/tty/serial/bcm63xx_uart.c-858- if (ret) {
--
drivers/tty/serial/clps711x.c=438=static int uart_clps711x_probe(struct platform_device *pdev)
--
drivers/tty/serial/clps711x.c-487-
drivers/tty/serial/clps711x.c:488: ret = uart_add_one_port(&clps711x_uart, &s->port);
drivers/tty/serial/clps711x.c-489- if (ret)
--
drivers/tty/serial/cpm_uart.c=1517=static int cpm_uart_probe(struct platform_device *ofdev)
--
drivers/tty/serial/cpm_uart.c-1538- if (!ret)
drivers/tty/serial/cpm_uart.c:1539: return uart_add_one_port(&cpm_reg, &pinfo->port);
drivers/tty/serial/cpm_uart.c-1540-
--
drivers/tty/serial/digicolor-usart.c=449=static int digicolor_uart_probe(struct platform_device *pdev)
--
drivers/tty/serial/digicolor-usart.c-502-
drivers/tty/serial/digicolor-usart.c:503: return uart_add_one_port(&digicolor_uart, &dp->port);
drivers/tty/serial/digicolor-usart.c-504-}
--
drivers/tty/serial/dz.c=765=static int __init dz_probe(struct platform_device *pdev)
--
drivers/tty/serial/dz.c-790-
drivers/tty/serial/dz.c:791: if (uart_add_one_port(&dz_reg, uport))
drivers/tty/serial/dz.c-792- uport->dev = NULL;
--
drivers/tty/serial/fsl_linflexuart.c=807=static int linflex_probe(struct platform_device *pdev)
--
drivers/tty/serial/fsl_linflexuart.c-850-
drivers/tty/serial/fsl_linflexuart.c:851: return uart_add_one_port(&linflex_reg, sport);
drivers/tty/serial/fsl_linflexuart.c-852-}
--
drivers/tty/serial/fsl_lpuart.c=2872=static int lpuart_probe(struct platform_device *pdev)
--
drivers/tty/serial/fsl_lpuart.c-2971-
drivers/tty/serial/fsl_lpuart.c:2972: ret = uart_add_one_port(&lpuart_reg, &sport->port);
drivers/tty/serial/fsl_lpuart.c-2973- if (ret)
--
drivers/tty/serial/icom.c=1697=static int icom_probe(struct pci_dev *dev,
--
drivers/tty/serial/icom.c-1785- icom_port->port + icom_adapter->index * 4;
drivers/tty/serial/icom.c:1786: if (uart_add_one_port (&icom_uart_driver, &icom_port->uart_port)) {
drivers/tty/serial/icom.c-1787- icom_port->status = ICOM_PORT_OFF;
--
drivers/tty/serial/imx.c=2409=static int imx_uart_probe(struct platform_device *pdev)
--
drivers/tty/serial/imx.c-2638-
drivers/tty/serial/imx.c:2639: ret = uart_add_one_port(&imx_uart_uart_driver, &sport->port);
drivers/tty/serial/imx.c-2640-
--
drivers/tty/serial/ip22zilog.c=1057=static int ip22zilog_probe(struct platform_device *pdev)
--
drivers/tty/serial/ip22zilog.c-1099- for (i = 0; i < NUM_CHANNELS; i++)
drivers/tty/serial/ip22zilog.c:1100: uart_add_one_port(&ip22zilog_reg,
drivers/tty/serial/ip22zilog.c-1101- &ip22zilog_port_table[i].port);
--
drivers/tty/serial/jsm/jsm_tty.c=432=int jsm_uart_port_init(struct jsm_board *brd)
--
drivers/tty/serial/jsm/jsm_tty.c-468- brd->channels[i]->uart_port.line = line;
drivers/tty/serial/jsm/jsm_tty.c:469: rc = uart_add_one_port(&jsm_uart_driver, &brd->channels[i]->uart_port);
drivers/tty/serial/jsm/jsm_tty.c-470- if (rc) {
--
drivers/tty/serial/lantiq.c=804=static int lqasc_probe(struct platform_device *pdev)
--
drivers/tty/serial/lantiq.c-882-
drivers/tty/serial/lantiq.c:883: ret = uart_add_one_port(&lqasc_reg, port);
drivers/tty/serial/lantiq.c-884-
--
drivers/tty/serial/liteuart.c=280=static int liteuart_probe(struct platform_device *pdev)
--
drivers/tty/serial/liteuart.c-326-
drivers/tty/serial/liteuart.c:327: ret = uart_add_one_port(&liteuart_driver, &uart->port);
drivers/tty/serial/liteuart.c-328- if (ret)
--
drivers/tty/serial/lpc32xx_hs.c=605=static int serial_hs_lpc32xx_probe(struct platform_device *pdev)
--
drivers/tty/serial/lpc32xx_hs.c-646-
drivers/tty/serial/lpc32xx_hs.c:647: ret = uart_add_one_port(&lpc32xx_hs_reg, &p->port);
drivers/tty/serial/lpc32xx_hs.c-648-
--
drivers/tty/serial/ma35d1_serial.c=685=static int ma35d1serial_probe(struct platform_device *pdev)
--
drivers/tty/serial/ma35d1_serial.c-737-
drivers/tty/serial/ma35d1_serial.c:738: ret = uart_add_one_port(&ma35d1serial_reg, &up->port);
drivers/tty/serial/ma35d1_serial.c-739- if (ret < 0)
--
drivers/tty/serial/max3100.c=684=static int max3100_probe(struct spi_device *spi)
--
drivers/tty/serial/max3100.c-729-
drivers/tty/serial/max3100.c:730: /* Read clock frequency from a property, uart_add_one_port() will fail if it's not set */
drivers/tty/serial/max3100.c-731- device_property_read_u32(dev, "clock-frequency", &max3100s[i]->port.uartclk);
drivers/tty/serial/max3100.c-732-
drivers/tty/serial/max3100.c:733: retval = uart_add_one_port(&max3100_uart_driver, &max3100s[i]->port);
drivers/tty/serial/max3100.c-734- if (retval < 0)
drivers/tty/serial/max3100.c:735: dev_err_probe(dev, retval, "uart_add_one_port failed for line %d\n", i);
drivers/tty/serial/max3100.c-736-
--
drivers/tty/serial/max310x.c=1309=static int max310x_probe(struct device *dev, const struct max310x_devtype *devtype,
--
drivers/tty/serial/max310x.c-1444- /* Register port */
drivers/tty/serial/max310x.c:1445: ret = uart_add_one_port(&max310x_uart, &s->p[i].port);
drivers/tty/serial/max310x.c-1446- if (ret)
--
drivers/tty/serial/mcf.c=571=static int mcf_probe(struct platform_device *pdev)
--
drivers/tty/serial/mcf.c-594-
drivers/tty/serial/mcf.c:595: uart_add_one_port(&mcf_driver, port);
drivers/tty/serial/mcf.c-596- }
--
drivers/tty/serial/men_z135_uart.c=800=static int men_z135_probe(struct mcb_device *mdev,
--
drivers/tty/serial/men_z135_uart.c-836-
drivers/tty/serial/men_z135_uart.c:837: err = uart_add_one_port(&men_z135_driver, &uart->port);
drivers/tty/serial/men_z135_uart.c-838- if (err)
--
drivers/tty/serial/meson_uart.c=695=static int meson_uart_probe(struct platform_device *pdev)
--
drivers/tty/serial/meson_uart.c-782-
drivers/tty/serial/meson_uart.c:783: ret = uart_add_one_port(uart_driver, port);
drivers/tty/serial/meson_uart.c-784- if (ret)
--
drivers/tty/serial/milbeaut_usio.c=497=static int mlb_usio_probe(struct platform_device *pdev)
--
drivers/tty/serial/milbeaut_usio.c-545-
drivers/tty/serial/milbeaut_usio.c:546: ret = uart_add_one_port(&mlb_usio_uart_driver, port);
drivers/tty/serial/milbeaut_usio.c-547- if (ret) {
--
drivers/tty/serial/mpc52xx_uart.c=1698=static int mpc52xx_uart_of_probe(struct platform_device *op)
--
drivers/tty/serial/mpc52xx_uart.c-1758- /* Add the port to the uart sub-system */
drivers/tty/serial/mpc52xx_uart.c:1759: ret = uart_add_one_port(&mpc52xx_uart_driver, port);
drivers/tty/serial/mpc52xx_uart.c-1760- if (ret)
--
drivers/tty/serial/mps2-uart.c=577=static int mps2_serial_probe(struct platform_device *pdev)
--
drivers/tty/serial/mps2-uart.c-594-
drivers/tty/serial/mps2-uart.c:595: ret = uart_add_one_port(&mps2_uart_driver, &mps_port->port);
drivers/tty/serial/mps2-uart.c-596- if (ret)
--
drivers/tty/serial/msm_serial.c=1798=static int msm_serial_probe(struct platform_device *pdev)
--
drivers/tty/serial/msm_serial.c-1863-
drivers/tty/serial/msm_serial.c:1864: return uart_add_one_port(&msm_uart_driver, port);
drivers/tty/serial/msm_serial.c-1865-}
--
drivers/tty/serial/mux.c=428=static int __init mux_probe(struct parisc_device *dev)
--
drivers/tty/serial/mux.c-466-
drivers/tty/serial/mux.c:467: status = uart_add_one_port(&mux_driver, port);
drivers/tty/serial/mux.c-468- BUG_ON(status);
--
drivers/tty/serial/mvebu-uart.c=875=static int mvebu_uart_probe(struct platform_device *pdev)
--
drivers/tty/serial/mvebu-uart.c-982-
drivers/tty/serial/mvebu-uart.c:983: return uart_add_one_port(&mvebu_uart_driver, port);
drivers/tty/serial/mvebu-uart.c-984-}
--
drivers/tty/serial/mxs-auart.c=1570=static int mxs_auart_probe(struct platform_device *pdev)
--
drivers/tty/serial/mxs-auart.c-1660-
drivers/tty/serial/mxs-auart.c:1661: ret = uart_add_one_port(&auart_driver, &s->port);
drivers/tty/serial/mxs-auart.c-1662- if (ret)
--
drivers/tty/serial/omap-serial.c=1539=static int serial_omap_probe(struct platform_device *pdev)
--
drivers/tty/serial/omap-serial.c-1640-
drivers/tty/serial/omap-serial.c:1641: ret = uart_add_one_port(&serial_omap_reg, &up->port);
drivers/tty/serial/omap-serial.c-1642- if (ret != 0)
--
drivers/tty/serial/owl-uart.c=644=static int owl_uart_probe(struct platform_device *pdev)
--
drivers/tty/serial/owl-uart.c-714-
drivers/tty/serial/owl-uart.c:715: ret = uart_add_one_port(&owl_uart_driver, &owl_port->port);
drivers/tty/serial/owl-uart.c-716- if (ret)
--
drivers/tty/serial/pch_uart.c=1645=static struct eg20t_port *pch_uart_init_port(struct pci_dev *pdev,
--
drivers/tty/serial/pch_uart.c-1723-#endif
drivers/tty/serial/pch_uart.c:1724: ret = uart_add_one_port(&pch_uart_driver, &priv->port);
drivers/tty/serial/pch_uart.c-1725- if (ret < 0)
--
drivers/tty/serial/pic32_uart.c=862=static int pic32_uart_probe(struct platform_device *pdev)
--
drivers/tty/serial/pic32_uart.c-911-
drivers/tty/serial/pic32_uart.c:912: ret = uart_add_one_port(&pic32_uart_driver, port);
drivers/tty/serial/pic32_uart.c-913- if (ret) {
--
drivers/tty/serial/pmac_zilog.c=1316=static const struct uart_ops pmz_pops = {
--
drivers/tty/serial/pmac_zilog.c-1343- * Unlike sunzilog, we don't need to pre-init the spinlock as we don't
drivers/tty/serial/pmac_zilog.c:1344: * register our console before uart_add_one_port() is called
drivers/tty/serial/pmac_zilog.c-1345- */
--
drivers/tty/serial/pmac_zilog.c=1460=static int pmz_attach(struct macio_dev *mdev, const struct of_device_id *match)
--
drivers/tty/serial/pmac_zilog.c-1488-
drivers/tty/serial/pmac_zilog.c:1489: return uart_add_one_port(&pmz_uart_reg, &uap->port);
drivers/tty/serial/pmac_zilog.c-1490-}
--
drivers/tty/serial/pmac_zilog.c=1684=static int pmz_attach(struct platform_device *pdev)
--
drivers/tty/serial/pmac_zilog.c-1699-
drivers/tty/serial/pmac_zilog.c:1700: return uart_add_one_port(&pmz_uart_reg, &uap->port);
drivers/tty/serial/pmac_zilog.c-1701-}
--
drivers/tty/serial/pxa.c=799=static int serial_pxa_probe(struct platform_device *dev)
--
drivers/tty/serial/pxa.c-860-
drivers/tty/serial/pxa.c:861: uart_add_one_port(&serial_pxa_reg, &sport->port);
drivers/tty/serial/pxa.c-862- platform_set_drvdata(dev, sport);
--
drivers/tty/serial/qcom_geni_serial.c=1801=static int qcom_geni_serial_probe(struct platform_device *pdev)
--
drivers/tty/serial/qcom_geni_serial.c-1923-
drivers/tty/serial/qcom_geni_serial.c:1924: ret = uart_add_one_port(drv, uport);
drivers/tty/serial/qcom_geni_serial.c-1925- if (ret)
--
drivers/tty/serial/rda-uart.c=703=static int rda_uart_probe(struct platform_device *pdev)
--
drivers/tty/serial/rda-uart.c-762-
drivers/tty/serial/rda-uart.c:763: ret = uart_add_one_port(&rda_uart_driver, &rda_port->port);
drivers/tty/serial/rda-uart.c-764- if (ret)
--
drivers/tty/serial/rp2.c=643=static int rp2_load_firmware(struct rp2_card *card, const struct firmware *fw)
--
drivers/tty/serial/rp2.c-681- rp2_init_port(rp, fw);
drivers/tty/serial/rp2.c:682: rc = uart_add_one_port(&rp2_uart_driver, p);
drivers/tty/serial/rp2.c-683- if (rc) {
--
drivers/tty/serial/sa1100.c=827=static int sa1100_serial_add_one_port(struct sa1100_port *sport, struct platform_device *dev)
--
drivers/tty/serial/sa1100.c-849-
drivers/tty/serial/sa1100.c:850: return uart_add_one_port(&sa1100_reg, &sport->port);
drivers/tty/serial/sa1100.c-851-}
--
drivers/tty/serial/samsung_tty.c=1939=static int s3c24xx_serial_probe(struct platform_device *pdev)
--
drivers/tty/serial/samsung_tty.c-2033- dev_dbg(&pdev->dev, "%s: adding port\n", __func__);
drivers/tty/serial/samsung_tty.c:2034: uart_add_one_port(&s3c24xx_uart_drv, &ourport->port);
drivers/tty/serial/samsung_tty.c-2035- platform_set_drvdata(pdev, &ourport->port);
--
drivers/tty/serial/sb1250-duart.c=925=static int __init sbd_init(void)
--
drivers/tty/serial/sb1250-duart.c-940- if (sport->duart)
drivers/tty/serial/sb1250-duart.c:941: uart_add_one_port(&sbd_reg, uport);
drivers/tty/serial/sb1250-duart.c-942- }
--
drivers/tty/serial/sc16is7xx.c=1470=static int sc16is7xx_setup_channel(struct sc16is7xx_one *one, int i,
--
drivers/tty/serial/sc16is7xx.c-1520- /* Register port */
drivers/tty/serial/sc16is7xx.c:1521: ret = uart_add_one_port(&sc16is7xx_uart, port);
drivers/tty/serial/sc16is7xx.c-1522- if (ret)
--
drivers/tty/serial/sccnxp.c=883=static int sccnxp_probe(struct platform_device *pdev)
--
drivers/tty/serial/sccnxp.c-991- s->port[i].has_sysrq = IS_ENABLED(CONFIG_SERIAL_SCCNXP_CONSOLE);
drivers/tty/serial/sccnxp.c:992: uart_add_one_port(&s->uart, &s->port[i]);
drivers/tty/serial/sccnxp.c-993- /* Set direction to input */
--
drivers/tty/serial/serial-tegra.c=1557=static int tegra_uart_probe(struct platform_device *pdev)
--
drivers/tty/serial/serial-tegra.c-1610- u->regshift = 2;
drivers/tty/serial/serial-tegra.c:1611: ret = uart_add_one_port(&tegra_uart_driver, u);
drivers/tty/serial/serial-tegra.c-1612- if (ret < 0) {
--
drivers/tty/serial/serial_base_bus.c=64=static int serial_base_device_init(struct uart_port *port,
--
drivers/tty/serial/serial_base_bus.c-81- if (!serial_base_initialized) {
drivers/tty/serial/serial_base_bus.c:82: dev_dbg(port->dev, "uart_add_one_port() called before arch_initcall()?\n");
drivers/tty/serial/serial_base_bus.c-83- return -EPROBE_DEFER;
--
drivers/tty/serial/serial_core.c=2704=static const struct tty_port_operations uart_port_ops = {
--
drivers/tty/serial/serial_core.c-2721- * @drv->port should be %NULL, and the per-port structures should be registered
drivers/tty/serial/serial_core.c:2722: * using uart_add_one_port() after this call has succeeded.
drivers/tty/serial/serial_core.c-2723- *
--
drivers/tty/serial/serial_core.c=2785=EXPORT_SYMBOL(uart_register_driver);
--
drivers/tty/serial/serial_core.c-2792- * driver must have removed all its ports via the uart_remove_one_port() if it
drivers/tty/serial/serial_core.c:2793: * registered them with uart_add_one_port(). (I.e. @drv->port is %NULL.)
drivers/tty/serial/serial_core.c-2794- *
--
drivers/tty/serial/serial_port.c=129=static int serial_port_remove(struct device *dev)
--
drivers/tty/serial/serial_port.c-140- */
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-144-}
drivers/tty/serial/serial_port.c:145:EXPORT_SYMBOL(uart_add_one_port);
drivers/tty/serial/serial_port.c-146-
--
drivers/tty/serial/serial_txx9.c=815=static void __init serial_txx9_register_ports(struct uart_driver *drv,
--
drivers/tty/serial/serial_txx9.c-826- if (up->iobase || up->mapbase)
drivers/tty/serial/serial_txx9.c:827: uart_add_one_port(drv, up);
drivers/tty/serial/serial_txx9.c-828- }
--
drivers/tty/serial/serial_txx9.c=959=static int serial_txx9_register_port(struct uart_port *port)
--
drivers/tty/serial/serial_txx9.c-991- uart->dev = port->dev;
drivers/tty/serial/serial_txx9.c:992: ret = uart_add_one_port(&serial_txx9_reg, uart);
drivers/tty/serial/serial_txx9.c-993- if (ret == 0)
--
drivers/tty/serial/sh-sci.c=3793=static int sci_probe_single(struct platform_device *dev,
--
drivers/tty/serial/sh-sci.c-3867-
drivers/tty/serial/sh-sci.c:3868: return uart_add_one_port(&sci_uart_driver, &sciport->port);
drivers/tty/serial/sh-sci.c-3869-}
--
drivers/tty/serial/sifive.c=973=static int sifive_serial_probe(struct platform_device *pdev)
--
drivers/tty/serial/sifive.c-1057-
drivers/tty/serial/sifive.c:1058: r = uart_add_one_port(&sifive_serial_uart_driver, &ssp->port);
drivers/tty/serial/sifive.c-1059- if (r != 0) {
--
drivers/tty/serial/sprd_serial.c=1177=static int sprd_probe(struct platform_device *pdev)
--
drivers/tty/serial/sprd_serial.c-1246-
drivers/tty/serial/sprd_serial.c:1247: ret = uart_add_one_port(&sprd_uart_driver, up);
drivers/tty/serial/sprd_serial.c-1248- if (ret)
--
drivers/tty/serial/st-asc.c=782=static int asc_serial_probe(struct platform_device *pdev)
--
drivers/tty/serial/st-asc.c-794-
drivers/tty/serial/st-asc.c:795: ret = uart_add_one_port(&asc_uart_driver, &ascport->port);
drivers/tty/serial/st-asc.c-796- if (ret)
--
drivers/tty/serial/stm32-usart.c=1765=static int stm32_usart_serial_probe(struct platform_device *pdev)
--
drivers/tty/serial/stm32-usart.c-1828-
drivers/tty/serial/stm32-usart.c:1829: ret = uart_add_one_port(&stm32_usart_driver, &stm32port->port);
drivers/tty/serial/stm32-usart.c-1830- if (ret)
--
drivers/tty/serial/sunhv.c=523=static int hv_probe(struct platform_device *op)
--
drivers/tty/serial/sunhv.c-572-
drivers/tty/serial/sunhv.c:573: err = uart_add_one_port(&sunhv_reg, port);
drivers/tty/serial/sunhv.c-574- if (err)
--
drivers/tty/serial/sunplus-uart.c=586=static int sunplus_uart_probe(struct platform_device *pdev)
--
drivers/tty/serial/sunplus-uart.c-651-
drivers/tty/serial/sunplus-uart.c:652: ret = uart_add_one_port(&sunplus_uart_driver, &sup->port);
drivers/tty/serial/sunplus-uart.c-653-#ifdef CONFIG_SERIAL_SUNPLUS_CONSOLE
--
drivers/tty/serial/sunsab.c=1015=static int sab_probe(struct platform_device *op)
--
drivers/tty/serial/sunsab.c-1042-
drivers/tty/serial/sunsab.c:1043: err = uart_add_one_port(&sunsab_reg, &up[0].port);
drivers/tty/serial/sunsab.c-1044- if (err)
--
drivers/tty/serial/sunsab.c-1046-
drivers/tty/serial/sunsab.c:1047: err = uart_add_one_port(&sunsab_reg, &up[1].port);
drivers/tty/serial/sunsab.c-1048- if (err)
--
drivers/tty/serial/sunsu.c=1386=static int su_probe(struct platform_device *op)
--
drivers/tty/serial/sunsu.c-1463- ignore_line);
drivers/tty/serial/sunsu.c:1464: err = uart_add_one_port(&sunsu_reg, &up->port);
drivers/tty/serial/sunsu.c-1465- if (err)
--
drivers/tty/serial/sunzilog.c=1396=static int zs_probe(struct platform_device *op)
--
drivers/tty/serial/sunzilog.c-1467- up->flags |= SUNZILOG_FLAG_IS_CONS;
drivers/tty/serial/sunzilog.c:1468: err = uart_add_one_port(&sunzilog_reg, &up[0].port);
drivers/tty/serial/sunzilog.c-1469- if (err) {
--
drivers/tty/serial/sunzilog.c-1477- up->flags |= SUNZILOG_FLAG_IS_CONS;
drivers/tty/serial/sunzilog.c:1478: err = uart_add_one_port(&sunzilog_reg, &up[1].port);
drivers/tty/serial/sunzilog.c-1479- if (err) {
--
drivers/tty/serial/tegra-tcu.c=178=static int tegra_tcu_probe(struct platform_device *pdev)
--
drivers/tty/serial/tegra-tcu.c-236-
drivers/tty/serial/tegra-tcu.c:237: err = uart_add_one_port(&tcu->driver, port);
drivers/tty/serial/tegra-tcu.c-238- if (err) {
--
drivers/tty/serial/tegra-utc.c=518=static int tegra_utc_register_port(struct tegra_utc_port *tup)
--
drivers/tty/serial/tegra-utc.c-521-
drivers/tty/serial/tegra-utc.c:522: ret = uart_add_one_port(&tegra_utc_driver, &tup->port);
drivers/tty/serial/tegra-utc.c-523- if (ret)
--
drivers/tty/serial/timbuart.c=407=static int timbuart_probe(struct platform_device *dev)
--
drivers/tty/serial/timbuart.c-453-
drivers/tty/serial/timbuart.c:454: err = uart_add_one_port(&timbuart_driver, &uart->port);
drivers/tty/serial/timbuart.c-455- if (err)
--
drivers/tty/serial/uartlite.c=635=static int ulite_assign(struct device *dev, int id, phys_addr_t base, int irq,
--
drivers/tty/serial/uartlite.c-677- /* Register the port */
drivers/tty/serial/uartlite.c:678: rc = uart_add_one_port(&ulite_uart_driver, port);
drivers/tty/serial/uartlite.c-679- if (rc) {
drivers/tty/serial/uartlite.c:680: dev_err(dev, "uart_add_one_port() failed; err=%i\n", rc);
drivers/tty/serial/uartlite.c-681- port->mapbase = 0;
--
drivers/tty/serial/ucc_uart.c=1234=static int ucc_uart_probe(struct platform_device *ofdev)
--
drivers/tty/serial/ucc_uart.c-1430- */
drivers/tty/serial/ucc_uart.c:1431: ret = uart_add_one_port(&ucc_uart_driver, &qe_port->port);
drivers/tty/serial/ucc_uart.c-1432- if (ret) {
--
drivers/tty/serial/vt8500_serial.c=600=static int vt8500_serial_probe(struct platform_device *pdev)
--
drivers/tty/serial/vt8500_serial.c-687-
drivers/tty/serial/vt8500_serial.c:688: uart_add_one_port(&vt8500_uart_driver, &vt8500_port->uart);
drivers/tty/serial/vt8500_serial.c-689-
--
drivers/tty/serial/xilinx_uartps.c=1071=static int cdns_uart_verify_port(struct uart_port *port,
--
drivers/tty/serial/xilinx_uartps.c-1089- * called when the driver adds a cdns_uart port via
drivers/tty/serial/xilinx_uartps.c:1090: * uart_add_one_port()
drivers/tty/serial/xilinx_uartps.c-1091- * @port: Handle to the uart port structure
--
drivers/tty/serial/xilinx_uartps.c=1641=static int cdns_uart_probe(struct platform_device *pdev)
--
drivers/tty/serial/xilinx_uartps.c-1820-
drivers/tty/serial/xilinx_uartps.c:1821: rc = uart_add_one_port(&cdns_uart_uart_driver, port);
drivers/tty/serial/xilinx_uartps.c-1822- if (rc) {
drivers/tty/serial/xilinx_uartps.c-1823- dev_err(&pdev->dev,
drivers/tty/serial/xilinx_uartps.c:1824: "uart_add_one_port() failed; err=%i\n", rc);
]
|
| 72/2 |
2026/07/28 01:31 |
llm |
expert |
0m
Model:
Tokens:
input: 60172
output: 31
thoughts: 1077
Thoughts: **Analyzing the 8250 Driver Vulnerability**
Okay, let's break this down systematically. I see there's a potential vulnerability within the `serial8250_unregister_port` function. The core of the problem stems from how it handles errors during port removal and re-registration, specifically regarding the `uart_add_one_port` function. Many drivers, when `uart_add_one_port` fails, will handle the error gracefully by freeing the port or returning an error, preventing the device from binding if a critical operation fails.
The 8250 driver, however, is structured differently. It has a fixed array of ports and re-registers them as ISA ports when a device is removed. The bug here lies in `serial8250_unregister_port` calling `uart_add_one_port` during `remove()`, and ignoring the return value, leaving a dangling `port_dev` pointer in the `serial8250_ports` array if the call fails. This dangling pointer then gets used in `uart_remove_one_port` when the function is called again.
The question is, can this be triggered? Directly, it seems not. Unprivileged users are out of luck, since you need root to write to sysfs `unbind` or to hot-plug PCI/PNP devices. Even with user namespaces, these methods are inaccessible.
Now, considering a VM guest... If it's a paravirtualized serial port (like virtio-console), it doesn't use the 8250 driver, so that's not the issue. However, if the VM guest has an emulated 8250 serial port, the guest root *could* trigger it by unbinding or hot-unplugging the PCI device, but that's within the guest, not the host. But then I realize the real question is whether it can trigger in the *host*, meaning can the guest trigger it on the host? No, because the host kernel uses KVM/QEMU to emulate the serial port.
But hold on, the *host* can hot-unplug and hot-plug the emulated PCI serial device within the guest. If, during that process, `uart_add_one_port` fails in the guest due to memory pressure, for example, then yes, the host could cause this dangling pointer issue in the guest kernel. A second unbind by the host would then lead to the crash. However, the prompt is asking if the *guest* can trigger the issue to affect the host. In this case, no, it's a guest-level crash.
Finally, we've got "Peripheral Trigger." If an attacker plugs in a malicious PCI device, say via Thunderbolt/PCIe, they *could* hot-plug/unplug it. If the device claims to be an 8250 serial port, it would bind to the `8250_pci` driver. Then, the attacker could hot-unplug it, triggering `serial8250_unregister_port`. If they could then manage to cause `uart_add_one_port` to fail—perhaps through memory exhaustion or exploiting another vulnerability—they could create that dangling pointer. A second hot-plug would then cause a crash, just as we predicted. Seems like that would work. Causing the `uart_add_one_port` to fail might be tricky without fault injection. But still, the Thunderbolt/PCIe hotplug is a plausible attack vector.
|
| 73/2 |
2026/07/28 01:31 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile: Name:uart_add_one_port]
Results: map[SourceCode: 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: }
SourceFile:drivers/tty/serial/serial_port.c]
|
| 74/2 |
2026/07/28 01:31 |
llm |
expert |
0m
Model:
Tokens:
input: 62187
output: 31
thoughts: 0
|
| 75/2 |
2026/07/28 01:31 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile: Name:serial_ctrl_register_port]
Results: map[SourceCode: 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: }
SourceFile:drivers/tty/serial/serial_ctrl.c]
|
| 76/2 |
2026/07/28 01:31 |
llm |
expert |
0m
Model:
Tokens:
input: 62366
output: 31
thoughts: 0
|
| 77/2 |
2026/07/28 01:31 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile: Name:serial_core_register_port]
Results: map[SourceCode: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:
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: }
SourceFile:drivers/tty/serial/serial_core.c]
|
| 78/2 |
2026/07/28 01:31 |
llm |
expert |
0m
Model:
Tokens:
input: 63202
output: 33
thoughts: 15
Thoughts: Okay, let's dive into this. Here's my thought process on tackling this particular function:
**Analyzing `serial_core_add_one_port`**
Alright, the first step is always to verify the function name: `serial_core_add_one_port`. That name is pretty straightforward, which is helpful. I immediately think about what it likely *does*. Given the context, and the standard naming conventions, I'm assuming it adds a single serial port to a core system or framework. My expertise immediately tells me this implies some interaction with the core serial driver and hardware abstraction. I need to understand exactly where this addition fits into the broader serial port management scheme. Is this called during driver initialization, device probe, or some other point? Understanding the lifecycle of this function is vital. I'll need to look at the surrounding code to determine its context. I wonder what data structures are involved...a port structure, likely some kind of list or array to store all ports, potentially some locking mechanisms to protect against race conditions. I'll look for those in the code.
|
| 79/2 |
2026/07/28 01:31 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile: 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]
|
| 80/2 |
2026/07/28 01:31 |
llm |
expert |
0m
Model:
Tokens:
input: 65021
output: 37
thoughts: 1188
Thoughts: **Analysis of the Serial Port Driver Vulnerability**
Okay, let's break this down. `serial_core_add_one_port` has a few failure paths. `-EINVAL` is impossible in normal operation. `-ENOMEM` is the real concern, caused by memory allocation failures in `kasprintf` or `kzalloc_objs`. The other failure, from `tty_port_register_device_attr_serdev`, just logs and returns, so it's not a direct cause of a vulnerability. My initial thought is that memory allocation failures are the hard part. They're tricky to trigger reliably without specific fault injection techniques. Invalid arguments are also ruled out as the `uport->line` and `state->uart_port` variables are assigned by other functions.
So, realistically, we're looking at a race condition during memory allocation within a hot-plug or unbind operation. Can an attacker control `uport->line` on a hot-plug? No, it's determined internally. This significantly limits the attack surface to memory exhaustion scenarios.
My intuition says it's practically impossible to exploit, since we're almost solely dependent on `-ENOMEM`, a very difficult scenario to reproduce in a live system. But, for the sake of the analysis, let's assume it *does* happen. Is it exploitable *if* triggered? Yes. Can unprivileged processes, user namespaces, filesystems or networks trigger it? No. Is it a Remote Trigger? No.
Ah, but now we get to the core: a Peripheral Trigger *might* be possible. A malicious PCI or Thunderbolt device could initiate hot-plug/unplug operations, combined with memory pressure, potentially leading to the bug. However, that usually means data sent in to the device. So the bug here is that the memory failure is in the *core driver registration* function.
Wait a minute, it's actually in `serial8250_unregister_port` which is called during hot-unplug. If the re-registration as an ISA port fails because of `-ENOMEM`, it creates a dangling pointer. Then, a *subsequent* hot-plug of the *same* device, or even a *different* device claiming the same port, triggers a UAF. Okay, this is key.
So, it *is* a Peripheral Trigger and I'm marking it as *true*. I have to take into account the fact that hot-plugging is the main way to reach this code path.
Let me make sure I'm following. Does `serial8250_unregister_port` re-register as an ISA port? Yes, if `serial8250_isa_devs` is not NULL. If a PCI serial device is hot-unplugged, `serial8250_unregister_port` is called. It calls `uart_remove_one_port` and then `uart_add_one_port` with `dev = &serial8250_isa_devs->dev`. If `uart_add_one_port` fails (e.g., `-ENOMEM`), then `port->port_dev` becomes dangling. If the attacker hot-plugs the PCI device again, `serial8250_register_8250_port` is called. It sees the same port, sees `uart->port.dev` is set (to `&serial8250_isa_devs->dev` from the previous failure), and calls `uart_remove_one_port`, which triggers the UAF.
Yes, a Peripheral Trigger is possible if the attacker can cause ENOMEM. I'll stick with marking Peripheral Trigger as true.
Let's do a final check. Are there other ways `serial_core_add_one_port` might fail? What if `serial_core_port_device_add` fails? If it fails, `port->port_dev` is set to the result of `serial_base_port_add`. So, what if `serial_base_port_add` fails? Then `port->port_dev` is not modified. Okay, next up: `serial_base_match_and_update_preferred_console`. If that fails, it jumps to `err_unregister_port_dev`, which *frees* `port->port_dev`, leaving it dangling! Can this failure *also* happen without ENOMEM? Okay. *That* might be the path.
|
| 81/2 |
2026/07/28 01:32 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile: Name:serial_base_match_and_update_preferred_console]
Results: map[SourceCode: 217: /**
218: * serial_base_match_and_update_preferred_console - Match and update a preferred console
219: * @drv: Serial port device driver
220: * @port: Serial port instance
221: *
222: * Tries to match and update the preferred console for a serial port for
223: * the kernel command line option console=DEVNAME:0.0.
224: *
225: * Cannot be called early for ISA ports, depends on struct device.
226: *
227: * Return: 0 on success, negative error code on failure.
228: */
229: int serial_base_match_and_update_preferred_console(struct uart_driver *drv,
230: struct uart_port *port)
231: {
232: const char *port_match __free(kfree) = NULL;
233: int ret;
234:
235: port_match = kasprintf(GFP_KERNEL, "%s:%d.%d", dev_name(port->dev),
236: port->ctrl_id, port->port_id);
237: if (!port_match)
238: return -ENOMEM;
239:
240: ret = match_devname_and_update_preferred_console(port_match,
241: drv->dev_name,
242: port->line);
243: if (ret == -ENOENT)
244: return 0;
245:
246: return ret;
247: }
SourceFile:drivers/tty/serial/serial_base_bus.c]
|
| 82/2 |
2026/07/28 01:32 |
llm |
expert |
0m
Model:
Tokens:
input: 57616
output: 31
thoughts: 0
|
| 83/2 |
2026/07/28 01:32 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile: Name:serial_core_ctrl_find]
Results: map[SourceCode: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: }
SourceFile:drivers/tty/serial/serial_core.c]
|
| 84/2 |
2026/07/28 01:32 |
llm |
expert |
0m
Model:
Tokens:
input: 68136
output: 36
thoughts: 53
Thoughts: Okay, let's break this down.
**Analyzing `serial_base_match_and_update_preferred_console`**
Right, so `serial_base_match_and_update_preferred_console`... if `kasprintf` in it fails, I know it's going to return `-ENOMEM`. That's standard error handling. But it calls `match_devname_and_update_preferred_console` inside, and that's the part I need to really understand. What possible return values does *that* function have? I need to dig into `match_devname_and_update_preferred_console`'s documentation or source code to know all the possible outcomes and their implications for the larger function. Essentially, I'm tracing the data flow to ensure proper error handling and understand the overall logic.
|
| 85/2 |
2026/07/28 01:32 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile: Name:match_devname_and_update_preferred_console]
Results: map[SourceCode:2709: /**
2710: * match_devname_and_update_preferred_console - Update a preferred console
2711: * when matching devname is found.
2712: * @devname: DEVNAME:0.0 style device name
2713: * @name: Name of the corresponding console driver, e.g. "ttyS"
2714: * @idx: Console index, e.g. port number.
2715: *
2716: * The function checks whether a device with the given @devname is
2717: * preferred via the console=DEVNAME:0.0 command line option.
2718: * It fills the missing console driver name and console index
2719: * so that a later register_console() call could find (match)
2720: * and enable this device.
2721: *
2722: * It might be used when a driver subsystem initializes particular
2723: * devices with already known DEVNAME:0.0 style names. And it
2724: * could predict which console driver name and index this device
2725: * would later get associated with.
2726: *
2727: * Return: 0 on success, negative error code on failure.
2728: */
2729: int match_devname_and_update_preferred_console(const char *devname,
2730: const char *name,
2731: const short idx)
2732: {
2733: struct console_cmdline *c = console_cmdline;
2734: int i;
2735:
2736: if (!devname || !strlen(devname) || !name || !strlen(name) || idx < 0)
2737: return -EINVAL;
2738:
2739: for (i = 0; i < MAX_CMDLINECONSOLES && (c->name[0] || c->devname[0]);
2740: i++, c++) {
2741: if (!strcmp(devname, c->devname)) {
2742: pr_info("associate the preferred console \"%s\" with \"%s%d\"\n",
2743: devname, name, idx);
2744: strscpy(c->name, name);
2745: c->index = idx;
2746: return 0;
2747: }
2748: }
2749:
2750: return -ENOENT;
2751: }
SourceFile:kernel/printk/printk.c]
|
| 86/2 |
2026/07/28 01:32 |
llm |
expert |
0m
Model:
Tokens:
input: 69120
output: 30
thoughts: 913
Thoughts: **My Analysis of the Serial Port Vulnerability**
Okay, so I've got this vulnerability laid out. The core of it seems to be in `serial_base_match_and_update_preferred_console`. It only fails in a couple of ways: invalid arguments (which, let's be honest, aren't likely the issue here), an "not found" which we're not seeing, or the dreaded `-ENOMEM`, which means a memory allocation failure. So, we're looking at a use-after-free scenario triggered by a memory allocation issue. This means it's not a straightforward exploit. We need to cause the kernel to fail to allocate memory at just the right moment.
Here's the breakdown:
* **Exploitable:** Absolutely. This is a use-after-free, specifically of a `struct serial_port_device` (2048 bytes). Critically, this structure contains a `struct device`. If we control that `struct device`, we can achieve arbitrary code execution by manipulating function pointers within it (`dev->release`, `dev->bus`, etc.). That's a huge win.
* **Denial of Service (DoS):** Yes, it crashes the kernel. Always a concern, but less interesting than a full exploit.
* **Unprivileged:** No. Triggering the vulnerability requires unbinding a device through sysfs or hot-plugging a PCI/PNP device. Both of those actions require root privileges. No dice for an unprivileged user.
* **UserNamespace:** Also no. We're dealing with sysfs and PCI hotplug, which are inaccessible in user namespaces.
* **VMGuestTrigger, VMHostTrigger, NetworkTrigger, RemoteTrigger, FilesystemTrigger:** Nope, none of these seem viable.
* **PeripheralTrigger:** Yes! If an attacker can hot-plug and hot-unplug a malicious PCI/Thunderbolt device and, crucially, also induce a memory allocation failure at the opportune moment, they could trigger the bug. This is the most likely avenue of attack.
But wait, hold on. Is there *another* way to get `serial_core_register_port` called? What if a driver calls `uart_add_one_port` directly? Yes, a lot of drivers do exactly that in their `probe` function. If `uart_add_one_port` fails, the `probe` function usually returns an error, and the device isn't bound to the driver. This prevents `remove` from being called, and thus `uart_remove_one_port`.
Ah, but *that's* interesting. If the port is statically allocated (or globally managed), the dangling pointer persists after the error. Then, when the port is reused, or when `serial8250_unregister_port` is called on it, the dangling pointer is dereferenced! Bingo! The vulnerability is specific to drivers that keep the `struct uart_port` around after `uart_add_one_port` fails *and* later call `uart_remove_one_port` on it. The 8250 driver is the key example. It allocates these ports statically, in that `serial8250_ports` array, which means they're not freed when `probe` fails. That pointer lives on after `probe` has failed!
Now, let's see if there are any other drivers that do this...I'll check if any other driver has a global array of ports, like `amba-pl011.c` with its `amba_ports[UART_NR]`. Let's dig into `pl011_probe` and see what we find!
|
| 87/2 |
2026/07/28 01:32 |
tool |
codesearch-definition-source |
0m
Error:
Args: map[ContextFile: Name:pl011_probe]
Results: map[SourceCode: SourceFile:]
|
| 88/2 |
2026/07/28 01:32 |
llm |
expert |
0m
Model:
Tokens:
input: 70875
output: 32
thoughts: 14
Thoughts: Okay, let's dive into this. Here's how I'd approach summarizing the "Thoughts" based on my expertise:
**Checking `pl011_register_port`**
Alright, my immediate thought is to zero in on this `pl011_register_port`. Given my background, I'm already anticipating what this probably is: it's likely a function call, and the name strongly suggests we're dealing with registering a serial port associated with a PL011 UART controller. That would imply some kind of device driver context. It's the standard ARM UART, so I immediately start mentally pulling up the relevant datasheets and driver code I've worked with in the past. I need to understand what this function does at its core: is it allocating memory for the port structure? Is it mapping registers? Does it set up interrupt handlers? Most likely it does a combination of these things and other initialization procedures. I need to investigate the arguments that are passed to this function. What kind of data is passed through these arguments? Is there a device pointer being passed? A base address? What are the implications of the configuration? With more information, I'll be able to quickly understand the function's purpose.
|
| 89/2 |
2026/07/28 01:32 |
tool |
codesearch-definition-source |
0m
Error:
Args: map[ContextFile: Name:pl011_register_port]
Results: map[SourceCode: SourceFile:]
|
| 90/2 |
2026/07/28 01:32 |
llm |
expert |
0m
Model:
Tokens:
input: 71201
output: 46
thoughts: 53
Thoughts: Okay, here's my interpretation of that thought process, tailored for an expert audience:
**Grasping the Significance**
Alright, the grep output just jumped out at me. I'm seeing that line: `drivers/tty/serial/amba-pl011.c=2969=static int pl011_register_port(struct uart_amba_port *uap)`. Instantly, a few things click. I've got a line number there, `2969`, which *usually* indicates a reasonably complex function, probably core to the driver's operation. More importantly, it's `pl011_register_port`. That name is immediately recognizable – this *has* to be the registration function for the PL011 UART driver. That's a critical entry point for device initialization and driver binding, so finding that here is significant. I need to take a closer look at what's going on *inside* that function. Given the naming convention and the file, it's highly likely this is the AMBA PL011 driver's port registration function. Knowing that, and seeing that it's `static`, informs further investigation. This suggests that the scope of this function is limited to within this C file; it isn't expected to be externally called.
|
| 91/2 |
2026/07/28 01:32 |
tool |
codesearch-definition-source |
0m
Error:
Args: map[ContextFile:drivers/tty/serial/amba-pl011.c Name:pl011_register_port]
Results: map[SourceCode: SourceFile:]
|
| 92/2 |
2026/07/28 01:32 |
llm |
expert |
0m
Model:
Tokens:
input: 71603
output: 42
thoughts: 17
Thoughts: Okay, here's the summary, expressed as though I'm the one having the thoughts, and tailored for an expert audience:
**Static Function Suspicions & Initial Investigation**
Right, so I'm looking at this code and something's just pinging… it's giving me a strong hunch that this thing might be implemented as a static function. That feels like the most likely scenario, at least initially, given what I know about the problem domain. Let's not jump to conclusions, though. Best to just go ahead and start by examining the input file to ensure everything makes sense from there. No need to start complex analysis just yet. Let's begin by doing a simple examination of the file.
|
| 93/2 |
2026/07/28 01:32 |
tool |
grepper |
0m
Args: map[Expression:pl011_register_port PathPrefix:drivers/tty/serial/amba-pl011.c]
Results: map[Output:drivers/tty/serial/amba-pl011.c=2940=static int pl011_setup_port(struct device *dev, struct uart_amba_port *uap,
--
drivers/tty/serial/amba-pl011.c-2968-
drivers/tty/serial/amba-pl011.c:2969:static int pl011_register_port(struct uart_amba_port *uap)
drivers/tty/serial/amba-pl011.c-2970-{
--
drivers/tty/serial/amba-pl011.c=3003=static int pl011_probe(struct amba_device *dev, const struct amba_id *id)
--
drivers/tty/serial/amba-pl011.c-3057-
drivers/tty/serial/amba-pl011.c:3058: return pl011_register_port(uap);
drivers/tty/serial/amba-pl011.c-3059-}
--
drivers/tty/serial/amba-pl011.c=3109=static int sbsa_uart_probe(struct platform_device *pdev)
--
drivers/tty/serial/amba-pl011.c-3162-
drivers/tty/serial/amba-pl011.c:3163: return pl011_register_port(uap);
drivers/tty/serial/amba-pl011.c-3164-}
]
|
| 94/2 |
2026/07/28 01:32 |
llm |
expert |
0m
Model:
Tokens:
input: 72205
output: 43
thoughts: 0
|
| 95/2 |
2026/07/28 01:32 |
tool |
read-file |
0m
Args: map[File:drivers/tty/serial/amba-pl011.c FirstLine:2969 LineCount:40]
Results: map[Contents:2969: static int pl011_register_port(struct uart_amba_port *uap)
2970: {
2971: int ret, i;
2972:
2973: /* Ensure interrupts from this UART are masked and cleared */
2974: pl011_write(0, uap, REG_IMSC);
2975: pl011_write(0xffff, uap, REG_ICR);
2976:
2977: if (!amba_reg.state) {
2978: ret = uart_register_driver(&amba_reg);
2979: if (ret < 0) {
2980: dev_err(uap->port.dev,
2981: "Failed to register AMBA-PL011 driver\n");
2982: for (i = 0; i < ARRAY_SIZE(amba_ports); i++)
2983: if (amba_ports[i] == uap)
2984: amba_ports[i] = NULL;
2985: return ret;
2986: }
2987: }
2988:
2989: ret = uart_add_one_port(&amba_reg, &uap->port);
2990: if (ret)
2991: pl011_unregister_port(uap);
2992:
2993: return ret;
2994: }
2995:
2996: static const struct serial_rs485 pl011_rs485_supported = {
2997: .flags = SER_RS485_ENABLED | SER_RS485_RTS_ON_SEND | SER_RS485_RTS_AFTER_SEND |
2998: SER_RS485_RX_DURING_TX,
2999: .delay_rts_before_send = 1,
3000: .delay_rts_after_send = 1,
3001: };
3002:
3003: static int pl011_probe(struct amba_device *dev, const struct amba_id *id)
3004: {
3005: struct uart_amba_port *uap;
3006: struct vendor_data *vendor = id->data;
3007: int portnr, ret;
3008: u32 val;
]
|
| 96/2 |
2026/07/28 01:32 |
llm |
expert |
0m
Model:
Tokens:
input: 72916
output: 43
thoughts: 57
Thoughts: Here's my summary, as the expert:
**Investigating the PL011 UART Driver's Port Handling**
Okay, so I'm digging into how the PL011 driver handles UART port registration and unregistration. I see that `pl011_register_port` is the key function. It's calling `uart_add_one_port` to, well, add a new port. The interesting thing is the error handling: if `uart_add_one_port` throws an error, `pl011_unregister_port(uap)` gets called to clean things up. This implies a straightforward, symmetrical registration/unregistration process, which is good. Now, I want to take a closer look at that unregistration function, `pl011_unregister_port`. I need to ensure that it's doing the right thing, especially if `uart_add_one_port` failed part way through and there is a partial registration. I'm going to verify it correctly reverts any changes and leaves the system in a clean state. Let's see... what's inside that function?
|
| 97/2 |
2026/07/28 01:33 |
tool |
read-file |
0m
Args: map[File:drivers/tty/serial/amba-pl011.c FirstLine:2940 LineCount:29]
Results: map[Contents:2940: static int pl011_setup_port(struct device *dev, struct uart_amba_port *uap,
2941: struct resource *mmiobase, int index)
2942: {
2943: void __iomem *base;
2944: int ret;
2945:
2946: base = devm_ioremap_resource(dev, mmiobase);
2947: if (IS_ERR(base))
2948: return PTR_ERR(base);
2949:
2950: index = pl011_probe_dt_alias(index, dev);
2951:
2952: uap->port.dev = dev;
2953: uap->port.mapbase = mmiobase->start;
2954: uap->port.membase = base;
2955: uap->port.fifosize = uap->fifosize;
2956: uap->port.has_sysrq = IS_ENABLED(CONFIG_SERIAL_AMBA_PL011_CONSOLE);
2957: uap->port.flags = UPF_BOOT_AUTOCONF;
2958: uap->port.line = index;
2959:
2960: ret = uart_get_rs485_mode(&uap->port);
2961: if (ret)
2962: return ret;
2963:
2964: amba_ports[index] = uap;
2965:
2966: return 0;
2967: }
2968:
]
|
| 98/2 |
2026/07/28 01:33 |
llm |
expert |
0m
Model:
Tokens:
input: 73713
output: 42
thoughts: 15
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Debugging a Missing Function Call**
Okay, so I'm in the middle of this code, and I've hit a snag. I'm expecting to see a call to `pl011_unregister_port` somewhere in this module, but it's just not there. I need to figure out where that unregistration is happening. Is it getting called in some obscure conditional path I haven't tracked yet? Or perhaps it's being done at a higher level, potentially within some device driver framework or subsystem?
Maybe I missed it because it is called through a macro or a function pointer? I should probably run a quick symbol search just to be sure I am not missing a renamed function, that is the best approach at this point. I need to make sure I am not looking at an older code version. It is also good to verify the include paths to make sure I am looking at the right headers. I may also need to check the dependencies of this module. This is all standard debugging procedure, but I need to make a solid assessment before moving forward.
|
| 99/2 |
2026/07/28 01:33 |
tool |
grepper |
0m
Args: map[Expression:pl011_unregister_port PathPrefix:drivers/tty/serial/amba-pl011.c]
Results: map[Output:drivers/tty/serial/amba-pl011.c=2880=static int pl011_probe_dt_alias(int index, struct device *dev)
--
drivers/tty/serial/amba-pl011.c-2912-/* unregisters the driver also if no more ports are left */
drivers/tty/serial/amba-pl011.c:2913:static void pl011_unregister_port(struct uart_amba_port *uap)
drivers/tty/serial/amba-pl011.c-2914-{
--
drivers/tty/serial/amba-pl011.c=2969=static int pl011_register_port(struct uart_amba_port *uap)
--
drivers/tty/serial/amba-pl011.c-2990- if (ret)
drivers/tty/serial/amba-pl011.c:2991: pl011_unregister_port(uap);
drivers/tty/serial/amba-pl011.c-2992-
--
drivers/tty/serial/amba-pl011.c=3061=static void pl011_remove(struct amba_device *dev)
--
drivers/tty/serial/amba-pl011.c-3065- uart_remove_one_port(&amba_reg, &uap->port);
drivers/tty/serial/amba-pl011.c:3066: pl011_unregister_port(uap);
drivers/tty/serial/amba-pl011.c-3067-}
--
drivers/tty/serial/amba-pl011.c=3166=static void sbsa_uart_remove(struct platform_device *pdev)
--
drivers/tty/serial/amba-pl011.c-3170- uart_remove_one_port(&amba_reg, &uap->port);
drivers/tty/serial/amba-pl011.c:3171: pl011_unregister_port(uap);
drivers/tty/serial/amba-pl011.c-3172-}
]
|
| 100/2 |
2026/07/28 01:33 |
llm |
expert |
0m
Model:
Tokens:
input: 74566
output: 43
thoughts: 0
|
| 101/2 |
2026/07/28 01:33 |
tool |
read-file |
0m
Args: map[File:drivers/tty/serial/amba-pl011.c FirstLine:2913 LineCount:25]
Results: map[Contents:2913: static void pl011_unregister_port(struct uart_amba_port *uap)
2914: {
2915: int i;
2916: bool busy = false;
2917:
2918: for (i = 0; i < ARRAY_SIZE(amba_ports); i++) {
2919: if (amba_ports[i] == uap)
2920: amba_ports[i] = NULL;
2921: else if (amba_ports[i])
2922: busy = true;
2923: }
2924: pl011_dma_remove(uap);
2925: if (!busy)
2926: uart_unregister_driver(&amba_reg);
2927: }
2928:
2929: static int pl011_find_free_port(void)
2930: {
2931: int i;
2932:
2933: for (i = 0; i < ARRAY_SIZE(amba_ports); i++)
2934: if (!amba_ports[i])
2935: return i;
2936:
2937: return -EBUSY;
]
|
| 102/2 |
2026/07/28 01:33 |
llm |
expert |
0m
Model:
Tokens:
input: 74965
output: 66
thoughts: 529
Thoughts: **Analysis of the UAF vulnerability**
Okay, so here's the breakdown of this potential UAF issue. My initial concern was centered around the PL011 driver, specifically the `pl011_unregister_port` function. My thinking went something like this: `pl011_unregister_port` just clears the `amba_ports[i]` entry, which means that it doesn't call `uart_remove_one_port`. Therefore, if `uart_add_one_port` fails during the registration process, `pl011_register_port` will call `pl011_unregister_port` and clean up the `amba_ports` entry. Then `pl011_probe` returns an error. This means the device is never actually bound, so `pl011_remove` isn't called, and consequently, `uart_remove_one_port` isn't executed. That path is clean; there's no UAF there.
The real problem lies in the `serial8250_unregister_port` function. It looks like it calls `uart_add_one_port` but ignores the error that's returned. This means that a port might be left hanging in `serial8250_ports` with a dangling `port_dev` pointer. Then, if `serial8250_unregister_port` (or `serial8250_register_8250_port`) is called again, it ends up calling `uart_remove_one_port` with this invalid pointer. That's the core of the UAF.
So, the analysis checks out. The issue is exploitable, and it could lead to a Denial of Service. However, it's not unprivileged. No user namespace, VM guest trigger, VM host trigger, network trigger, remote trigger, or filesystem trigger is possible. However, it *is* triggered by a peripheral, which requires memory pressure or fault injection to happen, but this is reachable via hotplug.
Now, I need to make sure I'm not missing anything. Are there any other ways to trigger `serial8250_unregister_port`? I know that `serial8250_remove` is called when the `serial8250` platform device is removed, but an unprivileged user can't remove a platform device. Same goes for `serial_pnp_remove` (PNP device) and `pciserial_detach_ports` (PCI device) – all these device removal functions are off-limits to unprivileged users. I've also checked `lpss8250_remove`, `mid8250_remove`, and `pericom8250_remove`, and these are all tied to PCI or platform devices as well.
Therefore, the exploit is definitely not accessible to unprivileged users, so I'll set the results accordingly.
|
| 103/2 |
2026/07/28 01:33 |
tool |
set-results |
0m
Args: map[DenialOfService:true Exploitable:true FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:true RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
Results: map[DenialOfService:true Exploitable:true FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:true RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
|
| 104/2 |
2026/07/28 01:33 |
llm |
expert |
0m
Model:
Tokens:
input: 76252
output: 697
thoughts: 357
Thoughts: **Final Thoughts on the 8250 Serial Driver Vulnerability**
Okay, the analysis is complete, and it's time to craft the response. The core issue is a textbook use-after-free within the 8250 serial driver subsystem. I've pinpointed it to a flaw in how `uart_add_one_port` failures are handled. Specifically, when `uart_add_one_port` bails out, perhaps because memory allocation goes sideways, it leaves the `port->port_dev` pointer dangling.
The crucial part is how `serial8250_unregister_port` behaves. It happily ignores the return value from `uart_add_one_port`. This means that if the registration fails, the faulty port entry persists in `serial8250_ports`, with its hazardous, dangling `port_dev`. Now, a later call to `serial8250_unregister_port`, or even `serial8250_register_8250_port` for the same port, triggers the deadly `uart_remove_one_port`. That function proceeds to dereference the dangling `port_dev`, fishing for the controller device. Bang, use-after-free.
The freed object here is a `struct serial_port_device`, a hefty 2048-byte chunk, which contains a `struct device`. The implications of this are pretty clear. Taking control of this object effectively hands an attacker control of the underlying `struct device`. That's bad news, since the `struct device` contains function pointers (`release`, `bus`, etc.). These function pointers open the door for a classic control flow hijack and the ability to execute arbitrary code.
Of course, the exploit chain isn't trivial. Getting this bug to bite requires the ability to unbind the driver via sysfs or to hot-plug/unplug a PCI or PNP serial device. This means root privileges are needed. No easy ride for unprivileged users or those trapped in user namespaces. The attacker would likely need to pull off some serious memory pressure to cause that initial allocation failure. The only theoretical vector I can see is a malicious peripheral like Thunderbolt or PCIe hotplug, which, combined with the memory-allocation bug, could be a real threat.
|
| 105/1 |
2026/07/28 01:33 |
action |
format-explanation |
0m
Results: map[Explanation:**Bug Analysis:**
The bug is a Use-After-Free (UAF) in the 8250 serial driver subsystem,
specifically within `serial_core_unregister_port()`.
**Root Cause:**
When a serial port is registered via `uart_add_one_port()` (which calls
`serial_core_register_port()`), a `struct serial_port_device` is allocated and
assigned to `port->port_dev`. If the subsequent port registration steps fail
(e.g., due to a memory allocation failure in `serial_core_add_one_port()`), the
error handling path correctly frees the `port_dev` object. However, it fails to
clear the `port->port_dev` pointer, leaving it dangling.
In the 8250 driver, `serial8250_unregister_port()` is called when a device is
unbound or hot-unplugged. This function unregisters the port and then attempts
to re-register it as a fallback ISA port by calling `uart_add_one_port()`.
Crucially, it ignores the return value of `uart_add_one_port()`. If this
re-registration fails, the port remains in the global `serial8250_ports` array
with the dangling `port_dev` pointer.
If the device is unbound again, or if another device is hot-plugged and reuses
the same port slot, `uart_remove_one_port()` is called. This function eventually
calls `serial_core_unregister_port()`, which dereferences the dangling
`port->port_dev` pointer to retrieve the controller device, triggering the UAF.
**Exploitability:**
The bug is exploitable. The freed object is a `struct serial_port_device`
allocated from the `kmalloc-2k` cache. This structure contains an embedded
`struct device`. If an attacker can spray the heap and control the contents of
this freed 2048-byte object, they can control the `struct device` fields. When
`device_del()` and `put_device()` are called on this controlled object, the
attacker can achieve arbitrary code execution by hijacking function pointers
associated with the device (such as `dev->release`, `dev->bus`, or `dev->class`
callbacks).
**Accessibility:**
Despite being exploitable, the bug is **not accessible to unprivileged users**.
Triggering the bug requires one of the following actions:
1. Unbinding the serial driver via sysfs
(`/sys/bus/platform/drivers/serial8250/unbind`), which is restricted to `root`
(mode `0200`).
2. Hot-plugging and hot-unplugging a physical PCI, PNP, or LPSS serial device.
Neither of these actions can be performed by an unprivileged user, nor can they
be performed from within an unprivileged user namespace (sysfs `unbind` files
are not writable, and PCI hotplug is restricted).
The bug could theoretically be triggered via a malicious peripheral (e.g., a
Thunderbolt/PCIe device that rapidly hot-plugs and hot-unplugs itself), provided
the attacker can also induce a memory allocation failure at the exact right
moment during the driver's registration phase.]
|