Merge series "Use-after-free be gone" from Lukas Wunner <lukas@wunner.de>:

Here's my proposal to fix the use-after-free bugs reported by
Sascha Hauer and Florian Fainelli:

I scrutinized all SPI drivers in the v5.10 tree:

* There are 9 drivers with a use-after-free in the ->remove() hook
  caused by accessing driver private data after spi_unregister_controller().

* There are 8 drivers which leak the spi_controller in the ->probe()
  error path because of a missing spi_controller_put().

I'm introducing devm_spi_alloc_master/slave() which automatically
calls spi_controller_put() on ->remove().  This fixes both classes
of bugs while at the same time reducing code amount and complexity
in the ->probe() hook.

I propose that spi_controller_unregister() should no longer release
a reference on the spi_controller.  Instead, drivers need to either
do it themselves or use one of the devm functions introduced herein.
The vast majority of drivers can be converted to the devm functions.
See the commit message of patch [1/4] for the rationale and details.

Enclosed are patches for 3 Broadcom drivers.
Patches for the other drivers are on this branch:
https://github.com/l1k/linux/commits/spi_fixes

@Florian Fainelli:  Could you verify that there are no KASAN splats or
leaks with these patches?  Unfortunately I do not have any SPI-capable
hardware at my disposal right now, so can only compile-test.  You may
want to augment spi_controller_release() with a printk() to log when
the spi_controller is freed.

@Mark Brown:  Patches [2/4] to [4/4] reference the SHA-1 of patch [1/4]
in their stable tags.  Because the hash is unknown to me until you apply
the patch, I've used "123456789abc" as a placeholder.  You'll have to
replace the hash if/when applying.  Alternatively, only apply patch [1/4]
and I'll repost the other patches with the hash fixed up.

Thanks!

Lukas Wunner (4):
  spi: Introduce device-managed SPI controller allocation
  spi: bcm2835: Fix use-after-free on unbind
  spi: bcm2835aux: Fix use-after-free on unbind
  spi: bcm-qspi: Fix use-after-free on unbind

 drivers/spi/spi-bcm-qspi.c   | 34 ++++++++-------------
 drivers/spi/spi-bcm2835.c    | 24 +++++----------
 drivers/spi/spi-bcm2835aux.c | 21 +++++--------
 drivers/spi/spi.c            | 58 +++++++++++++++++++++++++++++++++++-
 include/linux/spi/spi.h      | 19 ++++++++++++
 5 files changed, 103 insertions(+), 53 deletions(-)

--
2.28.0
This commit is contained in:
Mark Brown 2020-11-12 19:34:52 +00:00
commit c371dcf51c
No known key found for this signature in database
GPG Key ID: 24D68B725D5487D0
5 changed files with 103 additions and 53 deletions

View File

