x86/mm: accurately track which vCPU page-tables are loaded

Neither current nor curr_vcpu per-CPU fields accurately track which
page-tables are loaded.  There are corner cases when dealing with shadow
paging failures that switch to the idle vCPU page-tables without changing
current or curr_vcpu per-CPU fields.

Introduce a new per-CPU field that attempts to track which vCPU page-tables
are loaded.  Update such tracking when cr3 is changed, and do so in a
region with interrupts disabled, as to avoid handling interrupts with a
mismatch between the vCPU tracking field and the loaded page-tables.

As a result of this newly more accurate tracking the mapcache override
functionality can be removed: the dom0 PV builder was the only user of it,
and it's updated here to properly signal which vCPU page-tables are loaded
in the calls to switch_cr3_cr4().

Note the EFI page-tables have the Xen owned L4 slots copied from the idle
page-tables, so for the effects of the mapcache the EFI page-tables could
use the idle mapcache if it had one.  Pass the idle vCPU in the
switch_cr3_cr4() call that switches to the runtime EFI page-tables.

There are known issues with the use of mapcache in NMI context.  This patch
does not alter the behaviour.

This is CVE-2026-42488 / XSA-494.

Fixes: fb0ff49fe9 ("x86/shadow: defer releasing of PV's top-level shadow reference")
Signed-off-by: Roger Pau Monné <roger.pau@citrix.com>
Acked-by: Andrew Cooper <andrew.cooper3@citrix.com>
This commit is contained in:
Roger Pau Monne authored and Andrew Cooper committed 2026-06-09 12:45:56 +01:00
1 parent 161e8f61b5
commit 622c9a5ba9
12 files changed
+52 -60

No files matched your search

