| 0/0 |
2026/04/07 00:02 |
flow |
moderation |
25m
Results: map[Actionable:true Confident:true Explanation:The bug report is a KASAN slab-use-after-free in `kbd_keycode`, which is called
from `kbd_event` in the keyboard handler of the VT subsystem.
### Analysis of the Stack Traces:
1. **Access Stack Trace:**
The crash occurs in `kbd_keycode` at `drivers/tty/vt/keyboard.c:1435`.
```c
1434: (!vc_kbd_mode(kbd, VC_REPEAT) ||
1435: (tty && !L_ECHO(tty) && tty_chars_in_buffer(tty))))
{
```
The access is a read of size 4 at offset 860 bytes into the object.
`L_ECHO(tty)` is a macro that accesses `tty->termios.c_lflag`.
In `struct tty_struct`, `termios` is at offset 848 bytes, and `c_lflag` is
at offset 12 bytes within `struct ktermios`, totaling 860 bytes. This
perfectly matches the KASAN report.
2. **Allocation Stack Trace:**
The object was allocated in `alloc_tty_struct` (called via `tty_open` ->
`tty_init_dev`). This confirms the object is indeed a `struct tty_struct`.
3. **Free Stack Trace:**
The object was freed via a workqueue (`process_one_work` ->
`release_one_tty` -> `kfree`).
The "Last potentially related work creation" shows the work was queued in
`tty_release_struct` (called via `tty_release` when the last file descriptor
to the TTY is closed).
### Root Cause:
The function `kbd_keycode` obtains a pointer to the TTY associated with the
foreground console via `tty = vc->port.tty` (line 1381). However, it does so
without any locking or incrementing the TTY's reference count (kref).
If another process closes the TTY concurrently, `tty_release` will eventually
call `tty_release_struct`, which calls `release_tty`. `release_tty` calls
`tty->ops->shutdown(tty)` (which for VT is `con_shutdown`, setting `vc->port.tty
= NULL`) and then drops the last kref of the TTY, leading to its destruction in
a workqueue.
If `kbd_keycode` has already read the `tty` pointer before it was set to NULL,
it will continue to use it, leading to a use-after-free when it accesses
`tty->termios.c_lflag` at line 1435.
### Conclusion:
The report is self-consistent and provides all necessary information (access,
allocation, and free stacks) to identify the race condition and the affected
object. The subsystem (TTY/VT) and the specific code paths are clearly
identified.
The report is actionable.]
|
| 1/1 |
2026/04/07 00:02 |
action |
kernel-checkouter |
0m
Results: map[KernelSrc:/app/workdir/cache/src/8a6829b8a55a89259770dcf9d264c19c6a19853b]
|
| 2/1 |
2026/04/07 00:03 |
action |
kernel-builder |
13m
Results: map[KernelObj:/app/workdir/cache/build/8b1e50d9af40413728d8a2f5f22ea7c8d5bcecdf]
|
| 3/1 |
2026/04/07 00:16 |
action |
codesearch-prepare |
9m
Results: map[Index:codesearch-index]
|
| 4/1 |
2026/04/07 00:26 |
agent |
expert |
1m
Model:
Results: map[Actionable:true Confident:true]
Instruction:
You are an experienced Linux kernel developer tasked with determining if the given kernel bug
report is actionable or not. Actionable means that it contains enough info to root cause
the underlying bug, and that the report is self-consistent and makes sense, rather than
e.g. a one-off nonsensical crash induced by a previous memory corruption.
The bug report is about a use-after-free bug generated by KASAN tool.
It should contain 3 stack traces: the bad memory access stack, the heap block allocation stack,
and the heap block free stack. If the report does not contain 3 stacks, it's not actionable.
All 3 stack traces should be related to the same object type,
and usually be in the same kernel subsystem (at least leaf stack frames).
An example of an actionable and consistent report would be: first access stack relates
to an access to a field of struct Foo, allocation/free stacks relate to allocation/free
of the struct Foo.
In inconsistent/nonsensical reports an access may be to a struct Foo, but allocation
stack allocates a different structure in a different subsystem.
Look for other suspicious signals/inconsistencies that can make this report hard to
debug/understand.
In the final reply explain why you think the report is self-consistent and actionable,
or why it's inconsistent and/or not actionable.
Use the provided tools to confirm any assumptions, variables/fields being accessed, etc.
In particular, don't make assumptions about the kernel source code,
use codesearch tools to read the actual source code.
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt:
The bug report is:
==================================================================
BUG: KASA[ 730.192938][ T7920] BUG: KASAN: slab-use-after-free in kbd_keycode drivers/tty/vt/keyboard.c:1435 [inline]
BUG: KASA[ 730.192938][ T7920] BUG: KASAN: slab-use-after-free in kbd_event+0x3330/0x40d0 drivers/tty/vt/keyboard.c:1515
Read of size 4 at addr ffff88806944e35c by task syz.2.13424/7920
CPU: 1 UID: 0 PID: 7920 Comm: syz.2.13424 Tainted: G L syzkaller #0 PREEMPT_{RT,(full)}
Tainted: [L]=SOFTLOCKUP
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 03/18/2026
Call Trace:
<TASK>
dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
print_address_description mm/kasan/report.c:378 [inline]
print_report+0xba/0x230 mm/kasan/report.c:482
kasan_report+0x117/0x150 mm/kasan/report.c:595
kbd_keycode drivers/tty/vt/keyboard.c:1435 [inline]
kbd_event+0x3330/0x40d0 drivers/tty/vt/keyboard.c:1515
input_handle_events_default+0xd4/0x1a0 drivers/input/input.c:2541
input_pass_values+0x288/0x890 drivers/input/input.c:128
input_event_dispose+0x330/0x6b0 drivers/input/input.c:342
input_inject_event+0x1d8/0x330 drivers/input/input.c:424
evdev_write+0x328/0x4c0 drivers/input/evdev.c:528
vfs_write+0x2a3/0xba0 fs/read_write.c:686
ksys_write+0x156/0x270 fs/read_write.c:740
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x14d/0xf80 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f5547bac819
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:00007f5545dfe028 EFLAGS: 00000246 ORIG_RAX: 0000000000000001
RAX: ffffffffffffffda RBX: 00007f5547e25fa0 RCX: 00007f5547bac819
RDX: 0000000000002250 RSI: 0000200000000040 RDI: 0000000000000003
RBP: 00007f5547c42c91 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
R13: 00007f5547e26038 R14: 00007f5547e25fa0 R15: 00007ffe04f8cf48
</TASK>
Allocated by task 7915:
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+0x3a6/0x690 mm/slub.c:5380
kmalloc_noprof include/linux/slab.h:950 [inline]
kzalloc_noprof include/linux/slab.h:1188 [inline]
alloc_tty_struct+0xa6/0x7b0 drivers/tty/tty_io.c:3102
tty_init_dev+0x59/0x4d0 drivers/tty/tty_io.c:1400
tty_open_by_driver drivers/tty/tty_io.c:2073 [inline]
tty_open+0x86e/0xd80 drivers/tty/tty_io.c:2120
chrdev_open+0x4d0/0x5f0 fs/char_dev.c:411
do_dentry_open+0x83d/0x13e0 fs/open.c:949
vfs_open+0x3b/0x350 fs/open.c:1081
do_open fs/namei.c:4677 [inline]
path_openat+0x2e43/0x38a0 fs/namei.c:4836
do_file_open+0x23e/0x4a0 fs/namei.c:4865
do_sys_openat2+0x113/0x200 fs/open.c:1366
do_sys_open fs/open.c:1372 [inline]
__do_sys_openat fs/open.c:1388 [inline]
__se_sys_openat fs/open.c:1383 [inline]
__x64_sys_openat+0x138/0x170 fs/open.c:1383
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x14d/0xf80 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
Freed by task 15095:
kasan_save_stack mm/kasan/common.c:57 [inline]
kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
kasan_save_free_info+0x46/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:2685 [inline]
slab_free mm/slub.c:6165 [inline]
kfree+0x1c1/0x6c0 mm/slub.c:6483
process_one_work kernel/workqueue.c:3276 [inline]
process_scheduled_works+0xb6e/0x18c0 kernel/workqueue.c:3359
worker_thread+0xa53/0xfc0 kernel/workqueue.c:3440
kthread+0x388/0x470 kernel/kthread.c:436
ret_from_fork+0x51e/0xb90 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
Last potentially related work creation:
kasan_save_stack+0x3e/0x60 mm/kasan/common.c:57
kasan_record_aux_stack+0xbd/0xd0 mm/kasan/generic.c:556
insert_work+0x3d/0x330 kernel/workqueue.c:2199
__queue_work+0xcc6/0xff0 kernel/workqueue.c:2354
queue_work_on+0x106/0x1d0 kernel/workqueue.c:2405
tty_release_struct+0xb8/0xd0 drivers/tty/tty_io.c:1692
tty_release+0xcb6/0x1710 drivers/tty/tty_io.c:1852
__fput+0x461/0xa90 fs/file_table.c:469
task_work_run+0x1d9/0x270 kernel/task_work.c:233
resume_user_mode_work include/linux/resume_user_mode.h:50 [inline]
__exit_to_user_mode_loop kernel/entry/common.c:67 [inline]
exit_to_user_mode_loop+0xed/0x480 kernel/entry/common.c:98
__exit_to_user_mode_prepare include/linux/irq-entry-common.h:226 [inline]
syscall_exit_to_user_mode_prepare include/linux/irq-entry-common.h:256 [inline]
syscall_exit_to_user_mode include/linux/entry-common.h:325 [inline]
do_syscall_64+0x32d/0xf80 arch/x86/entry/syscall_64.c:100
entry_SYSCALL_64_after_hwframe+0x77/0x7f
The buggy address belongs to the object at ffff88806944e000
which belongs to the cache kmalloc-cg-2k of size 2048
The buggy address is located 860 bytes inside of
freed 2048-byte region [ffff88806944e000, ffff88806944e800)
The buggy address belongs to the physical page:
page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x69448
head: order:3 mapcount:0 entire_mapcount:0 nr_pages_mapped:0 pincount:0
memcg:ffff888069448811
flags: 0x80000000000040(head|node=0|zone=1)
page_type: f5(slab)
raw: 0080000000000040 ffff88813fe363c0 dead000000000100 dead000000000122
raw: 0000000000000000 0000100000080008 00000000f5000000 ffff888069448811
head: 0080000000000040 ffff88813fe363c0 dead000000000100 dead000000000122
head: 0000000000000000 0000100000080008 00000000f5000000 ffff888069448811
head: 0080000000000003 ffffea0001a51201 00000000ffffffff 00000000ffffffff
head: 0000000000000000 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 5878, tgid 5878 (syz-executor), ts 463972954830, free_ts 463959021215
set_page_owner include/linux/page_owner.h:32 [inline]
post_alloc_hook+0x231/0x280 mm/page_alloc.c:1889
prep_new_page mm/page_alloc.c:1897 [inline]
get_page_from_freelist+0x28bb/0x2950 mm/page_alloc.c:3962
__alloc_frozen_pages_noprof+0x18d/0x380 mm/page_alloc.c:5250
alloc_slab_page mm/slub.c:3292 [inline]
allocate_slab+0x77/0x660 mm/slub.c:3481
new_slab mm/slub.c:3539 [inline]
refill_objects+0x334/0x3c0 mm/slub.c:7175
refill_sheaf mm/slub.c:2812 [inline]
__pcs_replace_empty_main+0x35c/0x710 mm/slub.c:4615
alloc_from_pcs mm/slub.c:4717 [inline]
slab_alloc_node mm/slub.c:4851 [inline]
__do_kmalloc_node mm/slub.c:5259 [inline]
__kvmalloc_node_noprof+0x6f4/0x8e0 mm/slub.c:6752
alloc_fdtable+0x101/0x2c0 fs/file.c:219
dup_fd+0x879/0xb70 fs/file.c:420
copy_files+0xc8/0x120 kernel/fork.c:1636
copy_process+0x1767/0x3cd0 kernel/fork.c:2211
kernel_clone+0x249/0x840 kernel/fork.c:2653
__do_sys_clone kernel/fork.c:2794 [inline]
__se_sys_clone kernel/fork.c:2778 [inline]
__x64_sys_clone+0x1b6/0x230 kernel/fork.c:2778
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x14d/0xf80 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
page last free pid 1186 tgid 1186 stack trace:
reset_page_owner include/linux/page_owner.h:25 [inline]
__free_pages_prepare mm/page_alloc.c:1433 [inline]
__free_frozen_pages+0xfe3/0x1170 mm/page_alloc.c:2978
__folio_put+0x25d/0x310 mm/swap.c:112
put_netmem include/net/netmem.h:420 [inline]
skb_page_unref include/linux/skbuff_ref.h:43 [inline]
__skb_frag_unref include/linux/skbuff_ref.h:56 [inline]
skb_release_data+0x4cb/0x940 net/core/skbuff.c:1122
skb_release_all net/core/skbuff.c:1203 [inline]
__kfree_skb+0x5d/0x210 net/core/skbuff.c:1217
skb_defer_free_flush+0x191/0x260 net/core/dev.c:6837
net_rx_action+0x4a6/0xe00 net/core/dev.c:7910
handle_softirqs+0x1de/0x6f0 kernel/softirq.c:622
__do_softirq kernel/softirq.c:656 [inline]
__local_bh_enable_ip+0x170/0x2b0 kernel/softirq.c:302
local_bh_enable include/linux/bottom_half.h:33 [inline]
irq_forced_thread_fn+0xe9/0x120 kernel/irq/manage.c:1168
irq_thread+0x4c9/0x740 kernel/irq/manage.c:1271
kthread+0x388/0x470 kernel/kthread.c:436
ret_from_fork+0x51e/0xb90 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:
ffff88806944e200: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
ffff88806944e280: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
>ffff88806944e300: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
^
ffff88806944e380: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
ffff88806944e400: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
==================================================================
Reply: The bug report is a KASAN slab-use-after-free in `kbd_keycode`, which is called from `kbd_event` in the keyboard handler of the VT subsystem.
### Analysis of the Stack Traces:
1. **Access Stack Trace:**
The crash occurs in `kbd_keycode` at `drivers/tty/vt/keyboard.c:1435`.
```c
1434: (!vc_kbd_mode(kbd, VC_REPEAT) ||
1435: (tty && !L_ECHO(tty) && tty_chars_in_buffer(tty)))) {
```
The access is a read of size 4 at offset 860 bytes into the object.
`L_ECHO(tty)` is a macro that accesses `tty->termios.c_lflag`.
In `struct tty_struct`, `termios` is at offset 848 bytes, and `c_lflag` is at offset 12 bytes within `struct ktermios`, totaling 860 bytes. This perfectly matches the KASAN report.
2. **Allocation Stack Trace:**
The object was allocated in `alloc_tty_struct` (called via `tty_open` -> `tty_init_dev`). This confirms the object is indeed a `struct tty_struct`.
3. **Free Stack Trace:**
The object was freed via a workqueue (`process_one_work` -> `release_one_tty` -> `kfree`).
The "Last potentially related work creation" shows the work was queued in `tty_release_struct` (called via `tty_release` when the last file descriptor to the TTY is closed).
### Root Cause:
The function `kbd_keycode` obtains a pointer to the TTY associated with the foreground console via `tty = vc->port.tty` (line 1381). However, it does so without any locking or incrementing the TTY's reference count (kref).
If another process closes the TTY concurrently, `tty_release` will eventually call `tty_release_struct`, which calls `release_tty`. `release_tty` calls `tty->ops->shutdown(tty)` (which for VT is `con_shutdown`, setting `vc->port.tty = NULL`) and then drops the last kref of the TTY, leading to its destruction in a workqueue.
If `kbd_keycode` has already read the `tty` pointer before it was set to NULL, it will continue to use it, leading to a use-after-free when it accesses `tty->termios.c_lflag` at line 1435.
### Conclusion:
The report is self-consistent and provides all necessary information (access, allocation, and free stacks) to identify the race condition and the affected object. The subsystem (TTY/VT) and the specific code paths are clearly identified.
The report is actionable.
|
| 5/2 |
2026/04/07 00:26 |
llm |
expert |
0m
Model:
Tokens:
input: 7931
output: 82
thoughts: 695
Thoughts: **KASAN Slab-Use-After-Free in kbd_event - Analysis & Actionable Steps**
Okay, I'm looking at a KASAN slab-use-after-free, specifically within `kbd_event`. My initial focus is the stack traces; consistency and actionability are key here. I've got three core traces to dissect: the access, the allocation, and the free.
First, the access stack trace points to the bug. The access itself is happening within `kbd_keycode` at `drivers/tty/vt/keyboard.c:1435`. The call chain is pretty standard, marching through the input handling layer down to `evdev_write`. I can see this is bubbling up from the keyboard handling path. This is valuable, I need to know *what* it is accessing at that line and if that is consistent with the type, and where it would be getting that from.
Then, the allocation trace shows us that the problematic object is a `struct tty_struct`. It was allocated within `alloc_tty_struct`, as expected, and the allocation itself looks normal. This is typical for a TTY device. Nothing unexpected there.
Finally, the critical free stack trace is telling me the object was freed through a workqueue. This is very important. I need to understand *why* the TTY object is being freed through the workqueue and what context triggered that release. The "Last potentially related work creation" data is showing the `tty_struct` is being released in `tty_release_struct` which is called by `tty_release`. This is a clear indication of a TTY being released, so the KASAN report is making sense.
Now, the crucial step. My priority is understanding the relationship between `kbd_keycode` at line 1435 in `drivers/tty/vt/keyboard.c` and a `tty_struct`. I'm going to pull up that code snippet immediately. I need to figure out what `kbd_keycode` is doing with that line of code, and determine how it might be interacting with the `tty_struct` after it's been freed. Is there a race? A dangling pointer? Is this accessing the wrong location on the struct?
I need to see if the freed object is being held or accessed after its lifespan. I also need to understand *how* kbd_event is triggering a read on a `tty_struct`. This all makes sense, I just need to dig.
|
| 6/2 |
2026/04/07 00:26 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/tty/vt/keyboard.c IncludeLines:true Name:kbd_event]
Results: map[SourceCode:1506: static void kbd_event(struct input_handle *handle, unsigned int event_type,
1507: unsigned int event_code, int value)
1508: {
1509: /* We are called with interrupts disabled, just take the lock */
1510: scoped_guard(spinlock, &kbd_event_lock) {
1511: if (event_type == EV_MSC && event_code == MSC_RAW &&
1512: kbd_is_hw_raw(handle->dev))
1513: kbd_rawcode(value);
1514: if (event_type == EV_KEY && event_code <= KEY_MAX)
1515: kbd_keycode(event_code, value, kbd_is_hw_raw(handle->dev));
1516: }
1517:
1518: tasklet_schedule(&keyboard_tasklet);
1519: do_poke_blanked_console = 1;
1520: schedule_console_callback();
1521: }
SourceFile:drivers/tty/vt/keyboard.c]
|
| 7/2 |
2026/04/07 00:26 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/tty/vt/keyboard.c IncludeLines:true Name:kbd_keycode]
Results: map[SourceCode:1370: static void kbd_keycode(unsigned int keycode, int down, bool hw_raw)
1371: {
1372: struct vc_data *vc = vc_cons[fg_console].d;
1373: unsigned short keysym, *key_map;
1374: unsigned char type;
1375: bool raw_mode;
1376: struct tty_struct *tty;
1377: int shift_final;
1378: struct keyboard_notifier_param param = { .vc = vc, .value = keycode, .down = down };
1379: int rc;
1380:
1381: tty = vc->port.tty;
1382:
1383: if (tty && (!tty->driver_data)) {
1384: /* No driver data? Strange. Okay we fix it then. */
1385: tty->driver_data = vc;
1386: }
1387:
1388: kbd = &kbd_table[vc->vc_num];
1389:
1390: #ifdef CONFIG_SPARC
1391: if (keycode == KEY_STOP)
1392: sparc_l1_a_state = down;
1393: #endif
1394:
1395: rep = (down == 2);
1396:
1397: raw_mode = (kbd->kbdmode == VC_RAW);
1398: if (raw_mode && !hw_raw)
1399: if (emulate_raw(vc, keycode, !down << 7))
1400: if (keycode < BTN_MISC && printk_ratelimit())
1401: pr_warn("can't emulate rawmode for keycode %d\n",
1402: keycode);
1403:
1404: #ifdef CONFIG_SPARC
1405: if (keycode == KEY_A && sparc_l1_a_state) {
1406: sparc_l1_a_state = false;
1407: sun_do_break();
1408: }
1409: #endif
1410:
1411: if (kbd->kbdmode == VC_MEDIUMRAW) {
1412: /*
1413: * This is extended medium raw mode, with keys above 127
1414: * encoded as 0, high 7 bits, low 7 bits, with the 0 bearing
1415: * the 'up' flag if needed. 0 is reserved, so this shouldn't
1416: * interfere with anything else. The two bytes after 0 will
1417: * always have the up flag set not to interfere with older
1418: * applications. This allows for 16384 different keycodes,
1419: * which should be enough.
1420: */
1421: if (keycode < 128) {
1422: put_queue(vc, keycode | (!down << 7));
1423: } else {
1424: put_queue(vc, !down << 7);
1425: put_queue(vc, (keycode >> 7) | BIT(7));
1426: put_queue(vc, keycode | BIT(7));
1427: }
1428: raw_mode = true;
1429: }
1430:
1431: assign_bit(keycode, key_down, down);
1432:
1433: if (rep &&
1434: (!vc_kbd_mode(kbd, VC_REPEAT) ||
1435: (tty && !L_ECHO(tty) && tty_chars_in_buffer(tty)))) {
1436: /*
1437: * Don't repeat a key if the input buffers are not empty and the
1438: * characters get aren't echoed locally. This makes key repeat
1439: * usable with slow applications and under heavy loads.
1440: */
1441: return;
1442: }
1443:
1444: param.shift = shift_final = (shift_state | kbd->slockstate) ^ kbd->lockstate;
1445: param.ledstate = kbd->ledflagstate;
1446: key_map = key_maps[shift_final];
1447:
1448: rc = atomic_notifier_call_chain(&keyboard_notifier_list,
1449: KBD_KEYCODE, ¶m);
1450: if (rc == NOTIFY_STOP || !key_map) {
1451: atomic_notifier_call_chain(&keyboard_notifier_list,
1452: KBD_UNBOUND_KEYCODE, ¶m);
1453: do_compute_shiftstate();
1454: kbd->slockstate = 0;
1455: return;
1456: }
1457:
1458: if (keycode < NR_KEYS)
1459: keysym = key_map[keycode];
1460: else if (keycode >= KEY_BRL_DOT1 && keycode <= KEY_BRL_DOT8)
1461: keysym = U(K(KT_BRL, keycode - KEY_BRL_DOT1 + 1));
1462: else
1463: return;
1464:
1465: type = KTYP(keysym);
1466:
1467: if (type < 0xf0) {
1468: param.value = keysym;
1469: rc = atomic_notifier_call_chain(&keyboard_notifier_list,
1470: KBD_UNICODE, ¶m);
1471: if (rc != NOTIFY_STOP)
1472: if (down && !(raw_mode || kbd->kbdmode == VC_OFF))
1473: k_unicode(vc, keysym, !down);
1474: return;
1475: }
1476:
1477: type -= 0xf0;
1478:
1479: if (type == KT_LETTER) {
1480: type = KT_LATIN;
1481: if (vc_kbd_led(kbd, VC_CAPSLOCK)) {
1482: key_map = key_maps[shift_final ^ BIT(KG_SHIFT)];
1483: if (key_map)
1484: keysym = key_map[keycode];
1485: }
1486: }
1487:
1488: param.value = keysym;
1489: rc = atomic_notifier_call_chain(&keyboard_notifier_list,
1490: KBD_KEYSYM, ¶m);
1491: if (rc == NOTIFY_STOP)
1492: return;
1493:
1494: if ((raw_mode || kbd->kbdmode == VC_OFF) && type != KT_SPEC && type != KT_SHIFT)
1495: return;
1496:
1497: (*k_handler[type])(vc, KVAL(keysym), !down);
1498:
1499: param.ledstate = kbd->ledflagstate;
1500: atomic_notifier_call_chain(&keyboard_notifier_list, KBD_POST_KEYSYM, ¶m);
1501:
1502: if (type != KT_SLOCK)
1503: kbd->slockstate = 0;
1504: }
SourceFile:drivers/tty/vt/keyboard.c]
|
| 8/2 |
2026/04/07 00:26 |
llm |
expert |
0m
Model:
Tokens:
input: 11655
output: 84
thoughts: 467
Thoughts: **Analyzing a Potential Use-After-Free in the Keyboard Driver**
Okay, so I'm looking at this KASAN report, and it's pointing to a slab-use-after-free in `kbd_keycode` and `kbd_event` within the keyboard driver. Specifically, it highlights lines 1435 in `keyboard.c` and 1515 in `kbd_event` as the culprits.
Line 1435 is inside a conditional check related to echoing characters to the TTY: `(!vc_kbd_mode(kbd, VC_REPEAT) || (tty && !L_ECHO(tty) && tty_chars_in_buffer(tty)))`. The `tty` in question comes from `vc->port.tty` which is assigned to `tty` back at line 1381.
The `tty_struct` itself is allocated via `alloc_tty_struct` and subsequently freed by `tty_release_struct`, which is triggered through a workqueue. This suggests a potential race condition.
I've traced the call stack for `kbd_event`. It's getting triggered via the input system: it's invoked by `input_handle_events_default` -> `input_pass_values` -> `input_event_dispose` -> `input_inject_event` -> `evdev_write`. This means something is writing to a `/dev/input/eventX` device, resulting in a keyboard event which in turn calls `kbd_event`. Since this is an evdev event, that also means there is an input device sending events via a user space process directly to the kernel, and the keyboard driver handling it.
`kbd_event` is a callback registered during keyboard handler initialization. The `tty` in question is `vc->port.tty`. This is pointing to the TTY associated with the current virtual console. From the `KASAN` logs, the `tty` is being accessed through `vc_cons[fg_console].d`, so the `vc` variable.
My primary concern right now is that `vc->port.tty` might be freed while `kbd_event` is still running. That would certainly explain the UAF. The key to this is looking at how `vc->port.tty` is managed in relation to how `kbd_event` operates.
Wait a second... `kbd_event` is called with `kbd_event_lock` held (line 1510 in the code). But, I don't see `tty_release_struct` taking that lock! So, we have a possible race: `kbd_event` has its own lock, but the `tty` release path isn't synchronized with it. This is where I need to focus my efforts. I need to dive deeper into `tty_release_struct` and the management of `vc->port.tty` to understand the timing of the allocation and deallocation of the `tty` relative to the locking in `kbd_event`. This is where the root cause probably lives.
|
| 9/2 |
2026/04/07 00:26 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/tty/tty_io.c IncludeLines:true Name:tty_release_struct]
Results: map[SourceCode:1668: /**
1669: * tty_release_struct - release a tty struct
1670: * @tty: tty device
1671: * @idx: index of the tty
1672: *
1673: * Performs the final steps to release and free a tty device. It is roughly the
1674: * reverse of tty_init_dev().
1675: */
1676: void tty_release_struct(struct tty_struct *tty, int idx)
1677: {
1678: /*
1679: * Ask the line discipline code to release its structures
1680: */
1681: tty_ldisc_release(tty);
1682:
1683: /* Wait for pending work before tty destruction commmences */
1684: tty_flush_works(tty);
1685:
1686: tty_debug_hangup(tty, "freeing structure\n");
1687: /*
1688: * The release_tty function takes care of the details of clearing
1689: * the slots and preserving the termios structure.
1690: */
1691: mutex_lock(&tty_mutex);
1692: release_tty(tty, idx);
1693: mutex_unlock(&tty_mutex);
1694: }
SourceFile:drivers/tty/tty_io.c]
|
| 10/2 |
2026/04/07 00:26 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/tty/tty_io.c IncludeLines:true Name:tty_release]
Results: map[SourceCode:1697: /**
1698: * tty_release - vfs callback for close
1699: * @inode: inode of tty
1700: * @filp: file pointer for handle to tty
1701: *
1702: * Called the last time each file handle is closed that references this tty.
1703: * There may however be several such references.
1704: *
1705: * Locking:
1706: * Takes BKL. See tty_release_dev().
1707: *
1708: * Even releasing the tty structures is a tricky business. We have to be very
1709: * careful that the structures are all released at the same time, as interrupts
1710: * might otherwise get the wrong pointers.
1711: *
1712: * WSH 09/09/97: rewritten to avoid some nasty race conditions that could
1713: * lead to double frees or releasing memory still in use.
1714: */
1715: int tty_release(struct inode *inode, struct file *filp)
1716: {
1717: struct tty_struct *tty = file_tty(filp);
1718: struct tty_struct *o_tty = NULL;
1719: int do_sleep, final;
1720: int idx;
1721: long timeout = 0;
1722: int once = 1;
1723:
1724: if (tty_paranoia_check(tty, inode, __func__))
1725: return 0;
1726:
1727: tty_lock(tty);
1728: check_tty_count(tty, __func__);
1729:
1730: __tty_fasync(-1, filp, 0);
1731:
1732: idx = tty->index;
1733: if (tty->driver->type == TTY_DRIVER_TYPE_PTY &&
1734: tty->driver->subtype == PTY_TYPE_MASTER)
1735: o_tty = tty->link;
1736:
1737: if (tty_release_checks(tty, idx)) {
1738: tty_unlock(tty);
1739: return 0;
1740: }
1741:
1742: tty_debug_hangup(tty, "releasing (count=%d)\n", tty->count);
1743:
1744: if (tty->ops->close)
1745: tty->ops->close(tty, filp);
1746:
1747: /* If tty is pty master, lock the slave pty (stable lock order) */
1748: tty_lock_slave(o_tty);
1749:
1750: /*
1751: * Sanity check: if tty->count is going to zero, there shouldn't be
1752: * any waiters on tty->read_wait or tty->write_wait. We test the
1753: * wait queues and kick everyone out _before_ actually starting to
1754: * close. This ensures that we won't block while releasing the tty
1755: * structure.
1756: *
1757: * The test for the o_tty closing is necessary, since the master and
1758: * slave sides may close in any order. If the slave side closes out
1759: * first, its count will be one, since the master side holds an open.
1760: * Thus this test wouldn't be triggered at the time the slave closed,
1761: * so we do it now.
1762: */
1763: while (1) {
1764: do_sleep = 0;
1765:
1766: if (tty->count <= 1) {
1767: if (waitqueue_active(&tty->read_wait)) {
1768: wake_up_poll(&tty->read_wait, EPOLLIN);
1769: do_sleep++;
1770: }
1771: if (waitqueue_active(&tty->write_wait)) {
1772: wake_up_poll(&tty->write_wait, EPOLLOUT);
1773: do_sleep++;
1774: }
1775: }
1776: if (o_tty && o_tty->count <= 1) {
1777: if (waitqueue_active(&o_tty->read_wait)) {
1778: wake_up_poll(&o_tty->read_wait, EPOLLIN);
1779: do_sleep++;
1780: }
1781: if (waitqueue_active(&o_tty->write_wait)) {
1782: wake_up_poll(&o_tty->write_wait, EPOLLOUT);
1783: do_sleep++;
1784: }
1785: }
1786: if (!do_sleep)
1787: break;
1788:
1789: if (once) {
1790: once = 0;
1791: tty_warn(tty, "read/write wait queue active!\n");
1792: }
1793: schedule_timeout_killable(timeout);
1794: if (timeout < 120 * HZ)
1795: timeout = 2 * timeout + 1;
1796: else
1797: timeout = MAX_SCHEDULE_TIMEOUT;
1798: }
1799:
1800: if (o_tty) {
1801: if (--o_tty->count < 0) {
1802: tty_warn(tty, "bad slave count (%d)\n", o_tty->count);
1803: o_tty->count = 0;
1804: }
1805: }
1806: if (--tty->count < 0) {
1807: tty_warn(tty, "bad tty->count (%d)\n", tty->count);
1808: tty->count = 0;
1809: }
1810:
1811: /*
1812: * We've decremented tty->count, so we need to remove this file
1813: * descriptor off the tty->tty_files list; this serves two
1814: * purposes:
1815: * - check_tty_count sees the correct number of file descriptors
1816: * associated with this tty.
1817: * - do_tty_hangup no longer sees this file descriptor as
1818: * something that needs to be handled for hangups.
1819: */
1820: tty_del_file(filp);
1821:
1822: /*
1823: * Perform some housekeeping before deciding whether to return.
1824: *
1825: * If _either_ side is closing, make sure there aren't any
1826: * processes that still think tty or o_tty is their controlling
1827: * tty.
1828: */
1829: if (!tty->count) {
1830: read_lock(&tasklist_lock);
1831: session_clear_tty(tty->ctrl.session);
1832: if (o_tty)
1833: session_clear_tty(o_tty->ctrl.session);
1834: read_unlock(&tasklist_lock);
1835: }
1836:
1837: /* check whether both sides are closing ... */
1838: final = !tty->count && !(o_tty && o_tty->count);
1839:
1840: tty_unlock_slave(o_tty);
1841: tty_unlock(tty);
1842:
1843: /* At this point, the tty->count == 0 should ensure a dead tty
1844: * cannot be re-opened by a racing opener.
1845: */
1846:
1847: if (!final)
1848: return 0;
1849:
1850: tty_debug_hangup(tty, "final close\n");
1851:
1852: tty_release_struct(tty, idx);
1853: return 0;
1854: }
SourceFile:drivers/tty/tty_io.c]
|
| 11/2 |
2026/04/07 00:26 |
llm |
expert |
0m
Model:
Tokens:
input: 15650
output: 41
thoughts: 75
Thoughts: Here's my thought process, as requested:
**Deconstructing `tty_release_struct` and `release_tty`**
Okay, so I'm digging into `tty_release_struct`, and the first thing that jumps out is that it calls `release_tty`. That's pretty standard, makes sense. Let's see... looking at the code snippet, it's clear it's protected by `tty_mutex`. Good to see that locking in place. So, `tty_release_struct` handles some resources associated with a terminal, and calls a function `release_tty` to free the tty itself. Now, I need to understand what `release_tty` actually *does*. Let's go investigate that function, to see how it works and what kind of resources it releases, especially within the context of the lock.
|
| 12/2 |
2026/04/07 00:26 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/tty/tty_io.c IncludeLines:true Name:release_tty]
Results: map[SourceCode:1561: /**
1562: * release_tty - release tty structure memory
1563: * @tty: tty device release
1564: * @idx: index of the tty device release
1565: *
1566: * Release both @tty and a possible linked partner (think pty pair),
1567: * and decrement the refcount of the backing module.
1568: *
1569: * Locking:
1570: * tty_mutex
1571: * takes the file list lock internally when working on the list of ttys
1572: * that the driver keeps.
1573: */
1574: static void release_tty(struct tty_struct *tty, int idx)
1575: {
1576: /* This should always be true but check for the moment */
1577: WARN_ON(tty->index != idx);
1578: WARN_ON(!mutex_is_locked(&tty_mutex));
1579: if (tty->ops->shutdown)
1580: tty->ops->shutdown(tty);
1581: tty_save_termios(tty);
1582: tty_driver_remove_tty(tty->driver, tty);
1583: if (tty->port)
1584: tty->port->itty = NULL;
1585: if (tty->link)
1586: tty->link->port->itty = NULL;
1587: if (tty->port)
1588: tty_buffer_cancel_work(tty->port);
1589: if (tty->link)
1590: tty_buffer_cancel_work(tty->link->port);
1591:
1592: tty_kref_put(tty->link);
1593: tty_kref_put(tty);
1594: }
SourceFile:drivers/tty/tty_io.c]
|
| 13/2 |
2026/04/07 00:26 |
llm |
expert |
0m
Model:
Tokens:
input: 16492
output: 34
thoughts: 360
Thoughts: **Analyzing Potential TTY De-referencing Issues**
Okay, so I'm looking at this `kbd_keycode` function and I'm immediately concerned about a potential race condition with `tty_struct` deallocation. I see that the `tty_struct` gets freed when its `kref` hits zero, which is standard, but the question is *when* and *how* the kref is managed in relation to this access.
Line 1381 is the problematic line: `tty = vc->port.tty;`. `vc->port` is a `tty_port` structure and I need to figure out how this `tty` pointer is handled. I need to trace this back.
Alright, checking `drivers/tty/vt/vt.c`, I see how `vc->port` is initialized. But then it hits me. `kbd_keycode` just *accesses* `vc->port.tty` directly. No locking, no incrementing the kref count! That's a red flag. If `tty_release` gets called, itβll eventually call `tty_kref_put(tty)`, which could *potentially* free the `tty_struct` right out from under the function's feet! The `tty_struct` for a VC is usually managed by the VC driver.
Let's revisit that `kbd_keycode` snippet:
```c
1372: struct vc_data *vc = vc_cons[fg_console].d;
...
1381: tty = vc->port.tty;
...
1435: (tty && !L_ECHO(tty) && tty_chars_in_buffer(tty)))) {
```
The critical part is the final use of `tty`.
Now, if `vc->port.tty` *becomes* `NULL` in `release_tty` (line 1584), that would be a problem. Checking line 1584:
```c
1584: if (tty->port)
1585: tty->port->itty = NULL;
```
Wait a second! That's `itty`, not `tty`. But `struct tty_port` does have a `tty` field. I need to confirm the definitions and the usage to be sure here. I'm going to have to dive into `struct tty_port` definition to understand this completely. It seems like the intention is to eventually null out the `tty` pointer when a VC is released, and if this happens concurrently with `kbd_keycode` accessing it, it is definitely a problem. I suspect I might have a potential use-after-free situation here, and I'll need to further investigate the locking and memory management around the `tty` pointer to be certain.
|
| 14/2 |
2026/04/07 00:26 |
tool |
codesearch-struct-layout |
0m
Args: map[ContextFile:include/linux/tty.h Name:tty_port]
Results: map[Fields:[map[Name:buf OffsetBits:0 SizeBits:2176] map[Name:tty OffsetBits:2176 SizeBits:64] map[Name:itty OffsetBits:2240 SizeBits:64] map[Name:ops OffsetBits:2304 SizeBits:64] map[Name:client_ops OffsetBits:2368 SizeBits:64] map[Name:lock OffsetBits:2432 SizeBits:1024] map[Name:blocked_open OffsetBits:3456 SizeBits:32] map[Name:count OffsetBits:3488 SizeBits:32] map[Name:open_wait OffsetBits:3520 SizeBits:1152] map[Name:delta_msr_wait OffsetBits:4672 SizeBits:1152] map[Name:flags OffsetBits:5824 SizeBits:64] map[Name:iflags OffsetBits:5888 SizeBits:64] map[Name:console OffsetBits:5952 SizeBits:1] map[Name:mutex OffsetBits:6016 SizeBits:1024] map[Name:buf_mutex OffsetBits:7040 SizeBits:1024] map[Name:xmit_buf OffsetBits:8064 SizeBits:64] map[Name:xmit_fifo OffsetBits:8128 SizeBits:192] map[Name:close_delay OffsetBits:8320 SizeBits:32] map[Name:closing_wait OffsetBits:8352 SizeBits:32] map[Name:drain_delay OffsetBits:8384 SizeBits:32] map[Name:kref OffsetBits:8416 SizeBits:32] map[Name:client_data OffsetBits:8448 SizeBits:64]]]
|
| 15/2 |
2026/04/07 00:26 |
llm |
expert |
0m
Model:
Tokens:
input: 17990
output: 24
thoughts: 86
Thoughts: **Investigating the `itty` Field in `tty_port`**
Okay, so I'm looking at this `tty_port` structure and I see it has both `tty` and `itty`. Interesting. I'm focusing on the `release_tty` function specifically because I'm seeing this line, which I find curious:
```c
1583: if (tty->port)
1584: tty->port->itty = NULL;
```
Why is `itty` being set to `NULL`? What exactly is the purpose of this field? It's not immediately obvious from the context, so it's time to dig into the definition of `tty_port` itself. Let's head over to `include/linux/tty_port.h` and see what the header file reveals about `tty_port` and this `itty` member. Gotta understand the underlying structure to really grasp what's going on here.
|
| 16/2 |
2026/04/07 00:26 |
tool |
read-file |
0m
Args: map[File:include/linux/tty_port.h]
Results: map[Contents:/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _LINUX_TTY_PORT_H
#define _LINUX_TTY_PORT_H
#include <linux/kfifo.h>
#include <linux/kref.h>
#include <linux/mutex.h>
#include <linux/tty_buffer.h>
#include <linux/wait.h>
struct attribute_group;
struct tty_driver;
struct tty_port;
struct tty_struct;
/**
* struct tty_port_operations -- operations on tty_port
* @carrier_raised: return true if the carrier is raised on @port
* @dtr_rts: raise the DTR line if @active is true, otherwise lower DTR
* @shutdown: called when the last close completes or a hangup finishes IFF the
* port was initialized. Do not use to free resources. Turn off the device
* only. Called under the port mutex to serialize against @activate and
* @shutdown.
* @activate: called under the port mutex from tty_port_open(), serialized using
* the port mutex. Supposed to turn on the device.
*
* FIXME: long term getting the tty argument *out* of this would be good
* for consoles.
*
* @destruct: called on the final put of a port. Free resources, possibly incl.
* the port itself.
*/
struct tty_port_operations {
bool (*carrier_raised)(struct tty_port *port);
void (*dtr_rts)(struct tty_port *port, bool active);
void (*shutdown)(struct tty_port *port);
int (*activate)(struct tty_port *port, struct tty_struct *tty);
void (*destruct)(struct tty_port *port);
};
struct tty_port_client_operations {
size_t (*receive_buf)(struct tty_port *port, const u8 *cp, const u8 *fp,
size_t count);
void (*lookahead_buf)(struct tty_port *port, const u8 *cp,
const u8 *fp, size_t count);
void (*write_wakeup)(struct tty_port *port);
};
extern const struct tty_port_client_operations tty_port_default_client_ops;
/**
* struct tty_port -- port level information
*
* @buf: buffer for this port, locked internally
* @tty: back pointer to &struct tty_struct, valid only if the tty is open. Use
* tty_port_tty_get() to obtain it (and tty_kref_put() to release).
* @itty: internal back pointer to &struct tty_struct. Avoid this. It should be
* eliminated in the long term.
* @ops: tty port operations (like activate, shutdown), see &struct
* tty_port_operations
* @client_ops: tty port client operations (like receive_buf, write_wakeup).
* By default, tty_port_default_client_ops is used.
* @lock: lock protecting @tty
* @blocked_open: # of procs waiting for open in tty_port_block_til_ready()
* @count: usage count
* @open_wait: open waiters queue (waiting e.g. for a carrier)
* @delta_msr_wait: modem status change queue (waiting for MSR changes)
* @flags: user TTY flags (%ASYNC_)
* @iflags: internal flags (%TTY_PORT_)
* @console: when set, the port is a console
* @mutex: locking, for open, shutdown and other port operations
* @buf_mutex: @xmit_buf alloc lock
* @xmit_buf: optional xmit buffer used by some drivers
* @xmit_fifo: optional xmit buffer used by some drivers
* @close_delay: delay in jiffies to wait when closing the port
* @closing_wait: delay in jiffies for output to be sent before closing
* @drain_delay: set to zero if no pure time based drain is needed else set to
* size of fifo
* @kref: references counter. Reaching zero calls @ops->destruct() if non-%NULL
* or frees the port otherwise.
* @client_data: pointer to private data, for @client_ops
*
* Each device keeps its own port level information. &struct tty_port was
* introduced as a common structure for such information. As every TTY device
* shall have a backing tty_port structure, every driver can use these members.
*
* The tty port has a different lifetime to the tty so must be kept apart.
* In addition be careful as tty -> port mappings are valid for the life
* of the tty object but in many cases port -> tty mappings are valid only
* until a hangup so don't use the wrong path.
*
* Tty port shall be initialized by tty_port_init() and shut down either by
* tty_port_destroy() (refcounting not used), or tty_port_put() (refcounting).
*
* There is a lot of helpers around &struct tty_port too. To name the most
* significant ones: tty_port_open(), tty_port_close() (or
* tty_port_close_start() and tty_port_close_end() separately if need be), and
* tty_port_hangup(). These call @ops->activate() and @ops->shutdown() as
* needed.
*/
struct tty_port {
struct tty_bufhead buf;
struct tty_struct *tty;
struct tty_struct *itty;
const struct tty_port_operations *ops;
const struct tty_port_client_operations *client_ops;
spinlock_t lock;
int blocked_open;
int count;
wait_queue_head_t open_wait;
wait_queue_head_t delta_msr_wait;
unsigned long flags;
unsigned long iflags;
unsigned char console:1;
struct mutex mutex;
struct mutex buf_mutex;
u8 *xmit_buf;
DECLARE_KFIFO_PTR(xmit_fifo, u8);
unsigned int close_delay;
unsigned int closing_wait;
int drain_delay;
struct kref kref;
void *client_data;
};
/* tty_port::iflags bits -- use atomic bit ops */
#define TTY_PORT_INITIALIZED 0 /* device is initialized */
#define TTY_PORT_SUSPENDED 1 /* device is suspended */
#define TTY_PORT_ACTIVE 2 /* device is open */
/*
* uart drivers: use the uart_port::status field and the UPSTAT_* defines
* for s/w-based flow control steering and carrier detection status
*/
#define TTY_PORT_CTS_FLOW 3 /* h/w flow control enabled */
#define TTY_PORT_CHECK_CD 4 /* carrier detect enabled */
#define TTY_PORT_KOPENED 5 /* device exclusively opened by
kernel */
void tty_port_init(struct tty_port *port);
void tty_port_link_device(struct tty_port *port, struct tty_driver *driver,
unsigned index);
struct device *tty_port_register_device(struct tty_port *port,
struct tty_driver *driver, unsigned index,
struct device *device);
struct device *tty_port_register_device_attr(struct tty_port *port,
struct tty_driver *driver, unsigned index,
struct device *device, void *drvdata,
const struct attribute_group **attr_grp);
struct device *tty_port_register_device_attr_serdev(struct tty_port *port,
struct tty_driver *driver, unsigned index,
struct device *host, struct device *parent, void *drvdata,
const struct attribute_group **attr_grp);
void tty_port_unregister_device(struct tty_port *port,
struct tty_driver *driver, unsigned index);
int tty_port_alloc_xmit_buf(struct tty_port *port);
void tty_port_free_xmit_buf(struct tty_port *port);
void tty_port_destroy(struct tty_port *port);
void tty_port_put(struct tty_port *port);
static inline struct tty_port *tty_port_get(struct tty_port *port)
{
if (port && kref_get_unless_zero(&port->kref))
return port;
return NULL;
}
/* If the cts flow control is enabled, return true. */
static inline bool tty_port_cts_enabled(const struct tty_port *port)
{
return test_bit(TTY_PORT_CTS_FLOW, &port->iflags);
}
static inline void tty_port_set_cts_flow(struct tty_port *port, bool val)
{
assign_bit(TTY_PORT_CTS_FLOW, &port->iflags, val);
}
static inline bool tty_port_active(const struct tty_port *port)
{
return test_bit(TTY_PORT_ACTIVE, &port->iflags);
}
static inline void tty_port_set_active(struct tty_port *port, bool val)
{
assign_bit(TTY_PORT_ACTIVE, &port->iflags, val);
}
static inline bool tty_port_check_carrier(const struct tty_port *port)
{
return test_bit(TTY_PORT_CHECK_CD, &port->iflags);
}
static inline void tty_port_set_check_carrier(struct tty_port *port, bool val)
{
assign_bit(TTY_PORT_CHECK_CD, &port->iflags, val);
}
static inline bool tty_port_suspended(const struct tty_port *port)
{
return test_bit(TTY_PORT_SUSPENDED, &port->iflags);
}
static inline void tty_port_set_suspended(struct tty_port *port, bool val)
{
assign_bit(TTY_PORT_SUSPENDED, &port->iflags, val);
}
static inline bool tty_port_initialized(const struct tty_port *port)
{
return test_bit(TTY_PORT_INITIALIZED, &port->iflags);
}
static inline void tty_port_set_initialized(struct tty_port *port, bool val)
{
assign_bit(TTY_PORT_INITIALIZED, &port->iflags, val);
}
static inline bool tty_port_kopened(const struct tty_port *port)
{
return test_bit(TTY_PORT_KOPENED, &port->iflags);
}
static inline void tty_port_set_kopened(struct tty_port *port, bool val)
{
assign_bit(TTY_PORT_KOPENED, &port->iflags, val);
}
struct tty_struct *tty_port_tty_get(struct tty_port *port);
void tty_port_tty_set(struct tty_port *port, struct tty_struct *tty);
bool tty_port_carrier_raised(struct tty_port *port);
void tty_port_raise_dtr_rts(struct tty_port *port);
void tty_port_lower_dtr_rts(struct tty_port *port);
void tty_port_hangup(struct tty_port *port);
void __tty_port_tty_hangup(struct tty_port *port, bool check_clocal, bool async);
void tty_port_tty_wakeup(struct tty_port *port);
int tty_port_block_til_ready(struct tty_port *port, struct tty_struct *tty,
struct file *filp);
int tty_port_close_start(struct tty_port *port, struct tty_struct *tty,
struct file *filp);
void tty_port_close_end(struct tty_port *port, struct tty_struct *tty);
void tty_port_close(struct tty_port *port, struct tty_struct *tty,
struct file *filp);
int tty_port_install(struct tty_port *port, struct tty_driver *driver,
struct tty_struct *tty);
int tty_port_open(struct tty_port *port, struct tty_struct *tty,
struct file *filp);
static inline int tty_port_users(struct tty_port *port)
{
return port->count + port->blocked_open;
}
/**
* tty_port_tty_hangup - helper to hang up a tty asynchronously
* @port: tty port
* @check_clocal: hang only ttys with %CLOCAL unset?
*/
static inline void tty_port_tty_hangup(struct tty_port *port, bool check_clocal)
{
__tty_port_tty_hangup(port, check_clocal, true);
}
/**
* tty_port_tty_vhangup - helper to hang up a tty synchronously
* @port: tty port
*/
static inline void tty_port_tty_vhangup(struct tty_port *port)
{
__tty_port_tty_hangup(port, false, false);
}
#ifdef CONFIG_TTY
void tty_kref_put(struct tty_struct *tty);
__DEFINE_CLASS_IS_CONDITIONAL(tty_port_tty, true);
__DEFINE_UNLOCK_GUARD(tty_port_tty, struct tty_struct, tty_kref_put(_T->lock));
static inline class_tty_port_tty_t class_tty_port_tty_constructor(struct tty_port *tport)
{
class_tty_port_tty_t _t = {
.lock = tty_port_tty_get(tport),
};
return _t;
}
#define scoped_tty() ((struct tty_struct *)(__guard_ptr(tty_port_tty)(&scope)))
#endif
#endif
]
|
| 17/2 |
2026/04/07 00:26 |
llm |
expert |
0m
Model:
Tokens:
input: 21801
output: 45
thoughts: 489
Thoughts: **Analyzing a Potential Race Condition in the TTY Subsystem**
Okay, so I'm looking at this `tty_port` structure and the comment associated with its `tty` field. It's pretty clear: the `tty` pointer is only valid if the TTY is open, and we're supposed to use `tty_port_tty_get()` to grab it and `tty_kref_put()` to release it. Standard reference counting, nothing unusual there.
But then I see this call to `vc->port.tty` directly within `kbd_keycode`. No `tty_port_tty_get()`! Alarm bells are ringing β this screams race condition. I know this code, and this pattern is a classic trap.
Now, let's step back and consider the flow. `kbd_keycode` gets called from `kbd_event`. `kbd_event` is, of course, triggered by the input subsystem when a key is pressed or released. That makes it a critical path for user input.
If the TTY is being closed, `tty_release` is called, which calls `tty_release_struct`, which then calls `release_tty`. From what I understand of the subsystem, `release_tty` eventually calls `tty_kref_put(tty)`. If that was the last reference, boom, the `tty_struct` is freed. We are probably seeing the same scenario as the usual null-dereference.
The `vc->port.tty` is set when the console is opened as a TTY, likely during `tty_port_install` or a similar function, and specifically for VT consoles, it's during `con_install`.
Okay, I should probably double-check if we need `tty_port_tty_get` in `kbd_keycode`. Let's step back to the larger picture and check where locks are being held. I'm noting that `kbd_event` is called under the `kbd_event_lock`, which is a start. My main concern is, does `tty_release` or any other function that might clear `vc->port.tty` take this lock? If not, then we've got a window for a race.
Wait, looking at `release_tty` again, I see it setting `tty->port->itty` to NULL. But what about `tty->port->tty`? It would be interesting to find out where this `tty` is set to NULL. It would be most likely either `tty_port_close` or `tty_port_hangup`. I should check `tty_port_tty_set` while I'm at it.
Alright, time to dig deeper and confirm those assumptions.
|
| 18/2 |
2026/04/07 00:26 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/tty/tty_port.c IncludeLines:true Name:tty_port_tty_set]
Results: map[SourceCode: 306: /**
307: * tty_port_tty_set - set the tty of a port
308: * @port: tty port
309: * @tty: the tty
310: *
311: * Associate the port and tty pair. Manages any internal refcounts. Pass %NULL
312: * to deassociate a port.
313: */
314: void tty_port_tty_set(struct tty_port *port, struct tty_struct *tty)
315: {
316: guard(spinlock_irqsave)(&port->lock);
317: tty_kref_put(port->tty);
318: port->tty = tty_kref_get(tty);
319: }
SourceFile:drivers/tty/tty_port.c]
|
| 19/2 |
2026/04/07 00:26 |
llm |
expert |
0m
Model:
Tokens:
input: 23172
output: 34
thoughts: 346
Thoughts: **Analyzing a Potential Race Condition in the TTY Driver**
Okay, so I'm looking at this code and something's not quite right. I see `tty_port_tty_set` takes `port->lock`, which makes sense for synchronization. But then I see that `kbd_keycode` doesn't take the lock, and it's just *reading* `port->tty`. Right away, that's a red flag.
Specifically, at line 1381, it looks like `tty = vc->port.tty;` is being read. If `tty_port_tty_set(port, NULL)` is called at the same time, concurrently, then `port->tty` could potentially get freed while `kbd_keycode` is in the process of reading it.
Wait, looking deeper into `tty_port_tty_set`, I see it calls `tty_kref_put(port->tty)`. Ah, that confirms my initial suspicion! If `port->tty` was the last reference, `tty_kref_put` will free it. So, if `kbd_keycode` just *read* the pointer, it might be holding onto a dangling pointer to freed memory.
And the report seems to validate this: it says "Read of size 4 at addr..." and that the "buggy address is located 860 bytes inside of freed..." region. That sounds exactly like what I was thinking: `kbd_keycode` is reading from a region that was already freed.
Now, let me check where the actual read is occurring. I need to understand what's being accessed. Let's look at the offset of `L_ECHO` or `tty_chars_in_buffer` within `tty_struct` to find out the location of the value being read.
Okay, I see `L_ECHO(tty)` is just a macro. It accesses `tty->termios.c_lflag`. So, itβs not a direct pointer, it is using a macro to access a member.
I'll need to check the `tty_struct` layout to determine the exact location of `termios.c_lflag`, to better understand where the read is occurring. It is probable that the buggy address will fall within this region.
|
| 20/2 |
2026/04/07 00:26 |
tool |
codesearch-struct-layout |
0m
Args: map[ContextFile:include/linux/tty.h Name:tty_struct]
Results: map[Fields:[map[Name:kref OffsetBits:0 SizeBits:32] map[Name:index OffsetBits:32 SizeBits:32] map[Name:dev OffsetBits:64 SizeBits:64] map[Name:driver OffsetBits:128 SizeBits:64] map[Name:port OffsetBits:192 SizeBits:64] map[Name:ops OffsetBits:256 SizeBits:64] map[Name:ldisc OffsetBits:320 SizeBits:64] map[Name:ldisc_sem OffsetBits:384 SizeBits:1216] map[Name:atomic_write_lock OffsetBits:1600 SizeBits:1024] map[Name:legacy_mutex OffsetBits:2624 SizeBits:1024] map[Name:throttle_mutex OffsetBits:3648 SizeBits:1024] map[Name:termios_rwsem OffsetBits:4672 SizeBits:1088] map[Name:winsize_mutex OffsetBits:5760 SizeBits:1024] map[Name:termios OffsetBits:6784 SizeBits:352] map[Name:termios_locked OffsetBits:7136 SizeBits:352] map[Name:name OffsetBits:7488 SizeBits:512] map[Name:flags OffsetBits:8000 SizeBits:64] map[Name:count OffsetBits:8064 SizeBits:32] map[Name:receive_room OffsetBits:8096 SizeBits:32] map[Name:winsize OffsetBits:8128 SizeBits:64] map[Name:flow OffsetBits:8192 SizeBits:1088] map[Name:ctrl OffsetBits:9280 SizeBits:1216] map[Name:hw_stopped OffsetBits:10496 SizeBits:8] map[Name:closing OffsetBits:10504 SizeBits:8] map[Name:flow_change OffsetBits:10528 SizeBits:32] map[Name:link OffsetBits:10560 SizeBits:64] map[Name:fasync OffsetBits:10624 SizeBits:64] map[Name:write_wait OffsetBits:10688 SizeBits:1152] map[Name:read_wait OffsetBits:11840 SizeBits:1152] map[Name:hangup_work OffsetBits:12992 SizeBits:576] map[Name:disc_data OffsetBits:13568 SizeBits:64] map[Name:driver_data OffsetBits:13632 SizeBits:64] map[Name:files_lock OffsetBits:13696 SizeBits:1024] map[Name:write_cnt OffsetBits:14720 SizeBits:32] map[Name:write_buf OffsetBits:14784 SizeBits:64] map[Name:tty_files OffsetBits:14848 SizeBits:128] map[Name:SAK_work OffsetBits:14976 SizeBits:576]]]
|
| 21/2 |
2026/04/07 00:26 |
llm |
expert |
0m
Model:
Tokens:
input: 24879
output: 34
thoughts: 57
Thoughts: **Analyzing a Potential Memory Corruption Issue**
Okay, so I'm digging into this problem, and it looks like `termios` is located at bit offset 6784, which translates to an offset of 848 bytes. Hmm, interesting. I've also identified the buggy address at offset 860 bytes. This immediately suggests a potential overwrite scenario, perhaps a buffer overflow or something similar is affecting a data element within the structure.
Now, `termios` is defined as a `struct ktermios`, so the structure's internal layout is crucial here. Understanding the sizes and offsets of each member within `struct ktermios` will provide the insight required to deduce which variables are being corrupted and the nature of the overflow. I'm going to pull up the header file to confirm the structure's exact layout and determine what's happening.
|
| 22/2 |
2026/04/07 00:26 |
tool |
codesearch-struct-layout |
0m
Args: map[ContextFile:include/linux/termios.h Name:ktermios]
Results: map[Fields:[map[Name:c_iflag OffsetBits:0 SizeBits:32] map[Name:c_oflag OffsetBits:32 SizeBits:32] map[Name:c_cflag OffsetBits:64 SizeBits:32] map[Name:c_lflag OffsetBits:96 SizeBits:32] map[Name:c_line OffsetBits:128 SizeBits:8] map[Name:c_cc OffsetBits:136 SizeBits:152] map[Name:c_ispeed OffsetBits:288 SizeBits:32] map[Name:c_ospeed OffsetBits:320 SizeBits:32]]]
|
| 23/2 |
2026/04/07 00:26 |
llm |
expert |
0m
Model:
Tokens:
input: 25334
output: 41
thoughts: 450
Thoughts: **Analyzing a Potential Use-After-Free Vulnerability**
Okay, let's break this down. It looks like we've got a potential use-after-free situation on our hands. The core of the issue seems to be how the `kbd_keycode` function interacts with `tty` structures, specifically related to the `termios` data. I've tracked the memory offsets: `c_lflag` within the `termios` structure is 12 bytes from the start, and `termios` itself sits 848 bytes into a `tty_struct`, putting `c_lflag` at 860 bytes from the beginning of the `tty_struct`. That matches the buggy address offset we've got.
So, the issue is that `kbd_keycode` appears to be trying to access `tty->termios.c_lflag` *after* the `tty` structure has potentially been freed. The `tty` pointer itself is obtained from `vc->port.tty`. This field, `vc->port.tty`, is set to `NULL` when the TTY is closed.
Now, the code in `kbd_keycode` itself is revealing. It looks like we're retrieving the `tty` pointer on line 1381, and then have some checks:
* If the `tty` pointer is not null (line 1383), and it doesn't have driver data, the code sets `tty->driver_data = vc` (line 1385). This seems like an attempt to "fix" something.
The potential race condition, the UAF, happens if the `tty` pointer is valid at line 1381, but is freed *before* line 1435, which involves a check that references the `tty` structure.
Now, I need to understand the lifecycle of this `tty` structure more closely. Let's look at the `tty_release` path, specifically: `tty_release` -> `tty_release_struct` -> `release_tty` -> `tty_kref_put`. I see that `release_tty` does set `tty->port->itty = NULL`. However, it doesn't appear to set `tty->port->tty = NULL`. That's a key observation.
But, hang on, other functions like `tty_port_close` or `tty_port_hangup` *should* set `port->tty` to `NULL`. The VT driver, which we're looking at, is defined in `drivers/tty/vt/vt.c`. So let's check `con_close` inside `drivers/tty/vt/vt.c`. That should show us the `tty` lifecycle.
|
| 24/2 |
2026/04/07 00:26 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/tty/vt/vt.c IncludeLines:true Name:con_close]
Results: map[SourceCode:3686: static void con_close(struct tty_struct *tty, struct file *filp)
3687: {
3688: /* Nothing to do - we defer to shutdown */
3689: }
SourceFile:drivers/tty/vt/vt.c]
|
| 25/2 |
2026/04/07 00:26 |
llm |
expert |
0m
Model:
Tokens:
input: 26509
output: 41
thoughts: 8
Thoughts: Okay, here's my summarized thought process, presented as if I were considering the question myself, and assuming I'm already deep in the subject matter:
**Considering `con_shutdown`**
Hmm, `con_shutdown`... That's a key function, isn't it? My mind immediately goes to the potential race conditions and concurrency issues. Is it thread-safe? How does it handle pending requests or ongoing transactions? I need to think about what exactly is being shut down. Is it just the connection, the entire process, or a more isolated subsystem? I'm picturing the internal state and the dependencies involved. The way it interacts with any established connections or active channels is critical. We can't just blindly drop everything, especially if there's any data integrity concern.
I need to quickly assess the context: what environment am I in? What are the potential side effects? It's easy to overlook edge cases. The underlying memory management - how is that all handled during shutdown? I should really consider the implications on any linked or dependent components as well, especially if there are multiple parts involved. I hope I remembered to flush buffers if there's a reason to!
|
| 26/2 |
2026/04/07 00:27 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/tty/vt/vt.c IncludeLines:true Name:con_shutdown]
Results: map[SourceCode:3691: static void con_shutdown(struct tty_struct *tty)
3692: {
3693: struct vc_data *vc = tty->driver_data;
3694: BUG_ON(vc == NULL);
3695:
3696: guard(console_lock)();
3697: vc->port.tty = NULL;
3698: }
SourceFile:drivers/tty/vt/vt.c]
|
| 27/2 |
2026/04/07 00:27 |
llm |
expert |
0m
Model:
Tokens:
input: 26947
output: 19
thoughts: 892
Thoughts: **Analyzing a Potential Race Condition in the TTY Subsystem**
Okay, so I've got this crash report, and it's pointing to a classic race condition, which is always interesting. I see that `con_shutdown` is setting `vc->port.tty = NULL`. That's the key. This function is called from `release_tty`, which in turn is called by `tty_release_struct`, which is ultimately called by `tty_release` when the TTY's reference count drops to zero. That makes the path: `tty_release` -> `tty_release_struct` -> `release_tty` -> `tty->ops->shutdown(tty)` -> `con_shutdown` -> `vc->port.tty = NULL`.
The report indicates a problem if `kbd_keycode` is running concurrently. The crash shows `kbd_keycode` reading `tty = vc->port.tty`, then `con_shutdown` nulling it out, `release_tty` freeing the `tty_struct`, and finally, `kbd_keycode` accessing `tty->termios.c_lflag`. Boom. Use-after-free. Definitely a race.
My immediate reaction is that `kbd_keycode` should be using `tty_port_tty_get` or some mechanism to protect the TTY from disappearing. I'll need to figure out the right synchronization primitives.
However, the report is very consistent. Allocation, access, and free are all for a `tty_struct`. I'm going to double check and make sure that this is indeed what is happening.
Let me see... I need to check where else in `kbd_keycode` the `tty` is accessed. Aha, line 1381 and 1435. It's used in a couple of places.
Now, I'm thinking about the locking. Is there any other lock that might prevent this race? I see that `kbd_event` is called under `kbd_event_lock`. That's good. But does `con_shutdown` also take `kbd_event_lock`? No, it takes `console_lock`. That's the problem.
Alright, so the call path here is from `evdev_write` -> `input_inject_event` -> `kbd_event` -> `kbd_keycode`. The critical thing is that it's process context, as is the TTY release. So we have two processes: one calling `kbd_event` and the other releasing the TTY. They're definitely racing!
Let me make sure. `kbd_event` *is* called with `kbd_event_lock` held.
```c
1510: scoped_guard(spinlock, &kbd_event_lock) {
```
But, `release_tty` doesn't take `kbd_event_lock`, and this is why the problem is happening.
Okay, I need to check if that `kbd_event_lock` is being used anywhere else. It could be part of the solution. This is definitely actionable. I have a very clear picture of the problem.
|
| 28/2 |
2026/04/07 00:27 |
tool |
grepper |
0m
Args: map[Expression:kbd_event_lock]
Results: map[Output:drivers/tty/vt/keyboard.c=137=static struct input_handler kbd_handler;
drivers/tty/vt/keyboard.c:138:static DEFINE_SPINLOCK(kbd_event_lock);
drivers/tty/vt/keyboard.c-139-static DEFINE_SPINLOCK(led_lock);
--
drivers/tty/vt/keyboard.c=391=static void set_leds(void)
--
drivers/tty/vt/keyboard.c-399- * undefined, so that shiftkey release is seen. The caller must hold the
drivers/tty/vt/keyboard.c:400: * kbd_event_lock.
drivers/tty/vt/keyboard.c-401- */
--
drivers/tty/vt/keyboard.c=425=void vt_set_leds_compute_shiftstate(void)
--
drivers/tty/vt/keyboard.c-434-
drivers/tty/vt/keyboard.c:435: guard(spinlock_irqsave)(&kbd_event_lock);
drivers/tty/vt/keyboard.c-436- do_compute_shiftstate();
--
drivers/tty/vt/keyboard.c=1179=void vt_set_led_state(unsigned int console, int leds)
--
drivers/tty/vt/keyboard.c-1193- * the LED updating. We can't do it yet because fn_hold calls stop_tty
drivers/tty/vt/keyboard.c:1194: * and start_tty under the kbd_event_lock, while normal tty paths
drivers/tty/vt/keyboard.c-1195- * don't hold the lock. We probably need to split out an LED lock
--
drivers/tty/vt/keyboard.c=1506=static void kbd_event(struct input_handle *handle, unsigned int event_type,
--
drivers/tty/vt/keyboard.c-1509- /* We are called with interrupts disabled, just take the lock */
drivers/tty/vt/keyboard.c:1510: scoped_guard(spinlock, &kbd_event_lock) {
drivers/tty/vt/keyboard.c-1511- if (event_type == EV_MSC && event_code == MSC_RAW &&
--
drivers/tty/vt/keyboard.c=1652=static int vt_do_kdgkbdiacr(void __user *udp)
--
drivers/tty/vt/keyboard.c-1663- copy it after we unlock */
drivers/tty/vt/keyboard.c:1664: scoped_guard(spinlock_irqsave, &kbd_event_lock) {
drivers/tty/vt/keyboard.c-1665- asize = accent_table_size;
--
drivers/tty/vt/keyboard.c=1680=static int vt_do_kdgkbdiacruc(void __user *udp)
--
drivers/tty/vt/keyboard.c-1691- copy it after we unlock */
drivers/tty/vt/keyboard.c:1692: scoped_guard(spinlock_irqsave, &kbd_event_lock) {
drivers/tty/vt/keyboard.c-1693- asize = accent_table_size;
--
drivers/tty/vt/keyboard.c=1705=static int vt_do_kdskbdiacr(void __user *udp, int perm)
--
drivers/tty/vt/keyboard.c-1725-
drivers/tty/vt/keyboard.c:1726: guard(spinlock_irqsave)(&kbd_event_lock);
drivers/tty/vt/keyboard.c-1727- accent_table_size = ct;
--
drivers/tty/vt/keyboard.c=1740=static int vt_do_kdskbdiacruc(void __user *udp, int perm)
--
drivers/tty/vt/keyboard.c-1760- }
drivers/tty/vt/keyboard.c:1761: guard(spinlock_irqsave)(&kbd_event_lock);
drivers/tty/vt/keyboard.c-1762- if (ct)
--
drivers/tty/vt/keyboard.c=1801=int vt_do_kdskbmode(unsigned int console, unsigned int arg)
--
drivers/tty/vt/keyboard.c-1804-
drivers/tty/vt/keyboard.c:1805: guard(spinlock_irqsave)(&kbd_event_lock);
drivers/tty/vt/keyboard.c-1806- switch(arg) {
--
drivers/tty/vt/keyboard.c=1837=int vt_do_kdskbmeta(unsigned int console, unsigned int arg)
--
drivers/tty/vt/keyboard.c-1840-
drivers/tty/vt/keyboard.c:1841: guard(spinlock_irqsave)(&kbd_event_lock);
drivers/tty/vt/keyboard.c-1842- switch(arg) {
--
drivers/tty/vt/keyboard.c=1877=static unsigned short vt_kdgkbent(unsigned char kbdmode, unsigned char idx,
--
drivers/tty/vt/keyboard.c-1882- /* Ensure another thread doesn't free it under us */
drivers/tty/vt/keyboard.c:1883: guard(spinlock_irqsave)(&kbd_event_lock);
drivers/tty/vt/keyboard.c-1884- key_map = key_maps[map];
--
drivers/tty/vt/keyboard.c=1895=static int vt_kdskbent(unsigned char kbdmode, unsigned char idx,
--
drivers/tty/vt/keyboard.c-1900- if (!idx && val == K_NOSUCHMAP) {
drivers/tty/vt/keyboard.c:1901: guard(spinlock_irqsave)(&kbd_event_lock);
drivers/tty/vt/keyboard.c-1902- /* deallocate map */
--
drivers/tty/vt/keyboard.c-1931-
drivers/tty/vt/keyboard.c:1932: guard(spinlock_irqsave)(&kbd_event_lock);
drivers/tty/vt/keyboard.c-1933- key_map = key_maps[map];
--
drivers/tty/vt/keyboard.c=2047=int vt_do_kdskled(unsigned int console, int cmd, unsigned long arg, int perm)
--
drivers/tty/vt/keyboard.c-2055- case KDGKBLED:
drivers/tty/vt/keyboard.c:2056: scoped_guard(spinlock_irqsave, &kbd_event_lock)
drivers/tty/vt/keyboard.c-2057- ucval = kb->ledflagstate | (kb->default_ledflagstate << 4);
--
drivers/tty/vt/keyboard.c=2124=void vt_reset_unicode(unsigned int console)
drivers/tty/vt/keyboard.c-2125-{
drivers/tty/vt/keyboard.c:2126: guard(spinlock_irqsave)(&kbd_event_lock);
drivers/tty/vt/keyboard.c-2127- kbd_table[console].kbdmode = default_utf8 ? VC_UNICODE : VC_XLATE;
--
drivers/tty/vt/keyboard.c=2149=void vt_reset_keyboard(unsigned int console)
--
drivers/tty/vt/keyboard.c-2152-
drivers/tty/vt/keyboard.c:2153: guard(spinlock_irqsave)(&kbd_event_lock);
drivers/tty/vt/keyboard.c-2154- set_vc_kbd_mode(kb, VC_REPEAT);
--
drivers/tty/vt/keyboard.c=2191=void vt_set_kbd_mode_bit(unsigned int console, int bit)
--
drivers/tty/vt/keyboard.c-2194-
drivers/tty/vt/keyboard.c:2195: guard(spinlock_irqsave)(&kbd_event_lock);
drivers/tty/vt/keyboard.c-2196- set_vc_kbd_mode(kb, bit);
--
drivers/tty/vt/keyboard.c=2208=void vt_clr_kbd_mode_bit(unsigned int console, int bit)
--
drivers/tty/vt/keyboard.c-2211-
drivers/tty/vt/keyboard.c:2212: guard(spinlock_irqsave)(&kbd_event_lock);
drivers/tty/vt/keyboard.c-2213- clr_vc_kbd_mode(kb, bit);
]
|
| 29/2 |
2026/04/07 00:27 |
llm |
expert |
0m
Model:
Tokens:
input: 30703
output: 41
thoughts: 174
Thoughts: **Race Condition Analysis - Keyboard Input**
Okay, so I'm digging into this code, and it looks like `kbd_event_lock` isn't being used within `con_shutdown` or `release_tty`. That's a red flag. Hmm, so there's a race condition lurking somewhere...
Alright, let's trace this out. I know `kbd_keycode` gets called from `kbd_event`, and `kbd_event` is called from `input_handle_events_default`. That function, in turn, is invoked from `input_pass_values`, which is called from `input_event_dispose`, and finally, `input_inject_event`. Got it. That's the keyboard input path.
Now, where's the keyboard input registered? Ah, right. The `input_handle` for the keyboard gets registered during keyboard handler initialization. The code's pointing me at `kbd_handler` in `drivers/tty/vt/keyboard.c`. Time to see exactly how that handler is initialized and where this race condition could be triggered. I need to understand the timing of the registration and the shutdown/release calls. This is where the real work begins.
|
| 30/2 |
2026/04/07 00:27 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/tty/vt/keyboard.c IncludeLines:true Name:kbd_init]
Results: map[SourceCode:1623: int __init kbd_init(void)
1624: {
1625: int i;
1626: int error;
1627:
1628: for (i = 0; i < MAX_NR_CONSOLES; i++) {
1629: kbd_table[i].ledflagstate = kbd_defleds();
1630: kbd_table[i].default_ledflagstate = kbd_defleds();
1631: kbd_table[i].ledmode = LED_SHOW_FLAGS;
1632: kbd_table[i].lockstate = KBD_DEFLOCK;
1633: kbd_table[i].slockstate = 0;
1634: kbd_table[i].modeflags = KBD_DEFMODE;
1635: kbd_table[i].kbdmode = default_utf8 ? VC_UNICODE : VC_XLATE;
1636: }
1637:
1638: kbd_init_leds();
1639:
1640: error = input_register_handler(&kbd_handler);
1641: if (error)
1642: return error;
1643:
1644: tasklet_enable(&keyboard_tasklet);
1645: tasklet_schedule(&keyboard_tasklet);
1646:
1647: return 0;
1648: }
SourceFile:drivers/tty/vt/keyboard.c]
|
| 31/2 |
2026/04/07 00:27 |
llm |
expert |
0m
Model:
Tokens:
input: 31597
output: 41
thoughts: 40
Thoughts: Okay, here's my interpretation of this process, broken down as if these were my thoughts:
**Keyboard Input Flow: A Mental Map**
Right, so `kbd_handler` is the central point. Think of it as the main dispatcher for keyboard input. It's *registered*, meaning it's ready and waiting to receive calls. That's the baseline, the foundation.
Now, whenever a new input device β in this case, a keyboard β is *registered* with the system, I need to connect the dots. That's where `kbd_handler.connect` kicks in. It's essentially the bridge.
And, thankfully, `kbd_handler.connect` is just an alias for `kbd_connect`. That makes it clean, easy to follow. Less indirection is always welcome, and now it's clear what's happening. `kbd_connect` handles the low-level connection to this new keyboard. Good. Makes sense. This helps me maintain the system in a clean, predictable manner.
|
| 32/2 |
2026/04/07 00:27 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/tty/vt/keyboard.c IncludeLines:true Name:kbd_connect]
Results: map[SourceCode:1540: /*
1541: * When a keyboard (or other input device) is found, the kbd_connect
1542: * function is called. The function then looks at the device, and if it
1543: * likes it, it can open it and get events from it. In this (kbd_connect)
1544: * function, we should decide which VT to bind that keyboard to initially.
1545: */
1546: static int kbd_connect(struct input_handler *handler, struct input_dev *dev,
1547: const struct input_device_id *id)
1548: {
1549: int error;
1550:
1551: struct input_handle __free(kfree) *handle = kzalloc_obj(*handle);
1552: if (!handle)
1553: return -ENOMEM;
1554:
1555: handle->dev = dev;
1556: handle->handler = handler;
1557: handle->name = "kbd";
1558:
1559: error = input_register_handle(handle);
1560: if (error)
1561: return error;
1562:
1563: error = input_open_device(handle);
1564: if (error)
1565: goto err_unregister_handle;
1566:
1567: retain_and_null_ptr(handle);
1568:
1569: return 0;
1570:
1571: err_unregister_handle:
1572: input_unregister_handle(handle);
1573: return error;
1574: }
SourceFile:drivers/tty/vt/keyboard.c]
|
| 33/2 |
2026/04/07 00:27 |
llm |
expert |
0m
Model:
Tokens:
input: 32398
output: 43
thoughts: 758
Thoughts: **Analyzing a Potential TTY/VT Race Condition**
Okay, so the `kbd_handler` is constantly on the lookout, gobbling up keyboard events from every keyboard input device. It then routes those events to the active foreground console, which is represented by this global variable `fg_console`. The data associated with that console is accessed through `vc_cons[fg_console].d`, and critically, if the console is linked to a TTY (like `/dev/tty1` is opened), we have `vc->port.tty` pointing to its `tty_struct`.
Hereβs the rub: if a process opens a TTY and then closes it, `vc->port.tty` eventually gets set to NULL, and the corresponding `tty_struct` is freed. However, right at that moment, if a keypress happens, `kbd_event` is triggered. `kbd_event` grabs `fg_console`, gets the `vc`, and attempts to access `vc->port.tty`. Boom! Race condition: we could be reading a freed `tty_struct`!
This is a very well-defined bug report. I see the access, allocation, and free stacks all pointing to the `tty_struct` β a classic. The specific access is to a field within the `tty_struct`, `termios.c_lflag`. The subsystem involved is clearly TTY/VT. Everything is lining up neatly.
But hold onβ¦ there's more. There's this "Last potentially related work creation" stack trace, and it's intriguing. It points to `tty_release_struct` calling `queue_work_on`, which is related to workqueues. That's unusual. Let me re-examine `tty_release_struct` itself. It seems odd that itβs calling workqueuesβ¦
Okay, let's look at the source for `tty_release_struct` again. I see it has a mutex lock. `release_tty` is called in line 1692. Then, line 1693 unlocks the mutex. But, no workqueue call there. I have to see what `tty_release` does. But wait, the stack trace indicates that the workqueue is called *within* `tty_release_struct`, specifically at offset `0xb8/0xd0`. That doesn't align with the source code I'm looking at. I'm going to carefully re-examine `tty_release_struct` again, to see if I missed anything. Perhaps the workqueue call is buried deeper within? Or maybe `tty_release` is where the action happens. I have to follow the call stack.
|
| 34/2 |
2026/04/07 00:27 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/tty/tty_io.c IncludeLines:true Name:tty_release_struct]
Results: map[SourceCode:1668: /**
1669: * tty_release_struct - release a tty struct
1670: * @tty: tty device
1671: * @idx: index of the tty
1672: *
1673: * Performs the final steps to release and free a tty device. It is roughly the
1674: * reverse of tty_init_dev().
1675: */
1676: void tty_release_struct(struct tty_struct *tty, int idx)
1677: {
1678: /*
1679: * Ask the line discipline code to release its structures
1680: */
1681: tty_ldisc_release(tty);
1682:
1683: /* Wait for pending work before tty destruction commmences */
1684: tty_flush_works(tty);
1685:
1686: tty_debug_hangup(tty, "freeing structure\n");
1687: /*
1688: * The release_tty function takes care of the details of clearing
1689: * the slots and preserving the termios structure.
1690: */
1691: mutex_lock(&tty_mutex);
1692: release_tty(tty, idx);
1693: mutex_unlock(&tty_mutex);
1694: }
SourceFile:drivers/tty/tty_io.c]
|
| 35/2 |
2026/04/07 00:27 |
llm |
expert |
0m
Model:
Tokens:
input: 34180
output: 44
thoughts: 108
Thoughts: **Analyzing TTY Release in Kernel Code**
Okay, so I'm digging into this `tty_release_struct` function and it looks like the crucial point, where the action *actually* happens, is right around line 1692. Ah, there it is β the call to `release_tty`. From what I can see, `release_tty` then directly calls `tty_kref_put(tty)`. This is where it gets interesting because `tty_kref_put` handles the reference counting, and when that refcount hits zero, it triggers `tty_free_struct`. Now, the important question is: is `tty_free_struct` leveraging a workqueue for asynchronous cleanup? I need to confirm that. Time to dive into the `tty_kref_put` function itself to see what's actually going on.
|
| 36/2 |
2026/04/07 00:27 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/tty/tty_io.c IncludeLines:true Name:tty_kref_put]
Results: map[SourceCode:1547: /**
1548: * tty_kref_put - release a tty kref
1549: * @tty: tty device
1550: *
1551: * Release a reference to the @tty device and if need be let the kref layer
1552: * destruct the object for us.
1553: */
1554: void tty_kref_put(struct tty_struct *tty)
1555: {
1556: if (tty)
1557: kref_put(&tty->kref, queue_release_one_tty);
1558: }
SourceFile:drivers/tty/tty_io.c]
|
| 37/2 |
2026/04/07 00:27 |
llm |
expert |
0m
Model:
Tokens:
input: 34722
output: 45
thoughts: 35
Thoughts: Okay, here's my interpretation of that thought process, tailored for an expert audience, and written in the first person:
**Analyzing the TTY Release Mechanism**
Alright, let's break this down. Seeing `kref_put(&tty->kref, queue_release_one_tty)` immediately tells me we're dealing with reference counting for a terminal, specifically the TTY. The `kref_put` function is decrements the reference count. The second argument, `queue_release_one_tty`, *that's* the interesting part. A TTY isn't immediately released when its reference count drops to zero, and the inclusion of `queue_release_one_tty` strongly implies deferred release logic.
My immediate assumption is that this function likely enqueues some type of work or task, either to a workqueue, a threaded handler, or perhaps even a direct call to a kernel thread, whose job it is to perform the actual TTY cleanup when the last reference is gone. This likely means handling resources like flushing any remaining data in the buffer, releasing associated memory, and signaling any waiting processes or applications that the TTY is no longer available. This could have been done inside the original function but it seems a more efficient approach to schedule the release in a separate queue, for performance reasons. I'll need to dig into `queue_release_one_tty` to confirm the actual execution path, but this is a solid initial hypothesis based on standard kernel practices. I wonder if it has any lock considerations...
|
| 38/2 |
2026/04/07 00:27 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/tty/tty_io.c IncludeLines:true Name:queue_release_one_tty]
Results: map[SourceCode:1536: static void queue_release_one_tty(struct kref *kref)
1537: {
1538: struct tty_struct *tty = container_of(kref, struct tty_struct, kref);
1539:
1540: /* The hangup queue is now free so we can reuse it rather than
1541: * waste a chunk of memory for each port.
1542: */
1543: INIT_WORK(&tty->hangup_work, release_one_tty);
1544: schedule_work(&tty->hangup_work);
1545: }
SourceFile:drivers/tty/tty_io.c]
|
| 39/2 |
2026/04/07 00:27 |
llm |
expert |
0m
Model:
Tokens:
input: 35324
output: 19
thoughts: 1877
Thoughts: **Actionable Bug Report Analysis**
Okay, so here's what I've got. `queue_release_one_tty` schedules `release_one_tty`, which then calls `kfree(tty)`. The provided stack trace for the "freed by" task perfectly confirms this: `process_one_work` calls `release_one_tty`, which ultimately calls `kfree`. The report's consistent and gives me something to work with.
The core of the issue is that `kbd_keycode` accesses `vc->port.tty` without any protection like `tty_port_tty_get`. Hang on, though. `kbd_keycode` is called under `kbd_event_lock`, while `con_shutdown` (which sets `vc->port.tty = NULL`) is called under `console_lock`. Meanwhile `release_one_tty` (which frees the `tty_struct`) is called from a workqueue.
The race condition goes like this: `kbd_keycode` reads `tty = vc->port.tty`. `con_shutdown` sets `vc->port.tty = NULL`. Then `release_one_tty` frees the `tty` structure. Finally, `kbd_keycode` tries to use the now-freed `tty`. The critical detail is that `con_shutdown` sets the TTY pointer to NULL *before* it's actually freed, but `kbd_keycode` has a stale pointer.
The fix? Use `tty_port_tty_get` to get a reference to the TTY. And I need to verify its context. `kbd_keycode` is called from `kbd_event`. `kbd_event` is called from the input system, so it *could* be called from an interrupt context. But `tty_port_tty_get` uses `kref_get_unless_zero`, which is safe in interrupt context, and `tty_kref_put` calls `queue_release_one_tty`, which is also safe.
Wait, `kbd_keycode` is called with `kbd_event_lock` held. That's a spinlock, so `kbd_keycode` is in an atomic context. Both `tty_port_tty_get` and `tty_kref_put` are safe in atomic context, so the fix is straightforward and I can proceed confidently.
The report also shows where the `tty_struct` was allocated: in `alloc_tty_struct`, which is called through a few functions and ultimately called by `tty_open` via the tty driver.
I've checked the line numbers. `kbd_keycode` uses the `tty` pointer at line 1435, which accesses `tty->termios.c_lflag`. KASAN confirms the memory access address at the right offset, so that's consistent. I also saw that the inline function `tty_chars_in_buffer` also accesses `tty->ops`.
The bug is clearly a race between `kbd_keycode` and TTY release. I'm feeling good about this analysis. And I should double-check that `kbd_keycode` isn't called from somewhere else. Nope, it's `static` and called only from `kbd_event`. And `kbd_event` is `static` and is only registered within the input system. I should be able to rely on this.
Final check of the report: BUG: KASAN slab-use-after-free in `kbd_keycode`. Read of size 4 at the given address, task 7920, stack trace from `kbd_event` to `kbd_keycode`, allocated by task 7915 using `alloc_tty_struct`, freed by task 15095 by way of `kfree`, with a potentially related work creation in `tty_release_struct`. Everything aligns.
I also remember that `tty_init_dev` calls `alloc_tty_struct`. `tty_open` calls `tty_open_by_driver` which calls `tty_init_dev`. And the TTY is freed when the last reference is dropped in `tty_release` upon closing the last file descriptor. The race is therefore between an `evdev_write` (injecting a key event) and `close(tty_fd)`. Wait, `evdev_write` is writing to an input device and not the TTY itself. So the TTY is `/dev/tty1`, while the input device is `/dev/input/eventX`. Thus, task 7920 is writing to `/dev/input/eventX` and task 15095 is closing `/dev/tty1` (or whatever task closed the TTY). That makes sense β Syzkaller often
|
| 40/2 |
2026/04/07 00:27 |
tool |
set-results |
0m
Args: map[Actionable:true Confident:true]
Results: map[Actionable:true Confident:true]
|
| 41/2 |
2026/04/07 00:27 |
llm |
expert |
0m
Model:
Tokens:
input: 38262
output: 659
thoughts: 134
Thoughts: **Bug Analysis Complete: A Use-After-Free in kbd_keycode**
Alright, the deep dive is done. The analysis is solid, and the report I just generated is both actionable and self-consistent β that's what I like to see. The smoking gun? It's a textbook use-after-free vulnerability, smack dab in `kbd_keycode`. The code is accessing `vc->port.tty` without any safeguards, and it's racing with the TTY release. This is classic.
I meticulously traced the execution paths. The stack traces β the access itself, the allocation, and the subsequent free β they all nail down the same `tty_struct` object. No ambiguity there. Furthermore, the offset of the memory access (860 bytes) is a perfect match for the `tty->termios.c_lflag` field, which, of course, is what `kbd_keycode` is trying to get at.
The allocation and deallocation routes for `tty_struct` are completely standard, which is exactly what I'd expect. The underlying issue is not in these, but within the access itself.
Frankly, I think the report I produced is pretty damn clear. It provides all the necessary details to get right to the root of the problem. Time to move on to the fix.
|
| 42/1 |
2026/04/07 00:27 |
action |
format-explanation |
0m
Results: map[Explanation:The bug report is a KASAN slab-use-after-free in `kbd_keycode`, which is called
from `kbd_event` in the keyboard handler of the VT subsystem.
### Analysis of the Stack Traces:
1. **Access Stack Trace:**
The crash occurs in `kbd_keycode` at `drivers/tty/vt/keyboard.c:1435`.
```c
1434: (!vc_kbd_mode(kbd, VC_REPEAT) ||
1435: (tty && !L_ECHO(tty) && tty_chars_in_buffer(tty))))
{
```
The access is a read of size 4 at offset 860 bytes into the object.
`L_ECHO(tty)` is a macro that accesses `tty->termios.c_lflag`.
In `struct tty_struct`, `termios` is at offset 848 bytes, and `c_lflag` is
at offset 12 bytes within `struct ktermios`, totaling 860 bytes. This
perfectly matches the KASAN report.
2. **Allocation Stack Trace:**
The object was allocated in `alloc_tty_struct` (called via `tty_open` ->
`tty_init_dev`). This confirms the object is indeed a `struct tty_struct`.
3. **Free Stack Trace:**
The object was freed via a workqueue (`process_one_work` ->
`release_one_tty` -> `kfree`).
The "Last potentially related work creation" shows the work was queued in
`tty_release_struct` (called via `tty_release` when the last file descriptor
to the TTY is closed).
### Root Cause:
The function `kbd_keycode` obtains a pointer to the TTY associated with the
foreground console via `tty = vc->port.tty` (line 1381). However, it does so
without any locking or incrementing the TTY's reference count (kref).
If another process closes the TTY concurrently, `tty_release` will eventually
call `tty_release_struct`, which calls `release_tty`. `release_tty` calls
`tty->ops->shutdown(tty)` (which for VT is `con_shutdown`, setting `vc->port.tty
= NULL`) and then drops the last kref of the TTY, leading to its destruction in
a workqueue.
If `kbd_keycode` has already read the `tty` pointer before it was set to NULL,
it will continue to use it, leading to a use-after-free when it accesses
`tty->termios.c_lflag` at line 1435.
### Conclusion:
The report is self-consistent and provides all necessary information (access,
allocation, and free stacks) to identify the race condition and the affected
object. The subsystem (TTY/VT) and the specific code paths are clearly
identified.
The report is actionable.]
|