@ -1327,7 +1327,7 @@ int bcm_qspi_probe(struct platform_device *pdev,
data = of_id->data;
master = spi_alloc_master(dev, sizeof(struct bcm_qspi));
master = devm_spi_alloc_master(dev, sizeof(struct bcm_qspi));
if (!master) {
dev_err(dev, "error allocating spi_master\n");
return -ENOMEM;
@ -1367,21 +1367,17 @@ int bcm_qspi_probe(struct platform_device *pdev,
if (res) {
qspi->base[MSPI] = devm_ioremap_resource(dev, res);
if (IS_ERR(qspi->base[MSPI])) {
ret = PTR_ERR(qspi->base[MSPI]);
goto qspi_resource_err;
}
if (IS_ERR(qspi->base[MSPI]))
return PTR_ERR(qspi->base[MSPI]);
} else {
goto qspi_resource_err;
return 0;
}
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "bspi");
if (res) {
qspi->base[BSPI] = devm_ioremap_resource(dev, res);
if (IS_ERR(qspi->base[BSPI])) {
ret = PTR_ERR(qspi->base[BSPI]);
goto qspi_resource_err;
}
if (IS_ERR(qspi->base[BSPI]))
return PTR_ERR(qspi->base[BSPI]);
qspi->bspi_mode = true;
} else {
qspi->bspi_mode = false;
@ -1392,18 +1388,14 @@ int bcm_qspi_probe(struct platform_device *pdev,
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "cs_reg");
if (res) {
qspi->base[CHIP_SELECT] = devm_ioremap_resource(dev, res);
if (IS_ERR(qspi->base[CHIP_SELECT])) {
ret = PTR_ERR(qspi->base[CHIP_SELECT]);
goto qspi_resource_err;
}
if (IS_ERR(qspi->base[CHIP_SELECT]))
return PTR_ERR(qspi->base[CHIP_SELECT]);
}
qspi->dev_ids = kcalloc(num_irqs, sizeof(struct bcm_qspi_dev_id),
GFP_KERNEL);
if (!qspi->dev_ids) {
ret = -ENOMEM;
goto qspi_resource_err;
}
if (!qspi->dev_ids)
return -ENOMEM;
for (val = 0; val < num_irqs; val++) {
irq = -1;
@ -1484,7 +1476,7 @@ int bcm_qspi_probe(struct platform_device *pdev,
qspi->xfer_mode.addrlen = -1;
qspi->xfer_mode.hp = -1;
ret = devm_spi_register_master(&pdev->dev, master);
ret = spi_register_master(master);
if (ret < 0) {
dev_err(dev, "can't register master\n");
goto qspi_reg_err;
@ -1497,8 +1489,6 @@ qspi_reg_err:
clk_disable_unprepare(qspi->clk);
qspi_probe_err:
kfree(qspi->dev_ids);
qspi_resource_err:
spi_master_put(master);
return ret;
}
/* probe function to be called by SoC specific platform driver probe */
@ -1508,10 +1498,10 @@ int bcm_qspi_remove(struct platform_device *pdev)
{
struct bcm_qspi *qspi = platform_get_drvdata(pdev);
spi_unregister_master(qspi->master);
bcm_qspi_hw_uninit(qspi);
clk_disable_unprepare(qspi->clk);
kfree(qspi->dev_ids);
spi_unregister_master(qspi->master);
return 0;
}

View File

@ -1278,7 +1278,7 @@ static int bcm2835_spi_probe(struct platform_device *pdev)
struct bcm2835_spi *bs;
int err;
ctlr = spi_alloc_master(&pdev->dev, ALIGN(sizeof(*bs),
ctlr = devm_spi_alloc_master(&pdev->dev, ALIGN(sizeof(*bs),
dma_get_cache_alignment()));
if (!ctlr)
return -ENOMEM;
@ -1299,23 +1299,17 @@ static int bcm2835_spi_probe(struct platform_device *pdev)
bs->ctlr = ctlr;
bs->regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(bs->regs)) {
err = PTR_ERR(bs->regs);
goto out_controller_put;
}
if (IS_ERR(bs->regs))
return PTR_ERR(bs->regs);
bs->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(bs->clk)) {
err = dev_err_probe(&pdev->dev, PTR_ERR(bs->clk),
"could not get clk\n");
goto out_controller_put;
}
if (IS_ERR(bs->clk))
return dev_err_probe(&pdev->dev, PTR_ERR(bs->clk),
"could not get clk\n");
bs->irq = platform_get_irq(pdev, 0);
if (bs->irq <= 0) {
err = bs->irq ? bs->irq : -ENODEV;
goto out_controller_put;
}
if (bs->irq <= 0)
return bs->irq ? bs->irq : -ENODEV;
clk_prepare_enable(bs->clk);
@ -1349,8 +1343,6 @@ out_dma_release:
bcm2835_dma_release(ctlr, bs);
out_clk_disable:
clk_disable_unprepare(bs->clk);
out_controller_put:
spi_controller_put(ctlr);
return err;
}

View File

@ -494,7 +494,7 @@ static int bcm2835aux_spi_probe(struct platform_device *pdev)
unsigned long clk_hz;
int err;
master = spi_alloc_master(&pdev->dev, sizeof(*bs));
master = devm_spi_alloc_master(&pdev->dev, sizeof(*bs));
if (!master)
return -ENOMEM;
@ -524,29 +524,24 @@ static int bcm2835aux_spi_probe(struct platform_device *pdev)
/* the main area */
bs->regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(bs->regs)) {
err = PTR_ERR(bs->regs);
goto out_master_put;
}
if (IS_ERR(bs->regs))
return PTR_ERR(bs->regs);
bs->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(bs->clk)) {
err = PTR_ERR(bs->clk);
dev_err(&pdev->dev, "could not get clk: %d\n", err);
goto out_master_put;
return PTR_ERR(bs->clk);
}
bs->irq = platform_get_irq(pdev, 0);
if (bs->irq <= 0) {
err = bs->irq ? bs->irq : -ENODEV;
goto out_master_put;
}
if (bs->irq <= 0)
return bs->irq ? bs->irq : -ENODEV;
/* this also enables the HW block */
err = clk_prepare_enable(bs->clk);
if (err) {
dev_err(&pdev->dev, "could not prepare clock: %d\n", err);
goto out_master_put;
return err;
}
/* just checking if the clock returns a sane value */
@ -581,8 +576,6 @@ static int bcm2835aux_spi_probe(struct platform_device *pdev)
out_clk_disable:
clk_disable_unprepare(bs->clk);
out_master_put:
spi_master_put(master);
return err;
}

View File

@ -2442,6 +2442,49 @@ struct spi_controller *__spi_alloc_controller(struct device *dev,
}
EXPORT_SYMBOL_GPL(__spi_alloc_controller);
static void devm_spi_release_controller(struct device *dev, void *ctlr)
{
spi_controller_put(*(struct spi_controller **)ctlr);
}
/**
* __devm_spi_alloc_controller - resource-managed __spi_alloc_controller()
* @dev: physical device of SPI controller
* @size: how much zeroed driver-private data to allocate
* @slave: whether to allocate an SPI master (false) or SPI slave (true)
* Context: can sleep
*
* Allocate an SPI controller and automatically release a reference on it
* when @dev is unbound from its driver. Drivers are thus relieved from
* having to call spi_controller_put().
*
* The arguments to this function are identical to __spi_alloc_controller().
*
* Return: the SPI controller structure on success, else NULL.
*/
struct spi_controller *__devm_spi_alloc_controller(struct device *dev,
unsigned int size,
bool slave)
{
struct spi_controller **ptr, *ctlr;
ptr = devres_alloc(devm_spi_release_controller, sizeof(*ptr),
GFP_KERNEL);
if (!ptr)
return NULL;
ctlr = __spi_alloc_controller(dev, size, slave);
if (ctlr) {
*ptr = ctlr;
devres_add(dev, ptr);
} else {
devres_free(ptr);
}
return ctlr;
}
EXPORT_SYMBOL_GPL(__devm_spi_alloc_controller);
#ifdef CONFIG_OF
static int of_spi_get_gpio_numbers(struct spi_controller *ctlr)
{
@ -2778,6 +2821,11 @@ int devm_spi_register_controller(struct device *dev,
}
EXPORT_SYMBOL_GPL(devm_spi_register_controller);
static int devm_spi_match_controller(struct device *dev, void *res, void *ctlr)
{
return *(struct spi_controller **)res == ctlr;
}
static int __unregister(struct device *dev, void *null)
{
spi_unregister_device(to_spi_device(dev));
@ -2819,7 +2867,15 @@ void spi_unregister_controller(struct spi_controller *ctlr)
list_del(&ctlr->list);
mutex_unlock(&board_lock);
device_unregister(&ctlr->dev);
device_del(&ctlr->dev);
/* Release the last reference on the controller if its driver
* has not yet been converted to devm_spi_alloc_master/slave().
*/
if (!devres_find(ctlr->dev.parent, devm_spi_release_controller,
devm_spi_match_controller, ctlr))
put_device(&ctlr->dev);
/* free bus id */
mutex_lock(&board_lock);
if (found == ctlr)

View File

@ -734,6 +734,25 @@ static inline struct spi_controller *spi_alloc_slave(struct device *host,
return __spi_alloc_controller(host, size, true);
}
struct spi_controller *__devm_spi_alloc_controller(struct device *dev,
unsigned int size,
bool slave);
static inline struct spi_controller *devm_spi_alloc_master(struct device *dev,
unsigned int size)
{
return __devm_spi_alloc_controller(dev, size, false);
}
static inline struct spi_controller *devm_spi_alloc_slave(struct device *dev,
unsigned int size)
{
if (!IS_ENABLED(CONFIG_SPI_SLAVE))
return NULL;
return __devm_spi_alloc_controller(dev, size, true);
}
extern int spi_register_controller(struct spi_controller *ctlr);
extern int devm_spi_register_controller(struct device *dev,
struct spi_controller *ctlr);