| From cee6035f2d9c5d8edd7b1ef2f272aa1077abd8f3 Mon Sep 17 00:00:00 2001 |
| From: Francesco Ruggeri <fruggeri@arista.com> |
| Date: Tue, 19 Nov 2019 21:47:27 -0800 |
| Subject: [PATCH] ACPI: OSL: only free map once in osl.c |
| |
| 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: Paul Gortmaker <paul.gortmaker@windriver.com> |
| |
| diff --git a/drivers/acpi/osl.c b/drivers/acpi/osl.c |
| index cc7507091dec..620b88ef05ee 100644 |
| --- a/drivers/acpi/osl.c |
| +++ b/drivers/acpi/osl.c |
| @@ -360,19 +360,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); |
| } |
| |
| /** |
| @@ -392,6 +394,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); |
| @@ -405,10 +408,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); |
| |
| @@ -443,6 +447,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; |
| @@ -458,10 +463,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); |
| |
| -- |
| 2.7.4 |
| |