ebc4dec
From 419a4bbc20cf7c5d7d9dedae59fb8049922e6a2c Mon Sep 17 00:00:00 2001
ebc4dec
From: Henry Wang <Henry.Wang@arm.com>
ebc4dec
Date: Mon, 6 Jun 2022 06:17:28 +0000
ebc4dec
Subject: [PATCH 2/4] xen/arm: Construct the P2M pages pool for guests
ebc4dec
ebc4dec
This commit constructs the p2m pages pool for guests from the
ebc4dec
data structure and helper perspective.
ebc4dec
ebc4dec
This is implemented by:
ebc4dec
ebc4dec
- Adding a `struct paging_domain` which contains a freelist, a
ebc4dec
counter variable and a spinlock to `struct arch_domain` to
ebc4dec
indicate the free p2m pages and the number of p2m total pages in
ebc4dec
the p2m pages pool.
ebc4dec
ebc4dec
- Adding a helper `p2m_get_allocation` to get the p2m pool size.
ebc4dec
ebc4dec
- Adding a helper `p2m_set_allocation` to set the p2m pages pool
ebc4dec
size. This helper should be called before allocating memory for
ebc4dec
a guest.
ebc4dec
ebc4dec
- Adding a helper `p2m_teardown_allocation` to free the p2m pages
ebc4dec
pool. This helper should be called during the xl domain destory.
ebc4dec
ebc4dec
This is part of CVE-2022-33747 / XSA-409.
ebc4dec
ebc4dec
Signed-off-by: Henry Wang <Henry.Wang@arm.com>
ebc4dec
Reviewed-by: Stefano Stabellini <sstabellini@kernel.org>
ebc4dec
---
ebc4dec
 xen/arch/arm/p2m.c           | 88 ++++++++++++++++++++++++++++++++++++
ebc4dec
 xen/include/asm-arm/domain.h | 10 ++++
ebc4dec
 xen/include/asm-arm/p2m.h    |  4 ++
ebc4dec
 3 files changed, 102 insertions(+)
ebc4dec
ebc4dec
diff --git a/xen/arch/arm/p2m.c b/xen/arch/arm/p2m.c
ebc4dec
index 3bcd1e897e88..79f3d37f5230 100644
ebc4dec
--- a/xen/arch/arm/p2m.c
ebc4dec
+++ b/xen/arch/arm/p2m.c
ebc4dec
@@ -50,6 +50,92 @@ static uint64_t generate_vttbr(uint16_t vmid, mfn_t root_mfn)
ebc4dec
     return (mfn_to_maddr(root_mfn) | ((uint64_t)vmid << 48));
ebc4dec
 }
ebc4dec
 
ebc4dec
+/* Return the size of the pool, rounded up to the nearest MB */
ebc4dec
+unsigned int p2m_get_allocation(struct domain *d)
ebc4dec
+{
ebc4dec
+    unsigned long nr_pages = ACCESS_ONCE(d->arch.paging.p2m_total_pages);
ebc4dec
+
ebc4dec
+    return ROUNDUP(nr_pages, 1 << (20 - PAGE_SHIFT)) >> (20 - PAGE_SHIFT);
ebc4dec
+}
ebc4dec
+
ebc4dec
+/*
ebc4dec
+ * Set the pool of pages to the required number of pages.
ebc4dec
+ * Returns 0 for success, non-zero for failure.
ebc4dec
+ * Call with d->arch.paging.lock held.
ebc4dec
+ */
ebc4dec
+int p2m_set_allocation(struct domain *d, unsigned long pages, bool *preempted)
ebc4dec
+{
ebc4dec
+    struct page_info *pg;
ebc4dec
+
ebc4dec
+    ASSERT(spin_is_locked(&d->arch.paging.lock));
ebc4dec
+
ebc4dec
+    for ( ; ; )
ebc4dec
+    {
ebc4dec
+        if ( d->arch.paging.p2m_total_pages < pages )
ebc4dec
+        {
ebc4dec
+            /* Need to allocate more memory from domheap */
ebc4dec
+            pg = alloc_domheap_page(NULL, 0);
ebc4dec
+            if ( pg == NULL )
ebc4dec
+            {
ebc4dec
+                printk(XENLOG_ERR "Failed to allocate P2M pages.\n");
ebc4dec
+                return -ENOMEM;
ebc4dec
+            }
ebc4dec
+            ACCESS_ONCE(d->arch.paging.p2m_total_pages) =
ebc4dec
+                d->arch.paging.p2m_total_pages + 1;
ebc4dec
+            page_list_add_tail(pg, &d->arch.paging.p2m_freelist);
ebc4dec
+        }
ebc4dec
+        else if ( d->arch.paging.p2m_total_pages > pages )
ebc4dec
+        {
ebc4dec
+            /* Need to return memory to domheap */
ebc4dec
+            pg = page_list_remove_head(&d->arch.paging.p2m_freelist);
ebc4dec
+            if( pg )
ebc4dec
+            {
ebc4dec
+                ACCESS_ONCE(d->arch.paging.p2m_total_pages) =
ebc4dec
+                    d->arch.paging.p2m_total_pages - 1;
ebc4dec
+                free_domheap_page(pg);
ebc4dec
+            }
ebc4dec
+            else
ebc4dec
+            {
ebc4dec
+                printk(XENLOG_ERR
ebc4dec
+                       "Failed to free P2M pages, P2M freelist is empty.\n");
ebc4dec
+                return -ENOMEM;
ebc4dec
+            }
ebc4dec
+        }
ebc4dec
+        else
ebc4dec
+            break;
ebc4dec
+
ebc4dec
+        /* Check to see if we need to yield and try again */
ebc4dec
+        if ( preempted && general_preempt_check() )
ebc4dec
+        {
ebc4dec
+            *preempted = true;
ebc4dec
+            return -ERESTART;
ebc4dec
+        }
ebc4dec
+    }
ebc4dec
+
ebc4dec
+    return 0;
ebc4dec
+}
ebc4dec
+
ebc4dec
+int p2m_teardown_allocation(struct domain *d)
ebc4dec
+{
ebc4dec
+    int ret = 0;
ebc4dec
+    bool preempted = false;
ebc4dec
+
ebc4dec
+    spin_lock(&d->arch.paging.lock);
ebc4dec
+    if ( d->arch.paging.p2m_total_pages != 0 )
ebc4dec
+    {
ebc4dec
+        ret = p2m_set_allocation(d, 0, &preempted);
ebc4dec
+        if ( preempted )
ebc4dec
+        {
ebc4dec
+            spin_unlock(&d->arch.paging.lock);
ebc4dec
+            return -ERESTART;
ebc4dec
+        }
ebc4dec
+        ASSERT(d->arch.paging.p2m_total_pages == 0);
ebc4dec
+    }
ebc4dec
+    spin_unlock(&d->arch.paging.lock);
ebc4dec
+
ebc4dec
+    return ret;
ebc4dec
+}
ebc4dec
+
ebc4dec
 /* Unlock the flush and do a P2M TLB flush if necessary */
