Hardware-poisoned page frames are tracked only in the running kernel's data
structures, so a kexec loses them and the next kernel hands the known-bad RAM
back out.

Add an EFI configuration table to carry that list to the next kernel. It uses
the same growable linked-list layout as LINUX_EFI_MEMRESERVE and, like it, is
installed empty by the stub while boot services are still available -- a
running kernel can only append to the list, not install a new config table --
and rides the EFI system table across kexec.

Define the table and install the empty root here; the record and reserve
paths follow.

Signed-off-by: Breno Leitao <[email protected]>
---
 drivers/firmware/efi/Kconfig                   | 10 +++++++++
 drivers/firmware/efi/efi.c                     |  2 ++
 drivers/firmware/efi/libstub/efi-stub-helper.c | 30 ++++++++++++++++++++++++++
 drivers/firmware/efi/libstub/efi-stub.c        |  1 +
 drivers/firmware/efi/libstub/efistub.h         |  6 ++++++
 drivers/firmware/efi/libstub/x86-stub.c        |  2 ++
 include/linux/efi.h                            | 21 ++++++++++++++++++
 7 files changed, 72 insertions(+)

diff --git a/drivers/firmware/efi/Kconfig b/drivers/firmware/efi/Kconfig
index 29e0729299f5b..0e4ccfd968b87 100644
--- a/drivers/firmware/efi/Kconfig
+++ b/drivers/firmware/efi/Kconfig
@@ -263,6 +263,16 @@ config EFI_COCO_SECRET
          virt/coco/efi_secret module to access the secrets, which in turn
          allows userspace programs to access the injected secrets.
 
+config EFI_POISONED_MEMORY
+       bool "Carry hardware-poisoned pages across kexec"
+       depends on EFI_STUB && MEMORY_FAILURE
+       help
+         Record page frames that are hardware-poisoned while this kernel runs
+         into an EFI configuration table, and honor that table early on the
+         next kernel so a kexec does not hand known-bad RAM back out.
+
+         If unsure, say N.
+
 config OVMF_DEBUG_LOG
        bool "Expose OVMF firmware debug log via sysfs"
        depends on EFI
diff --git a/drivers/firmware/efi/efi.c b/drivers/firmware/efi/efi.c
index 0327a39d31fa5..f3b799930be44 100644
--- a/drivers/firmware/efi/efi.c
+++ b/drivers/firmware/efi/efi.c
@@ -55,6 +55,7 @@ struct efi __read_mostly efi = {
 #ifdef CONFIG_UNACCEPTED_MEMORY
        .unaccepted             = EFI_INVALID_TABLE_ADDR,
 #endif
+       .poisoned_memory        = EFI_INVALID_TABLE_ADDR,
 };
 EXPORT_SYMBOL(efi);
 
