XCP unplug currently restores partition minors onto the synthetic
p_ddev and frees that device while files opened through those
minors may still be active. Those files took drm_device references
on the primary ddev because the minors were redirected at XCP
alloc time. After unplug reverses the redirection, close paths
operate on the already-freed p_ddev, causing a use-after-free
and p_ddev refcount underflow while leaking the matching
primary-device references.

Split teardown into two phases. amdgpu_xcp_dev_unplug() only
calls drm_dev_unplug() so new opens fail, and leaves minor->dev
pointing at the primary device. amdgpu_xcp_dev_fini() restores
the partition device pointers and frees p_ddev from
amdgpu_driver_release_kms(), after the primary drm_device's last
reference is dropped and every redirected drm_file has completed
drm_minor_release().

Signed-off-by: Luqmaan Irshad <[email protected]>
---
 drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c |  6 +++
 drivers/gpu/drm/amd/amdgpu/amdgpu_xcp.c | 51 +++++++++++++++++++++----
 drivers/gpu/drm/amd/amdgpu/amdgpu_xcp.h |  1 +
 3 files changed, 51 insertions(+), 7 deletions(-)

diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c 
b/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c
index f2f179421c4d..403ca8fb1bc9 100644
--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c
+++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c
@@ -1638,6 +1638,12 @@ void amdgpu_driver_release_kms(struct drm_device *dev)
 {
        struct amdgpu_device *adev = drm_to_adev(dev);
 
+       /*
+        * XCP minors redirect their device reference to this DRM device.
+        * Reaching its final release guarantees that no open files still
+        * reference those minors, so the backing XCP devices can now be freed.
+        */
+       amdgpu_xcp_dev_fini(adev);
        amdgpu_device_fini_sw(adev);
        pci_set_drvdata(adev->pdev, NULL);
 }
diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_xcp.c 
b/drivers/gpu/drm/amd/amdgpu/amdgpu_xcp.c
index a8c79a66df1d..295cd9567512 100644
--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_xcp.c
+++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_xcp.c
@@ -412,7 +412,6 @@ int amdgpu_xcp_dev_register(struct amdgpu_device *adev,
 
 void amdgpu_xcp_dev_unplug(struct amdgpu_device *adev)
 {
-       struct drm_device *p_ddev;
        int i;
 
        if (!adev->xcp_mgr)
@@ -422,12 +421,50 @@ void amdgpu_xcp_dev_unplug(struct amdgpu_device *adev)
                if (!adev->xcp_mgr->xcp[i].ddev)
                        break;
 
-               p_ddev = adev->xcp_mgr->xcp[i].ddev;
-               drm_dev_unplug(p_ddev);
-               p_ddev->render->dev = adev->xcp_mgr->xcp[i].rdev;
-               p_ddev->primary->dev = adev->xcp_mgr->xcp[i].pdev;
-               p_ddev->driver =  adev->xcp_mgr->xcp[i].driver;
-               p_ddev->vma_offset_manager = 
adev->xcp_mgr->xcp[i].vma_offset_manager;
+               /*
+                * Stop new opens, but keep the partition DRM device and its
+                * minors alive. Existing drm_file objects still point at these
+                * minors, whose ->dev was redirected to the primary DRM device
+                * when the XCP device was allocated.
+                */
+               drm_dev_unplug(adev->xcp_mgr->xcp[i].ddev);
+       }
+}
+
+void amdgpu_xcp_dev_fini(struct amdgpu_device *adev)
+{
+       struct amdgpu_xcp *xcp;
+       struct drm_device *p_ddev;
+       int i;
+
+       if (!adev->xcp_mgr)
+               return;
+
+       for (i = 1; i < MAX_XCP; i++) {
+               xcp = &adev->xcp_mgr->xcp[i];
+               if (!xcp->ddev)
+                       break;
+
+               p_ddev = xcp->ddev;
+
+               /*
+                * Normally this was done by amdgpu_xcp_dev_unplug(). Also
+                * cover initialization unwind and other release paths.
+                */
+               if (p_ddev->registered)
+                       drm_dev_unplug(p_ddev);
+
+               /*
+                * The primary DRM device is in its final release callback, so
+                * every file that acquired a reference through these redirected
+                * minors has completed drm_minor_release(). It is now safe to
+                * restore the partition device and free it.
+                */
+               p_ddev->render->dev = xcp->rdev;
+               p_ddev->primary->dev = xcp->pdev;
+               p_ddev->driver = xcp->driver;
+               p_ddev->vma_offset_manager = xcp->vma_offset_manager;
+               xcp->ddev = NULL;
                amdgpu_xcp_drm_dev_free(p_ddev);
        }
 }
diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_xcp.h 
b/drivers/gpu/drm/amd/amdgpu/amdgpu_xcp.h
index 33157409eda0..e2fd32af9932 100644
--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_xcp.h
+++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_xcp.h
@@ -181,6 +181,7 @@ int amdgpu_xcp_get_inst_details(struct amdgpu_xcp *xcp,
 int amdgpu_xcp_dev_register(struct amdgpu_device *adev,
                                const struct pci_device_id *ent);
 void amdgpu_xcp_dev_unplug(struct amdgpu_device *adev);
+void amdgpu_xcp_dev_fini(struct amdgpu_device *adev);
 int amdgpu_xcp_open_device(struct amdgpu_device *adev,
                           struct amdgpu_fpriv *fpriv,
                           struct drm_file *file_priv);
-- 
2.53.0

Reply via email to