[PATCH 5.3 108/180] ACPI: OSL: only free map once in osl.c

Greg Kroah-Hartman
2019-12-16T18:11:31Z

From: Francesco Ruggeri <fruggeri@arista.com>

commit 833a426cc471b6088011b3d67f1dc4e147614647 upstream.

acpi_os_map_cleanup checks map->refcount outside of acpi_ioremap_lock
before freeing the map. This creates a race condition the can result
in the map being freed more than once.
A panic can be caused by running

for ((i=0; i<10; i++))
do
for ((j=0; j<100000; j++))
do
cat /sys/firmware/acpi/tables/data/BERT >/dev/null
done &
done

This patch makes sure that only the process that drops the reference
to 0 does the freeing.

Fixes: b7c1fadd6c2e ("ACPI: Do not use krefs under a mutex in osl.c")
Signed-off-by: Francesco Ruggeri <fruggeri@arista.com>
Reviewed-by: Dmitry Safonov <0x7f454c46@gmail.com>
Cc: All applicable <stable@vger.kernel.org>
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

---
drivers/acpi/osl.c | 28 +++++++++++++++++-----------
1 file changed, 17 insertions(+), 11 deletions(-)

--- a/drivers/acpi/osl.c
+++ b/drivers/acpi/osl.c
@@ -360,19 +360,21 @@ void *__ref acpi_os_map_memory(acpi_phys
}
EXPORT_SYMBOL_GPL(acpi_os_map_memory);

-static void acpi_os_drop_map_ref(struct acpi_ioremap *map)
+/* Must be called with mutex_lock(&acpi_ioremap_lock) */
+static unsigned long acpi_os_drop_map_ref(struct acpi_ioremap *map)
{
- if (!--map->refcount)
+ unsigned long refcount = --map->refcount;
+
+ if (!refcount)
list_del_rcu(&map->list);
+ return refcount;
}

static void acpi_os_map_cleanup(struct acpi_ioremap *map)
{
- if (!map->refcount) {
- synchronize_rcu_expedited();
- acpi_unmap(map->phys, map->virt);
- kfree(map);
- }
+ synchronize_rcu_expedited();
+ acpi_unmap(map->phys, map->virt);
+ kfree(map);
}

/**
@@ -392,6 +394,7 @@ static void acpi_os_map_cleanup(struct a
void __ref acpi_os_unmap_iomem(void __iomem *virt, acpi_size size)
{
struct acpi_ioremap *map;
+ unsigned long refcount;

if (!acpi_permanent_mmap) {
__acpi_unmap_table(virt, size);
@@ -405,10 +408,11 @@ void __ref acpi_os_unmap_iomem(void __io
WARN(true, PREFIX "%s: bad address %p\n", __func__, virt);
return;
}
- acpi_os_drop_map_ref(map);
+ refcount = acpi_os_drop_map_ref(map);
mutex_unlock(&acpi_ioremap_lock);

- acpi_os_map_cleanup(map);
+ if (!refcount)
+ acpi_os_map_cleanup(map);
}
EXPORT_SYMBOL_GPL(acpi_os_unmap_iomem);

@@ -443,6 +447,7 @@ void acpi_os_unmap_generic_address(struc
{
u64 addr;
struct acpi_ioremap *map;
+ unsigned long refcount;

if (gas->space_id != ACPI_ADR_SPACE_SYSTEM_MEMORY)
return;
@@ -458,10 +463,11 @@ void acpi_os_unmap_generic_address(struc
mutex_unlock(&acpi_ioremap_lock);
return;
}
- acpi_os_drop_map_ref(map);
+ refcount = acpi_os_drop_map_ref(map);
mutex_unlock(&acpi_ioremap_lock);

- acpi_os_map_cleanup(map);
+ if (!refcount)
+ acpi_os_map_cleanup(map);
}
EXPORT_SYMBOL(acpi_os_unmap_generic_address);