@@ -629,6 +630,7 @@ static const efi_config_table_type_t common_tables[] 
__initconst = {
        {EFI_TCG2_FINAL_EVENTS_TABLE_GUID,      &efi.tpm_final_log,     
"TPMFinalLog"   },
        {EFI_CC_FINAL_EVENTS_TABLE_GUID,        &efi.tpm_final_log,     
"CCFinalLog"    },
        {LINUX_EFI_MEMRESERVE_TABLE_GUID,       &mem_reserve,           
"MEMRESERVE"    },
+       {LINUX_EFI_POISONED_MEMORY_TABLE_GUID,  &efi.poisoned_memory,   
"POISON"        },
        {LINUX_EFI_INITRD_MEDIA_GUID,           &initrd,                
"INITRD"        },
        {EFI_RT_PROPERTIES_TABLE_GUID,          &rt_prop,               
"RTPROP"        },
 #ifdef CONFIG_OVMF_DEBUG_LOG
diff --git a/drivers/firmware/efi/libstub/efi-stub-helper.c 
b/drivers/firmware/efi/libstub/efi-stub-helper.c
index f27f2e1f00199..2d873bccac481 100644
--- a/drivers/firmware/efi/libstub/efi-stub-helper.c
+++ b/drivers/firmware/efi/libstub/efi-stub-helper.c
@@ -774,3 +774,33 @@ void efi_remap_image(unsigned long image_base, unsigned 
alloc_size,
                        efi_warn("Failed to remap data region 
non-executable\n");
        }
 }
+
+/*
+ * Install an empty root for the poisoned-memory table now, while boot services
+ * are available; the running kernel can only append to it later, not install a
+ * new config table. Shared by all stubs (x86 and the generic stub).
+ */
+#ifdef CONFIG_EFI_POISONED_MEMORY
+void install_poisoned_memory_table(void)
+{
+       efi_guid_t poisoned_memory_table_guid = 
LINUX_EFI_POISONED_MEMORY_TABLE_GUID;
+       struct linux_efi_poisoned_memory *pm;
+       efi_status_t status;
+
+       status = efi_bs_call(allocate_pool, EFI_LOADER_DATA, sizeof(*pm),
+                            (void **)&pm);
+       if (status != EFI_SUCCESS) {
+               efi_err("Failed to allocate poisoned-memory entry!\n");
+               return;
+       }
+
+       pm->next = 0;
+       pm->size = 0;
+       atomic_set(&pm->count, 0);
+
+       status = efi_bs_call(install_configuration_table,
+                            &poisoned_memory_table_guid, pm);
+       if (status != EFI_SUCCESS)
+               efi_err("Failed to install poisoned-memory config table!\n");
+}
+#endif
diff --git a/drivers/firmware/efi/libstub/efi-stub.c 
b/drivers/firmware/efi/libstub/efi-stub.c
index 42d6073bcd062..008635eb5027a 100644
--- a/drivers/firmware/efi/libstub/efi-stub.c
+++ b/drivers/firmware/efi/libstub/efi-stub.c
@@ -179,6 +179,7 @@ efi_status_t efi_stub_common(efi_handle_t handle,
                         EFI_RT_SUPPORTED_SET_VIRTUAL_ADDRESS_MAP);
 
        install_memreserve_table();
+       install_poisoned_memory_table();
 
        status = efi_boot_kernel(handle, image, image_addr, cmdline_ptr);
 
diff --git a/drivers/firmware/efi/libstub/efistub.h 
b/drivers/firmware/efi/libstub/efistub.h
index fd91fc15ec810..44436869c4efe 100644
--- a/drivers/firmware/efi/libstub/efistub.h
+++ b/drivers/firmware/efi/libstub/efistub.h
@@ -1169,6 +1169,12 @@ efi_enable_reset_attack_mitigation(void) { }
 
 void efi_retrieve_eventlog(void);
 
+#ifdef CONFIG_EFI_POISONED_MEMORY
+void install_poisoned_memory_table(void);
+#else
+static inline void install_poisoned_memory_table(void) { }
+#endif
+
 struct sysfb_display_info *alloc_primary_display(void);
 struct sysfb_display_info *__alloc_primary_display(void);
 void free_primary_display(struct sysfb_display_info *dpy);
diff --git a/drivers/firmware/efi/libstub/x86-stub.c 
b/drivers/firmware/efi/libstub/x86-stub.c
index cef32e2c82d8f..f90de11bc8855 100644
--- a/drivers/firmware/efi/libstub/x86-stub.c
+++ b/drivers/firmware/efi/libstub/x86-stub.c
@@ -1023,6 +1023,8 @@ void __noreturn efi_stub_entry(efi_handle_t handle,
 
        setup_unaccepted_memory();
 
+       install_poisoned_memory_table();
+
        status = exit_boot(boot_params, handle);
        if (status != EFI_SUCCESS) {
                efi_err("exit_boot() failed!\n");
diff --git a/include/linux/efi.h b/include/linux/efi.h
index b3c83516593d1..7787eb8d4e4c1 100644
--- a/include/linux/efi.h
+++ b/include/linux/efi.h
@@ -422,6 +422,7 @@ void efi_native_runtime_setup(void);
 #define LINUX_EFI_COCO_SECRET_AREA_GUID                EFI_GUID(0xadf956ad, 
0xe98c, 0x484c,  0xae, 0x11, 0xb5, 0x1c, 0x7d, 0x33, 0x64, 0x47)
 #define LINUX_EFI_BOOT_MEMMAP_GUID             EFI_GUID(0x800f683f, 0xd08b, 
0x423a,  0xa2, 0x93, 0x96, 0x5c, 0x3c, 0x6f, 0xe2, 0xb4)
 #define LINUX_EFI_UNACCEPTED_MEM_TABLE_GUID    EFI_GUID(0xd5d1de3c, 0x105c, 
0x44f9,  0x9e, 0xa9, 0xbc, 0xef, 0x98, 0x12, 0x00, 0x31)
+#define LINUX_EFI_POISONED_MEMORY_TABLE_GUID   EFI_GUID(0x78a5bf07, 0x7d2a, 
0x2889,  0x16, 0x31, 0x63, 0xd4, 0x56, 0xf2, 0x83, 0x50)
 
 #define RISCV_EFI_BOOT_PROTOCOL_GUID           EFI_GUID(0xccd15fec, 0x6f73, 
0x4eec,  0x83, 0x95, 0x3e, 0x69, 0xe4, 0xb9, 0x40, 0xbf)
 
@@ -650,6 +651,7 @@ extern struct efi {
        unsigned long                   mokvar_table;           /* MOK variable 
config table */
        unsigned long                   coco_secret;            /* Confidential 
computing secret table */
        unsigned long                   unaccepted;             /* Unaccepted 
memory table */
+       unsigned long                   poisoned_memory;        /* 
Hardware-poisoned memory table */
 
        efi_get_time_t                  *get_time;
        efi_set_time_t                  *set_time;
@@ -1271,6 +1273,25 @@ struct linux_efi_memreserve {
 #define EFI_MEMRESERVE_COUNT(size) (((size) - sizeof(struct 
linux_efi_memreserve)) \
        / sizeof_field(struct linux_efi_memreserve, entry[0]))
 
+/*
+ * Frames hardware-poisoned while a kernel runs are recorded here so a kexec'd
+ * kernel can keep them out of its allocator. Same growable linked-list shape
+ * as linux_efi_memreserve; carried across kexec via the EFI system table.
+ */
+struct linux_efi_poisoned_memory {
+       int             size;                   // allocated size of the array
+       atomic_t        count;                  // number of entries used
+       phys_addr_t     next;                   // pa of next struct instance
+       struct {
+               phys_addr_t     base;
+               phys_addr_t     size;
+       } entry[];
+};
+
+#define EFI_POISONED_MEMORY_COUNT(size) \
+       (((size) - sizeof(struct linux_efi_poisoned_memory)) \
+       / sizeof_field(struct linux_efi_poisoned_memory, entry[0]))
+
 void __init efi_arch_mem_reserve(phys_addr_t addr, u64 size);
 
 /*

-- 
2.53.0-Meta


Reply via email to