[PATCH for-4.22] x86/kexec: Check for a good per-cpu area before accessing IDTs

Andrew Cooper posted 1 patch 1 month ago
Patches applied successfully (tree, apply log)
git fetch https://gitlab.com/xen-project/patchew/xen tags/patchew/20260622172054.504778-1-andrew.cooper3@citrix.com
xen/arch/x86/machine_kexec.c | 8 +++++++-
xen/common/percpu.c          | 1 -
xen/include/xen/percpu.h     | 1 +
3 files changed, 8 insertions(+), 2 deletions(-)
[PATCH for-4.22] x86/kexec: Check for a good per-cpu area before accessing IDTs
Posted by Andrew Cooper 1 month ago
Prior to commit 9c20d3c5915d ("x86/IDT: Make idt_tables[] be per_cpu(idt)"),
the global idt_tables[] was always safe to use for CPUs in any state.

However, not-yet-onlined CPUs (e.g. MADT with more entries than exist in
practice) or offlined CPUs (e.g. xen-hptool) have their per-cpu pointer
poisoned to detect incorrect uses.  machine_kexec() trips over the posion when
clobbering #MC entry paths.

This fixes a fatal #GP (non-canonical memory reference) when trying to enter
the crash kernel.

Fixes: 9c20d3c5915d ("x86/IDT: Make idt_tables[] be per_cpu(idt)")
Reported-by: Lin Liu <Lin.Liu01@citrix.com>
Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
---
CC: Jan Beulich <jbeulich@suse.com>
CC: Roger Pau Monné <roger.pau@citrix.com>
CC: Teddy Astie <teddy.astie@vates.tech>
CC: Oleksii Kurochko <oleksii.kurochko@gmail.com>
CC: Lin Liu <Lin.Liu01@citrix.com>

The fix here is a bit ugly.  nmi_shootdown_cpus() uses the cpu_online_map but
this is wrong too; it misses parked CPUs, which do want to be captured.

For 4.22.  This is the minimal fix to stop systems crashing, but more work is
needed to make this path fully robust.
---
 xen/arch/x86/machine_kexec.c | 8 +++++++-
 xen/common/percpu.c          | 1 -
 xen/include/xen/percpu.h     | 1 +
 3 files changed, 8 insertions(+), 2 deletions(-)

diff --git a/xen/arch/x86/machine_kexec.c b/xen/arch/x86/machine_kexec.c
index f921eec5aae6..0f5437cb65cc 100644
--- a/xen/arch/x86/machine_kexec.c
+++ b/xen/arch/x86/machine_kexec.c
@@ -18,6 +18,7 @@
 #include <xen/domain_page.h>
 #include <xen/elfstructs.h>
 #include <xen/kexec.h>
+#include <xen/percpu.h>
 #include <xen/types.h>
 
 #include <asm/fixmap.h>
