diff --git a/xen/arch/x86/mm/paging.c b/xen/arch/x86/mm/paging.c index 2b0445ffe9..dcee496eb0 100644 --- a/xen/arch/x86/mm/paging.c +++ b/xen/arch/x86/mm/paging.c @@ -873,6 +873,8 @@ void paging_dump_domain_info(struct domain *d) printk(" paging assistance: "); if ( paging_mode_shadow(d) ) printk("shadow "); + if ( paging_mode_sh_forced(d) ) + printk("forced "); if ( paging_mode_hap(d) ) printk("hap "); if ( paging_mode_refcounts(d) ) diff --git a/xen/arch/x86/mm/shadow/common.c b/xen/arch/x86/mm/shadow/common.c index dd61b50eb7..fd42d734e7 100644 --- a/xen/arch/x86/mm/shadow/common.c +++ b/xen/arch/x86/mm/shadow/common.c @@ -3177,6 +3177,15 @@ static void sh_new_mode(struct domain *d, u32 new_mode) ASSERT(paging_locked_by_me(d)); ASSERT(d != current->domain); + /* + * If PG_SH_forced has previously been activated because of writing an + * L1TF-vulnerable PTE, it must remain active for the remaining lifetime + * of the domain, even if the logdirty mode needs to be controlled for + * migration purposes. + */ + if ( paging_mode_sh_forced(d) ) + new_mode |= PG_SH_forced | PG_SH_enable; + d->arch.paging.mode = new_mode; for_each_vcpu(d, v) sh_update_paging_modes(v); @@ -4057,6 +4066,33 @@ void shadow_audit_tables(struct vcpu *v) #endif /* Shadow audit */ +#ifdef CONFIG_PV + +void pv_l1tf_tasklet(unsigned long data) +{ + struct domain *d = (void *)data; + + domain_pause(d); + paging_lock(d); + + if ( !paging_mode_sh_forced(d) && !d->is_dying ) + { + int ret = shadow_one_bit_enable(d, PG_SH_forced); + + if ( ret ) + { + printk(XENLOG_G_ERR "d%d Failed to enable PG_SH_forced: %d\n", + d->domain_id, ret); + domain_crash(d); + } + } + + paging_unlock(d); + domain_unpause(d); +} + +#endif /* CONFIG_PV */ + /* * Local variables: * mode: C diff --git a/xen/arch/x86/pv/domain.c b/xen/arch/x86/pv/domain.c index a4f0bd239d..3230ac6a22 100644 --- a/xen/arch/x86/pv/domain.c +++ b/xen/arch/x86/pv/domain.c @@ -13,6 +13,7 @@ #include #include #include +#include static __read_mostly enum { PCID_OFF, @@ -209,6 +210,8 @@ int pv_vcpu_initialise(struct vcpu *v) void pv_domain_destroy(struct domain *d) { + pv_l1tf_domain_destroy(d); + destroy_perdomain_mapping(d, GDT_LDT_VIRT_START, GDT_LDT_MBYTES << (20 - PAGE_SHIFT)); @@ -229,6 +232,8 @@ int pv_domain_initialise(struct domain *d) }; int rc = -ENOMEM; + pv_l1tf_domain_init(d); + d->arch.pv_domain.gdt_ldt_l1tab = alloc_xenheap_pages(0, MEMF_node(domain_to_node(d))); if ( !d->arch.pv_domain.gdt_ldt_l1tab ) diff --git a/xen/common/tasklet.c b/xen/common/tasklet.c index 0f0a6f8365..d4fea3151c 100644 --- a/xen/common/tasklet.c +++ b/xen/common/tasklet.c @@ -156,6 +156,10 @@ void tasklet_kill(struct tasklet *t) spin_lock_irqsave(&tasklet_lock, flags); + /* Cope with uninitialised tasklets. */ + if ( list_head_is_null(&t->list) ) + goto unlock; + if ( !list_empty(&t->list) ) { BUG_ON(t->is_dead || t->is_running || (t->scheduled_on < 0)); @@ -172,6 +176,7 @@ void tasklet_kill(struct tasklet *t) spin_lock_irqsave(&tasklet_lock, flags); } + unlock: spin_unlock_irqrestore(&tasklet_lock, flags); } diff --git a/xen/include/asm-x86/domain.h b/xen/include/asm-x86/domain.h index e2442f4e52..09f6b3d2f8 100644 --- a/xen/include/asm-x86/domain.h +++ b/xen/include/asm-x86/domain.h @@ -121,6 +121,11 @@ struct shadow_domain { /* Has this domain ever used HVMOP_pagetable_dying? */ bool_t pagetable_dying_op; + +#ifdef CONFIG_PV + /* PV L1 Terminal Fault mitigation. */ + struct tasklet pv_l1tf_tasklet; +#endif /* CONFIG_PV */ #endif }; @@ -257,6 +262,8 @@ struct pv_domain bool xpti; /* Use PCID feature? */ bool pcid; + /* Mitigate L1TF with shadow/crashing? */ + bool check_l1tf; /* map_domain_page() mapping cache. */ struct mapcache_domain mapcache; diff --git a/xen/include/asm-x86/paging.h b/xen/include/asm-x86/paging.h index f0085511c7..f440e3e53c 100644 --- a/xen/include/asm-x86/paging.h +++ b/xen/include/asm-x86/paging.h @@ -37,11 +37,14 @@ #define PG_SH_shift 20 #define PG_HAP_shift 21 +#define PG_SHF_shift 22 /* We're in one of the shadow modes */ #ifdef CONFIG_SHADOW_PAGING #define PG_SH_enable (1U << PG_SH_shift) +#define PG_SH_forced (1U << PG_SHF_shift) #else #define PG_SH_enable 0 +#define PG_SH_forced 0 #endif #define PG_HAP_enable (1U << PG_HAP_shift) @@ -62,6 +65,7 @@ #define paging_mode_enabled(_d) (!!(_d)->arch.paging.mode) #define paging_mode_shadow(_d) (!!((_d)->arch.paging.mode & PG_SH_enable)) +#define paging_mode_sh_forced(_d) (!!((_d)->arch.paging.mode & PG_SH_forced)) #define paging_mode_hap(_d) (!!((_d)->arch.paging.mode & PG_HAP_enable)) #define paging_mode_refcounts(_d) (!!((_d)->arch.paging.mode & PG_refcounts)) diff --git a/xen/include/asm-x86/shadow.h b/xen/include/asm-x86/shadow.h index 94a34fd16a..14afb7db52 100644 --- a/xen/include/asm-x86/shadow.h +++ b/xen/include/asm-x86/shadow.h @@ -29,6 +29,7 @@ #include #include #include +#include /***************************************************************************** * Macros to tell which shadow paging mode a domain is in*/ @@ -115,6 +116,37 @@ static inline int shadow_domctl(struct domain *d, #endif /* CONFIG_SHADOW_PAGING */ +/* + * Mitigations for L1TF / CVE-2018-3620 for PV guests. + * + * We cannot alter an architecturally-legitimate PTE which a PV guest has + * chosen to write, as traditional paged-out metadata is L1TF-vulnerable. + * What we can do is force a PV guest which writes a vulnerable PTE into + * shadow mode, so Xen controls the pagetables which are reachable by the CPU + * pagewalk. + */ + +void pv_l1tf_tasklet(unsigned long data); + +static inline void pv_l1tf_domain_init(struct domain *d) +{ + d->arch.pv_domain.check_l1tf = + opt_pv_l1tf & (is_hardware_domain(d) + ? OPT_PV_L1TF_DOM0 : OPT_PV_L1TF_DOMU); + +#if defined(CONFIG_SHADOW_PAGING) && defined(CONFIG_PV) + tasklet_init(&d->arch.paging.shadow.pv_l1tf_tasklet, + pv_l1tf_tasklet, (unsigned long)d); +#endif +} + +static inline void pv_l1tf_domain_destroy(struct domain *d) +{ +#if defined(CONFIG_SHADOW_PAGING) && defined(CONFIG_PV) + tasklet_kill(&d->arch.paging.shadow.pv_l1tf_tasklet); +#endif +} + /* Remove all shadows of the guest mfn. */ static inline void shadow_remove_all_shadows(struct domain *d, mfn_t gmfn) { diff --git a/xen/include/xen/list.h b/xen/include/xen/list.h index fa07d720ee..1387abb211 100644 --- a/xen/include/xen/list.h +++ b/xen/include/xen/list.h @@ -51,6 +51,11 @@ static inline void INIT_LIST_HEAD(struct list_head *list) list->prev = list; } +static inline bool list_head_is_null(const struct list_head *list) +{ + return !list->next && !list->prev; +} + /* * Insert a new entry between two known consecutive entries. *