All of lore.kernel.org
 help / color / mirror / Atom feed
* [BUG] iommu: KASAN slab-use-after-free in iommufd_auto_response_faults
@ 2026-07-07  6:12 Peiyang He
  2026-07-07 10:36 ` Peiyang He
                   ` (3 more replies)
  0 siblings, 4 replies; 18+ messages in thread
From: Peiyang He @ 2026-07-07  6:12 UTC (permalink / raw)
  To: jgg, kevin.tian, iommu
  Cc: linux-kernel, syzkaller, baolu.lu, nicolinc, joro, will,
	robin.murphy, dwmw2, suravee.suthikulpanit, vasant.hegde,
	yi.l.liu, jpb

Hello Linux kernel developers and maintainers,

We found a KASAN slab-use-after-free bug in iommufd_auto_response_faults when fuzzing with Syzkaller.

After our digging, we believe the root cause is that the same iopf_group can
remain linked from the IOMMUFD fault delivery/response queues while the generic
IOPF queue removal path frees it during HWPT detach.

Kernel version: commit 8cd9520d35a6c38db6567e97dd93b1f11f185dc6 (tag v7.1).
And we believe the bug is also possible in the current mainline.
Note that the code line numbers in the following analysis are based on this kernel version.

Relevant kernel config: (the complete config is available in https://drive.google.com/file/d/12oVjpfA3-PnZGMI6afFT6Ik33jXDcIn9/view?usp=sharing)

CONFIG_IOMMU_SUPPORT=y
CONFIG_IOMMU_API=y
CONFIG_IOMMU_IOPF=y
CONFIG_IOMMU_SVA=y
CONFIG_IOMMUFD=y
CONFIG_IOMMUFD_TEST=y
CONFIG_FAULT_INJECTION=y
CONFIG_KASAN=y
CONFIG_KASAN_GENERIC=y
CONFIG_KASAN_INLINE=y
CONFIG_KASAN_STACK=y
CONFIG_KASAN_VMALLOC=y


================
Syzkaller report
================

BUG: KASAN: slab-use-after-free in iommufd_auto_response_faults+0x66f/0x710 drivers/iommu/iommufd/eventq.c:33
Read of size 8 at addr ffff888047e6f0c0 by task syz.2.3/9340

CPU: 0 UID: 0 PID: 9340 Comm: syz.2.3 Not tainted 7.1.0 #3 PREEMPT(full) 
Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
Call Trace:
 <TASK>
 __dump_stack lib/dump_stack.c:94 [inline]
 dump_stack_lvl+0x10e/0x1f0 lib/dump_stack.c:120
 print_address_description mm/kasan/report.c:378 [inline]
 print_report+0xf7/0x600 mm/kasan/report.c:482
 kasan_report+0xe4/0x120 mm/kasan/report.c:595
 iommufd_auto_response_faults+0x66f/0x710 drivers/iommu/iommufd/eventq.c:33
 iommufd_hwpt_detach_device drivers/iommu/iommufd/device.c:557 [inline]
 iommufd_hw_pagetable_detach+0x399/0x6a0 drivers/iommu/iommufd/device.c:706
 iommufd_device_detach+0x1c/0x70 drivers/iommu/iommufd/device.c:1059
 iommufd_selftest_destroy+0x75/0xf0 drivers/iommu/iommufd/selftest.c:1920
 iommufd_fops_release+0x1aa/0x3f0 drivers/iommu/iommufd/main.c:361
 __fput+0x402/0xb80 fs/file_table.c:510
 task_work_run+0x150/0x240 kernel/task_work.c:233
 resume_user_mode_work include/linux/resume_user_mode.h:50 [inline]
 __exit_to_user_mode_loop kernel/entry/common.c:67 [inline]
 exit_to_user_mode_loop+0x107/0x4e0 kernel/entry/common.c:98
 __exit_to_user_mode_prepare include/linux/irq-entry-common.h:207 [inline]
 syscall_exit_to_user_mode_prepare include/linux/irq-entry-common.h:230 [inline]
 syscall_exit_to_user_mode include/linux/entry-common.h:318 [inline]
 do_syscall_64+0x629/0x800 arch/x86/entry/syscall_64.c:100
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7fa0621a788d
Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 90 f3 0f 1e fa 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 b0 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007ffe5bc4d088 EFLAGS: 00000246 ORIG_RAX: 00000000000001b4
RAX: 0000000000000000 RBX: 00007ffe5bc4d158 RCX: 00007fa0621a788d
RDX: 0000000000000000 RSI: 000000000000001e RDI: 0000000000000003
RBP: 00007fa062437da0 R08: 0000000000000001 R09: 000000216224f34c
R10: 0000001b32020000 R11: 0000000000000246 R12: 00007fa062435fac
R13: 00007fa062435fa0 R14: 000000000002c4fa R15: 000000000002c458
 </TASK>

Allocated by task 9341:
 kasan_save_stack+0x33/0x60 mm/kasan/common.c:57
 kasan_save_track+0x14/0x30 mm/kasan/common.c:78
 poison_kmalloc_redzone mm/kasan/common.c:398 [inline]
 __kasan_kmalloc+0xaa/0xb0 mm/kasan/common.c:415
 kmalloc_noprof include/linux/slab.h:950 [inline]
 kzalloc_noprof include/linux/slab.h:1188 [inline]
 iopf_group_alloc drivers/iommu/io-pgfault.c:88 [inline]
 iommu_report_device_fault+0x480/0x1b00 drivers/iommu/io-pgfault.c:252
 iommufd_test_trigger_iopf drivers/iommu/iommufd/selftest.c:1756 [inline]
 iommufd_test+0x2868/0x6190 drivers/iommu/iommufd/selftest.c:2128
 iommufd_fops_ioctl+0x367/0x540 drivers/iommu/iommufd/main.c:533
 vfs_ioctl fs/ioctl.c:51 [inline]
 __do_sys_ioctl fs/ioctl.c:597 [inline]
 __se_sys_ioctl fs/ioctl.c:583 [inline]
 __x64_sys_ioctl+0x18e/0x210 fs/ioctl.c:583
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0x116/0x800 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f

Freed by task 9340:
 kasan_save_stack+0x33/0x60 mm/kasan/common.c:57
 kasan_save_track+0x14/0x30 mm/kasan/common.c:78
 kasan_save_free_info+0x3b/0x60 mm/kasan/generic.c:584
 poison_slab_object mm/kasan/common.c:253 [inline]
 __kasan_slab_free+0x5f/0x80 mm/kasan/common.c:285
 kasan_slab_free include/linux/kasan.h:235 [inline]
 slab_free_hook mm/slub.c:2689 [inline]
 slab_free mm/slub.c:6251 [inline]
 kfree+0x301/0x6e0 mm/slub.c:6566
 iopf_free_group drivers/iommu/io-pgfault.c:58 [inline]
 iopf_queue_remove_device+0x573/0x990 drivers/iommu/io-pgfault.c:481
 mock_dev_disable_iopf drivers/iommu/iommufd/selftest.c:594 [inline]
 mock_dev_disable_iopf drivers/iommu/iommufd/selftest.c:584 [inline]
 mock_domain_nop_attach+0x443/0x510 drivers/iommu/iommufd/selftest.c:244
 __iommu_attach_device drivers/iommu/iommu.c:2172 [inline]
 __iommu_device_set_domain+0x1d3/0x470 drivers/iommu/iommu.c:2426
 __iommu_group_set_domain_internal+0x1aa/0x460 drivers/iommu/iommu.c:2498
 __iommu_group_set_domain_nofail drivers/iommu/iommu.c:157 [inline]
 __iommu_group_set_core_domain+0x9c/0xf0 drivers/iommu/iommu.c:2161
 iommu_detach_group_handle+0x27/0x40 drivers/iommu/iommu.c:3955
 iommufd_hwpt_detach_device drivers/iommu/iommufd/device.c:553 [inline]
 iommufd_hw_pagetable_detach+0x387/0x6a0 drivers/iommu/iommufd/device.c:706
 iommufd_device_detach+0x1c/0x70 drivers/iommu/iommufd/device.c:1059
 iommufd_selftest_destroy+0x75/0xf0 drivers/iommu/iommufd/selftest.c:1920
 iommufd_fops_release+0x1aa/0x3f0 drivers/iommu/iommufd/main.c:361
 __fput+0x402/0xb80 fs/file_table.c:510
 task_work_run+0x150/0x240 kernel/task_work.c:233
 resume_user_mode_work include/linux/resume_user_mode.h:50 [inline]
 __exit_to_user_mode_loop kernel/entry/common.c:67 [inline]
 exit_to_user_mode_loop+0x107/0x4e0 kernel/entry/common.c:98
 __exit_to_user_mode_prepare include/linux/irq-entry-common.h:207 [inline]
 syscall_exit_to_user_mode_prepare include/linux/irq-entry-common.h:230 [inline]
 syscall_exit_to_user_mode include/linux/entry-common.h:318 [inline]
 do_syscall_64+0x629/0x800 arch/x86/entry/syscall_64.c:100
 entry_SYSCALL_64_after_hwframe+0x77/0x7f

The buggy address belongs to the object at ffff888047e6f000
 which belongs to the cache kmalloc-256 of size 256
The buggy address is located 192 bytes inside of
 freed 256-byte region [ffff888047e6f000, ffff888047e6f100)

The buggy address belongs to the physical page:
page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x47e6e
head: order:1 mapcount:0 entire_mapcount:0 nr_pages_mapped:0 pincount:0
flags: 0x4fff00000000040(head|node=1|zone=1|lastcpupid=0x7ff)
page_type: f5(slab)
raw: 04fff00000000040 ffff888015c41b40 dead000000000100 dead000000000122
raw: 0000000000000000 0000000800100010 00000000f5000000 0000000000000000
head: 04fff00000000040 ffff888015c41b40 dead000000000100 dead000000000122
head: 0000000000000000 0000000800100010 00000000f5000000 0000000000000000
head: 04fff00000000001 ffffffffffffff81 00000000ffffffff 00000000ffffffff
head: ffffffffffffffff 0000000000000000 00000000ffffffff 0000000000000002
page dumped because: kasan: bad access detected
page_owner tracks the page as allocated
page last allocated via order 1, migratetype Unmovable, gfp_mask 0xd20c0(__GFP_IO|__GFP_FS|__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP|__GFP_NOMEMALLOC), pid 8164, tgid 8164 (syz-executor), ts 181531236805, free_ts 173068828921
 set_page_owner include/linux/page_owner.h:32 [inline]
 post_alloc_hook+0xfc/0x120 mm/page_alloc.c:1853
 prep_new_page mm/page_alloc.c:1861 [inline]
 get_page_from_freelist+0x75b/0x3220 mm/page_alloc.c:3941
 __alloc_frozen_pages_noprof+0x27e/0x2b00 mm/page_alloc.c:5221
 alloc_slab_page mm/slub.c:3278 [inline]
 allocate_slab mm/slub.c:3467 [inline]
 new_slab+0xa6/0x670 mm/slub.c:3525
 refill_objects+0x278/0x420 mm/slub.c:7272
 refill_sheaf mm/slub.c:2816 [inline]
 __pcs_replace_empty_main+0x2ed/0x640 mm/slub.c:4652
 alloc_from_pcs mm/slub.c:4750 [inline]
 slab_alloc_node mm/slub.c:4884 [inline]
 __do_kmalloc_node mm/slub.c:5295 [inline]
 __kmalloc_noprof+0x68d/0x850 mm/slub.c:5308
 kmalloc_noprof include/linux/slab.h:954 [inline]
 kzalloc_noprof include/linux/slab.h:1188 [inline]
 fib_create_info+0x5c3/0x4680 net/ipv4/fib_semantics.c:1400
 fib_table_insert+0x177/0x1c40 net/ipv4/fib_trie.c:1212
 fib_magic+0x4d4/0x5c0 net/ipv4/fib_frontend.c:1134
 fib_add_ifaddr+0x4d2/0x580 net/ipv4/fib_frontend.c:1178
 fib_netdev_event+0x3cf/0x710 net/ipv4/fib_frontend.c:1516
 notifier_call_chain+0xbc/0x430 kernel/notifier.c:85
 call_netdevice_notifiers_info+0xbe/0x110 net/core/dev.c:2249
 call_netdevice_notifiers_extack net/core/dev.c:2287 [inline]
 call_netdevice_notifiers net/core/dev.c:2301 [inline]
 __dev_notify_flags+0x12c/0x2e0 net/core/dev.c:9797
 netif_change_flags+0x108/0x160 net/core/dev.c:9826
page last free pid 8177 tgid 8177 stack trace:
 reset_page_owner include/linux/page_owner.h:25 [inline]
 __free_pages_prepare mm/page_alloc.c:1397 [inline]
 __free_frozen_pages+0x763/0xfc0 mm/page_alloc.c:2938
 qlink_free mm/kasan/quarantine.c:163 [inline]
 qlist_free_all+0x47/0xf0 mm/kasan/quarantine.c:179
 kasan_quarantine_reduce+0x195/0x1e0 mm/kasan/quarantine.c:286
 __kasan_slab_alloc+0x69/0x90 mm/kasan/common.c:350
 kasan_slab_alloc include/linux/kasan.h:253 [inline]
 slab_post_alloc_hook mm/slub.c:4570 [inline]
 slab_alloc_node mm/slub.c:4899 [inline]
 kmem_cache_alloc_noprof+0x2ff/0x6f0 mm/slub.c:4906
 alloc_filename fs/namei.c:142 [inline]
 do_getname+0x35/0x390 fs/namei.c:182
 class_filename_uflags_constructor include/linux/fs.h:2556 [inline]
 do_faccessat+0x111/0xc10 fs/open.c:486
 __do_sys_faccessat2 fs/open.c:542 [inline]
 __se_sys_faccessat2 fs/open.c:539 [inline]
 __x64_sys_faccessat2+0x96/0x100 fs/open.c:539
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0x116/0x800 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f

Memory state around the buggy address:
 ffff888047e6ef80: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
 ffff888047e6f000: fa fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
>ffff888047e6f080: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
                                           ^
 ffff888047e6f100: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
 ffff888047e6f180: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
==================================================================

<<<<<<<<<<<<<<< tail report >>>>>>>>>>>>>>>

The complete log is available in https://drive.google.com/file/d/1lvOOQPeVBrr9WP5Mkfj0vIywDzoKjX9a/view?usp=sharing

==========
Root cause
==========

G = a iopf_group allocated by iommu_report_device_fault().
F = a iommufd_fault bound to the hardware page table.
H = a iommufd_hw_pagetable being detached.
M = a mock_dev used by the iommufd selftest path.

TLDR:

The detach path frees G in iopf_queue_remove_device() while detaching the mock
device. The same thread then continues the same iommufd_hwpt_detach_device()
call and dereferences G AGAIN from IOMMUFD's fault state in
iommufd_auto_response_faults().

The precondition is that a fault-capable HWPT has an outstanding fault that was
delivered to IOMMUFD but has not yet been responded to by userspace.
Once that state exists, detach deterministically exposes the stale ownership:
the generic IOPF removal path frees G without first removing it from IOMMUFD's
F->common.deliver list or F->response xarray.


Detailed call stack:

iommufd selftest injects an IOPF for M
--> iommufd_test() /* case IOMMU_TEST_OP_TRIGGER_IOPF */
    drivers/iommu/iommufd/selftest.c:2061
    --> iommu_report_device_fault(M, ...)
        drivers/iommu/io-pgfault.c:214
        --> allocate G
            drivers/iommu/io-pgfault.c:252
        --> put G on the generic pending list via G->pending_node
            drivers/iommu/io-pgfault.c:110
        --> call group->attach_handle->domain->iopf_handler(G)
            drivers/iommu/io-pgfault.c:262
            --> iommufd_fault_iopf_handler(G)
                drivers/iommu/iommufd/eventq.c:456
                --> queue G to F->common.deliver via G->node
                drivers/iommu/iommufd/eventq.c:465

After this synchronous fault injection returns, the same G is reachable from:
1. generic fault_param->faults via G->pending_node
2. IOMMUFD F->common.deliver via G->node, or F->response as an xarray value

close/destroy iommufd object
--> iommufd_fops_release()
    drivers/iommu/iommufd/main.c:325
    --> iommufd_selftest_destroy()
        drivers/iommu/iommufd/selftest.c:1914
        --> iommufd_device_detach()
            drivers/iommu/iommufd/device.c:1055
            --> iommufd_hw_pagetable_detach()
                drivers/iommu/iommufd/device.c:687
                --> iommufd_hwpt_detach_device(H, M, IOMMU_NO_PASID)
                    drivers/iommu/iommufd/device.c:545
                    --> handle = iommufd_device_get_attach_handle(...)
                        drivers/iommu/iommufd/device.c:551
                    --> iommu_detach_group_handle(H->domain, M->group)
                        drivers/iommu/iommufd/device.c:553

                        /* Detach switches the mock device away from H. */
                        --> mock_domain_nop_attach()
                            drivers/iommu/iommufd/selftest.c:212
                            --> mock_dev_disable_iopf(M, H->domain)
                                drivers/iommu/iommufd/selftest.c:584
                                --> iopf_queue_remove_device(..., M)
                                    drivers/iommu/io-pgfault.c:450
                                    --> list_for_each_entry_safe(G, ...)
                                        over fault_param->faults
                                        drivers/iommu/io-pgfault.c:471
                                    --> list_del_init(&G->pending_node)
                                        drivers/iommu/io-pgfault.c:480
                                    --> iopf_free_group(G)
                                        drivers/iommu/io-pgfault.c:481
                                        /* G is freed here. */

                    /* The same iommufd_hwpt_detach_device() invocation. */
                    --> iommufd_auto_response_faults(H, handle)
                        drivers/iommu/iommufd/eventq.c:19
                        --> list_for_each_entry_safe(G, next,
                                                     &F->common.deliver, node)
                            drivers/iommu/iommufd/eventq.c:33
                        --> if (G->attach_handle != &handle->handle)
                            drivers/iommu/iommufd/eventq.c:34
                            --> KASAN: use-after-free, because G was freed by
                                iopf_queue_remove_device() but is still linked
                                from F->common.deliver.

                        Or, if G has already moved to F->response:
                        --> xa_for_each(&F->response, index, G)
                            drivers/iommu/iommufd/eventq.c:46
                        --> if (G->attach_handle != &handle->handle)
                            drivers/iommu/iommufd/eventq.c:47
                            --> same stale G pointer can be dereferenced from
                                the xarray.

The analysis above goes through the iommufd selftest mock device (M). We
have not reproduced this on real Intel/AMD hardware, but the same lifetime
issue is not specific to the mock driver: Intel and AMD both have real detach
paths that can reach the very same iopf_queue_remove_device(). This is exactly
what iommufd_hwpt_detach_device() triggers, via iommu_detach_group_handle() /
iommu_detach_device_pasid(), right before it calls iommufd_auto_response_faults().

The following is a static code-path analysis.

iommu_detach_group_handle() (RID-level detach) dispatches through the
blocking domain's attach_dev op, like mock_domain_nop_attach() in the
above analysis. iommu_detach_device_pasid() (PASID-level detach) is different:
it dispatches through the blocking domain's set_dev_pasid op instead
(drivers/iommu/iommu.c:3815 -> drivers/iommu/iommu.c:3553).

Intel (drivers/iommu/intel/) reaches iopf_queue_remove_device() on both paths:
  blocking_domain_attach_dev()       [attach_dev, RID]       iommu.c:2755
  blocking_domain_set_dev_pasid()    [set_dev_pasid, PASID]  iommu.c:3557
  --> iopf_for_domain_remove()                               iommu.h:1263
  --> intel_iommu_disable_iopf()                             iommu.c:3471
  --> iopf_queue_remove_device()                             iommu.c:3483

AMD (drivers/iommu/amd/): the evidence here only covers the RID/group-detach
path.
  blocked_domain_attach_device()     [attach_dev, RID]       iommu.c:2883
  --> detach_device()                                        iommu.c:2398
  --> amd_iommu_iopf_remove_device()                         ppr.c:270
  --> iopf_queue_remove_device()                             ppr.c:273
AMD's PASID path, blocked_domain_set_dev_pasid() (iommu.c:2900), only calls
amd_iommu_remove_dev_pasid() and does not reach iopf_queue_remove_device().

====
Note
====
LLM assistance was used when analyzing the root cause and writing this report.
But we have made our best effort to ensure the description and analysis in this
report are accurate and correct.

Thanks,
Peiyang

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [BUG] iommu: KASAN slab-use-after-free in iommufd_auto_response_faults
  2026-07-07  6:12 [BUG] iommu: KASAN slab-use-after-free in iommufd_auto_response_faults Peiyang He
@ 2026-07-07 10:36 ` Peiyang He
  2026-07-08 14:38   ` Ankit Soni
  2026-07-13  6:10 ` Tian, Kevin
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 18+ messages in thread
From: Peiyang He @ 2026-07-07 10:36 UTC (permalink / raw)
  To: jgg, kevin.tian, iommu
  Cc: linux-kernel, syzkaller, baolu.lu, nicolinc, joro, will,
	robin.murphy, dwmw2, suravee.suthikulpanit, vasant.hegde,
	yi.l.liu, jpb

> The analysis above goes through the iommufd selftest mock device (M). We
> have not reproduced this on real Intel/AMD hardware, but the same lifetime
> issue is not specific to the mock driver: Intel and AMD both have real detach
> paths that can reach the very same iopf_queue_remove_device(). This is exactly
> what iommufd_hwpt_detach_device() triggers, via iommu_detach_group_handle() /
> iommu_detach_device_pasid(), right before it calls iommufd_auto_response_faults().
> 
> The following is a static code-path analysis.
> 
> iommu_detach_group_handle() (RID-level detach) dispatches through the
> blocking domain's attach_dev op, like mock_domain_nop_attach() in the
> above analysis. iommu_detach_device_pasid() (PASID-level detach) is different:
> it dispatches through the blocking domain's set_dev_pasid op instead
> (drivers/iommu/iommu.c:3815 -> drivers/iommu/iommu.c:3553).
> 
> Intel (drivers/iommu/intel/) reaches iopf_queue_remove_device() on both paths:
>   blocking_domain_attach_dev()       [attach_dev, RID]       iommu.c:2755
>   blocking_domain_set_dev_pasid()    [set_dev_pasid, PASID]  iommu.c:3557
>   --> iopf_for_domain_remove()                               iommu.h:1263
>   --> intel_iommu_disable_iopf()                             iommu.c:3471
>   --> iopf_queue_remove_device()                             iommu.c:3483
> 
> AMD (drivers/iommu/amd/): the evidence here only covers the RID/group-detach
> path.
>   blocked_domain_attach_device()     [attach_dev, RID]       iommu.c:2883
>   --> detach_device()                                        iommu.c:2398
>   --> amd_iommu_iopf_remove_device()                         ppr.c:270
>   --> iopf_queue_remove_device()                             ppr.c:273
> AMD's PASID path, blocked_domain_set_dev_pasid() (iommu.c:2900), only calls
> amd_iommu_remove_dev_pasid() and does not reach iopf_queue_remove_device().

Hello Linux kernel developers and maintainers,

After further analysis of the code, we think that this UAF should not be feasible on AMD devices,
but still feasible on Intel devices.

===
AMD
===

We found no path by which a real AMD PPR fault can land in an IOMMUFD hw_pagetable's
fault->common.deliver or fault->response.

A real AMD PPR fault is always PASID-valid: iommu_call_iopf_notifier(), the only
iommu_report_device_fault() call site under drivers/iommu/amd/, rejects PASID 0 and always sets
IOMMU_FAULT_PAGE_REQUEST_PASID_VALID (drivers/iommu/amd/ppr.c:107,140-148).

find_fault_handler() (drivers/iommu/io-pgfault.c:118) resolves a PASID-valid fault only via a
per-PASID attach_handle, or a fallback to a RID-level NESTED domain gated by ops->user_pasid_table.
AMD does not set that flag (only arm-smmu-v3.c:4389 and the iommufd selftest mock, selftest.c:861,
do), so a failed per-PASID lookup is simply aborted on AMD, never delivered to any RID-level domain.

The per-PASID lookup cannot succeed toward an IOMMUFD HWPT on AMD, because the
AMD domains that IOMMUFD can allocate as HWPTs do not implement .set_dev_pasid:
amdv1_ops (iommu.c:2663), amdv2_ops (iommu.c:2741), and nested_domain_ops
(drivers/iommu/amd/nested.c:291) all lack it. Therefore, if userspace tries to
attach one of those IOMMUFD HWPT domains to a PASID, iommu_attach_device_pasid()
rejects it with -EOPNOTSUPP at the !domain->ops->set_dev_pasid check
(drivers/iommu/iommu.c:3625,3641-3644). The AMD nested RID attach path is not a
substitute here either: nested_attach_device() refuses attachment when PASID is
enabled (WARN_ON(dev_data->pasid_enabled), nested.c:244).

AMD does have a domain with .set_dev_pasid, amd_sva_domain_ops
(drivers/iommu/amd/pasid.c:177), but that is the kernel SVA domain path, not an
IOMMUFD HWPT. The SVA allocation path installs iommu_sva_iopf_handler()
(drivers/iommu/iommu-sva.c:298,327), not iommufd_fault_iopf_handler(), so faults
delivered there are not linked into an iommufd_fault object's deliver/response
queues.

=====
Intel
=====

Intel's PRQ descriptor carries a real, hardware-set pasid_present bit: intel_prq_report()
(drivers/iommu/intel/prq.c:175) only sets IOMMU_FAULT_PAGE_REQUEST_PASID_VALID when
desc->pasid_present is true, so a non-PASID PRI request reaches find_fault_handler()'s RID
(IOMMU_NO_PASID) lookup directly -- a RID-attached fault-capable HWPT can receive it.

Unlike AMD, Intel's IOMMUFD-attachable domains do implement .set_dev_pasid: intel_fs_paging_domain_ops
and intel_ss_paging_domain_ops (drivers/iommu/intel/iommu.c:3894,3905) and intel_nested_domain_ops
(drivers/iommu/intel/nested.c:190) all set it to intel_iommu_set_dev_pasid /
intel_nested_set_dev_pasid, so a PASID-tagged fault can also resolve to an IOMMUFD attach_handle.

Both RID and PASID detach reach iopf_queue_remove_device() the same way as the mock device:
blocking_domain_attach_dev() / blocking_domain_set_dev_pasid() (drivers/iommu/intel/iommu.c:2755,3557)
-> iopf_for_domain_remove() (drivers/iommu/intel/iommu.h:1263) -> intel_iommu_disable_iopf()
(iommu.c:3471) -> iopf_queue_remove_device() (iommu.c:3483), but only once info->iopf_refcount
drops to zero. This refcount is shared per device across every attach path, so the bug may only fire
when the detached domain is the last fault-capable attachment on that device.


Again, the above analysis is based solely on the code.
For now we do not have suitable hardware to test the behavior of the AMD and Intel drivers.

Thanks,
Peiyang



^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [BUG] iommu: KASAN slab-use-after-free in iommufd_auto_response_faults
  2026-07-07 10:36 ` Peiyang He
@ 2026-07-08 14:38   ` Ankit Soni
  0 siblings, 0 replies; 18+ messages in thread
From: Ankit Soni @ 2026-07-08 14:38 UTC (permalink / raw)
  To: Peiyang He
  Cc: jgg, kevin.tian, iommu, linux-kernel, syzkaller, baolu.lu,
	nicolinc, joro, will, robin.murphy, dwmw2, suravee.suthikulpanit,
	vasant.hegde, yi.l.liu, jpb

On Tue, Jul 07, 2026 at 06:36:40PM +0800, Peiyang He wrote:
> > The analysis above goes through the iommufd selftest mock device (M). We
> > have not reproduced this on real Intel/AMD hardware, but the same lifetime
> > issue is not specific to the mock driver: Intel and AMD both have real detach
> > paths that can reach the very same iopf_queue_remove_device(). This is exactly
> > what iommufd_hwpt_detach_device() triggers, via iommu_detach_group_handle() /
> > iommu_detach_device_pasid(), right before it calls iommufd_auto_response_faults().
> > 

../..

> > AMD (drivers/iommu/amd/): the evidence here only covers the RID/group-detach
> > path.
> >   blocked_domain_attach_device()     [attach_dev, RID]       iommu.c:2883
> >   --> detach_device()                                        iommu.c:2398
> >   --> amd_iommu_iopf_remove_device()                         ppr.c:270
> >   --> iopf_queue_remove_device()                             ppr.c:273
> > AMD's PASID path, blocked_domain_set_dev_pasid() (iommu.c:2900), only calls
> > amd_iommu_remove_dev_pasid() and does not reach iopf_queue_remove_device().
> 
> Hello Linux kernel developers and maintainers,
> 
> After further analysis of the code, we think that this UAF should not be feasible on AMD devices,
> but still feasible on Intel devices.
> 
> ===
> AMD
> ===
> 
> We found no path by which a real AMD PPR fault can land in an IOMMUFD hw_pagetable's
> fault->common.deliver or fault->response.
> 
> A real AMD PPR fault is always PASID-valid: iommu_call_iopf_notifier(), the only
> iommu_report_device_fault() call site under drivers/iommu/amd/, rejects PASID 0 and always sets
> IOMMU_FAULT_PAGE_REQUEST_PASID_VALID (drivers/iommu/amd/ppr.c:107,140-148).
> 
> find_fault_handler() (drivers/iommu/io-pgfault.c:118) resolves a PASID-valid fault only via a
> per-PASID attach_handle, or a fallback to a RID-level NESTED domain gated by ops->user_pasid_table.
> AMD does not set that flag (only arm-smmu-v3.c:4389 and the iommufd selftest mock, selftest.c:861,
> do), so a failed per-PASID lookup is simply aborted on AMD, never delivered to any RID-level domain.
> 
> The per-PASID lookup cannot succeed toward an IOMMUFD HWPT on AMD, because the
> AMD domains that IOMMUFD can allocate as HWPTs do not implement .set_dev_pasid:
> amdv1_ops (iommu.c:2663), amdv2_ops (iommu.c:2741), and nested_domain_ops
> (drivers/iommu/amd/nested.c:291) all lack it. Therefore, if userspace tries to
> attach one of those IOMMUFD HWPT domains to a PASID, iommu_attach_device_pasid()
> rejects it with -EOPNOTSUPP at the !domain->ops->set_dev_pasid check
> (drivers/iommu/iommu.c:3625,3641-3644). The AMD nested RID attach path is not a
> substitute here either: nested_attach_device() refuses attachment when PASID is
> enabled (WARN_ON(dev_data->pasid_enabled), nested.c:244).
> 
> AMD does have a domain with .set_dev_pasid, amd_sva_domain_ops
> (drivers/iommu/amd/pasid.c:177), but that is the kernel SVA domain path, not an
> IOMMUFD HWPT. The SVA allocation path installs iommu_sva_iopf_handler()
> (drivers/iommu/iommu-sva.c:298,327), not iommufd_fault_iopf_handler(), so faults
> delivered there are not linked into an iommufd_fault object's deliver/response
> queues.
> 

Hi,

Your analysis is correct. Currently AMD is not affected by this bug.

The UAF requires iommufd_fault_iopf_handler to be registered as the
IOPF handler for the device, which only happens when an iopf-capable
HWPT is attached via a domain with .set_dev_pasid implemented.
AMD's IOMMUFD HWPT domain ops (amd_iommu_domain_ops) do not implement
.set_dev_pasid, so iommufd_fault_iopf_handler is never installed for
AMD. The PASID-less path through mock_domain_nop_attach is also not
reachable via the IOMMUFD fault path.

The existing _test_cmd_trigger_iopf() helper always responds immediately 
(read+write on fault_fd), which is why the current selftest  does not 
catch this.

@Jason,
Is it worth adding a test variant that triggers an IOPF without
responding and then detaches — to cover the cleanup path in
iommufd_auto_response_faults() as a regression test?

-Ankit

^ permalink raw reply	[flat|nested] 18+ messages in thread

* RE: [BUG] iommu: KASAN slab-use-after-free in iommufd_auto_response_faults
  2026-07-07  6:12 [BUG] iommu: KASAN slab-use-after-free in iommufd_auto_response_faults Peiyang He
  2026-07-07 10:36 ` Peiyang He
@ 2026-07-13  6:10 ` Tian, Kevin
  2026-07-13  9:01   ` Peiyang He
  2026-07-15  4:24 ` [PATCH] iommu/iommufd: Fix IOPF group ownership UAF Peiyang He
  2026-07-18  4:32 ` [PATCH v2] " Peiyang He
  3 siblings, 1 reply; 18+ messages in thread
From: Tian, Kevin @ 2026-07-13  6:10 UTC (permalink / raw)
  To: Peiyang He, jgg@ziepe.ca, iommu@lists.linux.dev
  Cc: linux-kernel@vger.kernel.org, syzkaller@googlegroups.com,
	baolu.lu@linux.intel.com, nicolinc@nvidia.com, joro@8bytes.org,
	will@kernel.org, robin.murphy@arm.com, dwmw2@infradead.org,
	suravee.suthikulpanit@amd.com, vasant.hegde@amd.com, Liu, Yi L,
	jpb@kernel.org

> From: Peiyang He <peiyang_he@smail.nju.edu.cn>
> Sent: Tuesday, July 7, 2026 2:13 PM
> 
> The analysis above goes through the iommufd selftest mock device (M). We
> have not reproduced this on real Intel/AMD hardware, but the same lifetime
> issue is not specific to the mock driver: Intel and AMD both have real detach
> paths that can reach the very same iopf_queue_remove_device(). This is
> exactly
> what iommufd_hwpt_detach_device() triggers, via
> iommu_detach_group_handle() /
> iommu_detach_device_pasid(), right before it calls
> iommufd_auto_response_faults().

yeah it's certainly a bug - it doesn't make sense to have an object sitting
on two lists after its ownership is transferred. Suppose we should delete
the group from iopf queue then add it to the iommufd queue in
iommufd_fault_iopf_handler().

Can you help send a fix accordingly?

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [BUG] iommu: KASAN slab-use-after-free in iommufd_auto_response_faults
  2026-07-13  6:10 ` Tian, Kevin
@ 2026-07-13  9:01   ` Peiyang He
  0 siblings, 0 replies; 18+ messages in thread
From: Peiyang He @ 2026-07-13  9:01 UTC (permalink / raw)
  To: Tian, Kevin, jgg@ziepe.ca, iommu@lists.linux.dev
  Cc: linux-kernel@vger.kernel.org, syzkaller@googlegroups.com,
	baolu.lu@linux.intel.com, nicolinc@nvidia.com, joro@8bytes.org,
	will@kernel.org, robin.murphy@arm.com, dwmw2@infradead.org,
	suravee.suthikulpanit@amd.com, vasant.hegde@amd.com, Liu, Yi L,
	jpb@kernel.org

On 2026/7/13 14:10, Tian, Kevin wrote:
>> From: Peiyang He <peiyang_he@smail.nju.edu.cn>
>> Sent: Tuesday, July 7, 2026 2:13 PM
>>
>> The analysis above goes through the iommufd selftest mock device (M). We
>> have not reproduced this on real Intel/AMD hardware, but the same lifetime
>> issue is not specific to the mock driver: Intel and AMD both have real detach
>> paths that can reach the very same iopf_queue_remove_device(). This is
>> exactly
>> what iommufd_hwpt_detach_device() triggers, via
>> iommu_detach_group_handle() /
>> iommu_detach_device_pasid(), right before it calls
>> iommufd_auto_response_faults().
> 
> yeah it's certainly a bug - it doesn't make sense to have an object sitting
> on two lists after its ownership is transferred. Suppose we should delete
> the group from iopf queue then add it to the iommufd queue in
> iommufd_fault_iopf_handler().
> 
> Can you help send a fix accordingly?

Hi Kevin,
Thanks for your reply! No problem, I will try sending a patch very soon.

Best,
Peiyang


^ permalink raw reply	[flat|nested] 18+ messages in thread

* [PATCH] iommu/iommufd: Fix IOPF group ownership UAF
  2026-07-07  6:12 [BUG] iommu: KASAN slab-use-after-free in iommufd_auto_response_faults Peiyang He
  2026-07-07 10:36 ` Peiyang He
  2026-07-13  6:10 ` Tian, Kevin
@ 2026-07-15  4:24 ` Peiyang He
  2026-07-17  8:00   ` Tian, Kevin
  2026-07-18  4:32 ` [PATCH v2] " Peiyang He
  3 siblings, 1 reply; 18+ messages in thread
From: Peiyang He @ 2026-07-15  4:24 UTC (permalink / raw)
  To: joro, will, jgg, kevin.tian
  Cc: robin.murphy, iommu, linux-kernel, stable, baolu.lu, Peiyang He

iopf_group_alloc() links each last-page IOPF group into the generic IOPF
pending list before invoking the domain fault handler.
iommufd_fault_iopf_handler() also queued an accepted group in the
IOMMUFD deliver list without removing it from the generic pending list.

When detach or HWPT replacement drops the device's IOPF reference count
to zero, an IOMMU driver may call iopf_queue_remove_device(). That
function responds to and frees groups through the generic pending list
without removing the same groups from IOMMUFD's deliver list or response
xarray. A later read, response, or cleanup can then access the freed
group and cause a UAF.

Fix this by separating response state from generic ownership.
Add iopf_group::response_pending to track whether a device response
is still owed, independently of generic pending-list membership,
and update that state under iommu_fault_param::lock in a helper.
Before publishing an accepted group to the IOMMUFD deliver list, call
iopf_group_take_ownership() to remove it from the generic pending list.
IOMMUFD then becomes the sole owner responsible for responding to and
freeing the group.

Closes: https://lore.kernel.org/all/B4F28798E2E784CA+d29f723c-b2b5-4b67-8d1c-4f7b9b0b27cb@smail.nju.edu.cn/
Fixes: 34765cbc679c ("iommufd: Associate fault object with iommufd_hw_pgtable")
Cc: stable@vger.kernel.org
Tested-by: Peiyang He <peiyang_he@smail.nju.edu.cn>
Signed-off-by: Peiyang He <peiyang_he@smail.nju.edu.cn>
Assisted-by: Codex:gpt-5.6-sol
---
I have tested this patch agaist my PoC which can stably trigger the UAF,
and the UAF is gone after applying this patch.

I also ran the iommufd selftest. Some testcases already fail before
this patch, and this patch does not make more testcases fail.

 drivers/iommu/io-pgfault.c     | 64 +++++++++++++++++++++++-----------
 drivers/iommu/iommufd/device.c |  2 +-
 drivers/iommu/iommufd/eventq.c |  2 ++
 include/linux/iommu.h          |  7 ++++
 4 files changed, 53 insertions(+), 22 deletions(-)

diff --git a/drivers/iommu/io-pgfault.c b/drivers/iommu/io-pgfault.c
index cca52a34d0ed..b873aef8d97d 100644
--- a/drivers/iommu/io-pgfault.c
+++ b/drivers/iommu/io-pgfault.c
@@ -98,6 +98,7 @@ static struct iopf_group *iopf_group_alloc(struct iommu_fault_param *iopf_param,
 	group->last_fault.fault = evt->fault;
 	INIT_LIST_HEAD(&group->faults);
 	INIT_LIST_HEAD(&group->pending_node);
+	group->response_pending = true;
 	list_add(&group->last_fault.list, &group->faults);
 
 	/* See if we have partial faults for this group */
@@ -314,13 +315,8 @@ int iopf_queue_flush_dev(struct device *dev)
 }
 EXPORT_SYMBOL_GPL(iopf_queue_flush_dev);
 
-/**
- * iopf_group_response - Respond a group of page faults
- * @group: the group of faults with the same group id
- * @status: the response code
- */
-void iopf_group_response(struct iopf_group *group,
-			 enum iommu_page_response_code status)
+static void iopf_group_response_locked(struct iopf_group *group,
+				       enum iommu_page_response_code status)
 {
 	struct iommu_fault_param *fault_param = group->fault_param;
 	struct iopf_fault *iopf = &group->last_fault;
@@ -332,16 +328,51 @@ void iopf_group_response(struct iopf_group *group,
 		.code = status,
 	};
 
+	lockdep_assert_held(&fault_param->lock);
+
 	/* Only send response if there is a fault report pending */
-	mutex_lock(&fault_param->lock);
-	if (!list_empty(&group->pending_node)) {
-		ops->page_response(dev, &group->last_fault, &resp);
+	if (!group->response_pending)
+		return;
+
+	ops->page_response(dev, &group->last_fault, &resp);
+	group->response_pending = false;
+	if (!list_empty(&group->pending_node))
 		list_del_init(&group->pending_node);
-	}
+}
+
+/**
+ * iopf_group_response - Respond a group of page faults
+ * @group: the group of faults with the same group id
+ * @status: the response code
+ */
+void iopf_group_response(struct iopf_group *group,
+			 enum iommu_page_response_code status)
+{
+	struct iommu_fault_param *fault_param = group->fault_param;
+
+	mutex_lock(&fault_param->lock);
+	iopf_group_response_locked(group, status);
 	mutex_unlock(&fault_param->lock);
 }
 EXPORT_SYMBOL_GPL(iopf_group_response);
 
+/**
+ * iopf_group_take_ownership - Take ownership of a group of page faults
+ * @group: the group of faults whose ownership is transferred to the fault handler
+ *
+ * Remove the group from the generic IOPF pending list. The fault handler is
+ * responsible for responding to and freeing the group after this returns.
+ */
+void iopf_group_take_ownership(struct iopf_group *group)
+{
+	struct iommu_fault_param *fault_param = group->fault_param;
+
+	mutex_lock(&fault_param->lock);
+	list_del_init(&group->pending_node);
+	mutex_unlock(&fault_param->lock);
+}
+EXPORT_SYMBOL_GPL(iopf_group_take_ownership);
+
 /**
  * iopf_queue_discard_partial - Remove all pending partial fault
  * @queue: the queue whose partial faults need to be discarded
@@ -454,7 +485,6 @@ void iopf_queue_remove_device(struct iopf_queue *queue, struct device *dev)
 	struct iopf_group *group, *temp;
 	struct dev_iommu *param = dev->iommu;
 	struct iommu_fault_param *fault_param;
-	const struct iommu_ops *ops = dev_iommu_ops(dev);
 
 	mutex_lock(&queue->lock);
 	mutex_lock(&param->lock);
@@ -469,15 +499,7 @@ void iopf_queue_remove_device(struct iopf_queue *queue, struct device *dev)
 		kfree(partial_iopf);
 
 	list_for_each_entry_safe(group, temp, &fault_param->faults, pending_node) {
-		struct iopf_fault *iopf = &group->last_fault;
-		struct iommu_page_response resp = {
-			.pasid = iopf->fault.prm.pasid,
-			.grpid = iopf->fault.prm.grpid,
-			.code = IOMMU_PAGE_RESP_INVALID
-		};
-
-		ops->page_response(dev, iopf, &resp);
-		list_del_init(&group->pending_node);
+		iopf_group_response_locked(group, IOMMU_PAGE_RESP_INVALID);
 		iopf_free_group(group);
 	}
 	mutex_unlock(&fault_param->lock);
diff --git a/drivers/iommu/iommufd/device.c b/drivers/iommu/iommufd/device.c
index 7deb3346cb78..d244a97a6325 100644
--- a/drivers/iommu/iommufd/device.c
+++ b/drivers/iommu/iommufd/device.c
@@ -582,7 +582,7 @@ static int iommufd_hwpt_replace_device(struct iommufd_device *idev,
 	if (rc)
 		goto out_free_handle;
 
-	iommufd_auto_response_faults(hwpt, old_handle);
+	iommufd_auto_response_faults(old, old_handle);
 	kfree(old_handle);
 
 	return 0;
diff --git a/drivers/iommu/iommufd/eventq.c b/drivers/iommu/iommufd/eventq.c
index 5129e3bf5461..70fd245356a4 100644
--- a/drivers/iommu/iommufd/eventq.c
+++ b/drivers/iommu/iommufd/eventq.c
@@ -484,6 +484,8 @@ int iommufd_fault_iopf_handler(struct iopf_group *group)
 	hwpt = group->attach_handle->domain->iommufd_hwpt;
 	fault = hwpt->fault;
 
+	iopf_group_take_ownership(group);
+
 	spin_lock(&fault->common.lock);
 	list_add_tail(&group->node, &fault->common.deliver);
 	spin_unlock(&fault->common.lock);
diff --git a/include/linux/iommu.h b/include/linux/iommu.h
index d20aa6f6863a..c2180a505ac0 100644
--- a/include/linux/iommu.h
+++ b/include/linux/iommu.h
@@ -133,6 +133,8 @@ struct iopf_group {
 	size_t fault_count;
 	/* list node for iommu_fault_param::faults */
 	struct list_head pending_node;
+	/* True if the device still needs a response for this group. */
+	bool response_pending;
 	struct work_struct work;
 	struct iommu_attach_handle *attach_handle;
 	/* The device's fault data parameter. */
@@ -1704,6 +1706,7 @@ void iopf_free_group(struct iopf_group *group);
 int iommu_report_device_fault(struct device *dev, struct iopf_fault *evt);
 void iopf_group_response(struct iopf_group *group,
 			 enum iommu_page_response_code status);
+void iopf_group_take_ownership(struct iopf_group *group);
 #else
 static inline int
 iopf_queue_add_device(struct iopf_queue *queue, struct device *dev)
@@ -1749,5 +1752,9 @@ static inline void iopf_group_response(struct iopf_group *group,
 				       enum iommu_page_response_code status)
 {
 }
+
+static inline void iopf_group_take_ownership(struct iopf_group *group)
+{
+}
 #endif /* CONFIG_IOMMU_IOPF */
 #endif /* __LINUX_IOMMU_H */

base-commit: 8062148046e1a6417d44e2ed86c04e66c2f4f2a1
-- 
2.43.0

^ permalink raw reply related	[flat|nested] 18+ messages in thread

* RE: [PATCH] iommu/iommufd: Fix IOPF group ownership UAF
  2026-07-15  4:24 ` [PATCH] iommu/iommufd: Fix IOPF group ownership UAF Peiyang He
@ 2026-07-17  8:00   ` Tian, Kevin
  2026-07-18  4:55     ` Peiyang He
  0 siblings, 1 reply; 18+ messages in thread
From: Tian, Kevin @ 2026-07-17  8:00 UTC (permalink / raw)
  To: Peiyang He, joro@8bytes.org, will@kernel.org, jgg@ziepe.ca
  Cc: robin.murphy@arm.com, iommu@lists.linux.dev,
	linux-kernel@vger.kernel.org, stable@vger.kernel.org,
	baolu.lu@linux.intel.com

> From: Peiyang He <peiyang_he@smail.nju.edu.cn>
> Sent: Wednesday, July 15, 2026 12:24 PM
> 
> iopf_group_alloc() links each last-page IOPF group into the generic IOPF
> pending list before invoking the domain fault handler.
> iommufd_fault_iopf_handler() also queued an accepted group in the
> IOMMUFD deliver list without removing it from the generic pending list.
> 
> When detach or HWPT replacement drops the device's IOPF reference count
> to zero, an IOMMU driver may call iopf_queue_remove_device(). That
> function responds to and frees groups through the generic pending list
> without removing the same groups from IOMMUFD's deliver list or response
> xarray. A later read, response, or cleanup can then access the freed
> group and cause a UAF.
> 
> Fix this by separating response state from generic ownership.
> Add iopf_group::response_pending to track whether a device response
> is still owed, independently of generic pending-list membership,
> and update that state under iommu_fault_param::lock in a helper.
> Before publishing an accepted group to the IOMMUFD deliver list, call
> iopf_group_take_ownership() to remove it from the generic pending list.
> IOMMUFD then becomes the sole owner responsible for responding to and
> freeing the group.

this is ok but could be simplified.

There is no use case in which we allow a group staying in the pending
list after its response has been sent. Actually every iopf_group_response()
is followed by iopf_free_group().

so let's just remove the list_empty() check in iopf_group_response().

better - move iopf_free_group() into iopf_group_response() (with a special
handling in err unwinding of iommu_report_device_fault()).

> +/**
> + * iopf_group_take_ownership - Take ownership of a group of page faults
> + * @group: the group of faults whose ownership is transferred to the fault
> handler
> + *
> + * Remove the group from the generic IOPF pending list. The fault handler is
> + * responsible for responding to and freeing the group after this returns.
> + */
> +void iopf_group_take_ownership(struct iopf_group *group)

iopf_group_dequeue()

> @@ -582,7 +582,7 @@ static int iommufd_hwpt_replace_device(struct
> iommufd_device *idev,
>  	if (rc)
>  		goto out_free_handle;
> 
> -	iommufd_auto_response_faults(hwpt, old_handle);
> +	iommufd_auto_response_faults(old, old_handle);

this belongs to another patch already queued by Jason.

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [PATCH v2] iommu/iommufd: Fix IOPF group ownership UAF
  2026-07-07  6:12 [BUG] iommu: KASAN slab-use-after-free in iommufd_auto_response_faults Peiyang He
                   ` (2 preceding siblings ...)
  2026-07-15  4:24 ` [PATCH] iommu/iommufd: Fix IOPF group ownership UAF Peiyang He
@ 2026-07-18  4:32 ` Peiyang He
  2026-07-20  5:13   ` Tian, Kevin
                     ` (2 more replies)
  3 siblings, 3 replies; 18+ messages in thread
From: Peiyang He @ 2026-07-18  4:32 UTC (permalink / raw)
  To: joro, will, jgg, kevin.tian
  Cc: robin.murphy, iommu, linux-kernel, stable, baolu.lu, Peiyang He

iopf_group_alloc() links each last-page IOPF group into the generic IOPF
pending list before invoking the domain fault handler.
iommufd_fault_iopf_handler() also queued an accepted group in the
IOMMUFD deliver list without removing it from the generic pending list.

When detach or HWPT replacement drops the device's IOPF reference count
to zero, an IOMMU driver may call iopf_queue_remove_device(). That
function responds to and frees groups through the generic pending list
without removing the same groups from IOMMUFD's deliver list or response
xarray. A later read, response, or cleanup can then access the freed
group and cause a UAF.

Fix this by dequeuing an accepted group from the generic pending list
before IOMMUFD queues it for userspace response, and by making
iopf_group_response() free the group as well after sending the response.
The abort_group cleanup in iommu_report_device_fault() keeps
using the locked helper, since abort_group is stack-allocated
and must not be freed by iopf_group_response().

Closes: https://lore.kernel.org/all/B4F28798E2E784CA+d29f723c-b2b5-4b67-8d1c-4f7b9b0b27cb@smail.nju.edu.cn/
Fixes: 34765cbc679c ("iommufd: Associate fault object with iommufd_hw_pgtable")
Cc: stable@vger.kernel.org
Tested-by: Peiyang He <peiyang_he@smail.nju.edu.cn>
Signed-off-by: Peiyang He <peiyang_he@smail.nju.edu.cn>
Assisted-by: Codex:gpt-5.6-sol
---
Changes in v2:
  - simplify the fix by moving group freeing into iopf_group_response()
    (suggested by Kevin)
  - rename iopf_group_take_ownership() to iopf_group_dequeue()
    (suggested by Kevin)
  - drop changes in iommufd_hwpt_replace_device (suggested by Kevin)
  - rebase to 12f16a37f298

 drivers/iommu/io-pgfault.c     | 84 +++++++++++++++++++++-------------
 drivers/iommu/iommu-sva.c      |  1 -
 drivers/iommu/iommufd/eventq.c |  7 +--
 include/linux/iommu.h          | 10 ++--
 4 files changed, 58 insertions(+), 44 deletions(-)

diff --git a/drivers/iommu/io-pgfault.c b/drivers/iommu/io-pgfault.c
index cca52a34d0ed69273f23d9ce7793f7065388273f..e5d3a32fc2a6c02acc120e7f7a95972ae658a223 100644
--- a/drivers/iommu/io-pgfault.c
+++ b/drivers/iommu/io-pgfault.c
@@ -52,12 +52,11 @@ static void __iopf_free_group(struct iopf_group *group)
 	iopf_put_dev_fault_param(group->fault_param);
 }
 
-void iopf_free_group(struct iopf_group *group)
+static void iopf_free_group(struct iopf_group *group)
 {
 	__iopf_free_group(group);
 	kfree(group);
 }
-EXPORT_SYMBOL_GPL(iopf_free_group);
 
 /* Non-last request of a group. Postpone until the last one. */
 static int report_partial_fault(struct iommu_fault_param *fault_param,
@@ -168,6 +167,25 @@ static void iopf_error_response(struct device *dev, struct iopf_fault *evt)
 	ops->page_response(dev, evt, &resp);
 }
 
+static void iopf_group_response_locked(struct iopf_group *group,
+				       enum iommu_page_response_code status)
+{
+	struct iommu_fault_param *fault_param = group->fault_param;
+	struct iopf_fault *iopf = &group->last_fault;
+	struct device *dev = fault_param->dev;
+	const struct iommu_ops *ops = dev_iommu_ops(dev);
+	struct iommu_page_response resp = {
+		.pasid = iopf->fault.prm.pasid,
+		.grpid = iopf->fault.prm.grpid,
+		.code = status,
+	};
+
+	lockdep_assert_held(&fault_param->lock);
+
+	ops->page_response(dev, &group->last_fault, &resp);
+	list_del_init(&group->pending_node);
+}
+
 /**
  * iommu_report_device_fault() - Report fault event to device driver
  * @dev: the device
@@ -255,10 +273,7 @@ int iommu_report_device_fault(struct device *dev, struct iopf_fault *evt)
 
 	group->attach_handle = attach_handle;
 
-	/*
-	 * On success iopf_handler must call iopf_group_response() and
-	 * iopf_free_group()
-	 */
+	/* On success iopf_handler must call iopf_group_response(). */
 	if (group->attach_handle->domain->iopf_handler(group))
 		goto err_abort;
 
@@ -267,11 +282,14 @@ int iommu_report_device_fault(struct device *dev, struct iopf_fault *evt)
 err_abort:
 	dev_warn_ratelimited(dev, "iopf with pasid %d aborted\n",
 			     fault->prm.pasid);
-	iopf_group_response(group, IOMMU_PAGE_RESP_FAILURE);
-	if (group == &abort_group)
+	if (group == &abort_group) {
+		mutex_lock(&group->fault_param->lock);
+		iopf_group_response_locked(group, IOMMU_PAGE_RESP_FAILURE);
+		mutex_unlock(&group->fault_param->lock);
 		__iopf_free_group(group);
-	else
-		iopf_free_group(group);
+	} else {
+		iopf_group_response(group, IOMMU_PAGE_RESP_FAILURE);
+	}
 
 	return 0;
 
@@ -318,30 +336,39 @@ EXPORT_SYMBOL_GPL(iopf_queue_flush_dev);
  * iopf_group_response - Respond a group of page faults
  * @group: the group of faults with the same group id
  * @status: the response code
+ *
+ * The group will be freed as well and must not be accessed after
+ * this function returns.
  */
 void iopf_group_response(struct iopf_group *group,
 			 enum iommu_page_response_code status)
 {
 	struct iommu_fault_param *fault_param = group->fault_param;
-	struct iopf_fault *iopf = &group->last_fault;
-	struct device *dev = group->fault_param->dev;
-	const struct iommu_ops *ops = dev_iommu_ops(dev);
-	struct iommu_page_response resp = {
-		.pasid = iopf->fault.prm.pasid,
-		.grpid = iopf->fault.prm.grpid,
-		.code = status,
-	};
 
-	/* Only send response if there is a fault report pending */
 	mutex_lock(&fault_param->lock);
-	if (!list_empty(&group->pending_node)) {
-		ops->page_response(dev, &group->last_fault, &resp);
-		list_del_init(&group->pending_node);
-	}
+	iopf_group_response_locked(group, status);
 	mutex_unlock(&fault_param->lock);
+	iopf_free_group(group);
 }
 EXPORT_SYMBOL_GPL(iopf_group_response);
 
+/**
+ * iopf_group_dequeue - Dequeue a page fault group from the pending list
+ * @group: the group to dequeue
+ *
+ * The fault handler is responsible for responding to the group after
+ * this function returns.
+ */
+void iopf_group_dequeue(struct iopf_group *group)
+{
+	struct iommu_fault_param *fault_param = group->fault_param;
+
+	mutex_lock(&fault_param->lock);
+	list_del_init(&group->pending_node);
+	mutex_unlock(&fault_param->lock);
+}
+EXPORT_SYMBOL_GPL(iopf_group_dequeue);
+
 /**
  * iopf_queue_discard_partial - Remove all pending partial fault
  * @queue: the queue whose partial faults need to be discarded
@@ -454,7 +481,6 @@ void iopf_queue_remove_device(struct iopf_queue *queue, struct device *dev)
 	struct iopf_group *group, *temp;
 	struct dev_iommu *param = dev->iommu;
 	struct iommu_fault_param *fault_param;
-	const struct iommu_ops *ops = dev_iommu_ops(dev);
 
 	mutex_lock(&queue->lock);
 	mutex_lock(&param->lock);
@@ -469,15 +495,7 @@ void iopf_queue_remove_device(struct iopf_queue *queue, struct device *dev)
 		kfree(partial_iopf);
 
 	list_for_each_entry_safe(group, temp, &fault_param->faults, pending_node) {
-		struct iopf_fault *iopf = &group->last_fault;
-		struct iommu_page_response resp = {
-			.pasid = iopf->fault.prm.pasid,
-			.grpid = iopf->fault.prm.grpid,
-			.code = IOMMU_PAGE_RESP_INVALID
-		};
-
-		ops->page_response(dev, iopf, &resp);
-		list_del_init(&group->pending_node);
+		iopf_group_response_locked(group, IOMMU_PAGE_RESP_INVALID);
 		iopf_free_group(group);
 	}
 	mutex_unlock(&fault_param->lock);
diff --git a/drivers/iommu/iommu-sva.c b/drivers/iommu/iommu-sva.c
index bc7c7232a43e2d5edde679b953e85380413a7f2b..caccaa315461c6d18ca936e84dfa1cefa5e33e4a 100644
--- a/drivers/iommu/iommu-sva.c
+++ b/drivers/iommu/iommu-sva.c
@@ -292,7 +292,6 @@ static void iommu_sva_handle_iopf(struct work_struct *work)
 	}
 
 	iopf_group_response(group, status);
-	iopf_free_group(group);
 }
 
 static int iommu_sva_iopf_handler(struct iopf_group *group)
diff --git a/drivers/iommu/iommufd/eventq.c b/drivers/iommu/iommufd/eventq.c
index 5129e3bf5461cb1ef85c550369c9d1b2aa771829..62110fe8f78f511c96be4fa4c2bf8fb552083f38 100644
--- a/drivers/iommu/iommufd/eventq.c
+++ b/drivers/iommu/iommufd/eventq.c
@@ -40,7 +40,6 @@ void iommufd_auto_response_faults(struct iommufd_hw_pagetable *hwpt,
 	list_for_each_entry_safe(group, next, &free_list, node) {
 		list_del(&group->node);
 		iopf_group_response(group, IOMMU_PAGE_RESP_INVALID);
-		iopf_free_group(group);
 	}
 
 	xa_for_each(&fault->response, index, group) {
@@ -48,7 +47,6 @@ void iommufd_auto_response_faults(struct iommufd_hw_pagetable *hwpt,
 			continue;
 		xa_erase(&fault->response, index);
 		iopf_group_response(group, IOMMU_PAGE_RESP_INVALID);
-		iopf_free_group(group);
 	}
 	mutex_unlock(&fault->mutex);
 }
@@ -70,12 +68,10 @@ void iommufd_fault_destroy(struct iommufd_object *obj)
 	list_for_each_entry_safe(group, next, &fault->common.deliver, node) {
 		list_del(&group->node);
 		iopf_group_response(group, IOMMU_PAGE_RESP_INVALID);
-		iopf_free_group(group);
 	}
 	xa_for_each(&fault->response, index, group) {
 		xa_erase(&fault->response, index);
 		iopf_group_response(group, IOMMU_PAGE_RESP_INVALID);
-		iopf_free_group(group);
 	}
 	xa_destroy(&fault->response);
 	mutex_destroy(&fault->mutex);
@@ -217,7 +213,6 @@ static ssize_t iommufd_fault_fops_write(struct file *filep, const char __user *b
 		}
 
 		iopf_group_response(group, response.code);
-		iopf_free_group(group);
 		done += response_size;
 	}
 	mutex_unlock(&fault->mutex);
@@ -484,6 +479,8 @@ int iommufd_fault_iopf_handler(struct iopf_group *group)
 	hwpt = group->attach_handle->domain->iommufd_hwpt;
 	fault = hwpt->fault;
 
+	iopf_group_dequeue(group);
+
 	spin_lock(&fault->common.lock);
 	list_add_tail(&group->node, &fault->common.deliver);
 	spin_unlock(&fault->common.lock);
diff --git a/include/linux/iommu.h b/include/linux/iommu.h
index d20aa6f6863ab35a5932911219705f2ffd0e3352..f16c88d9266180df79b4da515f5e3094ac61f9df 100644
--- a/include/linux/iommu.h
+++ b/include/linux/iommu.h
@@ -1700,10 +1700,10 @@ int iopf_queue_flush_dev(struct device *dev);
 struct iopf_queue *iopf_queue_alloc(const char *name);
 void iopf_queue_free(struct iopf_queue *queue);
 int iopf_queue_discard_partial(struct iopf_queue *queue);
-void iopf_free_group(struct iopf_group *group);
 int iommu_report_device_fault(struct device *dev, struct iopf_fault *evt);
 void iopf_group_response(struct iopf_group *group,
 			 enum iommu_page_response_code status);
+void iopf_group_dequeue(struct iopf_group *group);
 #else
 static inline int
 iopf_queue_add_device(struct iopf_queue *queue, struct device *dev)
@@ -1735,10 +1735,6 @@ static inline int iopf_queue_discard_partial(struct iopf_queue *queue)
 	return -ENODEV;
 }
 
-static inline void iopf_free_group(struct iopf_group *group)
-{
-}
-
 static inline int
 iommu_report_device_fault(struct device *dev, struct iopf_fault *evt)
 {
@@ -1749,5 +1745,9 @@ static inline void iopf_group_response(struct iopf_group *group,
 				       enum iommu_page_response_code status)
 {
 }
+
+static inline void iopf_group_dequeue(struct iopf_group *group)
+{
+}
 #endif /* CONFIG_IOMMU_IOPF */
 #endif /* __LINUX_IOMMU_H */

base-commit: 12f16a37f2982028e21919466401179647f603c7
-- 
2.43.0

^ permalink raw reply related	[flat|nested] 18+ messages in thread

* Re: [PATCH] iommu/iommufd: Fix IOPF group ownership UAF
  2026-07-17  8:00   ` Tian, Kevin
@ 2026-07-18  4:55     ` Peiyang He
  0 siblings, 0 replies; 18+ messages in thread
From: Peiyang He @ 2026-07-18  4:55 UTC (permalink / raw)
  To: Tian, Kevin, joro@8bytes.org, will@kernel.org, jgg@ziepe.ca
  Cc: robin.murphy@arm.com, iommu@lists.linux.dev,
	linux-kernel@vger.kernel.org, stable@vger.kernel.org,
	baolu.lu@linux.intel.com

On 2026/7/17 16:00, Tian, Kevin wrote:
>> From: Peiyang He <peiyang_he@smail.nju.edu.cn>
>> Sent: Wednesday, July 15, 2026 12:24 PM
>>
>> iopf_group_alloc() links each last-page IOPF group into the generic IOPF
>> pending list before invoking the domain fault handler.
>> iommufd_fault_iopf_handler() also queued an accepted group in the
>> IOMMUFD deliver list without removing it from the generic pending list.
>>
>> When detach or HWPT replacement drops the device's IOPF reference count
>> to zero, an IOMMU driver may call iopf_queue_remove_device(). That
>> function responds to and frees groups through the generic pending list
>> without removing the same groups from IOMMUFD's deliver list or response
>> xarray. A later read, response, or cleanup can then access the freed
>> group and cause a UAF.
>>
>> Fix this by separating response state from generic ownership.
>> Add iopf_group::response_pending to track whether a device response
>> is still owed, independently of generic pending-list membership,
>> and update that state under iommu_fault_param::lock in a helper.
>> Before publishing an accepted group to the IOMMUFD deliver list, call
>> iopf_group_take_ownership() to remove it from the generic pending list.
>> IOMMUFD then becomes the sole owner responsible for responding to and
>> freeing the group.
> 
> this is ok but could be simplified.
> 
> There is no use case in which we allow a group staying in the pending
> list after its response has been sent. Actually every iopf_group_response()
> is followed by iopf_free_group().
> 
> so let's just remove the list_empty() check in iopf_group_response().
> 
> better - move iopf_free_group() into iopf_group_response() (with a special
> handling in err unwinding of iommu_report_device_fault()).
> 
>> +/**
>> + * iopf_group_take_ownership - Take ownership of a group of page faults
>> + * @group: the group of faults whose ownership is transferred to the fault
>> handler
>> + *
>> + * Remove the group from the generic IOPF pending list. The fault handler is
>> + * responsible for responding to and freeing the group after this returns.
>> + */
>> +void iopf_group_take_ownership(struct iopf_group *group)
> 
> iopf_group_dequeue()
> 
>> @@ -582,7 +582,7 @@ static int iommufd_hwpt_replace_device(struct
>> iommufd_device *idev,
>>  	if (rc)
>>  		goto out_free_handle;
>>
>> -	iommufd_auto_response_faults(hwpt, old_handle);
>> +	iommufd_auto_response_faults(old, old_handle);
> 
> this belongs to another patch already queued by Jason.
> 

Hi Kevin,

v2 patch has addressed all these issues and passed my PoC. Thanks for your feedback!

Best,
Peiyang



^ permalink raw reply	[flat|nested] 18+ messages in thread

* RE: [PATCH v2] iommu/iommufd: Fix IOPF group ownership UAF
  2026-07-18  4:32 ` [PATCH v2] " Peiyang He
@ 2026-07-20  5:13   ` Tian, Kevin
  2026-07-20  8:50   ` [PATCH v3 0/2] " Peiyang He
       [not found]   ` <20260720085017.3998878-1-peiyang_he@smail.nju.edu.cn>
  2 siblings, 0 replies; 18+ messages in thread
From: Tian, Kevin @ 2026-07-20  5:13 UTC (permalink / raw)
  To: Peiyang He, joro@8bytes.org, will@kernel.org, jgg@ziepe.ca
  Cc: robin.murphy@arm.com, iommu@lists.linux.dev,
	linux-kernel@vger.kernel.org, stable@vger.kernel.org,
	baolu.lu@linux.intel.com

> From: Peiyang He <peiyang_he@smail.nju.edu.cn>
> Sent: Saturday, July 18, 2026 12:33 PM
> 
> iopf_group_alloc() links each last-page IOPF group into the generic IOPF
> pending list before invoking the domain fault handler.
> iommufd_fault_iopf_handler() also queued an accepted group in the
> IOMMUFD deliver list without removing it from the generic pending list.
> 
> When detach or HWPT replacement drops the device's IOPF reference count
> to zero, an IOMMU driver may call iopf_queue_remove_device(). That
> function responds to and frees groups through the generic pending list
> without removing the same groups from IOMMUFD's deliver list or response
> xarray. A later read, response, or cleanup can then access the freed
> group and cause a UAF.
> 
> Fix this by dequeuing an accepted group from the generic pending list
> before IOMMUFD queues it for userspace response, and by making
> iopf_group_response() free the group as well after sending the response.
> The abort_group cleanup in iommu_report_device_fault() keeps
> using the locked helper, since abort_group is stack-allocated
> and must not be freed by iopf_group_response().
> 
> Closes: https://lore.kernel.org/all/B4F28798E2E784CA+d29f723c-b2b5-4b67-
> 8d1c-4f7b9b0b27cb@smail.nju.edu.cn/
> Fixes: 34765cbc679c ("iommufd: Associate fault object with
> iommufd_hw_pgtable")
> Cc: stable@vger.kernel.org
> Tested-by: Peiyang He <peiyang_he@smail.nju.edu.cn>
> Signed-off-by: Peiyang He <peiyang_he@smail.nju.edu.cn>
> Assisted-by: Codex:gpt-5.6-sol

this is basically ok but let's split it into two.

the first patch just includes addition of iopf_group_dequeue() and
removal of the check on list_empty() in iopf_group_response(). that's
simple and correct for backporting to stable.

the 2nd patch does the remaining cleanup:

  __iopf_group_response_locked(): locked version of sending response
  __iopf_group_response(): acquire lock and send response
  iopf_group_response(): send response plus freeing group. still as the
  exported symbol

then...

> @@ -267,11 +282,14 @@ int iommu_report_device_fault(struct device *dev,
> struct iopf_fault *evt)
>  err_abort:
>  	dev_warn_ratelimited(dev, "iopf with pasid %d aborted\n",
>  			     fault->prm.pasid);
> -	iopf_group_response(group, IOMMU_PAGE_RESP_FAILURE);

...just replace iopf_group_response() with __iopf_group_response(). No
change to the following lines:

> -	if (group == &abort_group)
> +	if (group == &abort_group) {
> +		mutex_lock(&group->fault_param->lock);
> +		iopf_group_response_locked(group,
> IOMMU_PAGE_RESP_FAILURE);
> +		mutex_unlock(&group->fault_param->lock);
>  		__iopf_free_group(group);
> -	else
> -		iopf_free_group(group);
> +	} else {
> +		iopf_group_response(group, IOMMU_PAGE_RESP_FAILURE);
> +	}

[...]

>  void iopf_group_response(struct iopf_group *group,
>  			 enum iommu_page_response_code status)
>  {
>  	struct iommu_fault_param *fault_param = group->fault_param;
> -	struct iopf_fault *iopf = &group->last_fault;
> -	struct device *dev = group->fault_param->dev;
> -	const struct iommu_ops *ops = dev_iommu_ops(dev);
> -	struct iommu_page_response resp = {
> -		.pasid = iopf->fault.prm.pasid,
> -		.grpid = iopf->fault.prm.grpid,
> -		.code = status,
> -	};
> 
> -	/* Only send response if there is a fault report pending */
>  	mutex_lock(&fault_param->lock);
> -	if (!list_empty(&group->pending_node)) {
> -		ops->page_response(dev, &group->last_fault, &resp);
> -		list_del_init(&group->pending_node);
> -	}
> +	iopf_group_response_locked(group, status);
>  	mutex_unlock(&fault_param->lock);
> +	iopf_free_group(group);
>  }

iopf_group_response()
{
	__ iopf_group_response();
	iopf_free_group(group);
}

> @@ -469,15 +495,7 @@ void iopf_queue_remove_device(struct iopf_queue
> *queue, struct device *dev)
>  		kfree(partial_iopf);
> 
>  	list_for_each_entry_safe(group, temp, &fault_param->faults,
> pending_node) {
> -		struct iopf_fault *iopf = &group->last_fault;
> -		struct iommu_page_response resp = {
> -			.pasid = iopf->fault.prm.pasid,
> -			.grpid = iopf->fault.prm.grpid,
> -			.code = IOMMU_PAGE_RESP_INVALID
> -		};
> -
> -		ops->page_response(dev, iopf, &resp);
> -		list_del_init(&group->pending_node);
> +		iopf_group_response_locked(group,
> IOMMU_PAGE_RESP_INVALID);
>  		iopf_free_group(group);
>  	}

	__iopf_group_response_locked();
	iopf_free_group(group);


^ permalink raw reply	[flat|nested] 18+ messages in thread

* [PATCH v3 0/2] iommu/iommufd: Fix IOPF group ownership UAF
  2026-07-18  4:32 ` [PATCH v2] " Peiyang He
  2026-07-20  5:13   ` Tian, Kevin
@ 2026-07-20  8:50   ` Peiyang He
  2026-07-26 12:43     ` Will Deacon
       [not found]   ` <20260720085017.3998878-1-peiyang_he@smail.nju.edu.cn>
  2 siblings, 1 reply; 18+ messages in thread
From: Peiyang He @ 2026-07-20  8:50 UTC (permalink / raw)
  To: joro, will, jgg, kevin.tian
  Cc: robin.murphy, iommu, linux-kernel, baolu.lu, Peiyang He

This series fixes a UAF caused by an IOPF group being owned both by the
generic pending list and IOMMUFD's userspace fault queues:

  1/2 Minimum fix: dequeue accepted groups before IOMMUFD queues them
      for userspace response, then remove the check on list_empty()
      in iopf_group_response().

  2/2 Refactor: make iopf_group_response() free the group as well
      after responding.

Changes in v3:
  - split the v2 patch into a minimum UAF fix and a follow-up cleanup
    (suggested by Kevin)

Changes in v2:
  - simplify the fix by moving group freeing into iopf_group_response()
    (suggested by Kevin)
  - rename iopf_group_take_ownership() to iopf_group_dequeue()
    (suggested by Kevin)
  - drop changes in iommufd_hwpt_replace_device (suggested by Kevin)
  - rebase to 12f16a37f298

Peiyang He (2):
  iommu/iommufd: Fix IOPF group ownership UAF
  iommu: Make iopf_group_response() free groups

 drivers/iommu/io-pgfault.c     | 83 +++++++++++++++++++++-------------
 drivers/iommu/iommu-sva.c      |  1 -
 drivers/iommu/iommufd/eventq.c |  7 +--
 include/linux/iommu.h          | 10 ++--
 4 files changed, 59 insertions(+), 42 deletions(-)


base-commit: 12f16a37f2982028e21919466401179647f603c7
-- 
2.43.0

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [PATCH v3 1/2] iommu/iommufd: Fix IOPF group ownership UAF
       [not found]   ` <20260720085017.3998878-1-peiyang_he@smail.nju.edu.cn>
@ 2026-07-20  8:50     ` Peiyang He
  2026-07-20  9:44       ` Tian, Kevin
  2026-07-26 16:20       ` Jason Gunthorpe
  2026-07-20  8:50     ` [PATCH v3 2/2] iommu: Make iopf_group_response() free groups Peiyang He
  1 sibling, 2 replies; 18+ messages in thread
From: Peiyang He @ 2026-07-20  8:50 UTC (permalink / raw)
  To: joro, will, jgg, kevin.tian
  Cc: robin.murphy, iommu, linux-kernel, baolu.lu, Peiyang He, stable

iopf_group_alloc() links each last-page IOPF group into the generic IOPF
pending list before invoking the domain fault handler.
iommufd_fault_iopf_handler() also queued an accepted group in the
IOMMUFD deliver list without removing it from the generic pending list.

When detach or HWPT replacement drops the device's IOPF reference count
to zero, an IOMMU driver may call iopf_queue_remove_device(). That
function responds to and frees groups through the generic pending list
without removing the same groups from IOMMUFD's deliver list or response
xarray. A later read, response, or cleanup can then access the freed
group and cause a UAF.

Fix this by dequeuing an accepted group from the generic pending list
before IOMMUFD queues it for userspace response.
Make iopf_group_response() send a response regardless of pending-list
membership, so the dequeued group can still be completed by IOMMUFD.

Closes: https://lore.kernel.org/all/B4F28798E2E784CA+d29f723c-b2b5-4b67-8d1c-4f7b9b0b27cb@smail.nju.edu.cn/
Fixes: 34765cbc679c ("iommufd: Associate fault object with iommufd_hw_pgtable")
Cc: stable@vger.kernel.org
Tested-by: Peiyang He <peiyang_he@smail.nju.edu.cn>
Assisted-by: Codex:gpt-5.6-sol
Signed-off-by: Peiyang He <peiyang_he@smail.nju.edu.cn>
---
 drivers/iommu/io-pgfault.c     | 24 +++++++++++++++++++-----
 drivers/iommu/iommufd/eventq.c |  2 ++
 include/linux/iommu.h          |  5 +++++
 3 files changed, 26 insertions(+), 5 deletions(-)

diff --git a/drivers/iommu/io-pgfault.c b/drivers/iommu/io-pgfault.c
index cca52a34d0ed..c16ff1fc4b95 100644
--- a/drivers/iommu/io-pgfault.c
+++ b/drivers/iommu/io-pgfault.c
@@ -332,16 +332,30 @@ void iopf_group_response(struct iopf_group *group,
 		.code = status,
 	};
 
-	/* Only send response if there is a fault report pending */
 	mutex_lock(&fault_param->lock);
-	if (!list_empty(&group->pending_node)) {
-		ops->page_response(dev, &group->last_fault, &resp);
-		list_del_init(&group->pending_node);
-	}
+	ops->page_response(dev, &group->last_fault, &resp);
+	list_del_init(&group->pending_node);
 	mutex_unlock(&fault_param->lock);
 }
 EXPORT_SYMBOL_GPL(iopf_group_response);
 
+/**
+ * iopf_group_dequeue - Dequeue a page fault group from the pending list
+ * @group: the group to dequeue
+ *
+ * The fault handler is responsible for responding to the group after
+ * this function returns.
+ */
+void iopf_group_dequeue(struct iopf_group *group)
+{
+	struct iommu_fault_param *fault_param = group->fault_param;
+
+	mutex_lock(&fault_param->lock);
+	list_del_init(&group->pending_node);
+	mutex_unlock(&fault_param->lock);
+}
+EXPORT_SYMBOL_GPL(iopf_group_dequeue);
+
 /**
  * iopf_queue_discard_partial - Remove all pending partial fault
  * @queue: the queue whose partial faults need to be discarded
diff --git a/drivers/iommu/iommufd/eventq.c b/drivers/iommu/iommufd/eventq.c
index 5129e3bf5461..747dd5155121 100644
--- a/drivers/iommu/iommufd/eventq.c
+++ b/drivers/iommu/iommufd/eventq.c
@@ -484,6 +484,8 @@ int iommufd_fault_iopf_handler(struct iopf_group *group)
 	hwpt = group->attach_handle->domain->iommufd_hwpt;
 	fault = hwpt->fault;
 
+	iopf_group_dequeue(group);
+
 	spin_lock(&fault->common.lock);
 	list_add_tail(&group->node, &fault->common.deliver);
 	spin_unlock(&fault->common.lock);
diff --git a/include/linux/iommu.h b/include/linux/iommu.h
index d20aa6f6863a..ac43b8b93f14 100644
--- a/include/linux/iommu.h
+++ b/include/linux/iommu.h
@@ -1704,6 +1704,7 @@ void iopf_free_group(struct iopf_group *group);
 int iommu_report_device_fault(struct device *dev, struct iopf_fault *evt);
 void iopf_group_response(struct iopf_group *group,
 			 enum iommu_page_response_code status);
+void iopf_group_dequeue(struct iopf_group *group);
 #else
 static inline int
 iopf_queue_add_device(struct iopf_queue *queue, struct device *dev)
@@ -1749,5 +1750,9 @@ static inline void iopf_group_response(struct iopf_group *group,
 				       enum iommu_page_response_code status)
 {
 }
+
+static inline void iopf_group_dequeue(struct iopf_group *group)
+{
+}
 #endif /* CONFIG_IOMMU_IOPF */
 #endif /* __LINUX_IOMMU_H */
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PATCH v3 2/2] iommu: Make iopf_group_response() free groups
       [not found]   ` <20260720085017.3998878-1-peiyang_he@smail.nju.edu.cn>
  2026-07-20  8:50     ` [PATCH v3 1/2] " Peiyang He
@ 2026-07-20  8:50     ` Peiyang He
  2026-07-20  9:44       ` Tian, Kevin
  1 sibling, 1 reply; 18+ messages in thread
From: Peiyang He @ 2026-07-20  8:50 UTC (permalink / raw)
  To: joro, will, jgg, kevin.tian
  Cc: robin.murphy, iommu, linux-kernel, baolu.lu, Peiyang He

Every normal iopf_group_response() caller frees the group immediately
after sending the response. Make iopf_group_response() do both operations,
so callers no longer have to free the group separately.

Split the response operation into a helper that requires fault_param->lock,
a helper that acquires the lock without freeing, and the exported helper
that sends the response and frees the group.

iommu_report_device_fault() retains its special stack-allocated abort_group
handling. It uses the non-freeing helper, then keeps the existing choice
between __iopf_free_group() and iopf_free_group().
iopf_queue_remove_device() already holds the fault-parameter lock, so it
uses the locked helper before freeing each pending group.

Suggested-by: Kevin Tian <kevin.tian@intel.com>
Assisted-by: Codex:gpt-5.6-sol
Signed-off-by: Peiyang He <peiyang_he@smail.nju.edu.cn>
---
 drivers/iommu/io-pgfault.c     | 69 +++++++++++++++++++---------------
 drivers/iommu/iommu-sva.c      |  1 -
 drivers/iommu/iommufd/eventq.c |  5 ---
 include/linux/iommu.h          |  5 ---
 4 files changed, 38 insertions(+), 42 deletions(-)

diff --git a/drivers/iommu/io-pgfault.c b/drivers/iommu/io-pgfault.c
index c16ff1fc4b95..52366e2e9d76 100644
--- a/drivers/iommu/io-pgfault.c
+++ b/drivers/iommu/io-pgfault.c
@@ -52,12 +52,11 @@ static void __iopf_free_group(struct iopf_group *group)
 	iopf_put_dev_fault_param(group->fault_param);
 }
 
-void iopf_free_group(struct iopf_group *group)
+static void iopf_free_group(struct iopf_group *group)
 {
 	__iopf_free_group(group);
 	kfree(group);
 }
-EXPORT_SYMBOL_GPL(iopf_free_group);
 
 /* Non-last request of a group. Postpone until the last one. */
 static int report_partial_fault(struct iommu_fault_param *fault_param,
@@ -168,6 +167,35 @@ static void iopf_error_response(struct device *dev, struct iopf_fault *evt)
 	ops->page_response(dev, evt, &resp);
 }
 
+static void __iopf_group_response_locked(struct iopf_group *group,
+					 enum iommu_page_response_code status)
+{
+	struct iommu_fault_param *fault_param = group->fault_param;
+	struct iopf_fault *iopf = &group->last_fault;
+	struct device *dev = fault_param->dev;
+	const struct iommu_ops *ops = dev_iommu_ops(dev);
+	struct iommu_page_response resp = {
+		.pasid = iopf->fault.prm.pasid,
+		.grpid = iopf->fault.prm.grpid,
+		.code = status,
+	};
+
+	lockdep_assert_held(&fault_param->lock);
+
+	ops->page_response(dev, &group->last_fault, &resp);
+	list_del_init(&group->pending_node);
+}
+
+static void __iopf_group_response(struct iopf_group *group,
+				  enum iommu_page_response_code status)
+{
+	struct iommu_fault_param *fault_param = group->fault_param;
+
+	mutex_lock(&fault_param->lock);
+	__iopf_group_response_locked(group, status);
+	mutex_unlock(&fault_param->lock);
+}
+
 /**
  * iommu_report_device_fault() - Report fault event to device driver
  * @dev: the device
@@ -255,10 +283,7 @@ int iommu_report_device_fault(struct device *dev, struct iopf_fault *evt)
 
 	group->attach_handle = attach_handle;
 
-	/*
-	 * On success iopf_handler must call iopf_group_response() and
-	 * iopf_free_group()
-	 */
+	/* On success iopf_handler must call iopf_group_response(). */
 	if (group->attach_handle->domain->iopf_handler(group))
 		goto err_abort;
 
@@ -267,7 +292,7 @@ int iommu_report_device_fault(struct device *dev, struct iopf_fault *evt)
 err_abort:
 	dev_warn_ratelimited(dev, "iopf with pasid %d aborted\n",
 			     fault->prm.pasid);
-	iopf_group_response(group, IOMMU_PAGE_RESP_FAILURE);
+	__iopf_group_response(group, IOMMU_PAGE_RESP_FAILURE);
 	if (group == &abort_group)
 		__iopf_free_group(group);
 	else
@@ -318,24 +343,15 @@ EXPORT_SYMBOL_GPL(iopf_queue_flush_dev);
  * iopf_group_response - Respond a group of page faults
  * @group: the group of faults with the same group id
  * @status: the response code
+ *
+ * The group will be freed as well and must not be accessed after
+ * this function returns.
  */
 void iopf_group_response(struct iopf_group *group,
 			 enum iommu_page_response_code status)
 {
-	struct iommu_fault_param *fault_param = group->fault_param;
-	struct iopf_fault *iopf = &group->last_fault;
-	struct device *dev = group->fault_param->dev;
-	const struct iommu_ops *ops = dev_iommu_ops(dev);
-	struct iommu_page_response resp = {
-		.pasid = iopf->fault.prm.pasid,
-		.grpid = iopf->fault.prm.grpid,
-		.code = status,
-	};
-
-	mutex_lock(&fault_param->lock);
-	ops->page_response(dev, &group->last_fault, &resp);
-	list_del_init(&group->pending_node);
-	mutex_unlock(&fault_param->lock);
+	__iopf_group_response(group, status);
+	iopf_free_group(group);
 }
 EXPORT_SYMBOL_GPL(iopf_group_response);
 
@@ -468,7 +484,6 @@ void iopf_queue_remove_device(struct iopf_queue *queue, struct device *dev)
 	struct iopf_group *group, *temp;
 	struct dev_iommu *param = dev->iommu;
 	struct iommu_fault_param *fault_param;
-	const struct iommu_ops *ops = dev_iommu_ops(dev);
 
 	mutex_lock(&queue->lock);
 	mutex_lock(&param->lock);
@@ -483,15 +498,7 @@ void iopf_queue_remove_device(struct iopf_queue *queue, struct device *dev)
 		kfree(partial_iopf);
 
 	list_for_each_entry_safe(group, temp, &fault_param->faults, pending_node) {
-		struct iopf_fault *iopf = &group->last_fault;
-		struct iommu_page_response resp = {
-			.pasid = iopf->fault.prm.pasid,
-			.grpid = iopf->fault.prm.grpid,
-			.code = IOMMU_PAGE_RESP_INVALID
-		};
-
-		ops->page_response(dev, iopf, &resp);
-		list_del_init(&group->pending_node);
+		__iopf_group_response_locked(group, IOMMU_PAGE_RESP_INVALID);
 		iopf_free_group(group);
 	}
 	mutex_unlock(&fault_param->lock);
diff --git a/drivers/iommu/iommu-sva.c b/drivers/iommu/iommu-sva.c
index bc7c7232a43e..caccaa315461 100644
--- a/drivers/iommu/iommu-sva.c
+++ b/drivers/iommu/iommu-sva.c
@@ -292,7 +292,6 @@ static void iommu_sva_handle_iopf(struct work_struct *work)
 	}
 
 	iopf_group_response(group, status);
-	iopf_free_group(group);
 }
 
 static int iommu_sva_iopf_handler(struct iopf_group *group)
diff --git a/drivers/iommu/iommufd/eventq.c b/drivers/iommu/iommufd/eventq.c
index 747dd5155121..62110fe8f78f 100644
--- a/drivers/iommu/iommufd/eventq.c
+++ b/drivers/iommu/iommufd/eventq.c
@@ -40,7 +40,6 @@ void iommufd_auto_response_faults(struct iommufd_hw_pagetable *hwpt,
 	list_for_each_entry_safe(group, next, &free_list, node) {
 		list_del(&group->node);
 		iopf_group_response(group, IOMMU_PAGE_RESP_INVALID);
-		iopf_free_group(group);
 	}
 
 	xa_for_each(&fault->response, index, group) {
@@ -48,7 +47,6 @@ void iommufd_auto_response_faults(struct iommufd_hw_pagetable *hwpt,
 			continue;
 		xa_erase(&fault->response, index);
 		iopf_group_response(group, IOMMU_PAGE_RESP_INVALID);
-		iopf_free_group(group);
 	}
 	mutex_unlock(&fault->mutex);
 }
@@ -70,12 +68,10 @@ void iommufd_fault_destroy(struct iommufd_object *obj)
 	list_for_each_entry_safe(group, next, &fault->common.deliver, node) {
 		list_del(&group->node);
 		iopf_group_response(group, IOMMU_PAGE_RESP_INVALID);
-		iopf_free_group(group);
 	}
 	xa_for_each(&fault->response, index, group) {
 		xa_erase(&fault->response, index);
 		iopf_group_response(group, IOMMU_PAGE_RESP_INVALID);
-		iopf_free_group(group);
 	}
 	xa_destroy(&fault->response);
 	mutex_destroy(&fault->mutex);
@@ -217,7 +213,6 @@ static ssize_t iommufd_fault_fops_write(struct file *filep, const char __user *b
 		}
 
 		iopf_group_response(group, response.code);
-		iopf_free_group(group);
 		done += response_size;
 	}
 	mutex_unlock(&fault->mutex);
diff --git a/include/linux/iommu.h b/include/linux/iommu.h
index ac43b8b93f14..f16c88d92661 100644
--- a/include/linux/iommu.h
+++ b/include/linux/iommu.h
@@ -1700,7 +1700,6 @@ int iopf_queue_flush_dev(struct device *dev);
 struct iopf_queue *iopf_queue_alloc(const char *name);
 void iopf_queue_free(struct iopf_queue *queue);
 int iopf_queue_discard_partial(struct iopf_queue *queue);
-void iopf_free_group(struct iopf_group *group);
 int iommu_report_device_fault(struct device *dev, struct iopf_fault *evt);
 void iopf_group_response(struct iopf_group *group,
 			 enum iommu_page_response_code status);
@@ -1736,10 +1735,6 @@ static inline int iopf_queue_discard_partial(struct iopf_queue *queue)
 	return -ENODEV;
 }
 
-static inline void iopf_free_group(struct iopf_group *group)
-{
-}
-
 static inline int
 iommu_report_device_fault(struct device *dev, struct iopf_fault *evt)
 {
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* RE: [PATCH v3 1/2] iommu/iommufd: Fix IOPF group ownership UAF
  2026-07-20  8:50     ` [PATCH v3 1/2] " Peiyang He
@ 2026-07-20  9:44       ` Tian, Kevin
  2026-07-26 16:20       ` Jason Gunthorpe
  1 sibling, 0 replies; 18+ messages in thread
From: Tian, Kevin @ 2026-07-20  9:44 UTC (permalink / raw)
  To: Peiyang He, joro@8bytes.org, will@kernel.org, jgg@ziepe.ca
  Cc: robin.murphy@arm.com, iommu@lists.linux.dev,
	linux-kernel@vger.kernel.org, baolu.lu@linux.intel.com,
	stable@vger.kernel.org

> From: Peiyang He <peiyang_he@smail.nju.edu.cn>
> Sent: Monday, July 20, 2026 4:50 PM
> 
> iopf_group_alloc() links each last-page IOPF group into the generic IOPF
> pending list before invoking the domain fault handler.
> iommufd_fault_iopf_handler() also queued an accepted group in the
> IOMMUFD deliver list without removing it from the generic pending list.
> 
> When detach or HWPT replacement drops the device's IOPF reference count
> to zero, an IOMMU driver may call iopf_queue_remove_device(). That
> function responds to and frees groups through the generic pending list
> without removing the same groups from IOMMUFD's deliver list or response
> xarray. A later read, response, or cleanup can then access the freed
> group and cause a UAF.
> 
> Fix this by dequeuing an accepted group from the generic pending list
> before IOMMUFD queues it for userspace response.
> Make iopf_group_response() send a response regardless of pending-list
> membership, so the dequeued group can still be completed by IOMMUFD.
> 
> Closes: https://lore.kernel.org/all/B4F28798E2E784CA+d29f723c-b2b5-4b67-
> 8d1c-4f7b9b0b27cb@smail.nju.edu.cn/
> Fixes: 34765cbc679c ("iommufd: Associate fault object with
> iommufd_hw_pgtable")
> Cc: stable@vger.kernel.org
> Tested-by: Peiyang He <peiyang_he@smail.nju.edu.cn>
> Assisted-by: Codex:gpt-5.6-sol
> Signed-off-by: Peiyang He <peiyang_he@smail.nju.edu.cn>

Reviewed-by: Kevin Tian <kevin.tian@intel.com>

^ permalink raw reply	[flat|nested] 18+ messages in thread

* RE: [PATCH v3 2/2] iommu: Make iopf_group_response() free groups
  2026-07-20  8:50     ` [PATCH v3 2/2] iommu: Make iopf_group_response() free groups Peiyang He
@ 2026-07-20  9:44       ` Tian, Kevin
  0 siblings, 0 replies; 18+ messages in thread
From: Tian, Kevin @ 2026-07-20  9:44 UTC (permalink / raw)
  To: Peiyang He, joro@8bytes.org, will@kernel.org, jgg@ziepe.ca
  Cc: robin.murphy@arm.com, iommu@lists.linux.dev,
	linux-kernel@vger.kernel.org, baolu.lu@linux.intel.com

> From: Peiyang He <peiyang_he@smail.nju.edu.cn>
> Sent: Monday, July 20, 2026 4:50 PM
> 
> Every normal iopf_group_response() caller frees the group immediately
> after sending the response. Make iopf_group_response() do both operations,
> so callers no longer have to free the group separately.
> 
> Split the response operation into a helper that requires fault_param->lock,
> a helper that acquires the lock without freeing, and the exported helper
> that sends the response and frees the group.
> 
> iommu_report_device_fault() retains its special stack-allocated abort_group
> handling. It uses the non-freeing helper, then keeps the existing choice
> between __iopf_free_group() and iopf_free_group().
> iopf_queue_remove_device() already holds the fault-parameter lock, so it
> uses the locked helper before freeing each pending group.
> 
> Suggested-by: Kevin Tian <kevin.tian@intel.com>
> Assisted-by: Codex:gpt-5.6-sol
> Signed-off-by: Peiyang He <peiyang_he@smail.nju.edu.cn>

Reviewed-by: Kevin Tian <kevin.tian@intel.com>

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH v3 0/2] iommu/iommufd: Fix IOPF group ownership UAF
  2026-07-20  8:50   ` [PATCH v3 0/2] " Peiyang He
@ 2026-07-26 12:43     ` Will Deacon
  2026-07-26 16:15       ` Jason Gunthorpe
  0 siblings, 1 reply; 18+ messages in thread
From: Will Deacon @ 2026-07-26 12:43 UTC (permalink / raw)
  To: Peiyang He
  Cc: joro, jgg, kevin.tian, robin.murphy, iommu, linux-kernel,
	baolu.lu

On Mon, Jul 20, 2026 at 04:50:15PM +0800, Peiyang He wrote:
> This series fixes a UAF caused by an IOPF group being owned both by the
> generic pending list and IOMMUFD's userspace fault queues:
> 
>   1/2 Minimum fix: dequeue accepted groups before IOMMUFD queues them
>       for userspace response, then remove the check on list_empty()
>       in iopf_group_response().
> 
>   2/2 Refactor: make iopf_group_response() free the group as well
>       after responding.
> 
> Changes in v3:
>   - split the v2 patch into a minimum UAF fix and a follow-up cleanup
>     (suggested by Kevin)

Oops, I lost track of this as all the versions of the patches are in the
same thread. Please try to avoid that, as it makes it hard to track in
an email client and can also confuse tools such as b4.

Anyway, it looks like v3 is ready to go. Jason, is this something you
are planning to pick up or shall we take it via the IOMMU tree? It looks
like patch 1 should go in for -rc.

Will

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH v3 0/2] iommu/iommufd: Fix IOPF group ownership UAF
  2026-07-26 12:43     ` Will Deacon
@ 2026-07-26 16:15       ` Jason Gunthorpe
  0 siblings, 0 replies; 18+ messages in thread
From: Jason Gunthorpe @ 2026-07-26 16:15 UTC (permalink / raw)
  To: Will Deacon
  Cc: Peiyang He, joro, kevin.tian, robin.murphy, iommu, linux-kernel,
	baolu.lu

On Sun, Jul 26, 2026 at 01:43:55PM +0100, Will Deacon wrote:
> On Mon, Jul 20, 2026 at 04:50:15PM +0800, Peiyang He wrote:
> > This series fixes a UAF caused by an IOPF group being owned both by the
> > generic pending list and IOMMUFD's userspace fault queues:
> > 
> >   1/2 Minimum fix: dequeue accepted groups before IOMMUFD queues them
> >       for userspace response, then remove the check on list_empty()
> >       in iopf_group_response().
> > 
> >   2/2 Refactor: make iopf_group_response() free the group as well
> >       after responding.
> > 
> > Changes in v3:
> >   - split the v2 patch into a minimum UAF fix and a follow-up cleanup
> >     (suggested by Kevin)
> 
> Oops, I lost track of this as all the versions of the patches are in the
> same thread. Please try to avoid that, as it makes it hard to track in
> an email client and can also confuse tools such as b4.
> 
> Anyway, it looks like v3 is ready to go. Jason, is this something you
> are planning to pick up or shall we take it via the IOMMU tree? It looks
> like patch 1 should go in for -rc.

Yeah I have a bunch of iommufd thing for rc I'l pick it

Thanks,
Jason

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH v3 1/2] iommu/iommufd: Fix IOPF group ownership UAF
  2026-07-20  8:50     ` [PATCH v3 1/2] " Peiyang He
  2026-07-20  9:44       ` Tian, Kevin
@ 2026-07-26 16:20       ` Jason Gunthorpe
  1 sibling, 0 replies; 18+ messages in thread
From: Jason Gunthorpe @ 2026-07-26 16:20 UTC (permalink / raw)
  To: Peiyang He
  Cc: joro, will, kevin.tian, robin.murphy, iommu, linux-kernel,
	baolu.lu, stable

On Mon, Jul 20, 2026 at 04:50:16PM +0800, Peiyang He wrote:
> iopf_group_alloc() links each last-page IOPF group into the generic IOPF
> pending list before invoking the domain fault handler.
> iommufd_fault_iopf_handler() also queued an accepted group in the
> IOMMUFD deliver list without removing it from the generic pending list.
> 
> When detach or HWPT replacement drops the device's IOPF reference count
> to zero, an IOMMU driver may call iopf_queue_remove_device(). That
> function responds to and frees groups through the generic pending list
> without removing the same groups from IOMMUFD's deliver list or response
> xarray. A later read, response, or cleanup can then access the freed
> group and cause a UAF.
> 
> Fix this by dequeuing an accepted group from the generic pending list
> before IOMMUFD queues it for userspace response.
> Make iopf_group_response() send a response regardless of pending-list
> membership, so the dequeued group can still be completed by IOMMUFD.
> 
> Closes: https://lore.kernel.org/all/B4F28798E2E784CA+d29f723c-b2b5-4b67-8d1c-4f7b9b0b27cb@smail.nju.edu.cn/
> Fixes: 34765cbc679c ("iommufd: Associate fault object with iommufd_hw_pgtable")
> Cc: stable@vger.kernel.org
> Tested-by: Peiyang He <peiyang_he@smail.nju.edu.cn>
> Assisted-by: Codex:gpt-5.6-sol
> Signed-off-by: Peiyang He <peiyang_he@smail.nju.edu.cn>
> ---
>  drivers/iommu/io-pgfault.c     | 24 +++++++++++++++++++-----
>  drivers/iommu/iommufd/eventq.c |  2 ++
>  include/linux/iommu.h          |  5 +++++
>  3 files changed, 26 insertions(+), 5 deletions(-)

Applied to for-rc

Thanks,
Jason

^ permalink raw reply	[flat|nested] 18+ messages in thread

end of thread, other threads:[~2026-07-26 16:20 UTC | newest]

Thread overview: 18+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-07  6:12 [BUG] iommu: KASAN slab-use-after-free in iommufd_auto_response_faults Peiyang He
2026-07-07 10:36 ` Peiyang He
2026-07-08 14:38   ` Ankit Soni
2026-07-13  6:10 ` Tian, Kevin
2026-07-13  9:01   ` Peiyang He
2026-07-15  4:24 ` [PATCH] iommu/iommufd: Fix IOPF group ownership UAF Peiyang He
2026-07-17  8:00   ` Tian, Kevin
2026-07-18  4:55     ` Peiyang He
2026-07-18  4:32 ` [PATCH v2] " Peiyang He
2026-07-20  5:13   ` Tian, Kevin
2026-07-20  8:50   ` [PATCH v3 0/2] " Peiyang He
2026-07-26 12:43     ` Will Deacon
2026-07-26 16:15       ` Jason Gunthorpe
     [not found]   ` <20260720085017.3998878-1-peiyang_he@smail.nju.edu.cn>
2026-07-20  8:50     ` [PATCH v3 1/2] " Peiyang He
2026-07-20  9:44       ` Tian, Kevin
2026-07-26 16:20       ` Jason Gunthorpe
2026-07-20  8:50     ` [PATCH v3 2/2] iommu: Make iopf_group_response() free groups Peiyang He
2026-07-20  9:44       ` Tian, Kevin

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.