Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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>

authored by

André Draszik and committed by
Martin K. Petersen
f8fb2403 9ff7c383

+30 -32
+21 -10
drivers/ufs/core/ufshcd.c
··· 10227 10227 #endif /* CONFIG_PM_SLEEP */ 10228 10228 10229 10229 /** 10230 - * ufshcd_dealloc_host - deallocate Host Bus Adapter (HBA) 10231 - * @hba: pointer to Host Bus Adapter (HBA) 10232 - */ 10233 - void ufshcd_dealloc_host(struct ufs_hba *hba) 10234 - { 10235 - scsi_host_put(hba->host); 10236 - } 10237 - EXPORT_SYMBOL_GPL(ufshcd_dealloc_host); 10238 - 10239 - /** 10240 10230 * ufshcd_set_dma_mask - Set dma mask based on the controller 10241 10231 * addressing capability 10242 10232 * @hba: per adapter instance ··· 10245 10255 } 10246 10256 10247 10257 /** 10258 + * ufshcd_devres_release - devres cleanup handler, invoked during release of 10259 + * hba->dev 10260 + * @host: pointer to SCSI host 10261 + */ 10262 + static void ufshcd_devres_release(void *host) 10263 + { 10264 + scsi_host_put(host); 10265 + } 10266 + 10267 + /** 10248 10268 * ufshcd_alloc_host - allocate Host Bus Adapter (HBA) 10249 10269 * @dev: pointer to device handle 10250 10270 * @hba_handle: driver private handle 10251 10271 * 10252 10272 * Return: 0 on success, non-zero value on failure. 10273 + * 10274 + * NOTE: There is no corresponding ufshcd_dealloc_host() because this function 10275 + * keeps track of its allocations using devres and deallocates everything on 10276 + * device removal automatically. 10253 10277 */ 10254 10278 int ufshcd_alloc_host(struct device *dev, struct ufs_hba **hba_handle) 10255 10279 { ··· 10285 10281 err = -ENOMEM; 10286 10282 goto out_error; 10287 10283 } 10284 + 10285 + err = devm_add_action_or_reset(dev, ufshcd_devres_release, 10286 + host); 10287 + if (err) 10288 + return dev_err_probe(dev, err, 10289 + "failed to add ufshcd dealloc action\n"); 10290 + 10288 10291 host->nr_maps = HCTX_TYPE_POLL + 1; 10289 10292 hba = shost_priv(host); 10290 10293 hba->host = host;
-2
drivers/ufs/host/ufshcd-pci.c
··· 562 562 pm_runtime_forbid(&pdev->dev); 563 563 pm_runtime_get_noresume(&pdev->dev); 564 564 ufshcd_remove(hba); 565 - ufshcd_dealloc_host(hba); 566 565 } 567 566 568 567 /** ··· 604 605 err = ufshcd_init(hba, mmio_base, pdev->irq); 605 606 if (err) { 606 607 dev_err(&pdev->dev, "Initialization failed\n"); 607 - ufshcd_dealloc_host(hba); 608 608 return err; 609 609 } 610 610
+9 -19
drivers/ufs/host/ufshcd-pltfrm.c
··· 465 465 struct device *dev = &pdev->dev; 466 466 467 467 mmio_base = devm_platform_ioremap_resource(pdev, 0); 468 - if (IS_ERR(mmio_base)) { 469 - err = PTR_ERR(mmio_base); 470 - goto out; 471 - } 468 + if (IS_ERR(mmio_base)) 469 + return PTR_ERR(mmio_base); 472 470 473 471 irq = platform_get_irq(pdev, 0); 474 - if (irq < 0) { 475 - err = irq; 476 - goto out; 477 - } 472 + if (irq < 0) 473 + return irq; 478 474 479 475 err = ufshcd_alloc_host(dev, &hba); 480 476 if (err) { 481 477 dev_err(dev, "Allocation failed\n"); 482 - goto out; 478 + return err; 483 479 } 484 480 485 481 hba->vops = vops; ··· 484 488 if (err) { 485 489 dev_err(dev, "%s: clock parse failed %d\n", 486 490 __func__, err); 487 - goto dealloc_host; 491 + return err; 488 492 } 489 493 err = ufshcd_parse_regulator_info(hba); 490 494 if (err) { 491 495 dev_err(dev, "%s: regulator init failed %d\n", 492 496 __func__, err); 493 - goto dealloc_host; 497 + return err; 494 498 } 495 499 496 500 ufshcd_init_lanes_per_dir(hba); ··· 498 502 err = ufshcd_parse_operating_points(hba); 499 503 if (err) { 500 504 dev_err(dev, "%s: OPP parse failed %d\n", __func__, err); 501 - goto dealloc_host; 505 + return err; 502 506 } 503 507 504 508 err = ufshcd_init(hba, mmio_base, irq); 505 509 if (err) { 506 510 dev_err_probe(dev, err, "Initialization failed with error %d\n", 507 511 err); 508 - goto dealloc_host; 512 + return err; 509 513 } 510 514 511 515 pm_runtime_set_active(dev); 512 516 pm_runtime_enable(dev); 513 517 514 518 return 0; 515 - 516 - dealloc_host: 517 - ufshcd_dealloc_host(hba); 518 - out: 519 - return err; 520 519 } 521 520 EXPORT_SYMBOL_GPL(ufshcd_pltfrm_init); 522 521 ··· 525 534 526 535 pm_runtime_get_sync(&pdev->dev); 527 536 ufshcd_remove(hba); 528 - ufshcd_dealloc_host(hba); 529 537 pm_runtime_disable(&pdev->dev); 530 538 pm_runtime_put_noidle(&pdev->dev); 531 539 }
-1
include/ufs/ufshcd.h
··· 1309 1309 void ufshcd_enable_irq(struct ufs_hba *hba); 1310 1310 void ufshcd_disable_irq(struct ufs_hba *hba); 1311 1311 int ufshcd_alloc_host(struct device *, struct ufs_hba **); 1312 - void ufshcd_dealloc_host(struct ufs_hba *); 1313 1312 int ufshcd_hba_enable(struct ufs_hba *hba); 1314 1313 int ufshcd_init(struct ufs_hba *, void __iomem *, unsigned int); 1315 1314 int ufshcd_link_recovery(struct ufs_hba *hba);