ebc4dec
 void p2m_write_unlock(struct p2m_domain *p2m)
ebc4dec
 {
ebc4dec
@@ -1599,7 +1685,9 @@ int p2m_init(struct domain *d)
ebc4dec
     unsigned int cpu;
ebc4dec
 
ebc4dec
     rwlock_init(&p2m->lock);
ebc4dec
+    spin_lock_init(&d->arch.paging.lock);
ebc4dec
     INIT_PAGE_LIST_HEAD(&p2m->pages);
ebc4dec
+    INIT_PAGE_LIST_HEAD(&d->arch.paging.p2m_freelist);
ebc4dec
 
ebc4dec
     p2m->vmid = INVALID_VMID;
ebc4dec
 
ebc4dec
diff --git a/xen/include/asm-arm/domain.h b/xen/include/asm-arm/domain.h
ebc4dec
index 9b3647587a04..c90daa65afa7 100644
ebc4dec
--- a/xen/include/asm-arm/domain.h
ebc4dec
+++ b/xen/include/asm-arm/domain.h
ebc4dec
@@ -40,6 +40,14 @@ struct vtimer {
ebc4dec
     uint64_t cval;
ebc4dec
 };
ebc4dec
 
ebc4dec
+struct paging_domain {
ebc4dec
+    spinlock_t lock;
ebc4dec
+    /* Free P2M pages from the pre-allocated P2M pool */
ebc4dec
+    struct page_list_head p2m_freelist;
ebc4dec
+    /* Number of pages from the pre-allocated P2M pool */
ebc4dec
+    unsigned long p2m_total_pages;
ebc4dec
+};
ebc4dec
+
ebc4dec
 struct arch_domain
ebc4dec
 {
ebc4dec
 #ifdef CONFIG_ARM_64
ebc4dec
@@ -51,6 +59,8 @@ struct arch_domain
ebc4dec
 
ebc4dec
     struct hvm_domain hvm;
ebc4dec
 
ebc4dec
+    struct paging_domain paging;
ebc4dec
+
ebc4dec
     struct vmmio vmmio;
ebc4dec
 
ebc4dec
     /* Continuable domain_relinquish_resources(). */
ebc4dec
diff --git a/xen/include/asm-arm/p2m.h b/xen/include/asm-arm/p2m.h
ebc4dec
index b3ba83283e11..c9598740bd02 100644
ebc4dec
--- a/xen/include/asm-arm/p2m.h
ebc4dec
+++ b/xen/include/asm-arm/p2m.h
ebc4dec
@@ -218,6 +218,10 @@ void p2m_restore_state(struct vcpu *n);
ebc4dec
 /* Print debugging/statistial info about a domain's p2m */
ebc4dec
 void p2m_dump_info(struct domain *d);
ebc4dec
 
ebc4dec
+unsigned int p2m_get_allocation(struct domain *d);
ebc4dec
+int p2m_set_allocation(struct domain *d, unsigned long pages, bool *preempted);
ebc4dec
+int p2m_teardown_allocation(struct domain *d);
ebc4dec
+
ebc4dec
 static inline void p2m_write_lock(struct p2m_domain *p2m)
ebc4dec
 {
ebc4dec
     write_lock(&p2m->lock);
ebc4dec
-- 
ebc4dec
2.37.1
ebc4dec