Re: [PATCH] iommu/s390: Implement blocking domain

Jason Gunthorpe
2024-08-08T13:44:54Z

On Tue, Aug 06, 2024 at 03:45:15PM +0200, Niklas Schnelle wrote:
> This fixes a crash when surprise hot-unplugging a PCI device. This crash
> happens because during hot-unplug __iommu_group_set_domain_nofail()
> attaching the default domain fails when the platform no longer
> recognizes the device as it has already been removed and we end up with
> a NULL domain pointer and UAF.

Huh?

A device can't be removed before telling the iommu subsystem about
it. How did the domain become NULL asynchronously while the iommu side
thought it was still alive?? That seems like the main bug here..

> Note: I somewhat suspect this to be related to the following discussion
> or at least we have seen the same backtraces in reports that we suspect
> to be caused by the issue fixed with this patch.

No, it shouldn't be. That bug is because the netstack is continuing to
use a struct device with the DMA API after the device driver has been
removed. That is just illegal.

> diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
> index ed6c5cb60c5a..91b3b23bf55c 100644
> --- a/drivers/iommu/iommu.c
> +++ b/drivers/iommu/iommu.c
> @@ -119,8 +119,11 @@ static int __iommu_group_set_domain(struct iommu_group *group,
> static void __iommu_group_set_domain_nofail(struct iommu_group *group,
> struct iommu_domain *new_domain)
> {
> - WARN_ON(__iommu_group_set_domain_internal(
> - group, new_domain, IOMMU_SET_DOMAIN_MUST_SUCCEED));
> + int ret = __iommu_group_set_domain_internal(
> + group, new_domain, IOMMU_SET_DOMAIN_MUST_SUCCEED);
> +
> + /* Allow attach to fail when the device is gone */
> + WARN_ON(ret && ret != -ENODEV);
> }

Like this doesn't really make sense. Until the iommu subystem removes
the device from the group it really cannot be "gone".

The hypervisor could fail attachment because the hypervisor has
already fenced the device. Sure, that make sense, but it is not really
that the device is gone from a Linux perspective, it is just now in a
forced-blocked state.

Like Lu says, if we need to add a new flow for devices that are now
force-blocking and cannot be changed then it will need its own error
code.

But what is the backtrace that runs into this warn on? VFIO exiting
and trying to put the device back to the DMA API?

Though I feel like more is needed here if you expect to allow the
nofail version of this to actually fail.. For instance a force-blocked
device should block driver binding through the dma_owner APIs.

> +static int blocking_domain_attach_device(struct iommu_domain *domain,
> + struct device *dev)
> +{
> + struct s390_domain *s390_domain = to_s390_domain(domain);
> + struct zpci_dev *zdev = to_zpci_dev(dev);
> + unsigned long flags;
> +
> + if (!zdev)
> + return 0;
> +
> + /* Detach sets the blocking domain */
> + if (zdev->s390_domain)
> + s390_iommu_detach_device(&zdev->s390_domain->domain, dev);

When I've done these conversions on other drivers it was the case that
zdev->s390_domain is never NULL. Instead probe_device immediately
starts it as a blocking_domain or fails to probe.

This way we don't ever have the ill defined notion of NULL here.

> @@ -777,6 +812,8 @@ static int __init s390_iommu_init(void)
> subsys_initcall(s390_iommu_init);
>
> static const struct iommu_ops s390_iommu_ops = {
> + .blocked_domain = &s390_blocking_domain.domain,
> + .release_domain = &s390_blocking_domain.domain,

If you set release_domain then remove s390_iommu_release_device(), it
is the same code.

Jason