| 0/0 |
2026/08/09 23:25 |
flow |
moderation |
17m
Results: map[Actionable:true Explanation:An analysis of the KASAN use-after-free bug report has been conducted. The
report is **fully self-consistent, highly logical, and actionable**.
### Detailed Analysis of the Bug Report
1. **Object and Memory Layout Consistency**:
* The bug is a use-after-free read of size 8 at offset 32 bytes inside a
freed 40-byte region belonging to the `iommu_iova` cache.
* In `drivers/iommu/iova.c`, `struct iova` has a size of 40 bytes. Its fields
are:
* `node` (`struct rb_node`): offset 0, size 24 bytes.
* `pfn_hi` (`unsigned long`): offset 24, size 8 bytes.
* `pfn_lo` (`unsigned long`): offset 32, size 8 bytes.
* The trapping instruction is `if (pfn < iova->pfn_lo)` in
`private_find_iova` at line 285, which perfectly matches the read of size 8
at offset 32 bytes.
2. **Allocation and Free Stack Consistency**:
* **Allocation**: The `iova` structure was allocated during system
initialization via `iommu_setup_dma_ops` -> `reserve_iova` ->
`alloc_iova_mem`.
* **Free**: The `iova` structure was freed during device removal and domain
destruction via `iommu_domain_free` -> `iommu_put_dma_cookie` ->
`put_iova_domain` -> `free_iova_mem`.
* Both stacks are perfectly consistent and belong to the same `iommu`
subsystem and `iova` object type.
3. **Root Cause of the Use-After-Free**:
* The `<TASK>` stack trace shows that `iommu_domain_free` was in the process
of destroying the domain. It called `iommu_put_dma_cookie`, which called
`iommu_dma_free_fq` (to delete the flush queue timer) and `put_iova_domain`
(which freed all `iova` structures).
* Finally, `iommu_put_dma_cookie` called `kfree(cookie)`.
* Inside `kfree(cookie)`, the debugobjects subsystem
(`debug_check_no_obj_freed`) detected that the flush queue timer
`cookie->fq_timer` was still in the `ODEBUG_STATE_ACTIVE` state
(active/pending).
* To report this, `debug_check_no_obj_freed` printed a warning via `printk`
-> `console_flush_all`.
* Inside `console_flush_all`, interrupts were enabled, allowing an APIC timer
interrupt to occur on CPU 0.
* Upon exit from the interrupt, softirqs were processed, invoking the
expired/pending `fq_flush_timeout` timer handler on CPU 0.
* `fq_flush_timeout` called `fq_ring_free` -> `free_iova_fast` -> `free_iova`
-> `private_find_iova`.
* Since `put_iova_domain` had already completed and freed all `iova`
structures, `private_find_iova` accessed the freed `iova` structure,
triggering the KASAN use-after-free.
### Conclusion
The bug report is **actionable** because it contains all 3 required stack traces
(access, allocation, and free) which are completely consistent and reveal a
clear lifecycle/synchronization bug where the flush queue timer
`cookie->fq_timer` is not properly deactivated or is re-queued/run after the
domain's `iova` structures have been freed.]
|
| 1/1 |
2026/08/09 23:25 |
action |
kernel-checkouter |
0m
Results: map[KernelSrc:/app/workdir/cache/src/23f34877926e4a8386855e646fda4dd9dccd99b9]
|
| 2/1 |
2026/08/09 23:25 |
action |
kernel-builder |
0m
Results: map[KernelObj:/app/workdir/cache/build/a336c7a8629bc3d6298fba432a30764450022c13]
|
| 3/1 |
2026/08/09 23:25 |
action |
codesearch-prepare |
0m
Results: map[Index:codesearch-index]
|
| 4/1 |
2026/08/09 23:26 |
agent |
expert |
17m
Model:
Results:
Instruction: You are an experienced Linux kernel developer tasked with determining if the given kernel bug
report is actionable or not. Actionable means that it contains enough info to root cause
the underlying bug, and that the report is self-consistent and makes sense, rather than
e.g. a one-off nonsensical crash induced by a previous memory corruption.
The bug report is about a use-after-free bug generated by KASAN tool.
It should contain 3 stack traces: the bad memory access stack, the heap block allocation stack,
and the heap block free stack. If the report does not contain 3 stacks, it's not actionable.
All 3 stack traces should be related to the same object type,
and usually be in the same kernel subsystem (at least leaf stack frames).
An example of an actionable and consistent report would be: first access stack relates
to an access to a field of struct Foo, allocation/free stacks relate to allocation/free
of the struct Foo.
In inconsistent/nonsensical reports an access may be to a struct Foo, but allocation
stack allocates a different structure in a different subsystem.
Look for other suspicious signals/inconsistencies that can make this report hard to
debug/understand.
In the final reply explain why you think the report is self-consistent and actionable,
or why it's inconsistent and/or not actionable.
Use the provided tools to confirm any assumptions, variables/fields being accessed, etc.
Don't make assumptions about the kernel source code (it may be different from what you assume it is).
Extensively use the provided code access tools (codesearch-*, git-*, grepper, etc)
to examine the actual source code, and confirm any assumptions.
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt:
The bug report is:
==================================================================
BUG: KASAN: slab-use-after-free in private_find_iova drivers/iommu/iova.c:285 [inline]
BUG: KASAN: slab-use-after-free in free_iova+0xab/0x200 drivers/iommu/iova.c:355
Read of size 8 at addr ffff888000274d60 by task syz.0.0/5327
CPU: 0 UID: 0 PID: 5327 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:
<IRQ>
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
private_find_iova drivers/iommu/iova.c:285 [inline]
free_iova+0xab/0x200 drivers/iommu/iova.c:355
fq_ring_free_locked+0x1ab/0x310 drivers/iommu/dma-iommu.c:155
fq_ring_free drivers/iommu/dma-iommu.c:170 [inline]
fq_flush_timeout+0x234/0x2d0 drivers/iommu/dma-iommu.c:194
call_timer_fn+0x192/0x5e0 kernel/time/timer.c:1748
expire_timers kernel/time/timer.c:1799 [inline]
__run_timers kernel/time/timer.c:2374 [inline]
__run_timer_base+0x652/0x8b0 kernel/time/timer.c:2386
run_timer_base kernel/time/timer.c:2395 [inline]
run_timer_softirq+0xb7/0x170 kernel/time/timer.c:2405
handle_softirqs+0x225/0x840 kernel/softirq.c:622
__do_softirq kernel/softirq.c:656 [inline]
invoke_softirq kernel/softirq.c:496 [inline]
__irq_exit_rcu+0xca/0x220 kernel/softirq.c:735
irq_exit_rcu+0x9/0x30 kernel/softirq.c:752
instr_sysvec_apic_timer_interrupt arch/x86/kernel/apic/apic.c:1062 [inline]
sysvec_apic_timer_interrupt+0xa6/0xc0 arch/x86/kernel/apic/apic.c:1062
</IRQ>
<TASK>
asm_sysvec_apic_timer_interrupt+0x1a/0x20 arch/x86/include/asm/idtentry.h:674
RIP: 0010:console_flush_one_record arch/x86/include/asm/irqflags.h:-1 [inline]
RIP: 0010:console_flush_all+0x7d2/0xaf0 kernel/printk/printk.c:3343
Code: ff ff e8 81 3b 21 00 90 0f 0b 90 e9 9c fc ff ff e8 73 3b 21 00 e8 ce 5d 2d 0a 48 85 db 74 c0 e8 64 3b 21 00 fb 48 8b 5c 24 18 <48> 8b 44 24 28 42 80 3c 20 00 4d 8d 77 58 74 08 4c 89 f7 e8 e6 6d
RSP: 0018:ffffc9000f6271e0 EFLAGS: 00000283
RAX: ffffffff81a5e75c RBX: ffffc9000f627340 RCX: 0000000000100000
RDX: ffffc9000edfa000 RSI: 000000000002c5aa RDI: 000000000002c5ab
RBP: ffffc9000f6272f0 R08: ffffffff9057c237 R09: 1ffffffff20af846
R10: dffffc0000000000 R11: fffffbfff20af847 R12: dffffc0000000000
R13: 0000000000000001 R14: 0000000000000000 R15: ffffffff8f471ce0
__console_flush_and_unlock kernel/printk/printk.c:3373 [inline]
console_unlock+0xd1/0x1c0 kernel/printk/printk.c:3413
vprintk_emit+0x485/0x560 kernel/printk/printk.c:2479
__warn_printf lib/bug.c:189 [inline]
__report_bug+0x3c5/0x570 lib/bug.c:249
report_bug_entry+0x19a/0x290 lib/bug.c:274
handle_bug+0xce/0x200 arch/x86/kernel/traps.c:431
exc_invalid_op+0x1a/0x50 arch/x86/kernel/traps.c:490
asm_exc_invalid_op+0x1a/0x20 arch/x86/include/asm/idtentry.h:593
RIP: 0010:debug_print_object+0x18a/0x230 lib/debugobjects.c:629
Code: f8 48 c1 e8 03 80 3c 18 00 74 08 4c 89 ff e8 3d 13 56 fd 4d 8b 0f 4c 89 ef 48 8b 74 24 08 48 89 ea 44 89 e1 4d 89 f0 ff 34 24 <67> 48 0f b9 3a 48 83 c4 08 ff 05 03 b3 77 0b 48 83 c4 10 5b 41 5c
RSP: 0018:ffffc9000f6276f0 EFLAGS: 00010246
RAX: 1ffffffff17decd0 RBX: dffffc0000000000 RCX: 0000000000000000
RDX: ffffffff8c4bf940 RSI: ffffffff8c4bf3c0 RDI: ffffffff90632440
RBP: ffffffff8c4bf940 R08: ffff8880002684e0 R09: ffffffff8bef79e0
R10: dffffc0000000000 R11: ffffffff81b33db0 R12: 0000000000000000
R13: ffffffff90632440 R14: ffff8880002684e0 R15: ffffffff8bef6680
__debug_check_no_obj_freed lib/debugobjects.c:1171 [inline]
debug_check_no_obj_freed+0x2e3/0x450 lib/debugobjects.c:1201
slab_free_hook mm/slub.c:2608 [inline]
slab_free mm/slub.c:6377 [inline]
kfree+0x13e/0x640 mm/slub.c:6692
iommu_domain_free drivers/iommu/iommu.c:2131 [inline]
iommu_deinit_device+0x401/0x840 drivers/iommu/iommu.c:600
__iommu_group_remove_device+0x1dd/0x270 drivers/iommu/iommu.c:764
iommu_release_device drivers/iommu/iommu.c:783 [inline]
iommu_bus_notifier+0x6d/0x2c0 drivers/iommu/iommu.c:1826
notifier_call_chain+0x1a5/0x3d0 kernel/notifier.c:85
blocking_notifier_call_chain+0x6a/0x90 kernel/notifier.c:380
bus_notify+0x143/0x180 drivers/base/bus.c:1086
device_del+0x740/0x8f0 drivers/base/core.c:3978
pci_destroy_dev drivers/pci/remove.c:46 [inline]
pci_remove_bus_device+0x12a/0x2a0 drivers/pci/remove.c:110
pci_stop_and_remove_bus_device drivers/pci/remove.c:129 [inline]
pci_stop_and_remove_bus_device_locked+0x7f/0xb0 drivers/pci/remove.c:136
remove_store+0xe5/0x150 drivers/pci/pci-sysfs.c:523
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:0x7f2afa79e019
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:00007f2afb6f9fe8 EFLAGS: 00000246 ORIG_RAX: 0000000000000001
RAX: ffffffffffffffda RBX: 00007f2afaa25fa0 RCX: 00007f2afa79e019
RDX: 0000000000000001 RSI: 00002000000000c0 RDI: 0000000000000008
RBP: 00007f2afa83500c R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
R13: 00007f2afaa26038 R14: 00007f2afaa25fa0 R15: 00007fff4eb947c8
</TASK>
Allocated by task 1:
kasan_save_stack mm/kasan/common.c:57 [inline]
kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
unpoison_slab_object mm/kasan/common.c:340 [inline]
__kasan_slab_alloc+0x6c/0x80 mm/kasan/common.c:366
kasan_slab_alloc include/linux/kasan.h:253 [inline]
slab_post_alloc_hook mm/slub.c:4584 [inline]
slab_alloc_node mm/slub.c:4917 [inline]
kmem_cache_alloc_noprof+0x2a0/0x5f0 mm/slub.c:4931
alloc_iova_mem drivers/iommu/iova.c:231 [inline]
alloc_and_init_iova drivers/iommu/iova.c:474 [inline]
__insert_new_range drivers/iommu/iova.c:489 [inline]
reserve_iova+0x2b0/0x480 drivers/iommu/iova.c:544
iova_reserve_pci_windows drivers/iommu/dma-iommu.c:521 [inline]
iova_reserve_iommu_regions drivers/iommu/dma-iommu.c:563 [inline]
iommu_dma_init_domain drivers/iommu/dma-iommu.c:723 [inline]
iommu_setup_dma_ops+0xa21/0x1820 drivers/iommu/dma-iommu.c:2159
bus_iommu_probe+0x296/0x470 drivers/iommu/iommu.c:1983
iommu_device_register+0x1af/0x210 drivers/iommu/iommu.c:307
intel_iommu_init+0x6f0/0x8e0 drivers/iommu/intel/iommu.c:2662
pci_iommu_init+0x38/0x70 arch/x86/kernel/pci-dma.c:174
do_one_initcall+0x250/0x870 init/main.c:1347
do_initcall_level+0x10a/0x1a0 init/main.c:1409
do_initcalls+0x59/0xa0 init/main.c:1425
kernel_init_freeable+0x29d/0x3e0 init/main.c:1658
kernel_init+0x1d/0x1d0 init/main.c:1548
ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
Freed by task 5327:
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]
kmem_cache_free+0x182/0x650 mm/slub.c:6504
free_iova_mem drivers/iommu/iova.c:237 [inline]
put_iova_domain+0xcc/0x100 drivers/iommu/iova.c:454
iommu_put_dma_cookie+0x45c/0x560 drivers/iommu/dma-iommu.c:431
iommu_domain_free drivers/iommu/iommu.c:2131 [inline]
iommu_deinit_device+0x401/0x840 drivers/iommu/iommu.c:600
__iommu_group_remove_device+0x1dd/0x270 drivers/iommu/iommu.c:764
iommu_release_device drivers/iommu/iommu.c:783 [inline]
iommu_bus_notifier+0x6d/0x2c0 drivers/iommu/iommu.c:1826
notifier_call_chain+0x1a5/0x3d0 kernel/notifier.c:85
blocking_notifier_call_chain+0x6a/0x90 kernel/notifier.c:380
bus_notify+0x143/0x180 drivers/base/bus.c:1086
device_del+0x740/0x8f0 drivers/base/core.c:3978
pci_destroy_dev drivers/pci/remove.c:46 [inline]
pci_remove_bus_device+0x12a/0x2a0 drivers/pci/remove.c:110
pci_stop_and_remove_bus_device drivers/pci/remove.c:129 [inline]
pci_stop_and_remove_bus_device_locked+0x7f/0xb0 drivers/pci/remove.c:136
remove_store+0xe5/0x150 drivers/pci/pci-sysfs.c:523
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 ffff888000274d40
which belongs to the cache iommu_iova of size 40
The buggy address is located 32 bytes inside of
freed 40-byte region [ffff888000274d40, ffff888000274d68)
The buggy address belongs to the physical page:
page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x274
flags: 0x7ff00000000000(node=0|zone=0|lastcpupid=0x7ff)
page_type: f5(slab)
raw: 007ff00000000000 ffff888030463500 dead000000000100 dead000000000122
raw: 0000000000000000 0000000800400040 00000000f5000000 0000000000000000
page dumped because: kasan: bad access detected
page_owner tracks the page as allocated
page last allocated via order 0, migratetype Unmovable, gfp_mask 0xd2820(GFP_ATOMIC|__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP|__GFP_NOMEMALLOC), pid 1, tgid 1 (swapper/0), ts 8498569964, free_ts 0
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]
kmem_cache_alloc_noprof+0x382/0x5f0 mm/slub.c:4931
alloc_iova_mem drivers/iommu/iova.c:231 [inline]
alloc_and_init_iova drivers/iommu/iova.c:474 [inline]
__insert_new_range drivers/iommu/iova.c:489 [inline]
reserve_iova+0x2b0/0x480 drivers/iommu/iova.c:544
iova_reserve_pci_windows drivers/iommu/dma-iommu.c:521 [inline]
iova_reserve_iommu_regions drivers/iommu/dma-iommu.c:563 [inline]
iommu_dma_init_domain drivers/iommu/dma-iommu.c:723 [inline]
iommu_setup_dma_ops+0xa21/0x1820 drivers/iommu/dma-iommu.c:2159
bus_iommu_probe+0x296/0x470 drivers/iommu/iommu.c:1983
iommu_device_register+0x1af/0x210 drivers/iommu/iommu.c:307
intel_iommu_init+0x6f0/0x8e0 drivers/iommu/intel/iommu.c:2662
pci_iommu_init+0x38/0x70 arch/x86/kernel/pci-dma.c:174
do_one_initcall+0x250/0x870 init/main.c:1347
do_initcall_level+0x10a/0x1a0 init/main.c:1409
do_initcalls+0x59/0xa0 init/main.c:1425
page_owner free stack trace missing
Memory state around the buggy address:
ffff888000274c00: 00 00 00 00 00 fc fc fc 00 00 00 00 00 fc fc fc
ffff888000274c80: fa fb fb fb fb fc fc fc fa fb fb fb fb fc fc fc
>ffff888000274d00: fa fb fb fb fb fc fc fc fa fb fb fb fb fc fc fc
^
ffff888000274d80: fa fb fb fb fb fc fc fc 00 00 00 00 00 fc fc fc
ffff888000274e00: 00 00 00 00 00 fc fc fc 00 00 00 00 00 fc fc fc
==================================================================
----------------
Code disassembly (best guess), 1 bytes skipped:
0: ff ljmp (bad)
1: e8 81 3b 21 00 call 0x213b87
6: 90 nop
7: 0f 0b ud2
9: 90 nop
a: e9 9c fc ff ff jmp 0xfffffcab
f: e8 73 3b 21 00 call 0x213b87
14: e8 ce 5d 2d 0a call 0xa2d5de7
19: 48 85 db test %rbx,%rbx
1c: 74 c0 je 0xffffffde
1e: e8 64 3b 21 00 call 0x213b87
23: fb sti
24: 48 8b 5c 24 18 mov 0x18(%rsp),%rbx
* 29: 48 8b 44 24 28 mov 0x28(%rsp),%rax <-- trapping instruction
2e: 42 80 3c 20 00 cmpb $0x0,(%rax,%r12,1)
33: 4d 8d 77 58 lea 0x58(%r15),%r14
37: 74 08 je 0x41
39: 4c 89 f7 mov %r14,%rdi
3c: e8 .byte 0xe8
3d: e6 6d out %al,$0x6d
Reply: An analysis of the KASAN use-after-free bug report has been conducted. The report is **fully self-consistent, highly logical, and actionable**.
### Detailed Analysis of the Bug Report
1. **Object and Memory Layout Consistency**:
* The bug is a use-after-free read of size 8 at offset 32 bytes inside a freed 40-byte region belonging to the `iommu_iova` cache.
* In `drivers/iommu/iova.c`, `struct iova` has a size of 40 bytes. Its fields are:
* `node` (`struct rb_node`): offset 0, size 24 bytes.
* `pfn_hi` (`unsigned long`): offset 24, size 8 bytes.
* `pfn_lo` (`unsigned long`): offset 32, size 8 bytes.
* The trapping instruction is `if (pfn < iova->pfn_lo)` in `private_find_iova` at line 285, which perfectly matches the read of size 8 at offset 32 bytes.
2. **Allocation and Free Stack Consistency**:
* **Allocation**: The `iova` structure was allocated during system initialization via `iommu_setup_dma_ops` -> `reserve_iova` -> `alloc_iova_mem`.
* **Free**: The `iova` structure was freed during device removal and domain destruction via `iommu_domain_free` -> `iommu_put_dma_cookie` -> `put_iova_domain` -> `free_iova_mem`.
* Both stacks are perfectly consistent and belong to the same `iommu` subsystem and `iova` object type.
3. **Root Cause of the Use-After-Free**:
* The `<TASK>` stack trace shows that `iommu_domain_free` was in the process of destroying the domain. It called `iommu_put_dma_cookie`, which called `iommu_dma_free_fq` (to delete the flush queue timer) and `put_iova_domain` (which freed all `iova` structures).
* Finally, `iommu_put_dma_cookie` called `kfree(cookie)`.
* Inside `kfree(cookie)`, the debugobjects subsystem (`debug_check_no_obj_freed`) detected that the flush queue timer `cookie->fq_timer` was still in the `ODEBUG_STATE_ACTIVE` state (active/pending).
* To report this, `debug_check_no_obj_freed` printed a warning via `printk` -> `console_flush_all`.
* Inside `console_flush_all`, interrupts were enabled, allowing an APIC timer interrupt to occur on CPU 0.
* Upon exit from the interrupt, softirqs were processed, invoking the expired/pending `fq_flush_timeout` timer handler on CPU 0.
* `fq_flush_timeout` called `fq_ring_free` -> `free_iova_fast` -> `free_iova` -> `private_find_iova`.
* Since `put_iova_domain` had already completed and freed all `iova` structures, `private_find_iova` accessed the freed `iova` structure, triggering the KASAN use-after-free.
### Conclusion
The bug report is **actionable** because it contains all 3 required stack traces (access, allocation, and free) which are completely consistent and reveal a clear lifecycle/synchronization bug where the flush queue timer `cookie->fq_timer` is not properly deactivated or is re-queued/run after the domain's `iova` structures have been freed.
|
| 5/2 |
2026/08/09 23:26 |
llm |
expert |
0m
Model:
Tokens:
input: 11408
output: 38
thoughts: 1025
Thoughts: **Analyzing the KASAN Use-After-Free Report**
Alright, let's dive into this KASAN report and see if we can make sense of it. The report screams a slab-use-after-free issue, specifically in the iommu code. The report highlights that the errors occur in `drivers/iommu/iova.c` in the functions `private_find_iova` at line 285 and `free_iova` at line 355. Also, the read size is 8 bytes at an address.
The call chain reveals the access stack, the allocation stack, and the free stack, and I need to piece this together.
On the access side, the bug is reported from within `private_find_iova` and from `free_iova`. That's where the problem is manifesting. These are called from `fq_ring_free_locked` and down the line through a timer interrupt.
Looking at the allocation stack, we see `kmem_cache_alloc_noprof` at the core and then the `alloc_iova_mem` sequence, all the way back to the initialization of the IOMMU. This tells me where the memory was originally allocated.
The free stack reveals where the memory is supposed to be freed: `kmem_cache_free`, and then, going up the stack, we see a `put_iova_domain` call, and eventually IOMMU device de-initialization. This seems to be during device removal.
Now, I need to understand the relationship between these stacks. The critical link is likely between the allocation and free sequences: where is the memory being freed and when is the attempt to access it happening? Is it a race condition, a double-free, or a subtle use-after-free scenario?
To analyze, I'll examine the source code in `drivers/iommu/iova.c` and `drivers/iommu/dma-iommu.c` focusing on the lines mentioned. I will use the established tools at my disposal, such as `codesearch-find-references`, `read-file`, or `grepper`, to quickly identify key code locations and understand the control flow around these functions. My focus will be around lines 285, 355 and 454 in iova.c.
|
| 6/2 |
2026/08/09 23:26 |
tool |
read-file |
0m
Args: map[File:drivers/iommu/iova.c FirstLine:220 LineCount:50]
Results: map[Contents: 220: iova32_full:
221: spin_unlock_irqrestore(&iovad->iova_rbtree_lock, flags);
222: return -ENOMEM;
223: }
224:
225: static struct kmem_cache *iova_cache;
226: static unsigned int iova_cache_users;
227: static DEFINE_MUTEX(iova_cache_mutex);
228:
229: static struct iova *alloc_iova_mem(void)
230: {
231: return kmem_cache_zalloc(iova_cache, GFP_ATOMIC | __GFP_NOWARN);
232: }
233:
234: static void free_iova_mem(struct iova *iova)
235: {
236: if (iova->pfn_lo != IOVA_ANCHOR)
237: kmem_cache_free(iova_cache, iova);
238: }
239:
240: /**
241: * alloc_iova - allocates an iova
242: * @iovad: - iova domain in question
243: * @size: - size of page frames to allocate
244: * @limit_pfn: - max limit address
245: * @size_aligned: - set if size_aligned address range is required
246: * This function allocates an iova in the range iovad->start_pfn to limit_pfn,
247: * searching top-down from limit_pfn to iovad->start_pfn. If the size_aligned
248: * flag is set then the allocated address iova->pfn_lo will be naturally
249: * aligned on roundup_power_of_two(size).
250: */
251: struct iova *
252: alloc_iova(struct iova_domain *iovad, unsigned long size,
253: unsigned long limit_pfn,
254: bool size_aligned)
255: {
256: struct iova *new_iova;
257: int ret;
258:
259: new_iova = alloc_iova_mem();
260: if (!new_iova)
261: return NULL;
262:
263: ret = __alloc_and_insert_iova_range(iovad, size, limit_pfn + 1,
264: new_iova, size_aligned);
265:
266: if (ret) {
267: free_iova_mem(new_iova);
268: return NULL;
269: }
]
|
| 7/2 |
2026/08/09 23:26 |
llm |
expert |
0m
Model:
Tokens:
input: 13771
output: 39
thoughts: 30
Thoughts: Here's my analysis of the provided text, rendered in the first person, assuming an expert-level understanding of the subject:
**Examining iova.c for Context**
Okay, so the directive is to dig into `drivers/iommu/iova.c`. Let's focus our attention around lines 285 and 355. Those line numbers give me a pretty good starting point. I can quickly recall the usual areas of concern within that file; likely, we're dealing with either IO Virtual Address (IOVA) management and allocation, or, perhaps, interaction with the hardware page table or DMA remapping operations, given the context implied by "iommu". With those line numbers in mind, I anticipate these are code segments related to the core logic. Specifically, I imagine either IOVA space allocation, deallocation, or addressing issues related to the specific device interaction.
|
| 8/2 |
2026/08/09 23:26 |
tool |
read-file |
0m
Args: map[File:drivers/iommu/iova.c FirstLine:270 LineCount:100]
Results: map[Contents: 270:
271: return new_iova;
272: }
273: EXPORT_SYMBOL_GPL(alloc_iova);
274:
275: static struct iova *
276: private_find_iova(struct iova_domain *iovad, unsigned long pfn)
277: {
278: struct rb_node *node = iovad->rbroot.rb_node;
279:
280: assert_spin_locked(&iovad->iova_rbtree_lock);
281:
282: while (node) {
283: struct iova *iova = to_iova(node);
284:
285: if (pfn < iova->pfn_lo)
286: node = node->rb_left;
287: else if (pfn > iova->pfn_hi)
288: node = node->rb_right;
289: else
290: return iova; /* pfn falls within iova's range */
291: }
292:
293: return NULL;
294: }
295:
296: static void remove_iova(struct iova_domain *iovad, struct iova *iova)
297: {
298: assert_spin_locked(&iovad->iova_rbtree_lock);
299: __cached_rbnode_delete_update(iovad, iova);
300: rb_erase(&iova->node, &iovad->rbroot);
301: }
302:
303: /**
304: * find_iova - finds an iova for a given pfn
305: * @iovad: - iova domain in question.
306: * @pfn: - page frame number
307: * This function finds and returns an iova belonging to the
308: * given domain which matches the given pfn.
309: */
310: struct iova *find_iova(struct iova_domain *iovad, unsigned long pfn)
311: {
312: unsigned long flags;
313: struct iova *iova;
314:
315: /* Take the lock so that no other thread is manipulating the rbtree */
316: spin_lock_irqsave(&iovad->iova_rbtree_lock, flags);
317: iova = private_find_iova(iovad, pfn);
318: spin_unlock_irqrestore(&iovad->iova_rbtree_lock, flags);
319: return iova;
320: }
321: EXPORT_SYMBOL_GPL(find_iova);
322:
323: /**
324: * __free_iova - frees the given iova
325: * @iovad: iova domain in question.
326: * @iova: iova in question.
327: * Frees the given iova belonging to the giving domain
328: */
329: void
330: __free_iova(struct iova_domain *iovad, struct iova *iova)
331: {
332: unsigned long flags;
333:
334: spin_lock_irqsave(&iovad->iova_rbtree_lock, flags);
335: remove_iova(iovad, iova);
336: spin_unlock_irqrestore(&iovad->iova_rbtree_lock, flags);
337: free_iova_mem(iova);
338: }
339: EXPORT_SYMBOL_GPL(__free_iova);
340:
341: /**
342: * free_iova - finds and frees the iova for a given pfn
343: * @iovad: - iova domain in question.
344: * @pfn: - pfn that is allocated previously
345: * This functions finds an iova for a given pfn and then
346: * frees the iova from that domain.
347: */
348: void
349: free_iova(struct iova_domain *iovad, unsigned long pfn)
350: {
351: unsigned long flags;
352: struct iova *iova;
353:
354: spin_lock_irqsave(&iovad->iova_rbtree_lock, flags);
355: iova = private_find_iova(iovad, pfn);
356: if (!iova) {
357: spin_unlock_irqrestore(&iovad->iova_rbtree_lock, flags);
358: return;
359: }
360: remove_iova(iovad, iova);
361: spin_unlock_irqrestore(&iovad->iova_rbtree_lock, flags);
362: free_iova_mem(iova);
363: }
364: EXPORT_SYMBOL_GPL(free_iova);
365:
366: /**
367: * alloc_iova_fast - allocates an iova from rcache
368: * @iovad: - iova domain in question
369: * @size: - size of page frames to allocate
]
|
| 9/2 |
2026/08/09 23:26 |
llm |
expert |
0m
Model:
Tokens:
input: 15575
output: 36
thoughts: 104
Thoughts: **Analyzing the `struct iova` Definition**
Okay, let's dive into the details. I need to understand what's happening at offset 32 within the `struct iova` structure. The error seems to be pointing to that specific location. The address in question is `ffff888000274d60`, and I see that it falls within the range `[ffff888000274d40, ffff888000274d68)`, which is exactly 32 bytes from the start of the lower bound. So, it's pretty clear that I need to find the definition of `struct iova` to understand what that particular memory location is supposed to hold and figure out why there might be a problem. Time to track down the header file and dissect the structure!
|
| 10/2 |
2026/08/09 23:26 |
tool |
codesearch-struct-layout |
0m
Args: map[ContextFile:drivers/iommu/iova.c Name:iova]
Results: map[Fields:[map[Name:node OffsetBits:0 SizeBits:192] map[Name:pfn_hi OffsetBits:192 SizeBits:64] map[Name:pfn_lo OffsetBits:256 SizeBits:64]]]
|
| 11/2 |
2026/08/09 23:26 |
llm |
expert |
0m
Model:
Tokens:
input: 15974
output: 42
thoughts: 377
Thoughts: **Debugging a Suspect IOVA Bug**
Okay, here's what I'm thinking. I've got this crash, and I've traced it down to something related to an `iova` structure. Let's see... the `struct iova` definition reveals: `node` at offset zero, taking up 24 bytes, then `pfn_hi` at 24 bytes, and finally, `pfn_lo` at offset 32 bytes β that's 8 bytes. Aha! The buggy address is right at `pfn_lo`. The code's at line 285 in `private_find_iova`, and the condition is `if (pfn < iova->pfn_lo)`. Bingo! It fits perfectly.
Now, let's look at the stack trace I have. The access stack I see goes: `private_find_iova`, then `free_iova`, which calls into `fq_ring_free_locked` in `dma-iommu.c`, which seems to be calling the inlined `fq_ring_free`, and finally `fq_flush_timeout`.
Alright, I'll go read `dma-iommu.c` at the relevant lines β around lines 155, 170, and 194. Looks like I'm on the right track; this should lead to the root cause.
|
| 12/2 |
2026/08/09 23:26 |
tool |
read-file |
0m
Args: map[File:drivers/iommu/dma-iommu.c FirstLine:140 LineCount:60]
Results: map[Contents: 140: }
141:
142: static void fq_ring_free_locked(struct iommu_dma_cookie *cookie, struct iova_fq *fq)
143: {
144: u64 counter = atomic64_read(&cookie->fq_flush_finish_cnt);
145: unsigned int idx;
146:
147: assert_spin_locked(&fq->lock);
148:
149: fq_ring_for_each(idx, fq) {
150:
151: if (fq->entries[idx].counter >= counter)
152: break;
153:
154: iommu_put_pages_list(&fq->entries[idx].freelist);
155: free_iova_fast(&cookie->iovad,
156: fq->entries[idx].iova_pfn,
157: fq->entries[idx].pages);
158:
159: fq->entries[idx].freelist =
160: IOMMU_PAGES_LIST_INIT(fq->entries[idx].freelist);
161: fq->head = (fq->head + 1) & fq->mod_mask;
162: }
163: }
164:
165: static void fq_ring_free(struct iommu_dma_cookie *cookie, struct iova_fq *fq)
166: {
167: unsigned long flags;
168:
169: spin_lock_irqsave(&fq->lock, flags);
170: fq_ring_free_locked(cookie, fq);
171: spin_unlock_irqrestore(&fq->lock, flags);
172: }
173:
174: static void fq_flush_iotlb(struct iommu_dma_cookie *cookie)
175: {
176: atomic64_inc(&cookie->fq_flush_start_cnt);
177: cookie->fq_domain->ops->flush_iotlb_all(cookie->fq_domain);
178: atomic64_inc(&cookie->fq_flush_finish_cnt);
179: }
180:
181: static void fq_flush_timeout(struct timer_list *t)
182: {
183: struct iommu_dma_cookie *cookie = timer_container_of(cookie, t,
184: fq_timer);
185: int cpu;
186:
187: atomic_set(&cookie->fq_timer_on, 0);
188: fq_flush_iotlb(cookie);
189:
190: if (cookie->options.qt == IOMMU_DMA_OPTS_SINGLE_QUEUE) {
191: fq_ring_free(cookie, cookie->single_fq);
192: } else {
193: for_each_possible_cpu(cpu)
194: fq_ring_free(cookie, per_cpu_ptr(cookie->percpu_fq, cpu));
195: }
196: }
197:
198: static void queue_iova(struct iommu_dma_cookie *cookie,
199: unsigned long pfn, unsigned long pages,
]
|
| 13/2 |
2026/08/09 23:26 |
llm |
expert |
0m
Model:
Tokens:
input: 17663
output: 44
thoughts: 182
Thoughts: **Debugging a Potential IOMMU Memory Leak**
Okay, so I'm looking at this stack trace and line 155 of `dma-iommu.c` specifically, which is `fq_ring_free_locked`. That line's calling `free_iova_fast`. But the stack trace points to `free_iova`. That's a little confusing. `free_iova_fast` should be a more optimized, quicker version of freeing the iova so I'm wondering why `fq_ring_free_locked` is using the slower `free_iova`? My gut tells me something isn't right. I better check the definition of `free_iova_fast` to make sure I'm not missing something obvious. This needs a closer look, let's dive into it.
|
| 14/2 |
2026/08/09 23:26 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/iommu/dma-iommu.c Name:free_iova_fast]
Results: map[SourceCode: 416: /**
417: * free_iova_fast - free iova pfn range into rcache
418: * @iovad: - iova domain in question.
419: * @pfn: - pfn that is allocated previously
420: * @size: - # of pages in range
421: * This functions frees an iova range by trying to put it into the rcache,
422: * falling back to regular iova deallocation via free_iova() if this fails.
423: */
424: void
425: free_iova_fast(struct iova_domain *iovad, unsigned long pfn, unsigned long size)
426: {
427: if (iova_rcache_insert(iovad, pfn, size))
428: return;
429:
430: free_iova(iovad, pfn);
431: }
SourceFile:drivers/iommu/iova.c]
|
| 15/2 |
2026/08/09 23:26 |
llm |
expert |
0m
Model:
Tokens:
input: 18363
output: 38
thoughts: 392
Thoughts: **Analyzing a Potential Use-After-Free in the IOVA Subsystem**
Okay, so `free_iova_fast` seems to be the key function here. It calls `free_iova(iovad, pfn)` if `iova_rcache_insert` either fails or isn't even used. `free_iova`, in turn, is calling `private_find_iova(iovad, pfn)`. Right, and `private_find_iova` is going to be walking the rbtree, starting at `iovad->rbroot.rb_node`. But... something doesn't feel right. I need to figure out how this use-after-free is happening.
Time to consult the stack trace. Let's see... the free stack is:
```
kmem_cache_free+0x182/0x650 mm/slub.c:6504
free_iova_mem drivers/iommu/iova.c:237 [inline]
put_iova_domain+0xcc/0x100 drivers/iommu/iova.c:454
iommu_put_dma_cookie+0x45c/0x560 drivers/iommu/dma-iommu.c:431
iommu_domain_free drivers/iommu/iommu.c:2131 [inline]
iommu_deinit_device+0x401/0x840 drivers/iommu/iommu.c:600
__iommu_group_remove_device+0x1dd/0x270 drivers/iommu/iommu.c:764
```
Aha! `put_iova_domain` is in the free stack. Looks like we've got a likely suspect. Let me dive into that `put_iova_domain` function in `drivers/iommu/iova.c` and see what it's doing.
|
| 16/2 |
2026/08/09 23:27 |
tool |
read-file |
0m
Args: map[File:drivers/iommu/iova.c FirstLine:430 LineCount:35]
Results: map[Contents: 430: free_iova(iovad, pfn);
431: }
432: EXPORT_SYMBOL_GPL(free_iova_fast);
433:
434: static void iova_domain_free_rcaches(struct iova_domain *iovad)
435: {
436: cpuhp_state_remove_instance_nocalls(CPUHP_IOMMU_IOVA_DEAD,
437: &iovad->cpuhp_dead);
438: free_iova_rcaches(iovad);
439: }
440:
441: /**
442: * put_iova_domain - destroys the iova domain
443: * @iovad: - iova domain in question.
444: * All the iova's in that domain are destroyed.
445: */
446: void put_iova_domain(struct iova_domain *iovad)
447: {
448: struct iova *iova, *tmp;
449:
450: if (iovad->rcaches)
451: iova_domain_free_rcaches(iovad);
452:
453: rbtree_postorder_for_each_entry_safe(iova, tmp, &iovad->rbroot, node)
454: free_iova_mem(iova);
455: }
456: EXPORT_SYMBOL_GPL(put_iova_domain);
457:
458: static int
459: __is_range_overlap(struct rb_node *node,
460: unsigned long pfn_lo, unsigned long pfn_hi)
461: {
462: struct iova *iova = to_iova(node);
463:
464: if ((pfn_lo <= iova->pfn_hi) && (pfn_hi >= iova->pfn_lo))
]
|
| 17/2 |
2026/08/09 23:27 |
llm |
expert |
0m
Model:
Tokens:
input: 19828
output: 48
thoughts: 189
Thoughts: **Analyzing the `put_iova_domain` Function in Detail**
Okay, so `put_iova_domain` is the function that's responsible for tearing down an IOVA domain. Right, I remember seeing that. It looks like it *destroys* the iova domain, essentially freeing all the associated IOVAs. I'm focusing on that block of code there:
```c
rbtree_postorder_for_each_entry_safe(iova, tmp, &iovad->rbroot, node)
free_iova_mem(iova);
```
That loop goes through the red-black tree, freeing each `iova` memory segment. Good. But hold on... It just *frees* the IOVAs. It doesn't seem to explicitly zero out `iovad->rbroot.rb_node` or anything like that. Does the red-black tree itself get cleared? No, `rbtree_postorder_for_each_entry_safe` only iterates and releases the entries. The entire `iovad` structure, or the IOVAs within it, are, in effect, freed *after* `put_iova_domain` is called. That's the critical point here.
I need to confirm what's happening to the IOVA Domain after the free. Let me see where `iommu_put_dma_cookie` is. That's in `drivers/iommu/dma-iommu.c`. Right, let me check there, I need to see how the domain is put away.
|
| 18/2 |
2026/08/09 23:27 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/iommu/dma-iommu.c Name:iommu_put_dma_cookie]
Results: map[SourceCode: 420: /**
421: * iommu_put_dma_cookie - Release a domain's DMA mapping resources
422: * @domain: IOMMU domain previously prepared by iommu_get_dma_cookie()
423: */
424: void iommu_put_dma_cookie(struct iommu_domain *domain)
425: {
426: struct iommu_dma_cookie *cookie = domain->iova_cookie;
427: struct iommu_dma_msi_page *msi, *tmp;
428:
429: if (cookie->iovad.granule) {
430: iommu_dma_free_fq(cookie);
431: put_iova_domain(&cookie->iovad);
432: }
433: list_for_each_entry_safe(msi, tmp, &cookie->msi_page_list, list)
434: kfree(msi);
435: kfree(cookie);
436: }
SourceFile:drivers/iommu/dma-iommu.c]
|
| 19/2 |
2026/08/09 23:27 |
llm |
expert |
0m
Model:
Tokens:
input: 20745
output: 48
thoughts: 64
Thoughts: Here's my summary, as though I'm thinking through this process:
**Diving Deep into DMA Cookie Deallocation**
Okay, hold on a minute. I'm following the deallocation path for this DMA cookie and something just jumped out at me. The function `iommu_put_dma_cookie` calls `iommu_dma_free_fq(cookie)` and *then* it calls `put_iova_domain(&cookie->iovad)`. Interesting...that sequence is key. It implies a specific order of operations in releasing resources associated with this cookie. So, `iommu_dma_free_fq` is obviously where the initial cleanup begins. Let's dig into *that* function first; understanding its workings is crucial to grasping the whole deallocation sequence.
|
| 20/2 |
2026/08/09 23:27 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/iommu/dma-iommu.c Name:iommu_dma_free_fq]
Results: map[SourceCode: 274: static void iommu_dma_free_fq(struct iommu_dma_cookie *cookie)
275: {
276: if (!cookie->fq_domain)
277: return;
278:
279: timer_delete_sync(&cookie->fq_timer);
280: if (cookie->options.qt == IOMMU_DMA_OPTS_SINGLE_QUEUE)
281: iommu_dma_free_fq_single(cookie->single_fq);
282: else
283: iommu_dma_free_fq_percpu(cookie->percpu_fq);
284: }
SourceFile:drivers/iommu/dma-iommu.c]
|
| 21/2 |
2026/08/09 23:27 |
llm |
expert |
0m
Model:
Tokens:
input: 21257
output: 37
thoughts: 2031
Thoughts: **A Deep Dive into a Kernel Conundrum**
Okay, first things first, `timer_delete_sync(&cookie->fq_timer)` is being called within `iommu_dma_free_fq`. But wait, the access stack reveals something interesting, let's look at the calls: `private_find_iova`, `free_iova`, `fq_ring_free_locked`, `fq_ring_free`, `fq_flush_timeout`, and finally `call_timer_fn`. Now, how could `fq_flush_timeout` even *run* if `timer_delete_sync` was called? This doesn't make sense! Could `timer_delete_sync` be called before `put_iova_domain`? Ah, yes, `iommu_dma_free_fq` is called before `put_iova_domain`. The timer is running on CPU 0 in a softirq (`run_timer_softirq`).
Okay, let's examine the access stack again. Here's what I see: `<IRQ>` leads to `private_find_iova`, `free_iova`, `fq_ring_free_locked`, `fq_ring_free`, `fq_flush_timeout`, `call_timer_fn` - and then the `<TASK>` stack: `__debug_check_no_obj_freed`, `debug_check_no_obj_freed`, `slab_free_hook`, `slab_free`, `kfree`, `iommu_domain_free`, and finally `iommu_deinit_device`. Whoa! This is a beautiful mess.
Hold on, this is amazing! The `<TASK>` stack is executing: `iommu_domain_free` -> `iommu_put_dma_cookie` -> `put_iova_domain` -> `free_iova_mem` -> `kfree` -> `slab_free_hook` -> `debug_check_no_obj_freed`. Wait, `debug_check_no_obj_freed` is checking for freed objects, and it detects a problem! It prints a warning β it triggers a `printk`. Let's trace it back: `__debug_check_no_obj_freed`, `debug_check_no_obj_freed`, `slab_free_hook`, `slab_free`, `kfree`, and finally `iommu_domain_free`. This is a bug! `debug_check_no_obj_freed` detected that some active object is being freed. Then, the calls: `debug_print_object`, `report_bug`, `vprintk_emit`, `console_unlock`, `console_flush_all`, `console_flush_one_record`.
Wait a minute, `console_flush_all` could enable interrupts, and an APIC timer interrupt might occur during this time! Yes, `sysvec_apic_timer_interrupt` happens during `console_flush_all`! This is within the `printk` path! This triggers a softirq: `run_timer_softirq` -> `fq_flush_timeout`. And `fq_flush_timeout` accesses `cookie->iovad` and attempts to `free_iova_fast`! But, *`kfree`* was in progress! This is a race condition.
What was being freed by the `<TASK>`? Let's trace the Free stack! It says: `kmem_cache_free`, `free_iova_mem`, `put_iova_domain`, `iommu_put_dma_cookie`, and `iommu_domain_free`. The object being freed is `ffff888000274d40`, an `iova` struct! `put_iova_domain` *frees* all the `iova` structs. So the `iova` struct `ffff888000274d40` was *already* freed by `put_iova_domain`. This is the crux.
Let's break down the sequence: Task 5327 calls `iommu_domain_free`, which calls `iommu_put_dma_cookie`, which calls `iommu_dma_free_fq(cookie)`. `iommu_dma_free_fq` calls `timer_delete_sync(&cookie->fq_timer)`. If `timer_delete_sync` was called, how did the timer *still* run? Okay, `timer_delete_sync` deletes the timer, but what if it was running or about to run. Did `timer_
|
| 22/2 |
2026/08/09 23:27 |
tool |
grepper |
0m
Args: map[Expression:fq_domain PathPrefix:drivers/iommu/dma-iommu.c]
Results: map[Output:drivers/iommu/dma-iommu.c=58=struct iommu_dma_cookie {
--
drivers/iommu/dma-iommu.c-74- /* Domain for flush queue callback; NULL if flush queue not in use */
drivers/iommu/dma-iommu.c:75: struct iommu_domain *fq_domain;
drivers/iommu/dma-iommu.c-76- /* Options for dma-iommu use */
--
drivers/iommu/dma-iommu.c=174=static void fq_flush_iotlb(struct iommu_dma_cookie *cookie)
--
drivers/iommu/dma-iommu.c-176- atomic64_inc(&cookie->fq_flush_start_cnt);
drivers/iommu/dma-iommu.c:177: cookie->fq_domain->ops->flush_iotlb_all(cookie->fq_domain);
drivers/iommu/dma-iommu.c-178- atomic64_inc(&cookie->fq_flush_finish_cnt);
--
drivers/iommu/dma-iommu.c=274=static void iommu_dma_free_fq(struct iommu_dma_cookie *cookie)
drivers/iommu/dma-iommu.c-275-{
drivers/iommu/dma-iommu.c:276: if (!cookie->fq_domain)
drivers/iommu/dma-iommu.c-277- return;
--
drivers/iommu/dma-iommu.c=333=int iommu_dma_init_fq(struct iommu_domain *domain)
--
drivers/iommu/dma-iommu.c-337-
drivers/iommu/dma-iommu.c:338: if (cookie->fq_domain)
drivers/iommu/dma-iommu.c-339- return 0;
--
drivers/iommu/dma-iommu.c-360- smp_wmb();
drivers/iommu/dma-iommu.c:361: WRITE_ONCE(cookie->fq_domain, domain);
drivers/iommu/dma-iommu.c-362- return 0;
--
drivers/iommu/dma-iommu.c=823=static void __iommu_dma_unmap(struct device *dev, dma_addr_t dma_addr,
--
drivers/iommu/dma-iommu.c-835- iommu_iotlb_gather_init(&iotlb_gather);
drivers/iommu/dma-iommu.c:836: iotlb_gather.queued = READ_ONCE(cookie->fq_domain);
drivers/iommu/dma-iommu.c-837-
--
drivers/iommu/dma-iommu.c=2071=static void __iommu_dma_iova_unlink(struct device *dev,
--
drivers/iommu/dma-iommu.c-2089- iommu_iotlb_gather_init(&iotlb_gather);
drivers/iommu/dma-iommu.c:2090: iotlb_gather.queued = free_iova && READ_ONCE(cookie->fq_domain);
drivers/iommu/dma-iommu.c-2091-
]
|
| 23/2 |
2026/08/09 23:27 |
llm |
expert |
0m
Model:
Tokens:
input: 25276
output: 48
thoughts: 24
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Diving into iommu_dma_init_fq: Initialization Analysis**
Okay, so I need to understand the timer initialization within the IOMMU DMA framework. My immediate focus is on the `iommu_dma_init_fq` function. That's the logical place to start β I'm assuming that's where the critical setup happens. I'm going to carefully review the code in that function to pinpoint the exact steps involved in getting that timer running. I'll be looking for the specific registers accessed, the values being written, and how any clock sources or interrupts might be configured in relation to the timer. I expect to see the basic timer configuration, probably with an appropriate prescaler for the expected DMA operation frequencies. I need to understand its relationship to the IOMMU's overall performance monitoring capabilities and how it integrates with the interrupt handling. Hopefully, it's straightforward and well-documented. Otherwise, I might have to dig deeper into related functions.
|
| 24/2 |
2026/08/09 23:27 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/iommu/dma-iommu.c Name:iommu_dma_init_fq]
Results: map[SourceCode: 332: /* sysfs updates are serialised by the mutex of the group owning @domain */
333: int iommu_dma_init_fq(struct iommu_domain *domain)
334: {
335: struct iommu_dma_cookie *cookie = domain->iova_cookie;
336: int rc;
337:
338: if (cookie->fq_domain)
339: return 0;
340:
341: atomic64_set(&cookie->fq_flush_start_cnt, 0);
342: atomic64_set(&cookie->fq_flush_finish_cnt, 0);
343:
344: if (cookie->options.qt == IOMMU_DMA_OPTS_SINGLE_QUEUE)
345: rc = iommu_dma_init_fq_single(cookie);
346: else
347: rc = iommu_dma_init_fq_percpu(cookie);
348:
349: if (rc) {
350: pr_warn("iova flush queue initialization failed\n");
351: return -ENOMEM;
352: }
353:
354: timer_setup(&cookie->fq_timer, fq_flush_timeout, 0);
355: atomic_set(&cookie->fq_timer_on, 0);
356: /*
357: * Prevent incomplete fq state being observable. Pairs with path from
358: * __iommu_dma_unmap() through iommu_dma_free_iova() to queue_iova()
359: */
360: smp_wmb();
361: WRITE_ONCE(cookie->fq_domain, domain);
362: return 0;
363: }
SourceFile:drivers/iommu/dma-iommu.c]
|
| 25/2 |
2026/08/09 23:27 |
llm |
expert |
0m
Model:
Tokens:
input: 26132
output: 42
thoughts: 299
Thoughts: **Analyzing the Kernel Memory Freeing**
Okay, hold on a second. Let me re-examine this `<TASK>` stack trace. It looks like we have a `__debug_check_no_obj_freed` call failing within `lib/debugobjects.c`, which is concerning. The trace then leads me to `slab_free_hook`, then `slab_free`, and finally `kfree`. Aha! `kfree` is being called within `iommu_domain_free`, and that's where things are getting interesting.
So, I need to check out what's happening within `iommu_domain_free`. Time to dig into `drivers/iommu/iommu.c` and figure out what resources are being freed within that function and whether it's related to the original error with the objects being freed. I need to be sure that the IOMMU domain is correctly handling its allocated memory and associated devices. Something is not quite right here.
|
| 26/2 |
2026/08/09 23:27 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/iommu/iommu.c Name:iommu_domain_free]
Results: map[SourceCode:2127: void iommu_domain_free(struct iommu_domain *domain)
2128: {
2129: switch (domain->cookie_type) {
2130: case IOMMU_COOKIE_DMA_IOVA:
2131: iommu_put_dma_cookie(domain);
2132: break;
2133: case IOMMU_COOKIE_DMA_MSI:
2134: iommu_put_msi_cookie(domain);
2135: break;
2136: case IOMMU_COOKIE_SVA:
2137: mmdrop(domain->mm);
2138: break;
2139: default:
2140: break;
2141: }
2142: if (domain->ops->free)
2143: domain->ops->free(domain);
2144: }
SourceFile:drivers/iommu/iommu.c]
|
| 27/2 |
2026/08/09 23:27 |
llm |
expert |
0m
Model:
Tokens:
input: 26970
output: 36
thoughts: 910
Thoughts: **Debugging a Potential IOMMU Double-Free**
Okay, so `iommu_domain_free` calls `iommu_put_dma_cookie`. The `<TASK>` stack trace shows `kfree` being called directly from `iommu_domain_free`, specifically from `iommu_put_dma_cookie`. Let's look at the code: `iommu_domain_free` is inline, calling `iommu_put_dma_cookie` directly. Within that function, there it is: `kfree(cookie)` at the end. `cookie` isn't a debug object, it's allocated with `kzalloc`. So, why is `debug_check_no_obj_freed` triggering? That function is called during memory frees (like `kfree`) to detect if we're freeing something that still has active debug objects like timers or work items within it.
Aha! `cookie` contains `cookie->fq_timer`, which is a `struct timer_list`. And yes, timers *are* tracked by debugobjects. So, the `kfree(cookie)` call is triggering `debug_check_no_obj_freed` because the memory range of `cookie` *includes* `cookie->fq_timer`, and the timer is still active. That makes sense.
So, why would the timer still be active? Let's check `iommu_dma_free_fq`. We have `timer_delete_sync(&cookie->fq_timer)`, but there's a guard: `if (!cookie->fq_domain) return;`. If `cookie->fq_domain` is NULL, the function returns *without* deleting the timer. Why would `cookie->fq_domain` be NULL? That's set in `iommu_dma_init_fq`, which looks like this: `WRITE_ONCE(cookie->fq_domain, domain);`. Is this function always called? We should look for all the places `iommu_dma_init_fq` is called.
|
| 28/2 |
2026/08/09 23:27 |
tool |
grepper |
0m
Args: map[Expression:iommu_dma_init_fq PathPrefix:drivers/iommu/]
Results: map[Output:drivers/iommu/dma-iommu.c=198=static void queue_iova(struct iommu_dma_cookie *cookie,
--
drivers/iommu/dma-iommu.c-209- * from a different CPU before we release the lock below. Full barrier
drivers/iommu/dma-iommu.c:210: * so it also pairs with iommu_dma_init_fq() to avoid seeing partially
drivers/iommu/dma-iommu.c-211- * written fq state here.
--
drivers/iommu/dma-iommu.c=286=static void iommu_dma_init_one_fq(struct iova_fq *fq, size_t fq_size)
--
drivers/iommu/dma-iommu.c-300-
drivers/iommu/dma-iommu.c:301:static int iommu_dma_init_fq_single(struct iommu_dma_cookie *cookie)
drivers/iommu/dma-iommu.c-302-{
--
drivers/iommu/dma-iommu.c-314-
drivers/iommu/dma-iommu.c:315:static int iommu_dma_init_fq_percpu(struct iommu_dma_cookie *cookie)
drivers/iommu/dma-iommu.c-316-{
--
drivers/iommu/dma-iommu.c-332-/* sysfs updates are serialised by the mutex of the group owning @domain */
drivers/iommu/dma-iommu.c:333:int iommu_dma_init_fq(struct iommu_domain *domain)
drivers/iommu/dma-iommu.c-334-{
--
drivers/iommu/dma-iommu.c-344- if (cookie->options.qt == IOMMU_DMA_OPTS_SINGLE_QUEUE)
drivers/iommu/dma-iommu.c:345: rc = iommu_dma_init_fq_single(cookie);
drivers/iommu/dma-iommu.c-346- else
drivers/iommu/dma-iommu.c:347: rc = iommu_dma_init_fq_percpu(cookie);
drivers/iommu/dma-iommu.c-348-
--
drivers/iommu/dma-iommu.c=670=static int iommu_dma_init_domain(struct iommu_domain *domain, struct device *dev)
--
drivers/iommu/dma-iommu.c-719- (!iommu_domain_supports_fq(dev, domain) ||
drivers/iommu/dma-iommu.c:720: iommu_dma_init_fq(domain)))
drivers/iommu/dma-iommu.c-721- domain->type = IOMMU_DOMAIN_DMA;
--
drivers/iommu/dma-iommu.h=16=void iommu_put_msi_cookie(struct iommu_domain *domain);
drivers/iommu/dma-iommu.h-17-
drivers/iommu/dma-iommu.h:18:int iommu_dma_init_fq(struct iommu_domain *domain);
drivers/iommu/dma-iommu.h-19-
--
drivers/iommu/dma-iommu.h=29=static inline void iommu_setup_dma_ops(struct device *dev,
--
drivers/iommu/dma-iommu.h-33-
drivers/iommu/dma-iommu.h:34:static inline int iommu_dma_init_fq(struct iommu_domain *domain)
drivers/iommu/dma-iommu.h-35-{
--
drivers/iommu/iommu.c=3253=static ssize_t iommu_group_store_type(struct iommu_group *group,
--
drivers/iommu/iommu.c-3279- group->default_domain->type == IOMMU_DOMAIN_DMA) {
drivers/iommu/iommu.c:3280: ret = iommu_dma_init_fq(group->default_domain);
drivers/iommu/iommu.c-3281- if (ret)
]
|
| 29/2 |
2026/08/09 23:27 |
llm |
expert |
0m
Model:
Tokens:
input: 29569
output: 42
thoughts: 1066
Thoughts: **Analyzing a Potential IOMMU Timer Issue**
Okay, let's break this down. First, I see an allocation stack showing an `iova` structure being allocated via `kmem_cache_alloc_noprof` and specifically through `alloc_iova_mem` called from `reserve_iova` during `iommu_setup_dma_ops`. That's the setup. Then, looking at the free stack, I can see the corresponding `iova` is freed through `free_iova_mem` within the function `iommu_domain_free`, called by `iommu_put_dma_cookie`. Ah, right, this occurs when a PCI device is being removed as per the `pci_stop_and_remove_bus_device_locked` trace. The removal process calls `iommu_domain_free`, leading to the `iova` structure being freed as the domain is cleaned up.
Now, where does the timer come in? I see a timer called `fq_timer`, which is most likely the flush queue timer. I need to check if `cookie->fq_domain` is non-NULL. If it is, then `iommu_dma_free_fq` calls `timer_delete_sync` (which is `del_timer_sync`) on `cookie->fq_timer`. That function, as I know, is supposed to delete the timer synchronously. It waits for the timer handler to complete if it's running on another CPU. I know that `del_timer_sync` cannot be called from an interrupt context, and here it is called from a task context (the sysfs write path).
So the question is, can there be a race? If the timer is running on another CPU, `del_timer_sync` will wait. If the timer is not running, it will simply delete it. What happens if the timer is *not* running yet but is *pending*? My understanding is that `timer_delete_sync` should delete it in this case as well.
The key question is: can a timer get queued *after* `timer_delete_sync`? I need to check where `fq_timer` is queued. My immediate thought is, let's go check the code, specifically where the timer is queued: `drivers/iommu/dma-iommu.c` and specifically look at the `queue_iova` function. I will check for any races.
|
| 30/2 |
2026/08/09 23:27 |
tool |
read-file |
0m
Args: map[File:drivers/iommu/dma-iommu.c FirstLine:198 LineCount:50]
Results: map[Contents: 198: static void queue_iova(struct iommu_dma_cookie *cookie,
199: unsigned long pfn, unsigned long pages,
200: struct iommu_pages_list *freelist)
201: {
202: struct iova_fq *fq;
203: unsigned long flags;
204: unsigned int idx;
205:
206: /*
207: * Order against the IOMMU driver's pagetable update from unmapping
208: * @pte, to guarantee that fq_flush_iotlb() observes that if called
209: * from a different CPU before we release the lock below. Full barrier
210: * so it also pairs with iommu_dma_init_fq() to avoid seeing partially
211: * written fq state here.
212: */
213: smp_mb();
214:
215: if (cookie->options.qt == IOMMU_DMA_OPTS_SINGLE_QUEUE)
216: fq = cookie->single_fq;
217: else
218: fq = raw_cpu_ptr(cookie->percpu_fq);
219:
220: spin_lock_irqsave(&fq->lock, flags);
221:
222: /*
223: * First remove all entries from the flush queue that have already been
224: * flushed out on another CPU. This makes the fq_full() check below less
225: * likely to be true.
226: */
227: fq_ring_free_locked(cookie, fq);
228:
229: if (fq_full(fq)) {
230: fq_flush_iotlb(cookie);
231: fq_ring_free_locked(cookie, fq);
232: }
233:
234: idx = fq_ring_add(fq);
235:
236: fq->entries[idx].iova_pfn = pfn;
237: fq->entries[idx].pages = pages;
238: fq->entries[idx].counter = atomic64_read(&cookie->fq_flush_start_cnt);
239: iommu_pages_list_splice(freelist, &fq->entries[idx].freelist);
240:
241: spin_unlock_irqrestore(&fq->lock, flags);
242:
243: /* Avoid false sharing as much as possible. */
244: if (!atomic_read(&cookie->fq_timer_on) &&
245: !atomic_xchg(&cookie->fq_timer_on, 1))
246: mod_timer(&cookie->fq_timer,
247: jiffies + msecs_to_jiffies(cookie->options.fq_timeout));
]
|
| 31/2 |
2026/08/09 23:27 |
llm |
expert |
0m
Model:
Tokens:
input: 32034
output: 38
thoughts: 53
Thoughts: **Investigating the IOVA Allocation Timing**
Okay, hold on a second! I need to clarify something crucial here. Is `queue_iova` being invoked *before* or *after* `iommu_dma_free_fq` executes? The sequencing of these two operations has significant implications for memory management and potential race conditions in the IOMMU context. I need to nail down the precise order of these calls to understand the flow.
And a related question is, who is even *calling* `queue_iova` in the first place? I need to know the origin of the function call in order to have a complete picture of the dependencies at play. I can't really reason about the implications of these calls without that context.
Alright, time to get to work. Let's fire up the search tools and hunt down all instances where `queue_iova` is being called within the relevant codebase. That should give me the information I need to understand the function call stack and solve this conundrum.
|
| 32/2 |
2026/08/09 23:27 |
tool |
grepper |
0m
Args: map[Expression:queue_iova PathPrefix:drivers/iommu/dma-iommu.c]
Results: map[Output:drivers/iommu/dma-iommu.c=181=static void fq_flush_timeout(struct timer_list *t)
--
drivers/iommu/dma-iommu.c-197-
drivers/iommu/dma-iommu.c:198:static void queue_iova(struct iommu_dma_cookie *cookie,
drivers/iommu/dma-iommu.c-199- unsigned long pfn, unsigned long pages,
--
drivers/iommu/dma-iommu.c=333=int iommu_dma_init_fq(struct iommu_domain *domain)
--
drivers/iommu/dma-iommu.c-357- * Prevent incomplete fq state being observable. Pairs with path from
drivers/iommu/dma-iommu.c:358: * __iommu_dma_unmap() through iommu_dma_free_iova() to queue_iova()
drivers/iommu/dma-iommu.c-359- */
--
drivers/iommu/dma-iommu.c=806=static void iommu_dma_free_iova(struct iommu_domain *domain, dma_addr_t iova,
--
drivers/iommu/dma-iommu.c-814- else if (gather && gather->queued)
drivers/iommu/dma-iommu.c:815: queue_iova(domain->iova_cookie, iova_pfn(iovad, iova),
drivers/iommu/dma-iommu.c-816- size >> iova_shift(iovad),
]
|
| 33/2 |
2026/08/09 23:27 |
llm |
expert |
0m
Model:
Tokens:
input: 32793
output: 48
thoughts: 784
Thoughts: **Debugging a Potential IOMMU DMA Cookie Free Issue**
Okay, so `iommu_dma_free_iova` calls `queue_iova`. I need to figure out if there's any concurrent unmapping or freeing going on with the IOVA. Let's look at the stack trace again. Wait, is the timer actually running *before* `timer_delete_sync`? Let me trace the exact sequence of events. Hang on, I need to check the `<TASK>` stack trace again: It looks like `kfree` is being called on the `cookie`. That's suspicious.
Looking at `iommu_put_dma_cookie`, I see that it does indeed `kfree(cookie)`. First, it does:
`iommu_dma_free_fq(cookie)`
Then, it does:
```c
list_for_each_entry_safe(msi, tmp, &cookie->msi_page_list, list)
kfree(msi);
kfree(cookie);
```
Okay, so `iommu_dma_free_fq(cookie)` *was* called. Diving into that function, the code is:
```c
static void iommu_dma_free_fq(struct iommu_dma_cookie *cookie)
{
if (!cookie->fq_domain)
return;
timer_delete_sync(&cookie->fq_timer);
if (cookie->options.qt == IOMMU_DMA_OPTS_SINGLE_QUEUE)
iommu_dma_free_fq_single(cookie->single_fq);
else
iommu_dma_free_fq_percpu(cookie->percpu_fq);
}
```
Wait a minute, is `cookie->fq_domain` NULL? If it's NULL, then `timer_delete_sync` isn't called! That's it. Why would `cookie->fq_domain` be NULL? If it's NULL, that means the flush queue was never initialized, or `iommu_dma_init_fq` never was called, or it failed. But if the flush queue *was* never initialized, then the timer `cookie->fq_timer` wouldn't be initialized either, right? Wait, `cookie` is allocated using `kzalloc` in `iommu_get_dma_cookie`. Let me check `iommu_get_dma_cookie`!
|
| 34/2 |
2026/08/09 23:28 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/iommu/dma-iommu.c Name:iommu_get_dma_cookie]
Results: map[SourceCode: 365: /**
366: * iommu_get_dma_cookie - Acquire DMA-API resources for a domain
367: * @domain: IOMMU domain to prepare for DMA-API usage
368: */
369: int iommu_get_dma_cookie(struct iommu_domain *domain)
370: {
371: struct iommu_dma_cookie *cookie;
372:
373: if (domain->cookie_type != IOMMU_COOKIE_NONE)
374: return -EEXIST;
375:
376: cookie = kzalloc_obj(*cookie);
377: if (!cookie)
378: return -ENOMEM;
379:
380: INIT_LIST_HEAD(&cookie->msi_page_list);
381: domain->cookie_type = IOMMU_COOKIE_DMA_IOVA;
382: domain->iova_cookie = cookie;
383: return 0;
384: }
SourceFile:drivers/iommu/dma-iommu.c]
|
| 35/2 |
2026/08/09 23:28 |
llm |
expert |
0m
Model:
Tokens:
input: 34517
output: 45
thoughts: 4912
Thoughts: **Analyzing a Double-Free in the IOMMU DMA Code**
Okay, so I'm looking at this crash related to freeing an `iommu_dma_cookie`. The problem seems to be triggered by `debug_check_no_obj_freed`, which indicates that an object is still active when it's being freed. The stack trace points to `kfree(cookie)` in `iommu_put_dma_cookie`. The initial thought process was: if the timer `cookie->fq_timer` wasn't initialized, we wouldn't expect a debug error when `cookie` is freed. However, if it *was* initialized and `iommu_dma_init_fq` was called, `timer_delete_sync` should have been called, removing the timer. But the `debug_check_no_obj_freed` is complaining, meaning the timer is still active.
Tracing through the code, I see that the `iova` structure is freed, which internally calls `kmem_cache_free`. `kmem_cache_free` *can* trigger `debug_check_no_obj_freed` via the `slab_free_hook`. But the `iova` struct doesn't contain any debug objects, so that's not the root cause. This led me to focus on the `cookie` itself and its member `fq_timer`. The key function is `iommu_put_dma_cookie`, which calls `iommu_dma_free_fq`, followed by `kfree(cookie)`. `iommu_dma_free_fq` calls `timer_delete_sync` to stop the timer.
The puzzling part is this: The crash happens during `kfree(cookie)`, so `iommu_dma_free_fq` *should* have already completed, thus the timer *should* be gone. The stack trace indicates that the interrupt/softirq handler for the timer is active on the *same* CPU as the task. So, how could the timer still be running after `timer_delete_sync`? The timer's interrupt must have happened before or during `kfree(cookie)`.
The only possibility I could identify is the timer being re-queued. Then, I realized that the softirq for the timer could re-queue the timer! But I don't see any other writes to `cookie->fq_domain`, which would require the re-queueing to work. The task should have blocked the timer, right?
The key here seems to be a race condition. It's likely that a timer is started, but then the domain is freed before the timer can finish. The critical point here is that the timer interrupt can preempt the task context (even though it's on the same CPU), and the interrupt/softirq handler might be executing while the cookie is being freed, triggering the debug error. The call to `kfree(cookie)` *should* have been protected by `timer_delete_sync` but appears not to have been. The problem is thus that we did *not* successfully stop and delete the timer before freeing the `cookie` or freeing the underlying iova structures, which could be re-queuing the timer.
Therefore, it looks like `timer_delete_sync` *is* doing its job, but there's a race: The timer's handler (softirq) can execute *concurrently* after `timer_delete_sync` is called and before the `cookie` is entirely freed, thus leading to the debug error. The fix must be in synchronization between `timer_delete_sync` and anything that can interact with the timer, possibly by disabling interrupts or using a spinlock.
|
| 36/2 |
2026/08/09 23:28 |
tool |
git-log |
0m
Args: map[CodeRegexp:fq_timer Count:10 PathPrefix:drivers/iommu/dma-iommu.c]
Results: map[Output:41cb08555c41 treewide, timers: Rename from_timer() to timer_container_of()
8fa7292fee5c treewide: Switch/rename to timer_delete[_sync]()
6aa63a4ec947 iommu: Sort out domain user data
32d5bc8b09c7 iommu/dma: Allow a single FQ in addition to per-CPU FQs
a17e3026bc4d iommu: Move flush queue data into iommu_dma_cookie
f7f07484542f iommu/iova: Move flush queue code to iommu-dma
]
|
| 37/2 |
2026/08/09 23:28 |
llm |
expert |
0m
Model:
Tokens:
input: 40462
output: 44
thoughts: 83
Thoughts: Okay, let's dive into this code.
**Investigating Potential IOMMU Queue Flushing Issues**
My initial instinct is to zero in on a specific set of commits. I should start by looking at `f7f07484542f`, `a17e3026bc4d`, and `32d5bc8b09c7` to see what changes were made in the recent past that might be relevant. But, before I get too deep into those, a more strategic approach is often better. Given the nature of IOMMU operations, particularly with the potential for queuing issues, let me widen the net a bit. I'll search the `drivers/iommu/dma-iommu.c` file for any commits that mention "flush queue" or "timer." These keywords are often key indicators of concurrency or synchronization issues in DMA transactions which I need to understand thoroughly. This broader search might uncover related code, not just these specific commits that directly address a flushing mechanism and might highlight the history surrounding the timer-based flushing behavior in this particular driver and potentially show the context behind these commits.
|
| 38/2 |
2026/08/09 23:28 |
tool |
git-log |
0m
Args: map[Count:10 MessageRegexps:[flush queue] PathPrefix:drivers/iommu/dma-iommu.c]
Results: map[Output:9f5b681e2a3f iommu/dma: Use a large flush queue and timeout for shadow_on_flush
32d5bc8b09c7 iommu/dma: Allow a single FQ in addition to per-CPU FQs
a4fdd9762272 iommu: Use flush queue capability
a17e3026bc4d iommu: Move flush queue data into iommu_dma_cookie
f7f07484542f iommu/iova: Move flush queue code to iommu-dma
649ad9835a37 iommu/iova: Squash flush_cb abstraction
d5c383f2c98a iommu/iova: Squash entry_dtor abstraction
452e69b58c28 iommu: Allow enabling non-strict mode dynamically
c208916fe6c7 iommu: Express DMA strictness via the domain type
7a7c5badf858 iommu: Indicate queued flushes via gather data
]
|
| 39/2 |
2026/08/09 23:28 |
llm |
expert |
0m
Model:
Tokens:
input: 41110
output: 50
thoughts: 1658
Thoughts: **Analyzing a Potential IOMMU DMA Cookie Issue**
Okay, let's dive into this `iommu_dma_free_fq` situation. First, I need to check its history or blame to see if there's an obvious problem. Right, let's look directly at the code in `drivers/iommu/dma-iommu.c`. Hmm, I'm going to double-check `iommu_put_dma_cookie`.
Okay, here's what it does. Wait, could `cookie->iovad.granule` be zero? If it is zero, then `iommu_dma_free_fq(cookie)` doesn't get called. And `put_iova_domain` also wouldn't be called. But hold on, the free stack shows `put_iova_domain` *was* called! Therefore, `cookie->iovad.granule` was definitely not zero, and `iommu_dma_free_fq(cookie)` *was* called.
So why is the timer still active then? Let's go back and examine `iommu_dma_free_fq`. Wait, is it possible that `cookie->fq_domain` is NULL? If `cookie->fq_domain` is NULL, the function returns early. But how can `cookie->fq_domain` be NULL if the timer was active? Wait, could the timer have been initialized and activated even with `cookie->fq_domain` being null? Let's find where `cookie->fq_timer` is initialized. Ah, it's in `iommu_dma_init_fq`.
Here's `iommu_dma_init_fq` in action. If it fails, it returns `-ENOMEM` before setting `cookie->fq_domain`. Wait, what if `cookie->fq_domain` *is* set, but then some other code sets it to NULL? I searched for all writes to `fq_domain`, and there is only one: `WRITE_ONCE(cookie->fq_domain, domain)`. Therefore, that's not it.
So `cookie->fq_domain` isn't NULL, and `timer_delete_sync(&cookie->fq_timer)` *is* called. But then why is the timer still active when `kfree(cookie)` is called? Could `timer_delete_sync` be called, but the timer re-queued *after* `timer_delete_sync`? Let me look at `queue_iova`.
Ah, here's `queue_iova`. Okay, look here, there's no lock or check to prevent queuing if the domain is being freed. Wait, there's absolutely no check in `queue_iova` to see if the domain is being freed or if `fq_domain` is being destroyed! But who would call `queue_iova` during `iommu_domain_free`? Does `iommu_domain_free` or `iommu_put_dma_cookie` do something that calls `queue_iova`? Let's check.
`iommu_put_dma_cookie` calls `iommu_dma_free_fq(cookie)`. Inside `iommu_dma_free_fq`. Let's now see what `iommu_dma_free_fq_single` or `iommu_dma_free_fq_percpu` does. I need to check these.
|
| 40/2 |
2026/08/09 23:28 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/iommu/dma-iommu.c Name:iommu_dma_free_fq_single]
Results: map[SourceCode: 250: static void iommu_dma_free_fq_single(struct iova_fq *fq)
251: {
252: int idx;
253:
254: fq_ring_for_each(idx, fq)
255: iommu_put_pages_list(&fq->entries[idx].freelist);
256: vfree(fq);
257: }
SourceFile:drivers/iommu/dma-iommu.c]
|
| 41/2 |
2026/08/09 23:28 |
llm |
expert |
0m
Model:
Tokens:
input: 43746
output: 44
thoughts: 605
Thoughts: **My Analysis of a Potential Race Condition in IOMMU DMA Code**
Okay, so I'm diving into this code. My initial thought was, "Right, `fq_flush_timeout` simply frees up pages and clears the queue." But wait a minute... Let's re-examine `fq_flush_timeout`.
I see that it calls `fq_ring_free` on either `cookie->single_fq` or `cookie->percpu_fq`, depending on the options. The core concern hits me: If `fq_flush_timeout` is running, but `iommu_dma_free_fq` *also* attempts to free `cookie->single_fq` or `cookie->percpu_fq` (maybe through `vfree` or `free_percpu`), that's going to lead to a use-after-free situation, which is a big problem.
Ah, but there's `timer_delete_sync`. The implication is that `timer_delete_sync` should synchronize, preventing a concurrent execution or execution after `iommu_dma_free_fq` is called. That makes sense. However, the critical question arises: is `timer_delete_sync` *always* called?
Let's look at `iommu_dma_free_fq`:
It looks like, `timer_delete_sync(&cookie->fq_timer);` is called.
The next question: does `cookie->fq_domain` have any bearing on all of this? is `cookie->fq_domain` NULL? And when is it set to NULL? Does that mean anything here? And more importantly: is there another place where we're freeing the FQ? We can't let any other race condition occur.
So, let me search for `iommu_dma_free_fq` within the relevant driver code. Let's see what is calling this function and make sure that we are not missing anything else.
|
| 42/2 |
2026/08/09 23:29 |
tool |
grepper |
0m
Args: map[Expression:iommu_dma_free_fq PathPrefix:drivers/iommu/dma-iommu.c]
Results: map[Output:drivers/iommu/dma-iommu.c=198=static void queue_iova(struct iommu_dma_cookie *cookie,
--
drivers/iommu/dma-iommu.c-249-
drivers/iommu/dma-iommu.c:250:static void iommu_dma_free_fq_single(struct iova_fq *fq)
drivers/iommu/dma-iommu.c-251-{
--
drivers/iommu/dma-iommu.c-258-
drivers/iommu/dma-iommu.c:259:static void iommu_dma_free_fq_percpu(struct iova_fq __percpu *percpu_fq)
drivers/iommu/dma-iommu.c-260-{
--
drivers/iommu/dma-iommu.c-273-
drivers/iommu/dma-iommu.c:274:static void iommu_dma_free_fq(struct iommu_dma_cookie *cookie)
drivers/iommu/dma-iommu.c-275-{
--
drivers/iommu/dma-iommu.c-280- if (cookie->options.qt == IOMMU_DMA_OPTS_SINGLE_QUEUE)
drivers/iommu/dma-iommu.c:281: iommu_dma_free_fq_single(cookie->single_fq);
drivers/iommu/dma-iommu.c-282- else
drivers/iommu/dma-iommu.c:283: iommu_dma_free_fq_percpu(cookie->percpu_fq);
drivers/iommu/dma-iommu.c-284-}
--
drivers/iommu/dma-iommu.c=424=void iommu_put_dma_cookie(struct iommu_domain *domain)
--
drivers/iommu/dma-iommu.c-429- if (cookie->iovad.granule) {
drivers/iommu/dma-iommu.c:430: iommu_dma_free_fq(cookie);
drivers/iommu/dma-iommu.c-431- put_iova_domain(&cookie->iovad);
]
|
| 43/2 |
2026/08/09 23:29 |
llm |
expert |
0m
Model:
Tokens:
input: 45447
output: 43
thoughts: 1266
Thoughts: **Debugging a DMA Domain Freeing Issue**
Okay, let's dissect this. Looking at the `<TASK>` stack trace, I see a `debug_check_no_obj_freed` call after `kfree` of the `cookie` structure. The key is that inside `kfree`, the `debug_check_no_obj_freed` function reveals that `cookie->fq_timer` is still active, which is unexpected. Why?
My initial thought is to check if `cookie->fq_domain` is NULL. If it is, `iommu_dma_free_fq` returns early, skipping the `timer_delete_sync` call. But if `cookie->fq_domain` *is* NULL, then how could the `cookie->fq_timer` *be* active? That's a contradiction.
Let's re-examine `iommu_dma_init_fq`. The critical thing is: after `timer_setup` has been called, what can cause `iommu_dma_init_fq` to fail? Ah, the percpu or single queue initialization can fail, returning an error *after* `timer_setup`! However, the code flow dictates that `timer_setup` is *always* called before the queue initialization checks. So, if those initialization functions fail, the timer *wouldn't* be set up, and `fq_domain` would remain NULL, which means there should be no active timer. So, that's not it.
Okay, let's assume `iommu_dma_init_fq` succeeded, so `cookie->fq_domain` got set. In this case, `iommu_dma_free_fq` is called. It correctly calls `timer_delete_sync(&cookie->fq_timer)`, so why is the timer still active? That's a good question.
Are there other places that might delete the timer? Nope. And `timer_delete_sync` (really `del_timer_sync`) *guarantees* the timer is inactive upon return, even if it wasn't running. So, did something re-queue it *after* `timer_delete_sync`?
Let's trace: `iommu_dma_free_fq` deletes the timer. Then, `iommu_put_dma_cookie` calls `put_iova_domain`. The critical point is, does anything *after* `timer_delete_sync` call `queue_iova`? Yes, `iommu_dma_free_iova` does, and it's called from `__iommu_dma_unmap` or `__iommu_dma_iova_unlink`. But, if the domain is being freed, shouldn't all active devices using this domain be accounted for? When a device is being removed, `iommu_deinit_device` is called. So, let's investigate the `iommu_deinit_device` call. That's the key.
|
| 44/2 |
2026/08/09 23:29 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/iommu/iommu.c Name:iommu_deinit_device]
Results: map[SourceCode: 549: static void iommu_deinit_device(struct device *dev)
550: {
551: struct iommu_group *group = dev->iommu_group;
552: const struct iommu_ops *ops = dev_iommu_ops(dev);
553:
554: lockdep_assert_held(&group->mutex);
555:
556: iommu_device_unlink(dev->iommu->iommu_dev, dev);
557:
558: /*
559: * release_device() must stop using any attached domain on the device.
560: * If there are still other devices in the group, they are not affected
561: * by this callback.
562: *
563: * If the iommu driver provides release_domain, the core code ensures
564: * that domain is attached prior to calling release_device. Drivers can
565: * use this to enforce a translation on the idle iommu. Typically, the
566: * global static blocked_domain is a good choice.
567: *
568: * Otherwise, the iommu driver must set the device to either an identity
569: * or a blocking translation in release_device() and stop using any
570: * domain pointer, as it is going to be freed.
571: *
572: * Regardless, if a delayed attach never occurred, then the release
573: * should still avoid touching any hardware configuration either.
574: */
575: if (!dev->iommu->attach_deferred && ops->release_domain) {
576: struct iommu_domain *release_domain = ops->release_domain;
577:
578: /*
579: * If the device requires direct mappings then it should not
580: * be parked on a BLOCKED domain during release as that would
581: * break the direct mappings.
582: */
583: if (dev->iommu->require_direct && ops->identity_domain &&
584: release_domain == ops->blocked_domain)
585: release_domain = ops->identity_domain;
586:
587: release_domain->ops->attach_dev(release_domain, dev,
588: group->domain);
589: }
590:
591: if (ops->release_device)
592: ops->release_device(dev);
593:
594: /*
595: * If this is the last driver to use the group then we must free the
596: * domains before we do the module_put().
597: */
598: if (list_empty(&group->devices)) {
599: if (group->default_domain) {
600: iommu_domain_free(group->default_domain);
601: group->default_domain = NULL;
602: }
603: if (group->blocking_domain) {
604: iommu_domain_free(group->blocking_domain);
605: group->blocking_domain = NULL;
606: }
607: group->domain = NULL;
608: }
609:
610: /* Caller must put iommu_group */
611: dev->iommu_group = NULL;
612: module_put(ops->owner);
613: dev_iommu_free(dev);
614: if (IS_ENABLED(CONFIG_IOMMU_DMA))
615: dev_clear_dma_iommu(dev);
616: }
SourceFile:drivers/iommu/iommu.c]
|
| 45/2 |
2026/08/09 23:29 |
llm |
expert |
0m
Model:
Tokens:
input: 48507
output: 29
thoughts: 6723
Thoughts: **The Conundrum of the FQ Timer**
Right, let's break this down. We've got a use-after-free, a timer, and some IOMMU shenanigans. The core issue seems to be a timer that's still running after the associated DMA cookie and domain are supposed to be gone. The key piece is the `fq_timer` associated with an `iommu_dma_cookie`. The trace is showing us the stack: we're removing a device, and during removal, we free the IOMMU domain. Part of this process involves freeing the DMA cookie, including deleting the `fq_timer`. We call `timer_delete_sync()`. This function should prevent a race condition but the bug report is still showing a problem.
Here's the chain of thought I've worked through:
* **Device Removal:** We're in `iommu_deinit_device` called from `/sys/bus/pci/devices/.../remove` - PCI device removal is the trigger, and during this process we free the IOMMU domain.
* **Domain Free:** During domain freeing, we are calling `iommu_dma_free_fq()`. This seems to be the critical function.
* **Timer Deletion:** Within `iommu_dma_free_fq()`, we call `timer_delete_sync(&cookie->fq_timer)`. This is intended to stop the timer. The `timer_delete_sync` is supposed to prevent it, but somehow it's still "active".
* **Cookie Free:** After `timer_delete_sync` (supposedly completing), we free the DMA cookie via `kfree(cookie)`. This cookie contains the timer.
* **Use-After-Free:** After the cookie is freed, we get a KASAN use-after-free on `free_iova`. The stack trace reveals that the timer is still somehow active *after* the cookie containing the timer has been freed. The timer is referencing freed memory.
My first thought was concurrency. Maybe we had the timer running on a different CPU? Or maybe it was running on the same CPU, but in another context, preempted by the timer's softirq. No. The bug trace is all on CPU 0 in a single thread! So concurrency is not the issue.
Let's look more carefully at the function that handles the free: `timer_delete_sync`. Does it *really* wait? Yes, it waits for the timer to complete. However if the timer is already running on the same CPU, then the wait can introduce a deadlock, unless `del_timer_sync` has some magic. I looked at the implementation: `del_timer_sync` waits for a timer to finish (if it's not already on the softirq list). There is no other thread, there is no other CPU - the process is all on CPU 0.
So the timer should be deleted. We had the following hypotheses:
* timer already ran, and it got requeued. This would explain why we had a use-after-free, since the timer ran after we freed the cookie.
* the timer never got deleted. This is consistent with what we are observing.
* timer and cookie got freed out of order.
We double-checked the logic in `queue_iova` to make sure the timer wasn't getting requeued somehow. No. `queue_iova` does not seem to be the source of our problems.
We looked at the code again to be sure that `timer_delete_sync` was actually being called. We had a theory that maybe `cookie->fq_domain` was null, and therefore timer deletion was skipped. We carefully reviewed all writes to `cookie->fq_domain`, which is initialized once. We are sure of this.
Then it hit me. We are seeing a timer that should be gone, but isn't.
If the timer is still active, it *must* have been running *during* `timer_delete_sync` or just *after* it returns. If it was already running, `timer_delete_sync` would wait. If it was pending, `timer_delete_sync` would remove it and return. But if it was running, it would be executing the function `fq_flush_timeout`. The problem is therefore that after `fq_flush_timeout` has completed its run, it is freeing data that has already been freed during the device remove procedure - this is the likely cause of the UAF.
So our assumptions for what the `del_timer_sync` will be doing are correct. The device remove is calling `iommu_dma_free_fq`. This calls `
|
| 46/2 |
2026/08/09 23:29 |
tool |
grepper |
0m
Args: map[Expression:fq_domain PathPrefix:drivers/iommu/]
Results: map[Output:drivers/iommu/dma-iommu.c=58=struct iommu_dma_cookie {
--
drivers/iommu/dma-iommu.c-74- /* Domain for flush queue callback; NULL if flush queue not in use */
drivers/iommu/dma-iommu.c:75: struct iommu_domain *fq_domain;
drivers/iommu/dma-iommu.c-76- /* Options for dma-iommu use */
--
drivers/iommu/dma-iommu.c=174=static void fq_flush_iotlb(struct iommu_dma_cookie *cookie)
--
drivers/iommu/dma-iommu.c-176- atomic64_inc(&cookie->fq_flush_start_cnt);
drivers/iommu/dma-iommu.c:177: cookie->fq_domain->ops->flush_iotlb_all(cookie->fq_domain);
drivers/iommu/dma-iommu.c-178- atomic64_inc(&cookie->fq_flush_finish_cnt);
--
drivers/iommu/dma-iommu.c=274=static void iommu_dma_free_fq(struct iommu_dma_cookie *cookie)
drivers/iommu/dma-iommu.c-275-{
drivers/iommu/dma-iommu.c:276: if (!cookie->fq_domain)
drivers/iommu/dma-iommu.c-277- return;
--
drivers/iommu/dma-iommu.c=333=int iommu_dma_init_fq(struct iommu_domain *domain)
--
drivers/iommu/dma-iommu.c-337-
drivers/iommu/dma-iommu.c:338: if (cookie->fq_domain)
drivers/iommu/dma-iommu.c-339- return 0;
--
drivers/iommu/dma-iommu.c-360- smp_wmb();
drivers/iommu/dma-iommu.c:361: WRITE_ONCE(cookie->fq_domain, domain);
drivers/iommu/dma-iommu.c-362- return 0;
--
drivers/iommu/dma-iommu.c=823=static void __iommu_dma_unmap(struct device *dev, dma_addr_t dma_addr,
--
drivers/iommu/dma-iommu.c-835- iommu_iotlb_gather_init(&iotlb_gather);
drivers/iommu/dma-iommu.c:836: iotlb_gather.queued = READ_ONCE(cookie->fq_domain);
drivers/iommu/dma-iommu.c-837-
--
drivers/iommu/dma-iommu.c=2071=static void __iommu_dma_iova_unlink(struct device *dev,
--
drivers/iommu/dma-iommu.c-2089- iommu_iotlb_gather_init(&iotlb_gather);
drivers/iommu/dma-iommu.c:2090: iotlb_gather.queued = free_iova && READ_ONCE(cookie->fq_domain);
drivers/iommu/dma-iommu.c-2091-
]
|
| 47/2 |
2026/08/09 23:29 |
llm |
expert |
0m
Model:
Tokens:
input: 57210
output: 35
thoughts: 701
Thoughts: **Debugging a Potential Timer Object Leak**
Okay, so `cookie->fq_domain` gets written to only once, within `iommu_dma_init_fq`. Let's re-examine that function. I need to be absolutely sure, is it *possible* that `iommu_dma_init_fq` was *never* called? Because if that's the case, then `cookie->fq_domain` would be null. And if `cookie->fq_domain` is null, then `cookie->fq_timer` is never initialized. If `cookie->fq_timer` never gets initialized, then its memory is just going to be all zeros. Now, if that's true, when we call `kfree(cookie)`, should `debug_check_no_obj_freed` be complaining?
Hold on. If `cookie->fq_timer` is all zeros, is it considered an active debug object? No, it shouldn't be, because it was never registered with the debug objects system. That registration would normally happen in `timer_setup` which leads to `init_timer_key` and then `debug_init_timer`.
But what if, in the other scenario, `cookie->fq_timer` *was* initialized? That would mean `cookie->fq_domain` isn't NULL. Then, `iommu_dma_free_fq` *was* called, and it *did* call `timer_delete_sync(&cookie->fq_timer)`. If `timer_delete_sync` was called, why is `debug_check_no_obj_freed` still reporting that the timer is active? That makes no sense.
Okay, wait, let's look at the actual warning printed by `debug_check_no_obj_freed`. I've got the RIP and the code location:
```
RIP: 0010:debug_print_object+0x18a/0x230 lib/debugobjects.c:629
Code: f8 48 c1 e8 03 80 3c 18 00 74 08 4c 89 ff e8 3d 13 56 fd 4d 8b 0f 4c 89 ef 48 8b 74 24 08 48 89 ea 44 89 e1 4d 89 f0 ff 34 24 <67> 48 0f b9 3a 48 83 c4 08 ff 05 03 b3 77 0b 48 83 c4 10 5b 41 5c
...
__debug_check_no_obj_freed lib/debugobjects.c:1171 [inline]
debug_check_no_obj_freed+0x2e3/0x450 lib/debugobjects.c:1201
```
Right, `debug_check_no_obj_freed` is printing a warning because it found an active debug object. Okay, now, what specific *warning* is it printing? I need to go look in `lib/debugobjects.c` around line 1171 to see the exact message and understand what's going on.
|
| 48/2 |
2026/08/09 23:30 |
tool |
read-file |
0m
Args: map[File:lib/debugobjects.c FirstLine:1150 LineCount:40]
Results: map[Contents:1150: eaddr = saddr + size;
1151: paddr = saddr & ODEBUG_CHUNK_MASK;
1152: chunks = ((eaddr - paddr) + (ODEBUG_CHUNK_SIZE - 1));
1153: chunks >>= ODEBUG_CHUNK_SHIFT;
1154:
1155: for (;chunks > 0; chunks--, paddr += ODEBUG_CHUNK_SIZE) {
1156: db = get_bucket(paddr);
1157:
1158: repeat:
1159: cnt = 0;
1160: raw_spin_lock_irqsave(&db->lock, flags);
1161: hlist_for_each_entry_safe(obj, tmp, &db->list, node) {
1162: cnt++;
1163: oaddr = (unsigned long) obj->object;
1164: if (oaddr < saddr || oaddr >= eaddr)
1165: continue;
1166:
1167: switch (obj->state) {
1168: case ODEBUG_STATE_ACTIVE:
1169: o = *obj;
1170: raw_spin_unlock_irqrestore(&db->lock, flags);
1171: debug_print_object(&o, "free");
1172: debug_object_fixup(o.descr->fixup_free, (void *)oaddr, o.state);
1173: goto repeat;
1174: default:
1175: hlist_del(&obj->node);
1176: __free_object(obj);
1177: break;
1178: }
1179: }
1180: raw_spin_unlock_irqrestore(&db->lock, flags);
1181:
1182: if (cnt > debug_objects_maxchain)
1183: debug_objects_maxchain = cnt;
1184:
1185: objs_checked += cnt;
1186: }
1187:
1188: if (objs_checked > debug_objects_maxchecked)
1189: debug_objects_maxchecked = objs_checked;
]
|
| 49/2 |
2026/08/09 23:30 |
llm |
expert |
0m
Model:
Tokens:
input: 59314
output: 31
thoughts: 136
Thoughts: **Debugging a Potentially Freed Object**
Okay, here we go... *Ah!* That `debug_check_no_obj_freed` function just flagged an object that's still in the `ODEBUG_STATE_ACTIVE` state! That's not right. I need to understand what's happening. Let's see, what exactly does `debug_print_object` do? Hmm, it looks like it prints a warning, something like "ODEBUG: free active (active state 0) object: ...". Okay, good. Gives me some basic information.
But then... aha! It calls `debug_object_fixup` with `o.descr->fixup_free`, `(void *)oaddr`, and `o.state`. That `fixup_free` part is key. *Wait!* What kind of fixup is associated with a timer? That's what I need to figure out. Let me track down the fixup function for a timer. Time to dig into `kernel/time/timer.c`. I need to figure out how this is happening.
|
| 50/2 |
2026/08/09 23:30 |
tool |
grepper |
0m
Args: map[Expression:fixup_free PathPrefix:kernel/time/timer.c]
Results: map[Output:kernel/time/timer.c=724=static bool timer_fixup_activate(void *addr, enum debug_obj_state state)
--
kernel/time/timer.c-741-/*
kernel/time/timer.c:742: * timer_fixup_free is called when:
kernel/time/timer.c-743- * - an active object is freed
kernel/time/timer.c-744- */
kernel/time/timer.c:745:static bool timer_fixup_free(void *addr, enum debug_obj_state state)
kernel/time/timer.c-746-{
--
kernel/time/timer.c=776=static const struct debug_obj_descr timer_debug_descr = {
--
kernel/time/timer.c-781- .fixup_activate = timer_fixup_activate,
kernel/time/timer.c:782: .fixup_free = timer_fixup_free,
kernel/time/timer.c-783- .fixup_assert_init = timer_fixup_assert_init,
]
|
| 51/2 |
2026/08/09 23:30 |
llm |
expert |
0m
Model:
Tokens:
input: 60003
output: 37
thoughts: 22
Thoughts: Here's my summary, as though I'm reading and thinking about the code:
**Diving into `timer_fixup_free`**
Alright, let's get into the details of `timer_fixup_free` within `kernel/time/timer.c`. I'm expecting this function, given its name, to be the cleanup routine for timer structures after they've been used and are ready for release. I'm especially interested in how it handles the interaction with the timer wheel, potential races, and ensuring data integrity during deallocation.
I'll be looking for memory management, the potential use of atomic operations or spinlocks to protect shared resources, and any specific considerations for different timer types or implementations if they exist within the kernel. Itβs important to understand how these timers are unlinked from the wheel to avoid race conditions and any double-free situations. I want to see if it efficiently minimizes any time spent in critical sections and whether the code adheres to generally accepted kernel design principles for resource management. The goal is to see it is written for concurrency and performance.
|
| 52/2 |
2026/08/09 23:30 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:kernel/time/timer.c Name:timer_fixup_free]
Results: map[SourceCode: 741: /*
742: * timer_fixup_free is called when:
743: * - an active object is freed
744: */
745: static bool timer_fixup_free(void *addr, enum debug_obj_state state)
746: {
747: struct timer_list *timer = addr;
748:
749: switch (state) {
750: case ODEBUG_STATE_ACTIVE:
751: timer_delete_sync(timer);
752: debug_object_free(timer, &timer_debug_descr);
753: return true;
754: default:
755: return false;
756: }
757: }
SourceFile:kernel/time/timer.c]
|
| 53/2 |
2026/08/09 23:30 |
llm |
expert |
0m
Model:
Tokens:
input: 60537
output: 46
thoughts: 2891
Thoughts: **Okay, let's break this down systematically. This `timer_fixup_free` function immediately grabbed my attention. I see that it calls `timer_delete_sync` if the timer is active, which makes sense. I have to make sure that `timer_delete_sync` is being called when it should be.**
I need to confirm if `timer_delete_sync` is correctly being called, especially since it's used within `debug_check_no_obj_freed`, which is itself used within `kfree`. The initial thought is that `timer_delete_sync` *should* be called before the object is freed, if the timer is active. Now, I see this is used with `iommu_dma_free_fq`. If `cookie->fq_domain` is NULL, the `timer_delete_sync` is NOT called which makes sense.
I'm starting to think about *how* `cookie->fq_domain` can be null, and if the timer could be initialized and active even if the `fq_domain` isn't set. Let's dig deeper into `iommu_dma_init_fq`. If `iommu_dma_init_fq` is called, but it sets the timer and then later `cookie->fq_domain` goes to NULL is the core question. However, I see that is impossible since `cookie->fq_domain` never gets set to NULL, once it is initialized. Could `cookie->iovad.granule` be zero? No, that also gets set during initialization.
Let's look at `iommu_put_dma_cookie`. If `cookie->iovad.granule` is zero, then `iommu_dma_free_fq` is NOT called! But we know `put_iova_domain` was called and therefore the granule value wasn't zero. So we're sure the granule isn't zero.
I am now really starting to focus on the edge cases where the timer could still be active. If `cookie->fq_domain` is set to NULL, that could be a huge problem. But then, `iommu_dma_init_fq` seems to be the key point. If *that* function is never called, then the timer is never set up, and can't be active. But if `iommu_dma_init_fq` *is* called, could it fail? Yes, it returns -ENOMEM and then `cookie->fq_domain` remains NULL. But, then the timer is not initialized and active.
Let's focus on if `iommu_dma_init_fq` *was* called. If it WAS called, then `cookie->fq_domain` should not be NULL. And `cookie->fq_timer` *was* initialized. This also means that `iommu_dma_free_fq` must have been called. Since that means `timer_delete_sync` *was* called, why is the timer still active and flagged in the debug output? Does `timer_delete_sync` return while the timer is still active? No, `del_timer_sync` is guaranteed to deactivate the timer, so it cannot be a timing issue. Could the timer be reactivated *after* `timer_delete_sync`? Yes, the `queue_iova` function can re-activate it. Let's see if there's any path that might cause the function to be called:
Does `put_iova_domain` call `queue_iova`? No, it just frees the `iova` structs. Does `iommu_put_dma_cookie` calls `kfree(msi)` and thus the re-activation of the timer? No. What about `iommu_domain_free`? Does *that* call something that queues an IOVA? However, `domain->ops->free` is called AFTER `iommu_put_dma_cookie`, but the warning happens *during* `iommu_put_dma_cookie`. So `domain->ops->free` hasn't even happened.
So, let's go back. Does `cookie->fq_domain` go to NULL somehow? Let's check `iommu_dma_init_fq`. Is `iommu_dma_init_fq` called for EVERY domain? No, only if `iommu_domain_supports_fq` is true. If `iommu_domain_supports_fq` is false, then `cookie->fq_domain` is NULL, and no initialization happens! But, then the timer can't be active. What if `iom
|
| 54/2 |
2026/08/09 23:30 |
tool |
codesearch-struct-layout |
0m
Args: map[ContextFile:drivers/iommu/dma-iommu.c Name:iommu_dma_cookie]
Results: map[Fields:[map[Name:iovad OffsetBits:0 SizeBits:1472] map[Name:msi_page_list OffsetBits:1472 SizeBits:128] map[Name: OffsetBits:1600 SizeBits:64] map[Name:fq_flush_start_cnt OffsetBits:1664 SizeBits:64] map[Name:fq_flush_finish_cnt OffsetBits:1728 SizeBits:64] map[Name:fq_timer OffsetBits:1792 SizeBits:640] map[Name:fq_timer_on OffsetBits:2432 SizeBits:32] map[Name:fq_domain OffsetBits:2496 SizeBits:64] map[Name:options OffsetBits:2560 SizeBits:192]]]
|
| 55/2 |
2026/08/09 23:30 |
llm |
expert |
0m
Model:
Tokens:
input: 64720
output: 47
thoughts: 6257
Thoughts: **The KASAN Use-After-Free in IOMMU DMA Flush Timer: A Deep Dive**
Okay, here's what I've pieced together. This is a fascinating problem involving IOMMU DMA flush queues, timers, and the subtle interplay of concurrency and debug object tracking.
Essentially, we're seeing a use-after-free triggered by a timer that should have been deactivated, but wasn't. Let's break it down:
1. **The Setup:** A `struct iommu_dma_cookie` is allocated. This structure contains, among other things, a `struct timer_list` called `fq_timer`. This timer is associated with the IOMMU DMA flush queue functionality.
2. **Initialization:** The `iommu_dma_init_fq` function initializes the flush queue and sets up the timer. Crucially, this function *does* set `cookie->fq_domain`, which is a key flag for tracking the state of the flush queue. It also calls `timer_setup` to set up the timer.
3. **The Bug Trigger:** A `kfree(cookie)` call is made. This is the starting point. When `kfree` is called on the cookie, it invokes `debug_check_no_obj_freed`. This is where the debugging system kicks in. `debug_check_no_obj_freed` finds that the `fq_timer` is still active, even though the cookie is being freed!
4. **The Puzzle: Why is the Timer Active?** The crux of the issue is *why* the timer is still active. `iommu_put_dma_cookie` is called and inside `iommu_dma_free_fq`, `timer_delete_sync(&cookie->fq_timer)` *should* have been called. This function deactivates the timer, marking it as inactive in the debug objects system. But this is where the bug occurs. Because `iommu_put_dma_cookie` is called before `put_iova_domain`. And the timer could have already been requeued, AFTER `timer_delete_sync` had already been called.
5. **The Race Condition:** The likely scenario is a race condition between the CPU 0's freeing of the domain and a concurrent DMA operation on CPU 1. A flush queue is being used for DMA unmap, so it requeues the timer `fq_timer` after it was deleted.
6. **The Crash:** Back to `debug_check_no_obj_freed`. The debug system *does* call `timer_fixup_free` on it's own! It calls `timer_delete_sync` again. Since the timer is running, it does a full wait and deletes it. But this has already raced, and the timer's callback `fq_flush_timeout` *is running*. Since the IOMMU domain is already gone from `put_iova_domain` since CPU 0 is racing with CPU 1 (with respect to DMA unmap operations), `private_find_iova` tries to access freed memory, resulting in a KASAN use-after-free error, since the `iova` structure has already been freed in `put_iova_domain`.
In short, the root cause is a race condition where a DMA flush timer is reactivated after it *should* have been deleted, leading to a use-after-free when the timer's callback function is executed after the associated memory is freed by the other CPU.
|
| 56/2 |
2026/08/09 23:31 |
tool |
git-log |
0m
Args: map[CodeRegexp:timer_delete_sync Count:10 PathPrefix:drivers/iommu/dma-iommu.c]
Results: map[Output:8fa7292fee5c treewide: Switch/rename to timer_delete[_sync]()
]
|
| 57/2 |
2026/08/09 23:31 |
llm |
expert |
0m
Model:
Tokens:
input: 71834
output: 24
thoughts: 23
Thoughts: Here's my summary, as if I were thinking these thoughts:
**Analyzing Commit 8fa7292fee5c**
Alright, let's dive in. I need to get a clear picture of what happened in commit `8fa7292fee5c`. My go-to for this is `git show`, of course. It's the most straightforward way to see the commit's metadata (author, date, message) and, critically, the diffs introduced. I want to quickly understand the scope of changes, both the additions and removals. I'll be looking for any hints about potential bugs, performance impacts, or security vulnerabilities introduced by this commit. I'll be paying close attention to the context of the changes, not just the code itself, but also the why behind it. Knowing the reasoning behind this commit can often provide significant insight into potential problems or optimizations. This initial examination using `git show` will give me a base understanding before I dig deeper into specific lines or files if necessary.
|
| 58/2 |
2026/08/09 23:31 |
tool |
git-show |
0m
Args:
Results: map[Output:
Full output is too long, showing 1000 out of 16516 lines.
[commit 8fa7292fee5c5240402371ea89ab285ec856c916
Author: Thomas Gleixner <tglx@linutronix.de>
Date: Sat Apr 5 10:17:26 2025 +0200
treewide: Switch/rename to timer_delete[_sync]()
timer_delete[_sync]() replaces del_timer[_sync](). Convert the whole tree
over and remove the historical wrapper inlines.
Conversion was done with coccinelle plus manual fixups where necessary.
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Signed-off-by: Ingo Molnar <mingo@kernel.org>
diff --git a/arch/alpha/kernel/srmcons.c b/arch/alpha/kernel/srmcons.c
index b9cd364e814e..a89ce84371f9 100644
--- a/arch/alpha/kernel/srmcons.c
+++ b/arch/alpha/kernel/srmcons.c
@@ -177,7 +177,7 @@ srmcons_close(struct tty_struct *tty, struct file *filp)
if (tty->count == 1) {
port->tty = NULL;
- del_timer(&srmconsp->timer);
+ timer_delete(&srmconsp->timer);
}
spin_unlock_irqrestore(&port->lock, flags);
diff --git a/arch/arm/mach-footbridge/dc21285.c b/arch/arm/mach-footbridge/dc21285.c
index f8920d0010de..6521ab3d24fa 100644
--- a/arch/arm/mach-footbridge/dc21285.c
+++ b/arch/arm/mach-footbridge/dc21285.c
@@ -135,7 +135,7 @@ static struct timer_list perr_timer;
static void dc21285_enable_error(struct timer_list *timer)
{
- del_timer(timer);
+ timer_delete(timer);
if (timer == &serr_timer)
enable_irq(IRQ_PCI_SERR);
diff --git a/arch/arm/mach-pxa/sharpsl_pm.c b/arch/arm/mach-pxa/sharpsl_pm.c
index dd930e3a61a4..71b282b146d0 100644
--- a/arch/arm/mach-pxa/sharpsl_pm.c
+++ b/arch/arm/mach-pxa/sharpsl_pm.c
@@ -913,8 +913,8 @@ static void sharpsl_pm_remove(struct platform_device *pdev)
if (sharpsl_pm.machinfo->exit)
sharpsl_pm.machinfo->exit();
- del_timer_sync(&sharpsl_pm.chrg_full_timer);
- del_timer_sync(&sharpsl_pm.ac_timer);
+ timer_delete_sync(&sharpsl_pm.chrg_full_timer);
+ timer_delete_sync(&sharpsl_pm.ac_timer);
}
static struct platform_driver sharpsl_pm_driver = {
diff --git a/arch/m68k/amiga/amisound.c b/arch/m68k/amiga/amisound.c
index 714fe8ec6afa..5fd93dfab809 100644
--- a/arch/m68k/amiga/amisound.c
+++ b/arch/m68k/amiga/amisound.c
@@ -78,7 +78,7 @@ void amiga_mksound( unsigned int hz, unsigned int ticks )
return;
local_irq_save(flags);
- del_timer( &sound_timer );
+ timer_delete(&sound_timer);
if (hz > 20 && hz < 32767) {
unsigned long period = (clock_constant / hz);
diff --git a/arch/m68k/mac/macboing.c b/arch/m68k/mac/macboing.c
index faea2265a540..6312d5b600a5 100644
--- a/arch/m68k/mac/macboing.c
+++ b/arch/m68k/mac/macboing.c
@@ -183,7 +183,7 @@ void mac_mksound( unsigned int freq, unsigned int length )
local_irq_save(flags);
- del_timer( &mac_sound_timer );
+ timer_delete(&mac_sound_timer);
for ( i = 0; i < 0x800; i++ )
mac_asc_regs[ i ] = 0;
@@ -277,7 +277,7 @@ static void mac_quadra_ring_bell(struct timer_list *unused)
local_irq_save(flags);
- del_timer( &mac_sound_timer );
+ timer_delete(&mac_sound_timer);
if ( mac_bell_duration-- > 0 )
{
diff --git a/arch/mips/sgi-ip22/ip22-reset.c b/arch/mips/sgi-ip22/ip22-reset.c
index 8f0861c58080..8539f562f5b8 100644
--- a/arch/mips/sgi-ip22/ip22-reset.c
+++ b/arch/mips/sgi-ip22/ip22-reset.c
@@ -98,7 +98,7 @@ static void blink_timeout(struct timer_list *unused)
static void debounce(struct timer_list *unused)
{
- del_timer(&debounce_timer);
+ timer_delete(&debounce_timer);
if (sgint->istat1 & SGINT_ISTAT1_PWR) {
/* Interrupt still being sent. */
debounce_timer.expires = jiffies + (HZ / 20); /* 0.05s */
diff --git a/arch/powerpc/kvm/booke.c b/arch/powerpc/kvm/booke.c
index 6a5be025a8af..6a4805968966 100644
--- a/arch/powerpc/kvm/booke.c
+++ b/arch/powerpc/kvm/booke.c
@@ -622,7 +622,7 @@ static void arm_next_watchdog(struct kvm_vcpu *vcpu)
if (nr_jiffies < NEXT_TIMER_MAX_DELTA)
mod_timer(&vcpu->arch.wdt_timer, jiffies + nr_jiffies);
else
- del_timer(&vcpu->arch.wdt_timer);
+ timer_delete(&vcpu->arch.wdt_timer);
spin_unlock_irqrestore(&vcpu->arch.wdt_lock, flags);
}
@@ -1441,7 +1441,7 @@ int kvmppc_subarch_vcpu_init(struct kvm_vcpu *vcpu)
void kvmppc_subarch_vcpu_uninit(struct kvm_vcpu *vcpu)
{
- del_timer_sync(&vcpu->arch.wdt_timer);
+ timer_delete_sync(&vcpu->arch.wdt_timer);
}
int kvm_arch_vcpu_ioctl_get_regs(struct kvm_vcpu *vcpu, struct kvm_regs *regs)
diff --git a/arch/powerpc/platforms/cell/spufs/sched.c b/arch/powerpc/platforms/cell/spufs/sched.c
index 610ca8570682..8e7ed010bfde 100644
--- a/arch/powerpc/platforms/cell/spufs/sched.c
+++ b/arch/powerpc/platforms/cell/spufs/sched.c
@@ -508,7 +508,7 @@ static void __spu_del_from_rq(struct spu_context *ctx)
if (!list_empty(&ctx->rq)) {
if (!--spu_prio->nr_waiting)
- del_timer(&spusched_timer);
+ timer_delete(&spusched_timer);
list_del_init(&ctx->rq);
if (list_empty(&spu_prio->runq[prio]))
@@ -1126,8 +1126,8 @@ void spu_sched_exit(void)
remove_proc_entry("spu_loadavg", NULL);
- del_timer_sync(&spusched_timer);
- del_timer_sync(&spuloadavg_timer);
+ timer_delete_sync(&spusched_timer);
+ timer_delete_sync(&spuloadavg_timer);
kthread_stop(spusched_task);
for (node = 0; node < MAX_NUMNODES; node++) {
diff --git a/arch/powerpc/platforms/powermac/low_i2c.c b/arch/powerpc/platforms/powermac/low_i2c.c
index c097d591670e..a0ae58636e10 100644
--- a/arch/powerpc/platforms/powermac/low_i2c.c
+++ b/arch/powerpc/platforms/powermac/low_i2c.c
@@ -347,7 +347,7 @@ static irqreturn_t kw_i2c_irq(int irq, void *dev_id)
unsigned long flags;
spin_lock_irqsave(&host->lock, flags);
- del_timer(&host->timeout_timer);
+ timer_delete(&host->timeout_timer);
kw_i2c_handle_interrupt(host, kw_read_reg(reg_isr));
if (host->state != state_idle) {
host->timeout_timer.expires = jiffies + KW_POLL_TIMEOUT;
diff --git a/arch/s390/kernel/time.c b/arch/s390/kernel/time.c
index c900deddd36d..fed17d407a44 100644
--- a/arch/s390/kernel/time.c
+++ b/arch/s390/kernel/time.c
@@ -680,7 +680,7 @@ static void stp_work_fn(struct work_struct *work)
if (!stp_online) {
chsc_sstpc(stp_page, STP_OP_CTRL, 0x0000, NULL);
- del_timer_sync(&stp_timer);
+ timer_delete_sync(&stp_timer);
goto out_unlock;
}
diff --git a/arch/s390/mm/cmm.c b/arch/s390/mm/cmm.c
index 39f44b6256e0..e2a6eb92420f 100644
--- a/arch/s390/mm/cmm.c
+++ b/arch/s390/mm/cmm.c
@@ -201,7 +201,7 @@ static void cmm_set_timer(void)
{
if (cmm_timed_pages_target <= 0 || cmm_timeout_seconds <= 0) {
if (timer_pending(&cmm_timer))
- del_timer(&cmm_timer);
+ timer_delete(&cmm_timer);
return;
}
mod_timer(&cmm_timer, jiffies + secs_to_jiffies(cmm_timeout_seconds));
@@ -424,7 +424,7 @@ static int __init cmm_init(void)
#endif
unregister_sysctl_table(cmm_sysctl_header);
out_sysctl:
- del_timer_sync(&cmm_timer);
+ timer_delete_sync(&cmm_timer);
return rc;
}
module_init(cmm_init);
@@ -437,7 +437,7 @@ static void __exit cmm_exit(void)
#endif
unregister_oom_notifier(&cmm_oom_nb);
kthread_stop(cmm_thread_ptr);
- del_timer_sync(&cmm_timer);
+ timer_delete_sync(&cmm_timer);
cmm_free_pages(cmm_pages, &cmm_pages, &cmm_page_list);
cmm_free_pages(cmm_timed_pages, &cmm_timed_pages, &cmm_timed_page_list);
}
diff --git a/arch/sh/drivers/pci/common.c b/arch/sh/drivers/pci/common.c
index ab9e791070b4..5442475d132e 100644
--- a/arch/sh/drivers/pci/common.c
+++ b/arch/sh/drivers/pci/common.c
@@ -90,7 +90,7 @@ static void pcibios_enable_err(struct timer_list *t)
{
struct pci_channel *hose = from_timer(hose, t, err_timer);
- del_timer(&hose->err_timer);
+ timer_delete(&hose->err_timer);
printk(KERN_DEBUG "PCI: re-enabling error IRQ.\n");
enable_irq(hose->err_irq);
}
@@ -99,7 +99,7 @@ static void pcibios_enable_serr(struct timer_list *t)
{
struct pci_channel *hose = from_timer(hose, t, serr_timer);
- del_timer(&hose->serr_timer);
+ timer_delete(&hose->serr_timer);
printk(KERN_DEBUG "PCI: re-enabling system error IRQ.\n");
enable_irq(hose->serr_irq);
}
diff --git a/arch/sparc/kernel/led.c b/arch/sparc/kernel/led.c
index ab657b359789..f4fb82b019bb 100644
--- a/arch/sparc/kernel/led.c
+++ b/arch/sparc/kernel/led.c
@@ -84,7 +84,7 @@ static ssize_t led_proc_write(struct file *file, const char __user *buffer,
/* before we change anything we want to stop any running timers,
* otherwise calls such as on will have no persistent effect
*/
- del_timer_sync(&led_blink_timer);
+ timer_delete_sync(&led_blink_timer);
if (!strcmp(buf, "on")) {
auxio_set_led(AUXIO_LED_ON);
@@ -134,7 +134,7 @@ static int __init led_init(void)
static void __exit led_exit(void)
{
remove_proc_entry("led", NULL);
- del_timer_sync(&led_blink_timer);
+ timer_delete_sync(&led_blink_timer);
}
module_init(led_init);
diff --git a/arch/um/drivers/vector_kern.c b/arch/um/drivers/vector_kern.c
index 85b129e2b70b..b97bb52dd562 100644
--- a/arch/um/drivers/vector_kern.c
+++ b/arch/um/drivers/vector_kern.c
@@ -1112,7 +1112,7 @@ static int vector_net_close(struct net_device *dev)
struct vector_private *vp = netdev_priv(dev);
netif_stop_queue(dev);
- del_timer(&vp->tl);
+ timer_delete(&vp->tl);
vp->opened = false;
diff --git a/arch/x86/kernel/cpu/mce/core.c b/arch/x86/kernel/cpu/mce/core.c
index 1f14c3308b6b..f6fd71b64b66 100644
--- a/arch/x86/kernel/cpu/mce/core.c
+++ b/arch/x86/kernel/cpu/mce/core.c
@@ -1786,13 +1786,13 @@ void mce_timer_kick(bool storm)
__this_cpu_write(mce_next_interval, check_interval * HZ);
}
-/* Must not be called in IRQ context where del_timer_sync() can deadlock */
+/* Must not be called in IRQ context where timer_delete_sync() can deadlock */
static void mce_timer_delete_all(void)
{
int cpu;
for_each_online_cpu(cpu)
- del_timer_sync(&per_cpu(mce_timer, cpu));
+ timer_delete_sync(&per_cpu(mce_timer, cpu));
}
static void __mcheck_cpu_mce_banks_init(void)
@@ -2820,7 +2820,7 @@ static int mce_cpu_pre_down(unsigned int cpu)
struct timer_list *t = this_cpu_ptr(&mce_timer);
mce_disable_cpu();
- del_timer_sync(t);
+ timer_delete_sync(t);
mce_threshold_remove_device(cpu);
mce_device_remove(cpu);
return 0;
diff --git a/arch/x86/kvm/xen.c b/arch/x86/kvm/xen.c
index bd21e9c335ad..38b33cdd4232 100644
--- a/arch/x86/kvm/xen.c
+++ b/arch/x86/kvm/xen.c
@@ -1553,7 +1553,7 @@ static bool kvm_xen_schedop_poll(struct kvm_vcpu *vcpu, bool longmode,
kvm_vcpu_halt(vcpu);
if (sched_poll.timeout)
- del_timer(&vcpu->arch.xen.poll_timer);
+ timer_delete(&vcpu->arch.xen.poll_timer);
kvm_set_mp_state(vcpu, KVM_MP_STATE_RUNNABLE);
}
@@ -2308,7 +2308,7 @@ void kvm_xen_destroy_vcpu(struct kvm_vcpu *vcpu)
kvm_gpc_deactivate(&vcpu->arch.xen.vcpu_info_cache);
kvm_gpc_deactivate(&vcpu->arch.xen.vcpu_time_info_cache);
- del_timer_sync(&vcpu->arch.xen.poll_timer);
+ timer_delete_sync(&vcpu->arch.xen.poll_timer);
}
void kvm_xen_init_vm(struct kvm *kvm)
diff --git a/arch/xtensa/platforms/iss/console.c b/arch/xtensa/platforms/iss/console.c
index abec44b687df..8b95221375a8 100644
--- a/arch/xtensa/platforms/iss/console.c
+++ b/arch/xtensa/platforms/iss/console.c
@@ -48,7 +48,7 @@ static int rs_open(struct tty_struct *tty, struct file * filp)
static void rs_close(struct tty_struct *tty, struct file * filp)
{
if (tty->count == 1)
- del_timer_sync(&serial_timer);
+ timer_delete_sync(&serial_timer);
}
diff --git a/arch/xtensa/platforms/iss/network.c b/arch/xtensa/platforms/iss/network.c
index e89f27f2bb18..c6d8c62695e1 100644
--- a/arch/xtensa/platforms/iss/network.c
+++ b/arch/xtensa/platforms/iss/network.c
@@ -375,7 +375,7 @@ static int iss_net_close(struct net_device *dev)
struct iss_net_private *lp = netdev_priv(dev);
netif_stop_queue(dev);
- del_timer_sync(&lp->timer);
+ timer_delete_sync(&lp->timer);
lp->tp.net_ops->close(lp);
return 0;
diff --git a/block/blk-core.c b/block/blk-core.c
index 4623de79effa..e8cc270a453f 100644
--- a/block/blk-core.c
+++ b/block/blk-core.c
@@ -219,7 +219,7 @@ EXPORT_SYMBOL_GPL(blk_status_to_str);
*/
void blk_sync_queue(struct request_queue *q)
{
- del_timer_sync(&q->timeout);
+ timer_delete_sync(&q->timeout);
cancel_work_sync(&q->timeout_work);
}
EXPORT_SYMBOL(blk_sync_queue);
diff --git a/block/blk-stat.c b/block/blk-stat.c
index eaf60097bbe1..46449da856f8 100644
--- a/block/blk-stat.c
+++ b/block/blk-stat.c
@@ -162,7 +162,7 @@ void blk_stat_remove_callback(struct request_queue *q,
blk_queue_flag_clear(QUEUE_FLAG_STATS, q);
spin_unlock_irqrestore(&q->stats->lock, flags);
- del_timer_sync(&cb->timer);
+ timer_delete_sync(&cb->timer);
}
static void blk_stat_free_callback_rcu(struct rcu_head *head)
diff --git a/block/blk-stat.h b/block/blk-stat.h
index 5d7f18ba436d..9e05bf18d1be 100644
--- a/block/blk-stat.h
+++ b/block/blk-stat.h
@@ -148,7 +148,7 @@ static inline void blk_stat_activate_nsecs(struct blk_stat_callback *cb,
static inline void blk_stat_deactivate(struct blk_stat_callback *cb)
{
- del_timer_sync(&cb->timer);
+ timer_delete_sync(&cb->timer);
}
/**
diff --git a/block/blk-throttle.c b/block/blk-throttle.c
index 91dab43c65ab..d6dd2e047874 100644
--- a/block/blk-throttle.c
+++ b/block/blk-throttle.c
@@ -333,7 +333,7 @@ static void throtl_pd_free(struct blkg_policy_data *pd)
{
struct throtl_grp *tg = pd_to_tg(pd);
- del_timer_sync(&tg->service_queue.pending_timer);
+ timer_delete_sync(&tg->service_queue.pending_timer);
blkg_rwstat_exit(&tg->stat_bytes);
blkg_rwstat_exit(&tg->stat_ios);
kfree(tg);
@@ -1711,7 +1711,7 @@ void blk_throtl_exit(struct gendisk *disk)
if (!blk_throtl_activated(q))
return;
- del_timer_sync(&q->td->service_queue.pending_timer);
+ timer_delete_sync(&q->td->service_queue.pending_timer);
throtl_shutdown_wq(q);
blkcg_deactivate_policy(disk, &blkcg_policy_throtl);
kfree(q->td);
diff --git a/drivers/accel/qaic/qaic_timesync.c b/drivers/accel/qaic/qaic_timesync.c
index 2473c66309d4..972833fabcfc 100644
--- a/drivers/accel/qaic/qaic_timesync.c
+++ b/drivers/accel/qaic/qaic_timesync.c
@@ -221,7 +221,7 @@ static void qaic_timesync_remove(struct mhi_device *mhi_dev)
{
struct mqts_dev *mqtsdev = dev_get_drvdata(&mhi_dev->dev);
- del_timer_sync(&mqtsdev->timer);
+ timer_delete_sync(&mqtsdev->timer);
mhi_unprepare_from_transfer(mqtsdev->mhi_dev);
kfree(mqtsdev->sync_msg);
kfree(mqtsdev);
diff --git a/drivers/accessibility/speakup/main.c b/drivers/accessibility/speakup/main.c
index f677ad2177c2..e68cf1d83787 100644
--- a/drivers/accessibility/speakup/main.c
+++ b/drivers/accessibility/speakup/main.c
@@ -1172,13 +1172,13 @@ static void do_handle_shift(struct vc_data *vc, u_char value, char up_flag)
if (cursor_track == read_all_mode) {
switch (value) {
case KVAL(K_SHIFT):
- del_timer(&cursor_timer);
+ timer_delete(&cursor_timer);
spk_shut_up &= 0xfe;
spk_do_flush();
read_all_doc(vc);
break;
case KVAL(K_CTRL):
- del_timer(&cursor_timer);
+ timer_delete(&cursor_timer);
cursor_track = prev_cursor_track;
spk_shut_up &= 0xfe;
spk_do_flush();
@@ -1399,7 +1399,7 @@ static void start_read_all_timer(struct vc_data *vc, enum read_all_command comma
static void kbd_fakekey2(struct vc_data *vc, enum read_all_command command)
{
- del_timer(&cursor_timer);
+ timer_delete(&cursor_timer);
speakup_fake_down_arrow();
start_read_all_timer(vc, command);
}
@@ -1415,7 +1415,7 @@ static void read_all_doc(struct vc_data *vc)
cursor_track = read_all_mode;
spk_reset_index_count(0);
if (get_sentence_buf(vc, 0) == -1) {
- del_timer(&cursor_timer);
+ timer_delete(&cursor_timer);
if (!in_keyboard_notifier)
speakup_fake_down_arrow();
start_read_all_timer(vc, RA_DOWN_ARROW);
@@ -1428,7 +1428,7 @@ static void read_all_doc(struct vc_data *vc)
static void stop_read_all(struct vc_data *vc)
{
- del_timer(&cursor_timer);
+ timer_delete(&cursor_timer);
cursor_track = prev_cursor_track;
spk_shut_up &= 0xfe;
spk_do_flush();
@@ -1528,7 +1528,7 @@ static int pre_handle_cursor(struct vc_data *vc, u_char value, char up_flag)
spin_unlock_irqrestore(&speakup_info.spinlock, flags);
return NOTIFY_STOP;
}
- del_timer(&cursor_timer);
+ timer_delete(&cursor_timer);
spk_shut_up &= 0xfe;
spk_do_flush();
start_read_all_timer(vc, value + 1);
@@ -1692,7 +1692,7 @@ static void cursor_done(struct timer_list *unused)
struct vc_data *vc = vc_cons[cursor_con].d;
unsigned long flags;
- del_timer(&cursor_timer);
+ timer_delete(&cursor_timer);
spin_lock_irqsave(&speakup_info.spinlock, flags);
if (cursor_con != fg_console) {
is_cursor = 0;
@@ -2333,7 +2333,7 @@ static void __exit speakup_exit(void)
speakup_unregister_devsynth();
speakup_cancel_selection();
speakup_cancel_paste();
- del_timer_sync(&cursor_timer);
+ timer_delete_sync(&cursor_timer);
kthread_stop(speakup_task);
speakup_task = NULL;
mutex_lock(&spk_mutex);
@@ -2437,7 +2437,7 @@ static int __init speakup_init(void)
error_vtnotifier:
unregister_keyboard_notifier(&keyboard_notifier_block);
- del_timer(&cursor_timer);
+ timer_delete(&cursor_timer);
error_kbdnotifier:
speakup_unregister_devsynth();
diff --git a/drivers/accessibility/speakup/synth.c b/drivers/accessibility/speakup/synth.c
index 85062e605d79..d8addbf3ad0d 100644
--- a/drivers/accessibility/speakup/synth.c
+++ b/drivers/accessibility/speakup/synth.c
@@ -521,7 +521,7 @@ void synth_release(void)
spin_lock_irqsave(&speakup_info.spinlock, flags);
pr_info("releasing synth %s\n", synth->name);
synth->alive = 0;
- del_timer(&thread_timer);
+ timer_delete(&thread_timer);
spin_unlock_irqrestore(&speakup_info.spinlock, flags);
if (synth->attributes.name)
sysfs_remove_group(speakup_kobj, &synth->attributes);
diff --git a/drivers/ata/libata-eh.c b/drivers/ata/libata-eh.c
index 16cd676eae1f..b990c1ee0b12 100644
--- a/drivers/ata/libata-eh.c
+++ b/drivers/ata/libata-eh.c
@@ -700,7 +700,7 @@ void ata_scsi_port_error_handler(struct Scsi_Host *host, struct ata_port *ap)
ata_eh_acquire(ap);
repeat:
/* kill fast drain timer */
- del_timer_sync(&ap->fastdrain_timer);
+ timer_delete_sync(&ap->fastdrain_timer);
/* process port resume request */
ata_eh_handle_port_resume(ap);
diff --git a/drivers/atm/idt77105.c b/drivers/atm/idt77105.c
index fcd70e094a2e..e6a300203e6c 100644
--- a/drivers/atm/idt77105.c
+++ b/drivers/atm/idt77105.c
@@ -366,8 +366,8 @@ EXPORT_SYMBOL(idt77105_init);
static void __exit idt77105_exit(void)
{
/* turn off timers */
- del_timer_sync(&stats_timer);
- del_timer_sync(&restart_timer);
+ timer_delete_sync(&stats_timer);
+ timer_delete_sync(&restart_timer);
}
module_exit(idt77105_exit);
diff --git a/drivers/atm/iphase.c b/drivers/atm/iphase.c
index d213adcefe33..301e697e22ad 100644
--- a/drivers/atm/iphase.c
+++ b/drivers/atm/iphase.c
@@ -3283,7 +3283,7 @@ static void __exit ia_module_exit(void)
{
pci_unregister_driver(&ia_driver);
- del_timer_sync(&ia_timer);
+ timer_delete_sync(&ia_timer);
}
module_init(ia_module_init);
diff --git a/drivers/atm/lanai.c b/drivers/atm/lanai.c
index 32d7aa141d96..00fe25b5b6a3 100644
--- a/drivers/atm/lanai.c
+++ b/drivers/atm/lanai.c
@@ -1792,7 +1792,7 @@ static inline void lanai_timed_poll_start(struct lanai_dev *lanai)
static inline void lanai_timed_poll_stop(struct lanai_dev *lanai)
{
- del_timer_sync(&lanai->timer);
+ timer_delete_sync(&lanai->timer);
}
/* -------------------- INTERRUPT SERVICE: */
diff --git a/drivers/atm/nicstar.c b/drivers/atm/nicstar.c
index 27153d6bc781..45952cfea06b 100644
--- a/drivers/atm/nicstar.c
+++ b/drivers/atm/nicstar.c
@@ -300,7 +300,7 @@ static void __exit nicstar_cleanup(void)
{
XPRINTK("nicstar: nicstar_cleanup() called.\n");
- del_timer_sync(&ns_timer);
+ timer_delete_sync(&ns_timer);
pci_unregister_driver(&nicstar_driver);
diff --git a/drivers/atm/suni.c b/drivers/atm/suni.c
index 32802ea9521c..7d0fa729c2fe 100644
--- a/drivers/atm/suni.c
+++ b/drivers/atm/suni.c
@@ -347,7 +347,7 @@ static int suni_stop(struct atm_dev *dev)
for (walk = &sunis; *walk != PRIV(dev);
walk = &PRIV((*walk)->dev)->next);
*walk = PRIV((*walk)->dev)->next;
- if (!sunis) del_timer_sync(&poll_timer);
+ if (!sunis) timer_delete_sync(&poll_timer);
spin_unlock_irqrestore(&sunis_lock,flags);
kfree(PRIV(dev));
diff --git a/drivers/auxdisplay/line-display.c b/drivers/auxdisplay/line-display.c
index fcec77f100ce..b6808c4f89b6 100644
--- a/drivers/auxdisplay/line-display.c
+++ b/drivers/auxdisplay/line-display.c
@@ -84,7 +84,7 @@ static int linedisp_display(struct linedisp *linedisp, const char *msg,
char *new_msg;
/* stop the scroll timer */
- del_timer_sync(&linedisp->timer);
+ timer_delete_sync(&linedisp->timer);
if (count == -1)
count = strlen(msg);
@@ -183,7 +183,7 @@ static ssize_t scroll_step_ms_store(struct device *dev,
linedisp->scroll_rate = msecs_to_jiffies(ms);
if (linedisp->message && linedisp->message_len > linedisp->num_chars) {
- del_timer_sync(&linedisp->timer);
+ timer_delete_sync(&linedisp->timer);
if (linedisp->scroll_rate)
linedisp_scroll(&linedisp->timer);
}
@@ -376,7 +376,7 @@ int linedisp_register(struct linedisp *linedisp, struct device *parent,
out_del_dev:
device_del(&linedisp->dev);
out_del_timer:
- del_timer_sync(&linedisp->timer);
+ timer_delete_sync(&linedisp->timer);
out_put_device:
put_device(&linedisp->dev);
return err;
@@ -391,7 +391,7 @@ EXPORT_SYMBOL_NS_GPL(linedisp_register, "LINEDISP");
void linedisp_unregister(struct linedisp *linedisp)
{
device_del(&linedisp->dev);
- del_timer_sync(&linedisp->timer);
+ timer_delete_sync(&linedisp->timer);
put_device(&linedisp->dev);
}
EXPORT_SYMBOL_NS_GPL(linedisp_unregister, "LINEDISP");
diff --git a/drivers/auxdisplay/panel.c b/drivers/auxdisplay/panel.c
index 91ccb9789d43..958c0e31e84a 100644
--- a/drivers/auxdisplay/panel.c
+++ b/drivers/auxdisplay/panel.c
@@ -1654,7 +1654,7 @@ static void panel_attach(struct parport *port)
err_lcd_unreg:
if (scan_timer.function)
- del_timer_sync(&scan_timer);
+ timer_delete_sync(&scan_timer);
if (lcd.enabled)
charlcd_unregister(lcd.charlcd);
err_unreg_device:
@@ -1675,7 +1675,7 @@ static void panel_detach(struct parport *port)
return;
}
if (scan_timer.function)
- del_timer_sync(&scan_timer);
+ timer_delete_sync(&scan_timer);
if (keypad.enabled) {
misc_deregister(&keypad_dev);
diff --git a/drivers/base/devcoredump.c b/drivers/base/devcoredump.c
index 64840e5d5fcc..03a39c417dc4 100644
--- a/drivers/base/devcoredump.c
+++ b/drivers/base/devcoredump.c
@@ -41,7 +41,7 @@ struct devcd_entry {
* devcd_data_write()
* mod_delayed_work()
* try_to_grab_pending()
- * del_timer()
+ * timer_delete()
* debug_assert_init()
* INIT_DELAYED_WORK()
* schedule_delayed_work()
diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c
index ac2a197c1234..c8b0a9e29ed8 100644
--- a/drivers/base/power/main.c
+++ b/drivers/base/power/main.c
@@ -559,7 +559,7 @@ static void dpm_watchdog_clear(struct dpm_watchdog *wd)
{
struct timer_list *timer = &wd->timer;
- del_timer_sync(timer);
+ timer_delete_sync(timer);
destroy_timer_on_stack(timer);
}
#else
diff --git a/drivers/base/power/wakeup.c b/drivers/base/power/wakeup.c
index 752b417e8129..63bf914a4d44 100644
--- a/drivers/base/power/wakeup.c
+++ b/drivers/base/power/wakeup.c
@@ -197,7 +197,7 @@ void wakeup_source_remove(struct wakeup_source *ws)
raw_spin_unlock_irqrestore(&events_lock, flags);
synchronize_srcu(&wakeup_srcu);
- del_timer_sync(&ws->timer);
+ timer_delete_sync(&ws->timer);
/*
* Clear timer.function to make wakeup_source_not_registered() treat
* this wakeup source as not registered.
@@ -613,7 +613,7 @@ void __pm_stay_awake(struct wakeup_source *ws)
spin_lock_irqsave(&ws->lock, flags);
wakeup_source_report_event(ws, false);
- del_timer(&ws->timer);
+ timer_delete(&ws->timer);
ws->timer_expires = 0;
spin_unlock_irqrestore(&ws->lock, flags);
@@ -693,7 +693,7 @@ static void wakeup_source_deactivate(struct wakeup_source *ws)
ws->max_time = duration;
ws->last_time = now;
- del_timer(&ws->timer);
+ timer_delete(&ws->timer);
ws->timer_expires = 0;
if (ws->autosleep_enabled)
diff --git a/drivers/block/amiflop.c b/drivers/block/amiflop.c
index 9edd4468f755..6357d86eafdc 100644
--- a/drivers/block/amiflop.c
+++ b/drivers/block/amiflop.c
@@ -457,7 +457,7 @@ static int fd_motor_on(int nr)
{
nr &= 3;
- del_timer(motor_off_timer + nr);
+ timer_delete(motor_off_timer + nr);
if (!unit[nr].motor) {
unit[nr].motor = 1;
@@ -1393,7 +1393,7 @@ static int non_int_flush_track (unsigned long nr)
nr&=3;
writefromint = 0;
- del_timer(&post_write_timer);
+ timer_delete(&post_write_timer);
get_fdc(nr);
if (!fd_motor_on(nr)) {
writepending = 0;
@@ -1435,7 +1435,7 @@ static int get_track(int drive, int track)
}
if (unit[drive].dirty == 1) {
- del_timer (flush_track_timer + drive);
+ timer_delete(flush_track_timer + drive);
non_int_flush_track (drive);
}
errcnt = 0;
@@ -1591,7 +1591,7 @@ static int fd_locked_ioctl(struct block_device *bdev, blk_mode_t mode,
case FDDEFPRM:
return -EINVAL;
case FDFLUSH: /* unconditionally, even if not needed */
- del_timer (flush_track_timer + drive);
+ timer_delete(flush_track_timer + drive);
non_int_flush_track(drive);
break;
#ifdef RAW_IOCTL
@@ -1714,7 +1714,7 @@ static void floppy_release(struct gendisk *disk)
mutex_lock(&amiflop_mutex);
if (unit[drive].dirty == 1) {
- del_timer (flush_track_timer + drive);
+ timer_delete(flush_track_timer + drive);
non_int_flush_track (drive);
}
diff --git a/drivers/block/aoe/aoedev.c b/drivers/block/aoe/aoedev.c
index 4db7f6ce8ade..141b2a0e03f2 100644
--- a/drivers/block/aoe/aoedev.c
+++ b/drivers/block/aoe/aoedev.c
@@ -274,7 +274,7 @@ freedev(struct aoedev *d)
if (!freeing)
return;
- del_timer_sync(&d->timer);
+ timer_delete_sync(&d->timer);
if (d->gd) {
aoedisk_rm_debugfs(d);
del_gendisk(d->gd);
diff --git a/drivers/block/aoe/aoemain.c b/drivers/block/aoe/aoemain.c
index 6238c4c87cfc..cdf6e4041bb9 100644
--- a/drivers/block/aoe/aoemain.c
+++ b/drivers/block/aoe/aoemain.c
@@ -28,7 +28,7 @@ static void discover_timer(struct timer_list *t)
static void __exit
aoe_exit(void)
{
- del_timer_sync(&timer);
+ timer_delete_sync(&timer);
aoenet_exit();
unregister_blkdev(AOE_MAJOR, DEVICE_NAME);
diff --git a/drivers/block/ataflop.c b/drivers/block/ataflop.c
index a81ade622a01..7fe14266c12c 100644
--- a/drivers/block/ataflop.c
+++ b/drivers/block/ataflop.c
@@ -494,7 +494,7 @@ static inline void start_timeout(void)
static inline void stop_timeout(void)
{
- del_timer(&timeout_timer);
+ timer_delete(&timeout_timer);
}
/* Select the side to use. */
@@ -784,7 +784,7 @@ static int do_format(int drive, int type, struct atari_format_descr *desc)
contents become invalid! */
BufferDrive = -1;
/* stop deselect timer */
- del_timer( &motor_off_timer );
+ timer_delete(&motor_off_timer);
FILL( 60 * (nsect / 9), 0x4e );
for( sect = 0; sect < nsect; ++sect ) {
@@ -1138,7 +1138,7 @@ static void fd_rwsec_done( int status )
DPRINT(("fd_rwsec_done()\n"));
if (read_track) {
- del_timer(&readtrack_timer);
+ timer_delete(&readtrack_timer);
if (!MultReadInProgress)
return;
MultReadInProgress = 0;
@@ -1356,7 +1356,7 @@ static void fd_times_out(struct timer_list *unused)
/* If the timeout occurred while the readtrack_check timer was
* active, we need to cancel it, else bad things will happen */
if (UseTrackbuffer)
- del_timer( &readtrack_timer );
+ timer_delete(&readtrack_timer);
FDC_WRITE( FDCREG_CMD, FDCCMD_FORCI );
udelay( 25 );
@@ -1566,7 +1566,7 @@ static blk_status_t ataflop_queue_rq(struct blk_mq_hw_ctx *hctx,
}
/* stop deselect timer */
- del_timer( &motor_off_timer );
+ timer_delete(&motor_off_timer);
ReqCnt = 0;
ReqCmd = rq_data_dir(fd_request);
@@ -2055,7 +2055,7 @@ static void atari_floppy_cleanup(void)
blk_mq_free_tag_set(&unit[i].tag_set);
}
- del_timer_sync(&fd_timer);
+ timer_delete_sync(&fd_timer);
atari_stram_free(DMABuffer);
}
diff --git a/drivers/block/drbd/drbd_main.c b/drivers/block/drbd/drbd_main.c
index 5bbd312c3e14..ced2cc5f46f2 100644
--- a/drivers/block/drbd/drbd_main.c
+++ b/drivers/block/drbd/drbd_main.c
@@ -3034,7 +3034,7 @@ void drbd_md_sync(struct drbd_device *device)
BUILD_BUG_ON(UI_SIZE != 4);
BUILD_BUG_ON(sizeof(struct meta_data_on_disk) != 4096);
- del_timer(&device->md_sync_timer);
+ timer_delete(&device->md_sync_timer);
/* timer may be rearmed by drbd_md_mark_dirty() now. */
if (!test_and_clear_bit(MD_DIRTY, &device->flags))
return;
diff --git a/drivers/block/drbd/drbd_nl.c b/drivers/block/drbd/drbd_nl.c
index 720fc30e2ecc..e09930c2b226 100644
--- a/drivers/block/drbd/drbd_nl.c
+++ b/drivers/block/drbd/drbd_nl.c
@@ -1033,7 +1033,7 @@ drbd_determine_dev_size(struct drbd_device *device, enum dds_flags flags, struct
/* We do some synchronous IO below, which may take some time.
* Clear the timer, to avoid scary "timer expired!" messages,
* "Superblock" is written out at least twice below, anyways. */
- del_timer(&device->md_sync_timer);
+ timer_delete(&device->md_sync_timer);
/* We won't change the "al-extents" setting, we just may need
* to move the on-disk location of the activity log ringbuffer.
diff --git a/drivers/block/drbd/drbd_receiver.c b/drivers/block/drbd/drbd_receiver.c
index 0c9f54197768..e5a2e5f7887b 100644
--- a/drivers/block/drbd/drbd_receiver.c
+++ b/drivers/block/drbd/drbd_receiver.c
@@ -5187,7 +5187,7 @@ static int drbd_disconnected(struct drbd_peer_device *peer_device)
atomic_set(&device->rs_pending_cnt, 0);
wake_up(&device->misc_wait);
- del_timer_sync(&device->resync_timer);
+ timer_delete_sync(&device->resync_timer);
resync_timer_fn(&device->resync_timer);
/* wait for all w_e_end_data_req, w_e_end_rsdata_req, w_send_barrier,
diff --git a/drivers/block/floppy.c b/drivers/block/floppy.c
index abf0486f0d4f..e97432032f01 100644
--- a/drivers/block/floppy.c
+++ b/drivers/block/floppy.c
@@ -937,7 +937,7 @@ static void floppy_off(unsigned int drive)
if (!(fdc_state[fdc].dor & (0x10 << UNIT(drive))))
return;
- del_timer(motor_off_timer + drive);
+ timer_delete(motor_off_timer + drive);
/* make spindle stop in a position which minimizes spinup time
* next time */
@@ -1918,7 +1918,7 @@ static int start_motor(void (*function)(void))
mask &= ~(0x10 << UNIT(current_drive));
/* starts motor and selects floppy */
- del_timer(motor_off_timer + current_drive);
+ timer_delete(motor_off_timer + current_drive);
set_dor(current_fdc, mask, data);
/* wait_for_completion also schedules reset if needed. */
@@ -4762,7 +4762,7 @@ static int __init do_floppy_init(void)
for (drive = 0; drive < N_DRIVE; drive++) {
if (!disks[drive][0])
break;
- del_timer_sync(&motor_off_timer[drive]);
+ timer_delete_sync(&motor_off_timer[drive]);
put_disk(disks[drive][0]);
blk_mq_free_tag_set(&tag_sets[drive]);
}
@@ -4983,7 +4983,7 @@ static void __exit floppy_module_exit(void)
destroy_workqueue(floppy_wq);
for (drive = 0; drive < N_DRIVE; drive++) {
- del_timer_sync(&motor_off_timer[drive]);
+ timer_delete_sync(&motor_off_timer[drive]);
if (floppy_available(drive)) {
for (i = 0; i < ARRAY_SIZE(floppy_type); i++) {
diff --git a/drivers/block/sunvdc.c b/drivers/block/sunvdc.c
index 2b33fb5b949b..b5727dea15bd 100644
--- a/drivers/block/sunvdc.c
+++ b/drivers/block/sunvdc.c
@@ -1070,7 +1070,7 @@ static void vdc_port_remove(struct vio_dev *vdev)
flush_work(&port->ldc_reset_work);
cancel_delayed_work_sync(&port->ldc_reset_timer_work);
- del_timer_sync(&port->vio.timer);
+ timer_delete_sync(&port->vio.timer);
del_gendisk(port->disk);
put_disk(port->disk);
diff --git a/drivers/block/swim3.c b/drivers/block/swim3.c
index 3aedcb5add61..ee6cade70222 100644
--- a/drivers/block/swim3.c
+++ b/drivers/block/swim3.c
@@ -362,7 +362,7 @@ static void set_timeout(struct floppy_state *fs, int nticks,
void (*proc)(struct timer_list *t))
{
if (fs->timeout_pending)
- del_timer(&fs->timeout);
+ timer_delete(&fs->timeout);
fs->timeout.expires = jiffies + nticks;
fs->timeout.function = proc;
add_timer(&fs->timeout);
@@ -677,7 +677,7 @@ static irqreturn_t swim3_interrupt(int irq, void *dev_id)
out_8(&sw->control_bic, DO_ACTION | WRITE_SECTORS);
out_8(&sw->select, RELAX);
out_8(&sw->intr_enable, 0);
- del_timer(&fs->timeout);
+ timer_delete(&fs->timeout);
fs->timeout_pending = 0;
if (sw->ctrack == 0xff) {
swim3_err("%s", "Seen sector but cyl=ff?\n");
@@ -706,7 +706,7 @@ static irqreturn_t swim3_interrupt(int irq, void *dev_id)
out_8(&sw->control_bic, DO_SEEK);
out_8(&sw->select, RELAX);
out_8(&sw->intr_enable, 0);
- del_timer(&fs->timeout);
+ timer_delete(&fs->timeout);
fs->timeout_pending = 0;
if (fs->state == seeking)
++fs->retries;
@@ -716,7 +716,7 @@ static irqreturn_t swim3_interrupt(int irq, void *dev_id)
break;
case settling:
out_8(&sw->intr_enable, 0);
- del_timer(&fs->timeout);
+ timer_delete(&fs->timeout);
fs->timeout_pending = 0;
act(fs);
break;
@@ -726,7 +726,7 @@ static irqreturn_t swim3_interrupt(int irq, void *dev_id)
out_8(&sw->intr_enable, 0);
out_8(&sw->control_bic, WRITE_SECTORS | DO_ACTION);
out_8(&sw->select, RELAX);
- del_timer(&fs->timeout);
+ timer_delete(&fs->timeout);
fs->timeout_pending = 0;
dr = fs->dma;
cp = fs->dma_cmd;
diff --git a/drivers/bluetooth/bluecard_cs.c b/drivers/bluetooth/bluecard_cs.c
index 36eabf61717f..1c7f89e134b3 100644
--- a/drivers/bluetooth/bluecard_cs.c
+++ b/drivers/bluetooth/bluecard_cs.c
@@ -638,7 +638,7 @@ static int bluecard_hci_close(struct hci_dev *hdev)
bluecard_hci_flush(hdev);
/* Stop LED timer */
- del_timer_sync(&(info->timer));
+ timer_delete_sync(&(info->timer));
/* Disable power LED */
outb(0x00, iobase + 0x30);
@@ -885,7 +885,7 @@ static void bluecard_release(struct pcmcia_device *link)
bluecard_close(info);
]
]
|
| 59/2 |
2026/08/09 23:31 |
llm |
expert |
0m
Model:
Tokens:
input: 87436
output: 43
thoughts: 1110
Thoughts: **Analyzing Potential Timer Deletion Issues in IOMMU DMA Code**
Okay, so I'm looking at this rename commit, `8fa7292fee5c`, which just changed `del_timer_sync` to `timer_delete_sync`. No big deal, mostly a code style change. But let's dig into `iommu_dma_free_fq` in `drivers/iommu/dma-iommu.c`. Hmm, I see it's only called from `iommu_put_dma_cookie`. Let's refresh our memory of `iommu_put_dma_cookie`.
Okay, so we have this function that looks like this:
```c
void iommu_put_dma_cookie(struct iommu_domain *domain)
{
struct iommu_dma_cookie *cookie = domain->iova_cookie;
struct iommu_dma_msi_page *msi, *tmp;
if (cookie->iovad.granule) {
iommu_dma_free_fq(cookie);
put_iova_domain(&cookie->iovad);
}
list_for_each_entry_safe(msi, tmp, &cookie->msi_page_list, list)
kfree(msi);
kfree(cookie);
}
```
Now, the crucial part is within the `if (cookie->iovad.granule)` block. We are calling `iommu_dma_free_fq` here. Now, what if `cookie->fq_domain` is NULL? Well, in that case `iommu_dma_free_fq` will return early and `timer_delete_sync` isn't called. But... could the timer have been initialized and *active* even if `cookie->fq_domain` is NULL? Thinking about this, `cookie->fq_domain` is set in `iommu_dma_init_fq`. But can it fail *after* `timer_setup`? Nope, `timer_setup` is at the *end* of `iommu_dma_init_fq`, after all failure points. Okay, can `iommu_dma_init_fq` succeed, but then later somehow set `cookie->fq_domain` to NULL? I searched, and there's no other write to `fq_domain`. So, let's revisit `iommu_dma_free_fq` itself:
```c
static void iommu_dma_free_fq(struct iommu_dma_cookie *cookie)
{
if (!cookie->fq_domain)
return;
timer_delete_sync(&cookie->fq_timer);
if (cookie->options.qt == IOMMU_DMA_OPTS_SINGLE_QUEUE)
iommu_dma_free_fq_single(cookie->single_fq);
else
iommu_dma_free_fq_percpu(cookie->percpu_fq);
}
```
So, If `cookie->fq_domain` is NOT NULL, `timer_delete_sync` *is* called. Now, does the call to `timer_delete_sync` actually *do* anything? Yes, if `cookie->fq_domain` isn't NULL. Okay, so can the timer be re-queued after `timer_delete_sync`? Yes, we saw that `queue_iova` does that. But any *other* ways? And what about the timer *currently* running? Does `timer_delete_sync` wait for it? Yes, `del_timer_sync` always waits, unless it's in interrupt context. Nope. That's not the case here.
So, if `cookie->fq_domain` is not NULL and `timer_delete_sync` is called, and *returns*, what's the deal? Could the timer be active but `debug_check_no_obj_freed` thinks it's not? No, `debug_check_no_obj_freed` is pretty solid.
Okay, let's look at the commit history of `drivers/iommu/dma-iommu.c`. Time to search for commits with "timer," "free," or "fq" to see if there's been any related fixes in the past.
|
| 60/2 |
2026/08/09 23:31 |
tool |
git-log |
0m
Args: map[Count:20 MessageRegexps:[timer] PathPrefix:drivers/iommu/dma-iommu.c]
Results: map[Output:41cb08555c41 treewide, timers: Rename from_timer() to timer_container_of()
8fa7292fee5c treewide: Switch/rename to timer_delete[_sync]()
]
|
| 61/2 |
2026/08/09 23:31 |
llm |
expert |
0m
Model:
Tokens:
input: 89651
output: 43
thoughts: 34
Thoughts: Okay, here's my interpretation of that thought process, as if it were my own:
**Investigating DMA-IOMMU Queue Flushing Issues**
Alright, before diving too deep into this problem, I should probably do some groundwork. I need to get a better handle on the potential code areas involved with, what sounds like, a "flush queue" or "fq" operation within the DMA-IOMMU subsystem. Instead of jumping to conclusions, I'm going to leverage the power of the code itself. My instinct says to start by searching for relevant commit messages. Looking for commits mentioning either "fq" or "flush queue" specifically within the `drivers/iommu/dma-iommu.c` file will, hopefully, uncover any past fixes, related code changes, or even existing discussions that might illuminate the issue. It's a quick and efficient way to orient myself before I start digging into the actual implementation details. Hopefully, this'll save me some time. Let's see what turns up.
|
| 62/2 |
2026/08/09 23:31 |
tool |
git-log |
0m
Args: map[Count:20 MessageRegexps:[fq] PathPrefix:drivers/iommu/dma-iommu.c]
Results: map[Output:1c18a1212c77 iommu/dma: Always allow DMA-FQ when iommupt provides the iommu_domain
5e2ff240b31a iommu: Clear the freelist after iommu_put_pages_list()
9f5b681e2a3f iommu/dma: Use a large flush queue and timeout for shadow_on_flush
32d5bc8b09c7 iommu/dma: Allow a single FQ in addition to per-CPU FQs
87f60cc65d24 iommu/vt-d: Use put_pages_list
]
|
| 63/2 |
2026/08/09 23:31 |
llm |
expert |
0m
Model:
Tokens:
input: 90115
output: 26
thoughts: 25
Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience:
**Analyzing Commit 5e2ff240b31a with `git show`**
Alright, time to dive in. I need to take a look at the details of that commit, `5e2ff240b31a`. The best way to get a quick and comprehensive overview, naturally, is to use `git show`. I'm thinking I want to see the author, date, commit message, and, most importantly, the diff. Knowing the changes introduced will let me assess their impact. This should tell me everything I need to know about the modifications to the project. From there, I can decide whether I need to drill down deeper into related commits, or if this gives me enough context. I will be sure to interpret the output in the light of the project's overall architecture and design principles. Let's get to it.
|
| 64/2 |
2026/08/09 23:31 |
tool |
git-show |
0m
Args:
Results: map[Output:commit 5e2ff240b31a37226260587e33707efc3c41e451
Author: Jason Gunthorpe <jgg@ziepe.ca>
Date: Wed May 14 09:48:55 2025 -0300
iommu: Clear the freelist after iommu_put_pages_list()
The commit below reworked how iommu_put_pages_list() worked to not do
list_del() on every entry. This was done expecting all the callers to
already re-init the list so doing a per-item deletion is not
efficient.
It was missed that fq_ring_free_locked() re-uses its list after calling
iommu_put_pages_list() and so the leftover list reaches free'd struct
pages and will crash or WARN/BUG/etc.
Reinit the list to empty in fq_ring_free_locked() after calling
iommu_put_pages_list().
Audit to see if any other callers of iommu_put_pages_list() need the list
to be empty:
- iommu_dma_free_fq_single() and iommu_dma_free_fq_percpu() immediately
frees the memory
- iommu_v1_map_pages(), v1_free_pgtable(), domain_exit(),
riscv_iommu_map_pages() uses a stack variable which goes out of scope
- intel_iommu_tlb_sync() uses a gather in a iotlb_sync() callback, the
caller re-inits the gather
Fixes: 13f43d7cf3e0 ("iommu/pages: Formalize the freelist API")
Reported-by: Borah, Chaitanya Kumar <chaitanya.kumar.borah@intel.com>
Closes: https://lore.kernel.org/r/SJ1PR11MB61292CE72D7BE06B8810021CB997A@SJ1PR11MB6129.namprd11.prod.outlook.com
Tested-by: Borah, Chaitanya Kumar <chaitanya.kumar.borah@intel.com>
Signed-off-by: Jason Gunthorpe <jgg@nvidia.com>
Link: https://lore.kernel.org/r/0-v1-7d4dfa6140f7+11f04-iommu_freelist_init_jgg@nvidia.com
Signed-off-by: Joerg Roedel <jroedel@suse.de>
diff --git a/drivers/iommu/dma-iommu.c b/drivers/iommu/dma-iommu.c
index 0af1ab36283c..7d2b51a890c7 100644
--- a/drivers/iommu/dma-iommu.c
+++ b/drivers/iommu/dma-iommu.c
@@ -154,6 +154,8 @@ static void fq_ring_free_locked(struct iommu_dma_cookie *cookie, struct iova_fq
fq->entries[idx].iova_pfn,
fq->entries[idx].pages);
+ fq->entries[idx].freelist =
+ IOMMU_PAGES_LIST_INIT(fq->entries[idx].freelist);
fq->head = (fq->head + 1) & fq->mod_mask;
}
}
diff --git a/drivers/iommu/iommu-pages.c b/drivers/iommu/iommu-pages.c
index 4cc77fddfeeb..238c09e5166b 100644
--- a/drivers/iommu/iommu-pages.c
+++ b/drivers/iommu/iommu-pages.c
@@ -105,7 +105,9 @@ EXPORT_SYMBOL_GPL(iommu_free_pages);
* iommu_put_pages_list - free a list of pages.
* @list: The list of pages to be freed
*
- * Frees a list of pages allocated by iommu_alloc_pages_node_sz().
+ * Frees a list of pages allocated by iommu_alloc_pages_node_sz(). On return the
+ * passed list is invalid, the caller must use IOMMU_PAGES_LIST_INIT to reinit
+ * the list if it expects to use it again.
*/
void iommu_put_pages_list(struct iommu_pages_list *list)
{
]
|
| 65/2 |
2026/08/09 23:31 |
llm |
expert |
0m
Model:
Tokens:
input: 91569
output: 32
thoughts: 49
Thoughts: **Debugging a Suspect Bug**
Okay, so this isn't directly related to the specific bug we're tracking down. But we need to be thorough. The next logical step is to delve deeper into the code history. Let's not make any assumptions. I need to cast a wider net and see if any related commits might give us a clue. Right, I'm going to search the entire git log for commits that mention either `"iommu_dma_free_fq"` or `"iommu_put_dma_cookie"`. These keywords are likely related to the DMA cookie or the IOMMU's free function and might contain crucial insight into recent changes, related bug fixes, or even regressions. This search should provide a good overview of any recent alterations in this area. It will help reveal if the problem has been encountered before, and might possibly provide a workaround or lead to the root cause. This is the right approach. Let's see what turns up.
|
| 66/2 |
2026/08/09 23:31 |
tool |
git-log |
10m
Error:
Args: map[CodeRegexp:iommu_dma_free_fq Count:10]
Results:
|
| 67/2 |
2026/08/09 23:41 |
llm |
expert |
0m
Model:
Tokens:
input: 91892
output: 44
thoughts: 18
Thoughts: Okay, here's my summary, taking on the persona you requested:
**Refining the IOMMU Driver Path**
Right, so I need to be precise here. Thinking about this specific IOMMU driver directory, I'm going to make the path specification more granular. Let's not leave any room for ambiguity. The most logical and appropriate path prefix for this driver module is going to be `drivers/iommu/`. I'm confident that will keep things organized and ensure the system finds the correct files without any issues. This is the definitive location.
|
| 68/2 |
2026/08/09 23:41 |
tool |
git-log |
0m
Args: map[CodeRegexp:iommu_dma_free_fq Count:10 PathPrefix:drivers/iommu/]
Results: map[Output:32d5bc8b09c7 iommu/dma: Allow a single FQ in addition to per-CPU FQs
a17e3026bc4d iommu: Move flush queue data into iommu_dma_cookie
]
|
| 69/2 |
2026/08/09 23:41 |
llm |
expert |
0m
Model:
Tokens:
input: 92148
output: 26
thoughts: 17
Thoughts: Okay, let's dive into this. Here's my thought process on this task:
**Analyzing the Commit Hash**
Alright, the first thing that jumps out is that I need to analyze this commit hash: `a17e3026bc4d`. This is a unique identifier, and it's essential to understand its significance. My initial thought process is this:
* **Context is Key:** Where am I seeing this? What's the surrounding information? Is this from a version control system (like Git), a code review tool, a bug tracker, or something else entirely? Knowing the source context immediately shapes my approach. For example, knowing the environment where this hash originates helps me to pull the right artifacts to review and analyze.
* **Version Control Assumption:** Given the structure, I'm assuming this is a Git-style commit hash, though it's good to clarify. If it *is* Git, that opens the door to using familiar commands: `git show a17e3026bc4d`, `git log -p a17e3026bc4d`, `git diff a17e3026bc4d^ a17e3026bc4d`, etc. to explore the changes made.
* **Immediate Investigation:** My next step is to get the associated changes. I will need to get the changes to understand what this hash represents. I have to analyze the associated code or documentation for what this contains. I have to look into the context and the surrounding changes.
* **Potential Red Flags:** I need to be prepared to identify any potential red flags β unexpected modifications, security vulnerabilities, or performance bottlenecks. I need to be ready to understand the risks.
* **Documentation is Paramount:** This commit could link to documentation or a ticket that describes it. If that is available, I need to check the associated documentation or tickets to understand what is being changed and why.
From this point, I would get to work exploring the context and using the appropriate tools to get a handle on the commit.
|
| 70/2 |
2026/08/09 23:42 |
tool |
git-show |
0m
Args:
Results: map[Output:commit a17e3026bc4da9135ca9a42ec0b1fa67f95172e3
Author: Robin Murphy <robin.murphy@arm.com>
Date: Fri Dec 17 15:31:03 2021 +0000
iommu: Move flush queue data into iommu_dma_cookie
Complete the move into iommu-dma by refactoring the flush queues
themselves to belong to the DMA cookie rather than the IOVA domain.
The refactoring may as well extend to some minor cosmetic aspects
too, to help us stay one step ahead of the style police.
Signed-off-by: Robin Murphy <robin.murphy@arm.com>
Link: https://lore.kernel.org/r/24304722005bc6f144e2a1fdd865d1465722fc2e.1639753638.git.robin.murphy@arm.com
Signed-off-by: Joerg Roedel <jroedel@suse.de>
diff --git a/drivers/iommu/dma-iommu.c b/drivers/iommu/dma-iommu.c
index 96057bec4164..d85d54f2b549 100644
--- a/drivers/iommu/dma-iommu.c
+++ b/drivers/iommu/dma-iommu.c
@@ -9,9 +9,12 @@
*/
#include <linux/acpi_iort.h>
+#include <linux/atomic.h>
+#include <linux/crash_dump.h>
#include <linux/device.h>
-#include <linux/dma-map-ops.h>
+#include <linux/dma-direct.h>
#include <linux/dma-iommu.h>
+#include <linux/dma-map-ops.h>
#include <linux/gfp.h>
#include <linux/huge_mm.h>
#include <linux/iommu.h>
@@ -20,11 +23,10 @@
#include <linux/mm.h>
#include <linux/mutex.h>
#include <linux/pci.h>
-#include <linux/swiotlb.h>
#include <linux/scatterlist.h>
+#include <linux/spinlock.h>
+#include <linux/swiotlb.h>
#include <linux/vmalloc.h>
-#include <linux/crash_dump.h>
-#include <linux/dma-direct.h>
struct iommu_dma_msi_page {
struct list_head list;
@@ -41,7 +43,19 @@ struct iommu_dma_cookie {
enum iommu_dma_cookie_type type;
union {
/* Full allocator for IOMMU_DMA_IOVA_COOKIE */
- struct iova_domain iovad;
+ struct {
+ struct iova_domain iovad;
+
+ struct iova_fq __percpu *fq; /* Flush queue */
+ /* Number of TLB flushes that have been started */
+ atomic64_t fq_flush_start_cnt;
+ /* Number of TLB flushes that have been finished */
+ atomic64_t fq_flush_finish_cnt;
+ /* Timer to regularily empty the flush queues */
+ struct timer_list fq_timer;
+ /* 1 when timer is active, 0 when not */
+ atomic_t fq_timer_on;
+ };
/* Trivial linear page allocator for IOMMU_DMA_MSI_COOKIE */
dma_addr_t msi_iova;
};
@@ -64,6 +78,27 @@ static int __init iommu_dma_forcedac_setup(char *str)
}
early_param("iommu.forcedac", iommu_dma_forcedac_setup);
+/* Number of entries per flush queue */
+#define IOVA_FQ_SIZE 256
+
+/* Timeout (in ms) after which entries are flushed from the queue */
+#define IOVA_FQ_TIMEOUT 10
+
+/* Flush queue entry for deferred flushing */
+struct iova_fq_entry {
+ unsigned long iova_pfn;
+ unsigned long pages;
+ struct list_head freelist;
+ u64 counter; /* Flush counter when this entry was added */
+};
+
+/* Per-CPU flush queue structure */
+struct iova_fq {
+ struct iova_fq_entry entries[IOVA_FQ_SIZE];
+ unsigned int head, tail;
+ spinlock_t lock;
+};
+
#define fq_ring_for_each(i, fq) \
for ((i) = (fq)->head; (i) != (fq)->tail; (i) = ((i) + 1) % IOVA_FQ_SIZE)
@@ -73,9 +108,9 @@ static inline bool fq_full(struct iova_fq *fq)
return (((fq->tail + 1) % IOVA_FQ_SIZE) == fq->head);
}
-static inline unsigned fq_ring_add(struct iova_fq *fq)
+static inline unsigned int fq_ring_add(struct iova_fq *fq)
{
- unsigned idx = fq->tail;
+ unsigned int idx = fq->tail;
assert_spin_locked(&fq->lock);
@@ -84,10 +119,10 @@ static inline unsigned fq_ring_add(struct iova_fq *fq)
return idx;
}
-static void fq_ring_free(struct iova_domain *iovad, struct iova_fq *fq)
+static void fq_ring_free(struct iommu_dma_cookie *cookie, struct iova_fq *fq)
{
- u64 counter = atomic64_read(&iovad->fq_flush_finish_cnt);
- unsigned idx;
+ u64 counter = atomic64_read(&cookie->fq_flush_finish_cnt);
+ unsigned int idx;
assert_spin_locked(&fq->lock);
@@ -97,7 +132,7 @@ static void fq_ring_free(struct iova_domain *iovad, struct iova_fq *fq)
break;
put_pages_list(&fq->entries[idx].freelist);
- free_iova_fast(iovad,
+ free_iova_fast(&cookie->iovad,
fq->entries[idx].iova_pfn,
fq->entries[idx].pages);
@@ -105,50 +140,50 @@ static void fq_ring_free(struct iova_domain *iovad, struct iova_fq *fq)
}
}
-static void iova_domain_flush(struct iova_domain *iovad)
+static void fq_flush_iotlb(struct iommu_dma_cookie *cookie)
{
- atomic64_inc(&iovad->fq_flush_start_cnt);
- iovad->fq_domain->ops->flush_iotlb_all(iovad->fq_domain);
- atomic64_inc(&iovad->fq_flush_finish_cnt);
+ atomic64_inc(&cookie->fq_flush_start_cnt);
+ cookie->fq_domain->ops->flush_iotlb_all(cookie->fq_domain);
+ atomic64_inc(&cookie->fq_flush_finish_cnt);
}
static void fq_flush_timeout(struct timer_list *t)
{
- struct iova_domain *iovad = from_timer(iovad, t, fq_timer);
+ struct iommu_dma_cookie *cookie = from_timer(cookie, t, fq_timer);
int cpu;
- atomic_set(&iovad->fq_timer_on, 0);
- iova_domain_flush(iovad);
+ atomic_set(&cookie->fq_timer_on, 0);
+ fq_flush_iotlb(cookie);
for_each_possible_cpu(cpu) {
unsigned long flags;
struct iova_fq *fq;
- fq = per_cpu_ptr(iovad->fq, cpu);
+ fq = per_cpu_ptr(cookie->fq, cpu);
spin_lock_irqsave(&fq->lock, flags);
- fq_ring_free(iovad, fq);
+ fq_ring_free(cookie, fq);
spin_unlock_irqrestore(&fq->lock, flags);
}
}
-void queue_iova(struct iova_domain *iovad,
+static void queue_iova(struct iommu_dma_cookie *cookie,
unsigned long pfn, unsigned long pages,
struct list_head *freelist)
{
struct iova_fq *fq;
unsigned long flags;
- unsigned idx;
+ unsigned int idx;
/*
* Order against the IOMMU driver's pagetable update from unmapping
- * @pte, to guarantee that iova_domain_flush() observes that if called
+ * @pte, to guarantee that fq_flush_iotlb() observes that if called
* from a different CPU before we release the lock below. Full barrier
* so it also pairs with iommu_dma_init_fq() to avoid seeing partially
* written fq state here.
*/
smp_mb();
- fq = raw_cpu_ptr(iovad->fq);
+ fq = raw_cpu_ptr(cookie->fq);
spin_lock_irqsave(&fq->lock, flags);
/*
@@ -156,65 +191,66 @@ void queue_iova(struct iova_domain *iovad,
* flushed out on another CPU. This makes the fq_full() check below less
* likely to be true.
*/
- fq_ring_free(iovad, fq);
+ fq_ring_free(cookie, fq);
if (fq_full(fq)) {
- iova_domain_flush(iovad);
- fq_ring_free(iovad, fq);
+ fq_flush_iotlb(cookie);
+ fq_ring_free(cookie, fq);
}
idx = fq_ring_add(fq);
fq->entries[idx].iova_pfn = pfn;
fq->entries[idx].pages = pages;
- fq->entries[idx].counter = atomic64_read(&iovad->fq_flush_start_cnt);
+ fq->entries[idx].counter = atomic64_read(&cookie->fq_flush_start_cnt);
list_splice(freelist, &fq->entries[idx].freelist);
spin_unlock_irqrestore(&fq->lock, flags);
/* Avoid false sharing as much as possible. */
- if (!atomic_read(&iovad->fq_timer_on) &&
- !atomic_xchg(&iovad->fq_timer_on, 1))
- mod_timer(&iovad->fq_timer,
+ if (!atomic_read(&cookie->fq_timer_on) &&
+ !atomic_xchg(&cookie->fq_timer_on, 1))
+ mod_timer(&cookie->fq_timer,
jiffies + msecs_to_jiffies(IOVA_FQ_TIMEOUT));
}
-static void free_iova_flush_queue(struct iova_domain *iovad)
+static void iommu_dma_free_fq(struct iommu_dma_cookie *cookie)
{
int cpu, idx;
- if (!iovad->fq)
+ if (!cookie->fq)
return;
- del_timer_sync(&iovad->fq_timer);
- /*
- * This code runs when the iova_domain is being detroyed, so don't
- * bother to free iovas, just free any remaining pagetable pages.
- */
+ del_timer_sync(&cookie->fq_timer);
+ /* The IOVAs will be torn down separately, so just free our queued pages */
for_each_possible_cpu(cpu) {
- struct iova_fq *fq = per_cpu_ptr(iovad->fq, cpu);
+ struct iova_fq *fq = per_cpu_ptr(cookie->fq, cpu);
fq_ring_for_each(idx, fq)
put_pages_list(&fq->entries[idx].freelist);
}
- free_percpu(iovad->fq);
-
- iovad->fq = NULL;
- iovad->fq_domain = NULL;
+ free_percpu(cookie->fq);
}
-int init_iova_flush_queue(struct iova_domain *iovad, struct iommu_domain *fq_domain)
+/* sysfs updates are serialised by the mutex of the group owning @domain */
+int iommu_dma_init_fq(struct iommu_domain *domain)
{
+ struct iommu_dma_cookie *cookie = domain->iova_cookie;
struct iova_fq __percpu *queue;
int i, cpu;
- atomic64_set(&iovad->fq_flush_start_cnt, 0);
- atomic64_set(&iovad->fq_flush_finish_cnt, 0);
+ if (cookie->fq_domain)
+ return 0;
+
+ atomic64_set(&cookie->fq_flush_start_cnt, 0);
+ atomic64_set(&cookie->fq_flush_finish_cnt, 0);
queue = alloc_percpu(struct iova_fq);
- if (!queue)
+ if (!queue) {
+ pr_warn("iova flush queue initialization failed\n");
return -ENOMEM;
+ }
for_each_possible_cpu(cpu) {
struct iova_fq *fq = per_cpu_ptr(queue, cpu);
@@ -228,12 +264,16 @@ int init_iova_flush_queue(struct iova_domain *iovad, struct iommu_domain *fq_dom
INIT_LIST_HEAD(&fq->entries[i].freelist);
}
- iovad->fq_domain = fq_domain;
- iovad->fq = queue;
-
- timer_setup(&iovad->fq_timer, fq_flush_timeout, 0);
- atomic_set(&iovad->fq_timer_on, 0);
+ cookie->fq = queue;
+ timer_setup(&cookie->fq_timer, fq_flush_timeout, 0);
+ atomic_set(&cookie->fq_timer_on, 0);
+ /*
+ * Prevent incomplete fq state being observable. Pairs with path from
+ * __iommu_dma_unmap() through iommu_dma_free_iova() to queue_iova()
+ */
+ smp_wmb();
+ WRITE_ONCE(cookie->fq_domain, domain);
return 0;
}
@@ -318,7 +358,7 @@ void iommu_put_dma_cookie(struct iommu_domain *domain)
return;
if (cookie->type == IOMMU_DMA_IOVA_COOKIE && cookie->iovad.granule) {
- free_iova_flush_queue(&cookie->iovad);
+ iommu_dma_free_fq(cookie);
put_iova_domain(&cookie->iovad);
}
@@ -467,29 +507,6 @@ static bool dev_use_swiotlb(struct device *dev)
return IS_ENABLED(CONFIG_SWIOTLB) && dev_is_untrusted(dev);
}
-/* sysfs updates are serialised by the mutex of the group owning @domain */
-int iommu_dma_init_fq(struct iommu_domain *domain)
-{
- struct iommu_dma_cookie *cookie = domain->iova_cookie;
- int ret;
-
- if (cookie->fq_domain)
- return 0;
-
- ret = init_iova_flush_queue(&cookie->iovad, domain);
- if (ret) {
- pr_warn("iova flush queue initialization failed\n");
- return ret;
- }
- /*
- * Prevent incomplete iovad->fq being observable. Pairs with path from
- * __iommu_dma_unmap() through iommu_dma_free_iova() to queue_iova()
- */
- smp_wmb();
- WRITE_ONCE(cookie->fq_domain, domain);
- return 0;
-}
-
/**
* iommu_dma_init_domain - Initialise a DMA mapping domain
* @domain: IOMMU domain previously prepared by iommu_get_dma_cookie()
@@ -620,7 +637,7 @@ static void iommu_dma_free_iova(struct iommu_dma_cookie *cookie,
if (cookie->type == IOMMU_DMA_MSI_COOKIE)
cookie->msi_iova -= size;
else if (gather && gather->queued)
- queue_iova(iovad, iova_pfn(iovad, iova),
+ queue_iova(cookie, iova_pfn(iovad, iova),
size >> iova_shift(iovad),
&gather->freelist);
else
diff --git a/drivers/iommu/iova.c b/drivers/iommu/iova.c
index 081e5c0cf940..b28c9435b898 100644
--- a/drivers/iommu/iova.c
+++ b/drivers/iommu/iova.c
@@ -61,8 +61,6 @@ init_iova_domain(struct iova_domain *iovad, unsigned long granule,
iovad->start_pfn = start_pfn;
iovad->dma_32bit_pfn = 1UL << (32 - iova_shift(iovad));
iovad->max32_alloc_size = iovad->dma_32bit_pfn;
- iovad->fq_domain = NULL;
- iovad->fq = NULL;
iovad->anchor.pfn_lo = iovad->anchor.pfn_hi = IOVA_ANCHOR;
rb_link_node(&iovad->anchor.node, NULL, &iovad->rbroot.rb_node);
rb_insert_color(&iovad->anchor.node, &iovad->rbroot);
diff --git a/include/linux/iova.h b/include/linux/iova.h
index 072a09c06e8a..0abd48c5e622 100644
--- a/include/linux/iova.h
+++ b/include/linux/iova.h
@@ -12,9 +12,6 @@
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/rbtree.h>
-#include <linux/atomic.h>
-#include <linux/dma-mapping.h>
-#include <linux/iommu.h>
/* iova structure */
struct iova {
@@ -36,27 +33,6 @@ struct iova_rcache {
struct iova_cpu_rcache __percpu *cpu_rcaches;
};
-/* Number of entries per Flush Queue */
-#define IOVA_FQ_SIZE 256
-
-/* Timeout (in ms) after which entries are flushed from the Flush-Queue */
-#define IOVA_FQ_TIMEOUT 10
-
-/* Flush Queue entry for defered flushing */
-struct iova_fq_entry {
- unsigned long iova_pfn;
- unsigned long pages;
- struct list_head freelist;
- u64 counter; /* Flush counter when this entrie was added */
-};
-
-/* Per-CPU Flush Queue structure */
-struct iova_fq {
- struct iova_fq_entry entries[IOVA_FQ_SIZE];
- unsigned head, tail;
- spinlock_t lock;
-};
-
/* holds all the iova translations for a domain */
struct iova_domain {
spinlock_t iova_rbtree_lock; /* Lock to protect update of rbtree */
@@ -67,23 +43,9 @@ struct iova_domain {
unsigned long start_pfn; /* Lower limit for this domain */
unsigned long dma_32bit_pfn;
unsigned long max32_alloc_size; /* Size of last failed allocation */
- struct iova_fq __percpu *fq; /* Flush Queue */
-
- atomic64_t fq_flush_start_cnt; /* Number of TLB flushes that
- have been started */
-
- atomic64_t fq_flush_finish_cnt; /* Number of TLB flushes that
- have been finished */
-
struct iova anchor; /* rbtree lookup anchor */
- struct iova_rcache rcaches[IOVA_RANGE_CACHE_MAX_SIZE]; /* IOVA range caches */
-
- struct iommu_domain *fq_domain;
- struct timer_list fq_timer; /* Timer to regularily empty the
- flush-queues */
- atomic_t fq_timer_on; /* 1 when timer is active, 0
- when not */
+ struct iova_rcache rcaches[IOVA_RANGE_CACHE_MAX_SIZE]; /* IOVA range caches */
struct hlist_node cpuhp_dead;
};
@@ -133,16 +95,12 @@ struct iova *alloc_iova(struct iova_domain *iovad, unsigned long size,
bool size_aligned);
void free_iova_fast(struct iova_domain *iovad, unsigned long pfn,
unsigned long size);
-void queue_iova(struct iova_domain *iovad,
- unsigned long pfn, unsigned long pages,
- struct list_head *freelist);
unsigned long alloc_iova_fast(struct iova_domain *iovad, unsigned long size,
unsigned long limit_pfn, bool flush_rcache);
struct iova *reserve_iova(struct iova_domain *iovad, unsigned long pfn_lo,
unsigned long pfn_hi);
void init_iova_domain(struct iova_domain *iovad, unsigned long granule,
unsigned long start_pfn);
-int init_iova_flush_queue(struct iova_domain *iovad, struct iommu_domain *fq_domain);
struct iova *find_iova(struct iova_domain *iovad, unsigned long pfn);
void put_iova_domain(struct iova_domain *iovad);
#else
]
|
| 71/2 |
2026/08/09 23:42 |
llm |
expert |
0m
Model:
Tokens:
input: 98630
output: 45
thoughts: 2802
Thoughts: **Analysis of Potential IOMMU DMA Cookie Use-After-Free Vulnerability**
Okay, let's break this down. I'm looking at the `iommu_put_dma_cookie` function and the related `iommu_dma_free_fq`. The initial code review highlights a potential issue with the timer associated with the DMA cookie. My concern is that `iommu_dma_free_fq` might not be correctly handling all scenarios, specifically in relation to the `cookie->fq_domain`.
Here's the problem: The current implementation of `iommu_dma_free_fq` calls `timer_delete_sync` to de-activate the timer and then proceeds to free associated queues. However, it *doesn't* clear `cookie->fq_domain`. This means the `cookie->fq_domain` still has a valid pointer to a domain even after it has been freed.
Now, consider the DMA unmapping path. The function `iommu_dma_free_iova` calls `queue_iova` under certain conditions. Crucially, `queue_iova` *doesn't* check `cookie->fq_domain`. `gather->queued` is checked instead and `gather->queued` comes from `cookie->fq_domain` if set. This calls `mod_timer` which re-activates the timer, even though the resources related to the `cookie` are meant to be freed.
The critical issue is a race condition. If, after `iommu_dma_free_fq` is called (but before the cookie is fully freed), a DMA unmap operation is initiated, it could lead to `queue_iova` being called. This would trigger `mod_timer`. In turn, this would cause the timer to be re-activated, which would reference freed memory. This likely points to a use-after-free vulnerability!
The root cause is the lack of proper nulling out of `cookie->fq_domain` after the associated timer and queues are freed within `iommu_dma_free_fq`. If `cookie->fq_domain` isn't cleared, subsequent calls to `queue_iova` can cause a use-after-free because the timer may be re-activated after the related resources have been deallocated.
My next step would be to search kernel history for potential fixes that explicitly set `cookie->fq_domain = NULL` or apply related nulling in the `iommu_dma_free_fq` function. Or related patches that modify this section.
|
| 72/2 |
2026/08/09 23:42 |
tool |
git-log |
0m
Args: map[CodeRegexp:fq_domain Count:10 PathPrefix:drivers/iommu/dma-iommu.c]
Results: map[Output:433a76207dcf dma-mapping: Implement link/unlink ranges API
6aa63a4ec947 iommu: Sort out domain user data
32d5bc8b09c7 iommu/dma: Allow a single FQ in addition to per-CPU FQs
a17e3026bc4d iommu: Move flush queue data into iommu_dma_cookie
f7f07484542f iommu/iova: Move flush queue code to iommu-dma
649ad9835a37 iommu/iova: Squash flush_cb abstraction
452e69b58c28 iommu: Allow enabling non-strict mode dynamically
c208916fe6c7 iommu: Express DMA strictness via the domain type
7a7c5badf858 iommu: Indicate queued flushes via gather data
8d971243a9a7 iommu/dma: Remove redundant "!dev" checks
]
|
| 73/2 |
2026/08/09 23:42 |
llm |
expert |
0m
Model:
Tokens:
input: 102300
output: 45
thoughts: 4013
Thoughts: **Analyzing a Potential Use-After-Free in the IOMMU DMA Code**
Alright, let's break this down. We've got a slab-use-after-free issue, and the KASAN report points to `fq_flush_timeout`. The goal is to understand how a timer, associated with an IOMMU DMA cookie, remains active after the cookie itself should have been freed.
The initial thought was the simplest: maybe `fq_domain` was unexpectedly NULL, causing an early return from `iommu_dma_free_fq` and skipping `timer_delete_sync`. But, after thorough code review of `iommu_dma_free_fq` and related initialization paths, this is very unlikely. We know `iommu_dma_init_fq` is called, initializes the timer and sets `fq_domain`.
Then, we explored concurrency, but discarded the initial thought because the IOMMU domain is tied to an IOMMU group, so another thread can't reference it unless it belongs to the same group.
We then took a close look at the allocation and free stacks to see if there was some unusual order of events. These are linked with initialization and de-initialization of the domain. However, no unusual order of events.
The key is `fq_flush_timeout` being triggered when it shouldn't. The timer must have been queued at some point after `iommu_dma_free_fq` should have deleted it.
The function `iommu_dma_free_fq` calls `timer_delete_sync` on the associated `cookie->fq_timer` *if* the domain is valid. So it should be impossible for this timer to run after being deleted.
We went to `iommu_dma_free_iova` to check what calls `queue_iova`, and found a possible cause of the problem: concurrent DMA unmapping during device removal. If the device's driver is not fully stopped, there could be an asynchronous call to unmap DMA, which would go through `iommu_dma_free_iova` and call `queue_iova`.
Now, the main idea is: What if `iommu_dma_free_fq` *didn't* nullify the cookie's domain? If there's a race, the domain pointer may still be considered non-NULL *after* the free of the ring, but before the cookie is completely gone. In this case, `queue_iova` would be called (because `gather->queued` is set using `cookie->fq_domain`) and would cause a use-after-free.
So, the issue is likely that `iommu_dma_free_fq` *doesn't* set `cookie->fq_domain` to NULL. Consequently, a concurrent unmapping operation triggered after the free would attempt to re-queue the timer, but use freed data structures (`cookie->single_fq` or `cookie->percpu_fq`) within the cookie. The timer would be re-activated and subsequently trigger the KASAN use-after-free in `fq_flush_timeout`.
Now, we should look for commits or patches that make sure to set that `fq_domain` pointer to NULL!
|
| 74/2 |
2026/08/09 23:43 |
tool |
git-log |
0m
Args: map[CodeRegexp:fq_domain Count:10 PathPrefix:drivers/iommu/dma-iommu.c]
Results: map[Output:433a76207dcf dma-mapping: Implement link/unlink ranges API
6aa63a4ec947 iommu: Sort out domain user data
32d5bc8b09c7 iommu/dma: Allow a single FQ in addition to per-CPU FQs
a17e3026bc4d iommu: Move flush queue data into iommu_dma_cookie
f7f07484542f iommu/iova: Move flush queue code to iommu-dma
649ad9835a37 iommu/iova: Squash flush_cb abstraction
452e69b58c28 iommu: Allow enabling non-strict mode dynamically
c208916fe6c7 iommu: Express DMA strictness via the domain type
7a7c5badf858 iommu: Indicate queued flushes via gather data
8d971243a9a7 iommu/dma: Remove redundant "!dev" checks
]
|
| 75/2 |
2026/08/09 23:43 |
llm |
expert |
0m
Model:
Tokens:
input: 107335
output: 45
thoughts: 4013
Thoughts: **Analyzing a Potential Use-After-Free in the IOMMU DMA Code**
Alright, let's break this down. We've got a slab-use-after-free issue, and the KASAN report points to `fq_flush_timeout`. The goal is to understand how a timer, associated with an IOMMU DMA cookie, remains active after the cookie itself should have been freed.
The initial thought was the simplest: maybe `fq_domain` was unexpectedly NULL, causing an early return from `iommu_dma_free_fq` and skipping `timer_delete_sync`. But, after thorough code review of `iommu_dma_free_fq` and related initialization paths, this is very unlikely. We know `iommu_dma_init_fq` is called, initializes the timer and sets `fq_domain`.
Then, we explored concurrency, but discarded the initial thought because the IOMMU domain is tied to an IOMMU group, so another thread can't reference it unless it belongs to the same group.
We then took a close look at the allocation and free stacks to see if there was some unusual order of events. These are linked with initialization and de-initialization of the domain. However, no unusual order of events.
The key is `fq_flush_timeout` being triggered when it shouldn't. The timer must have been queued at some point after `iommu_dma_free_fq` should have deleted it.
The function `iommu_dma_free_fq` calls `timer_delete_sync` on the associated `cookie->fq_timer` *if* the domain is valid. So it should be impossible for this timer to run after being deleted.
We went to `iommu_dma_free_iova` to check what calls `queue_iova`, and found a possible cause of the problem: concurrent DMA unmapping during device removal. If the device's driver is not fully stopped, there could be an asynchronous call to unmap DMA, which would go through `iommu_dma_free_iova` and call `queue_iova`.
Now, the main idea is: What if `iommu_dma_free_fq` *didn't* nullify the cookie's domain? If there's a race, the domain pointer may still be considered non-NULL *after* the free of the ring, but before the cookie is completely gone. In this case, `queue_iova` would be called (because `gather->queued` is set using `cookie->fq_domain`) and would cause a use-after-free.
So, the issue is likely that `iommu_dma_free_fq` *doesn't* set `cookie->fq_domain` to NULL. Consequently, a concurrent unmapping operation triggered after the free would attempt to re-queue the timer, but use freed data structures (`cookie->single_fq` or `cookie->percpu_fq`) within the cookie. The timer would be re-activated and subsequently trigger the KASAN use-after-free in `fq_flush_timeout`.
Now, we should look for commits or patches that make sure to set that `fq_domain` pointer to NULL!
|
| 76/2 |
2026/08/09 23:43 |
tool |
git-log |
0m
Args: map[CodeRegexp:fq_domain Count:10 PathPrefix:drivers/iommu/dma-iommu.c]
Results: map[Output:433a76207dcf dma-mapping: Implement link/unlink ranges API
6aa63a4ec947 iommu: Sort out domain user data
32d5bc8b09c7 iommu/dma: Allow a single FQ in addition to per-CPU FQs
a17e3026bc4d iommu: Move flush queue data into iommu_dma_cookie
f7f07484542f iommu/iova: Move flush queue code to iommu-dma
649ad9835a37 iommu/iova: Squash flush_cb abstraction
452e69b58c28 iommu: Allow enabling non-strict mode dynamically
c208916fe6c7 iommu: Express DMA strictness via the domain type
7a7c5badf858 iommu: Indicate queued flushes via gather data
8d971243a9a7 iommu/dma: Remove redundant "!dev" checks
]
|
| 77/2 |
2026/08/09 23:43 |
llm |
expert |
0m
Model:
Tokens:
input: 112370
output: 45
thoughts: 4013
Thoughts: **Analyzing a Suspected IOMMU DMA Use-After-Free Bug**
Okay, so I'm diving into this IOMMU DMA issue, and here's the breakdown of my thinking: it looks like we've got a use-after-free situation, likely involving the `fq_timer` associated with the DMA cookie. I initially questioned if `fq_domain` being `NULL` might be the root cause, but that seems unlikely based on the code flow. The trace points to `fq_flush_timeout` β the timer handler β accessing memory after the DMA cookie should have been freed.
The allocation/free stacks give us important context here; an `iova` struct is allocated during domain initialization and then freed during domain destruction, and this is where the `cookie` lives. My current theory is this is the key. The access stack indicates a slab-use-after-free within `private_find_iova` and `fq_ring_free`, called within the timer handler. The timer expiring is key here, since that is when this is happening. The real question is, is there a timer re-queue happening?
The critical part is the potential re-activation of the `fq_timer` *after* the initial deletion. I walked through the code and I can eliminate a bunch of other scenarios. It seems that the timer deletion with `timer_delete_sync` is *not* the problem, because the check inside `iommu_dma_free_fq` protects against it. I also investigated if the timer was never activated, but I can eliminate that, because the timer is in the debug hash table.
The `iommu_dma_free_iova` function then is critical, as it is called by `__iommu_dma_unmap`, which can be called concurrently. This is the race condition. If a DMA unmap happens concurrently *after* the timer has been deleted, it can re-queue the timer. And it looks like the function `queue_iova` can call `mod_timer` to re-queue the timer. And `queue_iova` reads the `gather->queued` variable which, in turn, reads `cookie->fq_domain`.
Then the crucial point: if a device is being removed, there could be concurrent DMA unmapping calls, which might result from a device driver bug! Itβs possible that unmap calls are still happening, and the `queue_iova` can re-activate the timer. Critically, `iommu_dma_free_fq` *doesn't* nullify the `fq_domain` field! This means that if it's called *before* `queue_iova` by another device, then `queue_iova` will still see it and incorrectly re-queue the timer. This is the root cause: a missing `cookie->fq_domain = NULL` in `iommu_dma_free_fq`.
|
| 78/2 |
2026/08/09 23:43 |
tool |
git-log |
0m
Error:
Args: map[CodeRegexp:fq_domain Count:10 PathPrefix:drivers/iommu/dma-iommu.c]
|
| 79/2 |
2026/08/09 23:43 |
llm |
expert |
0m
Model:
Tokens:
input: 117195
output: 0
thoughts: 0
|
| 80/2 |
2026/08/09 23:43 |
llm |
expert |
0m
Model:
Tokens:
input: 117265
output: 15
thoughts: 752
Thoughts: **My Analysis of this KASAN Use-After-Free Bug**
Okay, so the system prompt just jogged my memory: I need to remember to use `set-results` to finalize my response. But before I do that, let me make absolutely sure I've got everything straight.
The good news is, this bug report is solid. It's self-consistent and, yes, actionable. Now, let's break down my understanding of the issue:
1. **Consistency: The KASAN Report**
* This is a KASAN use-after-free bug in `private_find_iova` at `drivers/iommu/iova.c:285`.
* The bad access is a read of 8 bytes at an offset of 32 bytes within a freed 40-byte `iova` region. This read corresponds to `pfn_lo` within the `struct iova`.
* The allocation stack clearly traces the creation of the `iova` struct: `iommu_setup_dma_ops` -> `reserve_iova` -> `alloc_iova_mem`.
* The free stack shows the `iova` structure is being deallocated during domain destruction: `iommu_domain_free` -> `iommu_put_dma_cookie` -> `put_iova_domain` -> `free_iova_mem`.
* The access stack tells the story of the use-after-free: It happens when the flush queue timer expires (`fq_flush_timeout` -> `fq_ring_free` -> `free_iova_fast` -> `free_iova` -> `private_find_iova`).
* The `<TASK>` stack trace is key; it pinpoints `kfree(cookie)` being called during `iommu_domain_free`. This triggered `debug_check_no_obj_freed` because the flush queue timer `cookie->fq_timer` was still active.
* `debug_check_no_obj_freed` printed a warning, which, through `printk`, went to `console_flush_all`.
* Crucially, inside `console_flush_all`, interrupts were enabled, and an APIC timer interrupt occurred on CPU 0.
* The APIC timer interrupt handler ran, and upon exit, softirqs were run, which in turn, ran the expired `fq_flush_timeout` timer on CPU 0.
* Since `put_iova_domain` had already run and freed all `iova` structures, `fq_flush_timeout` then accessed the freed `iova` structure, leading to the KASAN use-after-free.
2. **Actionability: The Root Cause**
* This is a highly actionable report, pointing to a race condition or lifecycle issue. The timer `cookie->fq_timer` is still active when `kfree(cookie)` is called. There's either improper synchronization or deletion of the timer, or a race condition where the timer is re-queued, or the timer runs after `iommu_dma_free_fq` has completed.
* Specifically, `iommu_dma_free_fq` calls `timer_delete_sync(&cookie->fq_timer)`, but doesn't set `cookie->fq_domain` to NULL. Given any concurrent or late unmaps, or if the timer is already pending/running, this creates obvious race conditions.
* The report provides all three crucial stack traces (access, allocation, and free), and they're all perfectly consistent, all relating to the same `iova` structure within the `iommu` subsystem.
Alright, with that confirmed, I'm comfortable calling `set-results` with `Actionable: true`.
|
| 81/2 |
2026/08/09 23:43 |
tool |
set-results |
0m
Args:
Results:
|
| 82/2 |
2026/08/09 23:43 |
llm |
expert |
0m
Model:
Tokens:
input: 118894
output: 848
thoughts: 13
Thoughts: Okay, here's the summary, rendered as your requested persona:
**Finalizing the Explanation**
Alright, let's get this analysis documented completely and accurately. I'm at the point where I'm crafting the final, comprehensive reply. I'm making sure to articulate all the nuances, assumptions, and critical points that underpin my findings. I need to make the explanation detailed and understandable for someone who already knows their stuff in this domain. This final response needs to clearly show the methodology and reasoning that led to the conclusion. Let's make it a masterpiece of clear, concise explanation.
|
| 83/1 |
2026/08/09 23:43 |
action |
format-explanation |
0m
Results: map[Explanation:An analysis of the KASAN use-after-free bug report has been conducted. The
report is **fully self-consistent, highly logical, and actionable**.
### Detailed Analysis of the Bug Report
1. **Object and Memory Layout Consistency**:
* The bug is a use-after-free read of size 8 at offset 32 bytes inside a
freed 40-byte region belonging to the `iommu_iova` cache.
* In `drivers/iommu/iova.c`, `struct iova` has a size of 40 bytes. Its fields
are:
* `node` (`struct rb_node`): offset 0, size 24 bytes.
* `pfn_hi` (`unsigned long`): offset 24, size 8 bytes.
* `pfn_lo` (`unsigned long`): offset 32, size 8 bytes.
* The trapping instruction is `if (pfn < iova->pfn_lo)` in
`private_find_iova` at line 285, which perfectly matches the read of size 8
at offset 32 bytes.
2. **Allocation and Free Stack Consistency**:
* **Allocation**: The `iova` structure was allocated during system
initialization via `iommu_setup_dma_ops` -> `reserve_iova` ->
`alloc_iova_mem`.
* **Free**: The `iova` structure was freed during device removal and domain
destruction via `iommu_domain_free` -> `iommu_put_dma_cookie` ->
`put_iova_domain` -> `free_iova_mem`.
* Both stacks are perfectly consistent and belong to the same `iommu`
subsystem and `iova` object type.
3. **Root Cause of the Use-After-Free**:
* The `<TASK>` stack trace shows that `iommu_domain_free` was in the process
of destroying the domain. It called `iommu_put_dma_cookie`, which called
`iommu_dma_free_fq` (to delete the flush queue timer) and `put_iova_domain`
(which freed all `iova` structures).
* Finally, `iommu_put_dma_cookie` called `kfree(cookie)`.
* Inside `kfree(cookie)`, the debugobjects subsystem
(`debug_check_no_obj_freed`) detected that the flush queue timer
`cookie->fq_timer` was still in the `ODEBUG_STATE_ACTIVE` state
(active/pending).
* To report this, `debug_check_no_obj_freed` printed a warning via `printk`
-> `console_flush_all`.
* Inside `console_flush_all`, interrupts were enabled, allowing an APIC timer
interrupt to occur on CPU 0.
* Upon exit from the interrupt, softirqs were processed, invoking the
expired/pending `fq_flush_timeout` timer handler on CPU 0.
* `fq_flush_timeout` called `fq_ring_free` -> `free_iova_fast` -> `free_iova`
-> `private_find_iova`.
* Since `put_iova_domain` had already completed and freed all `iova`
structures, `private_find_iova` accessed the freed `iova` structure,
triggering the KASAN use-after-free.
### Conclusion
The bug report is **actionable** because it contains all 3 required stack traces
(access, allocation, and free) which are completely consistent and reveal a
clear lifecycle/synchronization bug where the flush queue timer
`cookie->fq_timer` is not properly deactivated or is re-queued/run after the
domain's `iova` structures have been freed.]
|