| From: Francesco Ruggeri <fruggeri@arista.com> |
| Date: Tue, 19 Nov 2019 21:47:27 -0800 |
| Subject: 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> |
| Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com> |
| Signed-off-by: Ben Hutchings <ben@decadent.org.uk> |
| --- |
| drivers/acpi/osl.c | 28 +++++++++++++++++----------- |
| 1 file changed, 17 insertions(+), 11 deletions(-) |
| |
| --- a/drivers/acpi/osl.c |
| +++ b/drivers/acpi/osl.c |
| @@ -416,24 +416,27 @@ acpi_os_map_memory(acpi_physical_address |
| } |
| 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); |
| } |
| |
| void __ref acpi_os_unmap_iomem(void __iomem *virt, acpi_size size) |
| { |
| struct acpi_ioremap *map; |
| + unsigned long refcount; |
| |
| if (!acpi_gbl_permanent_mmap) { |
| __acpi_unmap_table(virt, size); |
| @@ -447,10 +450,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); |
| |
| @@ -491,6 +495,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; |
| @@ -506,10 +511,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); |
| |