From 833a426cc471b6088011b3d67f1dc4e147614647 Mon Sep 17 00:00:00 2001 From: Francesco Ruggeri Date: Tue, 19 Nov 2019 21:47:27 -0800 Subject: ACPI: OSL: only free map once in osl.c 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 Reviewed-by: Dmitry Safonov <0x7f454c46@gmail.com> Cc: All applicable Signed-off-by: Rafael J. Wysocki --- drivers/acpi/osl.c | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) (limited to 'drivers/acpi') diff --git a/drivers/acpi/osl.c b/drivers/acpi/osl.c index a2e844a8e9ed..41168c027a5a 100644 --- a/drivers/acpi/osl.c +++ b/drivers/acpi/osl.c @@ -374,19 +374,21 @@ void *__ref acpi_os_map_memory(acpi_physical_address phys, acpi_size size) } 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); } /** @@ -406,6 +408,7 @@ static void acpi_os_map_cleanup(struct acpi_ioremap *map) 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); @@ -419,10 +422,11 @@ void __ref acpi_os_unmap_iomem(void __iomem *virt, acpi_size size) 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); @@ -457,6 +461,7 @@ void acpi_os_unmap_generic_address(struct acpi_generic_address *gas) { u64 addr; struct acpi_ioremap *map; + unsigned long refcount; if (gas->space_id != ACPI_ADR_SPACE_SYSTEM_MEMORY) return; @@ -472,10 +477,11 @@ void acpi_os_unmap_generic_address(struct acpi_generic_address *gas) 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); -- cgit v1.2.3