ACPI: APEI: Fix AER info corruption when error status data has multiple sections
authorShiju Jose <shiju.jose@huawei.com>
Wed, 20 Sep 2023 18:03:36 +0000 (02:03 +0800)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Tue, 28 Nov 2023 17:19:37 +0000 (17:19 +0000)
[ Upstream commit e2abc47a5a1a9f641e7cacdca643fdd40729bf6e ]

ghes_handle_aer() passes AER data to the PCI core for logging and
recovery by calling aer_recover_queue() with a pointer to struct
aer_capability_regs.

The problem was that aer_recover_queue() queues the pointer directly
without copying the aer_capability_regs data.  The pointer was to
the ghes->estatus buffer, which could be reused before
aer_recover_work_func() reads the data.

To avoid this problem, allocate a new aer_capability_regs structure
from the ghes_estatus_pool, copy the AER data from the ghes->estatus
buffer into it, pass a pointer to the new struct to
aer_recover_queue(), and free it after aer_recover_work_func() has
processed it.

Reported-by: Bjorn Helgaas <helgaas@kernel.org>
Acked-by: Bjorn Helgaas <bhelgaas@google.com>
Signed-off-by: Shiju Jose <shiju.jose@huawei.com>
[ rjw: Subject edits ]
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
drivers/acpi/apei/ghes.c
drivers/pci/pcie/aer.c
include/acpi/ghes.h

index ef59d6e..63ad054 100644 (file)
@@ -209,6 +209,20 @@ err_pool_alloc:
        return -ENOMEM;
 }
 
+/**
+ * ghes_estatus_pool_region_free - free previously allocated memory
+ *                                from the ghes_estatus_pool.
+ * @addr: address of memory to free.
+ * @size: size of memory to free.
+ *
+ * Returns none.
+ */
+void ghes_estatus_pool_region_free(unsigned long addr, u32 size)
+{
+       gen_pool_free(ghes_estatus_pool, addr, size);
+}
+EXPORT_SYMBOL_GPL(ghes_estatus_pool_region_free);
+
 static int map_gen_v2(struct ghes *ghes)
 {
        return apei_map_generic_address(&ghes->generic_v2->read_ack_register);
@@ -564,6 +578,7 @@ static void ghes_handle_aer(struct acpi_hest_generic_data *gdata)
            pcie_err->validation_bits & CPER_PCIE_VALID_AER_INFO) {
                unsigned int devfn;
                int aer_severity;
+               u8 *aer_info;
 
                devfn = PCI_DEVFN(pcie_err->device_id.device,
                                  pcie_err->device_id.function);
@@ -577,11 +592,17 @@ static void ghes_handle_aer(struct acpi_hest_generic_data *gdata)
                if (gdata->flags & CPER_SEC_RESET)
                        aer_severity = AER_FATAL;
 
+               aer_info = (void *)gen_pool_alloc(ghes_estatus_pool,
+                                                 sizeof(struct aer_capability_regs));
+               if (!aer_info)
+                       return;
+               memcpy(aer_info, pcie_err->aer_info, sizeof(struct aer_capability_regs));
+
                aer_recover_queue(pcie_err->device_id.segment,
                                  pcie_err->device_id.bus,
                                  devfn, aer_severity,
                                  (struct aer_capability_regs *)
-                                 pcie_err->aer_info);
+                                 aer_info);
        }
 #endif
 }
index 9c8fd69..40d84cb 100644 (file)
@@ -29,6 +29,7 @@
 #include <linux/kfifo.h>
 #include <linux/slab.h>
 #include <acpi/apei.h>
+#include <acpi/ghes.h>
 #include <ras/ras_event.h>
 
 #include "../pci.h"
@@ -997,6 +998,15 @@ static void aer_recover_work_func(struct work_struct *work)
                        continue;
                }
                cper_print_aer(pdev, entry.severity, entry.regs);
+               /*
+                * Memory for aer_capability_regs(entry.regs) is being allocated from the
+                * ghes_estatus_pool to protect it from overwriting when multiple sections
+                * are present in the error status. Thus free the same after processing
+                * the data.
+                */
+               ghes_estatus_pool_region_free((unsigned long)entry.regs,
+                                             sizeof(struct aer_capability_regs));
+
                if (entry.severity == AER_NONFATAL)
                        pcie_do_recovery(pdev, pci_channel_io_normal,
                                         aer_root_reset);
index 3c8bba9..be1dd4c 100644 (file)
@@ -73,8 +73,12 @@ int ghes_register_vendor_record_notifier(struct notifier_block *nb);
 void ghes_unregister_vendor_record_notifier(struct notifier_block *nb);
 
 struct list_head *ghes_get_devices(void);
+
+void ghes_estatus_pool_region_free(unsigned long addr, u32 size);
 #else
 static inline struct list_head *ghes_get_devices(void) { return NULL; }
+
+static inline void ghes_estatus_pool_region_free(unsigned long addr, u32 size) { return; }
 #endif
 
 int ghes_estatus_pool_init(unsigned int num_ghes);