@@ -171,7 +172,12 @@ void machine_kexec(struct kexec_image *image)
      */
     for ( i = 0; i < nr_cpu_ids; i++ )
     {
-        idt_entry_t *idt = per_cpu(idt, i);
+        idt_entry_t *idt;
+
+        if ( __per_cpu_offset[i] == INVALID_PERCPU_AREA )
+            continue;
+
+        idt = per_cpu(idt, i);
 
         if ( !idt )
             continue;
diff --git a/xen/common/percpu.c b/xen/common/percpu.c
index cdd70acbeaf3..f180f37253ed 100644
--- a/xen/common/percpu.c
+++ b/xen/common/percpu.c
@@ -13,7 +13,6 @@
 
 #define PERCPU_ORDER get_order_from_bytes(__per_cpu_data_end - __per_cpu_start)
 
-extern char __per_cpu_start[];
 extern const char __per_cpu_data_end[];
 
 unsigned long __read_mostly __per_cpu_offset[NR_CPUS];
diff --git a/xen/include/xen/percpu.h b/xen/include/xen/percpu.h
index fcf2095bd543..30609f49f0b3 100644
--- a/xen/include/xen/percpu.h
+++ b/xen/include/xen/percpu.h
@@ -43,6 +43,7 @@
 #endif
 
 extern unsigned long __per_cpu_offset[];
+extern char __per_cpu_start[];
 
 #define per_cpu(var, cpu)  \
     (*RELOC_HIDE(&per_cpu__##var, __per_cpu_offset[cpu]))

base-commit: 6a21252a742ec021a814e124b88d273da37065db
-- 
2.39.5


Re: [PATCH for-4.22] x86/kexec: Check for a good per-cpu area before accessing IDTs
Posted by Oleksii Kurochko 1 month ago

On 6/22/26 7:20 PM, Andrew Cooper wrote:
> Prior to commit 9c20d3c5915d ("x86/IDT: Make idt_tables[] be per_cpu(idt)"),
> the global idt_tables[] was always safe to use for CPUs in any state.
> 
> However, not-yet-onlined CPUs (e.g. MADT with more entries than exist in
> practice) or offlined CPUs (e.g. xen-hptool) have their per-cpu pointer
> poisoned to detect incorrect uses.  machine_kexec() trips over the posion when
> clobbering #MC entry paths.
> 
> This fixes a fatal #GP (non-canonical memory reference) when trying to enter
> the crash kernel.
> 
> Fixes: 9c20d3c5915d ("x86/IDT: Make idt_tables[] be per_cpu(idt)")
> Reported-by: Lin Liu <Lin.Liu01@citrix.com>
> Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
> ---
> CC: Jan Beulich <jbeulich@suse.com>
> CC: Roger Pau Monné <roger.pau@citrix.com>
> CC: Teddy Astie <teddy.astie@vates.tech>
> CC: Oleksii Kurochko <oleksii.kurochko@gmail.com>
> CC: Lin Liu <Lin.Liu01@citrix.com>
> 
> The fix here is a bit ugly.  nmi_shootdown_cpus() uses the cpu_online_map but
> this is wrong too; it misses parked CPUs, which do want to be captured.
> 
> For 4.22.  This is the minimal fix to stop systems crashing, but more work is
> needed to make this path fully robust.
> ---

Release-Acked-by: Oleksii Kurochko <oleksii.kurochko@gmail.com>

Thanks.

~ Oleksii

Re: [PATCH for-4.22] x86/kexec: Check for a good per-cpu area before accessing IDTs
Posted by Jan Beulich 1 month ago
On 22.06.2026 19:20, Andrew Cooper wrote:
> Prior to commit 9c20d3c5915d ("x86/IDT: Make idt_tables[] be per_cpu(idt)"),
> the global idt_tables[] was always safe to use for CPUs in any state.
> 
> However, not-yet-onlined CPUs (e.g. MADT with more entries than exist in
> practice) or offlined CPUs (e.g. xen-hptool) have their per-cpu pointer
> poisoned to detect incorrect uses.  machine_kexec() trips over the posion when
> clobbering #MC entry paths.
> 
> This fixes a fatal #GP (non-canonical memory reference) when trying to enter
> the crash kernel.
> 
> Fixes: 9c20d3c5915d ("x86/IDT: Make idt_tables[] be per_cpu(idt)")
> Reported-by: Lin Liu <Lin.Liu01@citrix.com>
> Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>

Reviewed-by: Jan Beulich <jbeulich@suse.com>
albeit preferably, because of ...

> The fix here is a bit ugly.  nmi_shootdown_cpus() uses the cpu_online_map but
> this is wrong too; it misses parked CPUs, which do want to be captured.

... this, ...

> @@ -171,7 +172,12 @@ void machine_kexec(struct kexec_image *image)
>       */
>      for ( i = 0; i < nr_cpu_ids; i++ )
>      {
> -        idt_entry_t *idt = per_cpu(idt, i);
> +        idt_entry_t *idt;
> +
> +        if ( __per_cpu_offset[i] == INVALID_PERCPU_AREA )
> +            continue;
> +
> +        idt = per_cpu(idt, i);
>  
>          if ( !idt )
>              continue;

... with the comment ahead of the for() also adjusted to at least briefly
mention why cpu_online() cannot be used, and why hence this (seemingly)
awkward check is needed instead.

Jan