+19 -27
View File
@@ -18,48 +18,40 @@
#include <asm/hardirq.h> #include <asm/hardirq.h>
#include <asm/setup.h> #include <asm/setup.h>
static DEFINE_PER_CPU(struct vcpu *, override);
static inline struct vcpu *mapcache_current_vcpu(void) static inline struct vcpu *mapcache_current_vcpu(void)
{ {
/* In the common case we use the mapcache of the running VCPU. */ struct vcpu *v = this_cpu(pgtable_vcpu);
struct vcpu *v = this_cpu(override) ?: current; struct vcpu *curr = current;
/* /*
* When current isn't properly set up yet, this is equivalent to * During early boot pgtable_vcpu is not set, callers must handle NULL.
* running in an idle vCPU (callers must check for NULL). * Non-PV domains don't have a mapcache, the directmap covers all physical
* address space.
*/ */
if ( !v ) if ( !v || !is_pv_vcpu(v) )
return NULL; return NULL;
/* /*
* When using efi runtime page tables, we have the equivalent of the idle * If we are in a lazy context-switch state from a PV vCPU do a full switch
* domain's page tables but current may point at another domain's VCPU. * to the idle vCPU now, otherwise an incoming FLUSH_VCPU_STATE IPI would
* Return NULL as though current is not properly set up yet. * change the page tables under our feet an invalidate any in-use mapcache
* entries.
*/ */
if ( efi_rs_using_pgtables() ) if ( unlikely(this_cpu(curr_vcpu) != curr) )
return NULL;
/*
* If guest_table is NULL, and we are running a paravirtualised guest,
* then it means we are running on the idle domain's page table and must
* therefore use its mapcache.
*/
if ( unlikely(pagetable_is_null(v->arch.guest_table)) && is_pv_vcpu(v) )
{ {
/* If we really are idling, perform lazy context switch now. */ ASSERT(curr == idle_vcpu[smp_processor_id()]);
if ( (v = idle_vcpu[smp_processor_id()]) == current )
sync_local_execstate(); sync_local_execstate();
/* We must now be running on the idle page table. */ /* We must now be running on the idle page table. */
ASSERT(cr3_pa(read_cr3()) == __pa(idle_pg_table)); ASSERT(cr3_pa(read_cr3()) == __pa(idle_pg_table));
} }
return v; /*
} * At this point we can guarantee Xen is not in lazy context switch: either
* the code above will have synced the state, or an incoming
void __init mapcache_override_current(struct vcpu *v) * FLUSH_VCPU_STATE IPI has done so behind our back. Use ACCESS_ONCE to
{ * ensure the compiler never returns the locally cached pgtable_vcpu value.
this_cpu(override) = v; */
return ACCESS_ONCE(this_cpu(pgtable_vcpu));
} }
#define mapcache_l2_entry(e) ((e) >> PAGETABLE_ORDER) #define mapcache_l2_entry(e) ((e) >> PAGETABLE_ORDER)
+4 -1
View File
@@ -104,7 +104,9 @@ static void do_tlb_flush(void)
local_irq_restore(flags); local_irq_restore(flags);
} }
void switch_cr3_cr4(unsigned long cr3, unsigned long cr4) DEFINE_PER_CPU(struct vcpu *, pgtable_vcpu);
void switch_cr3_cr4(struct vcpu *v, unsigned long cr3, unsigned long cr4)
{ {
unsigned long flags, old_cr4; unsigned long flags, old_cr4;
u32 t = 0; u32 t = 0;
@@ -148,6 +150,7 @@ void switch_cr3_cr4(unsigned long cr3, unsigned long cr4)
if ( (old_cr4 & X86_CR4_PCIDE) > (cr4 & X86_CR4_PCIDE) ) if ( (old_cr4 & X86_CR4_PCIDE) > (cr4 & X86_CR4_PCIDE) )
cr3 |= X86_CR3_NOFLUSH; cr3 |= X86_CR3_NOFLUSH;
write_cr3(cr3); write_cr3(cr3);
this_cpu(pgtable_vcpu) = v;
if ( old_cr4 != cr4 ) if ( old_cr4 != cr4 )
write_cr4(cr4); write_cr4(cr4);
-1
View File
@@ -90,7 +90,6 @@ struct mapcache_domain {
int mapcache_domain_init(struct domain *d); int mapcache_domain_init(struct domain *d);
int mapcache_vcpu_init(struct vcpu *v); int mapcache_vcpu_init(struct vcpu *v);
void mapcache_override_current(struct vcpu *v);
/* x86/64: toggle guest between kernel and user modes. */ /* x86/64: toggle guest between kernel and user modes. */
void toggle_guest_mode(struct vcpu *v); void toggle_guest_mode(struct vcpu *v);
+1 -1
View File
@@ -104,7 +104,7 @@ static inline void invlpg(const void *p)
} }
/* Write pagetable base and implicitly tick the tlbflush clock. */ /* Write pagetable base and implicitly tick the tlbflush clock. */
void switch_cr3_cr4(unsigned long cr3, unsigned long cr4); void switch_cr3_cr4(struct vcpu *v, unsigned long cr3, unsigned long cr4);
/* flush_* flag fields: */ /* flush_* flag fields: */
/* /*
+3
View File
@@ -329,6 +329,9 @@ DECLARE_PER_CPU(struct tss_page, tss_page);
DECLARE_PER_CPU(root_pgentry_t *, root_pgt); DECLARE_PER_CPU(root_pgentry_t *, root_pgt);
/* vCPU of the currently loaded page-tables. */
DECLARE_PER_CPU(struct vcpu *, pgtable_vcpu);
extern void write_ptbase(struct vcpu *v); extern void write_ptbase(struct vcpu *v);
/* PAUSE (encoding: REP NOP) is a good thing to insert into busy-wait loops. */ /* PAUSE (encoding: REP NOP) is a good thing to insert into busy-wait loops. */
+2 -2
View File
@@ -535,7 +535,7 @@ void write_ptbase(struct vcpu *v)
cpu_info->pv_cr3 = __pa(this_cpu(root_pgt)); cpu_info->pv_cr3 = __pa(this_cpu(root_pgt));
if ( new_cr4 & X86_CR4_PCIDE ) if ( new_cr4 & X86_CR4_PCIDE )
cpu_info->pv_cr3 |= get_pcid_bits(v, true); cpu_info->pv_cr3 |= get_pcid_bits(v, true);
switch_cr3_cr4(v->arch.cr3, new_cr4); switch_cr3_cr4(v, v->arch.cr3, new_cr4);
} }
else else
{ {
@@ -543,7 +543,7 @@ void write_ptbase(struct vcpu *v)
cpu_info->use_pv_cr3 = false; cpu_info->use_pv_cr3 = false;
cpu_info->xen_cr3 = 0; cpu_info->xen_cr3 = 0;
/* switch_cr3_cr4() serializes. */ /* switch_cr3_cr4() serializes. */
switch_cr3_cr4(v->arch.cr3, new_cr4); switch_cr3_cr4(v, v->arch.cr3, new_cr4);
cpu_info->pv_cr3 = 0; cpu_info->pv_cr3 = 0;
} }
} }
+4 -8
View File
@@ -831,8 +831,7 @@ static int __init dom0_construct(const struct boot_domain *bd)
update_cr3(v); update_cr3(v);
/* We run on dom0's page tables for the final part of the build process. */ /* We run on dom0's page tables for the final part of the build process. */
switch_cr3_cr4(cr3_pa(v->arch.cr3), read_cr4()); switch_cr3_cr4(v, cr3_pa(v->arch.cr3), read_cr4());
mapcache_override_current(v);
/* Copy the OS image and free temporary buffer. */ /* Copy the OS image and free temporary buffer. */
elf.dest_base = (void*)vkern_start; elf.dest_base = (void*)vkern_start;
@@ -841,8 +840,7 @@ static int __init dom0_construct(const struct boot_domain *bd)
rc = elf_load_binary(&elf); rc = elf_load_binary(&elf);
if ( rc < 0 ) if ( rc < 0 )
{ {
mapcache_override_current(NULL); switch_cr3_cr4(current, current->arch.cr3, read_cr4());
switch_cr3_cr4(current->arch.cr3, read_cr4());
printk("Failed to load the kernel binary\n"); printk("Failed to load the kernel binary\n");
goto out; goto out;
} }
@@ -853,8 +851,7 @@ static int __init dom0_construct(const struct boot_domain *bd)
if ( (parms.virt_hypercall < v_start) || if ( (parms.virt_hypercall < v_start) ||
(parms.virt_hypercall >= v_end) ) (parms.virt_hypercall >= v_end) )
{ {
mapcache_override_current(NULL); switch_cr3_cr4(current, current->arch.cr3, read_cr4());
switch_cr3_cr4(current->arch.cr3, read_cr4());
printk("Invalid HYPERCALL_PAGE field in ELF notes.\n"); printk("Invalid HYPERCALL_PAGE field in ELF notes.\n");
return -EINVAL; return -EINVAL;
} }
@@ -995,8 +992,7 @@ static int __init dom0_construct(const struct boot_domain *bd)
#endif #endif
/* Return to idle domain's page tables. */ /* Return to idle domain's page tables. */
mapcache_override_current(NULL); switch_cr3_cr4(current, current->arch.cr3, read_cr4());
switch_cr3_cr4(current->arch.cr3, read_cr4());
update_domain_wallclock_time(d); update_domain_wallclock_time(d);
+10 -1
View File
@@ -457,6 +457,8 @@ static void _toggle_guest_pt(struct vcpu *v)
pagetable_t old_shadow; pagetable_t old_shadow;
unsigned long cr3; unsigned long cr3;
ASSERT(local_irq_is_enabled());
v->arch.flags ^= TF_kernel_mode; v->arch.flags ^= TF_kernel_mode;
guest_update = v->arch.flags & TF_kernel_mode; guest_update = v->arch.flags & TF_kernel_mode;
old_shadow = update_cr3(v); old_shadow = update_cr3(v);
@@ -479,14 +481,21 @@ static void _toggle_guest_pt(struct vcpu *v)
{ {
cr3 &= ~X86_CR3_NOFLUSH; cr3 &= ~X86_CR3_NOFLUSH;
local_irq_disable();
if ( unlikely(mfn_eq(pagetable_get_mfn(old_shadow), if ( unlikely(mfn_eq(pagetable_get_mfn(old_shadow),
maddr_to_mfn(cr3))) ) maddr_to_mfn(cr3))) )
{ {
cr3 = idle_vcpu[v->processor]->arch.cr3;
/* Also suppress runstate/time area updates below. */ /* Also suppress runstate/time area updates below. */
guest_update = false; guest_update = false;
cr3 = idle_vcpu[v->processor]->arch.cr3;
this_cpu(pgtable_vcpu) = idle_vcpu[v->processor];
} }
write_cr3(cr3);
local_irq_enable();
} }
else
write_cr3(cr3); write_cr3(cr3);
if ( !pagetable_is_null(old_shadow) ) if ( !pagetable_is_null(old_shadow) )
+1
View File
@@ -1063,6 +1063,7 @@ static int cpu_smpboot_alloc(unsigned int cpu)
info->current_vcpu = idle_vcpu[cpu]; /* set_current() */ info->current_vcpu = idle_vcpu[cpu]; /* set_current() */
per_cpu(curr_vcpu, cpu) = idle_vcpu[cpu]; per_cpu(curr_vcpu, cpu) = idle_vcpu[cpu];
per_cpu(pgtable_vcpu, cpu) = idle_vcpu[cpu];
gdt = per_cpu(gdt, cpu) ?: alloc_xenheap_pages(0, memflags); gdt = per_cpu(gdt, cpu) ?: alloc_xenheap_pages(0, memflags);
if ( gdt == NULL ) if ( gdt == NULL )
-5
View File
@@ -7,11 +7,6 @@ bool efi_enabled(unsigned int feature)
return false; return false;
} }
bool efi_rs_using_pgtables(void)
{
return false;
}
unsigned long efi_get_time(void) unsigned long efi_get_time(void)
{ {
BUG(); BUG();
+8 -13
View File
@@ -50,7 +50,6 @@ const CHAR16 *__read_mostly efi_fw_vendor;
const EFI_RUNTIME_SERVICES *__read_mostly efi_rs; const EFI_RUNTIME_SERVICES *__read_mostly efi_rs;
#ifndef CONFIG_ARM /* TODO - disabled until implemented on ARM */ #ifndef CONFIG_ARM /* TODO - disabled until implemented on ARM */
static DEFINE_SPINLOCK(efi_rs_lock); static DEFINE_SPINLOCK(efi_rs_lock);
static unsigned int efi_rs_on_cpu = NR_CPUS;
#endif #endif
UINTN __read_mostly efi_memmap_size; UINTN __read_mostly efi_memmap_size;
@@ -93,6 +92,11 @@ struct efi_rs_state efi_rs_enter(void)
if ( mfn_eq(efi_l4_mfn, INVALID_MFN) ) if ( mfn_eq(efi_l4_mfn, INVALID_MFN) )
return state; return state;
/*
* If in lazy idle context switch state sync now to avoid an incoming
* FLUSH_VCPU_STATE IPI changing the loaded page-tables.
*/
sync_local_execstate();
state.cr3 = read_cr3(); state.cr3 = read_cr3();
vcpu_save_fpu(current); vcpu_save_fpu(current);
asm volatile ( "fnclex; fldcw %0" :: "m" (fcw) ); asm volatile ( "fnclex; fldcw %0" :: "m" (fcw) );
@@ -100,8 +104,6 @@ struct efi_rs_state efi_rs_enter(void)
spin_lock(&efi_rs_lock); spin_lock(&efi_rs_lock);
efi_rs_on_cpu = smp_processor_id();
/* prevent fixup_page_fault() from doing anything */ /* prevent fixup_page_fault() from doing anything */
irq_enter(); irq_enter();
@@ -116,7 +118,8 @@ struct efi_rs_state efi_rs_enter(void)
lgdt(&gdt_desc); lgdt(&gdt_desc);
} }
switch_cr3_cr4(mfn_to_maddr(efi_l4_mfn), read_cr4()); switch_cr3_cr4(idle_vcpu[smp_processor_id()], mfn_to_maddr(efi_l4_mfn),
read_cr4());
/* /*
* At the time of writing (2022), no UEFI firwmare is CET-IBT compatible. * At the time of writing (2022), no UEFI firwmare is CET-IBT compatible.
@@ -144,7 +147,7 @@ void efi_rs_leave(struct efi_rs_state *state)
if ( state->msr_s_cet ) if ( state->msr_s_cet )
wrmsrl(MSR_S_CET, state->msr_s_cet); wrmsrl(MSR_S_CET, state->msr_s_cet);
switch_cr3_cr4(state->cr3, read_cr4()); switch_cr3_cr4(curr, state->cr3, read_cr4());
if ( is_pv_vcpu(curr) && !is_idle_vcpu(curr) ) if ( is_pv_vcpu(curr) && !is_idle_vcpu(curr) )
{ {
struct desc_ptr gdt_desc = { struct desc_ptr gdt_desc = {
@@ -155,18 +158,10 @@ void efi_rs_leave(struct efi_rs_state *state)
lgdt(&gdt_desc); lgdt(&gdt_desc);
} }
irq_exit(); irq_exit();
efi_rs_on_cpu = NR_CPUS;
spin_unlock(&efi_rs_lock); spin_unlock(&efi_rs_lock);
vcpu_restore_fpu(curr); vcpu_restore_fpu(curr);
} }
bool efi_rs_using_pgtables(void)
{
return !mfn_eq(efi_l4_mfn, INVALID_MFN) &&
(smp_processor_id() == efi_rs_on_cpu) &&
(read_cr3() == mfn_to_maddr(efi_l4_mfn));
}
unsigned long efi_get_time(void) unsigned long efi_get_time(void)
{ {
EFI_TIME time; EFI_TIME time;
-1
View File
@@ -40,7 +40,6 @@ extern bool efi_secure_boot;
void efi_init_memory(void); void efi_init_memory(void);
bool efi_boot_mem_unused(unsigned long *start, unsigned long *end); bool efi_boot_mem_unused(unsigned long *start, unsigned long *end);
bool efi_rs_using_pgtables(void);
unsigned long efi_get_time(void); unsigned long efi_get_time(void);
void efi_reset_system(bool warm); void efi_reset_system(bool warm);
#ifndef COMPAT #ifndef COMPAT
Reference in new issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.