]> xenbits.xensource.com Git - xen.git/commitdiff
x86/shadow: Infrastructure to force a PV guest into shadow mode
authorJuergen Gross <jgross@suse.com>
Mon, 23 Jul 2018 06:11:40 +0000 (07:11 +0100)
committerAndrew Cooper <andrew.cooper3@citrix.com>
Tue, 14 Aug 2018 16:39:56 +0000 (17:39 +0100)
To mitigate L1TF, we cannot alter an architecturally-legitimate PTE a PV guest
chooses to write, but we can force the PV domain into shadow mode so Xen
controls the PTEs which are reachable by the CPU pagewalk.

Introduce new shadow mode, PG_SH_forced, and a tasklet to perform the
transition.  Later patches will introduce the logic to enable this mode at the
appropriate time.

To simplify vcpu cleanup, make tasklet_kill() idempotent with respect to
tasklet_init(), which involves adding a helper to check for an uninitialised
list head.

This is part of XSA-273 / CVE-2018-3620.

Signed-off-by: Juergen Gross <jgross@suse.com>
Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
Reviewed-by: Tim Deegan <tim@xen.org>
Reviewed-by: Jan Beulich <jbeulich@suse.com>
(cherry picked from commit b76ec3946bf6caca2c3950b857c008bc8db6723f)

xen/arch/x86/domain.c
xen/arch/x86/mm/paging.c
xen/arch/x86/mm/shadow/common.c
xen/common/tasklet.c
xen/include/asm-x86/domain.h
xen/include/asm-x86/paging.h
xen/include/asm-x86/shadow.h
xen/include/xen/list.h

index 3637c55baef0b6f6b86972cdb356e8d7b177a01a..3cfbf060b35b1af201933be8d292c087a6818386 100644 (file)
@@ -66,6 +66,7 @@
 #include <compat/vcpu.h>
 #include <asm/psr.h>
 #include <asm/spec_ctrl.h>
+#include <asm/shadow.h>
 
 static __read_mostly enum {
     PCID_OFF,
@@ -672,6 +673,8 @@ int arch_domain_create(struct domain *d, unsigned int domcr_flags,
         rc = 0;
     else
     {
+        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 )
@@ -777,7 +780,11 @@ int arch_domain_create(struct domain *d, unsigned int domcr_flags,
         paging_final_teardown(d);
     free_perdomain_mappings(d);
     if ( is_pv_domain(d) )
+    {
+        pv_l1tf_domain_destroy(d);
+
         free_xenheap_page(d->arch.pv_domain.gdt_ldt_l1tab);
+    }
     return rc;
 }
 
@@ -800,7 +807,11 @@ void arch_domain_destroy(struct domain *d)
 
     free_perdomain_mappings(d);
     if ( is_pv_domain(d) )
+    {
+        pv_l1tf_domain_destroy(d);
+
         free_xenheap_page(d->arch.pv_domain.gdt_ldt_l1tab);
+    }
 
     free_xenheap_page(d->shared_info);
     cleanup_domain_irq_mapping(d);
index 009c9bf14163cb7b8398f455be4a162a58aa66ca..2c829d4d796a335d04fd7e3c35f8dafca181614d 100644 (file)
@@ -874,6 +874,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) )
index 9d021a171361e61fc7a063b39ec9f0adc457341a..fde54e672429de6960a0c10f6da4ebd88848ce9c 100644 (file)
@@ -2969,6 +2969,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);
@@ -3856,6 +3865,29 @@ void shadow_audit_tables(struct vcpu *v)
 
 #endif /* Shadow audit */
 
+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);
+}
+
 /*
  * Local variables:
  * mode: C
index 4e42fa76713e3f5bec7679c73cb3b133dc37c1f1..9567944fc5e7cc67cf6d3ce212d6f8c77033586e 100644 (file)
@@ -153,6 +153,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));
@@ -169,6 +173,7 @@ void tasklet_kill(struct tasklet *t)
         spin_lock_irqsave(&tasklet_lock, flags);
     }
 
+ unlock:
     spin_unlock_irqrestore(&tasklet_lock, flags);
 }
 
index 20715e3207aeacfc2c76a569ab4d7bd03d8124b9..26004764715b9e7caf47accf344c2ef239633af6 100644 (file)
@@ -116,6 +116,9 @@ struct shadow_domain {
 
     /* Has this domain ever used HVMOP_pagetable_dying? */
     bool_t pagetable_dying_op;
+
+    /* PV L1 Terminal Fault mitigation. */
+    struct tasklet pv_l1tf_tasklet;
 #endif
 };
 
@@ -256,6 +259,8 @@ struct pv_domain
     bool_t xpti;
     /* Use PCID feature? */
     bool_t pcid;
+    /* Mitigate L1TF with shadow/crashing? */
+    bool_t check_l1tf;
 
     /* map_domain_page() mapping cache. */
     struct mapcache_domain mapcache;
index c4129530a6b2500620f77f6dc565319d65b37286..e44694faa600a76ecdd911963945dd8f9418d6e5 100644 (file)
 
 #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)
 
@@ -59,6 +62,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)
index 6d0aefb05ed86bfae3863d3087626d45e9117e23..7c658c11f527fc37688ff47b2e53c6ac9c8d5e00 100644 (file)
@@ -29,6 +29,7 @@
 #include <asm/flushtlb.h>
 #include <asm/paging.h>
 #include <asm/p2m.h>
+#include <asm/spec_ctrl.h>
 
 /*****************************************************************************
  * Macros to tell which shadow paging mode a domain is in*/
@@ -105,6 +106,37 @@ static inline int shadow_domctl(struct domain *d, xen_domctl_shadow_op_t *sc,
 
 #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);
+
+#ifdef CONFIG_SHADOW_PAGING
+    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)
+{
+#ifdef CONFIG_SHADOW_PAGING
+    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)
 {
index fa07d720eec5d2140d2834ea67205e4ed36d5c05..d99166a33a49ec6f74c26dc0bd2d7c3f7e09cc29 100644 (file)
@@ -51,6 +51,11 @@ static inline void INIT_LIST_HEAD(struct list_head *list)
     list->prev = list;
 }
 
+static inline bool_t list_head_is_null(const struct list_head *list)
+{
+    return !list->next && !list->prev;
+}
+
 /*
  * Insert a new entry between two known consecutive entries. 
  *