The PCI reset IOMMU helpers read pdev->dev.iommu_group and dev->iommu before taking any lock, and the whole PCI reset body (FLR alone sleeps 100ms) runs between their prepare and done halves -- meanwhile the iommu_device_unregister() traversal can tear the device's group away entirely, so the helper's post-reset half locks an already-freed group (use-after-free spinlock) or walks &NULL->devices (NULL deref oops). The kernel's own comment above these helpers states the contract -- "callers must ensure there will be no racy iommu_release_device() call, which otherwise would UAF the dev->iommu_group pointer" (drivers/iommu/iommu.c:4027-4032) -- but the unregister traversal is precisely an iommu_release_device() source that no reset-path caller can ensure against: it takes no device lock, so the device_lock the reset path holds (which does serialize device_del) protects nothing here. Concretely, on mainline 7.3: pci_dev_reset_iommu_prepare() (iommu.c:4034) reads the group at :4036 and tests dev_has_iommu() at :4042 before guard(mutex)(&group->mutex) at :4045; pci_dev_reset_iommu_done() (:4146) has the same shape at :4148, :4153, :4156 and then calls __dev_to_gdev() at :4158, which walks group->devices. The reset body bracketed by these helpers includes five call-site pairs in drivers/pci/pci.c (:4426/:4446 with the FLR msleep(100) at :4442, :4503/:4524 with another at :4520, :4561/:4578, :5019/:5031, :5056/:5076) -- a >=100ms window during which the traversal's millisecond-scale teardown fits with room to spare. Unlike the removal-side race I reported earlier, triggering needs NO concurrent device_del: a client device performing an FLR or bus reset (administrator "echo 1 > /sys/bus/pci/devices//reset", or a driver-initiated reset such as NIC reconfiguration) while the IOMMU instance is unregistered (virtio-iommu sysfs unbind, platform IOMMU) is enough -- the reset body's width makes this the easiest of the unregister-race family to hit. The detailed description: * Test environment / affected version Linux version 7.3.0 (gcc 13.3.0, GNU ld 2.42) #1 SMP PREEMPT_DYNAMIC -- vanilla mainline 7.3 release tarball (kernel.org, unmodified). All functions cited are byte-identical in 7.1.5 and 7.3; line numbers are from 7.3. This report is source-level: my test environment provides no PCI+ATS client to drive the E side, so the interleaving itself was not reproduced dynamically; the shared teardown half (the traversal freeing a group through the unconditional iommu_group_put() at iommu.c:775) is the same component my removal-side report demonstrated with KASAN. * Mechanism The helper pair's shape (entry reads precede every lock): int pci_dev_reset_iommu_prepare(struct pci_dev *pdev) /* :4034 */ { struct iommu_group *group = pdev->dev.iommu_group; /* :4036 lock-free */ ... if (!pci_ats_supported(pdev) || !dev_has_iommu(&pdev->dev)) /* :4042, second lock-free read */ return 0; guard(mutex)(&group->mutex); /* :4045 */ ... void pci_dev_reset_iommu_done(struct pci_dev *pdev) /* :4146 */ { struct iommu_group *group = pdev->dev.iommu_group; /* :4148 */ ... if (!pci_ats_supported(pdev) || !dev_has_iommu(&pdev->dev)) /* :4153 */ return; guard(mutex)(&group->mutex); /* :4156 */ gdev = __dev_to_gdev(&pdev->dev); /* :4158: walks group->devices */ A side: iommu_device_unregister() -> bus_for_each_dev -> remove_iommu_group() (:273) -> iommu_release_device() (:778) -> __iommu_group_remove_device() (:751) clears dev->iommu_group under the group lock (:766; also via iommu_deinit_device at :611) and, with the unconditional iommu_group_put() at :775, can free the group outright when this device held the last reference. Interleaving (done() half shown; prepare is symmetric): E: group = pdev->dev.iommu_group; /* :4148, reads G */ ... reset body: msleep(100) ... /* >=100ms */ A: traversal completes: group lock teardown, dev->iommu_group = NULL, param freed, :775 put drops the last ref -> G freed E: dev_has_iommu(&pdev->dev) /* :4153: - read lands before A's param clear -> true -> guard(mutex)(&group->mutex) /* :4156: mutex_lock on FREED group -> UAF (spinlock on freed/ reused memory) */ - G survived (other refs) -> __dev_to_gdev(&pdev->dev) /* :4158: re-reads dev->iommu_group == NULL -> for_each_ group_device(NULL) walks &NULL->devices -> NULL deref oops */ Symmetric variant: :4148 already reads NULL (after A's :766) while :4153 still sees the param -> guard(mutex) on a NULL group pointer. Why the existing locks do not help: the reset path holds the device lock, which serializes device_del (the case the :4027 comment was designed for -- PCI sysfs is removed before the removal notification). The unregister traversal is not device_del: it walks the bus's klist and calls iommu_release_device() directly, taking no client device lock and none of the locks the helpers take before their entry reads. * Trigger model Any runtime-unregistrable IOMMU instance (virtio-iommu sysfs unbind; platform IOMMUs without suppress_bind_attrs; PCI hot-unplug paths) times a client device undergoing FLR/bus reset during unregistration: # terminal A (root) echo > /sys/bus/virtio/drivers/virtio_iommu/unbind # terminal B (root) echo 1 > /sys/bus/pci/devices//reset # loop, or let the # driver reconfig # trigger FLR The >=100ms reset body makes the hit probability far higher than the removal-side race's millisecond overlap. * Evidence Source-level on 7.3, function by function (every anchor is quoted in the mechanism section above; a consolidated annotated list is available on request). The A-side teardown that the interleaving consumes -- the traversal entering iommu_release_device() without any of the probe-side locks and freeing a group via the unconditional :775 put -- is the identical component demonstrated with KASAN in my removal-side report (reproducer attached again here for convenience; its "ab" action drives exactly that teardown half). Thanks for taking a look. Regards, Hanxiang Xu xuhx@hust.edu.cn