mirror of
https://github.com/torvalds/linux.git
synced 2025-04-12 16:47:42 +00:00
scsi: ufs: core: Fix use-after free in init error and remove paths
devm_blk_crypto_profile_init() registers a cleanup handler to run when the associated (platform-) device is being released. For UFS, the crypto private data and pointers are stored as part of the ufs_hba's data structure 'struct ufs_hba::crypto_profile'. This structure is allocated as part of the underlying ufshcd and therefore Scsi_host allocation. During driver release or during error handling in ufshcd_pltfrm_init(), this structure is released as part of ufshcd_dealloc_host() before the (platform-) device associated with the crypto call above is released. Once this device is released, the crypto cleanup code will run, using the just-released 'struct ufs_hba::crypto_profile'. This causes a use-after-free situation: Call trace: kfree+0x60/0x2d8 (P) kvfree+0x44/0x60 blk_crypto_profile_destroy_callback+0x28/0x70 devm_action_release+0x1c/0x30 release_nodes+0x6c/0x108 devres_release_all+0x98/0x100 device_unbind_cleanup+0x20/0x70 really_probe+0x218/0x2d0 In other words, the initialisation code flow is: platform-device probe ufshcd_pltfrm_init() ufshcd_alloc_host() scsi_host_alloc() allocation of struct ufs_hba creation of scsi-host devices devm_blk_crypto_profile_init() devm registration of cleanup handler using platform-device and during error handling of ufshcd_pltfrm_init() or during driver removal: ufshcd_dealloc_host() scsi_host_put() put_device(scsi-host) release of struct ufs_hba put_device(platform-device) crypto cleanup handler To fix this use-after free, change ufshcd_alloc_host() to register a devres action to automatically cleanup the underlying SCSI device on ufshcd destruction, without requiring explicit calls to ufshcd_dealloc_host(). This way: * the crypto profile and all other ufs_hba-owned resources are destroyed before SCSI (as they've been registered after) * a memleak is plugged in tc-dwc-g210-pci.c remove() as a side-effect * EXPORT_SYMBOL_GPL(ufshcd_dealloc_host) can be removed fully as it's not needed anymore * no future drivers using ufshcd_alloc_host() could ever forget adding the cleanup Fixes: cb77cb5abe1f ("blk-crypto: rename blk_keyslot_manager to blk_crypto_profile") Fixes: d76d9d7d1009 ("scsi: ufs: use devm_blk_ksm_init()") Cc: stable@vger.kernel.org Signed-off-by: André Draszik <andre.draszik@linaro.org> Link: https://lore.kernel.org/r/20250124-ufshcd-fix-v4-1-c5d0144aae59@linaro.org Reviewed-by: Bean Huo <beanhuo@micron.com> Reviewed-by: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org> Acked-by: Eric Biggers <ebiggers@kernel.org> Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
This commit is contained in:
parent
9ff7c383b8
commit
f8fb2403dd
@ -10226,16 +10226,6 @@ int ufshcd_system_thaw(struct device *dev)
|
||||
EXPORT_SYMBOL_GPL(ufshcd_system_thaw);
|
||||
#endif /* CONFIG_PM_SLEEP */
|
||||
|
||||
/**
|
||||
* ufshcd_dealloc_host - deallocate Host Bus Adapter (HBA)
|
||||
* @hba: pointer to Host Bus Adapter (HBA)
|
||||
*/
|
||||
void ufshcd_dealloc_host(struct ufs_hba *hba)
|
||||
{
|
||||
scsi_host_put(hba->host);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(ufshcd_dealloc_host);
|
||||
|
||||
/**
|
||||
* ufshcd_set_dma_mask - Set dma mask based on the controller
|
||||
* addressing capability
|
||||
@ -10254,12 +10244,26 @@ static int ufshcd_set_dma_mask(struct ufs_hba *hba)
|
||||
return dma_set_mask_and_coherent(hba->dev, DMA_BIT_MASK(32));
|
||||
}
|
||||
|
||||
/**
|
||||
* ufshcd_devres_release - devres cleanup handler, invoked during release of
|
||||
* hba->dev
|
||||
* @host: pointer to SCSI host
|
||||
*/
|
||||
static void ufshcd_devres_release(void *host)
|
||||
{
|
||||
scsi_host_put(host);
|
||||
}
|
||||
|
||||
/**
|
||||
* ufshcd_alloc_host - allocate Host Bus Adapter (HBA)
|
||||
* @dev: pointer to device handle
|
||||
* @hba_handle: driver private handle
|
||||
*
|
||||
* Return: 0 on success, non-zero value on failure.
|
||||
*
|
||||
* NOTE: There is no corresponding ufshcd_dealloc_host() because this function
|
||||
* keeps track of its allocations using devres and deallocates everything on
|
||||
* device removal automatically.
|
||||
*/
|
||||
int ufshcd_alloc_host(struct device *dev, struct ufs_hba **hba_handle)
|
||||
{
|
||||
@ -10281,6 +10285,13 @@ int ufshcd_alloc_host(struct device *dev, struct ufs_hba **hba_handle)
|
||||
err = -ENOMEM;
|
||||
goto out_error;
|
||||
}
|
||||
|
||||
err = devm_add_action_or_reset(dev, ufshcd_devres_release,
|
||||
host);
|
||||
if (err)
|
||||
return dev_err_probe(dev, err,
|
||||
"failed to add ufshcd dealloc action\n");
|
||||
|
||||
host->nr_maps = HCTX_TYPE_POLL + 1;
|
||||
hba = shost_priv(host);
|
||||
hba->host = host;
|
||||
|
@ -562,7 +562,6 @@ static void ufshcd_pci_remove(struct pci_dev *pdev)
|
||||
pm_runtime_forbid(&pdev->dev);
|
||||
pm_runtime_get_noresume(&pdev->dev);
|
||||
ufshcd_remove(hba);
|
||||
ufshcd_dealloc_host(hba);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -605,7 +604,6 @@ ufshcd_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
|
||||
err = ufshcd_init(hba, mmio_base, pdev->irq);
|
||||
if (err) {
|
||||
dev_err(&pdev->dev, "Initialization failed\n");
|
||||
ufshcd_dealloc_host(hba);
|
||||
return err;
|
||||
}
|
||||
|
||||
|
@ -465,21 +465,17 @@ int ufshcd_pltfrm_init(struct platform_device *pdev,
|
||||
struct device *dev = &pdev->dev;
|
||||
|
||||
mmio_base = devm_platform_ioremap_resource(pdev, 0);
|
||||
if (IS_ERR(mmio_base)) {
|
||||
err = PTR_ERR(mmio_base);
|
||||
goto out;
|
||||
}
|
||||
if (IS_ERR(mmio_base))
|
||||
return PTR_ERR(mmio_base);
|
||||
|
||||
irq = platform_get_irq(pdev, 0);
|
||||
if (irq < 0) {
|
||||
err = irq;
|
||||
goto out;
|
||||
}
|
||||
if (irq < 0)
|
||||
return irq;
|
||||
|
||||
err = ufshcd_alloc_host(dev, &hba);
|
||||
if (err) {
|
||||
dev_err(dev, "Allocation failed\n");
|
||||
goto out;
|
||||
return err;
|
||||
}
|
||||
|
||||
hba->vops = vops;
|
||||
@ -488,13 +484,13 @@ int ufshcd_pltfrm_init(struct platform_device *pdev,
|
||||
if (err) {
|
||||
dev_err(dev, "%s: clock parse failed %d\n",
|
||||
__func__, err);
|
||||
goto dealloc_host;
|
||||
return err;
|
||||
}
|
||||
err = ufshcd_parse_regulator_info(hba);
|
||||
if (err) {
|
||||
dev_err(dev, "%s: regulator init failed %d\n",
|
||||
__func__, err);
|
||||
goto dealloc_host;
|
||||
return err;
|
||||
}
|
||||
|
||||
ufshcd_init_lanes_per_dir(hba);
|
||||
@ -502,25 +498,20 @@ int ufshcd_pltfrm_init(struct platform_device *pdev,
|
||||
err = ufshcd_parse_operating_points(hba);
|
||||
if (err) {
|
||||
dev_err(dev, "%s: OPP parse failed %d\n", __func__, err);
|
||||
goto dealloc_host;
|
||||
return err;
|
||||
}
|
||||
|
||||
err = ufshcd_init(hba, mmio_base, irq);
|
||||
if (err) {
|
||||
dev_err_probe(dev, err, "Initialization failed with error %d\n",
|
||||
err);
|
||||
goto dealloc_host;
|
||||
return err;
|
||||
}
|
||||
|
||||
pm_runtime_set_active(dev);
|
||||
pm_runtime_enable(dev);
|
||||
|
||||
return 0;
|
||||
|
||||
dealloc_host:
|
||||
ufshcd_dealloc_host(hba);
|
||||
out:
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(ufshcd_pltfrm_init);
|
||||
|
||||
@ -534,7 +525,6 @@ void ufshcd_pltfrm_remove(struct platform_device *pdev)
|
||||
|
||||
pm_runtime_get_sync(&pdev->dev);
|
||||
ufshcd_remove(hba);
|
||||
ufshcd_dealloc_host(hba);
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
pm_runtime_put_noidle(&pdev->dev);
|
||||
}
|
||||
|
@ -1309,7 +1309,6 @@ static inline void ufshcd_rmwl(struct ufs_hba *hba, u32 mask, u32 val, u32 reg)
|
||||
void ufshcd_enable_irq(struct ufs_hba *hba);
|
||||
void ufshcd_disable_irq(struct ufs_hba *hba);
|
||||
int ufshcd_alloc_host(struct device *, struct ufs_hba **);
|
||||
void ufshcd_dealloc_host(struct ufs_hba *);
|
||||
int ufshcd_hba_enable(struct ufs_hba *hba);
|
||||
int ufshcd_init(struct ufs_hba *, void __iomem *, unsigned int);
|
||||
int ufshcd_link_recovery(struct ufs_hba *hba);
|
||||
|
Loading…
x
Reference in New Issue
Block a user