Compare commits
11 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d5bea92a5 | ||
|
|
8cfa1f224d | ||
|
|
47e68f69a5 | ||
|
|
c1dd47a6db | ||
|
|
d1afe2704c | ||
|
|
9cb1314e73 | ||
|
|
7a127d3fbe | ||
|
|
ceaeefb05a | ||
|
|
ec4c2c59a0 | ||
|
|
d759d346c3 | ||
|
|
132ad1fbee |
79 changed files with 4652 additions and 6287 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -6,4 +6,4 @@ lwip-1.3.0.tar.gz
|
|||
pciutils-2.2.9.tar.bz2
|
||||
zlib-1.2.3.tar.gz
|
||||
polarssl-1.1.4-gpl.tgz
|
||||
/xen-4.12.1.tar.gz
|
||||
/xen-4.12.3.tar.gz
|
||||
|
|
|
|||
2
sources
2
sources
|
|
@ -4,4 +4,4 @@ SHA512 (newlib-1.16.0.tar.gz) = 40eb96bbc6736a16b6399e0cdb73e853d0d90b685c967e77
|
|||
SHA512 (zlib-1.2.3.tar.gz) = 021b958fcd0d346c4ba761bcf0cc40f3522de6186cf5a0a6ea34a70504ce9622b1c2626fce40675bc8282cf5f5ade18473656abc38050f72f5d6480507a2106e
|
||||
SHA512 (polarssl-1.1.4-gpl.tgz) = 88da614e4d3f4409c4fd3bb3e44c7587ba051e3fed4e33d526069a67e8180212e1ea22da984656f50e290049f60ddca65383e5983c0f8884f648d71f698303ad
|
||||
SHA512 (pciutils-2.2.9.tar.bz2) = 2b3d98d027e46d8c08037366dde6f0781ca03c610ef2b380984639e4ef39899ed8d8b8e4cd9c9dc54df101279b95879bd66bfd4d04ad07fef41e847ea7ae32b5
|
||||
SHA512 (xen-4.12.1.tar.gz) = be15d3af6b109771aaf59b3621e4e1560b7d9a963d9b7f6a1f6f4a1c907fd97434b060c15f75849b44fbda33f26eb51b030d14d068c6ad5103ad240fe7a98f40
|
||||
SHA512 (xen-4.12.3.tar.gz) = 7bbf4e752477f18143ac9a62fb633b1fbe115a1a9b03d0132f33dfca025bc9b76d9c2e9b66a3e407d14aff161b940b1a82e3e3ca43213798e9dd38b6970194e0
|
||||
|
|
|
|||
|
|
@ -1,341 +0,0 @@
|
|||
From 1e8932f0d815bee3102ede328676e1a4ca1926c7 Mon Sep 17 00:00:00 2001
|
||||
From: Paul Durrant <pdurrant@amazon.com>
|
||||
Date: Fri, 6 Dec 2019 12:46:24 +0100
|
||||
Subject: [PATCH] x86 / iommu: set up a scratch page in the quarantine domain
|
||||
|
||||
This patch introduces a new iommu_op to facilitate a per-implementation
|
||||
quarantine set up, and then further code for x86 implementations
|
||||
(amd and vtd) to set up a read-only scratch page to serve as the source
|
||||
for DMA reads whilst a device is assigned to dom_io. DMA writes will
|
||||
continue to fault as before.
|
||||
|
||||
The reason for doing this is that some hardware may continue to re-try
|
||||
DMA (despite FLR) in the event of an error, or even BME being cleared, and
|
||||
will fail to deal with DMA read faults gracefully. Having a scratch page
|
||||
mapped will allow pending DMA reads to complete and thus such buggy
|
||||
hardware will eventually be quiesced.
|
||||
|
||||
NOTE: These modifications are restricted to x86 implementations only as
|
||||
the buggy h/w I am aware of is only used with Xen in an x86
|
||||
environment. ARM may require similar code but, since I am not
|
||||
aware of the need, this patch does not modify any ARM implementation.
|
||||
|
||||
Signed-off-by: Paul Durrant <pdurrant@amazon.com>
|
||||
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||||
master commit: ea38867831da67eed0e9c61672c8941016b63dd9
|
||||
master date: 2019-11-29 18:27:54 +0000
|
||||
---
|
||||
xen/drivers/passthrough/amd/iommu_map.c | 62 ++++++++++++++++++++++
|
||||
xen/drivers/passthrough/amd/pci_amd_iommu.c | 14 ++---
|
||||
xen/drivers/passthrough/iommu.c | 17 +++++-
|
||||
xen/drivers/passthrough/vtd/iommu.c | 74 ++++++++++++++++++++++-----
|
||||
xen/include/asm-x86/hvm/svm/amd-iommu-proto.h | 3 ++
|
||||
xen/include/xen/iommu.h | 1 +
|
||||
6 files changed, 147 insertions(+), 24 deletions(-)
|
||||
|
||||
diff --git a/xen/drivers/passthrough/amd/iommu_map.c b/xen/drivers/passthrough/amd/iommu_map.c
|
||||
index 628aa60230..f876ce832b 100644
|
||||
--- a/xen/drivers/passthrough/amd/iommu_map.c
|
||||
+++ b/xen/drivers/passthrough/amd/iommu_map.c
|
||||
@@ -758,6 +758,68 @@ void amd_iommu_share_p2m(struct domain *d)
|
||||
}
|
||||
}
|
||||
|
||||
+int __init amd_iommu_quarantine_init(struct domain *d)
|
||||
+{
|
||||
+ struct domain_iommu *hd = dom_iommu(d);
|
||||
+ unsigned long max_gfn =
|
||||
+ PFN_DOWN((1ul << DEFAULT_DOMAIN_ADDRESS_WIDTH) - 1);
|
||||
+ unsigned int level = amd_iommu_get_paging_mode(max_gfn);
|
||||
+ uint64_t *table;
|
||||
+
|
||||
+ if ( hd->arch.root_table )
|
||||
+ {
|
||||
+ ASSERT_UNREACHABLE();
|
||||
+ return 0;
|
||||
+ }
|
||||
+
|
||||
+ spin_lock(&hd->arch.mapping_lock);
|
||||
+
|
||||
+ hd->arch.root_table = alloc_amd_iommu_pgtable();
|
||||
+ if ( !hd->arch.root_table )
|
||||
+ goto out;
|
||||
+
|
||||
+ table = __map_domain_page(hd->arch.root_table);
|
||||
+ while ( level )
|
||||
+ {
|
||||
+ struct page_info *pg;
|
||||
+ unsigned int i;
|
||||
+
|
||||
+ /*
|
||||
+ * The pgtable allocator is fine for the leaf page, as well as
|
||||
+ * page table pages, and the resulting allocations are always
|
||||
+ * zeroed.
|
||||
+ */
|
||||
+ pg = alloc_amd_iommu_pgtable();
|
||||
+ if ( !pg )
|
||||
+ break;
|
||||
+
|
||||
+ for ( i = 0; i < PTE_PER_TABLE_SIZE; i++ )
|
||||
+ {
|
||||
+ uint32_t *pde = (uint32_t *)&table[i];
|
||||
+
|
||||
+ /*
|
||||
+ * PDEs are essentially a subset of PTEs, so this function
|
||||
+ * is fine to use even at the leaf.
|
||||
+ */
|
||||
+ set_iommu_pde_present(pde, mfn_x(page_to_mfn(pg)), level - 1,
|
||||
+ false, true);
|
||||
+ }
|
||||
+
|
||||
+ unmap_domain_page(table);
|
||||
+ table = __map_domain_page(pg);
|
||||
+ level--;
|
||||
+ }
|
||||
+ unmap_domain_page(table);
|
||||
+
|
||||
+ out:
|
||||
+ spin_unlock(&hd->arch.mapping_lock);
|
||||
+
|
||||
+ amd_iommu_flush_all_pages(d);
|
||||
+
|
||||
+ /* Pages leaked in failure case */
|
||||
+ return level ? -ENOMEM : 0;
|
||||
+}
|
||||
+
|
||||
/*
|
||||
* Local variables:
|
||||
* mode: C
|
||||
diff --git a/xen/drivers/passthrough/amd/pci_amd_iommu.c b/xen/drivers/passthrough/amd/pci_amd_iommu.c
|
||||
index 15c13e1163..93ecae4a6c 100644
|
||||
--- a/xen/drivers/passthrough/amd/pci_amd_iommu.c
|
||||
+++ b/xen/drivers/passthrough/amd/pci_amd_iommu.c
|
||||
@@ -120,10 +120,6 @@ static void amd_iommu_setup_domain_device(
|
||||
u8 bus = pdev->bus;
|
||||
const struct domain_iommu *hd = dom_iommu(domain);
|
||||
|
||||
- /* dom_io is used as a sentinel for quarantined devices */
|
||||
- if ( domain == dom_io )
|
||||
- return;
|
||||
-
|
||||
BUG_ON( !hd->arch.root_table || !hd->arch.paging_mode ||
|
||||
!iommu->dev_table.buffer );
|
||||
|
||||
@@ -226,7 +222,7 @@ static int __must_check allocate_domain_resources(struct domain_iommu *hd)
|
||||
return rc;
|
||||
}
|
||||
|
||||
-static int get_paging_mode(unsigned long entries)
|
||||
+int amd_iommu_get_paging_mode(unsigned long entries)
|
||||
{
|
||||
int level = 1;
|
||||
|
||||
@@ -248,7 +244,8 @@ static int amd_iommu_domain_init(struct domain *d)
|
||||
|
||||
/* For pv and dom0, stick with get_paging_mode(max_page)
|
||||
* For HVM dom0, use 2 level page table at first */
|
||||
- hd->arch.paging_mode = is_hvm_domain(d) ? 2 : get_paging_mode(max_page);
|
||||
+ hd->arch.paging_mode = is_hvm_domain(d) ?
|
||||
+ 2 : amd_iommu_get_paging_mode(max_page);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -281,10 +278,6 @@ void amd_iommu_disable_domain_device(struct domain *domain,
|
||||
int req_id;
|
||||
u8 bus = pdev->bus;
|
||||
|
||||
- /* dom_io is used as a sentinel for quarantined devices */
|
||||
- if ( domain == dom_io )
|
||||
- return;
|
||||
-
|
||||
BUG_ON ( iommu->dev_table.buffer == NULL );
|
||||
req_id = get_dma_requestor_id(iommu->seg, PCI_BDF2(bus, devfn));
|
||||
dte = iommu->dev_table.buffer + (req_id * IOMMU_DEV_TABLE_ENTRY_SIZE);
|
||||
@@ -581,6 +574,7 @@ static void amd_dump_p2m_table(struct domain *d)
|
||||
static const struct iommu_ops __initconstrel amd_iommu_ops = {
|
||||
.init = amd_iommu_domain_init,
|
||||
.hwdom_init = amd_iommu_hwdom_init,
|
||||
+ .quarantine_init = amd_iommu_quarantine_init,
|
||||
.add_device = amd_iommu_add_device,
|
||||
.remove_device = amd_iommu_remove_device,
|
||||
.assign_device = amd_iommu_assign_device,
|
||||
diff --git a/xen/drivers/passthrough/iommu.c b/xen/drivers/passthrough/iommu.c
|
||||
index 20db08e1df..a747bfb946 100644
|
||||
--- a/xen/drivers/passthrough/iommu.c
|
||||
+++ b/xen/drivers/passthrough/iommu.c
|
||||
@@ -497,6 +497,21 @@ int iommu_iotlb_flush_all(struct domain *d, unsigned int flush_flags)
|
||||
return rc;
|
||||
}
|
||||
|
||||
+static int __init iommu_quarantine_init(void)
|
||||
+{
|
||||
+ const struct domain_iommu *hd = dom_iommu(dom_io);
|
||||
+ int rc;
|
||||
+
|
||||
+ rc = iommu_domain_init(dom_io);
|
||||
+ if ( rc )
|
||||
+ return rc;
|
||||
+
|
||||
+ if ( !hd->platform_ops->quarantine_init )
|
||||
+ return 0;
|
||||
+
|
||||
+ return hd->platform_ops->quarantine_init(dom_io);
|
||||
+}
|
||||
+
|
||||
int __init iommu_setup(void)
|
||||
{
|
||||
int rc = -ENODEV;
|
||||
@@ -530,7 +545,7 @@ int __init iommu_setup(void)
|
||||
printk("I/O virtualisation %sabled\n", iommu_enabled ? "en" : "dis");
|
||||
if ( iommu_enabled )
|
||||
{
|
||||
- if ( iommu_domain_init(dom_io) )
|
||||
+ if ( iommu_quarantine_init() )
|
||||
panic("Could not set up quarantine\n");
|
||||
|
||||
printk(" - Dom0 mode: %s\n",
|
||||
diff --git a/xen/drivers/passthrough/vtd/iommu.c b/xen/drivers/passthrough/vtd/iommu.c
|
||||
index 5663e9740d..576e72eba1 100644
|
||||
--- a/xen/drivers/passthrough/vtd/iommu.c
|
||||
+++ b/xen/drivers/passthrough/vtd/iommu.c
|
||||
@@ -192,7 +192,7 @@ u64 alloc_pgtable_maddr(struct acpi_drhd_unit *drhd, unsigned long npages)
|
||||
nodeid_t node = NUMA_NO_NODE;
|
||||
unsigned int i;
|
||||
|
||||
- rhsa = drhd_to_rhsa(drhd);
|
||||
+ rhsa = drhd ? drhd_to_rhsa(drhd) : NULL;
|
||||
if ( rhsa )
|
||||
node = pxm_to_node(rhsa->proximity_domain);
|
||||
|
||||
@@ -1340,10 +1340,6 @@ int domain_context_mapping_one(
|
||||
int agaw, rc, ret;
|
||||
bool_t flush_dev_iotlb;
|
||||
|
||||
- /* dom_io is used as a sentinel for quarantined devices */
|
||||
- if ( domain == dom_io )
|
||||
- return 0;
|
||||
-
|
||||
ASSERT(pcidevs_locked());
|
||||
spin_lock(&iommu->lock);
|
||||
maddr = bus_to_context_maddr(iommu, bus);
|
||||
@@ -1579,10 +1575,6 @@ int domain_context_unmap_one(
|
||||
int iommu_domid, rc, ret;
|
||||
bool_t flush_dev_iotlb;
|
||||
|
||||
- /* dom_io is used as a sentinel for quarantined devices */
|
||||
- if ( domain == dom_io )
|
||||
- return 0;
|
||||
-
|
||||
ASSERT(pcidevs_locked());
|
||||
spin_lock(&iommu->lock);
|
||||
|
||||
@@ -1715,10 +1707,6 @@ static int domain_context_unmap(struct domain *domain, u8 devfn,
|
||||
goto out;
|
||||
}
|
||||
|
||||
- /* dom_io is used as a sentinel for quarantined devices */
|
||||
- if ( domain == dom_io )
|
||||
- goto out;
|
||||
-
|
||||
/*
|
||||
* if no other devices under the same iommu owned by this domain,
|
||||
* clear iommu in iommu_bitmap and clear domain_id in domid_bitmp
|
||||
@@ -2736,9 +2724,69 @@ static void vtd_dump_p2m_table(struct domain *d)
|
||||
vtd_dump_p2m_table_level(hd->arch.pgd_maddr, agaw_to_level(hd->arch.agaw), 0, 0);
|
||||
}
|
||||
|
||||
+static int __init intel_iommu_quarantine_init(struct domain *d)
|
||||
+{
|
||||
+ struct domain_iommu *hd = dom_iommu(d);
|
||||
+ struct dma_pte *parent;
|
||||
+ unsigned int agaw = width_to_agaw(DEFAULT_DOMAIN_ADDRESS_WIDTH);
|
||||
+ unsigned int level = agaw_to_level(agaw);
|
||||
+ int rc;
|
||||
+
|
||||
+ if ( hd->arch.pgd_maddr )
|
||||
+ {
|
||||
+ ASSERT_UNREACHABLE();
|
||||
+ return 0;
|
||||
+ }
|
||||
+
|
||||
+ spin_lock(&hd->arch.mapping_lock);
|
||||
+
|
||||
+ hd->arch.pgd_maddr = alloc_pgtable_maddr(NULL, 1);
|
||||
+ if ( !hd->arch.pgd_maddr )
|
||||
+ goto out;
|
||||
+
|
||||
+ parent = map_vtd_domain_page(hd->arch.pgd_maddr);
|
||||
+ while ( level )
|
||||
+ {
|
||||
+ uint64_t maddr;
|
||||
+ unsigned int offset;
|
||||
+
|
||||
+ /*
|
||||
+ * The pgtable allocator is fine for the leaf page, as well as
|
||||
+ * page table pages, and the resulting allocations are always
|
||||
+ * zeroed.
|
||||
+ */
|
||||
+ maddr = alloc_pgtable_maddr(NULL, 1);
|
||||
+ if ( !maddr )
|
||||
+ break;
|
||||
+
|
||||
+ for ( offset = 0; offset < PTE_NUM; offset++ )
|
||||
+ {
|
||||
+ struct dma_pte *pte = &parent[offset];
|
||||
+
|
||||
+ dma_set_pte_addr(*pte, maddr);
|
||||
+ dma_set_pte_readable(*pte);
|
||||
+ }
|
||||
+ iommu_flush_cache_page(parent, 1);
|
||||
+
|
||||
+ unmap_vtd_domain_page(parent);
|
||||
+ parent = map_vtd_domain_page(maddr);
|
||||
+ level--;
|
||||
+ }
|
||||
+ unmap_vtd_domain_page(parent);
|
||||
+
|
||||
+ out:
|
||||
+ spin_unlock(&hd->arch.mapping_lock);
|
||||
+
|
||||
+ rc = iommu_flush_iotlb_all(d);
|
||||
+
|
||||
+ /* Pages leaked in failure case */
|
||||
+ return level ? -ENOMEM : rc;
|
||||
+}
|
||||
+
|
||||
const struct iommu_ops __initconstrel intel_iommu_ops = {
|
||||
.init = intel_iommu_domain_init,
|
||||
.hwdom_init = intel_iommu_hwdom_init,
|
||||
+ .quarantine_init = intel_iommu_quarantine_init,
|
||||
.add_device = intel_iommu_add_device,
|
||||
.enable_device = intel_iommu_enable_device,
|
||||
.remove_device = intel_iommu_remove_device,
|
||||
diff --git a/xen/include/asm-x86/hvm/svm/amd-iommu-proto.h b/xen/include/asm-x86/hvm/svm/amd-iommu-proto.h
|
||||
index c5697565d6..5a160b89ca 100644
|
||||
--- a/xen/include/asm-x86/hvm/svm/amd-iommu-proto.h
|
||||
+++ b/xen/include/asm-x86/hvm/svm/amd-iommu-proto.h
|
||||
@@ -51,6 +51,9 @@ void get_iommu_features(struct amd_iommu *iommu);
|
||||
int amd_iommu_init(void);
|
||||
int amd_iommu_update_ivrs_mapping_acpi(void);
|
||||
|
||||
+int amd_iommu_get_paging_mode(unsigned long entries);
|
||||
+int amd_iommu_quarantine_init(struct domain *d);
|
||||
+
|
||||
/* mapping functions */
|
||||
int __must_check amd_iommu_map_page(struct domain *d, dfn_t dfn,
|
||||
mfn_t mfn, unsigned int flags,
|
||||
diff --git a/xen/include/xen/iommu.h b/xen/include/xen/iommu.h
|
||||
index 70ee53d083..617c993f50 100644
|
||||
--- a/xen/include/xen/iommu.h
|
||||
+++ b/xen/include/xen/iommu.h
|
||||
@@ -189,6 +189,7 @@ typedef int iommu_grdm_t(xen_pfn_t start, xen_ulong_t nr, u32 id, void *ctxt);
|
||||
struct iommu_ops {
|
||||
int (*init)(struct domain *d);
|
||||
void (*hwdom_init)(struct domain *d);
|
||||
+ int (*quarantine_init)(struct domain *d);
|
||||
int (*add_device)(u8 devfn, device_t *dev);
|
||||
int (*enable_device)(device_t *dev);
|
||||
int (*remove_device)(u8 devfn, device_t *dev);
|
||||
--
|
||||
2.11.0
|
||||
|
||||
|
|
@ -1,110 +0,0 @@
|
|||
From 36d2ecb9991bf2d1ddb933872c3dfbd26300ca68 Mon Sep 17 00:00:00 2001
|
||||
From: Jan Beulich <jbeulich@suse.com>
|
||||
Date: Mon, 25 Nov 2019 16:01:00 +0100
|
||||
Subject: [PATCH] AMD/IOMMU: don't needlessly trigger errors/crashes when
|
||||
unmapping a page
|
||||
|
||||
Unmapping a page which has never been mapped should be a no-op (note how
|
||||
it already is in case there was no root page table allocated). There's
|
||||
in particular no need to grow the number of page table levels in use,
|
||||
and there's also no need to allocate intermediate page tables except
|
||||
when needing to split a large page.
|
||||
|
||||
Signed-off-by: Jan Beulich <jbeulich@suse.com>
|
||||
Reviewed-by: Paul Durrant <paul@xen.org>
|
||||
Acked-by: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
master commit: ad591454f069647c36a7daaa9ec23384c0263f0b
|
||||
master date: 2019-11-12 11:08:34 +0100
|
||||
---
|
||||
xen/drivers/passthrough/amd/iommu_map.c | 40 +++++++++++++++------------------
|
||||
1 file changed, 18 insertions(+), 22 deletions(-)
|
||||
|
||||
diff --git a/xen/drivers/passthrough/amd/iommu_map.c b/xen/drivers/passthrough/amd/iommu_map.c
|
||||
index 67329b0c95..628aa60230 100644
|
||||
--- a/xen/drivers/passthrough/amd/iommu_map.c
|
||||
+++ b/xen/drivers/passthrough/amd/iommu_map.c
|
||||
@@ -343,7 +343,7 @@ uint64_t amd_iommu_get_address_from_pte(void *pte)
|
||||
* page tables.
|
||||
*/
|
||||
static int iommu_pde_from_dfn(struct domain *d, unsigned long dfn,
|
||||
- unsigned long pt_mfn[])
|
||||
+ unsigned long pt_mfn[], bool map)
|
||||
{
|
||||
uint64_t *pde, *next_table_vaddr;
|
||||
unsigned long next_table_mfn;
|
||||
@@ -356,6 +356,13 @@ static int iommu_pde_from_dfn(struct domain *d, unsigned long dfn,
|
||||
|
||||
BUG_ON( table == NULL || level < 1 || level > 6 );
|
||||
|
||||
+ /*
|
||||
+ * A frame number past what the current page tables can represent can't
|
||||
+ * possibly have a mapping.
|
||||
+ */
|
||||
+ if ( dfn >> (PTE_PER_TABLE_SHIFT * level) )
|
||||
+ return 0;
|
||||
+
|
||||
next_table_mfn = mfn_x(page_to_mfn(table));
|
||||
|
||||
if ( level == 1 )
|
||||
@@ -415,6 +422,9 @@ static int iommu_pde_from_dfn(struct domain *d, unsigned long dfn,
|
||||
/* Install lower level page table for non-present entries */
|
||||
else if ( !iommu_is_pte_present((uint32_t *)pde) )
|
||||
{
|
||||
+ if ( !map )
|
||||
+ return 0;
|
||||
+
|
||||
if ( next_table_mfn == 0 )
|
||||
{
|
||||
table = alloc_amd_iommu_pgtable();
|
||||
@@ -577,7 +587,7 @@ int amd_iommu_map_page(struct domain *d, dfn_t dfn, mfn_t mfn,
|
||||
}
|
||||
}
|
||||
|
||||
- if ( iommu_pde_from_dfn(d, dfn_x(dfn), pt_mfn) || (pt_mfn[1] == 0) )
|
||||
+ if ( iommu_pde_from_dfn(d, dfn_x(dfn), pt_mfn, true) || (pt_mfn[1] == 0) )
|
||||
{
|
||||
spin_unlock(&hd->arch.mapping_lock);
|
||||
AMD_IOMMU_DEBUG("Invalid IO pagetable entry dfn = %"PRI_dfn"\n",
|
||||
@@ -615,24 +625,7 @@ int amd_iommu_unmap_page(struct domain *d, dfn_t dfn,
|
||||
return 0;
|
||||
}
|
||||
|
||||
- /* Since HVM domain is initialized with 2 level IO page table,
|
||||
- * we might need a deeper page table for lager dfn now */
|
||||
- if ( is_hvm_domain(d) )
|
||||
- {
|
||||
- int rc = update_paging_mode(d, dfn_x(dfn));
|
||||
-
|
||||
- if ( rc )
|
||||
- {
|
||||
- spin_unlock(&hd->arch.mapping_lock);
|
||||
- AMD_IOMMU_DEBUG("Update page mode failed dfn = %"PRI_dfn"\n",
|
||||
- dfn_x(dfn));
|
||||
- if ( rc != -EADDRNOTAVAIL )
|
||||
- domain_crash(d);
|
||||
- return rc;
|
||||
- }
|
||||
- }
|
||||
-
|
||||
- if ( iommu_pde_from_dfn(d, dfn_x(dfn), pt_mfn) || (pt_mfn[1] == 0) )
|
||||
+ if ( iommu_pde_from_dfn(d, dfn_x(dfn), pt_mfn, false) )
|
||||
{
|
||||
spin_unlock(&hd->arch.mapping_lock);
|
||||
AMD_IOMMU_DEBUG("Invalid IO pagetable entry dfn = %"PRI_dfn"\n",
|
||||
@@ -641,8 +634,11 @@ int amd_iommu_unmap_page(struct domain *d, dfn_t dfn,
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
- /* mark PTE as 'page not present' */
|
||||
- *flush_flags |= clear_iommu_pte_present(pt_mfn[1], dfn_x(dfn));
|
||||
+ if ( pt_mfn[1] )
|
||||
+ {
|
||||
+ /* Mark PTE as 'page not present'. */
|
||||
+ *flush_flags |= clear_iommu_pte_present(pt_mfn[1], dfn_x(dfn));
|
||||
+ }
|
||||
|
||||
spin_unlock(&hd->arch.mapping_lock);
|
||||
|
||||
--
|
||||
2.11.0
|
||||
|
||||
160
xen.spec
160
xen.spec
|
|
@ -1,6 +1,7 @@
|
|||
# Build ocaml bits unless rpmbuild was run with --without ocaml
|
||||
# or ocamlopt is missing (the xen makefile doesn't build ocaml bits if it isn't there)
|
||||
%define with_ocaml %{?_without_ocaml: 0} %{?!_without_ocaml: 1}
|
||||
#%%define with_ocaml %%{?_without_ocaml: 0} %%{?!_without_ocaml: 1}
|
||||
%define with_ocaml 0
|
||||
%define build_ocaml %(test -x %{_bindir}/ocamlopt && echo %{with_ocaml} || echo 0)
|
||||
# Build with docs unless rpmbuild was run with --without docs
|
||||
%define build_docs %{?_without_docs: 0} %{?!_without_docs: 1}
|
||||
|
|
@ -57,7 +58,7 @@
|
|||
|
||||
Summary: Xen is a virtual machine monitor
|
||||
Name: xen
|
||||
Version: 4.12.1
|
||||
Version: 4.12.3
|
||||
Release: 8%{?dist}
|
||||
License: GPLv2+ and LGPLv2+ and BSD
|
||||
URL: http://xen.org/
|
||||
|
|
@ -114,43 +115,45 @@ Patch41: xen.python.env.patch
|
|||
Patch42: xen.gcc9.fixes.patch
|
||||
Patch43: xen.python3.patch
|
||||
Patch54: xen.python38.patch
|
||||
Patch55: xsa296.patch
|
||||
Patch56: xsa298.patch
|
||||
Patch57: xsa299-4.12-0001-x86-mm-L1TF-checks-don-t-leave-a-partial-entry.patch
|
||||
Patch58: xsa299-4.12-0002-x86-mm-Don-t-re-set-PGT_pinned-on-a-partially-de-val.patch
|
||||
Patch59: xsa299-4.12-0003-x86-mm-Separate-out-partial_pte-tristate-into-indivi.patch
|
||||
Patch60: xsa299-4.12-0004-x86-mm-Use-flags-for-_put_page_type-rather-than-a-bo.patch
|
||||
Patch61: xsa299-4.12-0005-x86-mm-Rework-get_page_and_type_from_mfn-conditional.patch
|
||||
Patch62: xsa299-4.12-0006-x86-mm-Have-alloc_l-23-_table-clear-partial_flags-wh.patch
|
||||
Patch63: xsa299-4.12-0007-x86-mm-Always-retain-a-general-ref-on-partial.patch
|
||||
Patch64: xsa299-4.12-0008-x86-mm-Collapse-PTF_partial_set-and-PTF_partial_gene.patch
|
||||
Patch65: xsa299-4.12-0009-x86-mm-Properly-handle-linear-pagetable-promotion-fa.patch
|
||||
Patch66: xsa299-4.12-0010-x86-mm-Fix-nested-de-validation-on-error.patch
|
||||
Patch67: xsa299-4.12-0011-x86-mm-Don-t-drop-a-type-ref-unless-you-held-a-ref-t.patch
|
||||
Patch68: xsa301-master-1.patch
|
||||
Patch69: xsa301-master-2.patch
|
||||
Patch70: xsa301-master-3.patch
|
||||
Patch71: xsa302-4.12-0001-IOMMU-add-missing-HVM-check.patch
|
||||
Patch72: xsa302-4.12-0002-passthrough-quarantine-PCI-devices.patch
|
||||
Patch73: xsa303-0001-xen-arm32-entry-Split-__DEFINE_ENTRY_TRAP-in-two.patch
|
||||
Patch74: xsa303-0002-xen-arm32-entry-Fold-the-macro-SAVE_ALL-in-the-macro.patch
|
||||
Patch75: xsa303-0003-xen-arm32-Don-t-blindly-unmask-interrupts-on-trap-wi.patch
|
||||
Patch76: xsa303-0004-xen-arm64-Don-t-blindly-unmask-interrupts-on-trap-wi.patch
|
||||
Patch77: xsa304-4.12-1.patch
|
||||
Patch78: xsa304-4.12-2.patch
|
||||
Patch79: xsa304-4.12-3.patch
|
||||
Patch80: xsa305-4.12-1.patch
|
||||
Patch81: xsa305-4.12-2.patch
|
||||
Patch82: xsa306-4.12.patch
|
||||
Patch83: xsa307.patch
|
||||
Patch84: xsa308.patch
|
||||
Patch85: xsa309.patch
|
||||
Patch86: xsa310-0001-x86-mm-Set-old_guest_table-when-destroying-vcpu-page.patch
|
||||
Patch87: xsa310-0002-x86-mm-alloc-free_lN_table-Retain-partial_flags-on-E.patch
|
||||
Patch88: xsa310-0003-x86-mm-relinquish_memory-Grab-an-extra-type-ref-when.patch
|
||||
Patch89: xen.git-36d2ecb9991bf2d1ddb933872c3dfbd26300ca68.patch
|
||||
Patch90: xen.git-1e8932f0d815bee3102ede328676e1a4ca1926c7.patch
|
||||
Patch91: xsa311-4.12.patch
|
||||
Patch55: xsa320-4.12-1.patch
|
||||
Patch56: xsa320-4.12-2.patch
|
||||
Patch57: xsa317.patch
|
||||
Patch58: xsa319.patch
|
||||
Patch59: xsa328-4.12-1.patch
|
||||
Patch60: xsa328-4.12-2.patch
|
||||
Patch61: xsa321-4.12-1.patch
|
||||
Patch62: xsa321-4.12-2.patch
|
||||
Patch63: xsa321-4.12-3.patch
|
||||
Patch64: xsa321-4.12-4.patch
|
||||
Patch65: xsa321-4.12-5.patch
|
||||
Patch66: xsa321-4.12-6.patch
|
||||
Patch67: xsa321-4.12-7.patch
|
||||
Patch68: xsa327.patch
|
||||
Patch69: xsa335-qemu.patch
|
||||
Patch70: xsa335-trad.patch
|
||||
Patch71: xsa333.patch
|
||||
Patch72: xsa334-4.12.patch
|
||||
Patch73: xsa336.patch
|
||||
Patch74: xsa337-4.12-1.patch
|
||||
Patch75: xsa337-4.12-2.patch
|
||||
Patch76: xsa338.patch
|
||||
Patch77: xsa339.patch
|
||||
Patch78: xsa340.patch
|
||||
Patch79: xsa342-4.13.patch
|
||||
Patch80: xsa343-4.12-1.patch
|
||||
Patch81: xsa343-4.12-2.patch
|
||||
Patch82: xsa343-4.12-3.patch
|
||||
Patch83: xsa344-4.12-1.patch
|
||||
Patch84: xsa344-4.12-2.patch
|
||||
Patch85: xsa345-4.12-0001-x86-mm-Refactor-map_pages_to_xen-to-have-only-a-sing.patch
|
||||
Patch86: xsa345-4.12-0002-x86-mm-Refactor-modify_xen_mappings-to-have-one-exit.patch
|
||||
Patch87: xsa345-4.12-0003-x86-mm-Prevent-some-races-in-hypervisor-mapping-upda.patch
|
||||
Patch88: xsa346-4.12-1.patch
|
||||
Patch89: xsa346-4.12-2.patch
|
||||
Patch90: xsa347-4.12-1.patch
|
||||
Patch91: xsa347-4.12-2.patch
|
||||
Patch92: xsa286-4.12-0001-x86-pv-Drop-FLUSH_TLB_GLOBAL-in-do_mmu_update-for-XP.patch
|
||||
Patch93: xsa286-4.12-0002-x86-pv-Flush-TLB-in-response-to-paging-structure-cha.patch
|
||||
|
||||
|
||||
%if %build_qemutrad
|
||||
|
|
@ -363,7 +366,6 @@ manage Xen virtual machines.
|
|||
%patch66 -p1
|
||||
%patch67 -p1
|
||||
%patch68 -p1
|
||||
%patch69 -p1
|
||||
%patch70 -p1
|
||||
%patch71 -p1
|
||||
%patch72 -p1
|
||||
|
|
@ -386,6 +388,8 @@ manage Xen virtual machines.
|
|||
%patch89 -p1
|
||||
%patch90 -p1
|
||||
%patch91 -p1
|
||||
%patch92 -p1
|
||||
%patch93 -p1
|
||||
|
||||
# qemu-xen-traditional patches
|
||||
pushd tools/qemu-xen-traditional
|
||||
|
|
@ -402,6 +406,7 @@ popd
|
|||
|
||||
# qemu-xen patches
|
||||
pushd tools/qemu-xen
|
||||
%patch69 -p1
|
||||
popd
|
||||
|
||||
# stubdom sources
|
||||
|
|
@ -949,6 +954,81 @@ fi
|
|||
%endif
|
||||
|
||||
%changelog
|
||||
* Tue Nov 03 2020 Michael Young <m.a.young@durham.ac.uk> - 4.12.3-8
|
||||
- revised patch for XSA-286 (mitigating performance impact)
|
||||
|
||||
* Thu Oct 29 2020 Michael Young <m.a.young@durham.ac.uk> - 4.12.3-7
|
||||
- x86 PV guest INVLPG-like flushes may leave stale TLB entries
|
||||
[XSA-286, CVE-2020-27674] (#1891092)
|
||||
|
||||
* Tue Oct 20 2020 Michael Young <m.a.young@durham.ac.uk> - 4.12.3-6
|
||||
- x86: Race condition in Xen mapping code [XSA-345, CVE-2020-27672]
|
||||
(#1891097)
|
||||
- undue deferral of IOMMU TLB flushes [XSA-346, CVE-2020-27671]
|
||||
(#1891093)
|
||||
- unsafe AMD IOMMU page table updates [XSA-347, CVE-2020-27670]
|
||||
(#1891088)
|
||||
|
||||
* Tue Sep 22 2020 Michael Young <m.a.young@durham.ac.uk> - 4.12.3-5
|
||||
- x86 pv: Crash when handling guest access to MSR_MISC_ENABLE [XSA-333,
|
||||
CVE-2020-25602] (#1881619)
|
||||
- Missing unlock in XENMEM_acquire_resource error path [XSA-334,
|
||||
CVE-2020-25598] (#1881616)
|
||||
- race when migrating timers between x86 HVM vCPU-s [XSA-336,
|
||||
CVE-2020-25604] (#1881618)
|
||||
- PCI passthrough code reading back hardware registers [XSA-337,
|
||||
CVE-2020-25595] (#1881587)
|
||||
- once valid event channels may not turn invalid [XSA-338, CVE-2020-25597]
|
||||
(#1881588)
|
||||
- x86 pv guest kernel DoS via SYSENTER [XSA-339, CVE-2020-25596]
|
||||
(#1881617)
|
||||
- Missing memory barriers when accessing/allocating an event channel [XSA-340,
|
||||
CVE-2020-25603] (#1881583)
|
||||
- out of bounds event channels available to 32-bit x86 domains [XSA-342,
|
||||
CVE-2020-25600] (#1881582)
|
||||
- races with evtchn_reset() [XSA-343, CVE-2020-25599] (#1881581)
|
||||
- lack of preemption in evtchn_reset() / evtchn_destroy() [XSA-344,
|
||||
CVE-2020-25601] (#1881586)
|
||||
|
||||
* Tue Aug 25 2020 Michael Young <m.a.young@durham.ac.uk> - 4.12.3-4
|
||||
- QEMU: usb: out-of-bounds r/w access issue [XSA-335, CVE-2020-14364]
|
||||
(#1871850)
|
||||
|
||||
* Tue Jul 07 2020 Michael Young <m.a.young@durham.ac.uk> - 4.12.3-3
|
||||
- incorrect error handling in event channel port allocation leads to
|
||||
DoS [XSA-317, CVE-2020-15566] (#1854465)
|
||||
- inverted code paths in x86 dirty VRAM tracking leads to DoS
|
||||
[XSA-319, CVE-2020-15563] (#1854463)
|
||||
- xen: insufficient cache write-back under VT-d leads to DoS
|
||||
[XSA-321, CVE-2020-15565] (#1854467)
|
||||
- missing alignment check in VCPUOP_register_vcpu_info leads to DoS
|
||||
[XSA-327, CVE-2020-15564] (#1854458)
|
||||
- non-atomic modification of live EPT PTE leads to DoS
|
||||
[XSA-328, CVE-2020-15567] (#1854464)
|
||||
|
||||
* Wed Jun 10 2020 Michael Young <m.a.young@durham.ac.uk> - 4.12.3-2
|
||||
- Special Register Buffer speculative side channel [XSA-320]
|
||||
|
||||
* Tue May 19 2020 Michael Young <m.a.young@durham.ac.uk> - 4.12.3-1
|
||||
- update to 4.12.3
|
||||
remove patches for issues now fixed upstream
|
||||
|
||||
* Wed Apr 15 2020 Michael Young <m.a.young@durham.ac.uk> - 4.12.2-3
|
||||
- multiple xenoprof issues [XSA-313, CVE-2020-11740, CVE-2020-11741]
|
||||
(#1823912, #1823914)
|
||||
- Missing memory barriers in read-write unlock paths [XSA-314,
|
||||
CVE-2020-11739] (#1823784)
|
||||
- Bad error path in GNTTABOP_map_grant [XSA-316, CVE-2020-11743] (#1823926)
|
||||
- Bad continuation handling in GNTTABOP_copy [XSA-318, CVE-2020-11742]
|
||||
(#1823943)
|
||||
|
||||
* Tue Jan 14 2020 Michael Young <m.a.young@durham.ac.uk> - 4.12.2-2
|
||||
- arm: a CPU may speculate past the ERET instruction [XSA-312]
|
||||
|
||||
* Mon Jan 06 2020 Michael Young <m.a.young@durham.ac.uk> - 4.12.2-1
|
||||
- update to 4.12.2
|
||||
remove patches for issues now fixed upstream
|
||||
|
||||
* Wed Dec 11 2019 Michael Young <m.a.young@durham.ac.uk> - 4.12.1-8
|
||||
- denial of service in find_next_bit() [XSA-307, CVE-2019-19581,
|
||||
CVE-2019-19582] (#1782211)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
From b1d6f37aa5aa9f3fc5a269b9dd21b7feb7444be0 Mon Sep 17 00:00:00 2001
|
||||
From: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Date: Thu, 22 Oct 2020 11:28:58 +0100
|
||||
Subject: [PATCH 1/2] x86/pv: Drop FLUSH_TLB_GLOBAL in do_mmu_update() for XPTI
|
||||
|
||||
c/s 9d1d31ad9498 "x86: slightly reduce Meltdown band-aid overhead" removed the
|
||||
use of Global TLB flushes on the Xen entry path, but added a FLUSH_TLB_GLOBAL
|
||||
to the L4 path in do_mmu_update().
|
||||
|
||||
However, this was unnecessary.
|
||||
|
||||
It is the guests responsibility to perform appropriate TLB flushing if the L4
|
||||
modification altered an established mapping in a flush-relevant way. In this
|
||||
case, an MMUEXT_OP hypercall will follow. The case which Xen needs to cover
|
||||
is when new mappings are created, and the resync on the exit-to-guest path
|
||||
covers this correctly.
|
||||
|
||||
There is a corner case with multiple vCPUs in hypercalls at the same time,
|
||||
which 9d1d31ad9498 changed, and this patch changes back to its original XPTI
|
||||
behaviour.
|
||||
|
||||
Architecturally, established TLB entries can continue to be used until the
|
||||
broadcast flush has completed. Therefore, even with concurrent hypercalls,
|
||||
the guest cannot depend on older mappings not being used until an MMUEXT_OP
|
||||
hypercall completes. Xen's implementation of guest-initiated flushes will
|
||||
take correct effect on top of an in-progress hypercall, picking up new mapping
|
||||
setting before the other vCPU's MMUEXT_OP completes.
|
||||
|
||||
Note: The correctness of this change is not impacted by whether XPTI uses
|
||||
global mappings or not. Correctness there depends on the behaviour of Xen on
|
||||
the entry/exit paths when switching two/from the XPTI "shadow" pagetables.
|
||||
|
||||
This is (not really) XSA-286 (but necessary to simplify the logic).
|
||||
|
||||
Fixes: 9d1d31ad9498 ("x86: slightly reduce Meltdown band-aid overhead")
|
||||
Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||||
(cherry picked from commit 055e1c3a3d95b1e753148369fbc4ba48782dd602)
|
||||
---
|
||||
xen/arch/x86/mm.c | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/xen/arch/x86/mm.c b/xen/arch/x86/mm.c
|
||||
index e7b8f4ee4b..86f31b334f 100644
|
||||
--- a/xen/arch/x86/mm.c
|
||||
+++ b/xen/arch/x86/mm.c
|
||||
@@ -4301,7 +4301,7 @@ long do_mmu_update(
|
||||
|
||||
cpumask_andnot(mask, pt_owner->dirty_cpumask, cpumask_of(cpu));
|
||||
if ( !cpumask_empty(mask) )
|
||||
- flush_mask(mask, FLUSH_TLB_GLOBAL | FLUSH_ROOT_PGTBL);
|
||||
+ flush_mask(mask, FLUSH_ROOT_PGTBL);
|
||||
}
|
||||
|
||||
perfc_add(num_page_updates, i);
|
||||
--
|
||||
2.20.1
|
||||
|
||||
|
|
@ -0,0 +1,166 @@
|
|||
From 4100d463dbdd95d85fabe387dd5676bed75f65f7 Mon Sep 17 00:00:00 2001
|
||||
From: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Date: Mon, 19 Oct 2020 15:51:22 +0100
|
||||
Subject: [PATCH 2/2] x86/pv: Flush TLB in response to paging structure changes
|
||||
|
||||
With MMU_UPDATE, a PV guest can make changes to higher level pagetables. This
|
||||
is safe from Xen's point of view (as the update only affects guest mappings),
|
||||
and the guest is required to flush (if necessary) after making updates.
|
||||
|
||||
However, Xen's use of linear pagetables (UPDATE_VA_MAPPING, GNTTABOP_map,
|
||||
writeable pagetables, etc.) is an implementation detail outside of the
|
||||
API/ABI.
|
||||
|
||||
Changes in the paging structure require invalidations in the linear pagetable
|
||||
range for subsequent accesses into the linear pagetables to access non-stale
|
||||
mappings. Xen must provide suitable flushing to prevent intermixed guest
|
||||
actions from accidentally accessing/modifying the wrong pagetable.
|
||||
|
||||
For all L2 and higher modifications, flush the TLB. PV guests cannot create
|
||||
L2 or higher entries with the Global bit set, so no mappings established in
|
||||
the linear range can be global. (This could in principle be an order 39 flush
|
||||
starting at LINEAR_PT_VIRT_START, but no such mechanism exists in practice.)
|
||||
|
||||
Express the necessary flushes as a set of booleans which accumulate across the
|
||||
operation. Comment the flushing logic extensively.
|
||||
|
||||
This is XSA-286.
|
||||
|
||||
Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||||
(cherry picked from commit 16a20963b3209788f2c0d3a3eebb7d92f03f5883)
|
||||
---
|
||||
xen/arch/x86/mm.c | 69 ++++++++++++++++++++++++++++++++++++++++-------
|
||||
1 file changed, 59 insertions(+), 10 deletions(-)
|
||||
|
||||
diff --git a/xen/arch/x86/mm.c b/xen/arch/x86/mm.c
|
||||
index 86f31b334f..db4cfdf20b 100644
|
||||
--- a/xen/arch/x86/mm.c
|
||||
+++ b/xen/arch/x86/mm.c
|
||||
@@ -4005,7 +4005,8 @@ long do_mmu_update(
|
||||
struct vcpu *curr = current, *v = curr;
|
||||
struct domain *d = v->domain, *pt_owner = d, *pg_owner;
|
||||
mfn_t map_mfn = INVALID_MFN;
|
||||
- bool sync_guest = false;
|
||||
+ bool flush_linear_pt = false, flush_root_pt_local = false,
|
||||
+ flush_root_pt_others = false;
|
||||
uint32_t xsm_needed = 0;
|
||||
uint32_t xsm_checked = 0;
|
||||
int rc = put_old_guest_table(curr);
|
||||
@@ -4155,6 +4156,8 @@ long do_mmu_update(
|
||||
break;
|
||||
rc = mod_l2_entry(va, l2e_from_intpte(req.val), mfn,
|
||||
cmd == MMU_PT_UPDATE_PRESERVE_AD, v);
|
||||
+ if ( !rc )
|
||||
+ flush_linear_pt = true;
|
||||
break;
|
||||
|
||||
case PGT_l3_page_table:
|
||||
@@ -4162,6 +4165,8 @@ long do_mmu_update(
|
||||
break;
|
||||
rc = mod_l3_entry(va, l3e_from_intpte(req.val), mfn,
|
||||
cmd == MMU_PT_UPDATE_PRESERVE_AD, v);
|
||||
+ if ( !rc )
|
||||
+ flush_linear_pt = true;
|
||||
break;
|
||||
|
||||
case PGT_l4_page_table:
|
||||
@@ -4169,6 +4174,8 @@ long do_mmu_update(
|
||||
break;
|
||||
rc = mod_l4_entry(va, l4e_from_intpte(req.val), mfn,
|
||||
cmd == MMU_PT_UPDATE_PRESERVE_AD, v);
|
||||
+ if ( !rc )
|
||||
+ flush_linear_pt = true;
|
||||
if ( !rc && pt_owner->arch.pv.xpti )
|
||||
{
|
||||
bool local_in_use = false;
|
||||
@@ -4176,7 +4183,7 @@ long do_mmu_update(
|
||||
if ( pagetable_get_pfn(curr->arch.guest_table) == mfn )
|
||||
{
|
||||
local_in_use = true;
|
||||
- get_cpu_info()->root_pgt_changed = true;
|
||||
+ flush_root_pt_local = true;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -4188,7 +4195,7 @@ long do_mmu_update(
|
||||
(1 + !!(page->u.inuse.type_info & PGT_pinned) +
|
||||
(pagetable_get_pfn(curr->arch.guest_table_user) ==
|
||||
mfn) + local_in_use) )
|
||||
- sync_guest = true;
|
||||
+ flush_root_pt_others = true;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -4290,19 +4297,61 @@ long do_mmu_update(
|
||||
if ( va )
|
||||
unmap_domain_page(va);
|
||||
|
||||
- if ( sync_guest )
|
||||
+ /*
|
||||
+ * Perform required TLB maintenance.
|
||||
+ *
|
||||
+ * This logic currently depend on flush_linear_pt being a superset of the
|
||||
+ * flush_root_pt_* conditions.
|
||||
+ *
|
||||
+ * pt_owner may not be current->domain. This may occur during
|
||||
+ * construction of 32bit PV guests, or debugging of PV guests. The
|
||||
+ * behaviour cannot be correct with domain unpaused. We therefore expect
|
||||
+ * pt_owner->dirty_cpumask to be empty, but it is a waste of effort to
|
||||
+ * explicitly check for, and exclude, this corner case.
|
||||
+ *
|
||||
+ * flush_linear_pt requires a FLUSH_TLB to all dirty CPUs. The flush must
|
||||
+ * be performed now to maintain correct behaviour across a multicall.
|
||||
+ * i.e. we cannot relax FLUSH_TLB to FLUSH_ROOT_PGTBL, given that the
|
||||
+ * former is a side effect of the latter, because the resync (which is in
|
||||
+ * the return-to-guest path) happens too late.
|
||||
+ *
|
||||
+ * flush_root_pt_* requires FLUSH_ROOT_PGTBL on either the local CPU
|
||||
+ * (implies pt_owner == current->domain and current->processor set in
|
||||
+ * pt_owner->dirty_cpumask), and/or all *other* dirty CPUs as there are
|
||||
+ * references we can't account for locally.
|
||||
+ */
|
||||
+ if ( flush_linear_pt /* || flush_root_pt_local || flush_root_pt_others */ )
|
||||
{
|
||||
+ unsigned int cpu = smp_processor_id();
|
||||
+ cpumask_t *mask = pt_owner->dirty_cpumask;
|
||||
+
|
||||
/*
|
||||
- * Force other vCPU-s of the affected guest to pick up L4 entry
|
||||
- * changes (if any).
|
||||
+ * Always handle local flushing separately (if applicable), to
|
||||
+ * separate the flush invocations appropriately for scope of the two
|
||||
+ * flush_root_pt_* variables.
|
||||
*/
|
||||
- unsigned int cpu = smp_processor_id();
|
||||
- cpumask_t *mask = per_cpu(scratch_cpumask, cpu);
|
||||
+ if ( likely(cpumask_test_cpu(cpu, mask)) )
|
||||
+ {
|
||||
+ mask = per_cpu(scratch_cpumask, cpu);
|
||||
|
||||
- cpumask_andnot(mask, pt_owner->dirty_cpumask, cpumask_of(cpu));
|
||||
+ cpumask_copy(mask, pt_owner->dirty_cpumask);
|
||||
+ __cpumask_clear_cpu(cpu, mask);
|
||||
+
|
||||
+ flush_local(FLUSH_TLB |
|
||||
+ (flush_root_pt_local ? FLUSH_ROOT_PGTBL : 0));
|
||||
+ }
|
||||
+ else
|
||||
+ /* Sanity check. flush_root_pt_local implies local cpu is dirty. */
|
||||
+ ASSERT(!flush_root_pt_local);
|
||||
+
|
||||
+ /* Flush the remote dirty CPUs. Does not include the local CPU. */
|
||||
if ( !cpumask_empty(mask) )
|
||||
- flush_mask(mask, FLUSH_ROOT_PGTBL);
|
||||
+ flush_mask(mask, FLUSH_TLB |
|
||||
+ (flush_root_pt_others ? FLUSH_ROOT_PGTBL : 0));
|
||||
}
|
||||
+ else
|
||||
+ /* Sanity check. flush_root_pt_* implies flush_linear_pt. */
|
||||
+ ASSERT(!flush_root_pt_local && !flush_root_pt_others);
|
||||
|
||||
perfc_add(num_page_updates, i);
|
||||
|
||||
--
|
||||
2.20.1
|
||||
|
||||
195
xsa296.patch
195
xsa296.patch
|
|
@ -1,195 +0,0 @@
|
|||
From: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Subject: xen/hypercall: Don't use BUG() for parameter checking in hypercall_create_continuation()
|
||||
|
||||
Since c/s 1d429034 "hypercall: update vcpu_op to take an unsigned vcpuid",
|
||||
which incorrectly swapped 'i' for 'u' in the parameter type list, guests have
|
||||
been able to hit the BUG() in next_args()'s default case.
|
||||
|
||||
Correct these back to 'i'.
|
||||
|
||||
In addition, make adjustments to prevent this class of issue from occurring in
|
||||
the future - crashing Xen is not an appropriate form of parameter checking.
|
||||
|
||||
Capitalise NEXT_ARG() to catch all uses, to highlight that it is a macro doing
|
||||
non-function-like things behind the scenes, and undef it when appropriate.
|
||||
Implement a bad_fmt: block which prints an error, asserts unreachable, and
|
||||
crashes the guest.
|
||||
|
||||
On the ARM side, drop all parameter checking of p. It is asymmetric with the
|
||||
x86 side, and akin to expecting memcpy() or sprintf() to check their src/fmt
|
||||
parameter before use. A caller passing "" or something other than a string
|
||||
literal will be obvious during code review.
|
||||
|
||||
This is XSA-296.
|
||||
|
||||
Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Acked-by: Julien Grall <julien.grall@arm.com>
|
||||
|
||||
diff --git a/xen/arch/arm/domain.c b/xen/arch/arm/domain.c
|
||||
index 941bbff4fe..a3da8e9c08 100644
|
||||
--- a/xen/arch/arm/domain.c
|
||||
+++ b/xen/arch/arm/domain.c
|
||||
@@ -383,14 +383,15 @@ void sync_vcpu_execstate(struct vcpu *v)
|
||||
/* Nothing to do -- no lazy switching */
|
||||
}
|
||||
|
||||
-#define next_arg(fmt, args) ({ \
|
||||
+#define NEXT_ARG(fmt, args) \
|
||||
+({ \
|
||||
unsigned long __arg; \
|
||||
switch ( *(fmt)++ ) \
|
||||
{ \
|
||||
case 'i': __arg = (unsigned long)va_arg(args, unsigned int); break; \
|
||||
case 'l': __arg = (unsigned long)va_arg(args, unsigned long); break; \
|
||||
case 'h': __arg = (unsigned long)va_arg(args, void *); break; \
|
||||
- default: __arg = 0; BUG(); \
|
||||
+ default: goto bad_fmt; \
|
||||
} \
|
||||
__arg; \
|
||||
})
|
||||
@@ -405,9 +406,6 @@ unsigned long hypercall_create_continuation(
|
||||
unsigned int i;
|
||||
va_list args;
|
||||
|
||||
- /* All hypercalls take at least one argument */
|
||||
- BUG_ON( !p || *p == '\0' );
|
||||
-
|
||||
current->hcall_preempted = true;
|
||||
|
||||
va_start(args, format);
|
||||
@@ -415,7 +413,7 @@ unsigned long hypercall_create_continuation(
|
||||
if ( mcs->flags & MCSF_in_multicall )
|
||||
{
|
||||
for ( i = 0; *p != '\0'; i++ )
|
||||
- mcs->call.args[i] = next_arg(p, args);
|
||||
+ mcs->call.args[i] = NEXT_ARG(p, args);
|
||||
|
||||
/* Return value gets written back to mcs->call.result */
|
||||
rc = mcs->call.result;
|
||||
@@ -431,7 +429,7 @@ unsigned long hypercall_create_continuation(
|
||||
|
||||
for ( i = 0; *p != '\0'; i++ )
|
||||
{
|
||||
- arg = next_arg(p, args);
|
||||
+ arg = NEXT_ARG(p, args);
|
||||
|
||||
switch ( i )
|
||||
{
|
||||
@@ -454,7 +452,7 @@ unsigned long hypercall_create_continuation(
|
||||
|
||||
for ( i = 0; *p != '\0'; i++ )
|
||||
{
|
||||
- arg = next_arg(p, args);
|
||||
+ arg = NEXT_ARG(p, args);
|
||||
|
||||
switch ( i )
|
||||
{
|
||||
@@ -475,8 +473,16 @@ unsigned long hypercall_create_continuation(
|
||||
va_end(args);
|
||||
|
||||
return rc;
|
||||
+
|
||||
+ bad_fmt:
|
||||
+ gprintk(XENLOG_ERR, "Bad hypercall continuation format '%c'\n", *p);
|
||||
+ ASSERT_UNREACHABLE();
|
||||
+ domain_crash(current->domain);
|
||||
+ return 0;
|
||||
}
|
||||
|
||||
+#undef NEXT_ARG
|
||||
+
|
||||
void startup_cpu_idle_loop(void)
|
||||
{
|
||||
struct vcpu *v = current;
|
||||
diff --git a/xen/arch/x86/hypercall.c b/xen/arch/x86/hypercall.c
|
||||
index d483dbaa6b..4643e5eb43 100644
|
||||
--- a/xen/arch/x86/hypercall.c
|
||||
+++ b/xen/arch/x86/hypercall.c
|
||||
@@ -80,14 +80,15 @@ const hypercall_args_t hypercall_args_table[NR_hypercalls] =
|
||||
#undef COMP
|
||||
#undef ARGS
|
||||
|
||||
-#define next_arg(fmt, args) ({ \
|
||||
+#define NEXT_ARG(fmt, args) \
|
||||
+({ \
|
||||
unsigned long __arg; \
|
||||
switch ( *(fmt)++ ) \
|
||||
{ \
|
||||
case 'i': __arg = (unsigned long)va_arg(args, unsigned int); break; \
|
||||
case 'l': __arg = (unsigned long)va_arg(args, unsigned long); break; \
|
||||
case 'h': __arg = (unsigned long)va_arg(args, void *); break; \
|
||||
- default: __arg = 0; BUG(); \
|
||||
+ default: goto bad_fmt; \
|
||||
} \
|
||||
__arg; \
|
||||
})
|
||||
@@ -109,7 +110,7 @@ unsigned long hypercall_create_continuation(
|
||||
if ( mcs->flags & MCSF_in_multicall )
|
||||
{
|
||||
for ( i = 0; *p != '\0'; i++ )
|
||||
- mcs->call.args[i] = next_arg(p, args);
|
||||
+ mcs->call.args[i] = NEXT_ARG(p, args);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -121,7 +122,7 @@ unsigned long hypercall_create_continuation(
|
||||
{
|
||||
for ( i = 0; *p != '\0'; i++ )
|
||||
{
|
||||
- arg = next_arg(p, args);
|
||||
+ arg = NEXT_ARG(p, args);
|
||||
switch ( i )
|
||||
{
|
||||
case 0: regs->rdi = arg; break;
|
||||
@@ -137,7 +138,7 @@ unsigned long hypercall_create_continuation(
|
||||
{
|
||||
for ( i = 0; *p != '\0'; i++ )
|
||||
{
|
||||
- arg = next_arg(p, args);
|
||||
+ arg = NEXT_ARG(p, args);
|
||||
switch ( i )
|
||||
{
|
||||
case 0: regs->rbx = arg; break;
|
||||
@@ -154,8 +155,16 @@ unsigned long hypercall_create_continuation(
|
||||
va_end(args);
|
||||
|
||||
return op;
|
||||
+
|
||||
+ bad_fmt:
|
||||
+ gprintk(XENLOG_ERR, "Bad hypercall continuation format '%c'\n", *p);
|
||||
+ ASSERT_UNREACHABLE();
|
||||
+ domain_crash(curr->domain);
|
||||
+ return 0;
|
||||
}
|
||||
|
||||
+#undef NEXT_ARG
|
||||
+
|
||||
int hypercall_xlat_continuation(unsigned int *id, unsigned int nr,
|
||||
unsigned int mask, ...)
|
||||
{
|
||||
diff --git a/xen/common/compat/domain.c b/xen/common/compat/domain.c
|
||||
index 39877b3ab2..2531fa7421 100644
|
||||
--- a/xen/common/compat/domain.c
|
||||
+++ b/xen/common/compat/domain.c
|
||||
@@ -81,7 +81,7 @@ int compat_vcpu_op(int cmd, unsigned int vcpuid, XEN_GUEST_HANDLE_PARAM(void) ar
|
||||
}
|
||||
|
||||
if ( rc == -ERESTART )
|
||||
- rc = hypercall_create_continuation(__HYPERVISOR_vcpu_op, "iuh",
|
||||
+ rc = hypercall_create_continuation(__HYPERVISOR_vcpu_op, "iih",
|
||||
cmd, vcpuid, arg);
|
||||
|
||||
break;
|
||||
diff --git a/xen/common/domain.c b/xen/common/domain.c
|
||||
index 2308588052..65bcd85e34 100644
|
||||
--- a/xen/common/domain.c
|
||||
+++ b/xen/common/domain.c
|
||||
@@ -1411,7 +1411,7 @@ long do_vcpu_op(int cmd, unsigned int vcpuid, XEN_GUEST_HANDLE_PARAM(void) arg)
|
||||
|
||||
rc = arch_initialise_vcpu(v, arg);
|
||||
if ( rc == -ERESTART )
|
||||
- rc = hypercall_create_continuation(__HYPERVISOR_vcpu_op, "iuh",
|
||||
+ rc = hypercall_create_continuation(__HYPERVISOR_vcpu_op, "iih",
|
||||
cmd, vcpuid, arg);
|
||||
|
||||
break;
|
||||
89
xsa298.patch
89
xsa298.patch
|
|
@ -1,89 +0,0 @@
|
|||
From: Jan Beulich <jbeulich@suse.com>
|
||||
Subject: x86/PV: check GDT/LDT limits during emulation
|
||||
|
||||
Accesses beyond the LDT limit originating from emulation would trigger
|
||||
the ASSERT() in pv_map_ldt_shadow_page(). On production builds such
|
||||
accesses would cause an attempt to promote the touched page (offset from
|
||||
the present LDT base address) to a segment descriptor one. If this
|
||||
happens to succeed, guest user mode would be able to elevate its
|
||||
privileges to that of the guest kernel. This is particularly easy when
|
||||
there's no LDT at all, in which case the LDT base stored internally to
|
||||
Xen is simply zero.
|
||||
|
||||
Also adjust the ASSERT() that was triggering: It was off by one to
|
||||
begin with, and for production builds we also better use
|
||||
ASSERT_UNREACHABLE() instead with suitable recovery code afterwards.
|
||||
|
||||
This is XSA-298.
|
||||
|
||||
Reported-by: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Signed-off-by: Jan Beulich <jbeulich@suse.com>
|
||||
Reviewed-by: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
---
|
||||
v2: Correct 64-bit-only limit check (by folding into the common one).
|
||||
|
||||
--- a/xen/arch/x86/pv/emul-gate-op.c
|
||||
+++ b/xen/arch/x86/pv/emul-gate-op.c
|
||||
@@ -51,7 +51,13 @@ static int read_gate_descriptor(unsigned
|
||||
const seg_desc_t *pdesc = gdt_ldt_desc_ptr(gate_sel);
|
||||
|
||||
if ( (gate_sel < 4) ||
|
||||
- ((gate_sel >= FIRST_RESERVED_GDT_BYTE) && !(gate_sel & 4)) ||
|
||||
+ /*
|
||||
+ * We're interested in call gates only, which occupy a single
|
||||
+ * seg_desc_t for 32-bit and a consecutive pair of them for 64-bit.
|
||||
+ */
|
||||
+ ((gate_sel >> 3) + !is_pv_32bit_vcpu(v) >=
|
||||
+ (gate_sel & 4 ? v->arch.pv.ldt_ents
|
||||
+ : v->arch.pv.gdt_ents)) ||
|
||||
__get_user(desc, pdesc) )
|
||||
return 0;
|
||||
|
||||
@@ -70,7 +76,7 @@ static int read_gate_descriptor(unsigned
|
||||
if ( !is_pv_32bit_vcpu(v) )
|
||||
{
|
||||
if ( (*ar & 0x1f00) != 0x0c00 ||
|
||||
- (gate_sel >= FIRST_RESERVED_GDT_BYTE - 8 && !(gate_sel & 4)) ||
|
||||
+ /* Limit check done above already. */
|
||||
__get_user(desc, pdesc + 1) ||
|
||||
(desc.b & 0x1f00) )
|
||||
return 0;
|
||||
--- a/xen/arch/x86/pv/emulate.c
|
||||
+++ b/xen/arch/x86/pv/emulate.c
|
||||
@@ -31,7 +31,14 @@ int pv_emul_read_descriptor(unsigned int
|
||||
{
|
||||
seg_desc_t desc;
|
||||
|
||||
- if ( sel < 4)
|
||||
+ if ( sel < 4 ||
|
||||
+ /*
|
||||
+ * Don't apply the GDT limit here, as the selector may be a Xen
|
||||
+ * provided one. __get_user() will fail (without taking further
|
||||
+ * action) for ones falling in the gap between guest populated
|
||||
+ * and Xen ones.
|
||||
+ */
|
||||
+ ((sel & 4) && (sel >> 3) >= v->arch.pv.ldt_ents) )
|
||||
desc.b = desc.a = 0;
|
||||
else if ( __get_user(desc, gdt_ldt_desc_ptr(sel)) )
|
||||
return 0;
|
||||
--- a/xen/arch/x86/pv/mm.c
|
||||
+++ b/xen/arch/x86/pv/mm.c
|
||||
@@ -92,12 +92,16 @@ bool pv_map_ldt_shadow_page(unsigned int
|
||||
BUG_ON(unlikely(in_irq()));
|
||||
|
||||
/*
|
||||
- * Hardware limit checking should guarantee this property. NB. This is
|
||||
+ * Prior limit checking should guarantee this property. NB. This is
|
||||
* safe as updates to the LDT can only be made by MMUEXT_SET_LDT to the
|
||||
* current vcpu, and vcpu_reset() will block until this vcpu has been
|
||||
* descheduled before continuing.
|
||||
*/
|
||||
- ASSERT((offset >> 3) <= curr->arch.pv.ldt_ents);
|
||||
+ if ( unlikely((offset >> 3) >= curr->arch.pv.ldt_ents) )
|
||||
+ {
|
||||
+ ASSERT_UNREACHABLE();
|
||||
+ return false;
|
||||
+ }
|
||||
|
||||
if ( is_pv_32bit_domain(currd) )
|
||||
linear = (uint32_t)linear;
|
||||
|
|
@ -1,94 +0,0 @@
|
|||
From 33d051917d5ef38f678b507a3c832afde48b9b49 Mon Sep 17 00:00:00 2001
|
||||
From: George Dunlap <george.dunlap@citrix.com>
|
||||
Date: Thu, 10 Oct 2019 17:57:49 +0100
|
||||
Subject: [PATCH 01/11] x86/mm: L1TF checks don't leave a partial entry
|
||||
|
||||
On detection of a potential L1TF issue, most validation code returns
|
||||
-ERESTART to allow the switch to shadow mode to happen and cause the
|
||||
original operation to be restarted.
|
||||
|
||||
However, in the validation code, the return value -ERESTART has been
|
||||
repurposed to indicate 1) the function has partially completed
|
||||
something which needs to be undone, and 2) calling put_page_type()
|
||||
should cleanly undo it. This causes problems in several places.
|
||||
|
||||
For L1 tables, on receiving an -ERESTART return from alloc_l1_table(),
|
||||
alloc_page_type() will set PGT_partial on the page. If for some
|
||||
reason the original operation never restarts, then on domain
|
||||
destruction, relinquish_memory() will call free_page_type() on the
|
||||
page.
|
||||
|
||||
Unfortunately, alloc_ and free_l1_table() aren't set up to deal with
|
||||
PGT_partial. When returning a failure, alloc_l1_table() always
|
||||
de-validates whatever it's validated so far, and free_l1_table()
|
||||
always devalidates the whole page. This means that if
|
||||
relinquish_memory() calls free_page_type() on an L1 that didn't
|
||||
complete due to an L1TF, it will call put_page_from_l1e() on "page
|
||||
entries" that have never been validated.
|
||||
|
||||
For L2+ tables, setting rc to ERESTART causes the rest of the
|
||||
alloc_lN_table() function to *think* that the entry in question will
|
||||
have PGT_partial set. This will cause it to set partial_pte = 1. If
|
||||
relinqush_memory() then calls free_page_type() on one of those pages,
|
||||
then free_lN_table() will call put_page_from_lNe() on the entry when
|
||||
it shouldn't.
|
||||
|
||||
Rather than indicating -ERESTART, indicate -EINTR. This is the code
|
||||
to indicate that nothing has changed from when you started the call
|
||||
(which is effectively how alloc_l1_table() handles errors).
|
||||
|
||||
mod_lN_entry() shouldn't have any of these types of problems, so leave
|
||||
potential changes there for a clean-up patch later.
|
||||
|
||||
This is part of XSA-299.
|
||||
|
||||
Reported-by: George Dunlap <george.dunlap@citrix.com>
|
||||
Signed-off-by: George Dunlap <george.dunlap@citrix.com>
|
||||
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||||
---
|
||||
xen/arch/x86/mm.c | 8 ++++----
|
||||
1 file changed, 4 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/xen/arch/x86/mm.c b/xen/arch/x86/mm.c
|
||||
index 3557cd1178..a1b55c10ff 100644
|
||||
--- a/xen/arch/x86/mm.c
|
||||
+++ b/xen/arch/x86/mm.c
|
||||
@@ -1409,7 +1409,7 @@ static int alloc_l1_table(struct page_info *page)
|
||||
{
|
||||
if ( !(l1e_get_flags(pl1e[i]) & _PAGE_PRESENT) )
|
||||
{
|
||||
- ret = pv_l1tf_check_l1e(d, pl1e[i]) ? -ERESTART : 0;
|
||||
+ ret = pv_l1tf_check_l1e(d, pl1e[i]) ? -EINTR : 0;
|
||||
if ( ret )
|
||||
goto out;
|
||||
}
|
||||
@@ -1517,7 +1517,7 @@ static int alloc_l2_table(struct page_info *page, unsigned long type)
|
||||
{
|
||||
if ( !pv_l1tf_check_l2e(d, l2e) )
|
||||
continue;
|
||||
- rc = -ERESTART;
|
||||
+ rc = -EINTR;
|
||||
}
|
||||
else
|
||||
rc = get_page_from_l2e(l2e, pfn, d, partial);
|
||||
@@ -1603,7 +1603,7 @@ static int alloc_l3_table(struct page_info *page)
|
||||
{
|
||||
if ( !pv_l1tf_check_l3e(d, l3e) )
|
||||
continue;
|
||||
- rc = -ERESTART;
|
||||
+ rc = -EINTR;
|
||||
}
|
||||
else
|
||||
rc = get_page_from_l3e(l3e, pfn, d, partial);
|
||||
@@ -1783,7 +1783,7 @@ static int alloc_l4_table(struct page_info *page)
|
||||
{
|
||||
if ( !pv_l1tf_check_l4e(d, l4e) )
|
||||
continue;
|
||||
- rc = -ERESTART;
|
||||
+ rc = -EINTR;
|
||||
}
|
||||
else
|
||||
rc = get_page_from_l4e(l4e, pfn, d, partial);
|
||||
--
|
||||
2.23.0
|
||||
|
||||
|
|
@ -1,99 +0,0 @@
|
|||
From b490792c18f74b76ec8161721c1e07f810e36309 Mon Sep 17 00:00:00 2001
|
||||
From: George Dunlap <george.dunlap@citrix.com>
|
||||
Date: Thu, 10 Oct 2019 17:57:49 +0100
|
||||
Subject: [PATCH 02/11] x86/mm: Don't re-set PGT_pinned on a partially
|
||||
de-validated page
|
||||
|
||||
When unpinning pagetables, if an operation is interrupted,
|
||||
relinquish_memory() re-sets PGT_pinned so that the un-pin will
|
||||
pickedup again when the hypercall restarts.
|
||||
|
||||
This is appropriate when put_page_and_type_preemptible() returns
|
||||
-EINTR, which indicates that the page is back in its initial state
|
||||
(i.e., completely validated). However, for -ERESTART, this leads to a
|
||||
state where a page has both PGT_pinned and PGT_partial set.
|
||||
|
||||
This happens to work at the moment, although it's not really a
|
||||
"canonical" state; but in subsequent patches, where we need to make a
|
||||
distinction in handling between PGT_validated and PGT_partial pages,
|
||||
this causes issues.
|
||||
|
||||
Move to a "canonical" state by:
|
||||
- Only re-setting PGT_pinned on -EINTR
|
||||
- Re-dropping the refcount held by PGT_pinned on -ERESTART
|
||||
|
||||
In the latter case, the PGT_partial bit will be cleared further down
|
||||
with the rest of the other PGT_partial pages.
|
||||
|
||||
While here, clean up some trainling whitespace.
|
||||
|
||||
This is part of XSA-299.
|
||||
|
||||
Reported-by: George Dunlap <george.dunlap@citrix.com>
|
||||
Signed-off-by: George Dunlap <george.dunlap@citrix.com>
|
||||
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||||
---
|
||||
xen/arch/x86/domain.c | 31 ++++++++++++++++++++++++++++---
|
||||
1 file changed, 28 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/xen/arch/x86/domain.c b/xen/arch/x86/domain.c
|
||||
index 2585327834..59df8a6d8d 100644
|
||||
--- a/xen/arch/x86/domain.c
|
||||
+++ b/xen/arch/x86/domain.c
|
||||
@@ -114,7 +114,7 @@ static void play_dead(void)
|
||||
* this case, heap corruption or #PF can occur (when heap debugging is
|
||||
* enabled). For example, even printk() can involve tasklet scheduling,
|
||||
* which touches per-cpu vars.
|
||||
- *
|
||||
+ *
|
||||
* Consider very carefully when adding code to *dead_idle. Most hypervisor
|
||||
* subsystems are unsafe to call.
|
||||
*/
|
||||
@@ -1909,9 +1909,34 @@ static int relinquish_memory(
|
||||
break;
|
||||
case -ERESTART:
|
||||
case -EINTR:
|
||||
+ /*
|
||||
+ * -EINTR means PGT_validated has been re-set; re-set
|
||||
+ * PGT_pinned again so that it gets picked up next time
|
||||
+ * around.
|
||||
+ *
|
||||
+ * -ERESTART, OTOH, means PGT_partial is set instead. Put
|
||||
+ * it back on the list, but don't set PGT_pinned; the
|
||||
+ * section below will finish off de-validation. But we do
|
||||
+ * need to drop the general ref associated with
|
||||
+ * PGT_pinned, since put_page_and_type_preemptible()
|
||||
+ * didn't do it.
|
||||
+ *
|
||||
+ * NB we can do an ASSERT for PGT_validated, since we
|
||||
+ * "own" the type ref; but theoretically, the PGT_partial
|
||||
+ * could be cleared by someone else.
|
||||
+ */
|
||||
+ if ( ret == -EINTR )
|
||||
+ {
|
||||
+ ASSERT(page->u.inuse.type_info & PGT_validated);
|
||||
+ set_bit(_PGT_pinned, &page->u.inuse.type_info);
|
||||
+ }
|
||||
+ else
|
||||
+ put_page(page);
|
||||
+
|
||||
ret = -ERESTART;
|
||||
+
|
||||
+ /* Put the page back on the list and drop the ref we grabbed above */
|
||||
page_list_add(page, list);
|
||||
- set_bit(_PGT_pinned, &page->u.inuse.type_info);
|
||||
put_page(page);
|
||||
goto out;
|
||||
default:
|
||||
@@ -2161,7 +2186,7 @@ void vcpu_kick(struct vcpu *v)
|
||||
* pending flag. These values may fluctuate (after all, we hold no
|
||||
* locks) but the key insight is that each change will cause
|
||||
* evtchn_upcall_pending to be polled.
|
||||
- *
|
||||
+ *
|
||||
* NB2. We save the running flag across the unblock to avoid a needless
|
||||
* IPI for domains that we IPI'd to unblock.
|
||||
*/
|
||||
--
|
||||
2.23.0
|
||||
|
||||
|
|
@ -1,618 +0,0 @@
|
|||
From 0f9f61e5737fdd346550ec6e30161fa99e4653fa Mon Sep 17 00:00:00 2001
|
||||
From: George Dunlap <george.dunlap@citrix.com>
|
||||
Date: Thu, 10 Oct 2019 17:57:49 +0100
|
||||
Subject: [PATCH 03/11] x86/mm: Separate out partial_pte tristate into
|
||||
individual flags
|
||||
|
||||
At the moment, partial_pte is a tri-state that contains two distinct bits
|
||||
of information:
|
||||
|
||||
1. If zero, the pte at index [nr_validated_ptes] is un-validated. If
|
||||
non-zero, the pte was last seen with PGT_partial set.
|
||||
|
||||
2. If positive, the pte at index [nr_validated_ptes] does not hold a
|
||||
general reference count. If negative, it does.
|
||||
|
||||
To make future patches more clear, separate out this functionality
|
||||
into two distinct, named bits: PTF_partial_set (for #1) and
|
||||
PTF_partial_general_ref (for #2).
|
||||
|
||||
Additionally, a number of functions which need this information also
|
||||
take other flags to control behavior (such as `preemptible` and
|
||||
`defer`). These are hard to read in the caller (since you only see
|
||||
'true' or 'false'), and ugly when many are added together. In
|
||||
preparation for adding yet another flag in a future patch, collapse
|
||||
all of these into a single `flag` variable.
|
||||
|
||||
NB that this does mean checking for what was previously the '-1'
|
||||
condition a bit more ugly in the put_page_from_lNe functions (since
|
||||
you have to check for both partial_set and general ref); but this
|
||||
clause will go away in a future patch.
|
||||
|
||||
Also note that the original comment had an off-by-one error:
|
||||
partial_flags (like partial_pte before it) concerns
|
||||
plNe[nr_validated_ptes], not plNe[nr_validated_ptes+1].
|
||||
|
||||
No functional change intended.
|
||||
|
||||
This is part of XSA-299.
|
||||
|
||||
Reported-by: George Dunlap <george.dunlap@citrix.com>
|
||||
Signed-off-by: George Dunlap <george.dunlap@citrix.com>
|
||||
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||||
---
|
||||
xen/arch/x86/mm.c | 165 ++++++++++++++++++++++++---------------
|
||||
xen/include/asm-x86/mm.h | 41 +++++++---
|
||||
2 files changed, 128 insertions(+), 78 deletions(-)
|
||||
|
||||
diff --git a/xen/arch/x86/mm.c b/xen/arch/x86/mm.c
|
||||
index a1b55c10ff..3f6f8cc9b8 100644
|
||||
--- a/xen/arch/x86/mm.c
|
||||
+++ b/xen/arch/x86/mm.c
|
||||
@@ -1094,20 +1094,35 @@ get_page_from_l1e(
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PV
|
||||
+
|
||||
+/*
|
||||
+ * The following flags are used to specify behavior of various get and
|
||||
+ * put commands. The first two are also stored in page->partial_flags
|
||||
+ * to indicate the state of the page pointed to by
|
||||
+ * page->pte[page->nr_validated_entries]. See the comment in mm.h for
|
||||
+ * more information.
|
||||
+ */
|
||||
+#define PTF_partial_set (1 << 0)
|
||||
+#define PTF_partial_general_ref (1 << 1)
|
||||
+#define PTF_preemptible (1 << 2)
|
||||
+#define PTF_defer (1 << 3)
|
||||
+
|
||||
static int get_page_and_type_from_mfn(
|
||||
mfn_t mfn, unsigned long type, struct domain *d,
|
||||
- int partial, int preemptible)
|
||||
+ unsigned int flags)
|
||||
{
|
||||
struct page_info *page = mfn_to_page(mfn);
|
||||
int rc;
|
||||
+ bool preemptible = flags & PTF_preemptible,
|
||||
+ partial_ref = flags & PTF_partial_general_ref;
|
||||
|
||||
- if ( likely(partial >= 0) &&
|
||||
+ if ( likely(!partial_ref) &&
|
||||
unlikely(!get_page_from_mfn(mfn, d)) )
|
||||
return -EINVAL;
|
||||
|
||||
rc = _get_page_type(page, type, preemptible);
|
||||
|
||||
- if ( unlikely(rc) && partial >= 0 &&
|
||||
+ if ( unlikely(rc) && !partial_ref &&
|
||||
(!preemptible || page != current->arch.old_guest_table) )
|
||||
put_page(page);
|
||||
|
||||
@@ -1117,7 +1132,7 @@ static int get_page_and_type_from_mfn(
|
||||
define_get_linear_pagetable(l2);
|
||||
static int
|
||||
get_page_from_l2e(
|
||||
- l2_pgentry_t l2e, unsigned long pfn, struct domain *d, int partial)
|
||||
+ l2_pgentry_t l2e, unsigned long pfn, struct domain *d, unsigned int flags)
|
||||
{
|
||||
unsigned long mfn = l2e_get_pfn(l2e);
|
||||
int rc;
|
||||
@@ -1129,8 +1144,9 @@ get_page_from_l2e(
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
- rc = get_page_and_type_from_mfn(_mfn(mfn), PGT_l1_page_table, d,
|
||||
- partial, false);
|
||||
+ ASSERT(!(flags & PTF_preemptible));
|
||||
+
|
||||
+ rc = get_page_and_type_from_mfn(_mfn(mfn), PGT_l1_page_table, d, flags);
|
||||
if ( unlikely(rc == -EINVAL) && get_l2_linear_pagetable(l2e, pfn, d) )
|
||||
rc = 0;
|
||||
|
||||
@@ -1140,7 +1156,7 @@ get_page_from_l2e(
|
||||
define_get_linear_pagetable(l3);
|
||||
static int
|
||||
get_page_from_l3e(
|
||||
- l3_pgentry_t l3e, unsigned long pfn, struct domain *d, int partial)
|
||||
+ l3_pgentry_t l3e, unsigned long pfn, struct domain *d, unsigned int flags)
|
||||
{
|
||||
int rc;
|
||||
|
||||
@@ -1152,7 +1168,7 @@ get_page_from_l3e(
|
||||
}
|
||||
|
||||
rc = get_page_and_type_from_mfn(
|
||||
- l3e_get_mfn(l3e), PGT_l2_page_table, d, partial, 1);
|
||||
+ l3e_get_mfn(l3e), PGT_l2_page_table, d, flags | PTF_preemptible);
|
||||
if ( unlikely(rc == -EINVAL) &&
|
||||
!is_pv_32bit_domain(d) &&
|
||||
get_l3_linear_pagetable(l3e, pfn, d) )
|
||||
@@ -1164,7 +1180,7 @@ get_page_from_l3e(
|
||||
define_get_linear_pagetable(l4);
|
||||
static int
|
||||
get_page_from_l4e(
|
||||
- l4_pgentry_t l4e, unsigned long pfn, struct domain *d, int partial)
|
||||
+ l4_pgentry_t l4e, unsigned long pfn, struct domain *d, unsigned int flags)
|
||||
{
|
||||
int rc;
|
||||
|
||||
@@ -1176,7 +1192,7 @@ get_page_from_l4e(
|
||||
}
|
||||
|
||||
rc = get_page_and_type_from_mfn(
|
||||
- l4e_get_mfn(l4e), PGT_l3_page_table, d, partial, 1);
|
||||
+ l4e_get_mfn(l4e), PGT_l3_page_table, d, flags | PTF_preemptible);
|
||||
if ( unlikely(rc == -EINVAL) && get_l4_linear_pagetable(l4e, pfn, d) )
|
||||
rc = 0;
|
||||
|
||||
@@ -1277,7 +1293,7 @@ static void put_data_page(struct page_info *page, bool writeable)
|
||||
* Note also that this automatically deals correctly with linear p.t.'s.
|
||||
*/
|
||||
static int put_page_from_l2e(l2_pgentry_t l2e, unsigned long pfn,
|
||||
- int partial, bool defer)
|
||||
+ unsigned int flags)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
@@ -1300,12 +1316,13 @@ static int put_page_from_l2e(l2_pgentry_t l2e, unsigned long pfn,
|
||||
struct page_info *pg = l2e_get_page(l2e);
|
||||
struct page_info *ptpg = mfn_to_page(_mfn(pfn));
|
||||
|
||||
- if ( unlikely(partial > 0) )
|
||||
+ if ( (flags & (PTF_partial_set | PTF_partial_general_ref)) ==
|
||||
+ PTF_partial_set )
|
||||
{
|
||||
- ASSERT(!defer);
|
||||
+ ASSERT(!(flags & PTF_defer));
|
||||
rc = _put_page_type(pg, true, ptpg);
|
||||
}
|
||||
- else if ( defer )
|
||||
+ else if ( flags & PTF_defer )
|
||||
{
|
||||
current->arch.old_guest_ptpg = ptpg;
|
||||
current->arch.old_guest_table = pg;
|
||||
@@ -1322,7 +1339,7 @@ static int put_page_from_l2e(l2_pgentry_t l2e, unsigned long pfn,
|
||||
}
|
||||
|
||||
static int put_page_from_l3e(l3_pgentry_t l3e, unsigned long pfn,
|
||||
- int partial, bool defer)
|
||||
+ unsigned int flags)
|
||||
{
|
||||
struct page_info *pg;
|
||||
int rc;
|
||||
@@ -1345,13 +1362,14 @@ static int put_page_from_l3e(l3_pgentry_t l3e, unsigned long pfn,
|
||||
|
||||
pg = l3e_get_page(l3e);
|
||||
|
||||
- if ( unlikely(partial > 0) )
|
||||
+ if ( (flags & (PTF_partial_set | PTF_partial_general_ref)) ==
|
||||
+ PTF_partial_set )
|
||||
{
|
||||
- ASSERT(!defer);
|
||||
+ ASSERT(!(flags & PTF_defer));
|
||||
return _put_page_type(pg, true, mfn_to_page(_mfn(pfn)));
|
||||
}
|
||||
|
||||
- if ( defer )
|
||||
+ if ( flags & PTF_defer )
|
||||
{
|
||||
current->arch.old_guest_ptpg = mfn_to_page(_mfn(pfn));
|
||||
current->arch.old_guest_table = pg;
|
||||
@@ -1366,7 +1384,7 @@ static int put_page_from_l3e(l3_pgentry_t l3e, unsigned long pfn,
|
||||
}
|
||||
|
||||
static int put_page_from_l4e(l4_pgentry_t l4e, unsigned long pfn,
|
||||
- int partial, bool defer)
|
||||
+ unsigned int flags)
|
||||
{
|
||||
int rc = 1;
|
||||
|
||||
@@ -1375,13 +1393,14 @@ static int put_page_from_l4e(l4_pgentry_t l4e, unsigned long pfn,
|
||||
{
|
||||
struct page_info *pg = l4e_get_page(l4e);
|
||||
|
||||
- if ( unlikely(partial > 0) )
|
||||
+ if ( (flags & (PTF_partial_set | PTF_partial_general_ref)) ==
|
||||
+ PTF_partial_set )
|
||||
{
|
||||
- ASSERT(!defer);
|
||||
+ ASSERT(!(flags & PTF_defer));
|
||||
return _put_page_type(pg, true, mfn_to_page(_mfn(pfn)));
|
||||
}
|
||||
|
||||
- if ( defer )
|
||||
+ if ( flags & PTF_defer )
|
||||
{
|
||||
current->arch.old_guest_ptpg = mfn_to_page(_mfn(pfn));
|
||||
current->arch.old_guest_table = pg;
|
||||
@@ -1492,12 +1511,13 @@ static int alloc_l2_table(struct page_info *page, unsigned long type)
|
||||
unsigned long pfn = mfn_x(page_to_mfn(page));
|
||||
l2_pgentry_t *pl2e;
|
||||
unsigned int i;
|
||||
- int rc = 0, partial = page->partial_pte;
|
||||
+ int rc = 0;
|
||||
+ unsigned int partial_flags = page->partial_flags;
|
||||
|
||||
pl2e = map_domain_page(_mfn(pfn));
|
||||
|
||||
for ( i = page->nr_validated_ptes; i < L2_PAGETABLE_ENTRIES;
|
||||
- i++, partial = 0 )
|
||||
+ i++, partial_flags = 0 )
|
||||
{
|
||||
l2_pgentry_t l2e;
|
||||
|
||||
@@ -1520,17 +1540,18 @@ static int alloc_l2_table(struct page_info *page, unsigned long type)
|
||||
rc = -EINTR;
|
||||
}
|
||||
else
|
||||
- rc = get_page_from_l2e(l2e, pfn, d, partial);
|
||||
+ rc = get_page_from_l2e(l2e, pfn, d, partial_flags);
|
||||
|
||||
if ( rc == -ERESTART )
|
||||
{
|
||||
page->nr_validated_ptes = i;
|
||||
- page->partial_pte = partial ?: 1;
|
||||
+ /* Set 'set', retain 'general ref' */
|
||||
+ page->partial_flags = partial_flags | PTF_partial_set;
|
||||
}
|
||||
else if ( rc == -EINTR && i )
|
||||
{
|
||||
page->nr_validated_ptes = i;
|
||||
- page->partial_pte = 0;
|
||||
+ page->partial_flags = 0;
|
||||
rc = -ERESTART;
|
||||
}
|
||||
else if ( rc < 0 && rc != -EINTR )
|
||||
@@ -1539,7 +1560,7 @@ static int alloc_l2_table(struct page_info *page, unsigned long type)
|
||||
if ( i )
|
||||
{
|
||||
page->nr_validated_ptes = i;
|
||||
- page->partial_pte = 0;
|
||||
+ page->partial_flags = 0;
|
||||
current->arch.old_guest_ptpg = NULL;
|
||||
current->arch.old_guest_table = page;
|
||||
}
|
||||
@@ -1563,7 +1584,8 @@ static int alloc_l3_table(struct page_info *page)
|
||||
unsigned long pfn = mfn_x(page_to_mfn(page));
|
||||
l3_pgentry_t *pl3e;
|
||||
unsigned int i;
|
||||
- int rc = 0, partial = page->partial_pte;
|
||||
+ int rc = 0;
|
||||
+ unsigned int partial_flags = page->partial_flags;
|
||||
|
||||
pl3e = map_domain_page(_mfn(pfn));
|
||||
|
||||
@@ -1578,7 +1600,7 @@ static int alloc_l3_table(struct page_info *page)
|
||||
memset(pl3e + 4, 0, (L3_PAGETABLE_ENTRIES - 4) * sizeof(*pl3e));
|
||||
|
||||
for ( i = page->nr_validated_ptes; i < L3_PAGETABLE_ENTRIES;
|
||||
- i++, partial = 0 )
|
||||
+ i++, partial_flags = 0 )
|
||||
{
|
||||
l3_pgentry_t l3e = pl3e[i];
|
||||
|
||||
@@ -1597,7 +1619,8 @@ static int alloc_l3_table(struct page_info *page)
|
||||
else
|
||||
rc = get_page_and_type_from_mfn(
|
||||
l3e_get_mfn(l3e),
|
||||
- PGT_l2_page_table | PGT_pae_xen_l2, d, partial, 1);
|
||||
+ PGT_l2_page_table | PGT_pae_xen_l2, d,
|
||||
+ partial_flags | PTF_preemptible);
|
||||
}
|
||||
else if ( !(l3e_get_flags(l3e) & _PAGE_PRESENT) )
|
||||
{
|
||||
@@ -1606,17 +1629,18 @@ static int alloc_l3_table(struct page_info *page)
|
||||
rc = -EINTR;
|
||||
}
|
||||
else
|
||||
- rc = get_page_from_l3e(l3e, pfn, d, partial);
|
||||
+ rc = get_page_from_l3e(l3e, pfn, d, partial_flags);
|
||||
|
||||
if ( rc == -ERESTART )
|
||||
{
|
||||
page->nr_validated_ptes = i;
|
||||
- page->partial_pte = partial ?: 1;
|
||||
+ /* Set 'set', leave 'general ref' set if this entry was set */
|
||||
+ page->partial_flags = partial_flags | PTF_partial_set;
|
||||
}
|
||||
else if ( rc == -EINTR && i )
|
||||
{
|
||||
page->nr_validated_ptes = i;
|
||||
- page->partial_pte = 0;
|
||||
+ page->partial_flags = 0;
|
||||
rc = -ERESTART;
|
||||
}
|
||||
if ( rc < 0 )
|
||||
@@ -1633,7 +1657,7 @@ static int alloc_l3_table(struct page_info *page)
|
||||
if ( i )
|
||||
{
|
||||
page->nr_validated_ptes = i;
|
||||
- page->partial_pte = 0;
|
||||
+ page->partial_flags = 0;
|
||||
current->arch.old_guest_ptpg = NULL;
|
||||
current->arch.old_guest_table = page;
|
||||
}
|
||||
@@ -1767,10 +1791,11 @@ static int alloc_l4_table(struct page_info *page)
|
||||
unsigned long pfn = mfn_x(page_to_mfn(page));
|
||||
l4_pgentry_t *pl4e = map_domain_page(_mfn(pfn));
|
||||
unsigned int i;
|
||||
- int rc = 0, partial = page->partial_pte;
|
||||
+ int rc = 0;
|
||||
+ unsigned int partial_flags = page->partial_flags;
|
||||
|
||||
for ( i = page->nr_validated_ptes; i < L4_PAGETABLE_ENTRIES;
|
||||
- i++, partial = 0 )
|
||||
+ i++, partial_flags = 0 )
|
||||
{
|
||||
l4_pgentry_t l4e;
|
||||
|
||||
@@ -1786,12 +1811,13 @@ static int alloc_l4_table(struct page_info *page)
|
||||
rc = -EINTR;
|
||||
}
|
||||
else
|
||||
- rc = get_page_from_l4e(l4e, pfn, d, partial);
|
||||
+ rc = get_page_from_l4e(l4e, pfn, d, partial_flags);
|
||||
|
||||
if ( rc == -ERESTART )
|
||||
{
|
||||
page->nr_validated_ptes = i;
|
||||
- page->partial_pte = partial ?: 1;
|
||||
+ /* Set 'set', leave 'general ref' set if this entry was set */
|
||||
+ page->partial_flags = partial_flags | PTF_partial_set;
|
||||
}
|
||||
else if ( rc < 0 )
|
||||
{
|
||||
@@ -1801,7 +1827,7 @@ static int alloc_l4_table(struct page_info *page)
|
||||
if ( i )
|
||||
{
|
||||
page->nr_validated_ptes = i;
|
||||
- page->partial_pte = 0;
|
||||
+ page->partial_flags = 0;
|
||||
if ( rc == -EINTR )
|
||||
rc = -ERESTART;
|
||||
else
|
||||
@@ -1853,19 +1879,20 @@ static int free_l2_table(struct page_info *page)
|
||||
struct domain *d = page_get_owner(page);
|
||||
unsigned long pfn = mfn_x(page_to_mfn(page));
|
||||
l2_pgentry_t *pl2e;
|
||||
- int rc = 0, partial = page->partial_pte;
|
||||
- unsigned int i = page->nr_validated_ptes - !partial;
|
||||
+ int rc = 0;
|
||||
+ unsigned int partial_flags = page->partial_flags,
|
||||
+ i = page->nr_validated_ptes - !(partial_flags & PTF_partial_set);
|
||||
|
||||
pl2e = map_domain_page(_mfn(pfn));
|
||||
|
||||
for ( ; ; )
|
||||
{
|
||||
if ( is_guest_l2_slot(d, page->u.inuse.type_info, i) )
|
||||
- rc = put_page_from_l2e(pl2e[i], pfn, partial, false);
|
||||
+ rc = put_page_from_l2e(pl2e[i], pfn, partial_flags);
|
||||
if ( rc < 0 )
|
||||
break;
|
||||
|
||||
- partial = 0;
|
||||
+ partial_flags = 0;
|
||||
|
||||
if ( !i-- )
|
||||
break;
|
||||
@@ -1887,12 +1914,14 @@ static int free_l2_table(struct page_info *page)
|
||||
else if ( rc == -ERESTART )
|
||||
{
|
||||
page->nr_validated_ptes = i;
|
||||
- page->partial_pte = partial ?: -1;
|
||||
+ page->partial_flags = (partial_flags & PTF_partial_set) ?
|
||||
+ partial_flags :
|
||||
+ (PTF_partial_set | PTF_partial_general_ref);
|
||||
}
|
||||
else if ( rc == -EINTR && i < L2_PAGETABLE_ENTRIES - 1 )
|
||||
{
|
||||
page->nr_validated_ptes = i + 1;
|
||||
- page->partial_pte = 0;
|
||||
+ page->partial_flags = 0;
|
||||
rc = -ERESTART;
|
||||
}
|
||||
|
||||
@@ -1904,18 +1933,19 @@ static int free_l3_table(struct page_info *page)
|
||||
struct domain *d = page_get_owner(page);
|
||||
unsigned long pfn = mfn_x(page_to_mfn(page));
|
||||
l3_pgentry_t *pl3e;
|
||||
- int rc = 0, partial = page->partial_pte;
|
||||
- unsigned int i = page->nr_validated_ptes - !partial;
|
||||
+ int rc = 0;
|
||||
+ unsigned int partial_flags = page->partial_flags,
|
||||
+ i = page->nr_validated_ptes - !(partial_flags & PTF_partial_set);
|
||||
|
||||
pl3e = map_domain_page(_mfn(pfn));
|
||||
|
||||
for ( ; ; )
|
||||
{
|
||||
- rc = put_page_from_l3e(pl3e[i], pfn, partial, 0);
|
||||
+ rc = put_page_from_l3e(pl3e[i], pfn, partial_flags);
|
||||
if ( rc < 0 )
|
||||
break;
|
||||
|
||||
- partial = 0;
|
||||
+ partial_flags = 0;
|
||||
if ( rc == 0 )
|
||||
pl3e[i] = unadjust_guest_l3e(pl3e[i], d);
|
||||
|
||||
@@ -1934,12 +1964,14 @@ static int free_l3_table(struct page_info *page)
|
||||
if ( rc == -ERESTART )
|
||||
{
|
||||
page->nr_validated_ptes = i;
|
||||
- page->partial_pte = partial ?: -1;
|
||||
+ page->partial_flags = (partial_flags & PTF_partial_set) ?
|
||||
+ partial_flags :
|
||||
+ (PTF_partial_set | PTF_partial_general_ref);
|
||||
}
|
||||
else if ( rc == -EINTR && i < L3_PAGETABLE_ENTRIES - 1 )
|
||||
{
|
||||
page->nr_validated_ptes = i + 1;
|
||||
- page->partial_pte = 0;
|
||||
+ page->partial_flags = 0;
|
||||
rc = -ERESTART;
|
||||
}
|
||||
return rc > 0 ? 0 : rc;
|
||||
@@ -1950,26 +1982,29 @@ static int free_l4_table(struct page_info *page)
|
||||
struct domain *d = page_get_owner(page);
|
||||
unsigned long pfn = mfn_x(page_to_mfn(page));
|
||||
l4_pgentry_t *pl4e = map_domain_page(_mfn(pfn));
|
||||
- int rc = 0, partial = page->partial_pte;
|
||||
- unsigned int i = page->nr_validated_ptes - !partial;
|
||||
+ int rc = 0;
|
||||
+ unsigned partial_flags = page->partial_flags,
|
||||
+ i = page->nr_validated_ptes - !(partial_flags & PTF_partial_set);
|
||||
|
||||
do {
|
||||
if ( is_guest_l4_slot(d, i) )
|
||||
- rc = put_page_from_l4e(pl4e[i], pfn, partial, 0);
|
||||
+ rc = put_page_from_l4e(pl4e[i], pfn, partial_flags);
|
||||
if ( rc < 0 )
|
||||
break;
|
||||
- partial = 0;
|
||||
+ partial_flags = 0;
|
||||
} while ( i-- );
|
||||
|
||||
if ( rc == -ERESTART )
|
||||
{
|
||||
page->nr_validated_ptes = i;
|
||||
- page->partial_pte = partial ?: -1;
|
||||
+ page->partial_flags = (partial_flags & PTF_partial_set) ?
|
||||
+ partial_flags :
|
||||
+ (PTF_partial_set | PTF_partial_general_ref);
|
||||
}
|
||||
else if ( rc == -EINTR && i < L4_PAGETABLE_ENTRIES - 1 )
|
||||
{
|
||||
page->nr_validated_ptes = i + 1;
|
||||
- page->partial_pte = 0;
|
||||
+ page->partial_flags = 0;
|
||||
rc = -ERESTART;
|
||||
}
|
||||
|
||||
@@ -2247,7 +2282,7 @@ static int mod_l2_entry(l2_pgentry_t *pl2e,
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
- put_page_from_l2e(ol2e, pfn, 0, true);
|
||||
+ put_page_from_l2e(ol2e, pfn, PTF_defer);
|
||||
|
||||
return rc;
|
||||
}
|
||||
@@ -2315,7 +2350,7 @@ static int mod_l3_entry(l3_pgentry_t *pl3e,
|
||||
if ( !create_pae_xen_mappings(d, pl3e) )
|
||||
BUG();
|
||||
|
||||
- put_page_from_l3e(ol3e, pfn, 0, 1);
|
||||
+ put_page_from_l3e(ol3e, pfn, PTF_defer);
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -2378,7 +2413,7 @@ static int mod_l4_entry(l4_pgentry_t *pl4e,
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
- put_page_from_l4e(ol4e, pfn, 0, 1);
|
||||
+ put_page_from_l4e(ol4e, pfn, PTF_defer);
|
||||
return rc;
|
||||
}
|
||||
#endif /* CONFIG_PV */
|
||||
@@ -2649,7 +2684,7 @@ int free_page_type(struct page_info *page, unsigned long type,
|
||||
if ( !(type & PGT_partial) )
|
||||
{
|
||||
page->nr_validated_ptes = 1U << PAGETABLE_ORDER;
|
||||
- page->partial_pte = 0;
|
||||
+ page->partial_flags = 0;
|
||||
}
|
||||
|
||||
switch ( type & PGT_type_mask )
|
||||
@@ -2946,7 +2981,7 @@ static int _get_page_type(struct page_info *page, unsigned long type,
|
||||
if ( !(x & PGT_partial) )
|
||||
{
|
||||
page->nr_validated_ptes = 0;
|
||||
- page->partial_pte = 0;
|
||||
+ page->partial_flags = 0;
|
||||
}
|
||||
page->linear_pt_count = 0;
|
||||
rc = alloc_page_type(page, type, preemptible);
|
||||
@@ -3122,7 +3157,7 @@ int new_guest_cr3(mfn_t mfn)
|
||||
return 0;
|
||||
}
|
||||
|
||||
- rc = get_page_and_type_from_mfn(mfn, PGT_root_page_table, d, 0, 1);
|
||||
+ rc = get_page_and_type_from_mfn(mfn, PGT_root_page_table, d, PTF_preemptible);
|
||||
switch ( rc )
|
||||
{
|
||||
case 0:
|
||||
@@ -3473,7 +3508,7 @@ long do_mmuext_op(
|
||||
if ( op.arg1.mfn != 0 )
|
||||
{
|
||||
rc = get_page_and_type_from_mfn(
|
||||
- _mfn(op.arg1.mfn), PGT_root_page_table, currd, 0, 1);
|
||||
+ _mfn(op.arg1.mfn), PGT_root_page_table, currd, PTF_preemptible);
|
||||
|
||||
if ( unlikely(rc) )
|
||||
{
|
||||
diff --git a/xen/include/asm-x86/mm.h b/xen/include/asm-x86/mm.h
|
||||
index 6faa563167..8406ac3c37 100644
|
||||
--- a/xen/include/asm-x86/mm.h
|
||||
+++ b/xen/include/asm-x86/mm.h
|
||||
@@ -228,19 +228,34 @@ struct page_info
|
||||
* setting the flag must not drop that reference, whereas the instance
|
||||
* clearing it will have to.
|
||||
*
|
||||
- * If @partial_pte is positive then PTE at @nr_validated_ptes+1 has
|
||||
- * been partially validated. This implies that the general reference
|
||||
- * to the page (acquired from get_page_from_lNe()) would be dropped
|
||||
- * (again due to the apparent failure) and hence must be re-acquired
|
||||
- * when resuming the validation, but must not be dropped when picking
|
||||
- * up the page for invalidation.
|
||||
+ * If partial_flags & PTF_partial_set is set, then the page at
|
||||
+ * at @nr_validated_ptes had PGT_partial set as a result of an
|
||||
+ * operation on the current page. (That page may or may not
|
||||
+ * still have PGT_partial set.)
|
||||
*
|
||||
- * If @partial_pte is negative then PTE at @nr_validated_ptes+1 has
|
||||
- * been partially invalidated. This is basically the opposite case of
|
||||
- * above, i.e. the general reference to the page was not dropped in
|
||||
- * put_page_from_lNe() (due to the apparent failure), and hence it
|
||||
- * must be dropped when the put operation is resumed (and completes),
|
||||
- * but it must not be acquired if picking up the page for validation.
|
||||
+ * If PTF_partial_general_ref is set, then the PTE at
|
||||
+ * @nr_validated_ptef holds a general reference count for the
|
||||
+ * page.
|
||||
+ *
|
||||
+ * This happens:
|
||||
+ * - During de-validation, if de-validation of the page was
|
||||
+ * interrupted
|
||||
+ * - During validation, if an invalid entry is encountered and
|
||||
+ * validation is preemptible
|
||||
+ * - During validation, if PTF_partial_general_ref was set on
|
||||
+ * this entry to begin with (perhaps because we're picking
|
||||
+ * up from a partial de-validation).
|
||||
+ *
|
||||
+ * When resuming validation, if PTF_partial_general_ref is clear,
|
||||
+ * then a general reference must be re-acquired; if it is set, no
|
||||
+ * reference should be acquired.
|
||||
+ *
|
||||
+ * When resuming de-validation, if PTF_partial_general_ref is
|
||||
+ * clear, no reference should be dropped; if it is set, a
|
||||
+ * reference should be dropped.
|
||||
+ *
|
||||
+ * NB that PTF_partial_set and PTF_partial_general_ref are
|
||||
+ * defined in mm.c, the only place where they are used.
|
||||
*
|
||||
* The 3rd field, @linear_pt_count, indicates
|
||||
* - by a positive value, how many same-level page table entries a page
|
||||
@@ -251,7 +266,7 @@ struct page_info
|
||||
struct {
|
||||
u16 nr_validated_ptes:PAGETABLE_ORDER + 1;
|
||||
u16 :16 - PAGETABLE_ORDER - 1 - 2;
|
||||
- s16 partial_pte:2;
|
||||
+ u16 partial_flags:2;
|
||||
s16 linear_pt_count;
|
||||
};
|
||||
|
||||
--
|
||||
2.23.0
|
||||
|
||||
|
|
@ -1,140 +0,0 @@
|
|||
From db1d801aa8dcb918a27486a6e8d9cf5d7307dec3 Mon Sep 17 00:00:00 2001
|
||||
From: George Dunlap <george.dunlap@citrix.com>
|
||||
Date: Thu, 10 Oct 2019 17:57:49 +0100
|
||||
Subject: [PATCH 04/11] x86/mm: Use flags for _put_page_type rather than a
|
||||
boolean
|
||||
|
||||
This is in mainly in preparation for _put_page_type taking the
|
||||
partial_flags value in the future. It also makes it easier to read in
|
||||
the caller (since you see a flag name rather than `true` or `false`).
|
||||
|
||||
No functional change intended.
|
||||
|
||||
This is part of XSA-299.
|
||||
|
||||
Reported-by: George Dunlap <george.dunlap@citrix.com>
|
||||
Signed-off-by: George Dunlap <george.dunlap@citrix.com>
|
||||
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||||
---
|
||||
xen/arch/x86/mm.c | 25 +++++++++++++------------
|
||||
1 file changed, 13 insertions(+), 12 deletions(-)
|
||||
|
||||
diff --git a/xen/arch/x86/mm.c b/xen/arch/x86/mm.c
|
||||
index 3f6f8cc9b8..0740b61af8 100644
|
||||
--- a/xen/arch/x86/mm.c
|
||||
+++ b/xen/arch/x86/mm.c
|
||||
@@ -1200,7 +1200,7 @@ get_page_from_l4e(
|
||||
}
|
||||
#endif /* CONFIG_PV */
|
||||
|
||||
-static int _put_page_type(struct page_info *page, bool preemptible,
|
||||
+static int _put_page_type(struct page_info *page, unsigned int flags,
|
||||
struct page_info *ptpg);
|
||||
|
||||
void put_page_from_l1e(l1_pgentry_t l1e, struct domain *l1e_owner)
|
||||
@@ -1320,7 +1320,7 @@ static int put_page_from_l2e(l2_pgentry_t l2e, unsigned long pfn,
|
||||
PTF_partial_set )
|
||||
{
|
||||
ASSERT(!(flags & PTF_defer));
|
||||
- rc = _put_page_type(pg, true, ptpg);
|
||||
+ rc = _put_page_type(pg, PTF_preemptible, ptpg);
|
||||
}
|
||||
else if ( flags & PTF_defer )
|
||||
{
|
||||
@@ -1329,7 +1329,7 @@ static int put_page_from_l2e(l2_pgentry_t l2e, unsigned long pfn,
|
||||
}
|
||||
else
|
||||
{
|
||||
- rc = _put_page_type(pg, true, ptpg);
|
||||
+ rc = _put_page_type(pg, PTF_preemptible, ptpg);
|
||||
if ( likely(!rc) )
|
||||
put_page(pg);
|
||||
}
|
||||
@@ -1366,7 +1366,7 @@ static int put_page_from_l3e(l3_pgentry_t l3e, unsigned long pfn,
|
||||
PTF_partial_set )
|
||||
{
|
||||
ASSERT(!(flags & PTF_defer));
|
||||
- return _put_page_type(pg, true, mfn_to_page(_mfn(pfn)));
|
||||
+ return _put_page_type(pg, PTF_preemptible, mfn_to_page(_mfn(pfn)));
|
||||
}
|
||||
|
||||
if ( flags & PTF_defer )
|
||||
@@ -1376,7 +1376,7 @@ static int put_page_from_l3e(l3_pgentry_t l3e, unsigned long pfn,
|
||||
return 0;
|
||||
}
|
||||
|
||||
- rc = _put_page_type(pg, true, mfn_to_page(_mfn(pfn)));
|
||||
+ rc = _put_page_type(pg, PTF_preemptible, mfn_to_page(_mfn(pfn)));
|
||||
if ( likely(!rc) )
|
||||
put_page(pg);
|
||||
|
||||
@@ -1397,7 +1397,7 @@ static int put_page_from_l4e(l4_pgentry_t l4e, unsigned long pfn,
|
||||
PTF_partial_set )
|
||||
{
|
||||
ASSERT(!(flags & PTF_defer));
|
||||
- return _put_page_type(pg, true, mfn_to_page(_mfn(pfn)));
|
||||
+ return _put_page_type(pg, PTF_preemptible, mfn_to_page(_mfn(pfn)));
|
||||
}
|
||||
|
||||
if ( flags & PTF_defer )
|
||||
@@ -1407,7 +1407,7 @@ static int put_page_from_l4e(l4_pgentry_t l4e, unsigned long pfn,
|
||||
return 0;
|
||||
}
|
||||
|
||||
- rc = _put_page_type(pg, true, mfn_to_page(_mfn(pfn)));
|
||||
+ rc = _put_page_type(pg, PTF_preemptible, mfn_to_page(_mfn(pfn)));
|
||||
if ( likely(!rc) )
|
||||
put_page(pg);
|
||||
}
|
||||
@@ -2757,10 +2757,11 @@ static int _put_final_page_type(struct page_info *page, unsigned long type,
|
||||
}
|
||||
|
||||
|
||||
-static int _put_page_type(struct page_info *page, bool preemptible,
|
||||
+static int _put_page_type(struct page_info *page, unsigned int flags,
|
||||
struct page_info *ptpg)
|
||||
{
|
||||
unsigned long nx, x, y = page->u.inuse.type_info;
|
||||
+ bool preemptible = flags & PTF_preemptible;
|
||||
|
||||
ASSERT(current_locked_page_ne_check(page));
|
||||
|
||||
@@ -2969,7 +2970,7 @@ static int _get_page_type(struct page_info *page, unsigned long type,
|
||||
|
||||
if ( unlikely(iommu_ret) )
|
||||
{
|
||||
- _put_page_type(page, false, NULL);
|
||||
+ _put_page_type(page, 0, NULL);
|
||||
rc = iommu_ret;
|
||||
goto out;
|
||||
}
|
||||
@@ -2996,7 +2997,7 @@ static int _get_page_type(struct page_info *page, unsigned long type,
|
||||
|
||||
void put_page_type(struct page_info *page)
|
||||
{
|
||||
- int rc = _put_page_type(page, false, NULL);
|
||||
+ int rc = _put_page_type(page, 0, NULL);
|
||||
ASSERT(rc == 0);
|
||||
(void)rc;
|
||||
}
|
||||
@@ -3013,7 +3014,7 @@ int get_page_type(struct page_info *page, unsigned long type)
|
||||
|
||||
int put_page_type_preemptible(struct page_info *page)
|
||||
{
|
||||
- return _put_page_type(page, true, NULL);
|
||||
+ return _put_page_type(page, PTF_preemptible, NULL);
|
||||
}
|
||||
|
||||
int get_page_type_preemptible(struct page_info *page, unsigned long type)
|
||||
@@ -3030,7 +3031,7 @@ int put_old_guest_table(struct vcpu *v)
|
||||
if ( !v->arch.old_guest_table )
|
||||
return 0;
|
||||
|
||||
- switch ( rc = _put_page_type(v->arch.old_guest_table, true,
|
||||
+ switch ( rc = _put_page_type(v->arch.old_guest_table, PTF_preemptible,
|
||||
v->arch.old_guest_ptpg) )
|
||||
{
|
||||
case -EINTR:
|
||||
--
|
||||
2.23.0
|
||||
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
From 6f257854c8778774210281c5c21028c4b7739b44 Mon Sep 17 00:00:00 2001
|
||||
From: George Dunlap <george.dunlap@citrix.com>
|
||||
Date: Thu, 10 Oct 2019 17:57:49 +0100
|
||||
Subject: [PATCH 05/11] x86/mm: Rework get_page_and_type_from_mfn conditional
|
||||
|
||||
Make it easier to read by declaring the conditions in which we will
|
||||
retain the ref, rather than the conditions under which we release it.
|
||||
|
||||
The only way (page == current->arch.old_guest_table) can be true is if
|
||||
preemptible is true; so remove this from the query itself, and add an
|
||||
ASSERT() to that effect on the opposite path.
|
||||
|
||||
No functional change intended.
|
||||
|
||||
NB that alloc_lN_table() mishandle the "linear pt failure" situation
|
||||
described in the comment; this will be addressed in a future patch.
|
||||
|
||||
This is part of XSA-299.
|
||||
|
||||
Reported-by: George Dunlap <george.dunlap@citrix.com>
|
||||
Signed-off-by: George Dunlap <george.dunlap@citrix.com>
|
||||
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||||
---
|
||||
xen/arch/x86/mm.c | 39 +++++++++++++++++++++++++++++++++++++--
|
||||
1 file changed, 37 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/xen/arch/x86/mm.c b/xen/arch/x86/mm.c
|
||||
index 0740b61af8..0a4d39a2c3 100644
|
||||
--- a/xen/arch/x86/mm.c
|
||||
+++ b/xen/arch/x86/mm.c
|
||||
@@ -1122,8 +1122,43 @@ static int get_page_and_type_from_mfn(
|
||||
|
||||
rc = _get_page_type(page, type, preemptible);
|
||||
|
||||
- if ( unlikely(rc) && !partial_ref &&
|
||||
- (!preemptible || page != current->arch.old_guest_table) )
|
||||
+ /*
|
||||
+ * Retain the refcount if:
|
||||
+ * - page is fully validated (rc == 0)
|
||||
+ * - page is not validated (rc < 0) but:
|
||||
+ * - We came in with a reference (partial_ref)
|
||||
+ * - page is partially validated but there's been an error
|
||||
+ * (page == current->arch.old_guest_table)
|
||||
+ *
|
||||
+ * The partial_ref-on-error clause is worth an explanation. There
|
||||
+ * are two scenarios where partial_ref might be true coming in:
|
||||
+ * - mfn has been partially demoted as type `type`; i.e. has
|
||||
+ * PGT_partial set
|
||||
+ * - mfn has been partially demoted as L(type+1) (i.e., a linear
|
||||
+ * page; e.g. we're being called from get_page_from_l2e with
|
||||
+ * type == PGT_l1_table, but the mfn is PGT_l2_table)
|
||||
+ *
|
||||
+ * If there's an error, in the first case, _get_page_type will
|
||||
+ * either return -ERESTART, in which case we want to retain the
|
||||
+ * ref (as the caller will consider it retained), or -EINVAL, in
|
||||
+ * which case old_guest_table will be set; in both cases, we need
|
||||
+ * to retain the ref.
|
||||
+ *
|
||||
+ * In the second case, if there's an error, _get_page_type() can
|
||||
+ * *only* return -EINVAL, and *never* set old_guest_table. In
|
||||
+ * that case we also want to retain the reference, to allow the
|
||||
+ * page to continue to be torn down (i.e., PGT_partial cleared)
|
||||
+ * safely.
|
||||
+ *
|
||||
+ * Also note that we shouldn't be able to leave with the reference
|
||||
+ * count retained unless we succeeded, or the operation was
|
||||
+ * preemptible.
|
||||
+ */
|
||||
+ if ( likely(!rc) || partial_ref )
|
||||
+ /* nothing */;
|
||||
+ else if ( page == current->arch.old_guest_table )
|
||||
+ ASSERT(preemptible);
|
||||
+ else
|
||||
put_page(page);
|
||||
|
||||
return rc;
|
||||
--
|
||||
2.23.0
|
||||
|
||||
|
|
@ -1,111 +0,0 @@
|
|||
From 4ad70553611a7a4e4494d5a3b51b5cc295a488e0 Mon Sep 17 00:00:00 2001
|
||||
From: George Dunlap <george.dunlap@citrix.com>
|
||||
Date: Thu, 10 Oct 2019 17:57:49 +0100
|
||||
Subject: [PATCH 06/11] x86/mm: Have alloc_l[23]_table clear partial_flags when
|
||||
preempting
|
||||
|
||||
In order to allow recursive pagetable promotions and demotions to be
|
||||
interrupted, Xen must keep track of the state of the sub-pages
|
||||
promoted or demoted. This is stored in two elements in the page
|
||||
struct: nr_entries_validated and partial_flags.
|
||||
|
||||
The rule is that entries [0, nr_entries_validated) should always be
|
||||
validated and hold a general reference count. If partial_flags is
|
||||
zero, then [nr_entries_validated] is not validated and no reference
|
||||
count is held. If PTF_partial_set is set, then [nr_entries_validated]
|
||||
is partially validated.
|
||||
|
||||
At the moment, a distinction is made between promotion and demotion
|
||||
with regard to whether the entry itself "holds" a general reference
|
||||
count: when entry promotion is interrupted (i.e., returns -ERESTART),
|
||||
the entry is not considered to hold a reference; when entry demotion
|
||||
is interrupted, the entry is still considered to hold a general
|
||||
reference.
|
||||
|
||||
PTF_partial_general_ref is used to distinguish between these cases.
|
||||
If clear, it's a partial promotion => no general reference count held
|
||||
by the entry; if set, it's partial demotion, so a general reference
|
||||
count held. Because promotions and demotions can be interleaved, this
|
||||
value is passed to get_page_and_type_from_mfn and put_page_from_l*e,
|
||||
to be able to properly handle reference counts.
|
||||
|
||||
Unfortunately, when alloc_l[23]_table check hypercall_preempt_check()
|
||||
and return -ERESTART, they set nr_entries_validated, but don't clear
|
||||
partial_flags.
|
||||
|
||||
If we were picking up from a previously-interrupted promotion, that
|
||||
means that PTF_partial_set would be set even though
|
||||
[nr_entries_validated] was not partially validated. This means that
|
||||
if the page in this state were de-validated, put_page_type() would
|
||||
erroneously be called on that entry.
|
||||
|
||||
Perhaps worse, if we were racing with a de-validation, then we might
|
||||
leave both PTF_partial_set and PTF_partial_general_ref; and when
|
||||
de-validation picked up again, both the type and the general ref would
|
||||
be erroneously dropped from [nr_entries_validated].
|
||||
|
||||
In a sense, the real issue here is code duplication. Rather than
|
||||
duplicate the interruption code, set rc to -EINTR and fall through to
|
||||
the code which already handles that case correctly.
|
||||
|
||||
Given the logic at this point, it should be impossible for
|
||||
partial_flags to be non-zero; add an ASSERT() to catch any changes.
|
||||
|
||||
This is part of XSA-299.
|
||||
|
||||
Reported-by: George Dunlap <george.dunlap@citrix.com>
|
||||
Signed-off-by: George Dunlap <george.dunlap@citrix.com>
|
||||
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||||
---
|
||||
xen/arch/x86/mm.c | 25 ++++++-------------------
|
||||
1 file changed, 6 insertions(+), 19 deletions(-)
|
||||
|
||||
diff --git a/xen/arch/x86/mm.c b/xen/arch/x86/mm.c
|
||||
index 0a4d39a2c3..bbd29a68f4 100644
|
||||
--- a/xen/arch/x86/mm.c
|
||||
+++ b/xen/arch/x86/mm.c
|
||||
@@ -1554,21 +1554,13 @@ static int alloc_l2_table(struct page_info *page, unsigned long type)
|
||||
for ( i = page->nr_validated_ptes; i < L2_PAGETABLE_ENTRIES;
|
||||
i++, partial_flags = 0 )
|
||||
{
|
||||
- l2_pgentry_t l2e;
|
||||
+ l2_pgentry_t l2e = pl2e[i];
|
||||
|
||||
if ( i > page->nr_validated_ptes && hypercall_preempt_check() )
|
||||
- {
|
||||
- page->nr_validated_ptes = i;
|
||||
- rc = -ERESTART;
|
||||
- break;
|
||||
- }
|
||||
-
|
||||
- if ( !is_guest_l2_slot(d, type, i) )
|
||||
+ rc = -EINTR;
|
||||
+ else if ( !is_guest_l2_slot(d, type, i) )
|
||||
continue;
|
||||
-
|
||||
- l2e = pl2e[i];
|
||||
-
|
||||
- if ( !(l2e_get_flags(l2e) & _PAGE_PRESENT) )
|
||||
+ else if ( !(l2e_get_flags(l2e) & _PAGE_PRESENT) )
|
||||
{
|
||||
if ( !pv_l1tf_check_l2e(d, l2e) )
|
||||
continue;
|
||||
@@ -1640,13 +1632,8 @@ static int alloc_l3_table(struct page_info *page)
|
||||
l3_pgentry_t l3e = pl3e[i];
|
||||
|
||||
if ( i > page->nr_validated_ptes && hypercall_preempt_check() )
|
||||
- {
|
||||
- page->nr_validated_ptes = i;
|
||||
- rc = -ERESTART;
|
||||
- break;
|
||||
- }
|
||||
-
|
||||
- if ( is_pv_32bit_domain(d) && (i == 3) )
|
||||
+ rc = -EINTR;
|
||||
+ else if ( is_pv_32bit_domain(d) && (i == 3) )
|
||||
{
|
||||
if ( !(l3e_get_flags(l3e) & _PAGE_PRESENT) ||
|
||||
(l3e_get_flags(l3e) & l3_disallow_mask(d)) )
|
||||
--
|
||||
2.23.0
|
||||
|
||||
|
|
@ -1,378 +0,0 @@
|
|||
From 51fe4e67d954649fcf103116be6206a769f0db1e Mon Sep 17 00:00:00 2001
|
||||
From: George Dunlap <george.dunlap@citrix.com>
|
||||
Date: Thu, 10 Oct 2019 17:57:49 +0100
|
||||
Subject: [PATCH 07/11] x86/mm: Always retain a general ref on partial
|
||||
|
||||
In order to allow recursive pagetable promotions and demotions to be
|
||||
interrupted, Xen must keep track of the state of the sub-pages
|
||||
promoted or demoted. This is stored in two elements in the page struct:
|
||||
nr_entries_validated and partial_flags.
|
||||
|
||||
The rule is that entries [0, nr_entries_validated) should always be
|
||||
validated and hold a general reference count. If partial_flags is
|
||||
zero, then [nr_entries_validated] is not validated and no reference
|
||||
count is held. If PTF_partial_set is set, then [nr_entries_validated]
|
||||
is partially validated.
|
||||
|
||||
At the moment, a distinction is made between promotion and demotion
|
||||
with regard to whether the entry itself "holds" a general reference
|
||||
count: when entry promotion is interrupted (i.e., returns -ERESTART),
|
||||
the entry is not considered to hold a reference; when entry demotion
|
||||
is interrupted, the entry is still considered to hold a general
|
||||
reference.
|
||||
|
||||
PTF_partial_general_ref is used to distinguish between these cases.
|
||||
If clear, it's a partial promotion => no general reference count held
|
||||
by the entry; if set, it's partial demotion, so a general reference
|
||||
count held. Because promotions and demotions can be interleaved, this
|
||||
value is passed to get_page_and_type_from_mfn and put_page_from_l*e,
|
||||
to be able to properly handle reference counts.
|
||||
|
||||
Unfortunately, because a refcount is not held, it is possible to
|
||||
engineer a situation where PFT_partial_set is set but the page in
|
||||
question has been assigned to another domain. A sketch is provided in
|
||||
the appendix.
|
||||
|
||||
Fix this by having the parent page table entry hold a general
|
||||
reference count whenever PFT_partial_set is set. (For clarity of
|
||||
change, keep two separate flags. These will be collapsed in a
|
||||
subsequent changeset.)
|
||||
|
||||
This has two basic implications. On the put_page_from_lNe() side,
|
||||
this mean that the (partial_set && !partial_ref) case can never happen,
|
||||
and no longer needs to be special-cased.
|
||||
|
||||
Secondly, because both flags are set together, there's no need to carry over
|
||||
existing bits from partial_pte.
|
||||
|
||||
(NB there is still another issue with calling _put_page_type() on a
|
||||
page which had PGT_partial set; that will be handled in a subsequent
|
||||
patch.)
|
||||
|
||||
On the get_page_and_type_from_mfn() side, we need to distinguish
|
||||
between callers which hold a reference on partial (i.e.,
|
||||
alloc_lN_table()), and those which do not (new_cr3, PIN_LN_TABLE, and
|
||||
so on): pass a flag if the type should be retained on interruption.
|
||||
|
||||
NB that since l1 promotion can't be preempted, that get_page_from_l2e
|
||||
can't return -ERESTART.
|
||||
|
||||
This is part of XSA-299.
|
||||
|
||||
Reported-by: George Dunlap <george.dunlap@citrix.com>
|
||||
Signed-off-by: George Dunlap <george.dunlap@citrix.com>
|
||||
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||||
-----
|
||||
* Appendix: Engineering PTF_partial_set while a page belongs to a
|
||||
foreign domain
|
||||
|
||||
Suppose A is a page which can be promoted to an l3, and B is a page
|
||||
which can be promoted to an l2, and A[x] points to B. B has
|
||||
PGC_allocated set but no other general references.
|
||||
|
||||
V1: PIN_L3 A.
|
||||
A is validated, B is validated.
|
||||
A.type_count = 1 | PGT_validated | PGT_pinned
|
||||
B.type_count = 1 | PGT_validated
|
||||
B.count = 2 | PGC_allocated (A[x] holds a general ref)
|
||||
|
||||
V1: UNPIN A.
|
||||
A begins de-validation.
|
||||
Arrange to be interrupted when i < x
|
||||
V1->old_guest_table = A
|
||||
V1->old_guest_table_ref_held = false
|
||||
A.type_count = 1 | PGT_partial
|
||||
A.nr_validated_entries = i < x
|
||||
B.type_count = 0
|
||||
B.count = 1 | PGC_allocated
|
||||
|
||||
V2: MOD_L4_ENTRY to point some l4e to A.
|
||||
Picks up re-validation of A.
|
||||
Arrange to be interrupted halfway through B's validation
|
||||
B.type_count = 1 | PGT_partial
|
||||
B.count = 2 | PGC_allocated (PGT_partial holds a general ref)
|
||||
A.type_count = 1 | PGT_partial
|
||||
A.nr_validated_entries = x
|
||||
A.partial_pte = PTF_partial_set
|
||||
|
||||
V3: MOD_L3_ENTRY to point some other l3e (not in A) to B.
|
||||
Validates B.
|
||||
B.type_count = 1 | PGT_validated
|
||||
B.count = 2 | PGC_allocated ("other l3e" holds a general ref)
|
||||
|
||||
V3: MOD_L3_ENTRY to clear l3e pointing to B.
|
||||
Devalidates B.
|
||||
B.type_count = 0
|
||||
B.count = 1 | PGC_allocated
|
||||
|
||||
V3: decrease_reservation(B)
|
||||
Clears PGC_allocated
|
||||
B.count = 0 => B is freed
|
||||
|
||||
B gets assigned to a different domain
|
||||
|
||||
V1: Restarts UNPIN of A
|
||||
put_old_guest_table(A)
|
||||
...
|
||||
free_l3_table(A)
|
||||
|
||||
Now since A.partial_flags has PTF_partial_set, free_l3_table() will
|
||||
call put_page_from_l3e() on A[x], which points to B, while B is owned
|
||||
by another domain.
|
||||
|
||||
If A[x] held a general refcount for B on partial validation, as it does
|
||||
for partial de-validation, then B would still have a reference count of
|
||||
1 after PGC_allocated was freed; so B wouldn't be freed until after
|
||||
put_page_from_l3e() had happend on A[x].
|
||||
---
|
||||
xen/arch/x86/mm.c | 84 +++++++++++++++++++++++-----------------
|
||||
xen/include/asm-x86/mm.h | 15 ++++---
|
||||
2 files changed, 58 insertions(+), 41 deletions(-)
|
||||
|
||||
diff --git a/xen/arch/x86/mm.c b/xen/arch/x86/mm.c
|
||||
index bbd29a68f4..4d3ebf341d 100644
|
||||
--- a/xen/arch/x86/mm.c
|
||||
+++ b/xen/arch/x86/mm.c
|
||||
@@ -1102,10 +1102,11 @@ get_page_from_l1e(
|
||||
* page->pte[page->nr_validated_entries]. See the comment in mm.h for
|
||||
* more information.
|
||||
*/
|
||||
-#define PTF_partial_set (1 << 0)
|
||||
-#define PTF_partial_general_ref (1 << 1)
|
||||
-#define PTF_preemptible (1 << 2)
|
||||
-#define PTF_defer (1 << 3)
|
||||
+#define PTF_partial_set (1 << 0)
|
||||
+#define PTF_partial_general_ref (1 << 1)
|
||||
+#define PTF_preemptible (1 << 2)
|
||||
+#define PTF_defer (1 << 3)
|
||||
+#define PTF_retain_ref_on_restart (1 << 4)
|
||||
|
||||
static int get_page_and_type_from_mfn(
|
||||
mfn_t mfn, unsigned long type, struct domain *d,
|
||||
@@ -1114,7 +1115,11 @@ static int get_page_and_type_from_mfn(
|
||||
struct page_info *page = mfn_to_page(mfn);
|
||||
int rc;
|
||||
bool preemptible = flags & PTF_preemptible,
|
||||
- partial_ref = flags & PTF_partial_general_ref;
|
||||
+ partial_ref = flags & PTF_partial_general_ref,
|
||||
+ partial_set = flags & PTF_partial_set,
|
||||
+ retain_ref = flags & PTF_retain_ref_on_restart;
|
||||
+
|
||||
+ ASSERT(partial_ref == partial_set);
|
||||
|
||||
if ( likely(!partial_ref) &&
|
||||
unlikely(!get_page_from_mfn(mfn, d)) )
|
||||
@@ -1127,13 +1132,15 @@ static int get_page_and_type_from_mfn(
|
||||
* - page is fully validated (rc == 0)
|
||||
* - page is not validated (rc < 0) but:
|
||||
* - We came in with a reference (partial_ref)
|
||||
+ * - page is partially validated (rc == -ERESTART), and the
|
||||
+ * caller has asked the ref to be retained in that case
|
||||
* - page is partially validated but there's been an error
|
||||
* (page == current->arch.old_guest_table)
|
||||
*
|
||||
* The partial_ref-on-error clause is worth an explanation. There
|
||||
* are two scenarios where partial_ref might be true coming in:
|
||||
- * - mfn has been partially demoted as type `type`; i.e. has
|
||||
- * PGT_partial set
|
||||
+ * - mfn has been partially promoted / demoted as type `type`;
|
||||
+ * i.e. has PGT_partial set
|
||||
* - mfn has been partially demoted as L(type+1) (i.e., a linear
|
||||
* page; e.g. we're being called from get_page_from_l2e with
|
||||
* type == PGT_l1_table, but the mfn is PGT_l2_table)
|
||||
@@ -1156,7 +1163,8 @@ static int get_page_and_type_from_mfn(
|
||||
*/
|
||||
if ( likely(!rc) || partial_ref )
|
||||
/* nothing */;
|
||||
- else if ( page == current->arch.old_guest_table )
|
||||
+ else if ( page == current->arch.old_guest_table ||
|
||||
+ (retain_ref && rc == -ERESTART) )
|
||||
ASSERT(preemptible);
|
||||
else
|
||||
put_page(page);
|
||||
@@ -1354,8 +1362,8 @@ static int put_page_from_l2e(l2_pgentry_t l2e, unsigned long pfn,
|
||||
if ( (flags & (PTF_partial_set | PTF_partial_general_ref)) ==
|
||||
PTF_partial_set )
|
||||
{
|
||||
- ASSERT(!(flags & PTF_defer));
|
||||
- rc = _put_page_type(pg, PTF_preemptible, ptpg);
|
||||
+ /* partial_set should always imply partial_ref */
|
||||
+ BUG();
|
||||
}
|
||||
else if ( flags & PTF_defer )
|
||||
{
|
||||
@@ -1400,8 +1408,8 @@ static int put_page_from_l3e(l3_pgentry_t l3e, unsigned long pfn,
|
||||
if ( (flags & (PTF_partial_set | PTF_partial_general_ref)) ==
|
||||
PTF_partial_set )
|
||||
{
|
||||
- ASSERT(!(flags & PTF_defer));
|
||||
- return _put_page_type(pg, PTF_preemptible, mfn_to_page(_mfn(pfn)));
|
||||
+ /* partial_set should always imply partial_ref */
|
||||
+ BUG();
|
||||
}
|
||||
|
||||
if ( flags & PTF_defer )
|
||||
@@ -1431,8 +1439,8 @@ static int put_page_from_l4e(l4_pgentry_t l4e, unsigned long pfn,
|
||||
if ( (flags & (PTF_partial_set | PTF_partial_general_ref)) ==
|
||||
PTF_partial_set )
|
||||
{
|
||||
- ASSERT(!(flags & PTF_defer));
|
||||
- return _put_page_type(pg, PTF_preemptible, mfn_to_page(_mfn(pfn)));
|
||||
+ /* partial_set should always imply partial_ref */
|
||||
+ BUG();
|
||||
}
|
||||
|
||||
if ( flags & PTF_defer )
|
||||
@@ -1569,13 +1577,22 @@ static int alloc_l2_table(struct page_info *page, unsigned long type)
|
||||
else
|
||||
rc = get_page_from_l2e(l2e, pfn, d, partial_flags);
|
||||
|
||||
- if ( rc == -ERESTART )
|
||||
- {
|
||||
- page->nr_validated_ptes = i;
|
||||
- /* Set 'set', retain 'general ref' */
|
||||
- page->partial_flags = partial_flags | PTF_partial_set;
|
||||
- }
|
||||
- else if ( rc == -EINTR && i )
|
||||
+ /*
|
||||
+ * It shouldn't be possible for get_page_from_l2e to return
|
||||
+ * -ERESTART, since we never call this with PTF_preemptible.
|
||||
+ * (alloc_l1_table may return -EINTR on an L1TF-vulnerable
|
||||
+ * entry.)
|
||||
+ *
|
||||
+ * NB that while on a "clean" promotion, we can never get
|
||||
+ * PGT_partial. It is possible to arrange for an l2e to
|
||||
+ * contain a partially-devalidated l2; but in that case, both
|
||||
+ * of the following functions will fail anyway (the first
|
||||
+ * because the page in question is not an l1; the second
|
||||
+ * because the page is not fully validated).
|
||||
+ */
|
||||
+ ASSERT(rc != -ERESTART);
|
||||
+
|
||||
+ if ( rc == -EINTR && i )
|
||||
{
|
||||
page->nr_validated_ptes = i;
|
||||
page->partial_flags = 0;
|
||||
@@ -1584,6 +1601,7 @@ static int alloc_l2_table(struct page_info *page, unsigned long type)
|
||||
else if ( rc < 0 && rc != -EINTR )
|
||||
{
|
||||
gdprintk(XENLOG_WARNING, "Failure in alloc_l2_table: slot %#x\n", i);
|
||||
+ ASSERT(current->arch.old_guest_table == NULL);
|
||||
if ( i )
|
||||
{
|
||||
page->nr_validated_ptes = i;
|
||||
@@ -1642,7 +1660,7 @@ static int alloc_l3_table(struct page_info *page)
|
||||
rc = get_page_and_type_from_mfn(
|
||||
l3e_get_mfn(l3e),
|
||||
PGT_l2_page_table | PGT_pae_xen_l2, d,
|
||||
- partial_flags | PTF_preemptible);
|
||||
+ partial_flags | PTF_preemptible | PTF_retain_ref_on_restart);
|
||||
}
|
||||
else if ( !(l3e_get_flags(l3e) & _PAGE_PRESENT) )
|
||||
{
|
||||
@@ -1651,13 +1669,14 @@ static int alloc_l3_table(struct page_info *page)
|
||||
rc = -EINTR;
|
||||
}
|
||||
else
|
||||
- rc = get_page_from_l3e(l3e, pfn, d, partial_flags);
|
||||
+ rc = get_page_from_l3e(l3e, pfn, d,
|
||||
+ partial_flags | PTF_retain_ref_on_restart);
|
||||
|
||||
if ( rc == -ERESTART )
|
||||
{
|
||||
page->nr_validated_ptes = i;
|
||||
/* Set 'set', leave 'general ref' set if this entry was set */
|
||||
- page->partial_flags = partial_flags | PTF_partial_set;
|
||||
+ page->partial_flags = PTF_partial_set | PTF_partial_general_ref;
|
||||
}
|
||||
else if ( rc == -EINTR && i )
|
||||
{
|
||||
@@ -1833,13 +1852,14 @@ static int alloc_l4_table(struct page_info *page)
|
||||
rc = -EINTR;
|
||||
}
|
||||
else
|
||||
- rc = get_page_from_l4e(l4e, pfn, d, partial_flags);
|
||||
+ rc = get_page_from_l4e(l4e, pfn, d,
|
||||
+ partial_flags | PTF_retain_ref_on_restart);
|
||||
|
||||
if ( rc == -ERESTART )
|
||||
{
|
||||
page->nr_validated_ptes = i;
|
||||
/* Set 'set', leave 'general ref' set if this entry was set */
|
||||
- page->partial_flags = partial_flags | PTF_partial_set;
|
||||
+ page->partial_flags = PTF_partial_set | PTF_partial_general_ref;
|
||||
}
|
||||
else if ( rc < 0 )
|
||||
{
|
||||
@@ -1936,9 +1956,7 @@ static int free_l2_table(struct page_info *page)
|
||||
else if ( rc == -ERESTART )
|
||||
{
|
||||
page->nr_validated_ptes = i;
|
||||
- page->partial_flags = (partial_flags & PTF_partial_set) ?
|
||||
- partial_flags :
|
||||
- (PTF_partial_set | PTF_partial_general_ref);
|
||||
+ page->partial_flags = PTF_partial_set | PTF_partial_general_ref;
|
||||
}
|
||||
else if ( rc == -EINTR && i < L2_PAGETABLE_ENTRIES - 1 )
|
||||
{
|
||||
@@ -1986,9 +2004,7 @@ static int free_l3_table(struct page_info *page)
|
||||
if ( rc == -ERESTART )
|
||||
{
|
||||
page->nr_validated_ptes = i;
|
||||
- page->partial_flags = (partial_flags & PTF_partial_set) ?
|
||||
- partial_flags :
|
||||
- (PTF_partial_set | PTF_partial_general_ref);
|
||||
+ page->partial_flags = PTF_partial_set | PTF_partial_general_ref;
|
||||
}
|
||||
else if ( rc == -EINTR && i < L3_PAGETABLE_ENTRIES - 1 )
|
||||
{
|
||||
@@ -2019,9 +2035,7 @@ static int free_l4_table(struct page_info *page)
|
||||
if ( rc == -ERESTART )
|
||||
{
|
||||
page->nr_validated_ptes = i;
|
||||
- page->partial_flags = (partial_flags & PTF_partial_set) ?
|
||||
- partial_flags :
|
||||
- (PTF_partial_set | PTF_partial_general_ref);
|
||||
+ page->partial_flags = PTF_partial_set | PTF_partial_general_ref;
|
||||
}
|
||||
else if ( rc == -EINTR && i < L4_PAGETABLE_ENTRIES - 1 )
|
||||
{
|
||||
diff --git a/xen/include/asm-x86/mm.h b/xen/include/asm-x86/mm.h
|
||||
index 8406ac3c37..02079e1324 100644
|
||||
--- a/xen/include/asm-x86/mm.h
|
||||
+++ b/xen/include/asm-x86/mm.h
|
||||
@@ -238,22 +238,25 @@ struct page_info
|
||||
* page.
|
||||
*
|
||||
* This happens:
|
||||
- * - During de-validation, if de-validation of the page was
|
||||
+ * - During validation or de-validation, if the operation was
|
||||
* interrupted
|
||||
* - During validation, if an invalid entry is encountered and
|
||||
* validation is preemptible
|
||||
* - During validation, if PTF_partial_general_ref was set on
|
||||
- * this entry to begin with (perhaps because we're picking
|
||||
- * up from a partial de-validation).
|
||||
+ * this entry to begin with (perhaps because it picked up a
|
||||
+ * previous operation)
|
||||
*
|
||||
- * When resuming validation, if PTF_partial_general_ref is clear,
|
||||
- * then a general reference must be re-acquired; if it is set, no
|
||||
- * reference should be acquired.
|
||||
+ * When resuming validation, if PTF_partial_general_ref is
|
||||
+ * clear, then a general reference must be re-acquired; if it
|
||||
+ * is set, no reference should be acquired.
|
||||
*
|
||||
* When resuming de-validation, if PTF_partial_general_ref is
|
||||
* clear, no reference should be dropped; if it is set, a
|
||||
* reference should be dropped.
|
||||
*
|
||||
+ * NB at the moment, PTF_partial_set should be set if and only if
|
||||
+ * PTF_partial_general_ref is set.
|
||||
+ *
|
||||
* NB that PTF_partial_set and PTF_partial_general_ref are
|
||||
* defined in mm.c, the only place where they are used.
|
||||
*
|
||||
--
|
||||
2.23.0
|
||||
|
||||
|
|
@ -1,227 +0,0 @@
|
|||
From 8a8d836f7f7418e659d37817a66cd7a6b115042b Mon Sep 17 00:00:00 2001
|
||||
From: George Dunlap <george.dunlap@citrix.com>
|
||||
Date: Thu, 10 Oct 2019 17:57:49 +0100
|
||||
Subject: [PATCH 08/11] x86/mm: Collapse PTF_partial_set and
|
||||
PTF_partial_general_ref into one
|
||||
|
||||
...now that they are equivalent. No functional change intended.
|
||||
|
||||
Reported-by: George Dunlap <george.dunlap@citrix.com>
|
||||
Signed-off-by: George Dunlap <george.dunlap@citrix.com>
|
||||
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||||
---
|
||||
xen/arch/x86/mm.c | 50 +++++++++++-----------------------------
|
||||
xen/include/asm-x86/mm.h | 29 +++++++++++------------
|
||||
2 files changed, 26 insertions(+), 53 deletions(-)
|
||||
|
||||
diff --git a/xen/arch/x86/mm.c b/xen/arch/x86/mm.c
|
||||
index 4d3ebf341d..886e93b8aa 100644
|
||||
--- a/xen/arch/x86/mm.c
|
||||
+++ b/xen/arch/x86/mm.c
|
||||
@@ -1097,13 +1097,12 @@ get_page_from_l1e(
|
||||
|
||||
/*
|
||||
* The following flags are used to specify behavior of various get and
|
||||
- * put commands. The first two are also stored in page->partial_flags
|
||||
- * to indicate the state of the page pointed to by
|
||||
+ * put commands. The first is also stored in page->partial_flags to
|
||||
+ * indicate the state of the page pointed to by
|
||||
* page->pte[page->nr_validated_entries]. See the comment in mm.h for
|
||||
* more information.
|
||||
*/
|
||||
#define PTF_partial_set (1 << 0)
|
||||
-#define PTF_partial_general_ref (1 << 1)
|
||||
#define PTF_preemptible (1 << 2)
|
||||
#define PTF_defer (1 << 3)
|
||||
#define PTF_retain_ref_on_restart (1 << 4)
|
||||
@@ -1115,13 +1114,10 @@ static int get_page_and_type_from_mfn(
|
||||
struct page_info *page = mfn_to_page(mfn);
|
||||
int rc;
|
||||
bool preemptible = flags & PTF_preemptible,
|
||||
- partial_ref = flags & PTF_partial_general_ref,
|
||||
partial_set = flags & PTF_partial_set,
|
||||
retain_ref = flags & PTF_retain_ref_on_restart;
|
||||
|
||||
- ASSERT(partial_ref == partial_set);
|
||||
-
|
||||
- if ( likely(!partial_ref) &&
|
||||
+ if ( likely(!partial_set) &&
|
||||
unlikely(!get_page_from_mfn(mfn, d)) )
|
||||
return -EINVAL;
|
||||
|
||||
@@ -1131,14 +1127,14 @@ static int get_page_and_type_from_mfn(
|
||||
* Retain the refcount if:
|
||||
* - page is fully validated (rc == 0)
|
||||
* - page is not validated (rc < 0) but:
|
||||
- * - We came in with a reference (partial_ref)
|
||||
+ * - We came in with a reference (partial_set)
|
||||
* - page is partially validated (rc == -ERESTART), and the
|
||||
* caller has asked the ref to be retained in that case
|
||||
* - page is partially validated but there's been an error
|
||||
* (page == current->arch.old_guest_table)
|
||||
*
|
||||
- * The partial_ref-on-error clause is worth an explanation. There
|
||||
- * are two scenarios where partial_ref might be true coming in:
|
||||
+ * The partial_set-on-error clause is worth an explanation. There
|
||||
+ * are two scenarios where partial_set might be true coming in:
|
||||
* - mfn has been partially promoted / demoted as type `type`;
|
||||
* i.e. has PGT_partial set
|
||||
* - mfn has been partially demoted as L(type+1) (i.e., a linear
|
||||
@@ -1161,7 +1157,7 @@ static int get_page_and_type_from_mfn(
|
||||
* count retained unless we succeeded, or the operation was
|
||||
* preemptible.
|
||||
*/
|
||||
- if ( likely(!rc) || partial_ref )
|
||||
+ if ( likely(!rc) || partial_set )
|
||||
/* nothing */;
|
||||
else if ( page == current->arch.old_guest_table ||
|
||||
(retain_ref && rc == -ERESTART) )
|
||||
@@ -1359,13 +1355,7 @@ static int put_page_from_l2e(l2_pgentry_t l2e, unsigned long pfn,
|
||||
struct page_info *pg = l2e_get_page(l2e);
|
||||
struct page_info *ptpg = mfn_to_page(_mfn(pfn));
|
||||
|
||||
- if ( (flags & (PTF_partial_set | PTF_partial_general_ref)) ==
|
||||
- PTF_partial_set )
|
||||
- {
|
||||
- /* partial_set should always imply partial_ref */
|
||||
- BUG();
|
||||
- }
|
||||
- else if ( flags & PTF_defer )
|
||||
+ if ( flags & PTF_defer )
|
||||
{
|
||||
current->arch.old_guest_ptpg = ptpg;
|
||||
current->arch.old_guest_table = pg;
|
||||
@@ -1405,13 +1395,6 @@ static int put_page_from_l3e(l3_pgentry_t l3e, unsigned long pfn,
|
||||
|
||||
pg = l3e_get_page(l3e);
|
||||
|
||||
- if ( (flags & (PTF_partial_set | PTF_partial_general_ref)) ==
|
||||
- PTF_partial_set )
|
||||
- {
|
||||
- /* partial_set should always imply partial_ref */
|
||||
- BUG();
|
||||
- }
|
||||
-
|
||||
if ( flags & PTF_defer )
|
||||
{
|
||||
current->arch.old_guest_ptpg = mfn_to_page(_mfn(pfn));
|
||||
@@ -1436,13 +1419,6 @@ static int put_page_from_l4e(l4_pgentry_t l4e, unsigned long pfn,
|
||||
{
|
||||
struct page_info *pg = l4e_get_page(l4e);
|
||||
|
||||
- if ( (flags & (PTF_partial_set | PTF_partial_general_ref)) ==
|
||||
- PTF_partial_set )
|
||||
- {
|
||||
- /* partial_set should always imply partial_ref */
|
||||
- BUG();
|
||||
- }
|
||||
-
|
||||
if ( flags & PTF_defer )
|
||||
{
|
||||
current->arch.old_guest_ptpg = mfn_to_page(_mfn(pfn));
|
||||
@@ -1676,7 +1652,7 @@ static int alloc_l3_table(struct page_info *page)
|
||||
{
|
||||
page->nr_validated_ptes = i;
|
||||
/* Set 'set', leave 'general ref' set if this entry was set */
|
||||
- page->partial_flags = PTF_partial_set | PTF_partial_general_ref;
|
||||
+ page->partial_flags = PTF_partial_set;
|
||||
}
|
||||
else if ( rc == -EINTR && i )
|
||||
{
|
||||
@@ -1859,7 +1835,7 @@ static int alloc_l4_table(struct page_info *page)
|
||||
{
|
||||
page->nr_validated_ptes = i;
|
||||
/* Set 'set', leave 'general ref' set if this entry was set */
|
||||
- page->partial_flags = PTF_partial_set | PTF_partial_general_ref;
|
||||
+ page->partial_flags = PTF_partial_set;
|
||||
}
|
||||
else if ( rc < 0 )
|
||||
{
|
||||
@@ -1956,7 +1932,7 @@ static int free_l2_table(struct page_info *page)
|
||||
else if ( rc == -ERESTART )
|
||||
{
|
||||
page->nr_validated_ptes = i;
|
||||
- page->partial_flags = PTF_partial_set | PTF_partial_general_ref;
|
||||
+ page->partial_flags = PTF_partial_set;
|
||||
}
|
||||
else if ( rc == -EINTR && i < L2_PAGETABLE_ENTRIES - 1 )
|
||||
{
|
||||
@@ -2004,7 +1980,7 @@ static int free_l3_table(struct page_info *page)
|
||||
if ( rc == -ERESTART )
|
||||
{
|
||||
page->nr_validated_ptes = i;
|
||||
- page->partial_flags = PTF_partial_set | PTF_partial_general_ref;
|
||||
+ page->partial_flags = PTF_partial_set;
|
||||
}
|
||||
else if ( rc == -EINTR && i < L3_PAGETABLE_ENTRIES - 1 )
|
||||
{
|
||||
@@ -2035,7 +2011,7 @@ static int free_l4_table(struct page_info *page)
|
||||
if ( rc == -ERESTART )
|
||||
{
|
||||
page->nr_validated_ptes = i;
|
||||
- page->partial_flags = PTF_partial_set | PTF_partial_general_ref;
|
||||
+ page->partial_flags = PTF_partial_set;
|
||||
}
|
||||
else if ( rc == -EINTR && i < L4_PAGETABLE_ENTRIES - 1 )
|
||||
{
|
||||
diff --git a/xen/include/asm-x86/mm.h b/xen/include/asm-x86/mm.h
|
||||
index 02079e1324..f0fd35bf6b 100644
|
||||
--- a/xen/include/asm-x86/mm.h
|
||||
+++ b/xen/include/asm-x86/mm.h
|
||||
@@ -233,7 +233,7 @@ struct page_info
|
||||
* operation on the current page. (That page may or may not
|
||||
* still have PGT_partial set.)
|
||||
*
|
||||
- * If PTF_partial_general_ref is set, then the PTE at
|
||||
+ * Additionally, if PTF_partial_set is set, then the PTE at
|
||||
* @nr_validated_ptef holds a general reference count for the
|
||||
* page.
|
||||
*
|
||||
@@ -242,23 +242,20 @@ struct page_info
|
||||
* interrupted
|
||||
* - During validation, if an invalid entry is encountered and
|
||||
* validation is preemptible
|
||||
- * - During validation, if PTF_partial_general_ref was set on
|
||||
- * this entry to begin with (perhaps because it picked up a
|
||||
+ * - During validation, if PTF_partial_set was set on this
|
||||
+ * entry to begin with (perhaps because it picked up a
|
||||
* previous operation)
|
||||
*
|
||||
- * When resuming validation, if PTF_partial_general_ref is
|
||||
- * clear, then a general reference must be re-acquired; if it
|
||||
- * is set, no reference should be acquired.
|
||||
+ * When resuming validation, if PTF_partial_set is clear, then
|
||||
+ * a general reference must be re-acquired; if it is set, no
|
||||
+ * reference should be acquired.
|
||||
*
|
||||
- * When resuming de-validation, if PTF_partial_general_ref is
|
||||
- * clear, no reference should be dropped; if it is set, a
|
||||
- * reference should be dropped.
|
||||
+ * When resuming de-validation, if PTF_partial_set is clear,
|
||||
+ * no reference should be dropped; if it is set, a reference
|
||||
+ * should be dropped.
|
||||
*
|
||||
- * NB at the moment, PTF_partial_set should be set if and only if
|
||||
- * PTF_partial_general_ref is set.
|
||||
- *
|
||||
- * NB that PTF_partial_set and PTF_partial_general_ref are
|
||||
- * defined in mm.c, the only place where they are used.
|
||||
+ * NB that PTF_partial_set is defined in mm.c, the only place
|
||||
+ * where it is used.
|
||||
*
|
||||
* The 3rd field, @linear_pt_count, indicates
|
||||
* - by a positive value, how many same-level page table entries a page
|
||||
@@ -268,8 +265,8 @@ struct page_info
|
||||
*/
|
||||
struct {
|
||||
u16 nr_validated_ptes:PAGETABLE_ORDER + 1;
|
||||
- u16 :16 - PAGETABLE_ORDER - 1 - 2;
|
||||
- u16 partial_flags:2;
|
||||
+ u16 :16 - PAGETABLE_ORDER - 1 - 1;
|
||||
+ u16 partial_flags:1;
|
||||
s16 linear_pt_count;
|
||||
};
|
||||
|
||||
--
|
||||
2.23.0
|
||||
|
||||
|
|
@ -1,106 +0,0 @@
|
|||
From da3d1d258e54fe600f7f75287183b74d957ec63b Mon Sep 17 00:00:00 2001
|
||||
From: George Dunlap <george.dunlap@citrix.com>
|
||||
Date: Thu, 10 Oct 2019 17:57:49 +0100
|
||||
Subject: [PATCH 09/11] x86/mm: Properly handle linear pagetable promotion
|
||||
failures
|
||||
|
||||
In order to allow recursive pagetable promotions and demotions to be
|
||||
interrupted, Xen must keep track of the state of the sub-pages
|
||||
promoted or demoted. This is stored in two elements in the page
|
||||
struct: nr_entries_validated and partial_flags.
|
||||
|
||||
The rule is that entries [0, nr_entries_validated) should always be
|
||||
validated and hold a general reference count. If partial_flags is
|
||||
zero, then [nr_entries_validated] is not validated and no reference
|
||||
count is held. If PTF_partial_set is set, then [nr_entries_validated]
|
||||
is partially validated, and a general reference count is held.
|
||||
|
||||
Unfortunately, in cases where an entry began with PTF_partial_set set,
|
||||
and get_page_from_lNe() returns -EINVAL, the PTF_partial_set bit is
|
||||
erroneously dropped. (This scenario can be engineered mainly by the
|
||||
use of interleaving of promoting and demoting a page which has "linear
|
||||
pagetable" entries; see the appendix for a sketch.) This means that
|
||||
we will "leak" a general reference count on the page in question,
|
||||
preventing the page from being freed.
|
||||
|
||||
Fix this by setting page->partial_flags to the partial_flags local
|
||||
variable.
|
||||
|
||||
This is part of XSA-299.
|
||||
|
||||
Reported-by: George Dunlap <george.dunlap@citrix.com>
|
||||
Signed-off-by: George Dunlap <george.dunlap@citrix.com>
|
||||
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||||
-----
|
||||
Appendix
|
||||
|
||||
Suppose A and B can both be promoted to L2 pages, and A[x] points to B.
|
||||
|
||||
V1: PIN_L2 B.
|
||||
B.type_count = 1 | PGT_validated
|
||||
B.count = 2 | PGC_allocated
|
||||
|
||||
V1: MOD_L3_ENTRY pointing something to A.
|
||||
In the process of validating A[x], grab an extra type / ref on B:
|
||||
B.type_count = 2 | PGT_validated
|
||||
B.count = 3 | PGC_allocated
|
||||
A.type_count = 1 | PGT_validated
|
||||
A.count = 2 | PGC_allocated
|
||||
|
||||
V1: UNPIN B.
|
||||
B.type_count = 1 | PGT_validate
|
||||
B.count = 2 | PGC_allocated
|
||||
|
||||
V1: MOD_L3_ENTRY removing the reference to A.
|
||||
De-validate A, down to A[x], which points to B.
|
||||
Drop the final type on B. Arrange to be interrupted.
|
||||
B.type_count = 1 | PGT_partial
|
||||
B.count = 2 | PGC_allocated
|
||||
A.type_count = 1 | PGT_partial
|
||||
A.nr_validated_entries = x
|
||||
A.partial_pte = -1
|
||||
|
||||
V2: MOD_L3_ENTRY adds a reference to A.
|
||||
|
||||
At this point, get_page_from_l2e(A[x]) tries
|
||||
get_page_and_type_from_mfn(), which fails because it's the wrong type;
|
||||
and get_l2_linear_pagetable() also fails, because B isn't validated as
|
||||
an l2 anymore.
|
||||
---
|
||||
xen/arch/x86/mm.c | 6 +++---
|
||||
1 file changed, 3 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/xen/arch/x86/mm.c b/xen/arch/x86/mm.c
|
||||
index 886e93b8aa..0a094291da 100644
|
||||
--- a/xen/arch/x86/mm.c
|
||||
+++ b/xen/arch/x86/mm.c
|
||||
@@ -1581,7 +1581,7 @@ static int alloc_l2_table(struct page_info *page, unsigned long type)
|
||||
if ( i )
|
||||
{
|
||||
page->nr_validated_ptes = i;
|
||||
- page->partial_flags = 0;
|
||||
+ page->partial_flags = partial_flags;
|
||||
current->arch.old_guest_ptpg = NULL;
|
||||
current->arch.old_guest_table = page;
|
||||
}
|
||||
@@ -1674,7 +1674,7 @@ static int alloc_l3_table(struct page_info *page)
|
||||
if ( i )
|
||||
{
|
||||
page->nr_validated_ptes = i;
|
||||
- page->partial_flags = 0;
|
||||
+ page->partial_flags = partial_flags;
|
||||
current->arch.old_guest_ptpg = NULL;
|
||||
current->arch.old_guest_table = page;
|
||||
}
|
||||
@@ -1845,7 +1845,7 @@ static int alloc_l4_table(struct page_info *page)
|
||||
if ( i )
|
||||
{
|
||||
page->nr_validated_ptes = i;
|
||||
- page->partial_flags = 0;
|
||||
+ page->partial_flags = partial_flags;
|
||||
if ( rc == -EINTR )
|
||||
rc = -ERESTART;
|
||||
else
|
||||
--
|
||||
2.23.0
|
||||
|
||||
|
|
@ -1,166 +0,0 @@
|
|||
From b3e169dc8daeae85b0b51c25fdb142e2e552ec7f Mon Sep 17 00:00:00 2001
|
||||
From: George Dunlap <george.dunlap@citrix.com>
|
||||
Date: Thu, 10 Oct 2019 17:57:49 +0100
|
||||
Subject: [PATCH 10/11] x86/mm: Fix nested de-validation on error
|
||||
|
||||
If an invalid entry is discovered when validating a page-table tree,
|
||||
the entire tree which has so far been validated must be de-validated.
|
||||
Since this may take a long time, alloc_l[2-4]_table() set current
|
||||
vcpu's old_guest_table immediately; put_old_guest_table() will make
|
||||
sure that put_page_type() will be called to finish off the
|
||||
de-validation before any other MMU operations can happen on the vcpu.
|
||||
|
||||
The invariant for partial pages should be:
|
||||
|
||||
* Entries [0, nr_validated_ptes) should be completely validated;
|
||||
put_page_type() will de-validate these.
|
||||
|
||||
* If [nr_validated_ptes] is partially validated, partial_flags should
|
||||
set PTF_partiaL_set. put_page_type() will be called on this page to
|
||||
finish off devalidation, and the appropriate refcount adjustments
|
||||
will be done.
|
||||
|
||||
alloc_l[2-3]_table() indicates partial validation to its callers by
|
||||
setting current->old_guest_table.
|
||||
|
||||
Unfortunately, this is mishandled.
|
||||
|
||||
Take the case where validating lNe[x] returns an error.
|
||||
|
||||
First, alloc_l3_table() doesn't check old_guest_table at all; as a
|
||||
result, partial_flags is not set when it should be. nr_validated_ptes
|
||||
is set to x; and since PFT_partial_set clear, de-validation resumes at
|
||||
nr_validated_ptes-1. This means that the l2 page at pl3e[x] will not
|
||||
have put_page_type() called on it when de-validating the rest of the
|
||||
l3: it will be stuck in the PGT_partial state until the domain is
|
||||
destroyed, or until it is re-used as an l2. (Any other page type will
|
||||
fail.)
|
||||
|
||||
Worse, alloc_l4_table(), rather than setting PTF_partial_set as it
|
||||
should, sets nr_validated_ptes to x+1. When de-validating, since
|
||||
partial is 0, this will correctly resume calling put_page_type at [x];
|
||||
but, if the put_page_type() is never called, but instead
|
||||
get_page_type() is called, validation will pick up at [x+1],
|
||||
neglecting to validate [x]. If the rest of the validation succeeds,
|
||||
the l4 will be validated even though [x] is invalid.
|
||||
|
||||
Fix this in both cases by setting PTF_partial_set if old_guest_table
|
||||
is set.
|
||||
|
||||
While here, add some safety catches:
|
||||
- old_guest_table must point to the page contained in
|
||||
[nr_validated_ptes].
|
||||
- alloc_l1_page shouldn't set old_guest_table
|
||||
|
||||
If we experience one of these situations in production builds, it's
|
||||
safer to avoid calling put_page_type for the pages in question. If
|
||||
they have PGT_partial set, they will be cleaned up on domain
|
||||
destruction; if not, we have no idea whether a type count is safe to
|
||||
drop. Retaining an extra type ref that should have been dropped may
|
||||
trigger a BUG() on the free_domain_page() path, but dropping a type
|
||||
count that shouldn't be dropped may cause a privilege escalation.
|
||||
|
||||
This is part of XSA-299.
|
||||
|
||||
Reported-by: George Dunlap <george.dunlap@citrix.com>
|
||||
Signed-off-by: George Dunlap <george.dunlap@citrix.com>
|
||||
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||||
---
|
||||
xen/arch/x86/mm.c | 53 +++++++++++++++++++++++++++++++++++++++++++++--
|
||||
1 file changed, 51 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/xen/arch/x86/mm.c b/xen/arch/x86/mm.c
|
||||
index 0a094291da..a432e69c74 100644
|
||||
--- a/xen/arch/x86/mm.c
|
||||
+++ b/xen/arch/x86/mm.c
|
||||
@@ -1580,6 +1580,20 @@ static int alloc_l2_table(struct page_info *page, unsigned long type)
|
||||
ASSERT(current->arch.old_guest_table == NULL);
|
||||
if ( i )
|
||||
{
|
||||
+ /*
|
||||
+ * alloc_l1_table() doesn't set old_guest_table; it does
|
||||
+ * its own tear-down immediately on failure. If it
|
||||
+ * did we'd need to check it and set partial_flags as we
|
||||
+ * do in alloc_l[34]_table().
|
||||
+ *
|
||||
+ * Note on the use of ASSERT: if it's non-null and
|
||||
+ * hasn't been cleaned up yet, it should have
|
||||
+ * PGT_partial set; and so the type will be cleaned up
|
||||
+ * on domain destruction. Unfortunately, we would
|
||||
+ * leak the general ref held by old_guest_table; but
|
||||
+ * leaking a page is less bad than a host crash.
|
||||
+ */
|
||||
+ ASSERT(current->arch.old_guest_table == NULL);
|
||||
page->nr_validated_ptes = i;
|
||||
page->partial_flags = partial_flags;
|
||||
current->arch.old_guest_ptpg = NULL;
|
||||
@@ -1607,6 +1621,7 @@ static int alloc_l3_table(struct page_info *page)
|
||||
unsigned int i;
|
||||
int rc = 0;
|
||||
unsigned int partial_flags = page->partial_flags;
|
||||
+ l3_pgentry_t l3e = l3e_empty();
|
||||
|
||||
pl3e = map_domain_page(_mfn(pfn));
|
||||
|
||||
@@ -1623,7 +1638,7 @@ static int alloc_l3_table(struct page_info *page)
|
||||
for ( i = page->nr_validated_ptes; i < L3_PAGETABLE_ENTRIES;
|
||||
i++, partial_flags = 0 )
|
||||
{
|
||||
- l3_pgentry_t l3e = pl3e[i];
|
||||
+ l3e = pl3e[i];
|
||||
|
||||
if ( i > page->nr_validated_ptes && hypercall_preempt_check() )
|
||||
rc = -EINTR;
|
||||
@@ -1675,6 +1690,24 @@ static int alloc_l3_table(struct page_info *page)
|
||||
{
|
||||
page->nr_validated_ptes = i;
|
||||
page->partial_flags = partial_flags;
|
||||
+ if ( current->arch.old_guest_table )
|
||||
+ {
|
||||
+ /*
|
||||
+ * We've experienced a validation failure. If
|
||||
+ * old_guest_table is set, "transfer" the general
|
||||
+ * reference count to pl3e[nr_validated_ptes] by
|
||||
+ * setting PTF_partial_set.
|
||||
+ *
|
||||
+ * As a precaution, check that old_guest_table is the
|
||||
+ * page pointed to by pl3e[nr_validated_ptes]. If
|
||||
+ * not, it's safer to leak a type ref on production
|
||||
+ * builds.
|
||||
+ */
|
||||
+ if ( current->arch.old_guest_table == l3e_get_page(l3e) )
|
||||
+ page->partial_flags = PTF_partial_set;
|
||||
+ else
|
||||
+ ASSERT_UNREACHABLE();
|
||||
+ }
|
||||
current->arch.old_guest_ptpg = NULL;
|
||||
current->arch.old_guest_table = page;
|
||||
}
|
||||
@@ -1851,7 +1884,23 @@ static int alloc_l4_table(struct page_info *page)
|
||||
else
|
||||
{
|
||||
if ( current->arch.old_guest_table )
|
||||
- page->nr_validated_ptes++;
|
||||
+ {
|
||||
+ /*
|
||||
+ * We've experienced a validation failure. If
|
||||
+ * old_guest_table is set, "transfer" the general
|
||||
+ * reference count to pl3e[nr_validated_ptes] by
|
||||
+ * setting PTF_partial_set.
|
||||
+ *
|
||||
+ * As a precaution, check that old_guest_table is the
|
||||
+ * page pointed to by pl4e[nr_validated_ptes]. If
|
||||
+ * not, it's safer to leak a type ref on production
|
||||
+ * builds.
|
||||
+ */
|
||||
+ if ( current->arch.old_guest_table == l4e_get_page(l4e) )
|
||||
+ page->partial_flags = PTF_partial_set;
|
||||
+ else
|
||||
+ ASSERT_UNREACHABLE();
|
||||
+ }
|
||||
current->arch.old_guest_ptpg = NULL;
|
||||
current->arch.old_guest_table = page;
|
||||
}
|
||||
--
|
||||
2.23.0
|
||||
|
||||
|
|
@ -1,413 +0,0 @@
|
|||
From ea3dc624c5e6325a9c2f079e52a85965d4ab6ce8 Mon Sep 17 00:00:00 2001
|
||||
From: George Dunlap <george.dunlap@citrix.com>
|
||||
Date: Thu, 10 Oct 2019 17:57:50 +0100
|
||||
Subject: [PATCH 11/11] x86/mm: Don't drop a type ref unless you held a ref to
|
||||
begin with
|
||||
|
||||
Validation and de-validation of pagetable trees may take arbitrarily
|
||||
large amounts of time, and so must be preemptible. This is indicated
|
||||
by setting the PGT_partial bit in the type_info, and setting
|
||||
nr_validated_entries and partial_flags appropriately. Specifically,
|
||||
if the entry at [nr_validated_entries] is partially validated,
|
||||
partial_flags should have the PGT_partial_set bit set, and the entry
|
||||
should hold a general reference count. During de-validation,
|
||||
put_page_type() is called on partially validated entries.
|
||||
|
||||
Unfortunately, there are a number of issues with the current algorithm.
|
||||
|
||||
First, doing a "normal" put_page_type() is not safe when no type ref
|
||||
is held: there is nothing to stop another vcpu from coming along and
|
||||
picking up validation again: at which point the put_page_type may drop
|
||||
the only page ref on an in-use page. Some examples are listed in the
|
||||
appendix.
|
||||
|
||||
The core issue is that put_page_type() is being called both to clean
|
||||
up PGT_partial, and to drop a type count; and has no way of knowing
|
||||
which is which; and so if in between, PGT_partial is cleared,
|
||||
put_page_type() will drop the type ref erroneously.
|
||||
|
||||
What is needed is to distinguish between two states:
|
||||
- Dropping a type ref which is held
|
||||
- Cleaning up a page which has been partially de/validated
|
||||
|
||||
Fix this by telling put_page_type() which of the two activities you
|
||||
intend.
|
||||
|
||||
When cleaning up a partial de/validation, take no action unless you
|
||||
find a page partially validated.
|
||||
|
||||
If put_page_type() is called without PTF_partial_set, and finds the
|
||||
page in a PGT_partial state anyway, then there's certainly been a
|
||||
misaccounting somewhere, and carrying on would almost certainly cause
|
||||
a security issue, so crash the host instead.
|
||||
|
||||
In put_page_from_lNe, pass partial_flags on to _put_page_type().
|
||||
|
||||
old_guest_table may be set either with a fully validated page (when
|
||||
using the "deferred put" pattern), or with a partially validated page
|
||||
(when a normal "de-validation" is interrupted, or when a validation
|
||||
fails part-way through due to invalid entries). Add a flag,
|
||||
old_guest_table_partial, to indicate which of these it is, and use
|
||||
that to pass the appropriate flag to _put_page_type().
|
||||
|
||||
While here, delete stray trailing whitespace.
|
||||
|
||||
This is part of XSA-299.
|
||||
|
||||
Reported-by: George Dunlap <george.dunlap@citrix.com>
|
||||
Signed-off-by: George Dunlap <george.dunlap@citrix.com>
|
||||
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||||
-----
|
||||
Appendix:
|
||||
|
||||
Suppose page A, when interpreted as an l3 pagetable, contains all
|
||||
valid entries; and suppose A[x] points to page B, which when
|
||||
interpreted as an l2 pagetable, contains all valid entries.
|
||||
|
||||
P1: PIN_L3_TABLE
|
||||
A -> PGT_l3_table | 1 | valid
|
||||
B -> PGT_l2_table | 1 | valid
|
||||
|
||||
P1: UNPIN_TABLE
|
||||
> Arrange to interrupt after B has been de-validated
|
||||
B:
|
||||
type_info -> PGT_l2_table | 0
|
||||
A:
|
||||
type_info -> PGT_l3_table | 1 | partial
|
||||
nr_validated_enties -> (less than x)
|
||||
|
||||
P2: mod_l4_entry to point to A
|
||||
> Arrange for this to be interrupted while B is being validated
|
||||
B:
|
||||
type_info -> PGT_l2_table | 1 | partial
|
||||
(nr_validated_entires &c set as appropriate)
|
||||
A:
|
||||
type_info -> PGT_l3_table | 1 | partial
|
||||
nr_validated_entries -> x
|
||||
partial_pte = 1
|
||||
|
||||
P3: mod_l3_entry some other unrelated l3 to point to B:
|
||||
B:
|
||||
type_info -> PGT_l2_table | 1
|
||||
|
||||
P1: Restart UNPIN_TABLE
|
||||
|
||||
At this point, since A.nr_validate_entries == x and A.partial_pte !=
|
||||
0, free_l3_table() will call put_page_from_l3e() on pl3e[x], dropping
|
||||
its type count to 0 while it's still being pointed to by some other l3
|
||||
|
||||
A similar issue arises with old_guest_table. Consider the following
|
||||
scenario:
|
||||
|
||||
Suppose A is a page which, when interpreted as an l2, has valid entries
|
||||
until entry x, which is invalid.
|
||||
|
||||
V1: PIN_L2_TABLE(A)
|
||||
<Validate until we try to validate [x], get -EINVAL>
|
||||
A -> PGT_l2_table | 1 | PGT_partial
|
||||
V1 -> old_guest_table = A
|
||||
<delayed>
|
||||
|
||||
V2: PIN_L2_TABLE(A)
|
||||
<Pick up where V1 left off, try to re-validate [x], get -EINVAL>
|
||||
A -> PGT_l2_table | 1 | PGT_partial
|
||||
V2 -> old_guest_table = A
|
||||
<restart>
|
||||
put_old_guest_table()
|
||||
_put_page_type(A)
|
||||
A -> PGT_l2_table | 0
|
||||
|
||||
V1: <restart>
|
||||
put_old_guest_table()
|
||||
_put_page_type(A) # UNDERFLOW
|
||||
|
||||
Indeed, it is possible to engineer for old_guest_table for every vcpu
|
||||
a guest has to point to the same page.
|
||||
---
|
||||
xen/arch/x86/domain.c | 6 +++
|
||||
xen/arch/x86/mm.c | 99 +++++++++++++++++++++++++++++++-----
|
||||
xen/include/asm-x86/domain.h | 4 +-
|
||||
3 files changed, 95 insertions(+), 14 deletions(-)
|
||||
|
||||
diff --git a/xen/arch/x86/domain.c b/xen/arch/x86/domain.c
|
||||
index 59df8a6d8d..f1ae5f89f5 100644
|
||||
--- a/xen/arch/x86/domain.c
|
||||
+++ b/xen/arch/x86/domain.c
|
||||
@@ -1104,9 +1104,15 @@ int arch_set_info_guest(
|
||||
rc = -ERESTART;
|
||||
/* Fallthrough */
|
||||
case -ERESTART:
|
||||
+ /*
|
||||
+ * NB that we're putting the kernel-mode table
|
||||
+ * here, which we've already successfully
|
||||
+ * validated above; hence partial = false;
|
||||
+ */
|
||||
v->arch.old_guest_ptpg = NULL;
|
||||
v->arch.old_guest_table =
|
||||
pagetable_get_page(v->arch.guest_table);
|
||||
+ v->arch.old_guest_table_partial = false;
|
||||
v->arch.guest_table = pagetable_null();
|
||||
break;
|
||||
default:
|
||||
diff --git a/xen/arch/x86/mm.c b/xen/arch/x86/mm.c
|
||||
index a432e69c74..81774368a0 100644
|
||||
--- a/xen/arch/x86/mm.c
|
||||
+++ b/xen/arch/x86/mm.c
|
||||
@@ -1359,10 +1359,11 @@ static int put_page_from_l2e(l2_pgentry_t l2e, unsigned long pfn,
|
||||
{
|
||||
current->arch.old_guest_ptpg = ptpg;
|
||||
current->arch.old_guest_table = pg;
|
||||
+ current->arch.old_guest_table_partial = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
- rc = _put_page_type(pg, PTF_preemptible, ptpg);
|
||||
+ rc = _put_page_type(pg, flags | PTF_preemptible, ptpg);
|
||||
if ( likely(!rc) )
|
||||
put_page(pg);
|
||||
}
|
||||
@@ -1385,6 +1386,7 @@ static int put_page_from_l3e(l3_pgentry_t l3e, unsigned long pfn,
|
||||
unsigned long mfn = l3e_get_pfn(l3e);
|
||||
bool writeable = l3e_get_flags(l3e) & _PAGE_RW;
|
||||
|
||||
+ ASSERT(!(flags & PTF_partial_set));
|
||||
ASSERT(!(mfn & ((1UL << (L3_PAGETABLE_SHIFT - PAGE_SHIFT)) - 1)));
|
||||
do {
|
||||
put_data_page(mfn_to_page(_mfn(mfn)), writeable);
|
||||
@@ -1397,12 +1399,14 @@ static int put_page_from_l3e(l3_pgentry_t l3e, unsigned long pfn,
|
||||
|
||||
if ( flags & PTF_defer )
|
||||
{
|
||||
+ ASSERT(!(flags & PTF_partial_set));
|
||||
current->arch.old_guest_ptpg = mfn_to_page(_mfn(pfn));
|
||||
current->arch.old_guest_table = pg;
|
||||
+ current->arch.old_guest_table_partial = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
- rc = _put_page_type(pg, PTF_preemptible, mfn_to_page(_mfn(pfn)));
|
||||
+ rc = _put_page_type(pg, flags | PTF_preemptible, mfn_to_page(_mfn(pfn)));
|
||||
if ( likely(!rc) )
|
||||
put_page(pg);
|
||||
|
||||
@@ -1421,12 +1425,15 @@ static int put_page_from_l4e(l4_pgentry_t l4e, unsigned long pfn,
|
||||
|
||||
if ( flags & PTF_defer )
|
||||
{
|
||||
+ ASSERT(!(flags & PTF_partial_set));
|
||||
current->arch.old_guest_ptpg = mfn_to_page(_mfn(pfn));
|
||||
current->arch.old_guest_table = pg;
|
||||
+ current->arch.old_guest_table_partial = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
- rc = _put_page_type(pg, PTF_preemptible, mfn_to_page(_mfn(pfn)));
|
||||
+ rc = _put_page_type(pg, flags | PTF_preemptible,
|
||||
+ mfn_to_page(_mfn(pfn)));
|
||||
if ( likely(!rc) )
|
||||
put_page(pg);
|
||||
}
|
||||
@@ -1535,6 +1542,14 @@ static int alloc_l2_table(struct page_info *page, unsigned long type)
|
||||
|
||||
pl2e = map_domain_page(_mfn(pfn));
|
||||
|
||||
+ /*
|
||||
+ * NB that alloc_l2_table will never set partial_pte on an l2; but
|
||||
+ * free_l2_table might if a linear_pagetable entry is interrupted
|
||||
+ * partway through de-validation. In that circumstance,
|
||||
+ * get_page_from_l2e() will always return -EINVAL; and we must
|
||||
+ * retain the type ref by doing the normal partial_flags tracking.
|
||||
+ */
|
||||
+
|
||||
for ( i = page->nr_validated_ptes; i < L2_PAGETABLE_ENTRIES;
|
||||
i++, partial_flags = 0 )
|
||||
{
|
||||
@@ -1598,6 +1613,7 @@ static int alloc_l2_table(struct page_info *page, unsigned long type)
|
||||
page->partial_flags = partial_flags;
|
||||
current->arch.old_guest_ptpg = NULL;
|
||||
current->arch.old_guest_table = page;
|
||||
+ current->arch.old_guest_table_partial = true;
|
||||
}
|
||||
}
|
||||
if ( rc < 0 )
|
||||
@@ -1704,12 +1720,16 @@ static int alloc_l3_table(struct page_info *page)
|
||||
* builds.
|
||||
*/
|
||||
if ( current->arch.old_guest_table == l3e_get_page(l3e) )
|
||||
+ {
|
||||
+ ASSERT(current->arch.old_guest_table_partial);
|
||||
page->partial_flags = PTF_partial_set;
|
||||
+ }
|
||||
else
|
||||
ASSERT_UNREACHABLE();
|
||||
}
|
||||
current->arch.old_guest_ptpg = NULL;
|
||||
current->arch.old_guest_table = page;
|
||||
+ current->arch.old_guest_table_partial = true;
|
||||
}
|
||||
while ( i-- > 0 )
|
||||
pl3e[i] = unadjust_guest_l3e(pl3e[i], d);
|
||||
@@ -1897,12 +1917,16 @@ static int alloc_l4_table(struct page_info *page)
|
||||
* builds.
|
||||
*/
|
||||
if ( current->arch.old_guest_table == l4e_get_page(l4e) )
|
||||
+ {
|
||||
+ ASSERT(current->arch.old_guest_table_partial);
|
||||
page->partial_flags = PTF_partial_set;
|
||||
+ }
|
||||
else
|
||||
ASSERT_UNREACHABLE();
|
||||
}
|
||||
current->arch.old_guest_ptpg = NULL;
|
||||
current->arch.old_guest_table = page;
|
||||
+ current->arch.old_guest_table_partial = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2831,6 +2855,28 @@ static int _put_page_type(struct page_info *page, unsigned int flags,
|
||||
x = y;
|
||||
nx = x - 1;
|
||||
|
||||
+ /*
|
||||
+ * Is this expected to do a full reference drop, or only
|
||||
+ * cleanup partial validation / devalidation?
|
||||
+ *
|
||||
+ * If the former, the caller must hold a "full" type ref;
|
||||
+ * which means the page must be validated. If the page is
|
||||
+ * *not* fully validated, continuing would almost certainly
|
||||
+ * open up a security hole. An exception to this is during
|
||||
+ * domain destruction, where PGT_validated can be dropped
|
||||
+ * without dropping a type ref.
|
||||
+ *
|
||||
+ * If the latter, do nothing unless type PGT_partial is set.
|
||||
+ * If it is set, the type count must be 1.
|
||||
+ */
|
||||
+ if ( !(flags & PTF_partial_set) )
|
||||
+ BUG_ON((x & PGT_partial) ||
|
||||
+ !((x & PGT_validated) || page_get_owner(page)->is_dying));
|
||||
+ else if ( !(x & PGT_partial) )
|
||||
+ return 0;
|
||||
+ else
|
||||
+ BUG_ON((x & PGT_count_mask) != 1);
|
||||
+
|
||||
ASSERT((x & PGT_count_mask) != 0);
|
||||
|
||||
switch ( nx & (PGT_locked | PGT_count_mask) )
|
||||
@@ -3092,17 +3138,34 @@ int put_old_guest_table(struct vcpu *v)
|
||||
if ( !v->arch.old_guest_table )
|
||||
return 0;
|
||||
|
||||
- switch ( rc = _put_page_type(v->arch.old_guest_table, PTF_preemptible,
|
||||
- v->arch.old_guest_ptpg) )
|
||||
+ rc = _put_page_type(v->arch.old_guest_table,
|
||||
+ PTF_preemptible |
|
||||
+ ( v->arch.old_guest_table_partial ?
|
||||
+ PTF_partial_set : 0 ),
|
||||
+ v->arch.old_guest_ptpg);
|
||||
+
|
||||
+ if ( rc == -ERESTART || rc == -EINTR )
|
||||
{
|
||||
- case -EINTR:
|
||||
- case -ERESTART:
|
||||
+ v->arch.old_guest_table_partial = (rc == -ERESTART);
|
||||
return -ERESTART;
|
||||
- case 0:
|
||||
- put_page(v->arch.old_guest_table);
|
||||
}
|
||||
|
||||
+ /*
|
||||
+ * It shouldn't be possible for _put_page_type() to return
|
||||
+ * anything else at the moment; but if it does happen in
|
||||
+ * production, leaking the type ref is probably the best thing to
|
||||
+ * do. Either way, drop the general ref held by old_guest_table.
|
||||
+ */
|
||||
+ ASSERT(rc == 0);
|
||||
+
|
||||
+ put_page(v->arch.old_guest_table);
|
||||
v->arch.old_guest_table = NULL;
|
||||
+ v->arch.old_guest_ptpg = NULL;
|
||||
+ /*
|
||||
+ * Safest default if someone sets old_guest_table without
|
||||
+ * explicitly setting old_guest_table_partial.
|
||||
+ */
|
||||
+ v->arch.old_guest_table_partial = true;
|
||||
|
||||
return rc;
|
||||
}
|
||||
@@ -3253,11 +3316,11 @@ int new_guest_cr3(mfn_t mfn)
|
||||
switch ( rc = put_page_and_type_preemptible(page) )
|
||||
{
|
||||
case -EINTR:
|
||||
- rc = -ERESTART;
|
||||
- /* fallthrough */
|
||||
case -ERESTART:
|
||||
curr->arch.old_guest_ptpg = NULL;
|
||||
curr->arch.old_guest_table = page;
|
||||
+ curr->arch.old_guest_table_partial = (rc == -ERESTART);
|
||||
+ rc = -ERESTART;
|
||||
break;
|
||||
default:
|
||||
BUG_ON(rc);
|
||||
@@ -3494,6 +3557,7 @@ long do_mmuext_op(
|
||||
{
|
||||
curr->arch.old_guest_ptpg = NULL;
|
||||
curr->arch.old_guest_table = page;
|
||||
+ curr->arch.old_guest_table_partial = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3528,6 +3592,11 @@ long do_mmuext_op(
|
||||
case -ERESTART:
|
||||
curr->arch.old_guest_ptpg = NULL;
|
||||
curr->arch.old_guest_table = page;
|
||||
+ /*
|
||||
+ * EINTR means we still hold the type ref; ERESTART
|
||||
+ * means PGT_partial holds the type ref
|
||||
+ */
|
||||
+ curr->arch.old_guest_table_partial = (rc == -ERESTART);
|
||||
rc = 0;
|
||||
break;
|
||||
default:
|
||||
@@ -3596,11 +3665,15 @@ long do_mmuext_op(
|
||||
switch ( rc = put_page_and_type_preemptible(page) )
|
||||
{
|
||||
case -EINTR:
|
||||
- rc = -ERESTART;
|
||||
- /* fallthrough */
|
||||
case -ERESTART:
|
||||
curr->arch.old_guest_ptpg = NULL;
|
||||
curr->arch.old_guest_table = page;
|
||||
+ /*
|
||||
+ * EINTR means we still hold the type ref;
|
||||
+ * ERESTART means PGT_partial holds the ref
|
||||
+ */
|
||||
+ curr->arch.old_guest_table_partial = (rc == -ERESTART);
|
||||
+ rc = -ERESTART;
|
||||
break;
|
||||
default:
|
||||
BUG_ON(rc);
|
||||
diff --git a/xen/include/asm-x86/domain.h b/xen/include/asm-x86/domain.h
|
||||
index 214e44ce1c..2cfce7b36b 100644
|
||||
--- a/xen/include/asm-x86/domain.h
|
||||
+++ b/xen/include/asm-x86/domain.h
|
||||
@@ -307,7 +307,7 @@ struct arch_domain
|
||||
|
||||
struct paging_domain paging;
|
||||
struct p2m_domain *p2m;
|
||||
- /* To enforce lock ordering in the pod code wrt the
|
||||
+ /* To enforce lock ordering in the pod code wrt the
|
||||
* page_alloc lock */
|
||||
int page_alloc_unlock_level;
|
||||
|
||||
@@ -581,6 +581,8 @@ struct arch_vcpu
|
||||
struct page_info *old_guest_table; /* partially destructed pagetable */
|
||||
struct page_info *old_guest_ptpg; /* containing page table of the */
|
||||
/* former, if any */
|
||||
+ bool old_guest_table_partial; /* Are we dropping a type ref, or just
|
||||
+ * finishing up a partial de-validation? */
|
||||
/* guest_table holds a ref to the page, and also a type-count unless
|
||||
* shadow refcounts are in use */
|
||||
pagetable_t shadow_table[4]; /* (MFN) shadow(s) of guest */
|
||||
--
|
||||
2.23.0
|
||||
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
From 19d6330f142cb941b6340a88592e8a294de0ff8c Mon Sep 17 00:00:00 2001
|
||||
From: Julien Grall <julien.grall@arm.com>
|
||||
Date: Tue, 15 Oct 2019 17:10:40 +0100
|
||||
Subject: [PATCH 1/3] xen/arm: p2m: Avoid aliasing guest physical frame
|
||||
|
||||
The P2M helpers implementation is quite lax and will end up to ignore
|
||||
the unused top bits of a guest physical frame.
|
||||
|
||||
This effectively means that p2m_set_entry() will create a mapping for a
|
||||
different frame (it is always equal to gfn & (mask unused bits)). Yet
|
||||
p2m->max_mapped_gfn will be updated using the original frame.
|
||||
|
||||
At the moment, p2m_get_entry() and p2m_resolve_translation_fault()
|
||||
assume that p2m_get_root_pointer() will always return a non-NULL pointer
|
||||
when the GFN is smaller than p2m->max_mapped_gfn.
|
||||
|
||||
Unfortunately, because of the aliasing described above, it would be
|
||||
possible to set p2m->max_mapped_gfn high enough so it covers frame that
|
||||
would lead p2m_get_root_pointer() to return NULL.
|
||||
|
||||
As we don't sanity check the guest physical frame provided by a guest, a
|
||||
malicious guest could craft a series of hypercalls that will hit the
|
||||
BUG_ON() and therefore DoS Xen.
|
||||
|
||||
To prevent aliasing, the function p2m_get_root_pointer() is now reworked
|
||||
to return NULL If any of the unused top bits are not zero. The caller
|
||||
can then decide what's the appropriate action to do. Since the two paths
|
||||
(i.e. P2M_ROOT_PAGES == 1 and P2M_ROOT_PAGES != 1) are now very
|
||||
similarly, take the opportunity to consolidate them making the code a
|
||||
bit simpler.
|
||||
|
||||
With this change, p2m_get_entry() will not try to insert a mapping as
|
||||
the root pointer is invalid.
|
||||
|
||||
Note that root_table is now switch to unsigned long as unsigned int is
|
||||
not enough to hold part of a GFN.
|
||||
|
||||
This is part of XSA-301.
|
||||
|
||||
Reported-by: Julien Grall <Julien.Grall@arm.com>
|
||||
Signed-off-by: Julien Grall <julien.grall@arm.com>
|
||||
Reviewed-by: Stefano Stabellini <sstabellini@kernel.org>
|
||||
---
|
||||
xen/arch/arm/p2m.c | 17 +++++------------
|
||||
1 file changed, 5 insertions(+), 12 deletions(-)
|
||||
|
||||
diff --git a/xen/arch/arm/p2m.c b/xen/arch/arm/p2m.c
|
||||
index a2749d9b6f..d0045a8b28 100644
|
||||
--- a/xen/arch/arm/p2m.c
|
||||
+++ b/xen/arch/arm/p2m.c
|
||||
@@ -229,21 +229,14 @@ void p2m_tlb_flush_sync(struct p2m_domain *p2m)
|
||||
static lpae_t *p2m_get_root_pointer(struct p2m_domain *p2m,
|
||||
gfn_t gfn)
|
||||
{
|
||||
- unsigned int root_table;
|
||||
-
|
||||
- if ( P2M_ROOT_PAGES == 1 )
|
||||
- return __map_domain_page(p2m->root);
|
||||
+ unsigned long root_table;
|
||||
|
||||
/*
|
||||
- * Concatenated root-level tables. The table number will be the
|
||||
- * offset at the previous level. It is not possible to
|
||||
- * concatenate a level-0 root.
|
||||
+ * While the root table index is the offset from the previous level,
|
||||
+ * we can't use (P2M_ROOT_LEVEL - 1) because the root level might be
|
||||
+ * 0. Yet we still want to check if all the unused bits are zeroed.
|
||||
*/
|
||||
- ASSERT(P2M_ROOT_LEVEL > 0);
|
||||
-
|
||||
- root_table = gfn_x(gfn) >> (level_orders[P2M_ROOT_LEVEL - 1]);
|
||||
- root_table &= LPAE_ENTRY_MASK;
|
||||
-
|
||||
+ root_table = gfn_x(gfn) >> (level_orders[P2M_ROOT_LEVEL] + LPAE_SHIFT);
|
||||
if ( root_table >= P2M_ROOT_PAGES )
|
||||
return NULL;
|
||||
|
||||
--
|
||||
2.23.0
|
||||
|
||||
|
|
@ -1,92 +0,0 @@
|
|||
From 3b896936f7505e929dd869d14afcb185d0ee75f8 Mon Sep 17 00:00:00 2001
|
||||
From: Julien Grall <julien.grall@arm.com>
|
||||
Date: Tue, 15 Oct 2019 17:10:41 +0100
|
||||
Subject: [PATCH 2/3] xen/arm: p2m: Avoid off-by-one check on
|
||||
p2m->max_mapped_gfn
|
||||
|
||||
The code base is using inconsistently the field p2m->max_mapped_gfn.
|
||||
Some of the useres expect that p2m->max_guest_gfn contain the highest
|
||||
mapped GFN while others expect highest + 1.
|
||||
|
||||
p2m->max_guest_gfn is set as highest + 1, because of that the sanity
|
||||
check on the GFN in p2m_resolved_translation_fault() and
|
||||
p2m_get_entry() can be bypassed when GFN == p2m->max_guest_gfn.
|
||||
|
||||
p2m_get_root_pointer(p2m->max_guest_gfn) may return NULL if it is
|
||||
outside of address range supported and therefore the BUG_ON() could be
|
||||
hit.
|
||||
|
||||
The current value hold in p2m->max_mapped_gfn is inconsistent with the
|
||||
expectation of the common code (see domain_get_maximum_gpfn()) and also
|
||||
the documentation of the field.
|
||||
|
||||
Rather than changing the check in p2m_translation_fault() and
|
||||
p2m_get_entry(), p2m->max_mapped_gfn is now containing the highest
|
||||
mapped GFN and the callers assuming "highest + 1" are now adjusted.
|
||||
|
||||
Take the opportunity to use 1UL rather than 1 as page_order could
|
||||
theoritically big enough to overflow a 32-bit integer.
|
||||
|
||||
Lastly, the documentation of the field max_guest_gfn to reflect how it
|
||||
is computed.
|
||||
|
||||
This is part of XSA-301.
|
||||
|
||||
Reported-by: Julien Grall <Julien.Grall@arm.com>
|
||||
Signed-off-by: Julien Grall <julien.grall@arm.com>
|
||||
Reviewed-by: Stefano Stabellini <sstabellini@kernel.org>
|
||||
---
|
||||
xen/arch/arm/p2m.c | 6 +++---
|
||||
xen/include/asm-arm/p2m.h | 5 +----
|
||||
2 files changed, 4 insertions(+), 7 deletions(-)
|
||||
|
||||
diff --git a/xen/arch/arm/p2m.c b/xen/arch/arm/p2m.c
|
||||
index d0045a8b28..8d20d27961 100644
|
||||
--- a/xen/arch/arm/p2m.c
|
||||
+++ b/xen/arch/arm/p2m.c
|
||||
@@ -1041,7 +1041,7 @@ static int __p2m_set_entry(struct p2m_domain *p2m,
|
||||
p2m_write_pte(entry, pte, p2m->clean_pte);
|
||||
|
||||
p2m->max_mapped_gfn = gfn_max(p2m->max_mapped_gfn,
|
||||
- gfn_add(sgfn, 1 << page_order));
|
||||
+ gfn_add(sgfn, (1UL << page_order) - 1));
|
||||
p2m->lowest_mapped_gfn = gfn_min(p2m->lowest_mapped_gfn, sgfn);
|
||||
}
|
||||
|
||||
@@ -1572,7 +1572,7 @@ int relinquish_p2m_mapping(struct domain *d)
|
||||
p2m_write_lock(p2m);
|
||||
|
||||
start = p2m->lowest_mapped_gfn;
|
||||
- end = p2m->max_mapped_gfn;
|
||||
+ end = gfn_add(p2m->max_mapped_gfn, 1);
|
||||
|
||||
for ( ; gfn_x(start) < gfn_x(end);
|
||||
start = gfn_next_boundary(start, order) )
|
||||
@@ -1641,7 +1641,7 @@ int p2m_cache_flush_range(struct domain *d, gfn_t *pstart, gfn_t end)
|
||||
p2m_read_lock(p2m);
|
||||
|
||||
start = gfn_max(start, p2m->lowest_mapped_gfn);
|
||||
- end = gfn_min(end, p2m->max_mapped_gfn);
|
||||
+ end = gfn_min(end, gfn_add(p2m->max_mapped_gfn, 1));
|
||||
|
||||
next_block_gfn = start;
|
||||
|
||||
diff --git a/xen/include/asm-arm/p2m.h b/xen/include/asm-arm/p2m.h
|
||||
index 89f82df380..5fdb6e8183 100644
|
||||
--- a/xen/include/asm-arm/p2m.h
|
||||
+++ b/xen/include/asm-arm/p2m.h
|
||||
@@ -36,10 +36,7 @@ struct p2m_domain {
|
||||
/* Current Translation Table Base Register for the p2m */
|
||||
uint64_t vttbr;
|
||||
|
||||
- /*
|
||||
- * Highest guest frame that's ever been mapped in the p2m
|
||||
- * Only takes into account ram and foreign mapping
|
||||
- */
|
||||
+ /* Highest guest frame that's ever been mapped in the p2m */
|
||||
gfn_t max_mapped_gfn;
|
||||
|
||||
/*
|
||||
--
|
||||
2.23.0
|
||||
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
From 060c2dd3b7c2674a019d94afb2b4ebf3663f6c6e Mon Sep 17 00:00:00 2001
|
||||
From: Julien Grall <julien.grall@arm.com>
|
||||
Date: Tue, 15 Oct 2019 17:10:42 +0100
|
||||
Subject: [PATCH 3/3] xen/arm: p2m: Don't check the return of
|
||||
p2m_get_root_pointer() with BUG_ON()
|
||||
|
||||
It turns out that the BUG_ON() was actually reachable with well-crafted
|
||||
hypercalls. The BUG_ON() is here to prevent catch logical error, so
|
||||
crashing Xen is a bit over the top.
|
||||
|
||||
While all the holes should now be fixed, it would be better to downgrade
|
||||
the BUG_ON() to something less fatal to prevent any more DoS.
|
||||
|
||||
The BUG_ON() in p2m_get_entry() is now replaced by ASSERT_UNREACHABLE()
|
||||
to catch mistake in debug build and return INVALID_MFN for production
|
||||
build. The interface also requires to set page_order to give an idea of
|
||||
the size of "hole". So 'level' is now set so we report a hole of size of
|
||||
the an entry of the root page-table. This stays inline with what happen
|
||||
when the GFN is higher than p2m->max_mapped_gfn.
|
||||
|
||||
The BUG_ON() in p2m_resolve_translation_fault() is now replaced by
|
||||
ASSERT_UNREACHABLE() to catch mistake in debug build and just report a
|
||||
fault for producion build.
|
||||
|
||||
This is part of XSA-301.
|
||||
|
||||
Reported-by: Julien Grall <Julien.Grall@arm.com>
|
||||
Signed-off-by: Julien Grall <julien.grall@arm.com>
|
||||
Reviewed-by: Stefano Stabellini <sstabellini@kernel.org>
|
||||
---
|
||||
xen/arch/arm/p2m.c | 13 +++++++++++--
|
||||
1 file changed, 11 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/xen/arch/arm/p2m.c b/xen/arch/arm/p2m.c
|
||||
index 8d20d27961..ce59f2b503 100644
|
||||
--- a/xen/arch/arm/p2m.c
|
||||
+++ b/xen/arch/arm/p2m.c
|
||||
@@ -395,7 +395,12 @@ mfn_t p2m_get_entry(struct p2m_domain *p2m, gfn_t gfn,
|
||||
* the table should always be non-NULL because the gfn is below
|
||||
* p2m->max_mapped_gfn and the root table pages are always present.
|
||||
*/
|
||||
- BUG_ON(table == NULL);
|
||||
+ if ( !table )
|
||||
+ {
|
||||
+ ASSERT_UNREACHABLE();
|
||||
+ level = P2M_ROOT_LEVEL;
|
||||
+ goto out;
|
||||
+ }
|
||||
|
||||
for ( level = P2M_ROOT_LEVEL; level < 3; level++ )
|
||||
{
|
||||
@@ -1196,7 +1201,11 @@ bool p2m_resolve_translation_fault(struct domain *d, gfn_t gfn)
|
||||
* The table should always be non-NULL because the gfn is below
|
||||
* p2m->max_mapped_gfn and the root table pages are always present.
|
||||
*/
|
||||
- BUG_ON(table == NULL);
|
||||
+ if ( !table )
|
||||
+ {
|
||||
+ ASSERT_UNREACHABLE();
|
||||
+ goto out;
|
||||
+ }
|
||||
|
||||
/*
|
||||
* Go down the page-tables until an entry has the valid bit unset or
|
||||
--
|
||||
2.23.0
|
||||
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
From 0c9c0fbb356e3210cb77b3d738be50981b26058a Mon Sep 17 00:00:00 2001
|
||||
From: Jan Beulich <jbeulich@suse.com>
|
||||
Date: Wed, 2 Oct 2019 13:36:59 +0200
|
||||
Subject: [PATCH 1/2] IOMMU: add missing HVM check
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
Fix an unguarded d->arch.hvm access in assign_device().
|
||||
|
||||
Signed-off-by: Jan Beulich <jbeulich@suse.com>
|
||||
Reviewed-by: Roger Pau Monné <roger.pau@citrix.com>
|
||||
Acked-by: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
|
||||
(cherry picked from commit 41fd1009cd7416b73d745a77c24b4e8d1a296fe6)
|
||||
Signed-off-by: Ian Jackson <ian.jackson@eu.citrix.com>
|
||||
---
|
||||
xen/drivers/passthrough/pci.c | 3 ++-
|
||||
1 file changed, 2 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/xen/drivers/passthrough/pci.c b/xen/drivers/passthrough/pci.c
|
||||
index 8108ed5f9a..d7420bd8bf 100644
|
||||
--- a/xen/drivers/passthrough/pci.c
|
||||
+++ b/xen/drivers/passthrough/pci.c
|
||||
@@ -1452,7 +1452,8 @@ static int assign_device(struct domain *d, u16 seg, u8 bus, u8 devfn, u32 flag)
|
||||
|
||||
/* Prevent device assign if mem paging or mem sharing have been
|
||||
* enabled for this domain */
|
||||
- if ( unlikely(d->arch.hvm.mem_sharing_enabled ||
|
||||
+ if ( unlikely((is_hvm_domain(d) &&
|
||||
+ d->arch.hvm.mem_sharing_enabled) ||
|
||||
vm_event_check_ring(d->vm_event_paging) ||
|
||||
p2m_get_hostp2m(d)->global_logdirty) )
|
||||
return -EXDEV;
|
||||
--
|
||||
2.11.0
|
||||
|
||||
|
|
@ -1,499 +0,0 @@
|
|||
From 278d8e585a9f110a1af0bd92a9fc43733c9c7227 Mon Sep 17 00:00:00 2001
|
||||
From: Paul Durrant <paul.durrant@citrix.com>
|
||||
Date: Mon, 14 Oct 2019 17:52:59 +0100
|
||||
Subject: [PATCH 2/2] passthrough: quarantine PCI devices
|
||||
|
||||
When a PCI device is assigned to an untrusted domain, it is possible for
|
||||
that domain to program the device to DMA to an arbitrary address. The
|
||||
IOMMU is used to protect the host from malicious DMA by making sure that
|
||||
the device addresses can only target memory assigned to the guest. However,
|
||||
when the guest domain is torn down the device is assigned back to dom0,
|
||||
thus allowing any in-flight DMA to potentially target critical host data.
|
||||
|
||||
This patch introduces a 'quarantine' for PCI devices using dom_io. When
|
||||
the toolstack makes a device assignable (by binding it to pciback), it
|
||||
will now also assign it to DOMID_IO and the device will only be assigned
|
||||
back to dom0 when the device is made unassignable again. Whilst device is
|
||||
assignable it will only ever transfer between dom_io and guest domains.
|
||||
dom_io is actually only used as a sentinel domain for quarantining purposes;
|
||||
it is not configured with any IOMMU mappings. Assignment to dom_io simply
|
||||
means that the device's initiator (requestor) identifier is not present in
|
||||
the IOMMU's device table and thus any DMA transactions issued will be
|
||||
terminated with a fault condition.
|
||||
|
||||
In addition, a fix to assignment handling is made for VT-d. Failure
|
||||
during the assignment step should not lead to a device still being
|
||||
associated with its prior owner. Hand the device to DomIO temporarily,
|
||||
until the assignment step has completed successfully. Remove the PI
|
||||
hooks from the source domain then earlier as well.
|
||||
|
||||
Failure of the recovery reassign_device_ownership() may not go silent:
|
||||
There e.g. may still be left over RMRR mappings in the domain assignment
|
||||
to which has failed, and hence we can't allow that domain to continue
|
||||
executing.
|
||||
|
||||
NOTE: This patch also includes one printk() cleanup; the
|
||||
"XEN_DOMCTL_assign_device: " tag is dropped in iommu_do_pci_domctl(),
|
||||
since similar printk()-s elsewhere also don't log such a tag.
|
||||
|
||||
This is XSA-302.
|
||||
|
||||
Signed-off-by: Paul Durrant <paul.durrant@citrix.com>
|
||||
Signed-off-by: Jan Beulich <jbeulich@suse.com>
|
||||
Signed-off-by: Ian Jackson <ian.jackson@eu.citrix.com>
|
||||
(cherry picked from commit ec99857f59f7f06236f11ca8b0b2303e5e745cc4)
|
||||
---
|
||||
tools/libxl/libxl_pci.c | 25 +++++++++++-
|
||||
xen/arch/x86/mm.c | 2 +
|
||||
xen/common/domctl.c | 14 ++++++-
|
||||
xen/drivers/passthrough/amd/pci_amd_iommu.c | 10 ++++-
|
||||
xen/drivers/passthrough/iommu.c | 9 +++++
|
||||
xen/drivers/passthrough/pci.c | 59 ++++++++++++++++++++++-------
|
||||
xen/drivers/passthrough/vtd/iommu.c | 40 ++++++++++++++++---
|
||||
xen/include/xen/pci.h | 3 ++
|
||||
8 files changed, 138 insertions(+), 24 deletions(-)
|
||||
|
||||
diff --git a/tools/libxl/libxl_pci.c b/tools/libxl/libxl_pci.c
|
||||
index 88c324ea23..d6a23fb5f8 100644
|
||||
--- a/tools/libxl/libxl_pci.c
|
||||
+++ b/tools/libxl/libxl_pci.c
|
||||
@@ -754,6 +754,7 @@ static int libxl__device_pci_assignable_add(libxl__gc *gc,
|
||||
libxl_device_pci *pcidev,
|
||||
int rebind)
|
||||
{
|
||||
+ libxl_ctx *ctx = libxl__gc_owner(gc);
|
||||
unsigned dom, bus, dev, func;
|
||||
char *spath, *driver_path = NULL;
|
||||
int rc;
|
||||
@@ -779,7 +780,7 @@ static int libxl__device_pci_assignable_add(libxl__gc *gc,
|
||||
}
|
||||
if ( rc ) {
|
||||
LOG(WARN, PCI_BDF" already assigned to pciback", dom, bus, dev, func);
|
||||
- return 0;
|
||||
+ goto quarantine;
|
||||
}
|
||||
|
||||
/* Check to see if there's already a driver that we need to unbind from */
|
||||
@@ -810,6 +811,19 @@ static int libxl__device_pci_assignable_add(libxl__gc *gc,
|
||||
return ERROR_FAIL;
|
||||
}
|
||||
|
||||
+quarantine:
|
||||
+ /*
|
||||
+ * DOMID_IO is just a sentinel domain, without any actual mappings,
|
||||
+ * so always pass XEN_DOMCTL_DEV_RDM_RELAXED to avoid assignment being
|
||||
+ * unnecessarily denied.
|
||||
+ */
|
||||
+ rc = xc_assign_device(ctx->xch, DOMID_IO, pcidev_encode_bdf(pcidev),
|
||||
+ XEN_DOMCTL_DEV_RDM_RELAXED);
|
||||
+ if ( rc < 0 ) {
|
||||
+ LOG(ERROR, "failed to quarantine "PCI_BDF, dom, bus, dev, func);
|
||||
+ return ERROR_FAIL;
|
||||
+ }
|
||||
+
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -817,9 +831,18 @@ static int libxl__device_pci_assignable_remove(libxl__gc *gc,
|
||||
libxl_device_pci *pcidev,
|
||||
int rebind)
|
||||
{
|
||||
+ libxl_ctx *ctx = libxl__gc_owner(gc);
|
||||
int rc;
|
||||
char *driver_path;
|
||||
|
||||
+ /* De-quarantine */
|
||||
+ rc = xc_deassign_device(ctx->xch, DOMID_IO, pcidev_encode_bdf(pcidev));
|
||||
+ if ( rc < 0 ) {
|
||||
+ LOG(ERROR, "failed to de-quarantine "PCI_BDF, pcidev->domain, pcidev->bus,
|
||||
+ pcidev->dev, pcidev->func);
|
||||
+ return ERROR_FAIL;
|
||||
+ }
|
||||
+
|
||||
/* Unbind from pciback */
|
||||
if ( (rc=pciback_dev_is_assigned(gc, pcidev)) < 0 ) {
|
||||
return ERROR_FAIL;
|
||||
diff --git a/xen/arch/x86/mm.c b/xen/arch/x86/mm.c
|
||||
index 3557cd1178..11d753d8d2 100644
|
||||
--- a/xen/arch/x86/mm.c
|
||||
+++ b/xen/arch/x86/mm.c
|
||||
@@ -295,9 +295,11 @@ void __init arch_init_memory(void)
|
||||
* Initialise our DOMID_IO domain.
|
||||
* This domain owns I/O pages that are within the range of the page_info
|
||||
* array. Mappings occur at the priv of the caller.
|
||||
+ * Quarantined PCI devices will be associated with this domain.
|
||||
*/
|
||||
dom_io = domain_create(DOMID_IO, NULL, false);
|
||||
BUG_ON(IS_ERR(dom_io));
|
||||
+ INIT_LIST_HEAD(&dom_io->arch.pdev_list);
|
||||
|
||||
/*
|
||||
* Initialise our COW domain.
|
||||
diff --git a/xen/common/domctl.c b/xen/common/domctl.c
|
||||
index d08b6274e2..e3c4be2b48 100644
|
||||
--- a/xen/common/domctl.c
|
||||
+++ b/xen/common/domctl.c
|
||||
@@ -391,6 +391,16 @@ long do_domctl(XEN_GUEST_HANDLE_PARAM(xen_domctl_t) u_domctl)
|
||||
|
||||
switch ( op->cmd )
|
||||
{
|
||||
+ case XEN_DOMCTL_assign_device:
|
||||
+ case XEN_DOMCTL_deassign_device:
|
||||
+ if ( op->domain == DOMID_IO )
|
||||
+ {
|
||||
+ d = dom_io;
|
||||
+ break;
|
||||
+ }
|
||||
+ else if ( op->domain == DOMID_INVALID )
|
||||
+ return -ESRCH;
|
||||
+ /* fall through */
|
||||
case XEN_DOMCTL_test_assign_device:
|
||||
if ( op->domain == DOMID_INVALID )
|
||||
{
|
||||
@@ -412,7 +422,7 @@ long do_domctl(XEN_GUEST_HANDLE_PARAM(xen_domctl_t) u_domctl)
|
||||
|
||||
if ( !domctl_lock_acquire() )
|
||||
{
|
||||
- if ( d )
|
||||
+ if ( d && d != dom_io )
|
||||
rcu_unlock_domain(d);
|
||||
return hypercall_create_continuation(
|
||||
__HYPERVISOR_domctl, "h", u_domctl);
|
||||
@@ -1074,7 +1084,7 @@ long do_domctl(XEN_GUEST_HANDLE_PARAM(xen_domctl_t) u_domctl)
|
||||
domctl_lock_release();
|
||||
|
||||
domctl_out_unlock_domonly:
|
||||
- if ( d )
|
||||
+ if ( d && d != dom_io )
|
||||
rcu_unlock_domain(d);
|
||||
|
||||
if ( copyback && __copy_to_guest(u_domctl, op, 1) )
|
||||
diff --git a/xen/drivers/passthrough/amd/pci_amd_iommu.c b/xen/drivers/passthrough/amd/pci_amd_iommu.c
|
||||
index 33a3798f36..15c13e1163 100644
|
||||
--- a/xen/drivers/passthrough/amd/pci_amd_iommu.c
|
||||
+++ b/xen/drivers/passthrough/amd/pci_amd_iommu.c
|
||||
@@ -120,6 +120,10 @@ static void amd_iommu_setup_domain_device(
|
||||
u8 bus = pdev->bus;
|
||||
const struct domain_iommu *hd = dom_iommu(domain);
|
||||
|
||||
+ /* dom_io is used as a sentinel for quarantined devices */
|
||||
+ if ( domain == dom_io )
|
||||
+ return;
|
||||
+
|
||||
BUG_ON( !hd->arch.root_table || !hd->arch.paging_mode ||
|
||||
!iommu->dev_table.buffer );
|
||||
|
||||
@@ -277,6 +281,10 @@ void amd_iommu_disable_domain_device(struct domain *domain,
|
||||
int req_id;
|
||||
u8 bus = pdev->bus;
|
||||
|
||||
+ /* dom_io is used as a sentinel for quarantined devices */
|
||||
+ if ( domain == dom_io )
|
||||
+ return;
|
||||
+
|
||||
BUG_ON ( iommu->dev_table.buffer == NULL );
|
||||
req_id = get_dma_requestor_id(iommu->seg, PCI_BDF2(bus, devfn));
|
||||
dte = iommu->dev_table.buffer + (req_id * IOMMU_DEV_TABLE_ENTRY_SIZE);
|
||||
@@ -363,7 +371,7 @@ static int amd_iommu_assign_device(struct domain *d, u8 devfn,
|
||||
ivrs_mappings[req_id].read_permission);
|
||||
}
|
||||
|
||||
- return reassign_device(hardware_domain, d, devfn, pdev);
|
||||
+ return reassign_device(pdev->domain, d, devfn, pdev);
|
||||
}
|
||||
|
||||
static void deallocate_next_page_table(struct page_info *pg, int level)
|
||||
diff --git a/xen/drivers/passthrough/iommu.c b/xen/drivers/passthrough/iommu.c
|
||||
index a6697d58fb..2762e1342f 100644
|
||||
--- a/xen/drivers/passthrough/iommu.c
|
||||
+++ b/xen/drivers/passthrough/iommu.c
|
||||
@@ -232,6 +232,9 @@ void iommu_teardown(struct domain *d)
|
||||
{
|
||||
struct domain_iommu *hd = dom_iommu(d);
|
||||
|
||||
+ if ( d == dom_io )
|
||||
+ return;
|
||||
+
|
||||
hd->status = IOMMU_STATUS_disabled;
|
||||
hd->platform_ops->teardown(d);
|
||||
tasklet_schedule(&iommu_pt_cleanup_tasklet);
|
||||
@@ -241,6 +244,9 @@ int iommu_construct(struct domain *d)
|
||||
{
|
||||
struct domain_iommu *hd = dom_iommu(d);
|
||||
|
||||
+ if ( d == dom_io )
|
||||
+ return 0;
|
||||
+
|
||||
if ( hd->status == IOMMU_STATUS_initialized )
|
||||
return 0;
|
||||
|
||||
@@ -521,6 +527,9 @@ int __init iommu_setup(void)
|
||||
printk("I/O virtualisation %sabled\n", iommu_enabled ? "en" : "dis");
|
||||
if ( iommu_enabled )
|
||||
{
|
||||
+ if ( iommu_domain_init(dom_io) )
|
||||
+ panic("Could not set up quarantine\n");
|
||||
+
|
||||
printk(" - Dom0 mode: %s\n",
|
||||
iommu_hwdom_passthrough ? "Passthrough" :
|
||||
iommu_hwdom_strict ? "Strict" : "Relaxed");
|
||||
diff --git a/xen/drivers/passthrough/pci.c b/xen/drivers/passthrough/pci.c
|
||||
index d7420bd8bf..d66a8a1daf 100644
|
||||
--- a/xen/drivers/passthrough/pci.c
|
||||
+++ b/xen/drivers/passthrough/pci.c
|
||||
@@ -1426,19 +1426,29 @@ static int iommu_remove_device(struct pci_dev *pdev)
|
||||
return hd->platform_ops->remove_device(pdev->devfn, pci_to_dev(pdev));
|
||||
}
|
||||
|
||||
-/*
|
||||
- * If the device isn't owned by the hardware domain, it means it already
|
||||
- * has been assigned to other domain, or it doesn't exist.
|
||||
- */
|
||||
static int device_assigned(u16 seg, u8 bus, u8 devfn)
|
||||
{
|
||||
struct pci_dev *pdev;
|
||||
+ int rc = 0;
|
||||
|
||||
pcidevs_lock();
|
||||
- pdev = pci_get_pdev_by_domain(hardware_domain, seg, bus, devfn);
|
||||
+
|
||||
+ pdev = pci_get_pdev(seg, bus, devfn);
|
||||
+
|
||||
+ if ( !pdev )
|
||||
+ rc = -ENODEV;
|
||||
+ /*
|
||||
+ * If the device exists and it is not owned by either the hardware
|
||||
+ * domain or dom_io then it must be assigned to a guest, or be
|
||||
+ * hidden (owned by dom_xen).
|
||||
+ */
|
||||
+ else if ( pdev->domain != hardware_domain &&
|
||||
+ pdev->domain != dom_io )
|
||||
+ rc = -EBUSY;
|
||||
+
|
||||
pcidevs_unlock();
|
||||
|
||||
- return pdev ? 0 : -EBUSY;
|
||||
+ return rc;
|
||||
}
|
||||
|
||||
static int assign_device(struct domain *d, u16 seg, u8 bus, u8 devfn, u32 flag)
|
||||
@@ -1452,7 +1462,8 @@ static int assign_device(struct domain *d, u16 seg, u8 bus, u8 devfn, u32 flag)
|
||||
|
||||
/* Prevent device assign if mem paging or mem sharing have been
|
||||
* enabled for this domain */
|
||||
- if ( unlikely((is_hvm_domain(d) &&
|
||||
+ if ( d != dom_io &&
|
||||
+ unlikely((is_hvm_domain(d) &&
|
||||
d->arch.hvm.mem_sharing_enabled) ||
|
||||
vm_event_check_ring(d->vm_event_paging) ||
|
||||
p2m_get_hostp2m(d)->global_logdirty) )
|
||||
@@ -1468,12 +1479,20 @@ static int assign_device(struct domain *d, u16 seg, u8 bus, u8 devfn, u32 flag)
|
||||
return rc;
|
||||
}
|
||||
|
||||
- pdev = pci_get_pdev_by_domain(hardware_domain, seg, bus, devfn);
|
||||
+ pdev = pci_get_pdev(seg, bus, devfn);
|
||||
+
|
||||
+ rc = -ENODEV;
|
||||
if ( !pdev )
|
||||
- {
|
||||
- rc = pci_get_pdev(seg, bus, devfn) ? -EBUSY : -ENODEV;
|
||||
goto done;
|
||||
- }
|
||||
+
|
||||
+ rc = 0;
|
||||
+ if ( d == pdev->domain )
|
||||
+ goto done;
|
||||
+
|
||||
+ rc = -EBUSY;
|
||||
+ if ( pdev->domain != hardware_domain &&
|
||||
+ pdev->domain != dom_io )
|
||||
+ goto done;
|
||||
|
||||
if ( pdev->msix )
|
||||
msixtbl_init(d);
|
||||
@@ -1496,6 +1515,10 @@ static int assign_device(struct domain *d, u16 seg, u8 bus, u8 devfn, u32 flag)
|
||||
}
|
||||
|
||||
done:
|
||||
+ /* The device is assigned to dom_io so mark it as quarantined */
|
||||
+ if ( !rc && d == dom_io )
|
||||
+ pdev->quarantine = true;
|
||||
+
|
||||
if ( !has_arch_pdevs(d) && has_iommu_pt(d) )
|
||||
iommu_teardown(d);
|
||||
pcidevs_unlock();
|
||||
@@ -1508,6 +1531,7 @@ int deassign_device(struct domain *d, u16 seg, u8 bus, u8 devfn)
|
||||
{
|
||||
const struct domain_iommu *hd = dom_iommu(d);
|
||||
struct pci_dev *pdev = NULL;
|
||||
+ struct domain *target;
|
||||
int ret = 0;
|
||||
|
||||
if ( !iommu_enabled || !hd->platform_ops )
|
||||
@@ -1518,12 +1542,16 @@ int deassign_device(struct domain *d, u16 seg, u8 bus, u8 devfn)
|
||||
if ( !pdev )
|
||||
return -ENODEV;
|
||||
|
||||
+ /* De-assignment from dom_io should de-quarantine the device */
|
||||
+ target = (pdev->quarantine && pdev->domain != dom_io) ?
|
||||
+ dom_io : hardware_domain;
|
||||
+
|
||||
while ( pdev->phantom_stride )
|
||||
{
|
||||
devfn += pdev->phantom_stride;
|
||||
if ( PCI_SLOT(devfn) != PCI_SLOT(pdev->devfn) )
|
||||
break;
|
||||
- ret = hd->platform_ops->reassign_device(d, hardware_domain, devfn,
|
||||
+ ret = hd->platform_ops->reassign_device(d, target, devfn,
|
||||
pci_to_dev(pdev));
|
||||
if ( !ret )
|
||||
continue;
|
||||
@@ -1534,7 +1562,7 @@ int deassign_device(struct domain *d, u16 seg, u8 bus, u8 devfn)
|
||||
}
|
||||
|
||||
devfn = pdev->devfn;
|
||||
- ret = hd->platform_ops->reassign_device(d, hardware_domain, devfn,
|
||||
+ ret = hd->platform_ops->reassign_device(d, target, devfn,
|
||||
pci_to_dev(pdev));
|
||||
if ( ret )
|
||||
{
|
||||
@@ -1544,6 +1572,9 @@ int deassign_device(struct domain *d, u16 seg, u8 bus, u8 devfn)
|
||||
return ret;
|
||||
}
|
||||
|
||||
+ if ( pdev->domain == hardware_domain )
|
||||
+ pdev->quarantine = false;
|
||||
+
|
||||
pdev->fault.count = 0;
|
||||
|
||||
if ( !has_arch_pdevs(d) && has_iommu_pt(d) )
|
||||
@@ -1722,7 +1753,7 @@ int iommu_do_pci_domctl(
|
||||
ret = hypercall_create_continuation(__HYPERVISOR_domctl,
|
||||
"h", u_domctl);
|
||||
else if ( ret )
|
||||
- printk(XENLOG_G_ERR "XEN_DOMCTL_assign_device: "
|
||||
+ printk(XENLOG_G_ERR
|
||||
"assign %04x:%02x:%02x.%u to dom%d failed (%d)\n",
|
||||
seg, bus, PCI_SLOT(devfn), PCI_FUNC(devfn),
|
||||
d->domain_id, ret);
|
||||
diff --git a/xen/drivers/passthrough/vtd/iommu.c b/xen/drivers/passthrough/vtd/iommu.c
|
||||
index 1db1cd9f2d..a8d1baa064 100644
|
||||
--- a/xen/drivers/passthrough/vtd/iommu.c
|
||||
+++ b/xen/drivers/passthrough/vtd/iommu.c
|
||||
@@ -1338,6 +1338,10 @@ int domain_context_mapping_one(
|
||||
int agaw, rc, ret;
|
||||
bool_t flush_dev_iotlb;
|
||||
|
||||
+ /* dom_io is used as a sentinel for quarantined devices */
|
||||
+ if ( domain == dom_io )
|
||||
+ return 0;
|
||||
+
|
||||
ASSERT(pcidevs_locked());
|
||||
spin_lock(&iommu->lock);
|
||||
maddr = bus_to_context_maddr(iommu, bus);
|
||||
@@ -1573,6 +1577,10 @@ int domain_context_unmap_one(
|
||||
int iommu_domid, rc, ret;
|
||||
bool_t flush_dev_iotlb;
|
||||
|
||||
+ /* dom_io is used as a sentinel for quarantined devices */
|
||||
+ if ( domain == dom_io )
|
||||
+ return 0;
|
||||
+
|
||||
ASSERT(pcidevs_locked());
|
||||
spin_lock(&iommu->lock);
|
||||
|
||||
@@ -1705,6 +1713,10 @@ static int domain_context_unmap(struct domain *domain, u8 devfn,
|
||||
goto out;
|
||||
}
|
||||
|
||||
+ /* dom_io is used as a sentinel for quarantined devices */
|
||||
+ if ( domain == dom_io )
|
||||
+ goto out;
|
||||
+
|
||||
/*
|
||||
* if no other devices under the same iommu owned by this domain,
|
||||
* clear iommu in iommu_bitmap and clear domain_id in domid_bitmp
|
||||
@@ -2441,6 +2453,15 @@ static int reassign_device_ownership(
|
||||
if ( ret )
|
||||
return ret;
|
||||
|
||||
+ if ( devfn == pdev->devfn )
|
||||
+ {
|
||||
+ list_move(&pdev->domain_list, &dom_io->arch.pdev_list);
|
||||
+ pdev->domain = dom_io;
|
||||
+ }
|
||||
+
|
||||
+ if ( !has_arch_pdevs(source) )
|
||||
+ vmx_pi_hooks_deassign(source);
|
||||
+
|
||||
if ( !has_arch_pdevs(target) )
|
||||
vmx_pi_hooks_assign(target);
|
||||
|
||||
@@ -2459,15 +2480,13 @@ static int reassign_device_ownership(
|
||||
pdev->domain = target;
|
||||
}
|
||||
|
||||
- if ( !has_arch_pdevs(source) )
|
||||
- vmx_pi_hooks_deassign(source);
|
||||
-
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int intel_iommu_assign_device(
|
||||
struct domain *d, u8 devfn, struct pci_dev *pdev, u32 flag)
|
||||
{
|
||||
+ struct domain *s = pdev->domain;
|
||||
struct acpi_rmrr_unit *rmrr;
|
||||
int ret = 0, i;
|
||||
u16 bdf, seg;
|
||||
@@ -2510,8 +2529,8 @@ static int intel_iommu_assign_device(
|
||||
}
|
||||
}
|
||||
|
||||
- ret = reassign_device_ownership(hardware_domain, d, devfn, pdev);
|
||||
- if ( ret )
|
||||
+ ret = reassign_device_ownership(s, d, devfn, pdev);
|
||||
+ if ( ret || d == dom_io )
|
||||
return ret;
|
||||
|
||||
/* Setup rmrr identity mapping */
|
||||
@@ -2524,11 +2543,20 @@ static int intel_iommu_assign_device(
|
||||
ret = rmrr_identity_mapping(d, 1, rmrr, flag);
|
||||
if ( ret )
|
||||
{
|
||||
- reassign_device_ownership(d, hardware_domain, devfn, pdev);
|
||||
+ int rc;
|
||||
+
|
||||
+ rc = reassign_device_ownership(d, s, devfn, pdev);
|
||||
printk(XENLOG_G_ERR VTDPREFIX
|
||||
" cannot map reserved region (%"PRIx64",%"PRIx64"] for Dom%d (%d)\n",
|
||||
rmrr->base_address, rmrr->end_address,
|
||||
d->domain_id, ret);
|
||||
+ if ( rc )
|
||||
+ {
|
||||
+ printk(XENLOG_ERR VTDPREFIX
|
||||
+ " failed to reclaim %04x:%02x:%02x.%u from %pd (%d)\n",
|
||||
+ seg, bus, PCI_SLOT(devfn), PCI_FUNC(devfn), d, rc);
|
||||
+ domain_crash(d);
|
||||
+ }
|
||||
break;
|
||||
}
|
||||
}
|
||||
diff --git a/xen/include/xen/pci.h b/xen/include/xen/pci.h
|
||||
index 8b21e8dc84..a031fd6020 100644
|
||||
--- a/xen/include/xen/pci.h
|
||||
+++ b/xen/include/xen/pci.h
|
||||
@@ -88,6 +88,9 @@ struct pci_dev {
|
||||
|
||||
nodeid_t node; /* NUMA node */
|
||||
|
||||
+ /* Device to be quarantined, don't automatically re-assign to dom0 */
|
||||
+ bool quarantine;
|
||||
+
|
||||
/* Device with errata, ignore the BARs. */
|
||||
bool ignore_bars;
|
||||
|
||||
--
|
||||
2.11.0
|
||||
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
From c8cb33fa64c9ccbfa2a494a9dad2e0a763c09176 Mon Sep 17 00:00:00 2001
|
||||
From: Julien Grall <julien.grall@arm.com>
|
||||
Date: Tue, 1 Oct 2019 13:07:53 +0100
|
||||
Subject: [PATCH 1/4] xen/arm32: entry: Split __DEFINE_ENTRY_TRAP in two
|
||||
|
||||
The preprocessing macro __DEFINE_ENTRY_TRAP is used to generate trap
|
||||
entry function. While the macro is fairly small today, follow-up patches
|
||||
will increase the size signicantly.
|
||||
|
||||
In general, assembly macros are more readable as they allow you to name
|
||||
parameters and avoid '\'. So the actual implementation of the trap is
|
||||
now switched to an assembly macro.
|
||||
|
||||
This is part of XSA-303.
|
||||
|
||||
Reported-by: Julien Grall <Julien.Grall@arm.com>
|
||||
Signed-off-by: Julien Grall <julien.grall@arm.com>
|
||||
Reviewed-by: Stefano Stabellini <sstabellini@kernel.org>
|
||||
Reviewed-by: Andre Przywara <andre.przywara@arm.com>
|
||||
---
|
||||
xen/arch/arm/arm32/entry.S | 34 +++++++++++++++++++---------------
|
||||
1 file changed, 19 insertions(+), 15 deletions(-)
|
||||
|
||||
diff --git a/xen/arch/arm/arm32/entry.S b/xen/arch/arm/arm32/entry.S
|
||||
index 0b4cd19abd..4a762e04f1 100644
|
||||
--- a/xen/arch/arm/arm32/entry.S
|
||||
+++ b/xen/arch/arm/arm32/entry.S
|
||||
@@ -126,24 +126,28 @@ abort_guest_exit_end:
|
||||
skip_check:
|
||||
mov pc, lr
|
||||
|
||||
-/*
|
||||
- * Macro to define trap entry. The iflags corresponds to the list of
|
||||
- * interrupts (Asynchronous Abort, IRQ, FIQ) to unmask.
|
||||
- */
|
||||
+ /*
|
||||
+ * Macro to define trap entry. The iflags corresponds to the list of
|
||||
+ * interrupts (Asynchronous Abort, IRQ, FIQ) to unmask.
|
||||
+ */
|
||||
+ .macro vector trap, iflags
|
||||
+ SAVE_ALL
|
||||
+ cpsie \iflags
|
||||
+ adr lr, return_from_trap
|
||||
+ mov r0, sp
|
||||
+ /*
|
||||
+ * Save the stack pointer in r11. It will be restored after the
|
||||
+ * trap has been handled (see return_from_trap).
|
||||
+ */
|
||||
+ mov r11, sp
|
||||
+ bic sp, #7 /* Align the stack pointer (noop on guest trap) */
|
||||
+ b do_trap_\trap
|
||||
+ .endm
|
||||
+
|
||||
#define __DEFINE_TRAP_ENTRY(trap, iflags) \
|
||||
ALIGN; \
|
||||
trap_##trap: \
|
||||
- SAVE_ALL; \
|
||||
- cpsie iflags; \
|
||||
- adr lr, return_from_trap; \
|
||||
- mov r0, sp; \
|
||||
- /* \
|
||||
- * Save the stack pointer in r11. It will be restored after the \
|
||||
- * trap has been handled (see return_from_trap). \
|
||||
- */ \
|
||||
- mov r11, sp; \
|
||||
- bic sp, #7; /* Align the stack pointer (noop on guest trap) */ \
|
||||
- b do_trap_##trap
|
||||
+ vector trap, iflags
|
||||
|
||||
/* Trap handler which unmask IRQ/Abort, keep FIQ masked */
|
||||
#define DEFINE_TRAP_ENTRY(trap) __DEFINE_TRAP_ENTRY(trap, ai)
|
||||
--
|
||||
2.11.0
|
||||
|
||||
|
|
@ -1,97 +0,0 @@
|
|||
From be7379207c83fa74f8a6c22a8ea213f02714776f Mon Sep 17 00:00:00 2001
|
||||
From: Julien Grall <julien.grall@arm.com>
|
||||
Date: Tue, 1 Oct 2019 13:15:48 +0100
|
||||
Subject: [PATCH 2/4] xen/arm32: entry: Fold the macro SAVE_ALL in the macro
|
||||
vector
|
||||
|
||||
Follow-up rework will require the macro vector to distinguish between
|
||||
a trap from a guest vs while in the hypervisor.
|
||||
|
||||
The macro SAVE_ALL already has code to distinguish between the two and
|
||||
it is only called by the vector macro. So fold the former into the
|
||||
latter. This will help to avoid duplicating the check.
|
||||
|
||||
This is part of XSA-303.
|
||||
|
||||
Reported-by: Julien Grall <Julien.Grall@arm.com>
|
||||
Signed-off-by: Julien Grall <julien.grall@arm.com>
|
||||
Reviewed-by: Stefano Stabellini <sstabellini@kernel.org>
|
||||
Reviewed-by: Andre Przywara <andre.przywara@arm.com>
|
||||
---
|
||||
xen/arch/arm/arm32/entry.S | 46 +++++++++++++++++++++++-----------------------
|
||||
1 file changed, 23 insertions(+), 23 deletions(-)
|
||||
|
||||
diff --git a/xen/arch/arm/arm32/entry.S b/xen/arch/arm/arm32/entry.S
|
||||
index 4a762e04f1..150cbc0b4b 100644
|
||||
--- a/xen/arch/arm/arm32/entry.S
|
||||
+++ b/xen/arch/arm/arm32/entry.S
|
||||
@@ -13,27 +13,6 @@
|
||||
#define RESTORE_BANKED(mode) \
|
||||
RESTORE_ONE_BANKED(SP_##mode) ; RESTORE_ONE_BANKED(LR_##mode) ; RESTORE_ONE_BANKED(SPSR_##mode)
|
||||
|
||||
-#define SAVE_ALL \
|
||||
- sub sp, #(UREGS_SP_usr - UREGS_sp); /* SP, LR, SPSR, PC */ \
|
||||
- push {r0-r12}; /* Save R0-R12 */ \
|
||||
- \
|
||||
- mrs r11, ELR_hyp; /* ELR_hyp is return address. */\
|
||||
- str r11, [sp, #UREGS_pc]; \
|
||||
- \
|
||||
- str lr, [sp, #UREGS_lr]; \
|
||||
- \
|
||||
- add r11, sp, #UREGS_kernel_sizeof+4; \
|
||||
- str r11, [sp, #UREGS_sp]; \
|
||||
- \
|
||||
- mrc CP32(r11, HSR); /* Save exception syndrome */ \
|
||||
- str r11, [sp, #UREGS_hsr]; \
|
||||
- \
|
||||
- mrs r11, SPSR_hyp; \
|
||||
- str r11, [sp, #UREGS_cpsr]; \
|
||||
- and r11, #PSR_MODE_MASK; \
|
||||
- cmp r11, #PSR_MODE_HYP; \
|
||||
- blne save_guest_regs
|
||||
-
|
||||
save_guest_regs:
|
||||
#ifdef CONFIG_ARM32_HARDEN_BRANCH_PREDICTOR
|
||||
/*
|
||||
@@ -52,7 +31,7 @@ save_guest_regs:
|
||||
ldr r11, =0xffffffff /* Clobber SP which is only valid for hypervisor frames. */
|
||||
str r11, [sp, #UREGS_sp]
|
||||
SAVE_ONE_BANKED(SP_usr)
|
||||
- /* LR_usr is the same physical register as lr and is saved in SAVE_ALL */
|
||||
+ /* LR_usr is the same physical register as lr and is saved by the caller */
|
||||
SAVE_BANKED(svc)
|
||||
SAVE_BANKED(abt)
|
||||
SAVE_BANKED(und)
|
||||
@@ -131,7 +110,28 @@ skip_check:
|
||||
* interrupts (Asynchronous Abort, IRQ, FIQ) to unmask.
|
||||
*/
|
||||
.macro vector trap, iflags
|
||||
- SAVE_ALL
|
||||
+ /* Save registers in the stack */
|
||||
+ sub sp, #(UREGS_SP_usr - UREGS_sp) /* SP, LR, SPSR, PC */
|
||||
+ push {r0-r12} /* Save R0-R12 */
|
||||
+ mrs r11, ELR_hyp /* ELR_hyp is return address */
|
||||
+ str r11, [sp, #UREGS_pc]
|
||||
+
|
||||
+ str lr, [sp, #UREGS_lr]
|
||||
+
|
||||
+ add r11, sp, #(UREGS_kernel_sizeof + 4)
|
||||
+
|
||||
+ str r11, [sp, #UREGS_sp]
|
||||
+
|
||||
+ mrc CP32(r11, HSR) /* Save exception syndrome */
|
||||
+ str r11, [sp, #UREGS_hsr]
|
||||
+
|
||||
+ mrs r11, SPSR_hyp
|
||||
+ str r11, [sp, #UREGS_cpsr]
|
||||
+ and r11, #PSR_MODE_MASK
|
||||
+ cmp r11, #PSR_MODE_HYP
|
||||
+ blne save_guest_regs
|
||||
+
|
||||
+ /* We are ready to handle the trap, setup the registers and jump. */
|
||||
cpsie \iflags
|
||||
adr lr, return_from_trap
|
||||
mov r0, sp
|
||||
--
|
||||
2.11.0
|
||||
|
||||
|
|
@ -1,226 +0,0 @@
|
|||
From 098fe877967870ffda2dfd9629a5fd272f6aacdc Mon Sep 17 00:00:00 2001
|
||||
From: Julien Grall <julien.grall@arm.com>
|
||||
Date: Fri, 11 Oct 2019 17:49:28 +0100
|
||||
Subject: [PATCH 3/4] xen/arm32: Don't blindly unmask interrupts on trap
|
||||
without a change of level
|
||||
|
||||
Exception vectors will unmask interrupts regardless the state of them in
|
||||
the interrupted context.
|
||||
|
||||
One of the consequences is IRQ will be unmasked when receiving an
|
||||
undefined instruction exception (used by WARN*) from the hypervisor.
|
||||
This could result to unexpected behavior such as deadlock (if a lock was
|
||||
shared with interrupts).
|
||||
|
||||
In a nutshell, interrupts should only be unmasked when it is safe to do.
|
||||
Xen only unmask IRQ and Abort interrupts, so the logic can stay simple.
|
||||
|
||||
As vectors exceptions may be shared between guest and hypervisor, we now
|
||||
need to have a different policy for the interrupts.
|
||||
|
||||
On exception from hypervisor, each vector will select the list of
|
||||
interrupts to inherit from the interrupted context. Any interrupts not
|
||||
listed will be kept masked.
|
||||
|
||||
On exception from the guest, the Abort and IRQ will be unmasked
|
||||
depending on the exact vector.
|
||||
|
||||
The interrupts will be kept unmasked when the vector cannot used by
|
||||
either guest or hypervisor.
|
||||
|
||||
Note that each vector is not anymore preceded by ALIGN. This is fine
|
||||
because the alignment is already bigger than what we need.
|
||||
|
||||
This is part of XSA-303.
|
||||
|
||||
Reported-by: Julien Grall <Julien.Grall@arm.com>
|
||||
Signed-off-by: Julien Grall <julien.grall@arm.com>
|
||||
Reviewed-by: Stefano Stabellini <sstabellini@kernel.org>
|
||||
Reviewed-by: Andre Przywara <andre.przywara@arm.com>
|
||||
---
|
||||
xen/arch/arm/arm32/entry.S | 138 +++++++++++++++++++++++++++++++++++----------
|
||||
1 file changed, 109 insertions(+), 29 deletions(-)
|
||||
|
||||
diff --git a/xen/arch/arm/arm32/entry.S b/xen/arch/arm/arm32/entry.S
|
||||
index 150cbc0b4b..ec90cca093 100644
|
||||
--- a/xen/arch/arm/arm32/entry.S
|
||||
+++ b/xen/arch/arm/arm32/entry.S
|
||||
@@ -4,6 +4,17 @@
|
||||
#include <asm/alternative.h>
|
||||
#include <public/xen.h>
|
||||
|
||||
+/*
|
||||
+ * Short-hands to defined the interrupts (A, I, F)
|
||||
+ *
|
||||
+ * _ means the interrupt state will not change
|
||||
+ * X means the state of interrupt X will change
|
||||
+ *
|
||||
+ * To be used with msr cpsr_* only
|
||||
+ */
|
||||
+#define IFLAGS_AIF PSR_ABT_MASK | PSR_IRQ_MASK | PSR_FIQ_MASK
|
||||
+#define IFLAGS_A_F PSR_ABT_MASK | PSR_FIQ_MASK
|
||||
+
|
||||
#define SAVE_ONE_BANKED(reg) mrs r11, reg; str r11, [sp, #UREGS_##reg]
|
||||
#define RESTORE_ONE_BANKED(reg) ldr r11, [sp, #UREGS_##reg]; msr reg, r11
|
||||
|
||||
@@ -106,10 +117,18 @@ skip_check:
|
||||
mov pc, lr
|
||||
|
||||
/*
|
||||
- * Macro to define trap entry. The iflags corresponds to the list of
|
||||
- * interrupts (Asynchronous Abort, IRQ, FIQ) to unmask.
|
||||
+ * Macro to define a trap entry.
|
||||
+ *
|
||||
+ * @guest_iflags: Optional list of interrupts to unmask when
|
||||
+ * entering from guest context. As this is used with cpsie,
|
||||
+ * the letter (a, i, f) should be used.
|
||||
+ *
|
||||
+ * @hyp_iflags: Optional list of interrupts to inherit when
|
||||
+ * entering from hypervisor context. Any interrupts not
|
||||
+ * listed will be kept unchanged. As this is used with cpsr_*,
|
||||
+ * IFLAGS_* short-hands should be used.
|
||||
*/
|
||||
- .macro vector trap, iflags
|
||||
+ .macro vector trap, guest_iflags=n, hyp_iflags=0
|
||||
/* Save registers in the stack */
|
||||
sub sp, #(UREGS_SP_usr - UREGS_sp) /* SP, LR, SPSR, PC */
|
||||
push {r0-r12} /* Save R0-R12 */
|
||||
@@ -127,12 +146,39 @@ skip_check:
|
||||
|
||||
mrs r11, SPSR_hyp
|
||||
str r11, [sp, #UREGS_cpsr]
|
||||
- and r11, #PSR_MODE_MASK
|
||||
- cmp r11, #PSR_MODE_HYP
|
||||
- blne save_guest_regs
|
||||
|
||||
+ /*
|
||||
+ * We need to distinguish whether we came from guest or
|
||||
+ * hypervisor context.
|
||||
+ */
|
||||
+ and r0, r11, #PSR_MODE_MASK
|
||||
+ cmp r0, #PSR_MODE_HYP
|
||||
+
|
||||
+ bne 1f
|
||||
+ /*
|
||||
+ * Trap from the hypervisor
|
||||
+ *
|
||||
+ * Inherit the state of the interrupts from the hypervisor
|
||||
+ * context. For that we need to use SPSR (stored in r11) and
|
||||
+ * modify CPSR accordingly.
|
||||
+ *
|
||||
+ * CPSR = (CPSR & ~hyp_iflags) | (SPSR & hyp_iflags)
|
||||
+ */
|
||||
+ mrs r10, cpsr
|
||||
+ bic r10, r10, #\hyp_iflags
|
||||
+ and r11, r11, #\hyp_iflags
|
||||
+ orr r10, r10, r11
|
||||
+ msr cpsr_cx, r10
|
||||
+ b 2f
|
||||
+
|
||||
+1:
|
||||
+ /* Trap from the guest */
|
||||
+ bl save_guest_regs
|
||||
+ .if \guest_iflags != n
|
||||
+ cpsie \guest_iflags
|
||||
+ .endif
|
||||
+2:
|
||||
/* We are ready to handle the trap, setup the registers and jump. */
|
||||
- cpsie \iflags
|
||||
adr lr, return_from_trap
|
||||
mov r0, sp
|
||||
/*
|
||||
@@ -144,20 +190,6 @@ skip_check:
|
||||
b do_trap_\trap
|
||||
.endm
|
||||
|
||||
-#define __DEFINE_TRAP_ENTRY(trap, iflags) \
|
||||
- ALIGN; \
|
||||
-trap_##trap: \
|
||||
- vector trap, iflags
|
||||
-
|
||||
-/* Trap handler which unmask IRQ/Abort, keep FIQ masked */
|
||||
-#define DEFINE_TRAP_ENTRY(trap) __DEFINE_TRAP_ENTRY(trap, ai)
|
||||
-
|
||||
-/* Trap handler which unmask Abort, keep IRQ/FIQ masked */
|
||||
-#define DEFINE_TRAP_ENTRY_NOIRQ(trap) __DEFINE_TRAP_ENTRY(trap, a)
|
||||
-
|
||||
-/* Trap handler which unmask IRQ, keep Abort/FIQ masked */
|
||||
-#define DEFINE_TRAP_ENTRY_NOABORT(trap) __DEFINE_TRAP_ENTRY(trap, i)
|
||||
-
|
||||
.align 5
|
||||
GLOBAL(hyp_traps_vector)
|
||||
b trap_reset /* 0x00 - Reset */
|
||||
@@ -228,14 +260,62 @@ decode_vectors:
|
||||
|
||||
#endif /* CONFIG_HARDEN_BRANCH_PREDICTOR */
|
||||
|
||||
-DEFINE_TRAP_ENTRY(reset)
|
||||
-DEFINE_TRAP_ENTRY(undefined_instruction)
|
||||
-DEFINE_TRAP_ENTRY(hypervisor_call)
|
||||
-DEFINE_TRAP_ENTRY(prefetch_abort)
|
||||
-DEFINE_TRAP_ENTRY(guest_sync)
|
||||
-DEFINE_TRAP_ENTRY_NOIRQ(irq)
|
||||
-DEFINE_TRAP_ENTRY_NOIRQ(fiq)
|
||||
-DEFINE_TRAP_ENTRY_NOABORT(data_abort)
|
||||
+/* Vector not used by the Hypervisor. */
|
||||
+trap_reset:
|
||||
+ vector reset
|
||||
+
|
||||
+/*
|
||||
+ * Vector only used by the Hypervisor.
|
||||
+ *
|
||||
+ * While the exception can be executed with all the interrupts (e.g.
|
||||
+ * IRQ) unmasked, the interrupted context may have purposefully masked
|
||||
+ * some of them. So we want to inherit the state from the interrupted
|
||||
+ * context.
|
||||
+ */
|
||||
+trap_undefined_instruction:
|
||||
+ vector undefined_instruction, hyp_iflags=IFLAGS_AIF
|
||||
+
|
||||
+/* We should never reach this trap */
|
||||
+trap_hypervisor_call:
|
||||
+ vector hypervisor_call
|
||||
+
|
||||
+/*
|
||||
+ * Vector only used by the hypervisor.
|
||||
+ *
|
||||
+ * While the exception can be executed with all the interrupts (e.g.
|
||||
+ * IRQ) unmasked, the interrupted context may have purposefully masked
|
||||
+ * some of them. So we want to inherit the state from the interrupted
|
||||
+ * context.
|
||||
+ */
|
||||
+trap_prefetch_abort:
|
||||
+ vector prefetch_abort, hyp_iflags=IFLAGS_AIF
|
||||
+
|
||||
+/*
|
||||
+ * Vector only used by the hypervisor.
|
||||
+ *
|
||||
+ * Data Abort should be rare and most likely fatal. It is best to not
|
||||
+ * unmask any interrupts to limit the amount of code that can run before
|
||||
+ * the Data Abort is treated.
|
||||
+ */
|
||||
+trap_data_abort:
|
||||
+ vector data_abort
|
||||
+
|
||||
+/* Vector only used by the guest. We can unmask Abort/IRQ. */
|
||||
+trap_guest_sync:
|
||||
+ vector guest_sync, guest_iflags=ai
|
||||
+
|
||||
+
|
||||
+/* Vector used by the hypervisor and the guest. */
|
||||
+trap_irq:
|
||||
+ vector irq, guest_iflags=a, hyp_iflags=IFLAGS_A_F
|
||||
+
|
||||
+/*
|
||||
+ * Vector used by the hypervisor and the guest.
|
||||
+ *
|
||||
+ * FIQ are not meant to happen, so we don't unmask any interrupts.
|
||||
+ */
|
||||
+trap_fiq:
|
||||
+ vector fiq
|
||||
|
||||
return_from_trap:
|
||||
/*
|
||||
--
|
||||
2.11.0
|
||||
|
||||
|
|
@ -1,114 +0,0 @@
|
|||
From c6d290ce157a044dec417fdda8db71e41a37d744 Mon Sep 17 00:00:00 2001
|
||||
From: Julien Grall <julien.grall@arm.com>
|
||||
Date: Mon, 7 Oct 2019 18:10:56 +0100
|
||||
Subject: [PATCH 4/4] xen/arm64: Don't blindly unmask interrupts on trap
|
||||
without a change of level
|
||||
|
||||
Some of the traps without a change of the level (i.e. hypervisor ->
|
||||
hypervisor) will unmask interrupts regardless the state of them in the
|
||||
interrupted context.
|
||||
|
||||
One of the consequences is IRQ will be unmasked when receiving a
|
||||
synchronous exception (used by WARN*()). This could result to unexpected
|
||||
behavior such as deadlock (if a lock was shared with interrupts).
|
||||
|
||||
In a nutshell, interrupts should only be unmasked when it is safe to
|
||||
do. Xen only unmask IRQ and Abort interrupts, so the logic can stay
|
||||
simple:
|
||||
- hyp_error: All the interrupts are now kept masked. SError should
|
||||
be pretty rare and if ever happen then we most likely want to
|
||||
avoid any other interrupts to be generated. The potential main
|
||||
"caller" is during virtual SError synchronization on the exit
|
||||
path from the guest (see check_pending_vserror).
|
||||
|
||||
- hyp_sync: The interrupts state is inherited from the interrupted
|
||||
context.
|
||||
|
||||
- hyp_irq: All the interrupts but IRQ state are inherited from the
|
||||
interrupted context. IRQ is kept masked.
|
||||
|
||||
This is part of XSA-303.
|
||||
|
||||
Reported-by: Julien Grall <Julien.Grall@arm.com>
|
||||
Signed-off-by: Julien Grall <julien.grall@arm.com>
|
||||
Reviewed-by: Stefano Stabellini <sstabellini@kernel.org>
|
||||
Reviewed-by: Andre Przywara <andre.przywara@arm.com>
|
||||
---
|
||||
xen/arch/arm/arm64/entry.S | 47 ++++++++++++++++++++++++++++++++++++++++++----
|
||||
1 file changed, 43 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/xen/arch/arm/arm64/entry.S b/xen/arch/arm/arm64/entry.S
|
||||
index 2d9a2713a1..3e41ba65b6 100644
|
||||
--- a/xen/arch/arm/arm64/entry.S
|
||||
+++ b/xen/arch/arm/arm64/entry.S
|
||||
@@ -188,24 +188,63 @@ hyp_error_invalid:
|
||||
entry hyp=1
|
||||
invalid BAD_ERROR
|
||||
|
||||
+/*
|
||||
+ * SError received while running in the hypervisor mode.
|
||||
+ *
|
||||
+ * Technically, we could unmask the IRQ if it were unmasked in the
|
||||
+ * interrupted context. However, this require to check the PSTATE. For
|
||||
+ * simplicity, as SError should be rare and potentially fatal,
|
||||
+ * all interrupts are kept masked.
|
||||
+ */
|
||||
hyp_error:
|
||||
entry hyp=1
|
||||
- msr daifclr, #2
|
||||
mov x0, sp
|
||||
bl do_trap_hyp_serror
|
||||
exit hyp=1
|
||||
|
||||
-/* Traps taken in Current EL with SP_ELx */
|
||||
+/*
|
||||
+ * Synchronous exception received while running in the hypervisor mode.
|
||||
+ *
|
||||
+ * While the exception could be executed with all the interrupts (e.g.
|
||||
+ * IRQ) unmasked, the interrupted context may have purposefully masked
|
||||
+ * some of them. So we want to inherit the state from the interrupted
|
||||
+ * context.
|
||||
+ */
|
||||
hyp_sync:
|
||||
entry hyp=1
|
||||
- msr daifclr, #6
|
||||
+
|
||||
+ /* Inherit interrupts */
|
||||
+ mrs x0, SPSR_el2
|
||||
+ and x0, x0, #(PSR_DBG_MASK | PSR_ABT_MASK | PSR_IRQ_MASK | PSR_FIQ_MASK)
|
||||
+ msr daif, x0
|
||||
+
|
||||
mov x0, sp
|
||||
bl do_trap_hyp_sync
|
||||
exit hyp=1
|
||||
|
||||
+/*
|
||||
+ * IRQ received while running in the hypervisor mode.
|
||||
+ *
|
||||
+ * While the exception could be executed with all the interrupts but IRQ
|
||||
+ * unmasked, the interrupted context may have purposefully masked some
|
||||
+ * of them. So we want to inherit the state from the interrupt context
|
||||
+ * and keep IRQ masked.
|
||||
+ *
|
||||
+ * XXX: We may want to consider an ordering between interrupts (e.g. if
|
||||
+ * SError are masked, then IRQ should be masked too). However, this
|
||||
+ * would require some rework in some paths (e.g. panic, livepatch) to
|
||||
+ * ensure the ordering is enforced everywhere.
|
||||
+ */
|
||||
hyp_irq:
|
||||
entry hyp=1
|
||||
- msr daifclr, #4
|
||||
+
|
||||
+ /* Inherit D, A, F interrupts and keep I masked */
|
||||
+ mrs x0, SPSR_el2
|
||||
+ mov x1, #(PSR_DBG_MASK | PSR_ABT_MASK | PSR_FIQ_MASK)
|
||||
+ and x0, x0, x1
|
||||
+ orr x0, x0, #PSR_IRQ_MASK
|
||||
+ msr daif, x0
|
||||
+
|
||||
mov x0, sp
|
||||
bl do_trap_irq
|
||||
exit hyp=1
|
||||
--
|
||||
2.11.0
|
||||
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
From: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Subject: x86/vtd: Hide superpage support for SandyBridge IOMMUs
|
||||
|
||||
Something causes SandyBridge IOMMUs to choke when sharing EPT pagetables, and
|
||||
an EPT superpage gets shattered. The root cause is still under investigation,
|
||||
but the end result is unusable in combination with CVE-2018-12207 protections.
|
||||
|
||||
This is part of XSA-304 / CVE-2018-12207
|
||||
|
||||
Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||||
|
||||
diff --git a/xen/drivers/passthrough/vtd/extern.h b/xen/drivers/passthrough/vtd/extern.h
|
||||
index 16eada9fa2..a71c8b0f84 100644
|
||||
--- a/xen/drivers/passthrough/vtd/extern.h
|
||||
+++ b/xen/drivers/passthrough/vtd/extern.h
|
||||
@@ -97,6 +97,8 @@ void vtd_ops_postamble_quirk(struct iommu* iommu);
|
||||
int __must_check me_wifi_quirk(struct domain *domain,
|
||||
u8 bus, u8 devfn, int map);
|
||||
void pci_vtd_quirk(const struct pci_dev *);
|
||||
+void quirk_iommu_caps(struct iommu *iommu);
|
||||
+
|
||||
bool_t platform_supports_intremap(void);
|
||||
bool_t platform_supports_x2apic(void);
|
||||
|
||||
diff --git a/xen/drivers/passthrough/vtd/iommu.c b/xen/drivers/passthrough/vtd/iommu.c
|
||||
index b3664ecbe0..5d34f75306 100644
|
||||
--- a/xen/drivers/passthrough/vtd/iommu.c
|
||||
+++ b/xen/drivers/passthrough/vtd/iommu.c
|
||||
@@ -1215,6 +1215,8 @@ int __init iommu_alloc(struct acpi_drhd_unit *drhd)
|
||||
if ( !(iommu->cap + 1) || !(iommu->ecap + 1) )
|
||||
return -ENODEV;
|
||||
|
||||
+ quirk_iommu_caps(iommu);
|
||||
+
|
||||
if ( cap_fault_reg_offset(iommu->cap) +
|
||||
cap_num_fault_regs(iommu->cap) * PRIMARY_FAULT_REG_LEN >= PAGE_SIZE ||
|
||||
ecap_iotlb_offset(iommu->ecap) >= PAGE_SIZE )
|
||||
diff --git a/xen/drivers/passthrough/vtd/quirks.c b/xen/drivers/passthrough/vtd/quirks.c
|
||||
index d6db862678..b02688e316 100644
|
||||
--- a/xen/drivers/passthrough/vtd/quirks.c
|
||||
+++ b/xen/drivers/passthrough/vtd/quirks.c
|
||||
@@ -540,3 +540,28 @@ void pci_vtd_quirk(const struct pci_dev *pdev)
|
||||
break;
|
||||
}
|
||||
}
|
||||
+
|
||||
+void __init quirk_iommu_caps(struct iommu *iommu)
|
||||
+{
|
||||
+ /*
|
||||
+ * IOMMU Quirks:
|
||||
+ *
|
||||
+ * SandyBridge IOMMUs claim support for 2M and 1G superpages, but don't
|
||||
+ * implement superpages internally.
|
||||
+ *
|
||||
+ * There are issues changing the walk length under in-flight DMA, which
|
||||
+ * has manifested as incompatibility between EPT/IOMMU sharing and the
|
||||
+ * workaround for CVE-2018-12207 / XSA-304. Hide the superpages
|
||||
+ * capabilities in the IOMMU, which will prevent Xen from sharing the EPT
|
||||
+ * and IOMMU pagetables.
|
||||
+ *
|
||||
+ * Detection of SandyBridge unfortunately has to be done by processor
|
||||
+ * model because the client parts don't expose their IOMMUs as PCI devices
|
||||
+ * we could match with a Device ID.
|
||||
+ */
|
||||
+ if ( boot_cpu_data.x86_vendor == X86_VENDOR_INTEL &&
|
||||
+ boot_cpu_data.x86 == 6 &&
|
||||
+ (boot_cpu_data.x86_model == 0x2a ||
|
||||
+ boot_cpu_data.x86_model == 0x2d) )
|
||||
+ iommu->cap &= ~(0xful << 34);
|
||||
+}
|
||||
|
|
@ -1,272 +0,0 @@
|
|||
From: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Subject: x86/vtx: Disable executable EPT superpages to work around
|
||||
CVE-2018-12207
|
||||
|
||||
CVE-2018-12207 covers a set of errata on various Intel processors, whereby a
|
||||
machine check exception can be generated in a corner case when an executable
|
||||
mapping changes size or cacheability without TLB invalidation. HVM guest
|
||||
kernels can trigger this to DoS the host.
|
||||
|
||||
To mitigate, in affected hardware, all EPT superpages are marked NX. When an
|
||||
instruction fetch violation is observed against the superpage, the superpage
|
||||
is shattered to 4k and has execute permissions restored. This prevents the
|
||||
guest kernel from being able to create the necessary preconditions in the iTLB
|
||||
to exploit the vulnerability.
|
||||
|
||||
This does come with a workload-dependent performance overhead, caused by
|
||||
increased TLB pressure. Performance can be restored, if guest kernels are
|
||||
trusted not to mount an attack, by specifying ept=exec-sp on the command line.
|
||||
|
||||
This is part of XSA-304 / CVE-2018-12207
|
||||
|
||||
Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Acked-by: George Dunlap <george.dunlap@citrix.com>
|
||||
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||||
|
||||
diff --git a/docs/misc/xen-command-line.pandoc b/docs/misc/xen-command-line.pandoc
|
||||
index 85081fdc94..e283017015 100644
|
||||
--- a/docs/misc/xen-command-line.pandoc
|
||||
+++ b/docs/misc/xen-command-line.pandoc
|
||||
@@ -895,7 +895,7 @@ Controls for interacting with the system Extended Firmware Interface.
|
||||
uncacheable.
|
||||
|
||||
### ept
|
||||
-> `= List of [ ad=<bool>, pml=<bool> ]`
|
||||
+> `= List of [ ad=<bool>, pml=<bool>, exec-sp=<bool> ]`
|
||||
|
||||
> Applicability: Intel
|
||||
|
||||
@@ -926,6 +926,16 @@ introduced with the Nehalem architecture.
|
||||
disable PML. `pml=0` can be used to prevent the use of PML on otherwise
|
||||
capable hardware.
|
||||
|
||||
+* The `exec-sp` boolean controls whether EPT superpages with execute
|
||||
+ permissions are permitted. In general this is good for performance.
|
||||
+
|
||||
+ However, on processors vulnerable CVE-2018-12207, HVM guest kernels can
|
||||
+ use executable superpages to crash the host. By default, executable
|
||||
+ superpages are disabled on affected hardware.
|
||||
+
|
||||
+ If HVM guest kernels are trusted not to mount a DoS against the system,
|
||||
+ this option can enabled to regain performance.
|
||||
+
|
||||
### extra_guest_irqs
|
||||
> `= [<domU number>][,<dom0 number>]`
|
||||
|
||||
diff --git a/xen/arch/x86/hvm/hvm.c b/xen/arch/x86/hvm/hvm.c
|
||||
index 2089a77270..84191d4e4b 100644
|
||||
--- a/xen/arch/x86/hvm/hvm.c
|
||||
+++ b/xen/arch/x86/hvm/hvm.c
|
||||
@@ -1814,6 +1814,24 @@ int hvm_hap_nested_page_fault(paddr_t gpa, unsigned long gla,
|
||||
break;
|
||||
}
|
||||
|
||||
+ /*
|
||||
+ * Workaround for XSA-304 / CVE-2018-12207. If we take an execution
|
||||
+ * fault against a non-executable superpage, shatter it to regain
|
||||
+ * execute permissions.
|
||||
+ */
|
||||
+ if ( page_order > 0 && npfec.insn_fetch && npfec.present && !violation )
|
||||
+ {
|
||||
+ int res = p2m_set_entry(p2m, _gfn(gfn), mfn, PAGE_ORDER_4K,
|
||||
+ p2mt, p2ma);
|
||||
+
|
||||
+ if ( res )
|
||||
+ printk(XENLOG_ERR "Failed to shatter gfn %"PRI_gfn": %d\n",
|
||||
+ gfn, res);
|
||||
+
|
||||
+ rc = !res;
|
||||
+ goto out_put_gfn;
|
||||
+ }
|
||||
+
|
||||
if ( violation )
|
||||
{
|
||||
/* Should #VE be emulated for this fault? */
|
||||
diff --git a/xen/arch/x86/hvm/vmx/vmcs.c b/xen/arch/x86/hvm/vmx/vmcs.c
|
||||
index 56519fee84..ec5ab860ad 100644
|
||||
--- a/xen/arch/x86/hvm/vmx/vmcs.c
|
||||
+++ b/xen/arch/x86/hvm/vmx/vmcs.c
|
||||
@@ -67,6 +67,7 @@ integer_param("ple_window", ple_window);
|
||||
|
||||
static bool __read_mostly opt_ept_pml = true;
|
||||
static s8 __read_mostly opt_ept_ad = -1;
|
||||
+int8_t __read_mostly opt_ept_exec_sp = -1;
|
||||
|
||||
static int __init parse_ept_param(const char *s)
|
||||
{
|
||||
@@ -82,6 +83,8 @@ static int __init parse_ept_param(const char *s)
|
||||
opt_ept_ad = val;
|
||||
else if ( (val = parse_boolean("pml", s, ss)) >= 0 )
|
||||
opt_ept_pml = val;
|
||||
+ else if ( (val = parse_boolean("exec-sp", s, ss)) >= 0 )
|
||||
+ opt_ept_exec_sp = val;
|
||||
else
|
||||
rc = -EINVAL;
|
||||
|
||||
diff --git a/xen/arch/x86/hvm/vmx/vmx.c b/xen/arch/x86/hvm/vmx/vmx.c
|
||||
index 26b7ddb5fe..28cba8ec28 100644
|
||||
--- a/xen/arch/x86/hvm/vmx/vmx.c
|
||||
+++ b/xen/arch/x86/hvm/vmx/vmx.c
|
||||
@@ -2445,6 +2445,102 @@ static void pi_notification_interrupt(struct cpu_user_regs *regs)
|
||||
static void __init lbr_tsx_fixup_check(void);
|
||||
static void __init bdw_erratum_bdf14_fixup_check(void);
|
||||
|
||||
+/*
|
||||
+ * Calculate whether the CPU is vulnerable to Instruction Fetch page
|
||||
+ * size-change MCEs.
|
||||
+ */
|
||||
+static bool __init has_if_pschange_mc(void)
|
||||
+{
|
||||
+ uint64_t caps = 0;
|
||||
+
|
||||
+ /*
|
||||
+ * If we are virtualised, there is nothing we can do. Our EPT tables are
|
||||
+ * shadowed by our hypervisor, and not walked by hardware.
|
||||
+ */
|
||||
+ if ( cpu_has_hypervisor )
|
||||
+ return false;
|
||||
+
|
||||
+ if ( boot_cpu_has(X86_FEATURE_ARCH_CAPS) )
|
||||
+ rdmsrl(MSR_ARCH_CAPABILITIES, caps);
|
||||
+
|
||||
+ if ( caps & ARCH_CAPS_IF_PSCHANGE_MC_NO )
|
||||
+ return false;
|
||||
+
|
||||
+ /*
|
||||
+ * IF_PSCHANGE_MC is only known to affect Intel Family 6 processors at
|
||||
+ * this time.
|
||||
+ */
|
||||
+ if ( boot_cpu_data.x86_vendor != X86_VENDOR_INTEL ||
|
||||
+ boot_cpu_data.x86 != 6 )
|
||||
+ return false;
|
||||
+
|
||||
+ switch ( boot_cpu_data.x86_model )
|
||||
+ {
|
||||
+ /*
|
||||
+ * Core processors since at least Nehalem are vulnerable.
|
||||
+ */
|
||||
+ case 0x1f: /* Auburndale / Havendale */
|
||||
+ case 0x1e: /* Nehalem */
|
||||
+ case 0x1a: /* Nehalem EP */
|
||||
+ case 0x2e: /* Nehalem EX */
|
||||
+ case 0x25: /* Westmere */
|
||||
+ case 0x2c: /* Westmere EP */
|
||||
+ case 0x2f: /* Westmere EX */
|
||||
+ case 0x2a: /* SandyBridge */
|
||||
+ case 0x2d: /* SandyBridge EP/EX */
|
||||
+ case 0x3a: /* IvyBridge */
|
||||
+ case 0x3e: /* IvyBridge EP/EX */
|
||||
+ case 0x3c: /* Haswell */
|
||||
+ case 0x3f: /* Haswell EX/EP */
|
||||
+ case 0x45: /* Haswell D */
|
||||
+ case 0x46: /* Haswell H */
|
||||
+ case 0x3d: /* Broadwell */
|
||||
+ case 0x47: /* Broadwell H */
|
||||
+ case 0x4f: /* Broadwell EP/EX */
|
||||
+ case 0x56: /* Broadwell D */
|
||||
+ case 0x4e: /* Skylake M */
|
||||
+ case 0x5e: /* Skylake D */
|
||||
+ case 0x55: /* Skylake-X / Cascade Lake */
|
||||
+ case 0x8e: /* Kaby / Coffee / Whiskey Lake M */
|
||||
+ case 0x9e: /* Kaby / Coffee / Whiskey Lake D */
|
||||
+ return true;
|
||||
+
|
||||
+ /*
|
||||
+ * Atom processors are not vulnerable.
|
||||
+ */
|
||||
+ case 0x1c: /* Pineview */
|
||||
+ case 0x26: /* Lincroft */
|
||||
+ case 0x27: /* Penwell */
|
||||
+ case 0x35: /* Cloverview */
|
||||
+ case 0x36: /* Cedarview */
|
||||
+ case 0x37: /* Baytrail / Valleyview (Silvermont) */
|
||||
+ case 0x4d: /* Avaton / Rangely (Silvermont) */
|
||||
+ case 0x4c: /* Cherrytrail / Brasswell */
|
||||
+ case 0x4a: /* Merrifield */
|
||||
+ case 0x5a: /* Moorefield */
|
||||
+ case 0x5c: /* Goldmont */
|
||||
+ case 0x5d: /* SoFIA 3G Granite/ES2.1 */
|
||||
+ case 0x65: /* SoFIA LTE AOSP */
|
||||
+ case 0x5f: /* Denverton */
|
||||
+ case 0x6e: /* Cougar Mountain */
|
||||
+ case 0x75: /* Lightning Mountain */
|
||||
+ case 0x7a: /* Gemini Lake */
|
||||
+ case 0x86: /* Jacobsville */
|
||||
+
|
||||
+ /*
|
||||
+ * Knights processors are not vulnerable.
|
||||
+ */
|
||||
+ case 0x57: /* Knights Landing */
|
||||
+ case 0x85: /* Knights Mill */
|
||||
+ return false;
|
||||
+
|
||||
+ default:
|
||||
+ printk("Unrecognised CPU model %#x - assuming vulnerable to IF_PSCHANGE_MC\n",
|
||||
+ boot_cpu_data.x86_model);
|
||||
+ return true;
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
const struct hvm_function_table * __init start_vmx(void)
|
||||
{
|
||||
set_in_cr4(X86_CR4_VMXE);
|
||||
@@ -2465,6 +2561,17 @@ const struct hvm_function_table * __init start_vmx(void)
|
||||
*/
|
||||
if ( cpu_has_vmx_ept && (cpu_has_vmx_pat || opt_force_ept) )
|
||||
{
|
||||
+ bool cpu_has_bug_pschange_mc = has_if_pschange_mc();
|
||||
+
|
||||
+ if ( opt_ept_exec_sp == -1 )
|
||||
+ {
|
||||
+ /* Default to non-executable superpages on vulnerable hardware. */
|
||||
+ opt_ept_exec_sp = !cpu_has_bug_pschange_mc;
|
||||
+
|
||||
+ if ( cpu_has_bug_pschange_mc )
|
||||
+ printk("VMX: Disabling executable EPT superpages due to CVE-2018-12207\n");
|
||||
+ }
|
||||
+
|
||||
vmx_function_table.hap_supported = 1;
|
||||
vmx_function_table.altp2m_supported = 1;
|
||||
|
||||
diff --git a/xen/arch/x86/mm/p2m-ept.c b/xen/arch/x86/mm/p2m-ept.c
|
||||
index 952ebad82f..834d4798c8 100644
|
||||
--- a/xen/arch/x86/mm/p2m-ept.c
|
||||
+++ b/xen/arch/x86/mm/p2m-ept.c
|
||||
@@ -174,6 +174,12 @@ static void ept_p2m_type_to_flags(struct p2m_domain *p2m, ept_entry_t *entry,
|
||||
break;
|
||||
}
|
||||
|
||||
+ /*
|
||||
+ * Don't create executable superpages if we need to shatter them to
|
||||
+ * protect against CVE-2018-12207.
|
||||
+ */
|
||||
+ if ( !opt_ept_exec_sp && is_epte_superpage(entry) )
|
||||
+ entry->x = 0;
|
||||
}
|
||||
|
||||
#define GUEST_TABLE_MAP_FAILED 0
|
||||
diff --git a/xen/include/asm-x86/hvm/vmx/vmx.h b/xen/include/asm-x86/hvm/vmx/vmx.h
|
||||
index ebaa74449b..371b912887 100644
|
||||
--- a/xen/include/asm-x86/hvm/vmx/vmx.h
|
||||
+++ b/xen/include/asm-x86/hvm/vmx/vmx.h
|
||||
@@ -28,6 +28,8 @@
|
||||
#include <asm/hvm/trace.h>
|
||||
#include <asm/hvm/vmx/vmcs.h>
|
||||
|
||||
+extern int8_t opt_ept_exec_sp;
|
||||
+
|
||||
typedef union {
|
||||
struct {
|
||||
u64 r : 1, /* bit 0 - Read permission */
|
||||
diff --git a/xen/include/asm-x86/msr-index.h b/xen/include/asm-x86/msr-index.h
|
||||
index 637259bd1f..32746aa8ae 100644
|
||||
--- a/xen/include/asm-x86/msr-index.h
|
||||
+++ b/xen/include/asm-x86/msr-index.h
|
||||
@@ -52,6 +52,7 @@
|
||||
#define ARCH_CAPS_SKIP_L1DFL (_AC(1, ULL) << 3)
|
||||
#define ARCH_CAPS_SSB_NO (_AC(1, ULL) << 4)
|
||||
#define ARCH_CAPS_MDS_NO (_AC(1, ULL) << 5)
|
||||
+#define ARCH_CAPS_IF_PSCHANGE_MC_NO (_AC(1, ULL) << 6)
|
||||
|
||||
#define MSR_FLUSH_CMD 0x0000010b
|
||||
#define FLUSH_CMD_L1D (_AC(1, ULL) << 0)
|
||||
|
|
@ -1,108 +0,0 @@
|
|||
From: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Subject: x86/vtx: Allow runtime modification of the exec-sp setting
|
||||
|
||||
See patch for details.
|
||||
|
||||
Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||||
Reviewed-by: George Dunlap <george.dunlap@citrix.com>
|
||||
|
||||
diff --git a/docs/misc/xen-command-line.pandoc b/docs/misc/xen-command-line.pandoc
|
||||
index e283017015..84221fe60a 100644
|
||||
--- a/docs/misc/xen-command-line.pandoc
|
||||
+++ b/docs/misc/xen-command-line.pandoc
|
||||
@@ -936,6 +936,21 @@ introduced with the Nehalem architecture.
|
||||
If HVM guest kernels are trusted not to mount a DoS against the system,
|
||||
this option can enabled to regain performance.
|
||||
|
||||
+ This boolean may be modified at runtime using `xl set-parameters
|
||||
+ ept=[no-]exec-sp` to switch between fast and secure.
|
||||
+
|
||||
+ * When switching from secure to fast, preexisting HVM domains will run
|
||||
+ at their current performance until they are rebooted; new domains will
|
||||
+ run without any overhead.
|
||||
+
|
||||
+ * When switching from fast to secure, all HVM domains will immediately
|
||||
+ suffer a performance penalty.
|
||||
+
|
||||
+ **Warning: No guarantee is made that this runtime option will be retained
|
||||
+ indefinitely, or that it will retain this exact behaviour. It is
|
||||
+ intended as an emergency option for people who first chose fast, then
|
||||
+ change their minds to secure, and wish not to reboot.**
|
||||
+
|
||||
### extra_guest_irqs
|
||||
> `= [<domU number>][,<dom0 number>]`
|
||||
|
||||
diff --git a/xen/arch/x86/hvm/vmx/vmcs.c b/xen/arch/x86/hvm/vmx/vmcs.c
|
||||
index ec5ab860ad..c4d8a5ba78 100644
|
||||
--- a/xen/arch/x86/hvm/vmx/vmcs.c
|
||||
+++ b/xen/arch/x86/hvm/vmx/vmcs.c
|
||||
@@ -95,6 +95,41 @@ static int __init parse_ept_param(const char *s)
|
||||
}
|
||||
custom_param("ept", parse_ept_param);
|
||||
|
||||
+static int parse_ept_param_runtime(const char *s)
|
||||
+{
|
||||
+ int val;
|
||||
+
|
||||
+ if ( !cpu_has_vmx_ept || !hvm_funcs.hap_supported ||
|
||||
+ !(hvm_funcs.hap_capabilities &
|
||||
+ (HVM_HAP_SUPERPAGE_2MB | HVM_HAP_SUPERPAGE_1GB)) )
|
||||
+ {
|
||||
+ printk("VMX: EPT not available, or not in use - ignoring\n");
|
||||
+ return 0;
|
||||
+ }
|
||||
+
|
||||
+ if ( (val = parse_boolean("exec-sp", s, NULL)) < 0 )
|
||||
+ return -EINVAL;
|
||||
+
|
||||
+ if ( val != opt_ept_exec_sp )
|
||||
+ {
|
||||
+ struct domain *d;
|
||||
+
|
||||
+ opt_ept_exec_sp = val;
|
||||
+
|
||||
+ rcu_read_lock(&domlist_read_lock);
|
||||
+ for_each_domain ( d )
|
||||
+ if ( paging_mode_hap(d) )
|
||||
+ p2m_change_entry_type_global(d, p2m_ram_rw, p2m_ram_rw);
|
||||
+ rcu_read_unlock(&domlist_read_lock);
|
||||
+ }
|
||||
+
|
||||
+ printk("VMX: EPT executable superpages %sabled\n",
|
||||
+ val ? "en" : "dis");
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+custom_runtime_only_param("ept", parse_ept_param_runtime);
|
||||
+
|
||||
/* Dynamic (run-time adjusted) execution control flags. */
|
||||
u32 vmx_pin_based_exec_control __read_mostly;
|
||||
u32 vmx_cpu_based_exec_control __read_mostly;
|
||||
diff --git a/xen/arch/x86/mm/p2m.c b/xen/arch/x86/mm/p2m.c
|
||||
index f518f86493..16608098b1 100644
|
||||
--- a/xen/arch/x86/mm/p2m.c
|
||||
+++ b/xen/arch/x86/mm/p2m.c
|
||||
@@ -289,15 +289,20 @@ static void change_entry_type_global(struct p2m_domain *p2m,
|
||||
p2m_type_t ot, p2m_type_t nt)
|
||||
{
|
||||
p2m->change_entry_type_global(p2m, ot, nt);
|
||||
- p2m->global_logdirty = (nt == p2m_ram_logdirty);
|
||||
+ /* Don't allow 'recalculate' operations to change the logdirty state. */
|
||||
+ if ( ot != nt )
|
||||
+ p2m->global_logdirty = (nt == p2m_ram_logdirty);
|
||||
}
|
||||
|
||||
+/*
|
||||
+ * May be called with ot = nt = p2m_ram_rw for its side effect of
|
||||
+ * recalculating all PTEs in the p2m.
|
||||
+ */
|
||||
void p2m_change_entry_type_global(struct domain *d,
|
||||
p2m_type_t ot, p2m_type_t nt)
|
||||
{
|
||||
struct p2m_domain *hostp2m = p2m_get_hostp2m(d);
|
||||
|
||||
- ASSERT(ot != nt);
|
||||
ASSERT(p2m_is_changeable(ot) && p2m_is_changeable(nt));
|
||||
|
||||
p2m_lock(hostp2m);
|
||||
|
|
@ -1,288 +0,0 @@
|
|||
From: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Subject: x86/tsx: Introduce tsx= to use MSR_TSX_CTRL when available
|
||||
|
||||
To protect against the TSX Async Abort speculative vulnerability, Intel have
|
||||
released new microcode for affected parts which introduce the MSR_TSX_CTRL
|
||||
control, which allows TSX to be turned off. This will be architectural on
|
||||
future parts.
|
||||
|
||||
Introduce tsx= to provide a global on/off for TSX, including its enumeration
|
||||
via CPUID. Provide stub virtualisation of this MSR, as it is not exposed to
|
||||
guests at the moment.
|
||||
|
||||
VMs may have booted before microcode is loaded, or before hosts have rebooted,
|
||||
and they still want to migrate freely. A VM which booted seeing TSX can
|
||||
migrate safely to hosts with TSX disabled - TSX will start unconditionally
|
||||
aborting, but still behave in a manner compatible with the ABI.
|
||||
|
||||
The guest-visible behaviour is equivalent to late loading the microcode and
|
||||
setting the RTM_DISABLE bit in the course of live patching.
|
||||
|
||||
This is part of XSA-305 / CVE-2019-11135
|
||||
|
||||
Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||||
|
||||
diff --git a/docs/misc/xen-command-line.pandoc b/docs/misc/xen-command-line.pandoc
|
||||
index e283017015..b7e1bf8e8b 100644
|
||||
--- a/docs/misc/xen-command-line.pandoc
|
||||
+++ b/docs/misc/xen-command-line.pandoc
|
||||
@@ -2033,6 +2033,20 @@ Xen version.
|
||||
### tsc (x86)
|
||||
> `= unstable | skewed | stable:socket`
|
||||
|
||||
+### tsx
|
||||
+ = <bool>
|
||||
+
|
||||
+ Applicability: x86
|
||||
+ Default: true
|
||||
+
|
||||
+Controls for the use of Transactional Synchronization eXtensions.
|
||||
+
|
||||
+On Intel parts released in Q3 2019 (with updated microcode), and future parts,
|
||||
+a control has been introduced which allows TSX to be turned off.
|
||||
+
|
||||
+On systems with the ability to turn TSX off, this boolean offers system wide
|
||||
+control of whether TSX is enabled or disabled.
|
||||
+
|
||||
### ucode (x86)
|
||||
> `= [<integer> | scan]`
|
||||
|
||||
diff --git a/xen/arch/x86/Makefile b/xen/arch/x86/Makefile
|
||||
index 8a8d8f060f..9b9a4435fb 100644
|
||||
--- a/xen/arch/x86/Makefile
|
||||
+++ b/xen/arch/x86/Makefile
|
||||
@@ -66,6 +66,7 @@ obj-y += sysctl.o
|
||||
obj-y += time.o
|
||||
obj-y += trace.o
|
||||
obj-y += traps.o
|
||||
+obj-y += tsx.o
|
||||
obj-y += usercopy.o
|
||||
obj-y += x86_emulate.o
|
||||
obj-$(CONFIG_TBOOT) += tboot.o
|
||||
diff --git a/xen/arch/x86/cpuid.c b/xen/arch/x86/cpuid.c
|
||||
index 57e80694f2..1727497459 100644
|
||||
--- a/xen/arch/x86/cpuid.c
|
||||
+++ b/xen/arch/x86/cpuid.c
|
||||
@@ -524,6 +524,20 @@ void recalculate_cpuid_policy(struct domain *d)
|
||||
if ( cpu_has_itsc && (d->disable_migrate || d->arch.vtsc) )
|
||||
__set_bit(X86_FEATURE_ITSC, max_fs);
|
||||
|
||||
+ /*
|
||||
+ * On hardware with MSR_TSX_CTRL, the admin may have elected to disable
|
||||
+ * TSX and hide the feature bits. Migrating-in VMs may have been booted
|
||||
+ * pre-mitigation when the TSX features were visbile.
|
||||
+ *
|
||||
+ * This situation is compatible (albeit with a perf hit to any TSX code in
|
||||
+ * the guest), so allow the feature bits to remain set.
|
||||
+ */
|
||||
+ if ( cpu_has_tsx_ctrl )
|
||||
+ {
|
||||
+ __set_bit(X86_FEATURE_HLE, max_fs);
|
||||
+ __set_bit(X86_FEATURE_RTM, max_fs);
|
||||
+ }
|
||||
+
|
||||
/* Clamp the toolstacks choices to reality. */
|
||||
for ( i = 0; i < ARRAY_SIZE(fs); i++ )
|
||||
fs[i] &= max_fs[i];
|
||||
diff --git a/xen/arch/x86/msr.c b/xen/arch/x86/msr.c
|
||||
index 56de0fe9e1..c2722d7c73 100644
|
||||
--- a/xen/arch/x86/msr.c
|
||||
+++ b/xen/arch/x86/msr.c
|
||||
@@ -132,6 +132,7 @@ int guest_rdmsr(const struct vcpu *v, uint32_t msr, uint64_t *val)
|
||||
case MSR_FLUSH_CMD:
|
||||
/* Write-only */
|
||||
case MSR_TSX_FORCE_ABORT:
|
||||
+ case MSR_TSX_CTRL:
|
||||
/* Not offered to guests. */
|
||||
goto gp_fault;
|
||||
|
||||
@@ -260,6 +261,7 @@ int guest_wrmsr(struct vcpu *v, uint32_t msr, uint64_t val)
|
||||
case MSR_ARCH_CAPABILITIES:
|
||||
/* Read-only */
|
||||
case MSR_TSX_FORCE_ABORT:
|
||||
+ case MSR_TSX_CTRL:
|
||||
/* Not offered to guests. */
|
||||
goto gp_fault;
|
||||
|
||||
diff --git a/xen/arch/x86/setup.c b/xen/arch/x86/setup.c
|
||||
index cf790f36ef..c1c7c44000 100644
|
||||
--- a/xen/arch/x86/setup.c
|
||||
+++ b/xen/arch/x86/setup.c
|
||||
@@ -1594,6 +1594,8 @@ void __init noreturn __start_xen(unsigned long mbi_p)
|
||||
|
||||
early_microcode_init();
|
||||
|
||||
+ tsx_init(); /* Needs microcode. May change HLE/RTM feature bits. */
|
||||
+
|
||||
identify_cpu(&boot_cpu_data);
|
||||
|
||||
set_in_cr4(X86_CR4_OSFXSR | X86_CR4_OSXMMEXCPT);
|
||||
diff --git a/xen/arch/x86/smpboot.c b/xen/arch/x86/smpboot.c
|
||||
index 737a44f055..e21cf0a310 100644
|
||||
--- a/xen/arch/x86/smpboot.c
|
||||
+++ b/xen/arch/x86/smpboot.c
|
||||
@@ -376,6 +376,8 @@ void start_secondary(void *unused)
|
||||
if ( boot_cpu_has(X86_FEATURE_IBRSB) )
|
||||
wrmsrl(MSR_SPEC_CTRL, default_xen_spec_ctrl);
|
||||
|
||||
+ tsx_init(); /* Needs microcode. May change HLE/RTM feature bits. */
|
||||
+
|
||||
if ( xen_guest )
|
||||
hypervisor_ap_setup();
|
||||
|
||||
diff --git a/xen/arch/x86/tsx.c b/xen/arch/x86/tsx.c
|
||||
new file mode 100644
|
||||
index 0000000000..a8ec2ccc69
|
||||
--- /dev/null
|
||||
+++ b/xen/arch/x86/tsx.c
|
||||
@@ -0,0 +1,74 @@
|
||||
+#include <xen/init.h>
|
||||
+#include <asm/msr.h>
|
||||
+
|
||||
+/*
|
||||
+ * Valid values:
|
||||
+ * 1 => Explicit tsx=1
|
||||
+ * 0 => Explicit tsx=0
|
||||
+ * -1 => Default, implicit tsx=1
|
||||
+ *
|
||||
+ * This is arranged such that the bottom bit encodes whether TSX is actually
|
||||
+ * disabled, while identifying various explicit (>=0) and implicit (<0)
|
||||
+ * conditions.
|
||||
+ */
|
||||
+int8_t __read_mostly opt_tsx = -1;
|
||||
+int8_t __read_mostly cpu_has_tsx_ctrl = -1;
|
||||
+
|
||||
+static int __init parse_tsx(const char *s)
|
||||
+{
|
||||
+ int rc = 0, val = parse_bool(s, NULL);
|
||||
+
|
||||
+ if ( val >= 0 )
|
||||
+ opt_tsx = val;
|
||||
+ else
|
||||
+ rc = -EINVAL;
|
||||
+
|
||||
+ return rc;
|
||||
+}
|
||||
+custom_param("tsx", parse_tsx);
|
||||
+
|
||||
+void tsx_init(void)
|
||||
+{
|
||||
+ /*
|
||||
+ * This function is first called between microcode being loaded, and CPUID
|
||||
+ * being scanned generally. Calculate from raw data whether MSR_TSX_CTRL
|
||||
+ * is available.
|
||||
+ */
|
||||
+ if ( unlikely(cpu_has_tsx_ctrl < 0) )
|
||||
+ {
|
||||
+ uint64_t caps = 0;
|
||||
+
|
||||
+ if ( boot_cpu_data.cpuid_level >= 7 &&
|
||||
+ (cpuid_count_edx(7, 0) & cpufeat_mask(X86_FEATURE_ARCH_CAPS)) )
|
||||
+ rdmsrl(MSR_ARCH_CAPABILITIES, caps);
|
||||
+
|
||||
+ cpu_has_tsx_ctrl = !!(caps & ARCH_CAPS_TSX_CTRL);
|
||||
+ }
|
||||
+
|
||||
+ if ( cpu_has_tsx_ctrl )
|
||||
+ {
|
||||
+ uint64_t val;
|
||||
+
|
||||
+ rdmsrl(MSR_TSX_CTRL, val);
|
||||
+
|
||||
+ val &= ~(TSX_CTRL_RTM_DISABLE | TSX_CTRL_CPUID_CLEAR);
|
||||
+ /* Check bottom bit only. Higher bits are various sentinals. */
|
||||
+ if ( !(opt_tsx & 1) )
|
||||
+ val |= TSX_CTRL_RTM_DISABLE | TSX_CTRL_CPUID_CLEAR;
|
||||
+
|
||||
+ wrmsrl(MSR_TSX_CTRL, val);
|
||||
+ }
|
||||
+ else if ( opt_tsx >= 0 )
|
||||
+ printk_once(XENLOG_WARNING
|
||||
+ "MSR_TSX_CTRL not available - Ignoring tsx= setting\n");
|
||||
+}
|
||||
+
|
||||
+/*
|
||||
+ * Local variables:
|
||||
+ * mode: C
|
||||
+ * c-file-style: "BSD"
|
||||
+ * c-basic-offset: 4
|
||||
+ * tab-width: 4
|
||||
+ * indent-tabs-mode: nil
|
||||
+ * End:
|
||||
+ */
|
||||
diff --git a/xen/include/asm-x86/msr-index.h b/xen/include/asm-x86/msr-index.h
|
||||
index 32746aa8ae..d5f3899f73 100644
|
||||
--- a/xen/include/asm-x86/msr-index.h
|
||||
+++ b/xen/include/asm-x86/msr-index.h
|
||||
@@ -53,6 +53,7 @@
|
||||
#define ARCH_CAPS_SSB_NO (_AC(1, ULL) << 4)
|
||||
#define ARCH_CAPS_MDS_NO (_AC(1, ULL) << 5)
|
||||
#define ARCH_CAPS_IF_PSCHANGE_MC_NO (_AC(1, ULL) << 6)
|
||||
+#define ARCH_CAPS_TSX_CTRL (_AC(1, ULL) << 7)
|
||||
|
||||
#define MSR_FLUSH_CMD 0x0000010b
|
||||
#define FLUSH_CMD_L1D (_AC(1, ULL) << 0)
|
||||
@@ -60,6 +61,10 @@
|
||||
#define MSR_TSX_FORCE_ABORT 0x0000010f
|
||||
#define TSX_FORCE_ABORT_RTM (_AC(1, ULL) << 0)
|
||||
|
||||
+#define MSR_TSX_CTRL 0x00000122
|
||||
+#define TSX_CTRL_RTM_DISABLE (_AC(1, ULL) << 0)
|
||||
+#define TSX_CTRL_CPUID_CLEAR (_AC(1, ULL) << 1)
|
||||
+
|
||||
/* Intel MSRs. Some also available on other CPUs */
|
||||
#define MSR_IA32_PERFCTR0 0x000000c1
|
||||
#define MSR_IA32_A_PERFCTR0 0x000004c1
|
||||
diff --git a/xen/include/asm-x86/processor.h b/xen/include/asm-x86/processor.h
|
||||
index d33ac34d29..1b52712180 100644
|
||||
--- a/xen/include/asm-x86/processor.h
|
||||
+++ b/xen/include/asm-x86/processor.h
|
||||
@@ -263,6 +263,16 @@ static always_inline unsigned int cpuid_count_ebx(
|
||||
return ebx;
|
||||
}
|
||||
|
||||
+static always_inline unsigned int cpuid_count_edx(
|
||||
+ unsigned int leaf, unsigned int subleaf)
|
||||
+{
|
||||
+ unsigned int edx, tmp;
|
||||
+
|
||||
+ cpuid_count(leaf, subleaf, &tmp, &tmp, &tmp, &edx);
|
||||
+
|
||||
+ return edx;
|
||||
+}
|
||||
+
|
||||
static inline unsigned long read_cr0(void)
|
||||
{
|
||||
unsigned long cr0;
|
||||
@@ -609,6 +619,9 @@ static inline uint8_t get_cpu_family(uint32_t raw, uint8_t *model,
|
||||
return fam;
|
||||
}
|
||||
|
||||
+extern int8_t opt_tsx, cpu_has_tsx_ctrl;
|
||||
+void tsx_init(void);
|
||||
+
|
||||
#endif /* !__ASSEMBLY__ */
|
||||
|
||||
#endif /* __ASM_X86_PROCESSOR_H */
|
||||
diff --git a/xen/include/xen/lib.h b/xen/include/xen/lib.h
|
||||
index 89939f43c8..6529f12dae 100644
|
||||
--- a/xen/include/xen/lib.h
|
||||
+++ b/xen/include/xen/lib.h
|
||||
@@ -114,6 +114,16 @@ extern int printk_ratelimit(void);
|
||||
#define gprintk(lvl, fmt, args...) \
|
||||
printk(XENLOG_GUEST lvl "%pv " fmt, current, ## args)
|
||||
|
||||
+#define printk_once(fmt, args...) \
|
||||
+({ \
|
||||
+ static bool __read_mostly once_; \
|
||||
+ if ( unlikely(!once_) ) \
|
||||
+ { \
|
||||
+ once_ = true; \
|
||||
+ printk(fmt, ## args); \
|
||||
+ } \
|
||||
+})
|
||||
+
|
||||
#ifdef NDEBUG
|
||||
|
||||
static inline void
|
||||
|
|
@ -1,192 +0,0 @@
|
|||
From: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Subject: x86/spec-ctrl: Mitigate the TSX Asynchronous Abort sidechannel
|
||||
|
||||
See patch documentation and comments.
|
||||
|
||||
This is part of XSA-305 / CVE-2019-11135
|
||||
|
||||
Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||||
|
||||
diff --git a/docs/misc/xen-command-line.pandoc b/docs/misc/xen-command-line.pandoc
|
||||
index b7e1bf8e8b..74e1e35b88 100644
|
||||
--- a/docs/misc/xen-command-line.pandoc
|
||||
+++ b/docs/misc/xen-command-line.pandoc
|
||||
@@ -1920,7 +1920,7 @@ extreme care.**
|
||||
An overall boolean value, `spec-ctrl=no`, can be specified to turn off all
|
||||
mitigations, including pieces of infrastructure used to virtualise certain
|
||||
mitigation features for guests. This also includes settings which `xpti`,
|
||||
-`smt`, `pv-l1tf` control, unless the respective option(s) have been
|
||||
+`smt`, `pv-l1tf`, `tsx` control, unless the respective option(s) have been
|
||||
specified earlier on the command line.
|
||||
|
||||
Alternatively, a slightly more restricted `spec-ctrl=no-xen` can be used to
|
||||
@@ -2037,7 +2037,7 @@ Xen version.
|
||||
= <bool>
|
||||
|
||||
Applicability: x86
|
||||
- Default: true
|
||||
+ Default: false on parts vulnerable to TAA, true otherwise
|
||||
|
||||
Controls for the use of Transactional Synchronization eXtensions.
|
||||
|
||||
@@ -2047,6 +2047,19 @@ a control has been introduced which allows TSX to be turned off.
|
||||
On systems with the ability to turn TSX off, this boolean offers system wide
|
||||
control of whether TSX is enabled or disabled.
|
||||
|
||||
+On parts vulnerable to CVE-2019-11135 / TSX Asynchronous Abort, the following
|
||||
+logic applies:
|
||||
+
|
||||
+ * An explicit `tsx=` choice is honoured, even if it is `true` and would
|
||||
+ result in a vulnerable system.
|
||||
+
|
||||
+ * When no explicit `tsx=` choice is given, parts vulnerable to TAA will be
|
||||
+ mitigated by disabling TSX, as this is the lowest overhead option.
|
||||
+
|
||||
+ * If the use of TSX is important, the more expensive TAA mitigations can be
|
||||
+ opted in to with `smt=0 spec-ctrl=md-clear`, at which point TSX will remain
|
||||
+ active by default.
|
||||
+
|
||||
### ucode (x86)
|
||||
> `= [<integer> | scan]`
|
||||
|
||||
diff --git a/xen/arch/x86/spec_ctrl.c b/xen/arch/x86/spec_ctrl.c
|
||||
index b37d40e643..800139d79c 100644
|
||||
--- a/xen/arch/x86/spec_ctrl.c
|
||||
+++ b/xen/arch/x86/spec_ctrl.c
|
||||
@@ -96,6 +96,9 @@ static int __init parse_spec_ctrl(const char *s)
|
||||
if ( opt_pv_l1tf_domu < 0 )
|
||||
opt_pv_l1tf_domu = 0;
|
||||
|
||||
+ if ( opt_tsx == -1 )
|
||||
+ opt_tsx = -3;
|
||||
+
|
||||
disable_common:
|
||||
opt_rsb_pv = false;
|
||||
opt_rsb_hvm = false;
|
||||
@@ -306,7 +309,7 @@ static void __init print_details(enum ind_thunk thunk, uint64_t caps)
|
||||
printk("Speculative mitigation facilities:\n");
|
||||
|
||||
/* Hardware features which pertain to speculative mitigations. */
|
||||
- printk(" Hardware features:%s%s%s%s%s%s%s%s%s%s%s%s\n",
|
||||
+ printk(" Hardware features:%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n",
|
||||
(_7d0 & cpufeat_mask(X86_FEATURE_IBRSB)) ? " IBRS/IBPB" : "",
|
||||
(_7d0 & cpufeat_mask(X86_FEATURE_STIBP)) ? " STIBP" : "",
|
||||
(_7d0 & cpufeat_mask(X86_FEATURE_L1D_FLUSH)) ? " L1D_FLUSH" : "",
|
||||
@@ -318,7 +321,9 @@ static void __init print_details(enum ind_thunk thunk, uint64_t caps)
|
||||
(caps & ARCH_CAPS_RSBA) ? " RSBA" : "",
|
||||
(caps & ARCH_CAPS_SKIP_L1DFL) ? " SKIP_L1DFL": "",
|
||||
(caps & ARCH_CAPS_SSB_NO) ? " SSB_NO" : "",
|
||||
- (caps & ARCH_CAPS_MDS_NO) ? " MDS_NO" : "");
|
||||
+ (caps & ARCH_CAPS_MDS_NO) ? " MDS_NO" : "",
|
||||
+ (caps & ARCH_CAPS_TSX_CTRL) ? " TSX_CTRL" : "",
|
||||
+ (caps & ARCH_CAPS_TAA_NO) ? " TAA_NO" : "");
|
||||
|
||||
/* Compiled-in support which pertains to mitigations. */
|
||||
if ( IS_ENABLED(CONFIG_INDIRECT_THUNK) || IS_ENABLED(CONFIG_SHADOW_PAGING) )
|
||||
@@ -332,7 +337,7 @@ static void __init print_details(enum ind_thunk thunk, uint64_t caps)
|
||||
"\n");
|
||||
|
||||
/* Settings for Xen's protection, irrespective of guests. */
|
||||
- printk(" Xen settings: BTI-Thunk %s, SPEC_CTRL: %s%s, Other:%s%s%s\n",
|
||||
+ printk(" Xen settings: BTI-Thunk %s, SPEC_CTRL: %s%s%s, Other:%s%s%s\n",
|
||||
thunk == THUNK_NONE ? "N/A" :
|
||||
thunk == THUNK_RETPOLINE ? "RETPOLINE" :
|
||||
thunk == THUNK_LFENCE ? "LFENCE" :
|
||||
@@ -341,6 +346,8 @@ static void __init print_details(enum ind_thunk thunk, uint64_t caps)
|
||||
(default_xen_spec_ctrl & SPEC_CTRL_IBRS) ? "IBRS+" : "IBRS-",
|
||||
!boot_cpu_has(X86_FEATURE_SSBD) ? "" :
|
||||
(default_xen_spec_ctrl & SPEC_CTRL_SSBD) ? " SSBD+" : " SSBD-",
|
||||
+ !(caps & ARCH_CAPS_TSX_CTRL) ? "" :
|
||||
+ (opt_tsx & 1) ? " TSX+" : " TSX-",
|
||||
opt_ibpb ? " IBPB" : "",
|
||||
opt_l1d_flush ? " L1D_FLUSH" : "",
|
||||
opt_md_clear_pv || opt_md_clear_hvm ? " VERW" : "");
|
||||
@@ -862,6 +869,7 @@ void __init init_speculation_mitigations(void)
|
||||
{
|
||||
enum ind_thunk thunk = THUNK_DEFAULT;
|
||||
bool use_spec_ctrl = false, ibrs = false, hw_smt_enabled;
|
||||
+ bool cpu_has_bug_taa;
|
||||
uint64_t caps = 0;
|
||||
|
||||
if ( boot_cpu_has(X86_FEATURE_ARCH_CAPS) )
|
||||
@@ -1086,6 +1094,53 @@ void __init init_speculation_mitigations(void)
|
||||
"enabled. Mitigations will not be fully effective. Please\n"
|
||||
"choose an explicit smt=<bool> setting. See XSA-297.\n");
|
||||
|
||||
+ /*
|
||||
+ * Vulnerability to TAA is a little complicated to quantify.
|
||||
+ *
|
||||
+ * In the pipeline, it is just another way to get speculative access to
|
||||
+ * stale load port, store buffer or fill buffer data, and therefore can be
|
||||
+ * considered a superset of MDS (on TSX-capable parts). On parts which
|
||||
+ * predate MDS_NO, the existing VERW flushing will mitigate this
|
||||
+ * sidechannel as well.
|
||||
+ *
|
||||
+ * On parts which contain MDS_NO, the lack of VERW flushing means that an
|
||||
+ * attacker can still use TSX to target microarchitectural buffers to leak
|
||||
+ * secrets. Therefore, we consider TAA to be the set of TSX-capable parts
|
||||
+ * which have MDS_NO but lack TAA_NO.
|
||||
+ *
|
||||
+ * Note: cpu_has_rtm (== hle) could already be hidden by `tsx=0` on the
|
||||
+ * cmdline. MSR_TSX_CTRL will only appear on TSX-capable parts, so
|
||||
+ * we check both to spot TSX in a microcode/cmdline independent way.
|
||||
+ */
|
||||
+ cpu_has_bug_taa =
|
||||
+ (cpu_has_rtm || (caps & ARCH_CAPS_TSX_CTRL)) &&
|
||||
+ (caps & (ARCH_CAPS_MDS_NO | ARCH_CAPS_TAA_NO)) == ARCH_CAPS_MDS_NO;
|
||||
+
|
||||
+ /*
|
||||
+ * On TAA-affected hardware, disabling TSX is the preferred mitigation, vs
|
||||
+ * the MDS mitigation of disabling HT and using VERW flushing.
|
||||
+ *
|
||||
+ * On CPUs which advertise MDS_NO, VERW has no flushing side effect until
|
||||
+ * the TSX_CTRL microcode is loaded, despite the MD_CLEAR CPUID bit being
|
||||
+ * advertised, and there isn't a MD_CLEAR_2 flag to use...
|
||||
+ *
|
||||
+ * If we're on affected hardware, able to do something about it (which
|
||||
+ * implies that VERW now works), no explicit TSX choice and traditional
|
||||
+ * MDS mitigations (no-SMT, VERW) not obviosuly in use (someone might
|
||||
+ * plausibly value TSX higher than Hyperthreading...), disable TSX to
|
||||
+ * mitigate TAA.
|
||||
+ */
|
||||
+ if ( opt_tsx == -1 && cpu_has_bug_taa && (caps & ARCH_CAPS_TSX_CTRL) &&
|
||||
+ ((hw_smt_enabled && opt_smt) ||
|
||||
+ !boot_cpu_has(X86_FEATURE_SC_VERW_IDLE)) )
|
||||
+ {
|
||||
+ setup_clear_cpu_cap(X86_FEATURE_HLE);
|
||||
+ setup_clear_cpu_cap(X86_FEATURE_RTM);
|
||||
+
|
||||
+ opt_tsx = 0;
|
||||
+ tsx_init();
|
||||
+ }
|
||||
+
|
||||
print_details(thunk, caps);
|
||||
|
||||
/*
|
||||
diff --git a/xen/arch/x86/tsx.c b/xen/arch/x86/tsx.c
|
||||
index a8ec2ccc69..2d202a0d4e 100644
|
||||
--- a/xen/arch/x86/tsx.c
|
||||
+++ b/xen/arch/x86/tsx.c
|
||||
@@ -5,7 +5,8 @@
|
||||
* Valid values:
|
||||
* 1 => Explicit tsx=1
|
||||
* 0 => Explicit tsx=0
|
||||
- * -1 => Default, implicit tsx=1
|
||||
+ * -1 => Default, implicit tsx=1, may change to 0 to mitigate TAA
|
||||
+ * -3 => Implicit tsx=1 (feed-through from spec-ctrl=0)
|
||||
*
|
||||
* This is arranged such that the bottom bit encodes whether TSX is actually
|
||||
* disabled, while identifying various explicit (>=0) and implicit (<0)
|
||||
diff --git a/xen/include/asm-x86/msr-index.h b/xen/include/asm-x86/msr-index.h
|
||||
index d5f3899f73..3971b992d3 100644
|
||||
--- a/xen/include/asm-x86/msr-index.h
|
||||
+++ b/xen/include/asm-x86/msr-index.h
|
||||
@@ -54,6 +54,7 @@
|
||||
#define ARCH_CAPS_MDS_NO (_AC(1, ULL) << 5)
|
||||
#define ARCH_CAPS_IF_PSCHANGE_MC_NO (_AC(1, ULL) << 6)
|
||||
#define ARCH_CAPS_TSX_CTRL (_AC(1, ULL) << 7)
|
||||
+#define ARCH_CAPS_TAA_NO (_AC(1, ULL) << 8)
|
||||
|
||||
#define MSR_FLUSH_CMD 0x0000010b
|
||||
#define FLUSH_CMD_L1D (_AC(1, ULL) << 0)
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
From: Jan Beulich <jbeulich@suse.com>
|
||||
Subject: IOMMU: default to always quarantining PCI devices
|
||||
|
||||
XSA-302 relies on the use of libxl's "assignable-add" feature to prepare
|
||||
devices to be assigned to untrusted guests.
|
||||
|
||||
Unfortunately, this is not considered a strictly required step for
|
||||
device assignment. The PCI passthrough documentation on the wiki
|
||||
describes alternate ways of preparing devices for assignment, and
|
||||
libvirt uses its own ways as well. Hosts where these alternate methods
|
||||
are used will still leave the system in a vulnerable state after the
|
||||
device comes back from a guest.
|
||||
|
||||
Default to always quarantining PCI devices, but provide a command line
|
||||
option to revert back to prior behavior (such that people who both
|
||||
sufficiently trust their guests and want to be able to use devices in
|
||||
Dom0 again after they had been in use by a guest wouldn't need to
|
||||
"manually" move such devices back from DomIO to Dom0).
|
||||
|
||||
This is XSA-306.
|
||||
|
||||
Reported-by: Marek Marczykowski-Górecki <marmarek@invisiblethingslab.com>
|
||||
Signed-off-by: Jan Beulich <jbeulich@suse.com>
|
||||
Reviewed-by: Wei Liu <wl@xen.org>
|
||||
|
||||
--- a/docs/misc/xen-command-line.pandoc
|
||||
+++ b/docs/misc/xen-command-line.pandoc
|
||||
@@ -1171,7 +1171,7 @@ detection of systems known to misbehave
|
||||
> Default: `new` unless directed-EOI is supported
|
||||
|
||||
### iommu
|
||||
- = List of [ <bool>, verbose, debug, force, required,
|
||||
+ = List of [ <bool>, verbose, debug, force, required, quarantine,
|
||||
sharept, intremap, intpost, crash-disable,
|
||||
snoop, qinval, igfx, amd-iommu-perdev-intremap,
|
||||
dom0-{passthrough,strict} ]
|
||||
@@ -1209,6 +1209,12 @@ boolean (e.g. `iommu=no`) can override t
|
||||
will prevent Xen from booting if IOMMUs aren't discovered and enabled
|
||||
successfully.
|
||||
|
||||
+* The `quarantine` boolean can be used to control Xen's behavior when
|
||||
+ de-assigning devices from guests. If enabled (the default), Xen always
|
||||
+ quarantines such devices; they must be explicitly assigned back to Dom0
|
||||
+ before they can be used there again. If disabled, Xen will only
|
||||
+ quarantine devices the toolstack hass arranged for getting quarantined.
|
||||
+
|
||||
* The `sharept` boolean controls whether the IOMMU pagetables are shared
|
||||
with the CPU-side HAP pagetables, or allocated separately. Sharing
|
||||
reduces the memory overhead, but doesn't work in combination with CPU-side
|
||||
--- a/xen/drivers/passthrough/iommu.c
|
||||
+++ b/xen/drivers/passthrough/iommu.c
|
||||
@@ -30,6 +30,7 @@ bool_t __initdata iommu_enable = 1;
|
||||
bool_t __read_mostly iommu_enabled;
|
||||
bool_t __read_mostly force_iommu;
|
||||
bool_t __read_mostly iommu_verbose;
|
||||
+bool __read_mostly iommu_quarantine = true;
|
||||
bool_t __read_mostly iommu_igfx = 1;
|
||||
bool_t __read_mostly iommu_snoop = 1;
|
||||
bool_t __read_mostly iommu_qinval = 1;
|
||||
@@ -74,6 +75,8 @@ static int __init parse_iommu_param(cons
|
||||
else if ( (val = parse_boolean("force", s, ss)) >= 0 ||
|
||||
(val = parse_boolean("required", s, ss)) >= 0 )
|
||||
force_iommu = val;
|
||||
+ else if ( (val = parse_boolean("quarantine", s, ss)) >= 0 )
|
||||
+ iommu_quarantine = val;
|
||||
else if ( (val = parse_boolean("igfx", s, ss)) >= 0 )
|
||||
iommu_igfx = val;
|
||||
else if ( (val = parse_boolean("verbose", s, ss)) >= 0 )
|
||||
--- a/xen/drivers/passthrough/pci.c
|
||||
+++ b/xen/drivers/passthrough/pci.c
|
||||
@@ -1548,7 +1548,8 @@ int deassign_device(struct domain *d, u1
|
||||
return -ENODEV;
|
||||
|
||||
/* De-assignment from dom_io should de-quarantine the device */
|
||||
- target = (pdev->quarantine && pdev->domain != dom_io) ?
|
||||
+ target = ((pdev->quarantine || iommu_quarantine) &&
|
||||
+ pdev->domain != dom_io) ?
|
||||
dom_io : hardware_domain;
|
||||
|
||||
while ( pdev->phantom_stride )
|
||||
--- a/xen/include/xen/iommu.h
|
||||
+++ b/xen/include/xen/iommu.h
|
||||
@@ -53,7 +53,7 @@ static inline bool_t dfn_eq(dfn_t x, dfn
|
||||
}
|
||||
|
||||
extern bool_t iommu_enable, iommu_enabled;
|
||||
-extern bool_t force_iommu, iommu_verbose, iommu_igfx;
|
||||
+extern bool force_iommu, iommu_quarantine, iommu_verbose, iommu_igfx;
|
||||
extern bool_t iommu_snoop, iommu_qinval, iommu_intremap, iommu_intpost;
|
||||
extern bool_t iommu_hap_pt_share;
|
||||
extern bool_t iommu_debug;
|
||||
99
xsa307.patch
99
xsa307.patch
|
|
@ -1,99 +0,0 @@
|
|||
From: Jan Beulich <jbeulich@suse.com>
|
||||
Subject: x86+Arm32: make find_next_{,zero_}bit() have well defined behavior
|
||||
|
||||
These functions getting used with the 2nd and 3rd arguments being equal
|
||||
wasn't well defined: Arm64 reliably returns the value of the 2nd
|
||||
argument in this case, while on x86 for bitmaps up to 64 bits wide the
|
||||
return value was undefined (due to the undefined behavior of a shift of
|
||||
a value by the number of bits it's wide) when the incoming value was 64.
|
||||
On Arm32 an actual out of bounds access would happen when the
|
||||
size/offset value is a multiple of 32; if this access doesn't fault, the
|
||||
return value would have been sufficiently correct afaict.
|
||||
|
||||
Make the functions consistently tolerate the last two arguments being
|
||||
equal (and in fact the 3rd argument being greater or equal to the 2nd),
|
||||
in favor of finding and fixing all the use sites that violate the
|
||||
original more strict assumption.
|
||||
|
||||
This is XSA-307.
|
||||
|
||||
Signed-off-by: Jan Beulich <jbeulich@suse.com>
|
||||
Acked-by: Julien Grall <julien@xen.org>
|
||||
---
|
||||
The most obvious (albeit still indirect) exposure to guests is
|
||||
evtchn_check_pollers(), which imo makes this a security issue at least
|
||||
for Arm32.
|
||||
|
||||
This was originally already discussed between (at least) Andrew and me,
|
||||
and I don't really recall who brought up the issue first.
|
||||
|
||||
Note that Arm's Linux origin of the code may call for syncing
|
||||
publication with them. Then again I don't want to tell them just to see
|
||||
them go public ahead of us.
|
||||
|
||||
--- a/xen/arch/arm/arm32/lib/findbit.S
|
||||
+++ b/xen/arch/arm/arm32/lib/findbit.S
|
||||
@@ -42,8 +42,8 @@ ENDPROC(_find_first_zero_bit_le)
|
||||
* Prototype: int find_next_zero_bit(void *addr, unsigned int maxbit, int offset)
|
||||
*/
|
||||
ENTRY(_find_next_zero_bit_le)
|
||||
- teq r1, #0
|
||||
- beq 3b
|
||||
+ cmp r1, r2
|
||||
+ bls 3b
|
||||
ands ip, r2, #7
|
||||
beq 1b @ If new byte, goto old routine
|
||||
ARM( ldrb r3, [r0, r2, lsr #3] )
|
||||
@@ -83,8 +83,8 @@ ENDPROC(_find_first_bit_le)
|
||||
* Prototype: int find_next_zero_bit(void *addr, unsigned int maxbit, int offset)
|
||||
*/
|
||||
ENTRY(_find_next_bit_le)
|
||||
- teq r1, #0
|
||||
- beq 3b
|
||||
+ cmp r1, r2
|
||||
+ bls 3b
|
||||
ands ip, r2, #7
|
||||
beq 1b @ If new byte, goto old routine
|
||||
ARM( ldrb r3, [r0, r2, lsr #3] )
|
||||
@@ -117,8 +117,8 @@ ENTRY(_find_first_zero_bit_be)
|
||||
ENDPROC(_find_first_zero_bit_be)
|
||||
|
||||
ENTRY(_find_next_zero_bit_be)
|
||||
- teq r1, #0
|
||||
- beq 3b
|
||||
+ cmp r1, r2
|
||||
+ bls 3b
|
||||
ands ip, r2, #7
|
||||
beq 1b @ If new byte, goto old routine
|
||||
eor r3, r2, #0x18 @ big endian byte ordering
|
||||
@@ -151,8 +151,8 @@ ENTRY(_find_first_bit_be)
|
||||
ENDPROC(_find_first_bit_be)
|
||||
|
||||
ENTRY(_find_next_bit_be)
|
||||
- teq r1, #0
|
||||
- beq 3b
|
||||
+ cmp r1, r2
|
||||
+ bls 3b
|
||||
ands ip, r2, #7
|
||||
beq 1b @ If new byte, goto old routine
|
||||
eor r3, r2, #0x18 @ big endian byte ordering
|
||||
--- a/xen/include/asm-x86/bitops.h
|
||||
+++ b/xen/include/asm-x86/bitops.h
|
||||
@@ -358,7 +358,7 @@ static always_inline unsigned int __scan
|
||||
const unsigned long *a__ = (addr); \
|
||||
unsigned int s__ = (size); \
|
||||
unsigned int o__ = (off); \
|
||||
- if ( __builtin_constant_p(size) && !s__ ) \
|
||||
+ if ( o__ >= s__ ) \
|
||||
r__ = s__; \
|
||||
else if ( __builtin_constant_p(size) && s__ <= BITS_PER_LONG ) \
|
||||
r__ = o__ + __scanbit(*(const unsigned long *)(a__) >> o__, s__); \
|
||||
@@ -390,7 +390,7 @@ static always_inline unsigned int __scan
|
||||
const unsigned long *a__ = (addr); \
|
||||
unsigned int s__ = (size); \
|
||||
unsigned int o__ = (off); \
|
||||
- if ( __builtin_constant_p(size) && !s__ ) \
|
||||
+ if ( o__ >= s__ ) \
|
||||
r__ = s__; \
|
||||
else if ( __builtin_constant_p(size) && s__ <= BITS_PER_LONG ) \
|
||||
r__ = o__ + __scanbit(~*(const unsigned long *)(a__) >> o__, s__); \
|
||||
74
xsa308.patch
74
xsa308.patch
|
|
@ -1,74 +0,0 @@
|
|||
From: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Subject: x86/vtx: Work around SingleStep + STI/MovSS VMEntry failures
|
||||
|
||||
See patch comment for technical details.
|
||||
|
||||
Concerning the timeline, this was first discovered in the aftermath of
|
||||
XSA-156 which caused #DB to be intercepted unconditionally, but only in
|
||||
its SingleStep + STI form which is restricted to privileged software.
|
||||
|
||||
After working with Intel and identifying the problematic vmentry check,
|
||||
this workaround was suggested, and the patch was posted in an RFC
|
||||
series. Outstanding work for that series (not breaking Introspection)
|
||||
is still pending, and this fix from it (which wouldn't have been good
|
||||
enough in its original form) wasn't committed.
|
||||
|
||||
A vmentry failure was reported to xen-devel, and debugging identified
|
||||
this bug in its SingleStep + MovSS form by way of INT1, which does not
|
||||
involve the use of any privileged instructions, and proving this to be a
|
||||
security issue.
|
||||
|
||||
This is XSA-308
|
||||
|
||||
Reported-by: Håkon Alstadheim <hakon@alstadheim.priv.no>
|
||||
Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||||
Acked-by: Kevin Tian <kevin.tian@intel.com>
|
||||
|
||||
diff --git a/xen/arch/x86/hvm/vmx/vmx.c b/xen/arch/x86/hvm/vmx/vmx.c
|
||||
index 6a5eeb5c13..59b836f43f 100644
|
||||
--- a/xen/arch/x86/hvm/vmx/vmx.c
|
||||
+++ b/xen/arch/x86/hvm/vmx/vmx.c
|
||||
@@ -3816,6 +3816,42 @@ void vmx_vmexit_handler(struct cpu_user_regs *regs)
|
||||
HVMTRACE_1D(TRAP_DEBUG, exit_qualification);
|
||||
__restore_debug_registers(v);
|
||||
write_debugreg(6, exit_qualification | DR_STATUS_RESERVED_ONE);
|
||||
+
|
||||
+ /*
|
||||
+ * Work around SingleStep + STI/MovSS VMEntry failures.
|
||||
+ *
|
||||
+ * We intercept #DB unconditionally to work around CVE-2015-8104 /
|
||||
+ * XSA-156 (guest-kernel induced host DoS).
|
||||
+ *
|
||||
+ * STI/MovSS shadows block/defer interrupts/exceptions (exact
|
||||
+ * details are complicated and poorly documented). Debug
|
||||
+ * exceptions delayed for any reason are stored in the
|
||||
+ * PENDING_DBG_EXCEPTIONS field.
|
||||
+ *
|
||||
+ * The falling edge of PENDING_DBG causes #DB to be delivered,
|
||||
+ * resulting in a VMExit, as #DB is intercepted. The VMCS still
|
||||
+ * reports blocked-by-STI/MovSS.
|
||||
+ *
|
||||
+ * The VMEntry checks when EFLAGS.TF is set don't like a VMCS in
|
||||
+ * this state. Despite a #DB queued in VMENTRY_INTR_INFO, the
|
||||
+ * state is rejected as DR6.BS isn't pending. Fix this up.
|
||||
+ */
|
||||
+ if ( unlikely(regs->eflags & X86_EFLAGS_TF) )
|
||||
+ {
|
||||
+ unsigned long int_info;
|
||||
+
|
||||
+ __vmread(GUEST_INTERRUPTIBILITY_INFO, &int_info);
|
||||
+
|
||||
+ if ( int_info & (VMX_INTR_SHADOW_STI | VMX_INTR_SHADOW_MOV_SS) )
|
||||
+ {
|
||||
+ unsigned long pending_dbg;
|
||||
+
|
||||
+ __vmread(GUEST_PENDING_DBG_EXCEPTIONS, &pending_dbg);
|
||||
+ __vmwrite(GUEST_PENDING_DBG_EXCEPTIONS,
|
||||
+ pending_dbg | DR_STEP);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
if ( !v->domain->debugger_attached )
|
||||
{
|
||||
unsigned long insn_len = 0;
|
||||
58
xsa309.patch
58
xsa309.patch
|
|
@ -1,58 +0,0 @@
|
|||
From 523e3974ed2213719a19218f5b246e382ceef18a Mon Sep 17 00:00:00 2001
|
||||
From: George Dunlap <george.dunlap@citrix.com>
|
||||
Date: Wed, 30 Oct 2019 17:05:28 +0000
|
||||
Subject: [PATCH] x86/mm: Don't reset linear_pt_count on partial validation
|
||||
|
||||
"Linear pagetables" is a technique which involves either pointing a
|
||||
pagetable at itself, or to another pagetable the same or higher level.
|
||||
Xen has limited support for linear pagetables: A page may either point
|
||||
to itself, or point to another page of the same level (i.e., L2 to L2,
|
||||
L3 to L3, and so on).
|
||||
|
||||
XSA-240 introduced an additional restriction that limited the "depth"
|
||||
of such chains by allowing pages to either *point to* other pages of
|
||||
the same level, or *be pointed to* by other pages of the same level,
|
||||
but not both. To implement this, we keep track of the number of
|
||||
outstanding times a page points to or is pointed to another page
|
||||
table, to prevent both from happening at the same time.
|
||||
|
||||
Unfortunately, the original commit introducing this reset this count
|
||||
when resuming validation of a partially-validated pagetable, dropping
|
||||
some "linear_pt_entry" counts.
|
||||
|
||||
On debug builds on systems where guests used this feature, this might
|
||||
lead to crashes that look like this:
|
||||
|
||||
Assertion 'oc > 0' failed at mm.c:874
|
||||
|
||||
Worse, if an attacker could engineer such a situation to occur, they
|
||||
might be able to make loops or other abitrary chains of linear
|
||||
pagetables, leading to the denial-of-service situation outlined in
|
||||
XSA-240.
|
||||
|
||||
This is XSA-309.
|
||||
|
||||
Reported-by: Manuel Bouyer <bouyer@antioche.eu.org>
|
||||
Signed-off-by: George Dunlap <george.dunlap@citrix.com>
|
||||
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||||
---
|
||||
xen/arch/x86/mm.c | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/xen/arch/x86/mm.c b/xen/arch/x86/mm.c
|
||||
index 7d4dd80a85..01393fb0da 100644
|
||||
--- a/xen/arch/x86/mm.c
|
||||
+++ b/xen/arch/x86/mm.c
|
||||
@@ -3059,8 +3059,8 @@ static int _get_page_type(struct page_info *page, unsigned long type,
|
||||
{
|
||||
page->nr_validated_ptes = 0;
|
||||
page->partial_flags = 0;
|
||||
+ page->linear_pt_count = 0;
|
||||
}
|
||||
- page->linear_pt_count = 0;
|
||||
rc = alloc_page_type(page, type, preemptible);
|
||||
}
|
||||
|
||||
--
|
||||
2.24.0
|
||||
|
||||
|
|
@ -1,167 +0,0 @@
|
|||
From 7c537dc8d28a03064a14171ed5c6fc329531816a Mon Sep 17 00:00:00 2001
|
||||
From: George Dunlap <george.dunlap@citrix.com>
|
||||
Date: Tue, 19 Nov 2019 11:40:34 +0000
|
||||
Subject: [PATCH 1/3] x86/mm: Set old_guest_table when destroying vcpu
|
||||
pagetables
|
||||
|
||||
Changeset 6c4efc1eba ("x86/mm: Don't drop a type ref unless you held a
|
||||
ref to begin with"), part of XSA-299, changed the calling discipline
|
||||
of put_page_type() such that if put_page_type() returned -ERESTART
|
||||
(indicating a partially de-validated page), subsequent calls to
|
||||
put_page_type() must be called with PTF_partial_set. If called on a
|
||||
partially de-validated page but without PTF_partial_set, Xen will
|
||||
BUG(), because to do otherwise would risk opening up the kind of
|
||||
privilege escalation bug described in XSA-299.
|
||||
|
||||
One place this was missed was in vcpu_destroy_pagetables().
|
||||
put_page_and_type_preemptible() is called, but on -ERESTART, the
|
||||
entire operation is simply restarted, causing put_page_type() to be
|
||||
called on a partially de-validated page without PTF_partial_set. The
|
||||
result was that if such an operation were interrupted, Xen would hit a
|
||||
BUG().
|
||||
|
||||
Fix this by having vcpu_destroy_pagetables() consistently pass off
|
||||
interrupted de-validations to put_old_page_type():
|
||||
- Unconditionally clear references to the page, even if
|
||||
put_page_and_type failed
|
||||
- Set old_guest_table and old_guest_table_partial appropriately
|
||||
|
||||
While here, do some refactoring:
|
||||
|
||||
- Move clearing of arch.cr3 to the top of the function
|
||||
|
||||
- Now that clearing is unconditional, move the unmap to the same
|
||||
conditional as the l4tab mapping. This also allows us to reduce
|
||||
the scope of the l4tab variable.
|
||||
|
||||
- Avoid code duplication by looping to drop references on
|
||||
guest_table_user
|
||||
|
||||
This is part of XSA-310.
|
||||
|
||||
Reported-by: Sarah Newman <srn@prgmr.com>
|
||||
Signed-off-by: George Dunlap <george.dunlap@citrix.com>
|
||||
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||||
---
|
||||
Added in v2.
|
||||
|
||||
Changes in v3:
|
||||
- Minor comment / whitespace fixes
|
||||
---
|
||||
xen/arch/x86/mm.c | 75 +++++++++++++++++++++++++++++------------------
|
||||
1 file changed, 47 insertions(+), 28 deletions(-)
|
||||
|
||||
diff --git a/xen/arch/x86/mm.c b/xen/arch/x86/mm.c
|
||||
index 01393fb0da..a759afc9e3 100644
|
||||
--- a/xen/arch/x86/mm.c
|
||||
+++ b/xen/arch/x86/mm.c
|
||||
@@ -3142,40 +3142,36 @@ int put_old_guest_table(struct vcpu *v)
|
||||
int vcpu_destroy_pagetables(struct vcpu *v)
|
||||
{
|
||||
unsigned long mfn = pagetable_get_pfn(v->arch.guest_table);
|
||||
- struct page_info *page;
|
||||
- l4_pgentry_t *l4tab = NULL;
|
||||
+ struct page_info *page = NULL;
|
||||
int rc = put_old_guest_table(v);
|
||||
+ bool put_guest_table_user = false;
|
||||
|
||||
if ( rc )
|
||||
return rc;
|
||||
|
||||
+ v->arch.cr3 = 0;
|
||||
+
|
||||
+ /*
|
||||
+ * Get the top-level guest page; either the guest_table itself, for
|
||||
+ * 64-bit, or the top-level l4 entry for 32-bit. Either way, remove
|
||||
+ * the reference to that page.
|
||||
+ */
|
||||
if ( is_pv_32bit_vcpu(v) )
|
||||
{
|
||||
- l4tab = map_domain_page(_mfn(mfn));
|
||||
- mfn = l4e_get_pfn(*l4tab);
|
||||
- }
|
||||
+ l4_pgentry_t *l4tab = map_domain_page(_mfn(mfn));
|
||||
|
||||
- if ( mfn )
|
||||
- {
|
||||
- page = mfn_to_page(_mfn(mfn));
|
||||
- if ( paging_mode_refcounts(v->domain) )
|
||||
- put_page(page);
|
||||
- else
|
||||
- rc = put_page_and_type_preemptible(page);
|
||||
- }
|
||||
-
|
||||
- if ( l4tab )
|
||||
- {
|
||||
- if ( !rc )
|
||||
- l4e_write(l4tab, l4e_empty());
|
||||
+ mfn = l4e_get_pfn(*l4tab);
|
||||
+ l4e_write(l4tab, l4e_empty());
|
||||
unmap_domain_page(l4tab);
|
||||
}
|
||||
- else if ( !rc )
|
||||
+ else
|
||||
{
|
||||
v->arch.guest_table = pagetable_null();
|
||||
+ put_guest_table_user = true;
|
||||
+ }
|
||||
|
||||
- /* Drop ref to guest_table_user (from MMUEXT_NEW_USER_BASEPTR) */
|
||||
- mfn = pagetable_get_pfn(v->arch.guest_table_user);
|
||||
+ /* Free that page if non-zero */
|
||||
+ do {
|
||||
if ( mfn )
|
||||
{
|
||||
page = mfn_to_page(_mfn(mfn));
|
||||
@@ -3183,18 +3179,41 @@ int vcpu_destroy_pagetables(struct vcpu *v)
|
||||
put_page(page);
|
||||
else
|
||||
rc = put_page_and_type_preemptible(page);
|
||||
+ mfn = 0;
|
||||
}
|
||||
- if ( !rc )
|
||||
- v->arch.guest_table_user = pagetable_null();
|
||||
- }
|
||||
|
||||
- v->arch.cr3 = 0;
|
||||
+ if ( !rc && put_guest_table_user )
|
||||
+ {
|
||||
+ /* Drop ref to guest_table_user (from MMUEXT_NEW_USER_BASEPTR) */
|
||||
+ mfn = pagetable_get_pfn(v->arch.guest_table_user);
|
||||
+ v->arch.guest_table_user = pagetable_null();
|
||||
+ put_guest_table_user = false;
|
||||
+ }
|
||||
+ } while ( mfn );
|
||||
|
||||
/*
|
||||
- * put_page_and_type_preemptible() is liable to return -EINTR. The
|
||||
- * callers of us expect -ERESTART so convert it over.
|
||||
+ * If a "put" operation was interrupted, finish things off in
|
||||
+ * put_old_guest_table() when the operation is restarted.
|
||||
*/
|
||||
- return rc != -EINTR ? rc : -ERESTART;
|
||||
+ switch ( rc )
|
||||
+ {
|
||||
+ case -EINTR:
|
||||
+ case -ERESTART:
|
||||
+ v->arch.old_guest_ptpg = NULL;
|
||||
+ v->arch.old_guest_table = page;
|
||||
+ v->arch.old_guest_table_partial = (rc == -ERESTART);
|
||||
+ rc = -ERESTART;
|
||||
+ break;
|
||||
+ default:
|
||||
+ /*
|
||||
+ * Failure to 'put' a page may cause it to leak, but that's
|
||||
+ * less bad than a crash.
|
||||
+ */
|
||||
+ ASSERT(rc == 0);
|
||||
+ break;
|
||||
+ }
|
||||
+
|
||||
+ return rc;
|
||||
}
|
||||
|
||||
int new_guest_cr3(mfn_t mfn)
|
||||
--
|
||||
2.24.0
|
||||
|
||||
|
|
@ -1,104 +0,0 @@
|
|||
From 128cb126aee9b4a2855ab898fdfbfe7009fbf1f5 Mon Sep 17 00:00:00 2001
|
||||
From: George Dunlap <george.dunlap@citrix.com>
|
||||
Date: Thu, 31 Oct 2019 11:17:38 +0000
|
||||
Subject: [PATCH 2/3] x86/mm: alloc/free_lN_table: Retain partial_flags on
|
||||
-EINTR
|
||||
|
||||
When validating or de-validating pages (in alloc_lN_table and
|
||||
free_lN_table respectively), the `partial_flags` local variable is
|
||||
used to keep track of whether the "current" PTE started the entire
|
||||
operation in a "may be partial" state.
|
||||
|
||||
One of the patches in XSA-299 addressed the fact that it is possible
|
||||
for a previously-partially-validated entry to subsequently be found to
|
||||
have invalid entries (indicated by returning -EINVAL); in which case
|
||||
page->partial_flags needs to be set to indicate that the current PTE
|
||||
may have the partial bit set (and thus _put_page_type() should be
|
||||
called with PTF_partial_set).
|
||||
|
||||
Unfortunately, the patches in XSA-299 assumed that once
|
||||
put_page_from_lNe() returned -ERESTART on a page, it was not possible
|
||||
for it to return -EINTR. This turns out to be true for
|
||||
alloc_lN_table() and free_lN_table, but not for _get_page_type() and
|
||||
_put_page_type(): both can return -EINTR when called on pages with
|
||||
PGT_partial set. In these cases, the pages PGT_partial will still be
|
||||
set; failing to set partial_flags appropriately may allow an attacker
|
||||
to do a privilege escalation similar to those described in XSA-299.
|
||||
|
||||
Fix this by always copying the local partial_flags variable into
|
||||
page->partial_flags when exiting early.
|
||||
|
||||
NB that on the "get" side, no adjustment to nr_validated_entries is
|
||||
needed: whether pte[i] is partially validated or entirely
|
||||
un-validated, we want nr_validated_entries = i. On the "put" side,
|
||||
however, we need to adjust nr_validated_entries appropriately: if
|
||||
pte[i] is entirely validated, we want nr_validated_entries = i + 1; if
|
||||
pte[i] is partially validated, we want nr_validated_entries = i.
|
||||
|
||||
This is part of XSA-310.
|
||||
|
||||
Reported-by: Sarah Newman <srn@prgmr.com>
|
||||
Signed-off-by: George Dunlap <george.dunlap@citrix.com>
|
||||
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||||
---
|
||||
xen/arch/x86/mm.c | 16 ++++++++--------
|
||||
1 file changed, 8 insertions(+), 8 deletions(-)
|
||||
|
||||
diff --git a/xen/arch/x86/mm.c b/xen/arch/x86/mm.c
|
||||
index a759afc9e3..97c8d73b7b 100644
|
||||
--- a/xen/arch/x86/mm.c
|
||||
+++ b/xen/arch/x86/mm.c
|
||||
@@ -1557,7 +1557,7 @@ static int alloc_l2_table(struct page_info *page, unsigned long type)
|
||||
if ( rc == -EINTR && i )
|
||||
{
|
||||
page->nr_validated_ptes = i;
|
||||
- page->partial_flags = 0;
|
||||
+ page->partial_flags = partial_flags;;
|
||||
rc = -ERESTART;
|
||||
}
|
||||
else if ( rc < 0 && rc != -EINTR )
|
||||
@@ -1660,7 +1660,7 @@ static int alloc_l3_table(struct page_info *page)
|
||||
else if ( rc == -EINTR && i )
|
||||
{
|
||||
page->nr_validated_ptes = i;
|
||||
- page->partial_flags = 0;
|
||||
+ page->partial_flags = partial_flags;
|
||||
rc = -ERESTART;
|
||||
}
|
||||
if ( rc < 0 )
|
||||
@@ -1982,8 +1982,8 @@ static int free_l2_table(struct page_info *page)
|
||||
}
|
||||
else if ( rc == -EINTR && i < L2_PAGETABLE_ENTRIES - 1 )
|
||||
{
|
||||
- page->nr_validated_ptes = i + 1;
|
||||
- page->partial_flags = 0;
|
||||
+ page->nr_validated_ptes = i + !(partial_flags & PTF_partial_set);
|
||||
+ page->partial_flags = partial_flags;
|
||||
rc = -ERESTART;
|
||||
}
|
||||
|
||||
@@ -2030,8 +2030,8 @@ static int free_l3_table(struct page_info *page)
|
||||
}
|
||||
else if ( rc == -EINTR && i < L3_PAGETABLE_ENTRIES - 1 )
|
||||
{
|
||||
- page->nr_validated_ptes = i + 1;
|
||||
- page->partial_flags = 0;
|
||||
+ page->nr_validated_ptes = i + !(partial_flags & PTF_partial_set);
|
||||
+ page->partial_flags = partial_flags;
|
||||
rc = -ERESTART;
|
||||
}
|
||||
return rc > 0 ? 0 : rc;
|
||||
@@ -2061,8 +2061,8 @@ static int free_l4_table(struct page_info *page)
|
||||
}
|
||||
else if ( rc == -EINTR && i < L4_PAGETABLE_ENTRIES - 1 )
|
||||
{
|
||||
- page->nr_validated_ptes = i + 1;
|
||||
- page->partial_flags = 0;
|
||||
+ page->nr_validated_ptes = i + !(partial_flags & PTF_partial_set);
|
||||
+ page->partial_flags = partial_flags;
|
||||
rc = -ERESTART;
|
||||
}
|
||||
|
||||
--
|
||||
2.24.0
|
||||
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
From e9f835982a726ae16997c566b5eafab74f8b4cb7 Mon Sep 17 00:00:00 2001
|
||||
From: George Dunlap <george.dunlap@citrix.com>
|
||||
Date: Mon, 28 Oct 2019 14:33:51 +0000
|
||||
Subject: [PATCH 3/3] x86/mm: relinquish_memory: Grab an extra type ref when
|
||||
setting PGT_partial
|
||||
|
||||
The PGT_partial bit in page->type_info holds both a type count and a
|
||||
general ref count. During domain tear-down, when free_page_type()
|
||||
returns -ERESTART, relinquish_memory() correctly handles the general
|
||||
ref count, but fails to grab an extra type count when setting
|
||||
PGT_partial. When this bit is eventually cleared, type_count underflows
|
||||
and triggers the following BUG in page_alloc.c:free_domheap_pages():
|
||||
|
||||
BUG_ON((pg[i].u.inuse.type_info & PGT_count_mask) != 0);
|
||||
|
||||
As far as we can tell, this page underflow cannot be exploited any any
|
||||
other way: The page can't be used as a pagetable by the dying domain
|
||||
because it's dying; it can't be used as a pagetable by any other
|
||||
domain since it belongs to the dying domain; and ownership can't
|
||||
transfer to any other domain without hitting the BUG_ON() in
|
||||
free_domheap_pages().
|
||||
|
||||
(steal_page() won't work on a page in this state, since it requires
|
||||
PGC_allocated to be set, and PGC_allocated will already have been
|
||||
cleared.)
|
||||
|
||||
Fix this by grabbing an extra type ref if setting PGT_partial in
|
||||
relinquish_memory.
|
||||
|
||||
This is part of XSA-310.
|
||||
|
||||
Reported-by: Sarah Newman <srn@prgmr.com>
|
||||
Signed-off-by: George Dunlap <george.dunlap@citrix.com>
|
||||
Acked-by: Jan Beulich <jbeulich@suse.com>
|
||||
---
|
||||
v2:
|
||||
- Move discussion of potential exploits into the commit message
|
||||
- Keep PGT_partial and put_page() ordering
|
||||
---
|
||||
xen/arch/x86/domain.c | 19 +++++++++++++++++++
|
||||
1 file changed, 19 insertions(+)
|
||||
|
||||
diff --git a/xen/arch/x86/domain.c b/xen/arch/x86/domain.c
|
||||
index f1dd86e12e..51880fc50d 100644
|
||||
--- a/xen/arch/x86/domain.c
|
||||
+++ b/xen/arch/x86/domain.c
|
||||
@@ -2049,6 +2049,25 @@ static int relinquish_memory(
|
||||
goto out;
|
||||
case -ERESTART:
|
||||
page_list_add(page, list);
|
||||
+ /*
|
||||
+ * PGT_partial holds a type ref and a general ref.
|
||||
+ * If we came in with PGT_partial set, then we 1)
|
||||
+ * don't need to grab an extra type count, and 2)
|
||||
+ * do need to drop the extra page ref we grabbed
|
||||
+ * at the top of the loop. If we didn't come in
|
||||
+ * with PGT_partial set, we 1) do need to drab an
|
||||
+ * extra type count, but 2) can transfer the page
|
||||
+ * ref we grabbed above to it.
|
||||
+ *
|
||||
+ * Note that we must increment type_info before
|
||||
+ * setting PGT_partial. Theoretically it should
|
||||
+ * be safe to drop the page ref before setting
|
||||
+ * PGT_partial, but do it afterwards just to be
|
||||
+ * extra safe.
|
||||
+ */
|
||||
+ if ( !(x & PGT_partial) )
|
||||
+ page->u.inuse.type_info++;
|
||||
+ smp_wmb();
|
||||
page->u.inuse.type_info |= PGT_partial;
|
||||
if ( x & PGT_partial )
|
||||
put_page(page);
|
||||
--
|
||||
2.24.0
|
||||
|
||||
|
|
@ -1,194 +0,0 @@
|
|||
From: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Subject: AMD/IOMMU: Cease using a dynamic height for the IOMMU pagetables
|
||||
|
||||
update_paging_mode() has multiple bugs:
|
||||
|
||||
1) Booting with iommu=debug will cause it to inform you that that it called
|
||||
without the pdev_list lock held.
|
||||
2) When growing by more than a single level, it leaks the newly allocated
|
||||
table(s) in the case of a further error.
|
||||
|
||||
Furthermore, the choice of default level for a domain has issues:
|
||||
|
||||
1) All HVM guests grow from 2 to 3 levels during construction because of the
|
||||
position of the VRAM just below the 4G boundary, so defaulting to 2 is a
|
||||
waste of effort.
|
||||
2) The limit for PV guests doesn't take memory hotplug into account, and
|
||||
isn't dynamic at runtime like HVM guests. This means that a PV guest may
|
||||
get RAM which it can't map in the IOMMU.
|
||||
|
||||
The dynamic height is a property unique to AMD, and adds a substantial
|
||||
quantity of complexity for what is a marginal performance improvement. Remove
|
||||
the complexity by removing the dynamic height.
|
||||
|
||||
PV guests now get 3 or 4 levels based on any hotplug regions in the host.
|
||||
This only makes a difference for hardware which previously had all RAM below
|
||||
the 512G boundary, and a hotplug region above.
|
||||
|
||||
HVM guests now get 4 levels (which will be sufficient until 256TB guests
|
||||
become a thing), because we don't currently have the information to know when
|
||||
3 would be safe to use.
|
||||
|
||||
The overhead of this extra level is not expected to be noticeable. It costs
|
||||
one page (4k) per domain, and one extra IO-TLB paging structure cache entry
|
||||
which is very hot and less likely to be evicted.
|
||||
|
||||
This is XSA-311.
|
||||
|
||||
Reported-by: XXX PERSON <XXX EMAIL>3
|
||||
Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Acked-by: Jan Beulich <jbeulich@suse.com>
|
||||
|
||||
diff --git a/xen/drivers/passthrough/amd/iommu_map.c b/xen/drivers/passthrough/amd/iommu_map.c
|
||||
index 628aa60230..9a222c95e1 100644
|
||||
--- a/xen/drivers/passthrough/amd/iommu_map.c
|
||||
+++ b/xen/drivers/passthrough/amd/iommu_map.c
|
||||
@@ -455,100 +455,6 @@ static int iommu_pde_from_dfn(struct domain *d, unsigned long dfn,
|
||||
return 0;
|
||||
}
|
||||
|
||||
-static int update_paging_mode(struct domain *d, unsigned long dfn)
|
||||
-{
|
||||
- uint16_t bdf;
|
||||
- void *device_entry;
|
||||
- unsigned int req_id, level, offset;
|
||||
- unsigned long flags;
|
||||
- struct pci_dev *pdev;
|
||||
- struct amd_iommu *iommu = NULL;
|
||||
- struct page_info *new_root = NULL;
|
||||
- struct page_info *old_root = NULL;
|
||||
- void *new_root_vaddr;
|
||||
- unsigned long old_root_mfn;
|
||||
- struct domain_iommu *hd = dom_iommu(d);
|
||||
-
|
||||
- if ( dfn == dfn_x(INVALID_DFN) )
|
||||
- return -EADDRNOTAVAIL;
|
||||
- ASSERT(!(dfn >> DEFAULT_DOMAIN_ADDRESS_WIDTH));
|
||||
-
|
||||
- level = hd->arch.paging_mode;
|
||||
- old_root = hd->arch.root_table;
|
||||
- offset = dfn >> (PTE_PER_TABLE_SHIFT * (level - 1));
|
||||
-
|
||||
- ASSERT(spin_is_locked(&hd->arch.mapping_lock) && is_hvm_domain(d));
|
||||
-
|
||||
- while ( offset >= PTE_PER_TABLE_SIZE )
|
||||
- {
|
||||
- /* Allocate and install a new root table.
|
||||
- * Only upper I/O page table grows, no need to fix next level bits */
|
||||
- new_root = alloc_amd_iommu_pgtable();
|
||||
- if ( new_root == NULL )
|
||||
- {
|
||||
- AMD_IOMMU_DEBUG("%s Cannot allocate I/O page table\n",
|
||||
- __func__);
|
||||
- return -ENOMEM;
|
||||
- }
|
||||
-
|
||||
- new_root_vaddr = __map_domain_page(new_root);
|
||||
- old_root_mfn = mfn_x(page_to_mfn(old_root));
|
||||
- set_iommu_pde_present(new_root_vaddr, old_root_mfn, level,
|
||||
- !!IOMMUF_writable, !!IOMMUF_readable);
|
||||
- level++;
|
||||
- old_root = new_root;
|
||||
- offset >>= PTE_PER_TABLE_SHIFT;
|
||||
- unmap_domain_page(new_root_vaddr);
|
||||
- }
|
||||
-
|
||||
- if ( new_root != NULL )
|
||||
- {
|
||||
- hd->arch.paging_mode = level;
|
||||
- hd->arch.root_table = new_root;
|
||||
-
|
||||
- if ( !pcidevs_locked() )
|
||||
- AMD_IOMMU_DEBUG("%s Try to access pdev_list "
|
||||
- "without aquiring pcidevs_lock.\n", __func__);
|
||||
-
|
||||
- /* Update device table entries using new root table and paging mode */
|
||||
- for_each_pdev( d, pdev )
|
||||
- {
|
||||
- if ( pdev->type == DEV_TYPE_PCI_HOST_BRIDGE )
|
||||
- continue;
|
||||
-
|
||||
- bdf = PCI_BDF2(pdev->bus, pdev->devfn);
|
||||
- iommu = find_iommu_for_device(pdev->seg, bdf);
|
||||
- if ( !iommu )
|
||||
- {
|
||||
- AMD_IOMMU_DEBUG("%s Fail to find iommu.\n", __func__);
|
||||
- return -ENODEV;
|
||||
- }
|
||||
-
|
||||
- spin_lock_irqsave(&iommu->lock, flags);
|
||||
- do {
|
||||
- req_id = get_dma_requestor_id(pdev->seg, bdf);
|
||||
- device_entry = iommu->dev_table.buffer +
|
||||
- (req_id * IOMMU_DEV_TABLE_ENTRY_SIZE);
|
||||
-
|
||||
- /* valid = 0 only works for dom0 passthrough mode */
|
||||
- amd_iommu_set_root_page_table((uint32_t *)device_entry,
|
||||
- page_to_maddr(hd->arch.root_table),
|
||||
- d->domain_id,
|
||||
- hd->arch.paging_mode, 1);
|
||||
-
|
||||
- amd_iommu_flush_device(iommu, req_id);
|
||||
- bdf += pdev->phantom_stride;
|
||||
- } while ( PCI_DEVFN2(bdf) != pdev->devfn &&
|
||||
- PCI_SLOT(bdf) == PCI_SLOT(pdev->devfn) );
|
||||
- spin_unlock_irqrestore(&iommu->lock, flags);
|
||||
- }
|
||||
-
|
||||
- /* For safety, invalidate all entries */
|
||||
- amd_iommu_flush_all_pages(d);
|
||||
- }
|
||||
- return 0;
|
||||
-}
|
||||
-
|
||||
int amd_iommu_map_page(struct domain *d, dfn_t dfn, mfn_t mfn,
|
||||
unsigned int flags, unsigned int *flush_flags)
|
||||
{
|
||||
@@ -573,20 +479,6 @@ int amd_iommu_map_page(struct domain *d, dfn_t dfn, mfn_t mfn,
|
||||
return rc;
|
||||
}
|
||||
|
||||
- /* Since HVM domain is initialized with 2 level IO page table,
|
||||
- * we might need a deeper page table for wider dfn now */
|
||||
- if ( is_hvm_domain(d) )
|
||||
- {
|
||||
- if ( update_paging_mode(d, dfn_x(dfn)) )
|
||||
- {
|
||||
- spin_unlock(&hd->arch.mapping_lock);
|
||||
- AMD_IOMMU_DEBUG("Update page mode failed dfn = %"PRI_dfn"\n",
|
||||
- dfn_x(dfn));
|
||||
- domain_crash(d);
|
||||
- return -EFAULT;
|
||||
- }
|
||||
- }
|
||||
-
|
||||
if ( iommu_pde_from_dfn(d, dfn_x(dfn), pt_mfn, true) || (pt_mfn[1] == 0) )
|
||||
{
|
||||
spin_unlock(&hd->arch.mapping_lock);
|
||||
diff --git a/xen/drivers/passthrough/amd/pci_amd_iommu.c b/xen/drivers/passthrough/amd/pci_amd_iommu.c
|
||||
index 15c13e1163..57dc2c5f20 100644
|
||||
--- a/xen/drivers/passthrough/amd/pci_amd_iommu.c
|
||||
+++ b/xen/drivers/passthrough/amd/pci_amd_iommu.c
|
||||
@@ -242,10 +242,17 @@ static int amd_iommu_domain_init(struct domain *d)
|
||||
{
|
||||
struct domain_iommu *hd = dom_iommu(d);
|
||||
|
||||
- /* For pv and dom0, stick with get_paging_mode(max_page)
|
||||
- * For HVM dom0, use 2 level page table at first */
|
||||
- hd->arch.paging_mode = is_hvm_domain(d) ?
|
||||
- 2 : amd_iommu_get_paging_mode(max_page);
|
||||
+ /*
|
||||
+ * Choose the number of levels for the IOMMU page tables.
|
||||
+ * - PV needs 3 or 4, depending on whether there is RAM (including hotplug
|
||||
+ * RAM) above the 512G boundary.
|
||||
+ * - HVM could in principle use 3 or 4 depending on how much guest
|
||||
+ * physical address space we give it, but this isn't known yet so use 4
|
||||
+ * unilaterally.
|
||||
+ */
|
||||
+ hd->arch.paging_mode = is_hvm_domain(d)
|
||||
+ ? 4 : amd_iommu_get_paging_mode(get_upper_mfn_bound());
|
||||
+
|
||||
return 0;
|
||||
}
|
||||
|
||||
50
xsa317.patch
Normal file
50
xsa317.patch
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
From aeb46e92f915f19a61d5a8a1f4b696793f64e6fb Mon Sep 17 00:00:00 2001
|
||||
From: Julien Grall <jgrall@amazon.com>
|
||||
Date: Thu, 19 Mar 2020 13:17:31 +0000
|
||||
Subject: [PATCH] xen/common: event_channel: Don't ignore error in
|
||||
get_free_port()
|
||||
|
||||
Currently, get_free_port() is assuming that the port has been allocated
|
||||
when evtchn_allocate_port() is not return -EBUSY.
|
||||
|
||||
However, the function may return an error when:
|
||||
- We exhausted all the event channels. This can happen if the limit
|
||||
configured by the administrator for the guest ('max_event_channels'
|
||||
in xl cfg) is higher than the ABI used by the guest. For instance,
|
||||
if the guest is using 2L, the limit should not be higher than 4095.
|
||||
- We cannot allocate memory (e.g Xen has not more memory).
|
||||
|
||||
Users of get_free_port() (such as EVTCHNOP_alloc_unbound) will validly
|
||||
assuming the port was valid and will next call evtchn_from_port(). This
|
||||
will result to a crash as the memory backing the event channel structure
|
||||
is not present.
|
||||
|
||||
Fixes: 368ae9a05fe ("xen/pvshim: forward evtchn ops between L0 Xen and L2 DomU")
|
||||
Signed-off-by: Julien Grall <jgrall@amazon.com>
|
||||
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||||
---
|
||||
xen/common/event_channel.c | 8 ++++----
|
||||
1 file changed, 4 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/xen/common/event_channel.c b/xen/common/event_channel.c
|
||||
index e86e2bfab0..a8d182b584 100644
|
||||
--- a/xen/common/event_channel.c
|
||||
+++ b/xen/common/event_channel.c
|
||||
@@ -195,10 +195,10 @@ static int get_free_port(struct domain *d)
|
||||
{
|
||||
int rc = evtchn_allocate_port(d, port);
|
||||
|
||||
- if ( rc == -EBUSY )
|
||||
- continue;
|
||||
-
|
||||
- return port;
|
||||
+ if ( rc == 0 )
|
||||
+ return port;
|
||||
+ else if ( rc != -EBUSY )
|
||||
+ return rc;
|
||||
}
|
||||
|
||||
return -ENOSPC;
|
||||
--
|
||||
2.17.1
|
||||
|
||||
27
xsa319.patch
Normal file
27
xsa319.patch
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
From: Jan Beulich <jbeulich@suse.com>
|
||||
Subject: x86/shadow: correct an inverted conditional in dirty VRAM tracking
|
||||
|
||||
This originally was "mfn_x(mfn) == INVALID_MFN". Make it like this
|
||||
again, taking the opportunity to also drop the unnecessary nearby
|
||||
braces.
|
||||
|
||||
This is XSA-319.
|
||||
|
||||
Fixes: 246a5a3377c2 ("xen: Use a typesafe to define INVALID_MFN")
|
||||
Signed-off-by: Jan Beulich <jbeulich@suse.com>
|
||||
Reviewed-by: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
|
||||
--- a/xen/arch/x86/mm/shadow/common.c
|
||||
+++ b/xen/arch/x86/mm/shadow/common.c
|
||||
@@ -3252,10 +3252,8 @@ int shadow_track_dirty_vram(struct domai
|
||||
int dirty = 0;
|
||||
paddr_t sl1ma = dirty_vram->sl1ma[i];
|
||||
|
||||
- if ( !mfn_eq(mfn, INVALID_MFN) )
|
||||
- {
|
||||
+ if ( mfn_eq(mfn, INVALID_MFN) )
|
||||
dirty = 1;
|
||||
- }
|
||||
else
|
||||
{
|
||||
page = mfn_to_page(mfn);
|
||||
133
xsa320-4.12-1.patch
Normal file
133
xsa320-4.12-1.patch
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
From: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Subject: x86/spec-ctrl: CPUID/MSR definitions for Special Register Buffer Data Sampling
|
||||
|
||||
This is part of XSA-320 / CVE-2020-0543
|
||||
|
||||
Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||||
Acked-by: Wei Liu <wl@xen.org>
|
||||
|
||||
diff --git a/docs/misc/xen-command-line.pandoc b/docs/misc/xen-command-line.pandoc
|
||||
index 3561d88b59..dbdaee92dc 100644
|
||||
--- a/docs/misc/xen-command-line.pandoc
|
||||
+++ b/docs/misc/xen-command-line.pandoc
|
||||
@@ -483,10 +483,10 @@ accounting for hardware capabilities as enumerated via CPUID.
|
||||
|
||||
Currently accepted:
|
||||
|
||||
-The Speculation Control hardware features `md-clear`, `ibrsb`, `stibp`, `ibpb`,
|
||||
-`l1d-flush` and `ssbd` are used by default if available and applicable. They can
|
||||
-be ignored, e.g. `no-ibrsb`, at which point Xen won't use them itself, and
|
||||
-won't offer them to guests.
|
||||
+The Speculation Control hardware features `srbds-ctrl`, `md-clear`, `ibrsb`,
|
||||
+`stibp`, `ibpb`, `l1d-flush` and `ssbd` are used by default if available and
|
||||
+applicable. They can be ignored, e.g. `no-ibrsb`, at which point Xen won't
|
||||
+use them itself, and won't offer them to guests.
|
||||
|
||||
### cpuid_mask_cpu
|
||||
> `= fam_0f_rev_[cdefg] | fam_10_rev_[bc] | fam_11_rev_b`
|
||||
diff --git a/tools/libxl/libxl_cpuid.c b/tools/libxl/libxl_cpuid.c
|
||||
index 4cf0f0738d..88b5760c85 100644
|
||||
--- a/tools/libxl/libxl_cpuid.c
|
||||
+++ b/tools/libxl/libxl_cpuid.c
|
||||
@@ -203,6 +203,7 @@ int libxl_cpuid_parse_config(libxl_cpuid_policy_list *cpuid, const char* str)
|
||||
|
||||
{"avx512-4vnniw",0x00000007, 0, CPUID_REG_EDX, 2, 1},
|
||||
{"avx512-4fmaps",0x00000007, 0, CPUID_REG_EDX, 3, 1},
|
||||
+ {"srbds-ctrl", 0x00000007, 0, CPUID_REG_EDX, 9, 1},
|
||||
{"md-clear", 0x00000007, 0, CPUID_REG_EDX, 10, 1},
|
||||
{"cet-ibt", 0x00000007, 0, CPUID_REG_EDX, 20, 1},
|
||||
{"ibrsb", 0x00000007, 0, CPUID_REG_EDX, 26, 1},
|
||||
diff --git a/tools/misc/xen-cpuid.c b/tools/misc/xen-cpuid.c
|
||||
index 2a00697643..b4c4dfcf19 100644
|
||||
--- a/tools/misc/xen-cpuid.c
|
||||
+++ b/tools/misc/xen-cpuid.c
|
||||
@@ -154,6 +154,7 @@ static const char *str_7d0[32] =
|
||||
[ 2] = "avx512_4vnniw", [ 3] = "avx512_4fmaps",
|
||||
[ 4] = "fsrm",
|
||||
|
||||
+ /* 8 */ [ 9] = "srbds-ctrl",
|
||||
[10] = "md-clear",
|
||||
/* 12 */ [13] = "tsx-force-abort",
|
||||
|
||||
diff --git a/xen/arch/x86/cpuid.c b/xen/arch/x86/cpuid.c
|
||||
index 1727497459..22d8c71a95 100644
|
||||
--- a/xen/arch/x86/cpuid.c
|
||||
+++ b/xen/arch/x86/cpuid.c
|
||||
@@ -59,6 +59,11 @@ static int __init parse_xen_cpuid(const char *s)
|
||||
if ( !val )
|
||||
setup_clear_cpu_cap(X86_FEATURE_SSBD);
|
||||
}
|
||||
+ else if ( (val = parse_boolean("srbds-ctrl", s, ss)) >= 0 )
|
||||
+ {
|
||||
+ if ( !val )
|
||||
+ setup_clear_cpu_cap(X86_FEATURE_SRBDS_CTRL);
|
||||
+ }
|
||||
else
|
||||
rc = -EINVAL;
|
||||
|
||||
diff --git a/xen/arch/x86/msr.c b/xen/arch/x86/msr.c
|
||||
index 4888fff16c..9ff27b7007 100644
|
||||
--- a/xen/arch/x86/msr.c
|
||||
+++ b/xen/arch/x86/msr.c
|
||||
@@ -133,6 +133,7 @@ int guest_rdmsr(const struct vcpu *v, uint32_t msr, uint64_t *val)
|
||||
/* Write-only */
|
||||
case MSR_TSX_FORCE_ABORT:
|
||||
case MSR_TSX_CTRL:
|
||||
+ case MSR_MCU_OPT_CTRL:
|
||||
case MSR_U_CET:
|
||||
case MSR_S_CET:
|
||||
case MSR_PL0_SSP ... MSR_INTERRUPT_SSP_TABLE:
|
||||
@@ -273,6 +274,7 @@ int guest_wrmsr(struct vcpu *v, uint32_t msr, uint64_t val)
|
||||
/* Read-only */
|
||||
case MSR_TSX_FORCE_ABORT:
|
||||
case MSR_TSX_CTRL:
|
||||
+ case MSR_MCU_OPT_CTRL:
|
||||
case MSR_U_CET:
|
||||
case MSR_S_CET:
|
||||
case MSR_PL0_SSP ... MSR_INTERRUPT_SSP_TABLE:
|
||||
diff --git a/xen/arch/x86/spec_ctrl.c b/xen/arch/x86/spec_ctrl.c
|
||||
index 800139d79c..5158e012ca 100644
|
||||
--- a/xen/arch/x86/spec_ctrl.c
|
||||
+++ b/xen/arch/x86/spec_ctrl.c
|
||||
@@ -309,12 +309,13 @@ static void __init print_details(enum ind_thunk thunk, uint64_t caps)
|
||||
printk("Speculative mitigation facilities:\n");
|
||||
|
||||
/* Hardware features which pertain to speculative mitigations. */
|
||||
- printk(" Hardware features:%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n",
|
||||
+ printk(" Hardware features:%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n",
|
||||
(_7d0 & cpufeat_mask(X86_FEATURE_IBRSB)) ? " IBRS/IBPB" : "",
|
||||
(_7d0 & cpufeat_mask(X86_FEATURE_STIBP)) ? " STIBP" : "",
|
||||
(_7d0 & cpufeat_mask(X86_FEATURE_L1D_FLUSH)) ? " L1D_FLUSH" : "",
|
||||
(_7d0 & cpufeat_mask(X86_FEATURE_SSBD)) ? " SSBD" : "",
|
||||
(_7d0 & cpufeat_mask(X86_FEATURE_MD_CLEAR)) ? " MD_CLEAR" : "",
|
||||
+ (_7d0 & cpufeat_mask(X86_FEATURE_SRBDS_CTRL)) ? " SRBDS_CTRL" : "",
|
||||
(e8b & cpufeat_mask(X86_FEATURE_IBPB)) ? " IBPB" : "",
|
||||
(caps & ARCH_CAPS_IBRS_ALL) ? " IBRS_ALL" : "",
|
||||
(caps & ARCH_CAPS_RDCL_NO) ? " RDCL_NO" : "",
|
||||
diff --git a/xen/include/asm-x86/msr-index.h b/xen/include/asm-x86/msr-index.h
|
||||
index 7693c4a71a..91994669e1 100644
|
||||
--- a/xen/include/asm-x86/msr-index.h
|
||||
+++ b/xen/include/asm-x86/msr-index.h
|
||||
@@ -179,6 +179,9 @@
|
||||
#define MSR_IA32_VMX_TRUE_ENTRY_CTLS 0x490
|
||||
#define MSR_IA32_VMX_VMFUNC 0x491
|
||||
|
||||
+#define MSR_MCU_OPT_CTRL 0x00000123
|
||||
+#define MCU_OPT_CTRL_RNGDS_MITG_DIS (_AC(1, ULL) << 0)
|
||||
+
|
||||
#define MSR_U_CET 0x000006a0
|
||||
#define MSR_S_CET 0x000006a2
|
||||
#define MSR_PL0_SSP 0x000006a4
|
||||
diff --git a/xen/include/public/arch-x86/cpufeatureset.h b/xen/include/public/arch-x86/cpufeatureset.h
|
||||
index 865a435d2c..31490a7c10 100644
|
||||
--- a/xen/include/public/arch-x86/cpufeatureset.h
|
||||
+++ b/xen/include/public/arch-x86/cpufeatureset.h
|
||||
@@ -243,6 +243,7 @@ XEN_CPUFEATURE(IBPB, 8*32+12) /*A IBPB support only (no IBRS, used by
|
||||
/* Intel-defined CPU features, CPUID level 0x00000007:0.edx, word 9 */
|
||||
XEN_CPUFEATURE(AVX512_4VNNIW, 9*32+ 2) /*A AVX512 Neural Network Instructions */
|
||||
XEN_CPUFEATURE(AVX512_4FMAPS, 9*32+ 3) /*A AVX512 Multiply Accumulation Single Precision */
|
||||
+XEN_CPUFEATURE(SRBDS_CTRL, 9*32+ 9) /* MSR_MCU_OPT_CTRL and RNGDS_MITG_DIS. */
|
||||
XEN_CPUFEATURE(MD_CLEAR, 9*32+10) /*A VERW clears microarchitectural buffers */
|
||||
XEN_CPUFEATURE(TSX_FORCE_ABORT, 9*32+13) /* MSR_TSX_FORCE_ABORT.RTM_ABORT */
|
||||
XEN_CPUFEATURE(CET_IBT, 9*32+20) /* CET - Indirect Branch Tracking */
|
||||
179
xsa320-4.12-2.patch
Normal file
179
xsa320-4.12-2.patch
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
From: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Subject: x86/spec-ctrl: Mitigate the Special Register Buffer Data Sampling sidechannel
|
||||
|
||||
See patch documentation and comments.
|
||||
|
||||
This is part of XSA-320 / CVE-2020-0543
|
||||
|
||||
Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||||
|
||||
diff --git a/docs/misc/xen-command-line.pandoc b/docs/misc/xen-command-line.pandoc
|
||||
index dbdaee92dc..337fbf0492 100644
|
||||
--- a/docs/misc/xen-command-line.pandoc
|
||||
+++ b/docs/misc/xen-command-line.pandoc
|
||||
@@ -1909,7 +1909,7 @@ By default SSBD will be mitigated at runtime (i.e `ssbd=runtime`).
|
||||
### spec-ctrl (x86)
|
||||
> `= List of [ <bool>, xen=<bool>, {pv,hvm,msr-sc,rsb,md-clear}=<bool>,
|
||||
> bti-thunk=retpoline|lfence|jmp, {ibrs,ibpb,ssbd,eager-fpu,
|
||||
-> l1d-flush}=<bool> ]`
|
||||
+> l1d-flush,srb-lock}=<bool> ]`
|
||||
|
||||
Controls for speculative execution sidechannel mitigations. By default, Xen
|
||||
will pick the most appropriate mitigations based on compiled in support,
|
||||
@@ -1981,6 +1981,12 @@ Irrespective of Xen's setting, the feature is virtualised for HVM guests to
|
||||
use. By default, Xen will enable this mitigation on hardware believed to be
|
||||
vulnerable to L1TF.
|
||||
|
||||
+On hardware supporting SRBDS_CTRL, the `srb-lock=` option can be used to force
|
||||
+or prevent Xen from protect the Special Register Buffer from leaking stale
|
||||
+data. By default, Xen will enable this mitigation, except on parts where MDS
|
||||
+is fixed and TAA is fixed/mitigated (in which case, there is believed to be no
|
||||
+way for an attacker to obtain the stale data).
|
||||
+
|
||||
### sync_console
|
||||
> `= <boolean>`
|
||||
|
||||
diff --git a/xen/arch/x86/acpi/power.c b/xen/arch/x86/acpi/power.c
|
||||
index c1d772f63f..a07aa3b9ed 100644
|
||||
--- a/xen/arch/x86/acpi/power.c
|
||||
+++ b/xen/arch/x86/acpi/power.c
|
||||
@@ -266,6 +266,9 @@ static int enter_state(u32 state)
|
||||
ci->spec_ctrl_flags |= (default_spec_ctrl_flags & SCF_ist_wrmsr);
|
||||
spec_ctrl_exit_idle(ci);
|
||||
|
||||
+ if ( boot_cpu_has(X86_FEATURE_SRBDS_CTRL) )
|
||||
+ wrmsrl(MSR_MCU_OPT_CTRL, default_xen_mcu_opt_ctrl);
|
||||
+
|
||||
done:
|
||||
spin_debug_enable();
|
||||
local_irq_restore(flags);
|
||||
diff --git a/xen/arch/x86/smpboot.c b/xen/arch/x86/smpboot.c
|
||||
index 699e21bfb7..b741d1354a 100644
|
||||
--- a/xen/arch/x86/smpboot.c
|
||||
+++ b/xen/arch/x86/smpboot.c
|
||||
@@ -369,12 +369,14 @@ void start_secondary(void *unused)
|
||||
microcode_resume_cpu(cpu);
|
||||
|
||||
/*
|
||||
- * If MSR_SPEC_CTRL is available, apply Xen's default setting and discard
|
||||
- * any firmware settings. Note: MSR_SPEC_CTRL may only become available
|
||||
- * after loading microcode.
|
||||
+ * If any speculative control MSRs are available, apply Xen's default
|
||||
+ * settings. Note: These MSRs may only become available after loading
|
||||
+ * microcode.
|
||||
*/
|
||||
if ( boot_cpu_has(X86_FEATURE_IBRSB) )
|
||||
wrmsrl(MSR_SPEC_CTRL, default_xen_spec_ctrl);
|
||||
+ if ( boot_cpu_has(X86_FEATURE_SRBDS_CTRL) )
|
||||
+ wrmsrl(MSR_MCU_OPT_CTRL, default_xen_mcu_opt_ctrl);
|
||||
|
||||
tsx_init(); /* Needs microcode. May change HLE/RTM feature bits. */
|
||||
|
||||
diff --git a/xen/arch/x86/spec_ctrl.c b/xen/arch/x86/spec_ctrl.c
|
||||
index 5158e012ca..e2fcefc86a 100644
|
||||
--- a/xen/arch/x86/spec_ctrl.c
|
||||
+++ b/xen/arch/x86/spec_ctrl.c
|
||||
@@ -64,6 +64,9 @@ static unsigned int __initdata l1d_maxphysaddr;
|
||||
static bool __initdata cpu_has_bug_msbds_only; /* => minimal HT impact. */
|
||||
static bool __initdata cpu_has_bug_mds; /* Any other M{LP,SB,FB}DS combination. */
|
||||
|
||||
+static int8_t __initdata opt_srb_lock = -1;
|
||||
+uint64_t __read_mostly default_xen_mcu_opt_ctrl;
|
||||
+
|
||||
static int __init parse_spec_ctrl(const char *s)
|
||||
{
|
||||
const char *ss;
|
||||
@@ -110,6 +113,7 @@ static int __init parse_spec_ctrl(const char *s)
|
||||
opt_ibpb = false;
|
||||
opt_ssbd = false;
|
||||
opt_l1d_flush = 0;
|
||||
+ opt_srb_lock = 0;
|
||||
}
|
||||
else if ( val > 0 )
|
||||
rc = -EINVAL;
|
||||
@@ -175,6 +179,8 @@ static int __init parse_spec_ctrl(const char *s)
|
||||
opt_eager_fpu = val;
|
||||
else if ( (val = parse_boolean("l1d-flush", s, ss)) >= 0 )
|
||||
opt_l1d_flush = val;
|
||||
+ else if ( (val = parse_boolean("srb-lock", s, ss)) >= 0 )
|
||||
+ opt_srb_lock = val;
|
||||
else
|
||||
rc = -EINVAL;
|
||||
|
||||
@@ -338,7 +344,7 @@ static void __init print_details(enum ind_thunk thunk, uint64_t caps)
|
||||
"\n");
|
||||
|
||||
/* Settings for Xen's protection, irrespective of guests. */
|
||||
- printk(" Xen settings: BTI-Thunk %s, SPEC_CTRL: %s%s%s, Other:%s%s%s\n",
|
||||
+ printk(" Xen settings: BTI-Thunk %s, SPEC_CTRL: %s%s%s, Other:%s%s%s%s\n",
|
||||
thunk == THUNK_NONE ? "N/A" :
|
||||
thunk == THUNK_RETPOLINE ? "RETPOLINE" :
|
||||
thunk == THUNK_LFENCE ? "LFENCE" :
|
||||
@@ -349,6 +355,8 @@ static void __init print_details(enum ind_thunk thunk, uint64_t caps)
|
||||
(default_xen_spec_ctrl & SPEC_CTRL_SSBD) ? " SSBD+" : " SSBD-",
|
||||
!(caps & ARCH_CAPS_TSX_CTRL) ? "" :
|
||||
(opt_tsx & 1) ? " TSX+" : " TSX-",
|
||||
+ !boot_cpu_has(X86_FEATURE_SRBDS_CTRL) ? "" :
|
||||
+ opt_srb_lock ? " SRB_LOCK+" : " SRB_LOCK-",
|
||||
opt_ibpb ? " IBPB" : "",
|
||||
opt_l1d_flush ? " L1D_FLUSH" : "",
|
||||
opt_md_clear_pv || opt_md_clear_hvm ? " VERW" : "");
|
||||
@@ -1142,6 +1150,34 @@ void __init init_speculation_mitigations(void)
|
||||
tsx_init();
|
||||
}
|
||||
|
||||
+ /* Calculate suitable defaults for MSR_MCU_OPT_CTRL */
|
||||
+ if ( boot_cpu_has(X86_FEATURE_SRBDS_CTRL) )
|
||||
+ {
|
||||
+ uint64_t val;
|
||||
+
|
||||
+ rdmsrl(MSR_MCU_OPT_CTRL, val);
|
||||
+
|
||||
+ /*
|
||||
+ * On some SRBDS-affected hardware, it may be safe to relax srb-lock
|
||||
+ * by default.
|
||||
+ *
|
||||
+ * On parts which enumerate MDS_NO and not TAA_NO, TSX is the only way
|
||||
+ * to access the Fill Buffer. If TSX isn't available (inc. SKU
|
||||
+ * reasons on some models), or TSX is explicitly disabled, then there
|
||||
+ * is no need for the extra overhead to protect RDRAND/RDSEED.
|
||||
+ */
|
||||
+ if ( opt_srb_lock == -1 &&
|
||||
+ (caps & (ARCH_CAPS_MDS_NO|ARCH_CAPS_TAA_NO)) == ARCH_CAPS_MDS_NO &&
|
||||
+ (!cpu_has_hle || ((caps & ARCH_CAPS_TSX_CTRL) && opt_tsx == 0)) )
|
||||
+ opt_srb_lock = 0;
|
||||
+
|
||||
+ val &= ~MCU_OPT_CTRL_RNGDS_MITG_DIS;
|
||||
+ if ( !opt_srb_lock )
|
||||
+ val |= MCU_OPT_CTRL_RNGDS_MITG_DIS;
|
||||
+
|
||||
+ default_xen_mcu_opt_ctrl = val;
|
||||
+ }
|
||||
+
|
||||
print_details(thunk, caps);
|
||||
|
||||
/*
|
||||
@@ -1173,6 +1209,9 @@ void __init init_speculation_mitigations(void)
|
||||
|
||||
wrmsrl(MSR_SPEC_CTRL, bsp_delay_spec_ctrl ? 0 : default_xen_spec_ctrl);
|
||||
}
|
||||
+
|
||||
+ if ( boot_cpu_has(X86_FEATURE_SRBDS_CTRL) )
|
||||
+ wrmsrl(MSR_MCU_OPT_CTRL, default_xen_mcu_opt_ctrl);
|
||||
}
|
||||
|
||||
static void __init __maybe_unused build_assertions(void)
|
||||
diff --git a/xen/include/asm-x86/spec_ctrl.h b/xen/include/asm-x86/spec_ctrl.h
|
||||
index ba03bb42e5..59bab1a41b 100644
|
||||
--- a/xen/include/asm-x86/spec_ctrl.h
|
||||
+++ b/xen/include/asm-x86/spec_ctrl.h
|
||||
@@ -53,6 +53,8 @@ extern int8_t opt_pv_l1tf_hwdom, opt_pv_l1tf_domu;
|
||||
*/
|
||||
extern paddr_t l1tf_addr_mask, l1tf_safe_maddr;
|
||||
|
||||
+extern uint64_t default_xen_mcu_opt_ctrl;
|
||||
+
|
||||
static inline void init_shadow_spec_ctrl_state(void)
|
||||
{
|
||||
struct cpu_info *info = get_cpu_info();
|
||||
31
xsa321-4.12-1.patch
Normal file
31
xsa321-4.12-1.patch
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
From: Jan Beulich <jbeulich@suse.com>
|
||||
Subject: vtd: improve IOMMU TLB flush
|
||||
|
||||
Do not limit PSI flushes to order 0 pages, in order to avoid doing a
|
||||
full TLB flush if the passed in page has an order greater than 0 and
|
||||
is aligned. Should increase the performance of IOMMU TLB flushes when
|
||||
dealing with page orders greater than 0.
|
||||
|
||||
This is part of XSA-321.
|
||||
|
||||
Signed-off-by: Jan Beulich <jbeulich@suse.com>
|
||||
|
||||
--- a/xen/drivers/passthrough/vtd/iommu.c
|
||||
+++ b/xen/drivers/passthrough/vtd/iommu.c
|
||||
@@ -611,13 +611,14 @@ static int __must_check iommu_flush_iotl
|
||||
if ( iommu_domid == -1 )
|
||||
continue;
|
||||
|
||||
- if ( page_count != 1 || dfn_eq(dfn, INVALID_DFN) )
|
||||
+ if ( !page_count || (page_count & (page_count - 1)) ||
|
||||
+ dfn_eq(dfn, INVALID_DFN) || !IS_ALIGNED(dfn_x(dfn), page_count) )
|
||||
rc = iommu_flush_iotlb_dsi(iommu, iommu_domid,
|
||||
0, flush_dev_iotlb);
|
||||
else
|
||||
rc = iommu_flush_iotlb_psi(iommu, iommu_domid,
|
||||
dfn_to_daddr(dfn),
|
||||
- PAGE_ORDER_4K,
|
||||
+ get_order_from_pages(page_count),
|
||||
!dma_old_pte_present,
|
||||
flush_dev_iotlb);
|
||||
|
||||
175
xsa321-4.12-2.patch
Normal file
175
xsa321-4.12-2.patch
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
From: <security@xenproject.org>
|
||||
Subject: vtd: prune (and rename) cache flush functions
|
||||
|
||||
Rename __iommu_flush_cache to iommu_sync_cache and remove
|
||||
iommu_flush_cache_page. Also remove the iommu_flush_cache_entry
|
||||
wrapper and just use iommu_sync_cache instead. Note the _entry suffix
|
||||
was meaningless as the wrapper was already taking a size parameter in
|
||||
bytes. While there also constify the addr parameter.
|
||||
|
||||
No functional change intended.
|
||||
|
||||
This is part of XSA-321.
|
||||
|
||||
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||||
|
||||
--- a/xen/drivers/passthrough/vtd/extern.h
|
||||
+++ b/xen/drivers/passthrough/vtd/extern.h
|
||||
@@ -38,8 +38,7 @@ void disable_qinval(struct iommu *iommu)
|
||||
int enable_intremap(struct iommu *iommu, int eim);
|
||||
void disable_intremap(struct iommu *iommu);
|
||||
|
||||
-void iommu_flush_cache_entry(void *addr, unsigned int size);
|
||||
-void iommu_flush_cache_page(void *addr, unsigned long npages);
|
||||
+void iommu_sync_cache(const void *addr, unsigned int size);
|
||||
int iommu_alloc(struct acpi_drhd_unit *drhd);
|
||||
void iommu_free(struct acpi_drhd_unit *drhd);
|
||||
|
||||
--- a/xen/drivers/passthrough/vtd/intremap.c
|
||||
+++ b/xen/drivers/passthrough/vtd/intremap.c
|
||||
@@ -231,7 +231,7 @@ static void free_remap_entry(struct iomm
|
||||
iremap_entries, iremap_entry);
|
||||
|
||||
update_irte(iommu, iremap_entry, &new_ire, false);
|
||||
- iommu_flush_cache_entry(iremap_entry, sizeof(*iremap_entry));
|
||||
+ iommu_sync_cache(iremap_entry, sizeof(*iremap_entry));
|
||||
iommu_flush_iec_index(iommu, 0, index);
|
||||
|
||||
unmap_vtd_domain_page(iremap_entries);
|
||||
@@ -403,7 +403,7 @@ static int ioapic_rte_to_remap_entry(str
|
||||
}
|
||||
|
||||
update_irte(iommu, iremap_entry, &new_ire, !init);
|
||||
- iommu_flush_cache_entry(iremap_entry, sizeof(*iremap_entry));
|
||||
+ iommu_sync_cache(iremap_entry, sizeof(*iremap_entry));
|
||||
iommu_flush_iec_index(iommu, 0, index);
|
||||
|
||||
unmap_vtd_domain_page(iremap_entries);
|
||||
@@ -694,7 +694,7 @@ static int msi_msg_to_remap_entry(
|
||||
update_irte(iommu, iremap_entry, &new_ire, msi_desc->irte_initialized);
|
||||
msi_desc->irte_initialized = true;
|
||||
|
||||
- iommu_flush_cache_entry(iremap_entry, sizeof(*iremap_entry));
|
||||
+ iommu_sync_cache(iremap_entry, sizeof(*iremap_entry));
|
||||
iommu_flush_iec_index(iommu, 0, index);
|
||||
|
||||
unmap_vtd_domain_page(iremap_entries);
|
||||
--- a/xen/drivers/passthrough/vtd/iommu.c
|
||||
+++ b/xen/drivers/passthrough/vtd/iommu.c
|
||||
@@ -158,7 +158,8 @@ static void __init free_intel_iommu(stru
|
||||
}
|
||||
|
||||
static int iommus_incoherent;
|
||||
-static void __iommu_flush_cache(void *addr, unsigned int size)
|
||||
+
|
||||
+void iommu_sync_cache(const void *addr, unsigned int size)
|
||||
{
|
||||
int i;
|
||||
static unsigned int clflush_size = 0;
|
||||
@@ -173,16 +174,6 @@ static void __iommu_flush_cache(void *ad
|
||||
cacheline_flush((char *)addr + i);
|
||||
}
|
||||
|
||||
-void iommu_flush_cache_entry(void *addr, unsigned int size)
|
||||
-{
|
||||
- __iommu_flush_cache(addr, size);
|
||||
-}
|
||||
-
|
||||
-void iommu_flush_cache_page(void *addr, unsigned long npages)
|
||||
-{
|
||||
- __iommu_flush_cache(addr, PAGE_SIZE * npages);
|
||||
-}
|
||||
-
|
||||
/* Allocate page table, return its machine address */
|
||||
u64 alloc_pgtable_maddr(struct acpi_drhd_unit *drhd, unsigned long npages)
|
||||
{
|
||||
@@ -207,7 +198,7 @@ u64 alloc_pgtable_maddr(struct acpi_drhd
|
||||
vaddr = __map_domain_page(cur_pg);
|
||||
memset(vaddr, 0, PAGE_SIZE);
|
||||
|
||||
- iommu_flush_cache_page(vaddr, 1);
|
||||
+ iommu_sync_cache(vaddr, PAGE_SIZE);
|
||||
unmap_domain_page(vaddr);
|
||||
cur_pg++;
|
||||
}
|
||||
@@ -242,7 +233,7 @@ static u64 bus_to_context_maddr(struct i
|
||||
}
|
||||
set_root_value(*root, maddr);
|
||||
set_root_present(*root);
|
||||
- iommu_flush_cache_entry(root, sizeof(struct root_entry));
|
||||
+ iommu_sync_cache(root, sizeof(struct root_entry));
|
||||
}
|
||||
maddr = (u64) get_context_addr(*root);
|
||||
unmap_vtd_domain_page(root_entries);
|
||||
@@ -300,7 +291,7 @@ static u64 addr_to_dma_page_maddr(struct
|
||||
*/
|
||||
dma_set_pte_readable(*pte);
|
||||
dma_set_pte_writable(*pte);
|
||||
- iommu_flush_cache_entry(pte, sizeof(struct dma_pte));
|
||||
+ iommu_sync_cache(pte, sizeof(struct dma_pte));
|
||||
}
|
||||
|
||||
if ( level == 2 )
|
||||
@@ -681,7 +672,7 @@ static int __must_check dma_pte_clear_on
|
||||
*flush_flags |= IOMMU_FLUSHF_modified;
|
||||
|
||||
spin_unlock(&hd->arch.mapping_lock);
|
||||
- iommu_flush_cache_entry(pte, sizeof(struct dma_pte));
|
||||
+ iommu_sync_cache(pte, sizeof(struct dma_pte));
|
||||
|
||||
unmap_vtd_domain_page(page);
|
||||
|
||||
@@ -720,7 +711,7 @@ static void iommu_free_page_table(struct
|
||||
iommu_free_pagetable(dma_pte_addr(*pte), next_level);
|
||||
|
||||
dma_clear_pte(*pte);
|
||||
- iommu_flush_cache_entry(pte, sizeof(struct dma_pte));
|
||||
+ iommu_sync_cache(pte, sizeof(struct dma_pte));
|
||||
}
|
||||
|
||||
unmap_vtd_domain_page(pt_vaddr);
|
||||
@@ -1449,7 +1440,7 @@ int domain_context_mapping_one(
|
||||
context_set_address_width(*context, agaw);
|
||||
context_set_fault_enable(*context);
|
||||
context_set_present(*context);
|
||||
- iommu_flush_cache_entry(context, sizeof(struct context_entry));
|
||||
+ iommu_sync_cache(context, sizeof(struct context_entry));
|
||||
spin_unlock(&iommu->lock);
|
||||
|
||||
/* Context entry was previously non-present (with domid 0). */
|
||||
@@ -1602,7 +1593,7 @@ int domain_context_unmap_one(
|
||||
|
||||
context_clear_present(*context);
|
||||
context_clear_entry(*context);
|
||||
- iommu_flush_cache_entry(context, sizeof(struct context_entry));
|
||||
+ iommu_sync_cache(context, sizeof(struct context_entry));
|
||||
|
||||
iommu_domid= domain_iommu_domid(domain, iommu);
|
||||
if ( iommu_domid == -1 )
|
||||
@@ -1837,7 +1828,7 @@ static int __must_check intel_iommu_map_
|
||||
|
||||
*pte = new;
|
||||
|
||||
- iommu_flush_cache_entry(pte, sizeof(struct dma_pte));
|
||||
+ iommu_sync_cache(pte, sizeof(struct dma_pte));
|
||||
spin_unlock(&hd->arch.mapping_lock);
|
||||
unmap_vtd_domain_page(page);
|
||||
|
||||
@@ -1912,7 +1903,7 @@ int iommu_pte_flush(struct domain *d, ui
|
||||
int iommu_domid;
|
||||
int rc = 0;
|
||||
|
||||
- iommu_flush_cache_entry(pte, sizeof(struct dma_pte));
|
||||
+ iommu_sync_cache(pte, sizeof(struct dma_pte));
|
||||
|
||||
for_each_drhd_unit ( drhd )
|
||||
{
|
||||
@@ -2777,7 +2768,7 @@ static int __init intel_iommu_quarantine
|
||||
dma_set_pte_addr(*pte, maddr);
|
||||
dma_set_pte_readable(*pte);
|
||||
}
|
||||
- iommu_flush_cache_page(parent, 1);
|
||||
+ iommu_sync_cache(parent, PAGE_SIZE);
|
||||
|
||||
unmap_vtd_domain_page(parent);
|
||||
parent = map_vtd_domain_page(maddr);
|
||||
82
xsa321-4.12-3.patch
Normal file
82
xsa321-4.12-3.patch
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
From: <security@xenproject.org>
|
||||
Subject: x86/iommu: introduce a cache sync hook
|
||||
|
||||
The hook is only implemented for VT-d and it uses the already existing
|
||||
iommu_sync_cache function present in VT-d code. The new hook is
|
||||
added so that the cache can be flushed by code outside of VT-d when
|
||||
using shared page tables.
|
||||
|
||||
Note that alloc_pgtable_maddr must use the now locally defined
|
||||
sync_cache function, because IOMMU ops are not yet setup the first
|
||||
time the function gets called during IOMMU initialization.
|
||||
|
||||
No functional change intended.
|
||||
|
||||
This is part of XSA-321.
|
||||
|
||||
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||||
|
||||
--- a/xen/drivers/passthrough/vtd/extern.h
|
||||
+++ b/xen/drivers/passthrough/vtd/extern.h
|
||||
@@ -38,7 +38,6 @@ void disable_qinval(struct iommu *iommu)
|
||||
int enable_intremap(struct iommu *iommu, int eim);
|
||||
void disable_intremap(struct iommu *iommu);
|
||||
|
||||
-void iommu_sync_cache(const void *addr, unsigned int size);
|
||||
int iommu_alloc(struct acpi_drhd_unit *drhd);
|
||||
void iommu_free(struct acpi_drhd_unit *drhd);
|
||||
|
||||
--- a/xen/drivers/passthrough/vtd/iommu.c
|
||||
+++ b/xen/drivers/passthrough/vtd/iommu.c
|
||||
@@ -159,7 +159,7 @@ static void __init free_intel_iommu(stru
|
||||
|
||||
static int iommus_incoherent;
|
||||
|
||||
-void iommu_sync_cache(const void *addr, unsigned int size)
|
||||
+static void sync_cache(const void *addr, unsigned int size)
|
||||
{
|
||||
int i;
|
||||
static unsigned int clflush_size = 0;
|
||||
@@ -198,7 +198,7 @@ u64 alloc_pgtable_maddr(struct acpi_drhd
|
||||
vaddr = __map_domain_page(cur_pg);
|
||||
memset(vaddr, 0, PAGE_SIZE);
|
||||
|
||||
- iommu_sync_cache(vaddr, PAGE_SIZE);
|
||||
+ sync_cache(vaddr, PAGE_SIZE);
|
||||
unmap_domain_page(vaddr);
|
||||
cur_pg++;
|
||||
}
|
||||
@@ -2813,6 +2813,7 @@ const struct iommu_ops __initconstrel in
|
||||
.iotlb_flush_all = iommu_flush_iotlb_all,
|
||||
.get_reserved_device_memory = intel_iommu_get_reserved_device_memory,
|
||||
.dump_p2m_table = vtd_dump_p2m_table,
|
||||
+ .sync_cache = sync_cache,
|
||||
};
|
||||
|
||||
/*
|
||||
--- a/xen/include/asm-x86/iommu.h
|
||||
+++ b/xen/include/asm-x86/iommu.h
|
||||
@@ -101,6 +101,13 @@ extern bool untrusted_msi;
|
||||
int pi_update_irte(const struct pi_desc *pi_desc, const struct pirq *pirq,
|
||||
const uint8_t gvec);
|
||||
|
||||
+#define iommu_sync_cache(addr, size) ({ \
|
||||
+ const struct iommu_ops *ops = iommu_get_ops(); \
|
||||
+ \
|
||||
+ if ( ops->sync_cache ) \
|
||||
+ ops->sync_cache(addr, size); \
|
||||
+})
|
||||
+
|
||||
#endif /* !__ARCH_X86_IOMMU_H__ */
|
||||
/*
|
||||
* Local variables:
|
||||
--- a/xen/include/xen/iommu.h
|
||||
+++ b/xen/include/xen/iommu.h
|
||||
@@ -221,6 +221,7 @@ struct iommu_ops {
|
||||
void (*update_ire_from_apic)(unsigned int apic, unsigned int reg, unsigned int value);
|
||||
unsigned int (*read_apic_from_ire)(unsigned int apic, unsigned int reg);
|
||||
int (*setup_hpet_msi)(struct msi_desc *);
|
||||
+ void (*sync_cache)(const void *addr, unsigned int size);
|
||||
#endif /* CONFIG_X86 */
|
||||
int __must_check (*suspend)(void);
|
||||
void (*resume)(void);
|
||||
36
xsa321-4.12-4.patch
Normal file
36
xsa321-4.12-4.patch
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
From: <security@xenproject.org>
|
||||
Subject: vtd: don't assume addresses are aligned in sync_cache
|
||||
|
||||
Current code in sync_cache assume that the address passed in is
|
||||
aligned to a cache line size. Fix the code to support passing in
|
||||
arbitrary addresses not necessarily aligned to a cache line size.
|
||||
|
||||
This is part of XSA-321.
|
||||
|
||||
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||||
|
||||
--- a/xen/drivers/passthrough/vtd/iommu.c
|
||||
+++ b/xen/drivers/passthrough/vtd/iommu.c
|
||||
@@ -161,8 +161,8 @@ static int iommus_incoherent;
|
||||
|
||||
static void sync_cache(const void *addr, unsigned int size)
|
||||
{
|
||||
- int i;
|
||||
- static unsigned int clflush_size = 0;
|
||||
+ static unsigned long clflush_size = 0;
|
||||
+ const void *end = addr + size;
|
||||
|
||||
if ( !iommus_incoherent )
|
||||
return;
|
||||
@@ -170,8 +170,9 @@ static void sync_cache(const void *addr,
|
||||
if ( clflush_size == 0 )
|
||||
clflush_size = get_cache_line_size();
|
||||
|
||||
- for ( i = 0; i < size; i += clflush_size )
|
||||
- cacheline_flush((char *)addr + i);
|
||||
+ addr -= (unsigned long)addr & (clflush_size - 1);
|
||||
+ for ( ; addr < end; addr += clflush_size )
|
||||
+ cacheline_flush((char *)addr);
|
||||
}
|
||||
|
||||
/* Allocate page table, return its machine address */
|
||||
24
xsa321-4.12-5.patch
Normal file
24
xsa321-4.12-5.patch
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
From: <security@xenproject.org>
|
||||
Subject: x86/alternative: introduce alternative_2
|
||||
|
||||
It's based on alternative_io_2 without inputs or outputs but with an
|
||||
added memory clobber.
|
||||
|
||||
This is part of XSA-321.
|
||||
|
||||
Acked-by: Jan Beulich <jbeulich@suse.com>
|
||||
|
||||
--- a/xen/include/asm-x86/alternative.h
|
||||
+++ b/xen/include/asm-x86/alternative.h
|
||||
@@ -113,6 +113,11 @@ extern void alternative_instructions(voi
|
||||
#define alternative(oldinstr, newinstr, feature) \
|
||||
asm volatile (ALTERNATIVE(oldinstr, newinstr, feature) : : : "memory")
|
||||
|
||||
+#define alternative_2(oldinstr, newinstr1, feature1, newinstr2, feature2) \
|
||||
+ asm volatile (ALTERNATIVE_2(oldinstr, newinstr1, feature1, \
|
||||
+ newinstr2, feature2) \
|
||||
+ : : : "memory")
|
||||
+
|
||||
/*
|
||||
* Alternative inline assembly with input.
|
||||
*
|
||||
91
xsa321-4.12-6.patch
Normal file
91
xsa321-4.12-6.patch
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
From: <security@xenproject.org>
|
||||
Subject: vtd: optimize CPU cache sync
|
||||
|
||||
Some VT-d IOMMUs are non-coherent, which requires a cache write back
|
||||
in order for the changes made by the CPU to be visible to the IOMMU.
|
||||
This cache write back was unconditionally done using clflush, but there are
|
||||
other more efficient instructions to do so, hence implement support
|
||||
for them using the alternative framework.
|
||||
|
||||
This is part of XSA-321.
|
||||
|
||||
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||||
|
||||
--- a/xen/drivers/passthrough/vtd/extern.h
|
||||
+++ b/xen/drivers/passthrough/vtd/extern.h
|
||||
@@ -64,7 +64,6 @@ int __must_check qinval_device_iotlb_syn
|
||||
u16 did, u16 size, u64 addr);
|
||||
|
||||
unsigned int get_cache_line_size(void);
|
||||
-void cacheline_flush(char *);
|
||||
void flush_all_cache(void);
|
||||
|
||||
u64 alloc_pgtable_maddr(struct acpi_drhd_unit *drhd, unsigned long npages);
|
||||
--- a/xen/drivers/passthrough/vtd/iommu.c
|
||||
+++ b/xen/drivers/passthrough/vtd/iommu.c
|
||||
@@ -31,6 +31,7 @@
|
||||
#include <xen/pci_regs.h>
|
||||
#include <xen/keyhandler.h>
|
||||
#include <asm/msi.h>
|
||||
+#include <asm/nops.h>
|
||||
#include <asm/irq.h>
|
||||
#include <asm/hvm/vmx/vmx.h>
|
||||
#include <asm/p2m.h>
|
||||
@@ -172,7 +173,42 @@ static void sync_cache(const void *addr,
|
||||
|
||||
addr -= (unsigned long)addr & (clflush_size - 1);
|
||||
for ( ; addr < end; addr += clflush_size )
|
||||
- cacheline_flush((char *)addr);
|
||||
+/*
|
||||
+ * The arguments to a macro must not include preprocessor directives. Doing so
|
||||
+ * results in undefined behavior, so we have to create some defines here in
|
||||
+ * order to avoid it.
|
||||
+ */
|
||||
+#if defined(HAVE_AS_CLWB)
|
||||
+# define CLWB_ENCODING "clwb %[p]"
|
||||
+#elif defined(HAVE_AS_XSAVEOPT)
|
||||
+# define CLWB_ENCODING "data16 xsaveopt %[p]" /* clwb */
|
||||
+#else
|
||||
+# define CLWB_ENCODING ".byte 0x66, 0x0f, 0xae, 0x30" /* clwb (%%rax) */
|
||||
+#endif
|
||||
+
|
||||
+#define BASE_INPUT(addr) [p] "m" (*(const char *)(addr))
|
||||
+#if defined(HAVE_AS_CLWB) || defined(HAVE_AS_XSAVEOPT)
|
||||
+# define INPUT BASE_INPUT
|
||||
+#else
|
||||
+# define INPUT(addr) "a" (addr), BASE_INPUT(addr)
|
||||
+#endif
|
||||
+ /*
|
||||
+ * Note regarding the use of NOP_DS_PREFIX: it's faster to do a clflush
|
||||
+ * + prefix than a clflush + nop, and hence the prefix is added instead
|
||||
+ * of letting the alternative framework fill the gap by appending nops.
|
||||
+ */
|
||||
+ alternative_io_2(".byte " __stringify(NOP_DS_PREFIX) "; clflush %[p]",
|
||||
+ "data16 clflush %[p]", /* clflushopt */
|
||||
+ X86_FEATURE_CLFLUSHOPT,
|
||||
+ CLWB_ENCODING,
|
||||
+ X86_FEATURE_CLWB, /* no outputs */,
|
||||
+ INPUT(addr));
|
||||
+#undef INPUT
|
||||
+#undef BASE_INPUT
|
||||
+#undef CLWB_ENCODING
|
||||
+
|
||||
+ alternative_2("", "sfence", X86_FEATURE_CLFLUSHOPT,
|
||||
+ "sfence", X86_FEATURE_CLWB);
|
||||
}
|
||||
|
||||
/* Allocate page table, return its machine address */
|
||||
--- a/xen/drivers/passthrough/vtd/x86/vtd.c
|
||||
+++ b/xen/drivers/passthrough/vtd/x86/vtd.c
|
||||
@@ -51,11 +51,6 @@ unsigned int get_cache_line_size(void)
|
||||
return ((cpuid_ebx(1) >> 8) & 0xff) * 8;
|
||||
}
|
||||
|
||||
-void cacheline_flush(char * addr)
|
||||
-{
|
||||
- clflush(addr);
|
||||
-}
|
||||
-
|
||||
void flush_all_cache()
|
||||
{
|
||||
wbinvd();
|
||||
151
xsa321-4.12-7.patch
Normal file
151
xsa321-4.12-7.patch
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
From: <security@xenproject.org>
|
||||
Subject: x86/ept: flush cache when modifying PTEs and sharing page tables
|
||||
|
||||
Modifications made to the page tables by EPT code need to be written
|
||||
to memory when the page tables are shared with the IOMMU, as Intel
|
||||
IOMMUs can be non-coherent and thus require changes to be written to
|
||||
memory in order to be visible to the IOMMU.
|
||||
|
||||
In order to achieve this make sure data is written back to memory
|
||||
after writing an EPT entry when the recalc bit is not set in
|
||||
atomic_write_ept_entry. If such bit is set, the entry will be
|
||||
adjusted and atomic_write_ept_entry will be called a second time
|
||||
without the recalc bit set. Note that when splitting a super page the
|
||||
new tables resulting of the split should also be written back.
|
||||
|
||||
Failure to do so can allow devices behind the IOMMU access to the
|
||||
stale super page, or cause coherency issues as changes made by the
|
||||
processor to the page tables are not visible to the IOMMU.
|
||||
|
||||
This allows to remove the VT-d specific iommu_pte_flush helper, since
|
||||
the cache write back is now performed by atomic_write_ept_entry, and
|
||||
hence iommu_iotlb_flush can be used to flush the IOMMU TLB. The newly
|
||||
used method (iommu_iotlb_flush) can result in less flushes, since it
|
||||
might sometimes be called rightly with 0 flags, in which case it
|
||||
becomes a no-op.
|
||||
|
||||
This is part of XSA-321.
|
||||
|
||||
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||||
|
||||
--- a/xen/arch/x86/mm/p2m-ept.c
|
||||
+++ b/xen/arch/x86/mm/p2m-ept.c
|
||||
@@ -58,6 +58,19 @@ static int atomic_write_ept_entry(struct
|
||||
|
||||
write_atomic(&entryptr->epte, new.epte);
|
||||
|
||||
+ /*
|
||||
+ * The recalc field on the EPT is used to signal either that a
|
||||
+ * recalculation of the EMT field is required (which doesn't effect the
|
||||
+ * IOMMU), or a type change. Type changes can only be between ram_rw,
|
||||
+ * logdirty and ioreq_server: changes to/from logdirty won't work well with
|
||||
+ * an IOMMU anyway, as IOMMU #PFs are not synchronous and will lead to
|
||||
+ * aborts, and changes to/from ioreq_server are already fully flushed
|
||||
+ * before returning to guest context (see
|
||||
+ * XEN_DMOP_map_mem_type_to_ioreq_server).
|
||||
+ */
|
||||
+ if ( !new.recalc && iommu_use_hap_pt(p2m->domain) )
|
||||
+ iommu_sync_cache(entryptr, sizeof(*entryptr));
|
||||
+
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -278,6 +291,9 @@ static bool_t ept_split_super_page(struc
|
||||
break;
|
||||
}
|
||||
|
||||
+ if ( iommu_use_hap_pt(p2m->domain) )
|
||||
+ iommu_sync_cache(table, EPT_PAGETABLE_ENTRIES * sizeof(ept_entry_t));
|
||||
+
|
||||
unmap_domain_page(table);
|
||||
|
||||
/* Even failed we should install the newly allocated ept page. */
|
||||
@@ -337,6 +353,9 @@ static int ept_next_level(struct p2m_dom
|
||||
if ( !next )
|
||||
return GUEST_TABLE_MAP_FAILED;
|
||||
|
||||
+ if ( iommu_use_hap_pt(p2m->domain) )
|
||||
+ iommu_sync_cache(next, EPT_PAGETABLE_ENTRIES * sizeof(ept_entry_t));
|
||||
+
|
||||
rc = atomic_write_ept_entry(p2m, ept_entry, e, next_level);
|
||||
ASSERT(rc == 0);
|
||||
}
|
||||
@@ -815,7 +834,10 @@ out:
|
||||
need_modify_vtd_table )
|
||||
{
|
||||
if ( iommu_use_hap_pt(d) )
|
||||
- rc = iommu_pte_flush(d, gfn, &ept_entry->epte, order, vtd_pte_present);
|
||||
+ rc = iommu_iotlb_flush(d, _dfn(gfn), (1u << order),
|
||||
+ (iommu_flags ? IOMMU_FLUSHF_added : 0) |
|
||||
+ (vtd_pte_present ? IOMMU_FLUSHF_modified
|
||||
+ : 0));
|
||||
else if ( need_iommu_pt_sync(d) )
|
||||
rc = iommu_flags ?
|
||||
iommu_legacy_map(d, _dfn(gfn), mfn, order, iommu_flags) :
|
||||
--- a/xen/drivers/passthrough/vtd/iommu.c
|
||||
+++ b/xen/drivers/passthrough/vtd/iommu.c
|
||||
@@ -1930,53 +1930,6 @@ static int intel_iommu_lookup_page(struc
|
||||
return 0;
|
||||
}
|
||||
|
||||
-int iommu_pte_flush(struct domain *d, uint64_t dfn, uint64_t *pte,
|
||||
- int order, int present)
|
||||
-{
|
||||
- struct acpi_drhd_unit *drhd;
|
||||
- struct iommu *iommu = NULL;
|
||||
- struct domain_iommu *hd = dom_iommu(d);
|
||||
- bool_t flush_dev_iotlb;
|
||||
- int iommu_domid;
|
||||
- int rc = 0;
|
||||
-
|
||||
- iommu_sync_cache(pte, sizeof(struct dma_pte));
|
||||
-
|
||||
- for_each_drhd_unit ( drhd )
|
||||
- {
|
||||
- iommu = drhd->iommu;
|
||||
- if ( !test_bit(iommu->index, &hd->arch.iommu_bitmap) )
|
||||
- continue;
|
||||
-
|
||||
- flush_dev_iotlb = !!find_ats_dev_drhd(iommu);
|
||||
- iommu_domid= domain_iommu_domid(d, iommu);
|
||||
- if ( iommu_domid == -1 )
|
||||
- continue;
|
||||
-
|
||||
- rc = iommu_flush_iotlb_psi(iommu, iommu_domid,
|
||||
- __dfn_to_daddr(dfn),
|
||||
- order, !present, flush_dev_iotlb);
|
||||
- if ( rc > 0 )
|
||||
- {
|
||||
- iommu_flush_write_buffer(iommu);
|
||||
- rc = 0;
|
||||
- }
|
||||
- }
|
||||
-
|
||||
- if ( unlikely(rc) )
|
||||
- {
|
||||
- if ( !d->is_shutting_down && printk_ratelimit() )
|
||||
- printk(XENLOG_ERR VTDPREFIX
|
||||
- " d%d: IOMMU pages flush failed: %d\n",
|
||||
- d->domain_id, rc);
|
||||
-
|
||||
- if ( !is_hardware_domain(d) )
|
||||
- domain_crash(d);
|
||||
- }
|
||||
-
|
||||
- return rc;
|
||||
-}
|
||||
-
|
||||
static int __init vtd_ept_page_compatible(struct iommu *iommu)
|
||||
{
|
||||
u64 ept_cap, vtd_cap = iommu->cap;
|
||||
--- a/xen/include/asm-x86/iommu.h
|
||||
+++ b/xen/include/asm-x86/iommu.h
|
||||
@@ -90,8 +90,6 @@ int iommu_setup_hpet_msi(struct msi_desc
|
||||
|
||||
/* While VT-d specific, this must get declared in a generic header. */
|
||||
int adjust_vtd_irq_affinities(void);
|
||||
-int __must_check iommu_pte_flush(struct domain *d, u64 gfn, u64 *pte,
|
||||
- int order, int present);
|
||||
bool_t iommu_supports_eim(void);
|
||||
int iommu_enable_x2apic_IR(void);
|
||||
void iommu_disable_x2apic_IR(void);
|
||||
63
xsa327.patch
Normal file
63
xsa327.patch
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
From 030300ebbb86c40c12db038714479d746167c767 Mon Sep 17 00:00:00 2001
|
||||
From: Julien Grall <jgrall@amazon.com>
|
||||
Date: Tue, 26 May 2020 18:31:33 +0100
|
||||
Subject: [PATCH] xen: Check the alignment of the offset pased via
|
||||
VCPUOP_register_vcpu_info
|
||||
|
||||
Currently a guest is able to register any guest physical address to use
|
||||
for the vcpu_info structure as long as the structure can fits in the
|
||||
rest of the frame.
|
||||
|
||||
This means a guest can provide an address that is not aligned to the
|
||||
natural alignment of the structure.
|
||||
|
||||
On Arm 32-bit, unaligned access are completely forbidden by the
|
||||
hypervisor. This will result to a data abort which is fatal.
|
||||
|
||||
On Arm 64-bit, unaligned access are only forbidden when used for atomic
|
||||
access. As the structure contains fields (such as evtchn_pending_self)
|
||||
that are updated using atomic operations, any unaligned access will be
|
||||
fatal as well.
|
||||
|
||||
While the misalignment is only fatal on Arm, a generic check is added
|
||||
as an x86 guest shouldn't sensibly pass an unaligned address (this
|
||||
would result to a split lock).
|
||||
|
||||
This is XSA-327.
|
||||
|
||||
Reported-by: Julien Grall <jgrall@amazon.com>
|
||||
Signed-off-by: Julien Grall <jgrall@amazon.com>
|
||||
Reviewed-by: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Reviewed-by: Stefano Stabellini <sstabellini@kernel.org>
|
||||
---
|
||||
xen/common/domain.c | 10 ++++++++++
|
||||
1 file changed, 10 insertions(+)
|
||||
|
||||
diff --git a/xen/common/domain.c b/xen/common/domain.c
|
||||
index 7cc9526139a6..e9be05f1d05f 100644
|
||||
--- a/xen/common/domain.c
|
||||
+++ b/xen/common/domain.c
|
||||
@@ -1227,10 +1227,20 @@ int map_vcpu_info(struct vcpu *v, unsigned long gfn, unsigned offset)
|
||||
void *mapping;
|
||||
vcpu_info_t *new_info;
|
||||
struct page_info *page;
|
||||
+ unsigned int align;
|
||||
|
||||
if ( offset > (PAGE_SIZE - sizeof(vcpu_info_t)) )
|
||||
return -EINVAL;
|
||||
|
||||
+#ifdef CONFIG_COMPAT
|
||||
+ if ( has_32bit_shinfo(d) )
|
||||
+ align = alignof(new_info->compat);
|
||||
+ else
|
||||
+#endif
|
||||
+ align = alignof(*new_info);
|
||||
+ if ( offset & (align - 1) )
|
||||
+ return -EINVAL;
|
||||
+
|
||||
if ( !mfn_eq(v->vcpu_info_mfn, INVALID_MFN) )
|
||||
return -EINVAL;
|
||||
|
||||
--
|
||||
2.17.1
|
||||
|
||||
118
xsa328-4.12-1.patch
Normal file
118
xsa328-4.12-1.patch
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
From: Jan Beulich <jbeulich@suse.com>
|
||||
Subject: x86/EPT: ept_set_middle_entry() related adjustments
|
||||
|
||||
ept_split_super_page() wants to further modify the newly allocated
|
||||
table, so have ept_set_middle_entry() return the mapped pointer rather
|
||||
than tearing it down and then getting re-established right again.
|
||||
|
||||
Similarly ept_next_level() wants to hand back a mapped pointer of
|
||||
the next level page, so re-use the one established by
|
||||
ept_set_middle_entry() in case that path was taken.
|
||||
|
||||
Pull the setting of suppress_ve ahead of insertion into the higher level
|
||||
table, and don't have ept_split_super_page() set the field a 2nd time.
|
||||
|
||||
This is part of XSA-328.
|
||||
|
||||
Signed-off-by: Jan Beulich <jbeulich@suse.com>
|
||||
|
||||
--- a/xen/arch/x86/mm/p2m-ept.c
|
||||
+++ b/xen/arch/x86/mm/p2m-ept.c
|
||||
@@ -187,8 +187,9 @@ static void ept_p2m_type_to_flags(struct
|
||||
#define GUEST_TABLE_SUPER_PAGE 2
|
||||
#define GUEST_TABLE_POD_PAGE 3
|
||||
|
||||
-/* Fill in middle levels of ept table */
|
||||
-static int ept_set_middle_entry(struct p2m_domain *p2m, ept_entry_t *ept_entry)
|
||||
+/* Fill in middle level of ept table; return pointer to mapped new table. */
|
||||
+static ept_entry_t *ept_set_middle_entry(struct p2m_domain *p2m,
|
||||
+ ept_entry_t *ept_entry)
|
||||
{
|
||||
mfn_t mfn;
|
||||
ept_entry_t *table;
|
||||
@@ -196,7 +197,12 @@ static int ept_set_middle_entry(struct p
|
||||
|
||||
mfn = p2m_alloc_ptp(p2m, 0);
|
||||
if ( mfn_eq(mfn, INVALID_MFN) )
|
||||
- return 0;
|
||||
+ return NULL;
|
||||
+
|
||||
+ table = map_domain_page(mfn);
|
||||
+
|
||||
+ for ( i = 0; i < EPT_PAGETABLE_ENTRIES; i++ )
|
||||
+ table[i].suppress_ve = 1;
|
||||
|
||||
ept_entry->epte = 0;
|
||||
ept_entry->mfn = mfn_x(mfn);
|
||||
@@ -208,14 +214,7 @@ static int ept_set_middle_entry(struct p
|
||||
|
||||
ept_entry->suppress_ve = 1;
|
||||
|
||||
- table = map_domain_page(mfn);
|
||||
-
|
||||
- for ( i = 0; i < EPT_PAGETABLE_ENTRIES; i++ )
|
||||
- table[i].suppress_ve = 1;
|
||||
-
|
||||
- unmap_domain_page(table);
|
||||
-
|
||||
- return 1;
|
||||
+ return table;
|
||||
}
|
||||
|
||||
/* free ept sub tree behind an entry */
|
||||
@@ -253,10 +252,10 @@ static bool_t ept_split_super_page(struc
|
||||
|
||||
ASSERT(is_epte_superpage(ept_entry));
|
||||
|
||||
- if ( !ept_set_middle_entry(p2m, &new_ept) )
|
||||
+ table = ept_set_middle_entry(p2m, &new_ept);
|
||||
+ if ( !table )
|
||||
return 0;
|
||||
|
||||
- table = map_domain_page(_mfn(new_ept.mfn));
|
||||
trunk = 1UL << ((level - 1) * EPT_TABLE_ORDER);
|
||||
|
||||
for ( i = 0; i < EPT_PAGETABLE_ENTRIES; i++ )
|
||||
@@ -267,7 +266,6 @@ static bool_t ept_split_super_page(struc
|
||||
epte->sp = (level > 1);
|
||||
epte->mfn += i * trunk;
|
||||
epte->snp = (iommu_enabled && iommu_snoop);
|
||||
- epte->suppress_ve = 1;
|
||||
|
||||
ept_p2m_type_to_flags(p2m, epte, epte->sa_p2mt, epte->access);
|
||||
|
||||
@@ -306,8 +304,7 @@ static int ept_next_level(struct p2m_dom
|
||||
ept_entry_t **table, unsigned long *gfn_remainder,
|
||||
int next_level)
|
||||
{
|
||||
- unsigned long mfn;
|
||||
- ept_entry_t *ept_entry, e;
|
||||
+ ept_entry_t *ept_entry, *next = NULL, e;
|
||||
u32 shift, index;
|
||||
|
||||
shift = next_level * EPT_TABLE_ORDER;
|
||||
@@ -332,19 +329,17 @@ static int ept_next_level(struct p2m_dom
|
||||
if ( read_only )
|
||||
return GUEST_TABLE_MAP_FAILED;
|
||||
|
||||
- if ( !ept_set_middle_entry(p2m, ept_entry) )
|
||||
+ next = ept_set_middle_entry(p2m, ept_entry);
|
||||
+ if ( !next )
|
||||
return GUEST_TABLE_MAP_FAILED;
|
||||
- else
|
||||
- e = atomic_read_ept_entry(ept_entry); /* Refresh */
|
||||
+ /* e is now stale and hence may not be used anymore below. */
|
||||
}
|
||||
-
|
||||
/* The only time sp would be set here is if we had hit a superpage */
|
||||
- if ( is_epte_superpage(&e) )
|
||||
+ else if ( is_epte_superpage(&e) )
|
||||
return GUEST_TABLE_SUPER_PAGE;
|
||||
|
||||
- mfn = e.mfn;
|
||||
unmap_domain_page(*table);
|
||||
- *table = map_domain_page(_mfn(mfn));
|
||||
+ *table = next ?: map_domain_page(_mfn(e.mfn));
|
||||
*gfn_remainder &= (1UL << shift) - 1;
|
||||
return GUEST_TABLE_NORMAL_PAGE;
|
||||
}
|
||||
48
xsa328-4.12-2.patch
Normal file
48
xsa328-4.12-2.patch
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
From: <security@xenproject.org>
|
||||
Subject: x86/ept: atomically modify entries in ept_next_level
|
||||
|
||||
ept_next_level was passing a live PTE pointer to ept_set_middle_entry,
|
||||
which was then modified without taking into account that the PTE could
|
||||
be part of a live EPT table. This wasn't a security issue because the
|
||||
pages returned by p2m_alloc_ptp are zeroed, so adding such an entry
|
||||
before actually initializing it didn't allow a guest to access
|
||||
physical memory addresses it wasn't supposed to access.
|
||||
|
||||
This is part of XSA-328.
|
||||
|
||||
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||||
|
||||
--- a/xen/arch/x86/mm/p2m-ept.c
|
||||
+++ b/xen/arch/x86/mm/p2m-ept.c
|
||||
@@ -307,6 +307,8 @@ static int ept_next_level(struct p2m_dom
|
||||
ept_entry_t *ept_entry, *next = NULL, e;
|
||||
u32 shift, index;
|
||||
|
||||
+ ASSERT(next_level);
|
||||
+
|
||||
shift = next_level * EPT_TABLE_ORDER;
|
||||
|
||||
index = *gfn_remainder >> shift;
|
||||
@@ -323,16 +325,20 @@ static int ept_next_level(struct p2m_dom
|
||||
|
||||
if ( !is_epte_present(&e) )
|
||||
{
|
||||
+ int rc;
|
||||
+
|
||||
if ( e.sa_p2mt == p2m_populate_on_demand )
|
||||
return GUEST_TABLE_POD_PAGE;
|
||||
|
||||
if ( read_only )
|
||||
return GUEST_TABLE_MAP_FAILED;
|
||||
|
||||
- next = ept_set_middle_entry(p2m, ept_entry);
|
||||
+ next = ept_set_middle_entry(p2m, &e);
|
||||
if ( !next )
|
||||
return GUEST_TABLE_MAP_FAILED;
|
||||
- /* e is now stale and hence may not be used anymore below. */
|
||||
+
|
||||
+ rc = atomic_write_ept_entry(p2m, ept_entry, e, next_level);
|
||||
+ ASSERT(rc == 0);
|
||||
}
|
||||
/* The only time sp would be set here is if we had hit a superpage */
|
||||
else if ( is_epte_superpage(&e) )
|
||||
39
xsa333.patch
Normal file
39
xsa333.patch
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
From: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Subject: x86/pv: Handle the Intel-specific MSR_MISC_ENABLE correctly
|
||||
|
||||
This MSR doesn't exist on AMD hardware, and switching away from the safe
|
||||
functions in the common MSR path was an erroneous change.
|
||||
|
||||
Partially revert the change.
|
||||
|
||||
This is XSA-333.
|
||||
|
||||
Fixes: 4fdc932b3cc ("x86/Intel: drop another 32-bit leftover")
|
||||
Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||||
Reviewed-by: Wei Liu <wl@xen.org>
|
||||
|
||||
diff --git a/xen/arch/x86/pv/emul-priv-op.c b/xen/arch/x86/pv/emul-priv-op.c
|
||||
index efeb2a727e..6332c74b80 100644
|
||||
--- a/xen/arch/x86/pv/emul-priv-op.c
|
||||
+++ b/xen/arch/x86/pv/emul-priv-op.c
|
||||
@@ -924,7 +924,8 @@ static int read_msr(unsigned int reg, uint64_t *val,
|
||||
return X86EMUL_OKAY;
|
||||
|
||||
case MSR_IA32_MISC_ENABLE:
|
||||
- rdmsrl(reg, *val);
|
||||
+ if ( rdmsr_safe(reg, *val) )
|
||||
+ break;
|
||||
*val = guest_misc_enable(*val);
|
||||
return X86EMUL_OKAY;
|
||||
|
||||
@@ -1059,7 +1060,8 @@ static int write_msr(unsigned int reg, uint64_t val,
|
||||
break;
|
||||
|
||||
case MSR_IA32_MISC_ENABLE:
|
||||
- rdmsrl(reg, temp);
|
||||
+ if ( rdmsr_safe(reg, temp) )
|
||||
+ break;
|
||||
if ( val != guest_misc_enable(temp) )
|
||||
goto invalid;
|
||||
return X86EMUL_OKAY;
|
||||
57
xsa334-4.12.patch
Normal file
57
xsa334-4.12.patch
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
From: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Subject: xen/memory: Don't skip the RCU unlock path in acquire_resource()
|
||||
|
||||
In the case that an HVM Stubdomain makes an XENMEM_acquire_resource hypercall,
|
||||
the FIXME path will bypass rcu_unlock_domain() on the way out of the function.
|
||||
|
||||
Move the check to the start of the function. This does change the behaviour
|
||||
of the get-size path for HVM Stubdomains, but that functionality is currently
|
||||
broken and unused anyway, as well as being quite useless to entities which
|
||||
can't actually map the resource anyway.
|
||||
|
||||
This is XSA-334.
|
||||
|
||||
Fixes: 83fa6552ce ("common: add a new mappable resource type: XENMEM_resource_grant_table")
|
||||
Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||||
|
||||
Backport note: The deletion of the XENMEM_rsrc_acq_caller_owned clause is
|
||||
correct and intentional. This was an output-only flag who's use never
|
||||
survived into the Xen 4.12 release, and was subsequently deleted in Xen 4.13.
|
||||
|
||||
diff --git a/xen/common/memory.c b/xen/common/memory.c
|
||||
index dbc06fb0bf..ff88ebb314 100644
|
||||
--- a/xen/common/memory.c
|
||||
+++ b/xen/common/memory.c
|
||||
@@ -1059,6 +1059,14 @@ static int acquire_resource(
|
||||
xen_pfn_t mfn_list[32];
|
||||
int rc;
|
||||
|
||||
+ /*
|
||||
+ * FIXME: Until foreign pages inserted into the P2M are properly
|
||||
+ * reference counted, it is unsafe to allow mapping of
|
||||
+ * resource pages unless the caller is the hardware domain.
|
||||
+ */
|
||||
+ if ( paging_mode_translate(currd) && !is_hardware_domain(currd) )
|
||||
+ return -EACCES;
|
||||
+
|
||||
if ( copy_from_guest(&xmar, arg, 1) )
|
||||
return -EFAULT;
|
||||
|
||||
@@ -1115,16 +1123,6 @@ static int acquire_resource(
|
||||
xen_pfn_t gfn_list[ARRAY_SIZE(mfn_list)];
|
||||
unsigned int i;
|
||||
|
||||
- /*
|
||||
- * FIXME: Until foreign pages inserted into the P2M are properly
|
||||
- * reference counted, it is unsafe to allow mapping of
|
||||
- * non-caller-owned resource pages unless the caller is
|
||||
- * the hardware domain.
|
||||
- */
|
||||
- if ( !(xmar.flags & XENMEM_rsrc_acq_caller_owned) &&
|
||||
- !is_hardware_domain(currd) )
|
||||
- return -EACCES;
|
||||
-
|
||||
if ( copy_from_guest(gfn_list, xmar.frame_list, xmar.nr_frames) )
|
||||
rc = -EFAULT;
|
||||
|
||||
84
xsa335-qemu.patch
Normal file
84
xsa335-qemu.patch
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
From c5bd2924c6d6a5bcbffb8b5e7798a88970131c07 Mon Sep 17 00:00:00 2001
|
||||
From: Gerd Hoffmann <kraxel@redhat.com>
|
||||
Date: Mon, 17 Aug 2020 08:34:22 +0200
|
||||
Subject: [PATCH] usb: fix setup_len init (CVE-2020-14364)
|
||||
|
||||
Store calculated setup_len in a local variable, verify it, and only
|
||||
write it to the struct (USBDevice->setup_len) in case it passed the
|
||||
sanity checks.
|
||||
|
||||
This prevents other code (do_token_{in,out} functions specifically)
|
||||
from working with invalid USBDevice->setup_len values and overrunning
|
||||
the USBDevice->setup_buf[] buffer.
|
||||
|
||||
Fixes: CVE-2020-14364
|
||||
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
|
||||
---
|
||||
hw/usb/core.c | 16 ++++++++++------
|
||||
1 file changed, 10 insertions(+), 6 deletions(-)
|
||||
|
||||
diff --git a/hw/usb/core.c b/hw/usb/core.c
|
||||
index 5abd128b6bc5..5234dcc73fea 100644
|
||||
--- a/hw/usb/core.c
|
||||
+++ b/hw/usb/core.c
|
||||
@@ -129,6 +129,7 @@ void usb_wakeup(USBEndpoint *ep, unsigned int stream)
|
||||
static void do_token_setup(USBDevice *s, USBPacket *p)
|
||||
{
|
||||
int request, value, index;
|
||||
+ unsigned int setup_len;
|
||||
|
||||
if (p->iov.size != 8) {
|
||||
p->status = USB_RET_STALL;
|
||||
@@ -138,14 +139,15 @@ static void do_token_setup(USBDevice *s, USBPacket *p)
|
||||
usb_packet_copy(p, s->setup_buf, p->iov.size);
|
||||
s->setup_index = 0;
|
||||
p->actual_length = 0;
|
||||
- s->setup_len = (s->setup_buf[7] << 8) | s->setup_buf[6];
|
||||
- if (s->setup_len > sizeof(s->data_buf)) {
|
||||
+ setup_len = (s->setup_buf[7] << 8) | s->setup_buf[6];
|
||||
+ if (setup_len > sizeof(s->data_buf)) {
|
||||
fprintf(stderr,
|
||||
"usb_generic_handle_packet: ctrl buffer too small (%d > %zu)\n",
|
||||
- s->setup_len, sizeof(s->data_buf));
|
||||
+ setup_len, sizeof(s->data_buf));
|
||||
p->status = USB_RET_STALL;
|
||||
return;
|
||||
}
|
||||
+ s->setup_len = setup_len;
|
||||
|
||||
request = (s->setup_buf[0] << 8) | s->setup_buf[1];
|
||||
value = (s->setup_buf[3] << 8) | s->setup_buf[2];
|
||||
@@ -259,26 +261,28 @@ static void do_token_out(USBDevice *s, USBPacket *p)
|
||||
static void do_parameter(USBDevice *s, USBPacket *p)
|
||||
{
|
||||
int i, request, value, index;
|
||||
+ unsigned int setup_len;
|
||||
|
||||
for (i = 0; i < 8; i++) {
|
||||
s->setup_buf[i] = p->parameter >> (i*8);
|
||||
}
|
||||
|
||||
s->setup_state = SETUP_STATE_PARAM;
|
||||
- s->setup_len = (s->setup_buf[7] << 8) | s->setup_buf[6];
|
||||
s->setup_index = 0;
|
||||
|
||||
request = (s->setup_buf[0] << 8) | s->setup_buf[1];
|
||||
value = (s->setup_buf[3] << 8) | s->setup_buf[2];
|
||||
index = (s->setup_buf[5] << 8) | s->setup_buf[4];
|
||||
|
||||
- if (s->setup_len > sizeof(s->data_buf)) {
|
||||
+ setup_len = (s->setup_buf[7] << 8) | s->setup_buf[6];
|
||||
+ if (setup_len > sizeof(s->data_buf)) {
|
||||
fprintf(stderr,
|
||||
"usb_generic_handle_packet: ctrl buffer too small (%d > %zu)\n",
|
||||
- s->setup_len, sizeof(s->data_buf));
|
||||
+ setup_len, sizeof(s->data_buf));
|
||||
p->status = USB_RET_STALL;
|
||||
return;
|
||||
}
|
||||
+ s->setup_len = setup_len;
|
||||
|
||||
if (p->pid == USB_TOKEN_OUT) {
|
||||
usb_packet_copy(p, s->data_buf, s->setup_len);
|
||||
--
|
||||
2.18.4
|
||||
45
xsa335-trad.patch
Normal file
45
xsa335-trad.patch
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
From a62cdd675bc6a8053f6797b6add29b2853b081e3 Mon Sep 17 00:00:00 2001
|
||||
From: Ian Jackson <ian.jackson@eu.citrix.com>
|
||||
Date: Wed, 19 Aug 2020 18:31:45 +0100
|
||||
Subject: [PATCH] SUPPORT.md: Desupport qemu trad except stub dm
|
||||
|
||||
While investigating XSA-335 we discovered that many upstream security
|
||||
fixes were missing. It is not practical to backport them. There is
|
||||
no good reason to be running this very ancient version of qemu, except
|
||||
that it is the only way to run a stub dm which is currently supported
|
||||
by upstream.
|
||||
|
||||
Signed-off-by: Ian Jackson <ian.jackson@eu.citrix.com>
|
||||
---
|
||||
SUPPORT.md | 15 +++++++++++++++
|
||||
1 file changed, 15 insertions(+)
|
||||
|
||||
diff --git a/SUPPORT.md b/SUPPORT.md
|
||||
index 1479055c45..b0939052e2 100644
|
||||
--- a/SUPPORT.md
|
||||
+++ b/SUPPORT.md
|
||||
@@ -758,6 +758,21 @@ See the section **Blkback** for image formats supported by QEMU.
|
||||
|
||||
Status: Supported, not security supported
|
||||
|
||||
+### qemu-xen-traditional ###
|
||||
+
|
||||
+The Xen Project provides an old version of qemu with modifications
|
||||
+which enable use as a device model stub domain. The old version is
|
||||
+normally selected by default only in a stub dm configuration, but it
|
||||
+can be requested explicitly in other configurations, for example in
|
||||
+`xl` with `device_model_version="QEMU_XEN_TRADITIONAL"`.
|
||||
+
|
||||
+ Status, Device Model Stub Domains: Supported, with caveats
|
||||
+ Status, as host process device model: No security support, not recommended
|
||||
+
|
||||
+qemu-xen-traditional is security supported only for those available
|
||||
+devices which are supported for mainstream QEMU (see above), with
|
||||
+trusted driver domains (see Device Model Stub Domains).
|
||||
+
|
||||
## Virtual Firmware
|
||||
|
||||
### x86/HVM iPXE
|
||||
--
|
||||
2.20.1
|
||||
|
||||
283
xsa336.patch
Normal file
283
xsa336.patch
Normal file
|
|
@ -0,0 +1,283 @@
|
|||
From: Roger Pau Monné <roger.pau@citrix.com>
|
||||
Subject: x86/vpt: fix race when migrating timers between vCPUs
|
||||
|
||||
The current vPT code will migrate the emulated timers between vCPUs
|
||||
(change the pt->vcpu field) while just holding the destination lock,
|
||||
either from create_periodic_time or pt_adjust_global_vcpu_target if
|
||||
the global target is adjusted. Changing the periodic_timer vCPU field
|
||||
in this way creates a race where a third party could grab the lock in
|
||||
the unlocked region of pt_adjust_global_vcpu_target (or before
|
||||
create_periodic_time performs the vcpu change) and then release the
|
||||
lock from a different vCPU, creating a locking imbalance.
|
||||
|
||||
Introduce a per-domain rwlock in order to protect periodic_time
|
||||
migration between vCPU lists. Taking the lock in read mode prevents
|
||||
any timer from being migrated to a different vCPU, while taking it in
|
||||
write mode allows performing migration of timers across vCPUs. The
|
||||
per-vcpu locks are still used to protect all the other fields from the
|
||||
periodic_timer struct.
|
||||
|
||||
Note that such migration shouldn't happen frequently, and hence
|
||||
there's no performance drop as a result of such locking.
|
||||
|
||||
This is XSA-336.
|
||||
|
||||
Reported-by: Igor Druzhinin <igor.druzhinin@citrix.com>
|
||||
Tested-by: Igor Druzhinin <igor.druzhinin@citrix.com>
|
||||
Signed-off-by: Roger Pau Monné <roger.pau@citrix.com>
|
||||
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||||
---
|
||||
Changes since v2:
|
||||
- Re-order pt_adjust_vcpu to remove one if.
|
||||
- Fix pt_lock to not call pt_vcpu_lock, as we might end up using a
|
||||
stale value of pt->vcpu when taking the per-vcpu lock.
|
||||
|
||||
Changes since v1:
|
||||
- Use a per-domain rwlock to protect timer vCPU migration.
|
||||
|
||||
--- a/xen/arch/x86/hvm/hvm.c
|
||||
+++ b/xen/arch/x86/hvm/hvm.c
|
||||
@@ -658,6 +658,8 @@ int hvm_domain_initialise(struct domain
|
||||
/* need link to containing domain */
|
||||
d->arch.hvm.pl_time->domain = d;
|
||||
|
||||
+ rwlock_init(&d->arch.hvm.pl_time->pt_migrate);
|
||||
+
|
||||
/* Set the default IO Bitmap. */
|
||||
if ( is_hardware_domain(d) )
|
||||
{
|
||||
--- a/xen/arch/x86/hvm/vpt.c
|
||||
+++ b/xen/arch/x86/hvm/vpt.c
|
||||
@@ -153,23 +153,32 @@ static int pt_irq_masked(struct periodic
|
||||
return 1;
|
||||
}
|
||||
|
||||
-static void pt_lock(struct periodic_time *pt)
|
||||
+static void pt_vcpu_lock(struct vcpu *v)
|
||||
{
|
||||
- struct vcpu *v;
|
||||
+ read_lock(&v->domain->arch.hvm.pl_time->pt_migrate);
|
||||
+ spin_lock(&v->arch.hvm.tm_lock);
|
||||
+}
|
||||
|
||||
- for ( ; ; )
|
||||
- {
|
||||
- v = pt->vcpu;
|
||||
- spin_lock(&v->arch.hvm.tm_lock);
|
||||
- if ( likely(pt->vcpu == v) )
|
||||
- break;
|
||||
- spin_unlock(&v->arch.hvm.tm_lock);
|
||||
- }
|
||||
+static void pt_vcpu_unlock(struct vcpu *v)
|
||||
+{
|
||||
+ spin_unlock(&v->arch.hvm.tm_lock);
|
||||
+ read_unlock(&v->domain->arch.hvm.pl_time->pt_migrate);
|
||||
+}
|
||||
+
|
||||
+static void pt_lock(struct periodic_time *pt)
|
||||
+{
|
||||
+ /*
|
||||
+ * We cannot use pt_vcpu_lock here, because we need to acquire the
|
||||
+ * per-domain lock first and then (re-)fetch the value of pt->vcpu, or
|
||||
+ * else we might be using a stale value of pt->vcpu.
|
||||
+ */
|
||||
+ read_lock(&pt->vcpu->domain->arch.hvm.pl_time->pt_migrate);
|
||||
+ spin_lock(&pt->vcpu->arch.hvm.tm_lock);
|
||||
}
|
||||
|
||||
static void pt_unlock(struct periodic_time *pt)
|
||||
{
|
||||
- spin_unlock(&pt->vcpu->arch.hvm.tm_lock);
|
||||
+ pt_vcpu_unlock(pt->vcpu);
|
||||
}
|
||||
|
||||
static void pt_process_missed_ticks(struct periodic_time *pt)
|
||||
@@ -219,7 +228,7 @@ void pt_save_timer(struct vcpu *v)
|
||||
if ( v->pause_flags & VPF_blocked )
|
||||
return;
|
||||
|
||||
- spin_lock(&v->arch.hvm.tm_lock);
|
||||
+ pt_vcpu_lock(v);
|
||||
|
||||
list_for_each_entry ( pt, head, list )
|
||||
if ( !pt->do_not_freeze )
|
||||
@@ -227,7 +236,7 @@ void pt_save_timer(struct vcpu *v)
|
||||
|
||||
pt_freeze_time(v);
|
||||
|
||||
- spin_unlock(&v->arch.hvm.tm_lock);
|
||||
+ pt_vcpu_unlock(v);
|
||||
}
|
||||
|
||||
void pt_restore_timer(struct vcpu *v)
|
||||
@@ -235,7 +244,7 @@ void pt_restore_timer(struct vcpu *v)
|
||||
struct list_head *head = &v->arch.hvm.tm_list;
|
||||
struct periodic_time *pt;
|
||||
|
||||
- spin_lock(&v->arch.hvm.tm_lock);
|
||||
+ pt_vcpu_lock(v);
|
||||
|
||||
list_for_each_entry ( pt, head, list )
|
||||
{
|
||||
@@ -248,7 +257,7 @@ void pt_restore_timer(struct vcpu *v)
|
||||
|
||||
pt_thaw_time(v);
|
||||
|
||||
- spin_unlock(&v->arch.hvm.tm_lock);
|
||||
+ pt_vcpu_unlock(v);
|
||||
}
|
||||
|
||||
static void pt_timer_fn(void *data)
|
||||
@@ -309,7 +318,7 @@ int pt_update_irq(struct vcpu *v)
|
||||
int irq, pt_vector = -1;
|
||||
bool level;
|
||||
|
||||
- spin_lock(&v->arch.hvm.tm_lock);
|
||||
+ pt_vcpu_lock(v);
|
||||
|
||||
earliest_pt = NULL;
|
||||
max_lag = -1ULL;
|
||||
@@ -339,7 +348,7 @@ int pt_update_irq(struct vcpu *v)
|
||||
|
||||
if ( earliest_pt == NULL )
|
||||
{
|
||||
- spin_unlock(&v->arch.hvm.tm_lock);
|
||||
+ pt_vcpu_unlock(v);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -347,7 +356,7 @@ int pt_update_irq(struct vcpu *v)
|
||||
irq = earliest_pt->irq;
|
||||
level = earliest_pt->level;
|
||||
|
||||
- spin_unlock(&v->arch.hvm.tm_lock);
|
||||
+ pt_vcpu_unlock(v);
|
||||
|
||||
switch ( earliest_pt->source )
|
||||
{
|
||||
@@ -394,7 +403,7 @@ int pt_update_irq(struct vcpu *v)
|
||||
time_cb *cb = NULL;
|
||||
void *cb_priv;
|
||||
|
||||
- spin_lock(&v->arch.hvm.tm_lock);
|
||||
+ pt_vcpu_lock(v);
|
||||
/* Make sure the timer is still on the list. */
|
||||
list_for_each_entry ( pt, &v->arch.hvm.tm_list, list )
|
||||
if ( pt == earliest_pt )
|
||||
@@ -404,7 +413,7 @@ int pt_update_irq(struct vcpu *v)
|
||||
cb_priv = pt->priv;
|
||||
break;
|
||||
}
|
||||
- spin_unlock(&v->arch.hvm.tm_lock);
|
||||
+ pt_vcpu_unlock(v);
|
||||
|
||||
if ( cb != NULL )
|
||||
cb(v, cb_priv);
|
||||
@@ -441,12 +450,12 @@ void pt_intr_post(struct vcpu *v, struct
|
||||
if ( intack.source == hvm_intsrc_vector )
|
||||
return;
|
||||
|
||||
- spin_lock(&v->arch.hvm.tm_lock);
|
||||
+ pt_vcpu_lock(v);
|
||||
|
||||
pt = is_pt_irq(v, intack);
|
||||
if ( pt == NULL )
|
||||
{
|
||||
- spin_unlock(&v->arch.hvm.tm_lock);
|
||||
+ pt_vcpu_unlock(v);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -455,7 +464,7 @@ void pt_intr_post(struct vcpu *v, struct
|
||||
cb = pt->cb;
|
||||
cb_priv = pt->priv;
|
||||
|
||||
- spin_unlock(&v->arch.hvm.tm_lock);
|
||||
+ pt_vcpu_unlock(v);
|
||||
|
||||
if ( cb != NULL )
|
||||
cb(v, cb_priv);
|
||||
@@ -466,12 +475,12 @@ void pt_migrate(struct vcpu *v)
|
||||
struct list_head *head = &v->arch.hvm.tm_list;
|
||||
struct periodic_time *pt;
|
||||
|
||||
- spin_lock(&v->arch.hvm.tm_lock);
|
||||
+ pt_vcpu_lock(v);
|
||||
|
||||
list_for_each_entry ( pt, head, list )
|
||||
migrate_timer(&pt->timer, v->processor);
|
||||
|
||||
- spin_unlock(&v->arch.hvm.tm_lock);
|
||||
+ pt_vcpu_unlock(v);
|
||||
}
|
||||
|
||||
void create_periodic_time(
|
||||
@@ -490,7 +499,7 @@ void create_periodic_time(
|
||||
|
||||
destroy_periodic_time(pt);
|
||||
|
||||
- spin_lock(&v->arch.hvm.tm_lock);
|
||||
+ write_lock(&v->domain->arch.hvm.pl_time->pt_migrate);
|
||||
|
||||
pt->pending_intr_nr = 0;
|
||||
pt->do_not_freeze = 0;
|
||||
@@ -540,7 +549,7 @@ void create_periodic_time(
|
||||
init_timer(&pt->timer, pt_timer_fn, pt, v->processor);
|
||||
set_timer(&pt->timer, pt->scheduled);
|
||||
|
||||
- spin_unlock(&v->arch.hvm.tm_lock);
|
||||
+ write_unlock(&v->domain->arch.hvm.pl_time->pt_migrate);
|
||||
}
|
||||
|
||||
void destroy_periodic_time(struct periodic_time *pt)
|
||||
@@ -565,30 +574,20 @@ void destroy_periodic_time(struct period
|
||||
|
||||
static void pt_adjust_vcpu(struct periodic_time *pt, struct vcpu *v)
|
||||
{
|
||||
- int on_list;
|
||||
-
|
||||
ASSERT(pt->source == PTSRC_isa || pt->source == PTSRC_ioapic);
|
||||
|
||||
if ( pt->vcpu == NULL )
|
||||
return;
|
||||
|
||||
- pt_lock(pt);
|
||||
- on_list = pt->on_list;
|
||||
- if ( pt->on_list )
|
||||
- list_del(&pt->list);
|
||||
- pt->on_list = 0;
|
||||
- pt_unlock(pt);
|
||||
-
|
||||
- spin_lock(&v->arch.hvm.tm_lock);
|
||||
+ write_lock(&pt->vcpu->domain->arch.hvm.pl_time->pt_migrate);
|
||||
pt->vcpu = v;
|
||||
- if ( on_list )
|
||||
+ if ( pt->on_list )
|
||||
{
|
||||
- pt->on_list = 1;
|
||||
+ list_del(&pt->list);
|
||||
list_add(&pt->list, &v->arch.hvm.tm_list);
|
||||
-
|
||||
migrate_timer(&pt->timer, v->processor);
|
||||
}
|
||||
- spin_unlock(&v->arch.hvm.tm_lock);
|
||||
+ write_unlock(&pt->vcpu->domain->arch.hvm.pl_time->pt_migrate);
|
||||
}
|
||||
|
||||
void pt_adjust_global_vcpu_target(struct vcpu *v)
|
||||
--- a/xen/include/asm-x86/hvm/vpt.h
|
||||
+++ b/xen/include/asm-x86/hvm/vpt.h
|
||||
@@ -128,6 +128,13 @@ struct pl_time { /* platform time */
|
||||
struct RTCState vrtc;
|
||||
struct HPETState vhpet;
|
||||
struct PMTState vpmt;
|
||||
+ /*
|
||||
+ * rwlock to prevent periodic_time vCPU migration. Take the lock in read
|
||||
+ * mode in order to prevent the vcpu field of periodic_time from changing.
|
||||
+ * Lock must be taken in write mode when changes to the vcpu field are
|
||||
+ * performed, as it allows exclusive access to all the timers of a domain.
|
||||
+ */
|
||||
+ rwlock_t pt_migrate;
|
||||
/* guest_time = Xen sys time + stime_offset */
|
||||
int64_t stime_offset;
|
||||
/* Ensures monotonicity in appropriate timer modes. */
|
||||
92
xsa337-4.12-1.patch
Normal file
92
xsa337-4.12-1.patch
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
From: Roger Pau Monné <roger.pau@citrix.com>
|
||||
Subject: x86/msi: get rid of read_msi_msg
|
||||
|
||||
It's safer and faster to just use the cached last written
|
||||
(untranslated) MSI message stored in msi_desc for the single user that
|
||||
calls read_msi_msg.
|
||||
|
||||
This also prevents relying on the data read from the device MSI
|
||||
registers in order to figure out the index into the IOMMU interrupt
|
||||
remapping table, which is not safe.
|
||||
|
||||
This is part of XSA-337.
|
||||
|
||||
Reported-by: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Requested-by: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Signed-off-by: Roger Pau Monné <roger.pau@citrix.com>
|
||||
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||||
|
||||
--- a/xen/arch/x86/msi.c
|
||||
+++ b/xen/arch/x86/msi.c
|
||||
@@ -192,59 +192,6 @@ void msi_compose_msg(unsigned vector, co
|
||||
MSI_DATA_VECTOR(vector);
|
||||
}
|
||||
|
||||
-static bool read_msi_msg(struct msi_desc *entry, struct msi_msg *msg)
|
||||
-{
|
||||
- switch ( entry->msi_attrib.type )
|
||||
- {
|
||||
- case PCI_CAP_ID_MSI:
|
||||
- {
|
||||
- struct pci_dev *dev = entry->dev;
|
||||
- int pos = entry->msi_attrib.pos;
|
||||
- u16 data, seg = dev->seg;
|
||||
- u8 bus = dev->bus;
|
||||
- u8 slot = PCI_SLOT(dev->devfn);
|
||||
- u8 func = PCI_FUNC(dev->devfn);
|
||||
-
|
||||
- msg->address_lo = pci_conf_read32(seg, bus, slot, func,
|
||||
- msi_lower_address_reg(pos));
|
||||
- if ( entry->msi_attrib.is_64 )
|
||||
- {
|
||||
- msg->address_hi = pci_conf_read32(seg, bus, slot, func,
|
||||
- msi_upper_address_reg(pos));
|
||||
- data = pci_conf_read16(seg, bus, slot, func,
|
||||
- msi_data_reg(pos, 1));
|
||||
- }
|
||||
- else
|
||||
- {
|
||||
- msg->address_hi = 0;
|
||||
- data = pci_conf_read16(seg, bus, slot, func,
|
||||
- msi_data_reg(pos, 0));
|
||||
- }
|
||||
- msg->data = data;
|
||||
- break;
|
||||
- }
|
||||
- case PCI_CAP_ID_MSIX:
|
||||
- {
|
||||
- void __iomem *base = entry->mask_base;
|
||||
-
|
||||
- if ( unlikely(!msix_memory_decoded(entry->dev,
|
||||
- entry->msi_attrib.pos)) )
|
||||
- return false;
|
||||
- msg->address_lo = readl(base + PCI_MSIX_ENTRY_LOWER_ADDR_OFFSET);
|
||||
- msg->address_hi = readl(base + PCI_MSIX_ENTRY_UPPER_ADDR_OFFSET);
|
||||
- msg->data = readl(base + PCI_MSIX_ENTRY_DATA_OFFSET);
|
||||
- break;
|
||||
- }
|
||||
- default:
|
||||
- BUG();
|
||||
- }
|
||||
-
|
||||
- if ( iommu_intremap )
|
||||
- iommu_read_msi_from_ire(entry, msg);
|
||||
-
|
||||
- return true;
|
||||
-}
|
||||
-
|
||||
static int write_msi_msg(struct msi_desc *entry, struct msi_msg *msg)
|
||||
{
|
||||
entry->msg = *msg;
|
||||
@@ -322,10 +269,7 @@ void set_msi_affinity(struct irq_desc *d
|
||||
|
||||
ASSERT(spin_is_locked(&desc->lock));
|
||||
|
||||
- memset(&msg, 0, sizeof(msg));
|
||||
- if ( !read_msi_msg(msi_desc, &msg) )
|
||||
- return;
|
||||
-
|
||||
+ msg = msi_desc->msg;
|
||||
msg.data &= ~MSI_DATA_VECTOR_MASK;
|
||||
msg.data |= MSI_DATA_VECTOR(desc->arch.vector);
|
||||
msg.address_lo &= ~MSI_ADDR_DEST_ID_MASK;
|
||||
182
xsa337-4.12-2.patch
Normal file
182
xsa337-4.12-2.patch
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
From: Jan Beulich <jbeulich@suse.com>
|
||||
Subject: x86/MSI-X: restrict reading of table/PBA bases from BARs
|
||||
|
||||
When assigned to less trusted or un-trusted guests, devices may change
|
||||
state behind our backs (they may e.g. get reset by means we may not know
|
||||
about). Therefore we should avoid reading BARs from hardware once a
|
||||
device is no longer owned by Dom0. Furthermore when we can't read a BAR,
|
||||
or when we read zero, we shouldn't instead use the caller provided
|
||||
address unless that caller can be trusted.
|
||||
|
||||
Re-arrange the logic in msix_capability_init() such that only Dom0 (and
|
||||
only if the device isn't DomU-owned yet) or calls through
|
||||
PHYSDEVOP_prepare_msix will actually result in the reading of the
|
||||
respective BAR register(s). Additionally do so only as long as in-use
|
||||
table entries are known (note that invocation of PHYSDEVOP_prepare_msix
|
||||
counts as a "pseudo" entry). In all other uses the value already
|
||||
recorded will get used instead.
|
||||
|
||||
Clear the recorded values in _pci_cleanup_msix() as well as on the one
|
||||
affected error path. (Adjust this error path to also avoid blindly
|
||||
disabling MSI-X when it was enabled on entry to the function.)
|
||||
|
||||
While moving around variable declarations (in many cases to reduce their
|
||||
scopes), also adjust some of their types.
|
||||
|
||||
This is part of XSA-337.
|
||||
|
||||
Signed-off-by: Jan Beulich <jbeulich@suse.com>
|
||||
Reviewed-by: Roger Pau Monné <roger.pau@citrix.com>
|
||||
|
||||
--- a/xen/arch/x86/msi.c
|
||||
+++ b/xen/arch/x86/msi.c
|
||||
@@ -790,16 +790,14 @@ static int msix_capability_init(struct p
|
||||
{
|
||||
struct arch_msix *msix = dev->msix;
|
||||
struct msi_desc *entry = NULL;
|
||||
- int vf;
|
||||
u16 control;
|
||||
u64 table_paddr;
|
||||
u32 table_offset;
|
||||
- u8 bir, pbus, pslot, pfunc;
|
||||
u16 seg = dev->seg;
|
||||
u8 bus = dev->bus;
|
||||
u8 slot = PCI_SLOT(dev->devfn);
|
||||
u8 func = PCI_FUNC(dev->devfn);
|
||||
- bool maskall = msix->host_maskall;
|
||||
+ bool maskall = msix->host_maskall, zap_on_error = false;
|
||||
|
||||
ASSERT(pcidevs_locked());
|
||||
|
||||
@@ -837,43 +835,45 @@ static int msix_capability_init(struct p
|
||||
/* Locate MSI-X table region */
|
||||
table_offset = pci_conf_read32(seg, bus, slot, func,
|
||||
msix_table_offset_reg(pos));
|
||||
- bir = (u8)(table_offset & PCI_MSIX_BIRMASK);
|
||||
- table_offset &= ~PCI_MSIX_BIRMASK;
|
||||
+ if ( !msix->used_entries &&
|
||||
+ (!msi ||
|
||||
+ (is_hardware_domain(current->domain) &&
|
||||
+ (dev->domain == current->domain || dev->domain == dom_io))) )
|
||||
+ {
|
||||
+ unsigned int bir = table_offset & PCI_MSIX_BIRMASK, pbus, pslot, pfunc;
|
||||
+ int vf;
|
||||
+ paddr_t pba_paddr;
|
||||
+ unsigned int pba_offset;
|
||||
|
||||
- if ( !dev->info.is_virtfn )
|
||||
- {
|
||||
- pbus = bus;
|
||||
- pslot = slot;
|
||||
- pfunc = func;
|
||||
- vf = -1;
|
||||
- }
|
||||
- else
|
||||
- {
|
||||
- pbus = dev->info.physfn.bus;
|
||||
- pslot = PCI_SLOT(dev->info.physfn.devfn);
|
||||
- pfunc = PCI_FUNC(dev->info.physfn.devfn);
|
||||
- vf = PCI_BDF2(dev->bus, dev->devfn);
|
||||
- }
|
||||
-
|
||||
- table_paddr = read_pci_mem_bar(seg, pbus, pslot, pfunc, bir, vf);
|
||||
- WARN_ON(msi && msi->table_base != table_paddr);
|
||||
- if ( !table_paddr )
|
||||
- {
|
||||
- if ( !msi || !msi->table_base )
|
||||
+ if ( !dev->info.is_virtfn )
|
||||
{
|
||||
- pci_conf_write16(seg, bus, slot, func, msix_control_reg(pos),
|
||||
- control & ~PCI_MSIX_FLAGS_ENABLE);
|
||||
- xfree(entry);
|
||||
- return -ENXIO;
|
||||
+ pbus = bus;
|
||||
+ pslot = slot;
|
||||
+ pfunc = func;
|
||||
+ vf = -1;
|
||||
+ }
|
||||
+ else
|
||||
+ {
|
||||
+ pbus = dev->info.physfn.bus;
|
||||
+ pslot = PCI_SLOT(dev->info.physfn.devfn);
|
||||
+ pfunc = PCI_FUNC(dev->info.physfn.devfn);
|
||||
+ vf = PCI_BDF2(dev->bus, dev->devfn);
|
||||
}
|
||||
- table_paddr = msi->table_base;
|
||||
- }
|
||||
- table_paddr += table_offset;
|
||||
|
||||
- if ( !msix->used_entries )
|
||||
- {
|
||||
- u64 pba_paddr;
|
||||
- u32 pba_offset;
|
||||
+ table_paddr = read_pci_mem_bar(seg, pbus, pslot, pfunc, bir, vf);
|
||||
+ WARN_ON(msi && msi->table_base != table_paddr);
|
||||
+ if ( !table_paddr )
|
||||
+ {
|
||||
+ if ( !msi || !msi->table_base )
|
||||
+ {
|
||||
+ pci_conf_write16(seg, bus, slot, func, msix_control_reg(pos),
|
||||
+ control & ~PCI_MSIX_FLAGS_ENABLE);
|
||||
+ xfree(entry);
|
||||
+ return -ENXIO;
|
||||
+ }
|
||||
+ table_paddr = msi->table_base;
|
||||
+ }
|
||||
+ table_paddr += table_offset & ~PCI_MSIX_BIRMASK;
|
||||
|
||||
msix->nr_entries = nr_entries;
|
||||
msix->table.first = PFN_DOWN(table_paddr);
|
||||
@@ -894,7 +894,19 @@ static int msix_capability_init(struct p
|
||||
BITS_TO_LONGS(nr_entries) - 1);
|
||||
WARN_ON(rangeset_overlaps_range(mmio_ro_ranges, msix->pba.first,
|
||||
msix->pba.last));
|
||||
+
|
||||
+ zap_on_error = true;
|
||||
}
|
||||
+ else if ( !msix->table.first )
|
||||
+ {
|
||||
+ pci_conf_write16(seg, bus, slot, func, msix_control_reg(pos),
|
||||
+ control);
|
||||
+ xfree(entry);
|
||||
+ return -ENODATA;
|
||||
+ }
|
||||
+ else
|
||||
+ table_paddr = (msix->table.first << PAGE_SHIFT) +
|
||||
+ (table_offset & ~PCI_MSIX_BIRMASK & ~PAGE_MASK);
|
||||
|
||||
if ( entry )
|
||||
{
|
||||
@@ -905,8 +917,16 @@ static int msix_capability_init(struct p
|
||||
|
||||
if ( idx < 0 )
|
||||
{
|
||||
+ if ( zap_on_error )
|
||||
+ {
|
||||
+ msix->table.first = 0;
|
||||
+ msix->pba.first = 0;
|
||||
+
|
||||
+ control &= ~PCI_MSIX_FLAGS_ENABLE;
|
||||
+ }
|
||||
+
|
||||
pci_conf_write16(seg, bus, slot, func, msix_control_reg(pos),
|
||||
- control & ~PCI_MSIX_FLAGS_ENABLE);
|
||||
+ control);
|
||||
xfree(entry);
|
||||
return idx;
|
||||
}
|
||||
@@ -1102,9 +1122,14 @@ static void _pci_cleanup_msix(struct arc
|
||||
if ( rangeset_remove_range(mmio_ro_ranges, msix->table.first,
|
||||
msix->table.last) )
|
||||
WARN();
|
||||
+ msix->table.first = 0;
|
||||
+ msix->table.last = 0;
|
||||
+
|
||||
if ( rangeset_remove_range(mmio_ro_ranges, msix->pba.first,
|
||||
msix->pba.last) )
|
||||
WARN();
|
||||
+ msix->pba.first = 0;
|
||||
+ msix->pba.last = 0;
|
||||
}
|
||||
}
|
||||
|
||||
42
xsa338.patch
Normal file
42
xsa338.patch
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
From: Jan Beulich <jbeulich@suse.com>
|
||||
Subject: evtchn: relax port_is_valid()
|
||||
|
||||
To avoid ports potentially becoming invalid behind the back of certain
|
||||
other functions (due to ->max_evtchn shrinking) because of
|
||||
- a guest invoking evtchn_reset() and from a 2nd vCPU opening new
|
||||
channels in parallel (see also XSA-343),
|
||||
- alloc_unbound_xen_event_channel() produced channels living above the
|
||||
2-level range (see also XSA-342),
|
||||
drop the max_evtchns check from port_is_valid(). For a port for which
|
||||
the function once returned "true", the returned value may not turn into
|
||||
"false" later on. The function's result may only depend on bounds which
|
||||
can only ever grow (which is the case for d->valid_evtchns).
|
||||
|
||||
This also eliminates a false sense of safety, utilized by some of the
|
||||
users (see again XSA-343): Without a suitable lock held, d->max_evtchns
|
||||
may change at any time, and hence deducing that certain other operations
|
||||
are safe when port_is_valid() returned true is not legitimate. The
|
||||
opportunities to abuse this may get widened by the change here
|
||||
(depending on guest and host configuration), but will be taken care of
|
||||
by the other XSA.
|
||||
|
||||
This is XSA-338.
|
||||
|
||||
Fixes: 48974e6ce52e ("evtchn: use a per-domain variable for the max number of event channels")
|
||||
Signed-off-by: Jan Beulich <jbeulich@suse.com>
|
||||
Reviewed-by: Stefano Stabellini <sstabellini@kernel.org>
|
||||
Reviewed-by: Julien Grall <jgrall@amazon.com>
|
||||
---
|
||||
v5: New, split from larger patch.
|
||||
|
||||
--- a/xen/include/xen/event.h
|
||||
+++ b/xen/include/xen/event.h
|
||||
@@ -107,8 +107,6 @@ void notify_via_xen_event_channel(struct
|
||||
|
||||
static inline bool_t port_is_valid(struct domain *d, unsigned int p)
|
||||
{
|
||||
- if ( p >= d->max_evtchns )
|
||||
- return 0;
|
||||
return p < read_atomic(&d->valid_evtchns);
|
||||
}
|
||||
|
||||
76
xsa339.patch
Normal file
76
xsa339.patch
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
From: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Subject: x86/pv: Avoid double exception injection
|
||||
|
||||
There is at least one path (SYSENTER with NT set, Xen converts to #GP) which
|
||||
ends up injecting the #GP fault twice, first in compat_sysenter(), and then a
|
||||
second time in compat_test_all_events(), due to the stale TBF_EXCEPTION left
|
||||
in TRAPBOUNCE_flags.
|
||||
|
||||
The guest kernel sees the second fault first, which is a kernel level #GP
|
||||
pointing at the head of the #GP handler, and is therefore a userspace
|
||||
trigger-able DoS.
|
||||
|
||||
This particular bug has bitten us several times before, so rearrange
|
||||
{compat_,}create_bounce_frame() to clobber TRAPBOUNCE on success, rather than
|
||||
leaving this task to one area of code which isn't used uniformly.
|
||||
|
||||
Other scenarios which might result in a double injection (e.g. two calls
|
||||
directly to compat_create_bounce_frame) will now crash the guest, which is far
|
||||
more obvious than letting the kernel run with corrupt state.
|
||||
|
||||
This is XSA-339
|
||||
|
||||
Fixes: fdac9515607b ("x86: clear EFLAGS.NT in SYSENTER entry path")
|
||||
Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||||
|
||||
diff --git a/xen/arch/x86/x86_64/compat/entry.S b/xen/arch/x86/x86_64/compat/entry.S
|
||||
index c3e62f8734..73619f57ca 100644
|
||||
--- a/xen/arch/x86/x86_64/compat/entry.S
|
||||
+++ b/xen/arch/x86/x86_64/compat/entry.S
|
||||
@@ -78,7 +78,6 @@ compat_process_softirqs:
|
||||
sti
|
||||
.Lcompat_bounce_exception:
|
||||
call compat_create_bounce_frame
|
||||
- movb $0, TRAPBOUNCE_flags(%rdx)
|
||||
jmp compat_test_all_events
|
||||
|
||||
ALIGN
|
||||
@@ -352,7 +351,13 @@ __UNLIKELY_END(compat_bounce_null_selector)
|
||||
movl %eax,UREGS_cs+8(%rsp)
|
||||
movl TRAPBOUNCE_eip(%rdx),%eax
|
||||
movl %eax,UREGS_rip+8(%rsp)
|
||||
+
|
||||
+ /* Trapbounce complete. Clobber state to avoid an erroneous second injection. */
|
||||
+ xor %eax, %eax
|
||||
+ mov %ax, TRAPBOUNCE_cs(%rdx)
|
||||
+ mov %al, TRAPBOUNCE_flags(%rdx)
|
||||
ret
|
||||
+
|
||||
.section .fixup,"ax"
|
||||
.Lfx13:
|
||||
xorl %edi,%edi
|
||||
diff --git a/xen/arch/x86/x86_64/entry.S b/xen/arch/x86/x86_64/entry.S
|
||||
index 1e880eb9f6..71a00e846b 100644
|
||||
--- a/xen/arch/x86/x86_64/entry.S
|
||||
+++ b/xen/arch/x86/x86_64/entry.S
|
||||
@@ -90,7 +90,6 @@ process_softirqs:
|
||||
sti
|
||||
.Lbounce_exception:
|
||||
call create_bounce_frame
|
||||
- movb $0, TRAPBOUNCE_flags(%rdx)
|
||||
jmp test_all_events
|
||||
|
||||
ALIGN
|
||||
@@ -512,6 +511,11 @@ UNLIKELY_START(z, create_bounce_frame_bad_bounce_ip)
|
||||
jmp asm_domain_crash_synchronous /* Does not return */
|
||||
__UNLIKELY_END(create_bounce_frame_bad_bounce_ip)
|
||||
movq %rax,UREGS_rip+8(%rsp)
|
||||
+
|
||||
+ /* Trapbounce complete. Clobber state to avoid an erroneous second injection. */
|
||||
+ xor %eax, %eax
|
||||
+ mov %rax, TRAPBOUNCE_eip(%rdx)
|
||||
+ mov %al, TRAPBOUNCE_flags(%rdx)
|
||||
ret
|
||||
|
||||
.pushsection .fixup, "ax", @progbits
|
||||
65
xsa340.patch
Normal file
65
xsa340.patch
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
From: Julien Grall <jgrall@amazon.com>
|
||||
Subject: xen/evtchn: Add missing barriers when accessing/allocating an event channel
|
||||
|
||||
While the allocation of a bucket is always performed with the per-domain
|
||||
lock, the bucket may be accessed without the lock taken (for instance, see
|
||||
evtchn_send()).
|
||||
|
||||
Instead such sites relies on port_is_valid() to return a non-zero value
|
||||
when the port has a struct evtchn associated to it. The function will
|
||||
mostly check whether the port is less than d->valid_evtchns as all the
|
||||
buckets/event channels should be allocated up to that point.
|
||||
|
||||
Unfortunately a compiler is free to re-order the assignment in
|
||||
evtchn_allocate_port() so it would be possible to have d->valid_evtchns
|
||||
updated before the new bucket has finish to allocate.
|
||||
|
||||
Additionally on Arm, even if this was compiled "correctly", the
|
||||
processor can still re-order the memory access.
|
||||
|
||||
Add a write memory barrier in the allocation side and a read memory
|
||||
barrier when the port is valid to prevent any re-ordering issue.
|
||||
|
||||
This is XSA-340.
|
||||
|
||||
Reported-by: Julien Grall <jgrall@amazon.com>
|
||||
Signed-off-by: Julien Grall <jgrall@amazon.com>
|
||||
Reviewed-by: Stefano Stabellini <sstabellini@kernel.org>
|
||||
|
||||
--- a/xen/common/event_channel.c
|
||||
+++ b/xen/common/event_channel.c
|
||||
@@ -178,6 +178,13 @@ int evtchn_allocate_port(struct domain *
|
||||
return -ENOMEM;
|
||||
bucket_from_port(d, port) = chn;
|
||||
|
||||
+ /*
|
||||
+ * d->valid_evtchns is used to check whether the bucket can be
|
||||
+ * accessed without the per-domain lock. Therefore,
|
||||
+ * d->valid_evtchns should be seen *after* the new bucket has
|
||||
+ * been setup.
|
||||
+ */
|
||||
+ smp_wmb();
|
||||
write_atomic(&d->valid_evtchns, d->valid_evtchns + EVTCHNS_PER_BUCKET);
|
||||
}
|
||||
|
||||
--- a/xen/include/xen/event.h
|
||||
+++ b/xen/include/xen/event.h
|
||||
@@ -107,7 +107,17 @@ void notify_via_xen_event_channel(struct
|
||||
|
||||
static inline bool_t port_is_valid(struct domain *d, unsigned int p)
|
||||
{
|
||||
- return p < read_atomic(&d->valid_evtchns);
|
||||
+ if ( p >= read_atomic(&d->valid_evtchns) )
|
||||
+ return false;
|
||||
+
|
||||
+ /*
|
||||
+ * The caller will usually access the event channel afterwards and
|
||||
+ * may be done without taking the per-domain lock. The barrier is
|
||||
+ * going in pair the smp_wmb() barrier in evtchn_allocate_port().
|
||||
+ */
|
||||
+ smp_rmb();
|
||||
+
|
||||
+ return true;
|
||||
}
|
||||
|
||||
static inline struct evtchn *evtchn_from_port(struct domain *d, unsigned int p)
|
||||
145
xsa342-4.13.patch
Normal file
145
xsa342-4.13.patch
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
From: Jan Beulich <jbeulich@suse.com>
|
||||
Subject: evtchn/x86: enforce correct upper limit for 32-bit guests
|
||||
|
||||
The recording of d->max_evtchns in evtchn_2l_init(), in particular with
|
||||
the limited set of callers of the function, is insufficient. Neither for
|
||||
PV nor for HVM guests the bitness is known at domain_create() time, yet
|
||||
the upper bound in 2-level mode depends upon guest bitness. Recording
|
||||
too high a limit "allows" x86 32-bit domains to open not properly usable
|
||||
event channels, management of which (inside Xen) would then result in
|
||||
corruption of the shared info and vCPU info structures.
|
||||
|
||||
Keep the upper limit dynamic for the 2-level case, introducing a helper
|
||||
function to retrieve the effective limit. This helper is now supposed to
|
||||
be private to the event channel code. The used in do_poll() and
|
||||
domain_dump_evtchn_info() weren't consistent with port uses elsewhere
|
||||
and hence get switched to port_is_valid().
|
||||
|
||||
Furthermore FIFO mode's setup_ports() gets adjusted to loop only up to
|
||||
the prior ABI limit, rather than all the way up to the new one.
|
||||
|
||||
Finally a word on the change to do_poll(): Accessing ->max_evtchns
|
||||
without holding a suitable lock was never safe, as it as well as
|
||||
->evtchn_port_ops may change behind do_poll()'s back. Using
|
||||
port_is_valid() instead widens some the window for potential abuse,
|
||||
until we've dealt with the race altogether (see XSA-343).
|
||||
|
||||
This is XSA-342.
|
||||
|
||||
Reported-by: Julien Grall <jgrall@amazon.com>
|
||||
Fixes: 48974e6ce52e ("evtchn: use a per-domain variable for the max number of event channels")
|
||||
Signed-off-by: Jan Beulich <jbeulich@suse.com>
|
||||
Reviewed-by: Stefano Stabellini <sstabellini@kernel.org>
|
||||
Reviewed-by: Julien Grall <jgrall@amazon.com>
|
||||
|
||||
--- a/xen/common/event_2l.c
|
||||
+++ b/xen/common/event_2l.c
|
||||
@@ -103,7 +103,6 @@ static const struct evtchn_port_ops evtc
|
||||
void evtchn_2l_init(struct domain *d)
|
||||
{
|
||||
d->evtchn_port_ops = &evtchn_port_ops_2l;
|
||||
- d->max_evtchns = BITS_PER_EVTCHN_WORD(d) * BITS_PER_EVTCHN_WORD(d);
|
||||
}
|
||||
|
||||
/*
|
||||
--- a/xen/common/event_channel.c
|
||||
+++ b/xen/common/event_channel.c
|
||||
@@ -151,7 +151,7 @@ static void free_evtchn_bucket(struct do
|
||||
|
||||
int evtchn_allocate_port(struct domain *d, evtchn_port_t port)
|
||||
{
|
||||
- if ( port > d->max_evtchn_port || port >= d->max_evtchns )
|
||||
+ if ( port > d->max_evtchn_port || port >= max_evtchns(d) )
|
||||
return -ENOSPC;
|
||||
|
||||
if ( port_is_valid(d, port) )
|
||||
@@ -1396,13 +1396,11 @@ static void domain_dump_evtchn_info(stru
|
||||
|
||||
spin_lock(&d->event_lock);
|
||||
|
||||
- for ( port = 1; port < d->max_evtchns; ++port )
|
||||
+ for ( port = 1; port_is_valid(d, port); ++port )
|
||||
{
|
||||
const struct evtchn *chn;
|
||||
char *ssid;
|
||||
|
||||
- if ( !port_is_valid(d, port) )
|
||||
- continue;
|
||||
chn = evtchn_from_port(d, port);
|
||||
if ( chn->state == ECS_FREE )
|
||||
continue;
|
||||
--- a/xen/common/event_fifo.c
|
||||
+++ b/xen/common/event_fifo.c
|
||||
@@ -478,7 +478,7 @@ static void cleanup_event_array(struct d
|
||||
d->evtchn_fifo = NULL;
|
||||
}
|
||||
|
||||
-static void setup_ports(struct domain *d)
|
||||
+static void setup_ports(struct domain *d, unsigned int prev_evtchns)
|
||||
{
|
||||
unsigned int port;
|
||||
|
||||
@@ -488,7 +488,7 @@ static void setup_ports(struct domain *d
|
||||
* - save its pending state.
|
||||
* - set default priority.
|
||||
*/
|
||||
- for ( port = 1; port < d->max_evtchns; port++ )
|
||||
+ for ( port = 1; port < prev_evtchns; port++ )
|
||||
{
|
||||
struct evtchn *evtchn;
|
||||
|
||||
@@ -546,6 +546,8 @@ int evtchn_fifo_init_control(struct evtc
|
||||
if ( !d->evtchn_fifo )
|
||||
{
|
||||
struct vcpu *vcb;
|
||||
+ /* Latch the value before it changes during setup_event_array(). */
|
||||
+ unsigned int prev_evtchns = max_evtchns(d);
|
||||
|
||||
for_each_vcpu ( d, vcb ) {
|
||||
rc = setup_control_block(vcb);
|
||||
@@ -562,8 +564,7 @@ int evtchn_fifo_init_control(struct evtc
|
||||
goto error;
|
||||
|
||||
d->evtchn_port_ops = &evtchn_port_ops_fifo;
|
||||
- d->max_evtchns = EVTCHN_FIFO_NR_CHANNELS;
|
||||
- setup_ports(d);
|
||||
+ setup_ports(d, prev_evtchns);
|
||||
}
|
||||
else
|
||||
rc = map_control_block(v, gfn, offset);
|
||||
--- a/xen/common/schedule.c
|
||||
+++ b/xen/common/schedule.c
|
||||
@@ -1434,7 +1434,7 @@ static long do_poll(struct sched_poll *s
|
||||
goto out;
|
||||
|
||||
rc = -EINVAL;
|
||||
- if ( port >= d->max_evtchns )
|
||||
+ if ( !port_is_valid(d, port) )
|
||||
goto out;
|
||||
|
||||
rc = 0;
|
||||
--- a/xen/include/xen/event.h
|
||||
+++ b/xen/include/xen/event.h
|
||||
@@ -105,6 +105,12 @@ void notify_via_xen_event_channel(struct
|
||||
#define bucket_from_port(d, p) \
|
||||
((group_from_port(d, p))[((p) % EVTCHNS_PER_GROUP) / EVTCHNS_PER_BUCKET])
|
||||
|
||||
+static inline unsigned int max_evtchns(const struct domain *d)
|
||||
+{
|
||||
+ return d->evtchn_fifo ? EVTCHN_FIFO_NR_CHANNELS
|
||||
+ : BITS_PER_EVTCHN_WORD(d) * BITS_PER_EVTCHN_WORD(d);
|
||||
+}
|
||||
+
|
||||
static inline bool_t port_is_valid(struct domain *d, unsigned int p)
|
||||
{
|
||||
if ( p >= read_atomic(&d->valid_evtchns) )
|
||||
--- a/xen/include/xen/sched.h
|
||||
+++ b/xen/include/xen/sched.h
|
||||
@@ -382,7 +382,6 @@ struct domain
|
||||
/* Event channel information. */
|
||||
struct evtchn *evtchn; /* first bucket only */
|
||||
struct evtchn **evtchn_group[NR_EVTCHN_GROUPS]; /* all other buckets */
|
||||
- unsigned int max_evtchns; /* number supported by ABI */
|
||||
unsigned int max_evtchn_port; /* max permitted port number */
|
||||
unsigned int valid_evtchns; /* number of allocated event channels */
|
||||
spinlock_t event_lock;
|
||||
190
xsa343-4.12-1.patch
Normal file
190
xsa343-4.12-1.patch
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
From: Jan Beulich <jbeulich@suse.com>
|
||||
Subject: evtchn: evtchn_reset() shouldn't succeed with still-open ports
|
||||
|
||||
While the function closes all ports, it does so without holding any
|
||||
lock, and hence racing requests may be issued causing new ports to get
|
||||
opened. This would have been problematic in particular if such a newly
|
||||
opened port had a port number above the new implementation limit (i.e.
|
||||
when switching from FIFO to 2-level) after the reset, as prior to
|
||||
"evtchn: relax port_is_valid()" this could have led to e.g.
|
||||
evtchn_close()'s "BUG_ON(!port_is_valid(d2, port2))" to trigger.
|
||||
|
||||
Introduce a counter of active ports and check that it's (still) no
|
||||
larger then the number of Xen internally used ones after obtaining the
|
||||
necessary lock in evtchn_reset().
|
||||
|
||||
As to the access model of the new {active,xen}_evtchns fields - while
|
||||
all writes get done using write_atomic(), reads ought to use
|
||||
read_atomic() only when outside of a suitably locked region.
|
||||
|
||||
Note that as of now evtchn_bind_virq() and evtchn_bind_ipi() don't have
|
||||
a need to call check_free_port().
|
||||
|
||||
This is part of XSA-343.
|
||||
|
||||
Signed-off-by: Jan Beulich <jbeulich@suse.com>
|
||||
Reviewed-by: Stefano Stabellini <sstabellini@kernel.org>
|
||||
Reviewed-by: Julien Grall <jgrall@amazon.com>
|
||||
|
||||
--- a/xen/common/event_channel.c
|
||||
+++ b/xen/common/event_channel.c
|
||||
@@ -188,6 +188,8 @@ int evtchn_allocate_port(struct domain *
|
||||
write_atomic(&d->valid_evtchns, d->valid_evtchns + EVTCHNS_PER_BUCKET);
|
||||
}
|
||||
|
||||
+ write_atomic(&d->active_evtchns, d->active_evtchns + 1);
|
||||
+
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -211,11 +213,26 @@ static int get_free_port(struct domain *
|
||||
return -ENOSPC;
|
||||
}
|
||||
|
||||
+/*
|
||||
+ * Check whether a port is still marked free, and if so update the domain
|
||||
+ * counter accordingly. To be used on function exit paths.
|
||||
+ */
|
||||
+static void check_free_port(struct domain *d, evtchn_port_t port)
|
||||
+{
|
||||
+ if ( port_is_valid(d, port) &&
|
||||
+ evtchn_from_port(d, port)->state == ECS_FREE )
|
||||
+ write_atomic(&d->active_evtchns, d->active_evtchns - 1);
|
||||
+}
|
||||
+
|
||||
void evtchn_free(struct domain *d, struct evtchn *chn)
|
||||
{
|
||||
/* Clear pending event to avoid unexpected behavior on re-bind. */
|
||||
evtchn_port_clear_pending(d, chn);
|
||||
|
||||
+ if ( consumer_is_xen(chn) )
|
||||
+ write_atomic(&d->xen_evtchns, d->xen_evtchns - 1);
|
||||
+ write_atomic(&d->active_evtchns, d->active_evtchns - 1);
|
||||
+
|
||||
/* Reset binding to vcpu0 when the channel is freed. */
|
||||
chn->state = ECS_FREE;
|
||||
chn->notify_vcpu_id = 0;
|
||||
@@ -258,6 +275,7 @@ static long evtchn_alloc_unbound(evtchn_
|
||||
alloc->port = port;
|
||||
|
||||
out:
|
||||
+ check_free_port(d, port);
|
||||
spin_unlock(&d->event_lock);
|
||||
rcu_unlock_domain(d);
|
||||
|
||||
@@ -351,6 +369,7 @@ static long evtchn_bind_interdomain(evtc
|
||||
bind->local_port = lport;
|
||||
|
||||
out:
|
||||
+ check_free_port(ld, lport);
|
||||
spin_unlock(&ld->event_lock);
|
||||
if ( ld != rd )
|
||||
spin_unlock(&rd->event_lock);
|
||||
@@ -488,7 +507,7 @@ static long evtchn_bind_pirq(evtchn_bind
|
||||
struct domain *d = current->domain;
|
||||
struct vcpu *v = d->vcpu[0];
|
||||
struct pirq *info;
|
||||
- int port, pirq = bind->pirq;
|
||||
+ int port = 0, pirq = bind->pirq;
|
||||
long rc;
|
||||
|
||||
if ( (pirq < 0) || (pirq >= d->nr_pirqs) )
|
||||
@@ -536,6 +555,7 @@ static long evtchn_bind_pirq(evtchn_bind
|
||||
arch_evtchn_bind_pirq(d, pirq);
|
||||
|
||||
out:
|
||||
+ check_free_port(d, port);
|
||||
spin_unlock(&d->event_lock);
|
||||
|
||||
return rc;
|
||||
@@ -1011,10 +1031,10 @@ int evtchn_unmask(unsigned int port)
|
||||
return 0;
|
||||
}
|
||||
|
||||
-
|
||||
int evtchn_reset(struct domain *d)
|
||||
{
|
||||
unsigned int i;
|
||||
+ int rc = 0;
|
||||
|
||||
if ( d != current->domain && !d->controller_pause_count )
|
||||
return -EINVAL;
|
||||
@@ -1024,7 +1044,9 @@ int evtchn_reset(struct domain *d)
|
||||
|
||||
spin_lock(&d->event_lock);
|
||||
|
||||
- if ( d->evtchn_fifo )
|
||||
+ if ( d->active_evtchns > d->xen_evtchns )
|
||||
+ rc = -EAGAIN;
|
||||
+ else if ( d->evtchn_fifo )
|
||||
{
|
||||
/* Switching back to 2-level ABI. */
|
||||
evtchn_fifo_destroy(d);
|
||||
@@ -1033,7 +1055,7 @@ int evtchn_reset(struct domain *d)
|
||||
|
||||
spin_unlock(&d->event_lock);
|
||||
|
||||
- return 0;
|
||||
+ return rc;
|
||||
}
|
||||
|
||||
static long evtchn_set_priority(const struct evtchn_set_priority *set_priority)
|
||||
@@ -1219,10 +1241,9 @@ int alloc_unbound_xen_event_channel(
|
||||
|
||||
spin_lock(&ld->event_lock);
|
||||
|
||||
- rc = get_free_port(ld);
|
||||
+ port = rc = get_free_port(ld);
|
||||
if ( rc < 0 )
|
||||
goto out;
|
||||
- port = rc;
|
||||
chn = evtchn_from_port(ld, port);
|
||||
|
||||
rc = xsm_evtchn_unbound(XSM_TARGET, ld, chn, remote_domid);
|
||||
@@ -1238,7 +1259,10 @@ int alloc_unbound_xen_event_channel(
|
||||
|
||||
spin_unlock(&chn->lock);
|
||||
|
||||
+ write_atomic(&ld->xen_evtchns, ld->xen_evtchns + 1);
|
||||
+
|
||||
out:
|
||||
+ check_free_port(ld, port);
|
||||
spin_unlock(&ld->event_lock);
|
||||
|
||||
return rc < 0 ? rc : port;
|
||||
@@ -1314,6 +1338,7 @@ int evtchn_init(struct domain *d, unsign
|
||||
return -EINVAL;
|
||||
}
|
||||
evtchn_from_port(d, 0)->state = ECS_RESERVED;
|
||||
+ write_atomic(&d->active_evtchns, 0);
|
||||
|
||||
#if MAX_VIRT_CPUS > BITS_PER_LONG
|
||||
d->poll_mask = xzalloc_array(unsigned long, BITS_TO_LONGS(d->max_vcpus));
|
||||
@@ -1340,6 +1365,8 @@ void evtchn_destroy(struct domain *d)
|
||||
for ( i = 0; port_is_valid(d, i); i++ )
|
||||
evtchn_close(d, i, 0);
|
||||
|
||||
+ ASSERT(!d->active_evtchns);
|
||||
+
|
||||
clear_global_virq_handlers(d);
|
||||
|
||||
evtchn_fifo_destroy(d);
|
||||
--- a/xen/include/xen/sched.h
|
||||
+++ b/xen/include/xen/sched.h
|
||||
@@ -346,6 +346,16 @@ struct domain
|
||||
struct evtchn **evtchn_group[NR_EVTCHN_GROUPS]; /* all other buckets */
|
||||
unsigned int max_evtchn_port; /* max permitted port number */
|
||||
unsigned int valid_evtchns; /* number of allocated event channels */
|
||||
+ /*
|
||||
+ * Number of in-use event channels. Writers should use write_atomic().
|
||||
+ * Readers need to use read_atomic() only when not holding event_lock.
|
||||
+ */
|
||||
+ unsigned int active_evtchns;
|
||||
+ /*
|
||||
+ * Number of event channels used internally by Xen (not subject to
|
||||
+ * EVTCHNOP_reset). Read/write access like for active_evtchns.
|
||||
+ */
|
||||
+ unsigned int xen_evtchns;
|
||||
spinlock_t event_lock;
|
||||
const struct evtchn_port_ops *evtchn_port_ops;
|
||||
struct evtchn_fifo_domain *evtchn_fifo;
|
||||
290
xsa343-4.12-2.patch
Normal file
290
xsa343-4.12-2.patch
Normal file
|
|
@ -0,0 +1,290 @@
|
|||
From: Jan Beulich <jbeulich@suse.com>
|
||||
Subject: evtchn: convert per-channel lock to be IRQ-safe
|
||||
|
||||
... in order for send_guest_{global,vcpu}_virq() to be able to make use
|
||||
of it.
|
||||
|
||||
This is part of XSA-343.
|
||||
|
||||
Signed-off-by: Jan Beulich <jbeulich@suse.com>
|
||||
Acked-by: Julien Grall <jgrall@amazon.com>
|
||||
|
||||
--- a/xen/common/event_channel.c
|
||||
+++ b/xen/common/event_channel.c
|
||||
@@ -248,6 +248,7 @@ static long evtchn_alloc_unbound(evtchn_
|
||||
int port;
|
||||
domid_t dom = alloc->dom;
|
||||
long rc;
|
||||
+ unsigned long flags;
|
||||
|
||||
d = rcu_lock_domain_by_any_id(dom);
|
||||
if ( d == NULL )
|
||||
@@ -263,14 +264,14 @@ static long evtchn_alloc_unbound(evtchn_
|
||||
if ( rc )
|
||||
goto out;
|
||||
|
||||
- spin_lock(&chn->lock);
|
||||
+ spin_lock_irqsave(&chn->lock, flags);
|
||||
|
||||
chn->state = ECS_UNBOUND;
|
||||
if ( (chn->u.unbound.remote_domid = alloc->remote_dom) == DOMID_SELF )
|
||||
chn->u.unbound.remote_domid = current->domain->domain_id;
|
||||
evtchn_port_init(d, chn);
|
||||
|
||||
- spin_unlock(&chn->lock);
|
||||
+ spin_unlock_irqrestore(&chn->lock, flags);
|
||||
|
||||
alloc->port = port;
|
||||
|
||||
@@ -283,26 +284,32 @@ static long evtchn_alloc_unbound(evtchn_
|
||||
}
|
||||
|
||||
|
||||
-static void double_evtchn_lock(struct evtchn *lchn, struct evtchn *rchn)
|
||||
+static unsigned long double_evtchn_lock(struct evtchn *lchn,
|
||||
+ struct evtchn *rchn)
|
||||
{
|
||||
- if ( lchn < rchn )
|
||||
+ unsigned long flags;
|
||||
+
|
||||
+ if ( lchn <= rchn )
|
||||
{
|
||||
- spin_lock(&lchn->lock);
|
||||
- spin_lock(&rchn->lock);
|
||||
+ spin_lock_irqsave(&lchn->lock, flags);
|
||||
+ if ( lchn != rchn )
|
||||
+ spin_lock(&rchn->lock);
|
||||
}
|
||||
else
|
||||
{
|
||||
- if ( lchn != rchn )
|
||||
- spin_lock(&rchn->lock);
|
||||
+ spin_lock_irqsave(&rchn->lock, flags);
|
||||
spin_lock(&lchn->lock);
|
||||
}
|
||||
+
|
||||
+ return flags;
|
||||
}
|
||||
|
||||
-static void double_evtchn_unlock(struct evtchn *lchn, struct evtchn *rchn)
|
||||
+static void double_evtchn_unlock(struct evtchn *lchn, struct evtchn *rchn,
|
||||
+ unsigned long flags)
|
||||
{
|
||||
- spin_unlock(&lchn->lock);
|
||||
if ( lchn != rchn )
|
||||
- spin_unlock(&rchn->lock);
|
||||
+ spin_unlock(&lchn->lock);
|
||||
+ spin_unlock_irqrestore(&rchn->lock, flags);
|
||||
}
|
||||
|
||||
static long evtchn_bind_interdomain(evtchn_bind_interdomain_t *bind)
|
||||
@@ -312,6 +319,7 @@ static long evtchn_bind_interdomain(evtc
|
||||
int lport, rport = bind->remote_port;
|
||||
domid_t rdom = bind->remote_dom;
|
||||
long rc;
|
||||
+ unsigned long flags;
|
||||
|
||||
if ( rdom == DOMID_SELF )
|
||||
rdom = current->domain->domain_id;
|
||||
@@ -347,7 +355,7 @@ static long evtchn_bind_interdomain(evtc
|
||||
if ( rc )
|
||||
goto out;
|
||||
|
||||
- double_evtchn_lock(lchn, rchn);
|
||||
+ flags = double_evtchn_lock(lchn, rchn);
|
||||
|
||||
lchn->u.interdomain.remote_dom = rd;
|
||||
lchn->u.interdomain.remote_port = rport;
|
||||
@@ -364,7 +372,7 @@ static long evtchn_bind_interdomain(evtc
|
||||
*/
|
||||
evtchn_port_set_pending(ld, lchn->notify_vcpu_id, lchn);
|
||||
|
||||
- double_evtchn_unlock(lchn, rchn);
|
||||
+ double_evtchn_unlock(lchn, rchn, flags);
|
||||
|
||||
bind->local_port = lport;
|
||||
|
||||
@@ -387,6 +395,7 @@ int evtchn_bind_virq(evtchn_bind_virq_t
|
||||
struct domain *d = current->domain;
|
||||
int virq = bind->virq, vcpu = bind->vcpu;
|
||||
int rc = 0;
|
||||
+ unsigned long flags;
|
||||
|
||||
if ( (virq < 0) || (virq >= ARRAY_SIZE(v->virq_to_evtchn)) )
|
||||
return -EINVAL;
|
||||
@@ -424,14 +433,14 @@ int evtchn_bind_virq(evtchn_bind_virq_t
|
||||
|
||||
chn = evtchn_from_port(d, port);
|
||||
|
||||
- spin_lock(&chn->lock);
|
||||
+ spin_lock_irqsave(&chn->lock, flags);
|
||||
|
||||
chn->state = ECS_VIRQ;
|
||||
chn->notify_vcpu_id = vcpu;
|
||||
chn->u.virq = virq;
|
||||
evtchn_port_init(d, chn);
|
||||
|
||||
- spin_unlock(&chn->lock);
|
||||
+ spin_unlock_irqrestore(&chn->lock, flags);
|
||||
|
||||
v->virq_to_evtchn[virq] = bind->port = port;
|
||||
|
||||
@@ -448,6 +457,7 @@ static long evtchn_bind_ipi(evtchn_bind_
|
||||
struct domain *d = current->domain;
|
||||
int port, vcpu = bind->vcpu;
|
||||
long rc = 0;
|
||||
+ unsigned long flags;
|
||||
|
||||
if ( domain_vcpu(d, vcpu) == NULL )
|
||||
return -ENOENT;
|
||||
@@ -459,13 +469,13 @@ static long evtchn_bind_ipi(evtchn_bind_
|
||||
|
||||
chn = evtchn_from_port(d, port);
|
||||
|
||||
- spin_lock(&chn->lock);
|
||||
+ spin_lock_irqsave(&chn->lock, flags);
|
||||
|
||||
chn->state = ECS_IPI;
|
||||
chn->notify_vcpu_id = vcpu;
|
||||
evtchn_port_init(d, chn);
|
||||
|
||||
- spin_unlock(&chn->lock);
|
||||
+ spin_unlock_irqrestore(&chn->lock, flags);
|
||||
|
||||
bind->port = port;
|
||||
|
||||
@@ -509,6 +519,7 @@ static long evtchn_bind_pirq(evtchn_bind
|
||||
struct pirq *info;
|
||||
int port = 0, pirq = bind->pirq;
|
||||
long rc;
|
||||
+ unsigned long flags;
|
||||
|
||||
if ( (pirq < 0) || (pirq >= d->nr_pirqs) )
|
||||
return -EINVAL;
|
||||
@@ -541,14 +552,14 @@ static long evtchn_bind_pirq(evtchn_bind
|
||||
goto out;
|
||||
}
|
||||
|
||||
- spin_lock(&chn->lock);
|
||||
+ spin_lock_irqsave(&chn->lock, flags);
|
||||
|
||||
chn->state = ECS_PIRQ;
|
||||
chn->u.pirq.irq = pirq;
|
||||
link_pirq_port(port, chn, v);
|
||||
evtchn_port_init(d, chn);
|
||||
|
||||
- spin_unlock(&chn->lock);
|
||||
+ spin_unlock_irqrestore(&chn->lock, flags);
|
||||
|
||||
bind->port = port;
|
||||
|
||||
@@ -569,6 +580,7 @@ int evtchn_close(struct domain *d1, int
|
||||
struct evtchn *chn1, *chn2;
|
||||
int port2;
|
||||
long rc = 0;
|
||||
+ unsigned long flags;
|
||||
|
||||
again:
|
||||
spin_lock(&d1->event_lock);
|
||||
@@ -668,14 +680,14 @@ int evtchn_close(struct domain *d1, int
|
||||
BUG_ON(chn2->state != ECS_INTERDOMAIN);
|
||||
BUG_ON(chn2->u.interdomain.remote_dom != d1);
|
||||
|
||||
- double_evtchn_lock(chn1, chn2);
|
||||
+ flags = double_evtchn_lock(chn1, chn2);
|
||||
|
||||
evtchn_free(d1, chn1);
|
||||
|
||||
chn2->state = ECS_UNBOUND;
|
||||
chn2->u.unbound.remote_domid = d1->domain_id;
|
||||
|
||||
- double_evtchn_unlock(chn1, chn2);
|
||||
+ double_evtchn_unlock(chn1, chn2, flags);
|
||||
|
||||
goto out;
|
||||
|
||||
@@ -683,9 +695,9 @@ int evtchn_close(struct domain *d1, int
|
||||
BUG();
|
||||
}
|
||||
|
||||
- spin_lock(&chn1->lock);
|
||||
+ spin_lock_irqsave(&chn1->lock, flags);
|
||||
evtchn_free(d1, chn1);
|
||||
- spin_unlock(&chn1->lock);
|
||||
+ spin_unlock_irqrestore(&chn1->lock, flags);
|
||||
|
||||
out:
|
||||
if ( d2 != NULL )
|
||||
@@ -705,13 +717,14 @@ int evtchn_send(struct domain *ld, unsig
|
||||
struct evtchn *lchn, *rchn;
|
||||
struct domain *rd;
|
||||
int rport, ret = 0;
|
||||
+ unsigned long flags;
|
||||
|
||||
if ( !port_is_valid(ld, lport) )
|
||||
return -EINVAL;
|
||||
|
||||
lchn = evtchn_from_port(ld, lport);
|
||||
|
||||
- spin_lock(&lchn->lock);
|
||||
+ spin_lock_irqsave(&lchn->lock, flags);
|
||||
|
||||
/* Guest cannot send via a Xen-attached event channel. */
|
||||
if ( unlikely(consumer_is_xen(lchn)) )
|
||||
@@ -746,7 +759,7 @@ int evtchn_send(struct domain *ld, unsig
|
||||
}
|
||||
|
||||
out:
|
||||
- spin_unlock(&lchn->lock);
|
||||
+ spin_unlock_irqrestore(&lchn->lock, flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -1238,6 +1251,7 @@ int alloc_unbound_xen_event_channel(
|
||||
{
|
||||
struct evtchn *chn;
|
||||
int port, rc;
|
||||
+ unsigned long flags;
|
||||
|
||||
spin_lock(&ld->event_lock);
|
||||
|
||||
@@ -1250,14 +1264,14 @@ int alloc_unbound_xen_event_channel(
|
||||
if ( rc )
|
||||
goto out;
|
||||
|
||||
- spin_lock(&chn->lock);
|
||||
+ spin_lock_irqsave(&chn->lock, flags);
|
||||
|
||||
chn->state = ECS_UNBOUND;
|
||||
chn->xen_consumer = get_xen_consumer(notification_fn);
|
||||
chn->notify_vcpu_id = lvcpu;
|
||||
chn->u.unbound.remote_domid = remote_domid;
|
||||
|
||||
- spin_unlock(&chn->lock);
|
||||
+ spin_unlock_irqrestore(&chn->lock, flags);
|
||||
|
||||
write_atomic(&ld->xen_evtchns, ld->xen_evtchns + 1);
|
||||
|
||||
@@ -1280,11 +1294,12 @@ void notify_via_xen_event_channel(struct
|
||||
{
|
||||
struct evtchn *lchn, *rchn;
|
||||
struct domain *rd;
|
||||
+ unsigned long flags;
|
||||
|
||||
ASSERT(port_is_valid(ld, lport));
|
||||
lchn = evtchn_from_port(ld, lport);
|
||||
|
||||
- spin_lock(&lchn->lock);
|
||||
+ spin_lock_irqsave(&lchn->lock, flags);
|
||||
|
||||
if ( likely(lchn->state == ECS_INTERDOMAIN) )
|
||||
{
|
||||
@@ -1294,7 +1309,7 @@ void notify_via_xen_event_channel(struct
|
||||
evtchn_port_set_pending(rd, rchn->notify_vcpu_id, rchn);
|
||||
}
|
||||
|
||||
- spin_unlock(&lchn->lock);
|
||||
+ spin_unlock_irqrestore(&lchn->lock, flags);
|
||||
}
|
||||
|
||||
void evtchn_check_pollers(struct domain *d, unsigned int port)
|
||||
381
xsa343-4.12-3.patch
Normal file
381
xsa343-4.12-3.patch
Normal file
|
|
@ -0,0 +1,381 @@
|
|||
From: Jan Beulich <jbeulich@suse.com>
|
||||
Subject: evtchn: address races with evtchn_reset()
|
||||
|
||||
Neither d->evtchn_port_ops nor max_evtchns(d) may be used in an entirely
|
||||
lock-less manner, as both may change by a racing evtchn_reset(). In the
|
||||
common case, at least one of the domain's event lock or the per-channel
|
||||
lock needs to be held. In the specific case of the inter-domain sending
|
||||
by evtchn_send() and notify_via_xen_event_channel() holding the other
|
||||
side's per-channel lock is sufficient, as the channel can't change state
|
||||
without both per-channel locks held. Without such a channel changing
|
||||
state, evtchn_reset() can't complete successfully.
|
||||
|
||||
Lock-free accesses continue to be permitted for the shim (calling some
|
||||
otherwise internal event channel functions), as this happens while the
|
||||
domain is in effectively single-threaded mode. Special care also needs
|
||||
taking for the shim's marking of in-use ports as ECS_RESERVED (allowing
|
||||
use of such ports in the shim case is okay because switching into and
|
||||
hence also out of FIFO mode is impossible there).
|
||||
|
||||
As a side effect, certain operations on Xen bound event channels which
|
||||
were mistakenly permitted so far (e.g. unmask or poll) will be refused
|
||||
now.
|
||||
|
||||
This is part of XSA-343.
|
||||
|
||||
Reported-by: Julien Grall <jgrall@amazon.com>
|
||||
Signed-off-by: Jan Beulich <jbeulich@suse.com>
|
||||
Acked-by: Julien Grall <jgrall@amazon.com>
|
||||
|
||||
--- a/xen/arch/x86/irq.c
|
||||
+++ b/xen/arch/x86/irq.c
|
||||
@@ -2364,14 +2364,24 @@ static void dump_irqs(unsigned char key)
|
||||
|
||||
for ( i = 0; i < action->nr_guests; i++ )
|
||||
{
|
||||
+ struct evtchn *evtchn;
|
||||
+ unsigned int pending = 2, masked = 2;
|
||||
+
|
||||
d = action->guest[i];
|
||||
pirq = domain_irq_to_pirq(d, irq);
|
||||
info = pirq_info(d, pirq);
|
||||
+ evtchn = evtchn_from_port(d, info->evtchn);
|
||||
+ local_irq_disable();
|
||||
+ if ( spin_trylock(&evtchn->lock) )
|
||||
+ {
|
||||
+ pending = evtchn_is_pending(d, evtchn);
|
||||
+ masked = evtchn_is_masked(d, evtchn);
|
||||
+ spin_unlock(&evtchn->lock);
|
||||
+ }
|
||||
+ local_irq_enable();
|
||||
printk("%u:%3d(%c%c%c)",
|
||||
- d->domain_id, pirq,
|
||||
- evtchn_port_is_pending(d, info->evtchn) ? 'P' : '-',
|
||||
- evtchn_port_is_masked(d, info->evtchn) ? 'M' : '-',
|
||||
- (info->masked ? 'M' : '-'));
|
||||
+ d->domain_id, pirq, "-P?"[pending],
|
||||
+ "-M?"[masked], info->masked ? 'M' : '-');
|
||||
if ( i != action->nr_guests )
|
||||
printk(",");
|
||||
}
|
||||
--- a/xen/arch/x86/pv/shim.c
|
||||
+++ b/xen/arch/x86/pv/shim.c
|
||||
@@ -662,8 +662,11 @@ void pv_shim_inject_evtchn(unsigned int
|
||||
if ( port_is_valid(guest, port) )
|
||||
{
|
||||
struct evtchn *chn = evtchn_from_port(guest, port);
|
||||
+ unsigned long flags;
|
||||
|
||||
+ spin_lock_irqsave(&chn->lock, flags);
|
||||
evtchn_port_set_pending(guest, chn->notify_vcpu_id, chn);
|
||||
+ spin_unlock_irqrestore(&chn->lock, flags);
|
||||
}
|
||||
}
|
||||
|
||||
--- a/xen/common/event_2l.c
|
||||
+++ b/xen/common/event_2l.c
|
||||
@@ -63,8 +63,10 @@ static void evtchn_2l_unmask(struct doma
|
||||
}
|
||||
}
|
||||
|
||||
-static bool evtchn_2l_is_pending(const struct domain *d, evtchn_port_t port)
|
||||
+static bool evtchn_2l_is_pending(const struct domain *d,
|
||||
+ const struct evtchn *evtchn)
|
||||
{
|
||||
+ evtchn_port_t port = evtchn->port;
|
||||
unsigned int max_ports = BITS_PER_EVTCHN_WORD(d) * BITS_PER_EVTCHN_WORD(d);
|
||||
|
||||
ASSERT(port < max_ports);
|
||||
@@ -72,8 +74,10 @@ static bool evtchn_2l_is_pending(const s
|
||||
guest_test_bit(d, port, &shared_info(d, evtchn_pending)));
|
||||
}
|
||||
|
||||
-static bool evtchn_2l_is_masked(const struct domain *d, evtchn_port_t port)
|
||||
+static bool evtchn_2l_is_masked(const struct domain *d,
|
||||
+ const struct evtchn *evtchn)
|
||||
{
|
||||
+ evtchn_port_t port = evtchn->port;
|
||||
unsigned int max_ports = BITS_PER_EVTCHN_WORD(d) * BITS_PER_EVTCHN_WORD(d);
|
||||
|
||||
ASSERT(port < max_ports);
|
||||
--- a/xen/common/event_channel.c
|
||||
+++ b/xen/common/event_channel.c
|
||||
@@ -156,8 +156,9 @@ int evtchn_allocate_port(struct domain *
|
||||
|
||||
if ( port_is_valid(d, port) )
|
||||
{
|
||||
- if ( evtchn_from_port(d, port)->state != ECS_FREE ||
|
||||
- evtchn_port_is_busy(d, port) )
|
||||
+ const struct evtchn *chn = evtchn_from_port(d, port);
|
||||
+
|
||||
+ if ( chn->state != ECS_FREE || evtchn_is_busy(d, chn) )
|
||||
return -EBUSY;
|
||||
}
|
||||
else
|
||||
@@ -774,6 +775,7 @@ void send_guest_vcpu_virq(struct vcpu *v
|
||||
unsigned long flags;
|
||||
int port;
|
||||
struct domain *d;
|
||||
+ struct evtchn *chn;
|
||||
|
||||
ASSERT(!virq_is_global(virq));
|
||||
|
||||
@@ -784,7 +786,10 @@ void send_guest_vcpu_virq(struct vcpu *v
|
||||
goto out;
|
||||
|
||||
d = v->domain;
|
||||
- evtchn_port_set_pending(d, v->vcpu_id, evtchn_from_port(d, port));
|
||||
+ chn = evtchn_from_port(d, port);
|
||||
+ spin_lock(&chn->lock);
|
||||
+ evtchn_port_set_pending(d, v->vcpu_id, chn);
|
||||
+ spin_unlock(&chn->lock);
|
||||
|
||||
out:
|
||||
spin_unlock_irqrestore(&v->virq_lock, flags);
|
||||
@@ -813,7 +818,9 @@ void send_guest_global_virq(struct domai
|
||||
goto out;
|
||||
|
||||
chn = evtchn_from_port(d, port);
|
||||
+ spin_lock(&chn->lock);
|
||||
evtchn_port_set_pending(d, chn->notify_vcpu_id, chn);
|
||||
+ spin_unlock(&chn->lock);
|
||||
|
||||
out:
|
||||
spin_unlock_irqrestore(&v->virq_lock, flags);
|
||||
@@ -823,6 +830,7 @@ void send_guest_pirq(struct domain *d, c
|
||||
{
|
||||
int port;
|
||||
struct evtchn *chn;
|
||||
+ unsigned long flags;
|
||||
|
||||
/*
|
||||
* PV guests: It should not be possible to race with __evtchn_close(). The
|
||||
@@ -837,7 +845,9 @@ void send_guest_pirq(struct domain *d, c
|
||||
}
|
||||
|
||||
chn = evtchn_from_port(d, port);
|
||||
+ spin_lock_irqsave(&chn->lock, flags);
|
||||
evtchn_port_set_pending(d, chn->notify_vcpu_id, chn);
|
||||
+ spin_unlock_irqrestore(&chn->lock, flags);
|
||||
}
|
||||
|
||||
static struct domain *global_virq_handlers[NR_VIRQS] __read_mostly;
|
||||
@@ -1034,12 +1044,15 @@ int evtchn_unmask(unsigned int port)
|
||||
{
|
||||
struct domain *d = current->domain;
|
||||
struct evtchn *evtchn;
|
||||
+ unsigned long flags;
|
||||
|
||||
if ( unlikely(!port_is_valid(d, port)) )
|
||||
return -EINVAL;
|
||||
|
||||
evtchn = evtchn_from_port(d, port);
|
||||
+ spin_lock_irqsave(&evtchn->lock, flags);
|
||||
evtchn_port_unmask(d, evtchn);
|
||||
+ spin_unlock_irqrestore(&evtchn->lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1449,8 +1462,8 @@ static void domain_dump_evtchn_info(stru
|
||||
|
||||
printk(" %4u [%d/%d/",
|
||||
port,
|
||||
- evtchn_port_is_pending(d, port),
|
||||
- evtchn_port_is_masked(d, port));
|
||||
+ evtchn_is_pending(d, chn),
|
||||
+ evtchn_is_masked(d, chn));
|
||||
evtchn_port_print_state(d, chn);
|
||||
printk("]: s=%d n=%d x=%d",
|
||||
chn->state, chn->notify_vcpu_id, chn->xen_consumer);
|
||||
--- a/xen/common/event_fifo.c
|
||||
+++ b/xen/common/event_fifo.c
|
||||
@@ -296,23 +296,26 @@ static void evtchn_fifo_unmask(struct do
|
||||
evtchn_fifo_set_pending(v, evtchn);
|
||||
}
|
||||
|
||||
-static bool evtchn_fifo_is_pending(const struct domain *d, evtchn_port_t port)
|
||||
+static bool evtchn_fifo_is_pending(const struct domain *d,
|
||||
+ const struct evtchn *evtchn)
|
||||
{
|
||||
- const event_word_t *word = evtchn_fifo_word_from_port(d, port);
|
||||
+ const event_word_t *word = evtchn_fifo_word_from_port(d, evtchn->port);
|
||||
|
||||
return word && guest_test_bit(d, EVTCHN_FIFO_PENDING, word);
|
||||
}
|
||||
|
||||
-static bool_t evtchn_fifo_is_masked(const struct domain *d, evtchn_port_t port)
|
||||
+static bool_t evtchn_fifo_is_masked(const struct domain *d,
|
||||
+ const struct evtchn *evtchn)
|
||||
{
|
||||
- const event_word_t *word = evtchn_fifo_word_from_port(d, port);
|
||||
+ const event_word_t *word = evtchn_fifo_word_from_port(d, evtchn->port);
|
||||
|
||||
return !word || guest_test_bit(d, EVTCHN_FIFO_MASKED, word);
|
||||
}
|
||||
|
||||
-static bool_t evtchn_fifo_is_busy(const struct domain *d, evtchn_port_t port)
|
||||
+static bool_t evtchn_fifo_is_busy(const struct domain *d,
|
||||
+ const struct evtchn *evtchn)
|
||||
{
|
||||
- const event_word_t *word = evtchn_fifo_word_from_port(d, port);
|
||||
+ const event_word_t *word = evtchn_fifo_word_from_port(d, evtchn->port);
|
||||
|
||||
return word && guest_test_bit(d, EVTCHN_FIFO_LINKED, word);
|
||||
}
|
||||
--- a/xen/include/asm-x86/event.h
|
||||
+++ b/xen/include/asm-x86/event.h
|
||||
@@ -47,4 +47,10 @@ static inline bool arch_virq_is_global(u
|
||||
return true;
|
||||
}
|
||||
|
||||
+#ifdef CONFIG_PV_SHIM
|
||||
+# include <asm/pv/shim.h>
|
||||
+# define arch_evtchn_is_special(chn) \
|
||||
+ (pv_shim && (chn)->port && (chn)->state == ECS_RESERVED)
|
||||
+#endif
|
||||
+
|
||||
#endif
|
||||
--- a/xen/include/xen/event.h
|
||||
+++ b/xen/include/xen/event.h
|
||||
@@ -133,6 +133,24 @@ static inline struct evtchn *evtchn_from
|
||||
return bucket_from_port(d, p) + (p % EVTCHNS_PER_BUCKET);
|
||||
}
|
||||
|
||||
+/*
|
||||
+ * "usable" as in "by a guest", i.e. Xen consumed channels are assumed to be
|
||||
+ * taken care of separately where used for Xen's internal purposes.
|
||||
+ */
|
||||
+static bool evtchn_usable(const struct evtchn *evtchn)
|
||||
+{
|
||||
+ if ( evtchn->xen_consumer )
|
||||
+ return false;
|
||||
+
|
||||
+#ifdef arch_evtchn_is_special
|
||||
+ if ( arch_evtchn_is_special(evtchn) )
|
||||
+ return true;
|
||||
+#endif
|
||||
+
|
||||
+ BUILD_BUG_ON(ECS_FREE > ECS_RESERVED);
|
||||
+ return evtchn->state > ECS_RESERVED;
|
||||
+}
|
||||
+
|
||||
/* Wait on a Xen-attached event channel. */
|
||||
#define wait_on_xen_event_channel(port, condition) \
|
||||
do { \
|
||||
@@ -165,19 +183,24 @@ int evtchn_reset(struct domain *d);
|
||||
|
||||
/*
|
||||
* Low-level event channel port ops.
|
||||
+ *
|
||||
+ * All hooks have to be called with a lock held which prevents the channel
|
||||
+ * from changing state. This may be the domain event lock, the per-channel
|
||||
+ * lock, or in the case of sending interdomain events also the other side's
|
||||
+ * per-channel lock. Exceptions apply in certain cases for the PV shim.
|
||||
*/
|
||||
struct evtchn_port_ops {
|
||||
void (*init)(struct domain *d, struct evtchn *evtchn);
|
||||
void (*set_pending)(struct vcpu *v, struct evtchn *evtchn);
|
||||
void (*clear_pending)(struct domain *d, struct evtchn *evtchn);
|
||||
void (*unmask)(struct domain *d, struct evtchn *evtchn);
|
||||
- bool (*is_pending)(const struct domain *d, evtchn_port_t port);
|
||||
- bool (*is_masked)(const struct domain *d, evtchn_port_t port);
|
||||
+ bool (*is_pending)(const struct domain *d, const struct evtchn *evtchn);
|
||||
+ bool (*is_masked)(const struct domain *d, const struct evtchn *evtchn);
|
||||
/*
|
||||
* Is the port unavailable because it's still being cleaned up
|
||||
* after being closed?
|
||||
*/
|
||||
- bool (*is_busy)(const struct domain *d, evtchn_port_t port);
|
||||
+ bool (*is_busy)(const struct domain *d, const struct evtchn *evtchn);
|
||||
int (*set_priority)(struct domain *d, struct evtchn *evtchn,
|
||||
unsigned int priority);
|
||||
void (*print_state)(struct domain *d, const struct evtchn *evtchn);
|
||||
@@ -193,38 +216,67 @@ static inline void evtchn_port_set_pendi
|
||||
unsigned int vcpu_id,
|
||||
struct evtchn *evtchn)
|
||||
{
|
||||
- d->evtchn_port_ops->set_pending(d->vcpu[vcpu_id], evtchn);
|
||||
+ if ( evtchn_usable(evtchn) )
|
||||
+ d->evtchn_port_ops->set_pending(d->vcpu[vcpu_id], evtchn);
|
||||
}
|
||||
|
||||
static inline void evtchn_port_clear_pending(struct domain *d,
|
||||
struct evtchn *evtchn)
|
||||
{
|
||||
- d->evtchn_port_ops->clear_pending(d, evtchn);
|
||||
+ if ( evtchn_usable(evtchn) )
|
||||
+ d->evtchn_port_ops->clear_pending(d, evtchn);
|
||||
}
|
||||
|
||||
static inline void evtchn_port_unmask(struct domain *d,
|
||||
struct evtchn *evtchn)
|
||||
{
|
||||
- d->evtchn_port_ops->unmask(d, evtchn);
|
||||
+ if ( evtchn_usable(evtchn) )
|
||||
+ d->evtchn_port_ops->unmask(d, evtchn);
|
||||
}
|
||||
|
||||
-static inline bool evtchn_port_is_pending(const struct domain *d,
|
||||
- evtchn_port_t port)
|
||||
+static inline bool evtchn_is_pending(const struct domain *d,
|
||||
+ const struct evtchn *evtchn)
|
||||
{
|
||||
- return d->evtchn_port_ops->is_pending(d, port);
|
||||
+ return evtchn_usable(evtchn) && d->evtchn_port_ops->is_pending(d, evtchn);
|
||||
}
|
||||
|
||||
-static inline bool evtchn_port_is_masked(const struct domain *d,
|
||||
- evtchn_port_t port)
|
||||
+static inline bool evtchn_port_is_pending(struct domain *d, evtchn_port_t port)
|
||||
{
|
||||
- return d->evtchn_port_ops->is_masked(d, port);
|
||||
+ struct evtchn *evtchn = evtchn_from_port(d, port);
|
||||
+ bool rc;
|
||||
+ unsigned long flags;
|
||||
+
|
||||
+ spin_lock_irqsave(&evtchn->lock, flags);
|
||||
+ rc = evtchn_is_pending(d, evtchn);
|
||||
+ spin_unlock_irqrestore(&evtchn->lock, flags);
|
||||
+
|
||||
+ return rc;
|
||||
+}
|
||||
+
|
||||
+static inline bool evtchn_is_masked(const struct domain *d,
|
||||
+ const struct evtchn *evtchn)
|
||||
+{
|
||||
+ return !evtchn_usable(evtchn) || d->evtchn_port_ops->is_masked(d, evtchn);
|
||||
+}
|
||||
+
|
||||
+static inline bool evtchn_port_is_masked(struct domain *d, evtchn_port_t port)
|
||||
+{
|
||||
+ struct evtchn *evtchn = evtchn_from_port(d, port);
|
||||
+ bool rc;
|
||||
+ unsigned long flags;
|
||||
+
|
||||
+ spin_lock_irqsave(&evtchn->lock, flags);
|
||||
+ rc = evtchn_is_masked(d, evtchn);
|
||||
+ spin_unlock_irqrestore(&evtchn->lock, flags);
|
||||
+
|
||||
+ return rc;
|
||||
}
|
||||
|
||||
-static inline bool evtchn_port_is_busy(const struct domain *d,
|
||||
- evtchn_port_t port)
|
||||
+static inline bool evtchn_is_busy(const struct domain *d,
|
||||
+ const struct evtchn *evtchn)
|
||||
{
|
||||
return d->evtchn_port_ops->is_busy &&
|
||||
- d->evtchn_port_ops->is_busy(d, port);
|
||||
+ d->evtchn_port_ops->is_busy(d, evtchn);
|
||||
}
|
||||
|
||||
static inline int evtchn_port_set_priority(struct domain *d,
|
||||
@@ -233,6 +285,8 @@ static inline int evtchn_port_set_priori
|
||||
{
|
||||
if ( !d->evtchn_port_ops->set_priority )
|
||||
return -ENOSYS;
|
||||
+ if ( !evtchn_usable(evtchn) )
|
||||
+ return -EACCES;
|
||||
return d->evtchn_port_ops->set_priority(d, evtchn, priority);
|
||||
}
|
||||
|
||||
132
xsa344-4.12-1.patch
Normal file
132
xsa344-4.12-1.patch
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
From: Jan Beulich <jbeulich@suse.com>
|
||||
Subject: evtchn: arrange for preemption in evtchn_destroy()
|
||||
|
||||
Especially closing of fully established interdomain channels can take
|
||||
quite some time, due to the locking involved. Therefore we shouldn't
|
||||
assume we can clean up still active ports all in one go. Besides adding
|
||||
the necessary preemption check, also avoid pointlessly starting from
|
||||
(or now really ending at) 0; 1 is the lowest numbered port which may
|
||||
need closing.
|
||||
|
||||
Since we're now reducing ->valid_evtchns, free_xen_event_channel(),
|
||||
and (at least to be on the safe side) notify_via_xen_event_channel()
|
||||
need to cope with attempts to close / unbind from / send through already
|
||||
closed (and no longer valid, as per port_is_valid()) ports.
|
||||
|
||||
This is part of XSA-344.
|
||||
|
||||
Signed-off-by: Jan Beulich <jbeulich@suse.com>
|
||||
Acked-by: Julien Grall <jgrall@amazon.com>
|
||||
Reviewed-by: Stefano Stabellini <sstabellini@kernel.org>
|
||||
|
||||
--- a/xen/common/domain.c
|
||||
+++ b/xen/common/domain.c
|
||||
@@ -724,7 +724,6 @@ int domain_kill(struct domain *d)
|
||||
return domain_kill(d);
|
||||
d->is_dying = DOMDYING_dying;
|
||||
argo_destroy(d);
|
||||
- evtchn_destroy(d);
|
||||
gnttab_release_mappings(d);
|
||||
tmem_destroy(d->tmem_client);
|
||||
vnuma_destroy(d->vnuma);
|
||||
@@ -732,6 +731,9 @@ int domain_kill(struct domain *d)
|
||||
d->tmem_client = NULL;
|
||||
/* fallthrough */
|
||||
case DOMDYING_dying:
|
||||
+ rc = evtchn_destroy(d);
|
||||
+ if ( rc )
|
||||
+ break;
|
||||
rc = domain_relinquish_resources(d);
|
||||
if ( rc != 0 )
|
||||
break;
|
||||
--- a/xen/common/event_channel.c
|
||||
+++ b/xen/common/event_channel.c
|
||||
@@ -1297,7 +1297,16 @@ int alloc_unbound_xen_event_channel(
|
||||
|
||||
void free_xen_event_channel(struct domain *d, int port)
|
||||
{
|
||||
- BUG_ON(!port_is_valid(d, port));
|
||||
+ if ( !port_is_valid(d, port) )
|
||||
+ {
|
||||
+ /*
|
||||
+ * Make sure ->is_dying is read /after/ ->valid_evtchns, pairing
|
||||
+ * with the spin_barrier() and BUG_ON() in evtchn_destroy().
|
||||
+ */
|
||||
+ smp_rmb();
|
||||
+ BUG_ON(!d->is_dying);
|
||||
+ return;
|
||||
+ }
|
||||
|
||||
evtchn_close(d, port, 0);
|
||||
}
|
||||
@@ -1309,7 +1318,17 @@ void notify_via_xen_event_channel(struct
|
||||
struct domain *rd;
|
||||
unsigned long flags;
|
||||
|
||||
- ASSERT(port_is_valid(ld, lport));
|
||||
+ if ( !port_is_valid(ld, lport) )
|
||||
+ {
|
||||
+ /*
|
||||
+ * Make sure ->is_dying is read /after/ ->valid_evtchns, pairing
|
||||
+ * with the spin_barrier() and BUG_ON() in evtchn_destroy().
|
||||
+ */
|
||||
+ smp_rmb();
|
||||
+ ASSERT(ld->is_dying);
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
lchn = evtchn_from_port(ld, lport);
|
||||
|
||||
spin_lock_irqsave(&lchn->lock, flags);
|
||||
@@ -1380,8 +1399,7 @@ int evtchn_init(struct domain *d, unsign
|
||||
return 0;
|
||||
}
|
||||
|
||||
-
|
||||
-void evtchn_destroy(struct domain *d)
|
||||
+int evtchn_destroy(struct domain *d)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
@@ -1390,14 +1408,29 @@ void evtchn_destroy(struct domain *d)
|
||||
spin_barrier(&d->event_lock);
|
||||
|
||||
/* Close all existing event channels. */
|
||||
- for ( i = 0; port_is_valid(d, i); i++ )
|
||||
+ for ( i = d->valid_evtchns; --i; )
|
||||
+ {
|
||||
evtchn_close(d, i, 0);
|
||||
|
||||
+ /*
|
||||
+ * Avoid preempting when called from domain_create()'s error path,
|
||||
+ * and don't check too often (choice of frequency is arbitrary).
|
||||
+ */
|
||||
+ if ( i && !(i & 0x3f) && d->is_dying != DOMDYING_dead &&
|
||||
+ hypercall_preempt_check() )
|
||||
+ {
|
||||
+ write_atomic(&d->valid_evtchns, i);
|
||||
+ return -ERESTART;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
ASSERT(!d->active_evtchns);
|
||||
|
||||
clear_global_virq_handlers(d);
|
||||
|
||||
evtchn_fifo_destroy(d);
|
||||
+
|
||||
+ return 0;
|
||||
}
|
||||
|
||||
|
||||
--- a/xen/include/xen/sched.h
|
||||
+++ b/xen/include/xen/sched.h
|
||||
@@ -136,7 +136,7 @@ struct evtchn
|
||||
} __attribute__((aligned(64)));
|
||||
|
||||
int evtchn_init(struct domain *d, unsigned int max_port);
|
||||
-void evtchn_destroy(struct domain *d); /* from domain_kill */
|
||||
+int evtchn_destroy(struct domain *d); /* from domain_kill */
|
||||
void evtchn_destroy_final(struct domain *d); /* from complete_domain_destroy */
|
||||
|
||||
struct waitqueue_vcpu;
|
||||
203
xsa344-4.12-2.patch
Normal file
203
xsa344-4.12-2.patch
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
From: Jan Beulich <jbeulich@suse.com>
|
||||
Subject: evtchn: arrange for preemption in evtchn_reset()
|
||||
|
||||
Like for evtchn_destroy() looping over all possible event channels to
|
||||
close them can take a significant amount of time. Unlike done there, we
|
||||
can't alter domain properties (i.e. d->valid_evtchns) here. Borrow, in a
|
||||
lightweight form, the paging domctl continuation concept, redirecting
|
||||
the continuations to different sub-ops. Just like there this is to be
|
||||
able to allow for predictable overall results of the involved sub-ops:
|
||||
Racing requests should either complete or be refused.
|
||||
|
||||
Note that a domain can't interfere with an already started (by a remote
|
||||
domain) reset, due to being paused. It can prevent a remote reset from
|
||||
happening by leaving a reset unfinished, but that's only going to affect
|
||||
itself.
|
||||
|
||||
This is part of XSA-344.
|
||||
|
||||
Signed-off-by: Jan Beulich <jbeulich@suse.com>
|
||||
Acked-by: Julien Grall <jgrall@amazon.com>
|
||||
Reviewed-by: Stefano Stabellini <sstabellini@kernel.org>
|
||||
|
||||
--- a/xen/common/domain.c
|
||||
+++ b/xen/common/domain.c
|
||||
@@ -1170,7 +1170,7 @@ void domain_unpause_except_self(struct d
|
||||
domain_unpause(d);
|
||||
}
|
||||
|
||||
-int domain_soft_reset(struct domain *d)
|
||||
+int domain_soft_reset(struct domain *d, bool resuming)
|
||||
{
|
||||
struct vcpu *v;
|
||||
int rc;
|
||||
@@ -1184,7 +1184,7 @@ int domain_soft_reset(struct domain *d)
|
||||
}
|
||||
spin_unlock(&d->shutdown_lock);
|
||||
|
||||
- rc = evtchn_reset(d);
|
||||
+ rc = evtchn_reset(d, resuming);
|
||||
if ( rc )
|
||||
return rc;
|
||||
|
||||
--- a/xen/common/domctl.c
|
||||
+++ b/xen/common/domctl.c
|
||||
@@ -585,12 +585,22 @@ long do_domctl(XEN_GUEST_HANDLE_PARAM(xe
|
||||
}
|
||||
|
||||
case XEN_DOMCTL_soft_reset:
|
||||
+ case XEN_DOMCTL_soft_reset_cont:
|
||||
if ( d == current->domain ) /* no domain_pause() */
|
||||
{
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
- ret = domain_soft_reset(d);
|
||||
+ ret = domain_soft_reset(d, op->cmd == XEN_DOMCTL_soft_reset_cont);
|
||||
+ if ( ret == -ERESTART )
|
||||
+ {
|
||||
+ op->cmd = XEN_DOMCTL_soft_reset_cont;
|
||||
+ if ( !__copy_field_to_guest(u_domctl, op, cmd) )
|
||||
+ ret = hypercall_create_continuation(__HYPERVISOR_domctl,
|
||||
+ "h", u_domctl);
|
||||
+ else
|
||||
+ ret = -EFAULT;
|
||||
+ }
|
||||
break;
|
||||
|
||||
case XEN_DOMCTL_destroydomain:
|
||||
--- a/xen/common/event_channel.c
|
||||
+++ b/xen/common/event_channel.c
|
||||
@@ -1057,7 +1057,7 @@ int evtchn_unmask(unsigned int port)
|
||||
return 0;
|
||||
}
|
||||
|
||||
-int evtchn_reset(struct domain *d)
|
||||
+int evtchn_reset(struct domain *d, bool resuming)
|
||||
{
|
||||
unsigned int i;
|
||||
int rc = 0;
|
||||
@@ -1065,11 +1065,40 @@ int evtchn_reset(struct domain *d)
|
||||
if ( d != current->domain && !d->controller_pause_count )
|
||||
return -EINVAL;
|
||||
|
||||
- for ( i = 0; port_is_valid(d, i); i++ )
|
||||
+ spin_lock(&d->event_lock);
|
||||
+
|
||||
+ /*
|
||||
+ * If we are resuming, then start where we stopped. Otherwise, check
|
||||
+ * that a reset operation is not already in progress, and if none is,
|
||||
+ * record that this is now the case.
|
||||
+ */
|
||||
+ i = resuming ? d->next_evtchn : !d->next_evtchn;
|
||||
+ if ( i > d->next_evtchn )
|
||||
+ d->next_evtchn = i;
|
||||
+
|
||||
+ spin_unlock(&d->event_lock);
|
||||
+
|
||||
+ if ( !i )
|
||||
+ return -EBUSY;
|
||||
+
|
||||
+ for ( ; port_is_valid(d, i); i++ )
|
||||
+ {
|
||||
evtchn_close(d, i, 1);
|
||||
|
||||
+ /* NB: Choice of frequency is arbitrary. */
|
||||
+ if ( !(i & 0x3f) && hypercall_preempt_check() )
|
||||
+ {
|
||||
+ spin_lock(&d->event_lock);
|
||||
+ d->next_evtchn = i;
|
||||
+ spin_unlock(&d->event_lock);
|
||||
+ return -ERESTART;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
spin_lock(&d->event_lock);
|
||||
|
||||
+ d->next_evtchn = 0;
|
||||
+
|
||||
if ( d->active_evtchns > d->xen_evtchns )
|
||||
rc = -EAGAIN;
|
||||
else if ( d->evtchn_fifo )
|
||||
@@ -1204,7 +1233,8 @@ long do_event_channel_op(int cmd, XEN_GU
|
||||
break;
|
||||
}
|
||||
|
||||
- case EVTCHNOP_reset: {
|
||||
+ case EVTCHNOP_reset:
|
||||
+ case EVTCHNOP_reset_cont: {
|
||||
struct evtchn_reset reset;
|
||||
struct domain *d;
|
||||
|
||||
@@ -1217,9 +1247,13 @@ long do_event_channel_op(int cmd, XEN_GU
|
||||
|
||||
rc = xsm_evtchn_reset(XSM_TARGET, current->domain, d);
|
||||
if ( !rc )
|
||||
- rc = evtchn_reset(d);
|
||||
+ rc = evtchn_reset(d, cmd == EVTCHNOP_reset_cont);
|
||||
|
||||
rcu_unlock_domain(d);
|
||||
+
|
||||
+ if ( rc == -ERESTART )
|
||||
+ rc = hypercall_create_continuation(__HYPERVISOR_event_channel_op,
|
||||
+ "ih", EVTCHNOP_reset_cont, arg);
|
||||
break;
|
||||
}
|
||||
|
||||
--- a/xen/include/public/domctl.h
|
||||
+++ b/xen/include/public/domctl.h
|
||||
@@ -1144,7 +1144,10 @@ struct xen_domctl {
|
||||
#define XEN_DOMCTL_iomem_permission 20
|
||||
#define XEN_DOMCTL_ioport_permission 21
|
||||
#define XEN_DOMCTL_hypercall_init 22
|
||||
-#define XEN_DOMCTL_arch_setup 23 /* Obsolete IA64 only */
|
||||
+#ifdef __XEN__
|
||||
+/* #define XEN_DOMCTL_arch_setup 23 Obsolete IA64 only */
|
||||
+#define XEN_DOMCTL_soft_reset_cont 23
|
||||
+#endif
|
||||
#define XEN_DOMCTL_settimeoffset 24
|
||||
#define XEN_DOMCTL_getvcpuaffinity 25
|
||||
#define XEN_DOMCTL_real_mode_area 26 /* Obsolete PPC only */
|
||||
--- a/xen/include/public/event_channel.h
|
||||
+++ b/xen/include/public/event_channel.h
|
||||
@@ -74,6 +74,9 @@
|
||||
#define EVTCHNOP_init_control 11
|
||||
#define EVTCHNOP_expand_array 12
|
||||
#define EVTCHNOP_set_priority 13
|
||||
+#ifdef __XEN__
|
||||
+#define EVTCHNOP_reset_cont 14
|
||||
+#endif
|
||||
/* ` } */
|
||||
|
||||
typedef uint32_t evtchn_port_t;
|
||||
--- a/xen/include/xen/event.h
|
||||
+++ b/xen/include/xen/event.h
|
||||
@@ -171,7 +171,7 @@ void evtchn_check_pollers(struct domain
|
||||
void evtchn_2l_init(struct domain *d);
|
||||
|
||||
/* Close all event channels and reset to 2-level ABI. */
|
||||
-int evtchn_reset(struct domain *d);
|
||||
+int evtchn_reset(struct domain *d, bool resuming);
|
||||
|
||||
/*
|
||||
* Low-level event channel port ops.
|
||||
--- a/xen/include/xen/sched.h
|
||||
+++ b/xen/include/xen/sched.h
|
||||
@@ -356,6 +356,8 @@ struct domain
|
||||
* EVTCHNOP_reset). Read/write access like for active_evtchns.
|
||||
*/
|
||||
unsigned int xen_evtchns;
|
||||
+ /* Port to resume from in evtchn_reset(), when in a continuation. */
|
||||
+ unsigned int next_evtchn;
|
||||
spinlock_t event_lock;
|
||||
const struct evtchn_port_ops *evtchn_port_ops;
|
||||
struct evtchn_fifo_domain *evtchn_fifo;
|
||||
@@ -628,7 +630,7 @@ int domain_shutdown(struct domain *d, u8
|
||||
void domain_resume(struct domain *d);
|
||||
void domain_pause_for_debugger(void);
|
||||
|
||||
-int domain_soft_reset(struct domain *d);
|
||||
+int domain_soft_reset(struct domain *d, bool resuming);
|
||||
|
||||
int vcpu_start_shutdown_deferral(struct vcpu *v);
|
||||
void vcpu_end_shutdown_deferral(struct vcpu *v);
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
From e33fad3044aaaeec6ed9914925d9558695bdb09d Mon Sep 17 00:00:00 2001
|
||||
From: Wei Liu <wei.liu2@citrix.com>
|
||||
Date: Sat, 11 Jan 2020 21:57:41 +0000
|
||||
Subject: [PATCH 1/3] x86/mm: Refactor map_pages_to_xen to have only a single
|
||||
exit path
|
||||
|
||||
We will soon need to perform clean-ups before returning.
|
||||
|
||||
No functional change.
|
||||
|
||||
This is part of XSA-345.
|
||||
|
||||
Reported-by: Hongyan Xia <hongyxia@amazon.com>
|
||||
Signed-off-by: Wei Liu <wei.liu2@citrix.com>
|
||||
Signed-off-by: Hongyan Xia <hongyxia@amazon.com>
|
||||
Signed-off-by: George Dunlap <george.dunlap@citrix.com>
|
||||
---
|
||||
xen/arch/x86/mm.c | 17 +++++++++++------
|
||||
1 file changed, 11 insertions(+), 6 deletions(-)
|
||||
|
||||
diff --git a/xen/arch/x86/mm.c b/xen/arch/x86/mm.c
|
||||
index b4c90bd054..0e540f143b 100644
|
||||
--- a/xen/arch/x86/mm.c
|
||||
+++ b/xen/arch/x86/mm.c
|
||||
@@ -5227,6 +5227,7 @@ int map_pages_to_xen(
|
||||
l2_pgentry_t *pl2e, ol2e;
|
||||
l1_pgentry_t *pl1e, ol1e;
|
||||
unsigned int i;
|
||||
+ int rc = -ENOMEM;
|
||||
|
||||
#define flush_flags(oldf) do { \
|
||||
unsigned int o_ = (oldf); \
|
||||
@@ -5247,7 +5248,8 @@ int map_pages_to_xen(
|
||||
l3_pgentry_t ol3e, *pl3e = virt_to_xen_l3e(virt);
|
||||
|
||||
if ( !pl3e )
|
||||
- return -ENOMEM;
|
||||
+ goto out;
|
||||
+
|
||||
ol3e = *pl3e;
|
||||
|
||||
if ( cpu_has_page1gb &&
|
||||
@@ -5335,7 +5337,7 @@ int map_pages_to_xen(
|
||||
|
||||
pl2e = alloc_xen_pagetable();
|
||||
if ( pl2e == NULL )
|
||||
- return -ENOMEM;
|
||||
+ goto out;
|
||||
|
||||
for ( i = 0; i < L2_PAGETABLE_ENTRIES; i++ )
|
||||
l2e_write(pl2e + i,
|
||||
@@ -5364,7 +5366,7 @@ int map_pages_to_xen(
|
||||
|
||||
pl2e = virt_to_xen_l2e(virt);
|
||||
if ( !pl2e )
|
||||
- return -ENOMEM;
|
||||
+ goto out;
|
||||
|
||||
if ( ((((virt >> PAGE_SHIFT) | mfn_x(mfn)) &
|
||||
((1u << PAGETABLE_ORDER) - 1)) == 0) &&
|
||||
@@ -5407,7 +5409,7 @@ int map_pages_to_xen(
|
||||
{
|
||||
pl1e = virt_to_xen_l1e(virt);
|
||||
if ( pl1e == NULL )
|
||||
- return -ENOMEM;
|
||||
+ goto out;
|
||||
}
|
||||
else if ( l2e_get_flags(*pl2e) & _PAGE_PSE )
|
||||
{
|
||||
@@ -5434,7 +5436,7 @@ int map_pages_to_xen(
|
||||
|
||||
pl1e = alloc_xen_pagetable();
|
||||
if ( pl1e == NULL )
|
||||
- return -ENOMEM;
|
||||
+ goto out;
|
||||
|
||||
for ( i = 0; i < L1_PAGETABLE_ENTRIES; i++ )
|
||||
l1e_write(&pl1e[i],
|
||||
@@ -5578,7 +5580,10 @@ int map_pages_to_xen(
|
||||
|
||||
#undef flush_flags
|
||||
|
||||
- return 0;
|
||||
+ rc = 0;
|
||||
+
|
||||
+ out:
|
||||
+ return rc;
|
||||
}
|
||||
|
||||
int populate_pt_range(unsigned long virt, unsigned long nr_mfns)
|
||||
--
|
||||
2.25.1
|
||||
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
From a151b07d504d7f442256e1c82917334216c58a21 Mon Sep 17 00:00:00 2001
|
||||
From: Wei Liu <wei.liu2@citrix.com>
|
||||
Date: Sat, 11 Jan 2020 21:57:42 +0000
|
||||
Subject: [PATCH 2/3] x86/mm: Refactor modify_xen_mappings to have one exit
|
||||
path
|
||||
|
||||
We will soon need to perform clean-ups before returning.
|
||||
|
||||
No functional change.
|
||||
|
||||
This is part of XSA-345.
|
||||
|
||||
Reported-by: Hongyan Xia <hongyxia@amazon.com>
|
||||
Signed-off-by: Wei Liu <wei.liu2@citrix.com>
|
||||
Signed-off-by: Hongyan Xia <hongyxia@amazon.com>
|
||||
Signed-off-by: George Dunlap <george.dunlap@citrix.com>
|
||||
---
|
||||
xen/arch/x86/mm.c | 12 +++++++++---
|
||||
1 file changed, 9 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/xen/arch/x86/mm.c b/xen/arch/x86/mm.c
|
||||
index 0e540f143b..bff2689e60 100644
|
||||
--- a/xen/arch/x86/mm.c
|
||||
+++ b/xen/arch/x86/mm.c
|
||||
@@ -5610,6 +5610,7 @@ int modify_xen_mappings(unsigned long s, unsigned long e, unsigned int nf)
|
||||
l1_pgentry_t *pl1e;
|
||||
unsigned int i;
|
||||
unsigned long v = s;
|
||||
+ int rc = -ENOMEM;
|
||||
|
||||
/* Set of valid PTE bits which may be altered. */
|
||||
#define FLAGS_MASK (_PAGE_NX|_PAGE_RW|_PAGE_PRESENT)
|
||||
@@ -5651,7 +5652,8 @@ int modify_xen_mappings(unsigned long s, unsigned long e, unsigned int nf)
|
||||
/* PAGE1GB: shatter the superpage and fall through. */
|
||||
pl2e = alloc_xen_pagetable();
|
||||
if ( !pl2e )
|
||||
- return -ENOMEM;
|
||||
+ goto out;
|
||||
+
|
||||
for ( i = 0; i < L2_PAGETABLE_ENTRIES; i++ )
|
||||
l2e_write(pl2e + i,
|
||||
l2e_from_pfn(l3e_get_pfn(*pl3e) +
|
||||
@@ -5706,7 +5708,8 @@ int modify_xen_mappings(unsigned long s, unsigned long e, unsigned int nf)
|
||||
/* PSE: shatter the superpage and try again. */
|
||||
pl1e = alloc_xen_pagetable();
|
||||
if ( !pl1e )
|
||||
- return -ENOMEM;
|
||||
+ goto out;
|
||||
+
|
||||
for ( i = 0; i < L1_PAGETABLE_ENTRIES; i++ )
|
||||
l1e_write(&pl1e[i],
|
||||
l1e_from_pfn(l2e_get_pfn(*pl2e) + i,
|
||||
@@ -5835,7 +5838,10 @@ int modify_xen_mappings(unsigned long s, unsigned long e, unsigned int nf)
|
||||
flush_area(NULL, FLUSH_TLB_GLOBAL);
|
||||
|
||||
#undef FLAGS_MASK
|
||||
- return 0;
|
||||
+ rc = 0;
|
||||
+
|
||||
+ out:
|
||||
+ return rc;
|
||||
}
|
||||
|
||||
#undef flush_area
|
||||
--
|
||||
2.25.1
|
||||
|
||||
|
|
@ -0,0 +1,248 @@
|
|||
From abaf05e183f3bc3927844ae16d6642a382c0dbef Mon Sep 17 00:00:00 2001
|
||||
From: Hongyan Xia <hongyxia@amazon.com>
|
||||
Date: Sat, 11 Jan 2020 21:57:43 +0000
|
||||
Subject: [PATCH 3/3] x86/mm: Prevent some races in hypervisor mapping updates
|
||||
|
||||
map_pages_to_xen will attempt to coalesce mappings into 2MiB and 1GiB
|
||||
superpages if possible, to maximize TLB efficiency. This means both
|
||||
replacing superpage entries with smaller entries, and replacing
|
||||
smaller entries with superpages.
|
||||
|
||||
Unfortunately, while some potential races are handled correctly,
|
||||
others are not. These include:
|
||||
|
||||
1. When one processor modifies a sub-superpage mapping while another
|
||||
processor replaces the entire range with a superpage.
|
||||
|
||||
Take the following example:
|
||||
|
||||
Suppose L3[N] points to L2. And suppose we have two processors, A and
|
||||
B.
|
||||
|
||||
* A walks the pagetables, get a pointer to L2.
|
||||
* B replaces L3[N] with a 1GiB mapping.
|
||||
* B Frees L2
|
||||
* A writes L2[M] #
|
||||
|
||||
This is race exacerbated by the fact that virt_to_xen_l[21]e doesn't
|
||||
handle higher-level superpages properly: If you call virt_xen_to_l2e
|
||||
on a virtual address within an L3 superpage, you'll either hit a BUG()
|
||||
(most likely), or get a pointer into the middle of a data page; same
|
||||
with virt_xen_to_l1 on a virtual address within either an L3 or L2
|
||||
superpage.
|
||||
|
||||
So take the following example:
|
||||
|
||||
* A reads pl3e and discovers it to point to an L2.
|
||||
* B replaces L3[N] with a 1GiB mapping
|
||||
* A calls virt_to_xen_l2e() and hits the BUG_ON() #
|
||||
|
||||
2. When two processors simultaneously try to replace a sub-superpage
|
||||
mapping with a superpage mapping.
|
||||
|
||||
Take the following example:
|
||||
|
||||
Suppose L3[N] points to L2. And suppose we have two processors, A and B,
|
||||
both trying to replace L3[N] with a superpage.
|
||||
|
||||
* A walks the pagetables, get a pointer to pl3e, and takes a copy ol3e pointing to L2.
|
||||
* B walks the pagetables, gets a pointre to pl3e, and takes a copy ol3e pointing to L2.
|
||||
* A writes the new value into L3[N]
|
||||
* B writes the new value into L3[N]
|
||||
* A recursively frees all the L1's under L2, then frees L2
|
||||
* B recursively double-frees all the L1's under L2, then double-frees L2 #
|
||||
|
||||
Fix this by grabbing a lock for the entirety of the mapping update
|
||||
operation.
|
||||
|
||||
Rather than grabbing map_pgdir_lock for the entire operation, however,
|
||||
repurpose the PGT_locked bit from L3's page->type_info as a lock.
|
||||
This means that rather than locking the entire address space, we
|
||||
"only" lock a single 512GiB chunk of hypervisor address space at a
|
||||
time.
|
||||
|
||||
There was a proposal for a lock-and-reverify approach, where we walk
|
||||
the pagetables to the point where we decide what to do; then grab the
|
||||
map_pgdir_lock, re-verify the information we collected without the
|
||||
lock, and finally make the change (starting over again if anything had
|
||||
changed). Without being able to guarantee that the L2 table wasn't
|
||||
freed, however, that means every read would need to be considered
|
||||
potentially unsafe. Thinking carefully about that is probably
|
||||
something that wants to be done on public, not under time pressure.
|
||||
|
||||
This is part of XSA-345.
|
||||
|
||||
Reported-by: Hongyan Xia <hongyxia@amazon.com>
|
||||
Signed-off-by: Hongyan Xia <hongyxia@amazon.com>
|
||||
Signed-off-by: George Dunlap <george.dunlap@citrix.com>
|
||||
---
|
||||
xen/arch/x86/mm.c | 92 +++++++++++++++++++++++++++++++++++++++++++++--
|
||||
1 file changed, 89 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/xen/arch/x86/mm.c b/xen/arch/x86/mm.c
|
||||
index bff2689e60..d6ba8c4bb4 100644
|
||||
--- a/xen/arch/x86/mm.c
|
||||
+++ b/xen/arch/x86/mm.c
|
||||
@@ -2197,6 +2197,50 @@ void page_unlock(struct page_info *page)
|
||||
current_locked_page_set(NULL);
|
||||
}
|
||||
|
||||
+/*
|
||||
+ * L3 table locks:
|
||||
+ *
|
||||
+ * Used for serialization in map_pages_to_xen() and modify_xen_mappings().
|
||||
+ *
|
||||
+ * For Xen PT pages, the page->u.inuse.type_info is unused and it is safe to
|
||||
+ * reuse the PGT_locked flag. This lock is taken only when we move down to L3
|
||||
+ * tables and below, since L4 (and above, for 5-level paging) is still globally
|
||||
+ * protected by map_pgdir_lock.
|
||||
+ *
|
||||
+ * PV MMU update hypercalls call map_pages_to_xen while holding a page's page_lock().
|
||||
+ * This has two implications:
|
||||
+ * - We cannot reuse reuse current_locked_page_* for debugging
|
||||
+ * - To avoid the chance of deadlock, even for different pages, we
|
||||
+ * must never grab page_lock() after grabbing l3t_lock(). This
|
||||
+ * includes any page_lock()-based locks, such as
|
||||
+ * mem_sharing_page_lock().
|
||||
+ *
|
||||
+ * Also note that we grab the map_pgdir_lock while holding the
|
||||
+ * l3t_lock(), so to avoid deadlock we must avoid grabbing them in
|
||||
+ * reverse order.
|
||||
+ */
|
||||
+static void l3t_lock(struct page_info *page)
|
||||
+{
|
||||
+ unsigned long x, nx;
|
||||
+
|
||||
+ do {
|
||||
+ while ( (x = page->u.inuse.type_info) & PGT_locked )
|
||||
+ cpu_relax();
|
||||
+ nx = x | PGT_locked;
|
||||
+ } while ( cmpxchg(&page->u.inuse.type_info, x, nx) != x );
|
||||
+}
|
||||
+
|
||||
+static void l3t_unlock(struct page_info *page)
|
||||
+{
|
||||
+ unsigned long x, nx, y = page->u.inuse.type_info;
|
||||
+
|
||||
+ do {
|
||||
+ x = y;
|
||||
+ BUG_ON(!(x & PGT_locked));
|
||||
+ nx = x & ~PGT_locked;
|
||||
+ } while ( (y = cmpxchg(&page->u.inuse.type_info, x, nx)) != x );
|
||||
+}
|
||||
+
|
||||
#ifdef CONFIG_PV
|
||||
/*
|
||||
* PTE flags that a guest may change without re-validating the PTE.
|
||||
@@ -5217,6 +5261,23 @@ l1_pgentry_t *virt_to_xen_l1e(unsigned long v)
|
||||
flush_area_local((const void *)v, f) : \
|
||||
flush_area_all((const void *)v, f))
|
||||
|
||||
+#define L3T_INIT(page) (page) = ZERO_BLOCK_PTR
|
||||
+
|
||||
+#define L3T_LOCK(page) \
|
||||
+ do { \
|
||||
+ if ( locking ) \
|
||||
+ l3t_lock(page); \
|
||||
+ } while ( false )
|
||||
+
|
||||
+#define L3T_UNLOCK(page) \
|
||||
+ do { \
|
||||
+ if ( locking && (page) != ZERO_BLOCK_PTR ) \
|
||||
+ { \
|
||||
+ l3t_unlock(page); \
|
||||
+ (page) = ZERO_BLOCK_PTR; \
|
||||
+ } \
|
||||
+ } while ( false )
|
||||
+
|
||||
int map_pages_to_xen(
|
||||
unsigned long virt,
|
||||
mfn_t mfn,
|
||||
@@ -5228,6 +5289,7 @@ int map_pages_to_xen(
|
||||
l1_pgentry_t *pl1e, ol1e;
|
||||
unsigned int i;
|
||||
int rc = -ENOMEM;
|
||||
+ struct page_info *current_l3page;
|
||||
|
||||
#define flush_flags(oldf) do { \
|
||||
unsigned int o_ = (oldf); \
|
||||
@@ -5243,13 +5305,20 @@ int map_pages_to_xen(
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
+ L3T_INIT(current_l3page);
|
||||
+
|
||||
while ( nr_mfns != 0 )
|
||||
{
|
||||
- l3_pgentry_t ol3e, *pl3e = virt_to_xen_l3e(virt);
|
||||
+ l3_pgentry_t *pl3e, ol3e;
|
||||
|
||||
+ L3T_UNLOCK(current_l3page);
|
||||
+
|
||||
+ pl3e = virt_to_xen_l3e(virt);
|
||||
if ( !pl3e )
|
||||
goto out;
|
||||
|
||||
+ current_l3page = virt_to_page(pl3e);
|
||||
+ L3T_LOCK(current_l3page);
|
||||
ol3e = *pl3e;
|
||||
|
||||
if ( cpu_has_page1gb &&
|
||||
@@ -5583,6 +5652,7 @@ int map_pages_to_xen(
|
||||
rc = 0;
|
||||
|
||||
out:
|
||||
+ L3T_UNLOCK(current_l3page);
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -5611,6 +5681,7 @@ int modify_xen_mappings(unsigned long s, unsigned long e, unsigned int nf)
|
||||
unsigned int i;
|
||||
unsigned long v = s;
|
||||
int rc = -ENOMEM;
|
||||
+ struct page_info *current_l3page;
|
||||
|
||||
/* Set of valid PTE bits which may be altered. */
|
||||
#define FLAGS_MASK (_PAGE_NX|_PAGE_RW|_PAGE_PRESENT)
|
||||
@@ -5619,11 +5690,22 @@ int modify_xen_mappings(unsigned long s, unsigned long e, unsigned int nf)
|
||||
ASSERT(IS_ALIGNED(s, PAGE_SIZE));
|
||||
ASSERT(IS_ALIGNED(e, PAGE_SIZE));
|
||||
|
||||
+ L3T_INIT(current_l3page);
|
||||
+
|
||||
while ( v < e )
|
||||
{
|
||||
- l3_pgentry_t *pl3e = virt_to_xen_l3e(v);
|
||||
+ l3_pgentry_t *pl3e;
|
||||
+
|
||||
+ L3T_UNLOCK(current_l3page);
|
||||
|
||||
- if ( !pl3e || !(l3e_get_flags(*pl3e) & _PAGE_PRESENT) )
|
||||
+ pl3e = virt_to_xen_l3e(v);
|
||||
+ if ( !pl3e )
|
||||
+ goto out;
|
||||
+
|
||||
+ current_l3page = virt_to_page(pl3e);
|
||||
+ L3T_LOCK(current_l3page);
|
||||
+
|
||||
+ if ( !(l3e_get_flags(*pl3e) & _PAGE_PRESENT) )
|
||||
{
|
||||
/* Confirm the caller isn't trying to create new mappings. */
|
||||
ASSERT(!(nf & _PAGE_PRESENT));
|
||||
@@ -5841,9 +5923,13 @@ int modify_xen_mappings(unsigned long s, unsigned long e, unsigned int nf)
|
||||
rc = 0;
|
||||
|
||||
out:
|
||||
+ L3T_UNLOCK(current_l3page);
|
||||
return rc;
|
||||
}
|
||||
|
||||
+#undef L3T_LOCK
|
||||
+#undef L3T_UNLOCK
|
||||
+
|
||||
#undef flush_area
|
||||
|
||||
int destroy_xen_mappings(unsigned long s, unsigned long e)
|
||||
--
|
||||
2.25.1
|
||||
|
||||
50
xsa346-4.12-1.patch
Normal file
50
xsa346-4.12-1.patch
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
From: Jan Beulich <jbeulich@suse.com>
|
||||
Subject: IOMMU: suppress "iommu_dont_flush_iotlb" when about to free a page
|
||||
|
||||
Deferring flushes to a single, wide range one - as is done when
|
||||
handling XENMAPSPACE_gmfn_range - is okay only as long as
|
||||
pages don't get freed ahead of the eventual flush. While the only
|
||||
function setting the flag (xenmem_add_to_physmap()) suggests by its name
|
||||
that it's only mapping new entries, in reality the way
|
||||
xenmem_add_to_physmap_one() works means an unmap would happen not only
|
||||
for the page being moved (but not freed) but, if the destination GFN is
|
||||
populated, also for the page being displaced from that GFN. Collapsing
|
||||
the two flushes for this GFN into just one (end even more so deferring
|
||||
it to a batched invocation) is not correct.
|
||||
|
||||
This is part of XSA-346.
|
||||
|
||||
Fixes: cf95b2a9fd5a ("iommu: Introduce per cpu flag (iommu_dont_flush_iotlb) to avoid unnecessary iotlb... ")
|
||||
Signed-off-by: Jan Beulich <jbeulich@suse.com>
|
||||
Reviewed-by: Paul Durrant <paul@xen.org>
|
||||
Acked-by: Julien Grall <jgrall@amazon.com>
|
||||
|
||||
--- a/xen/common/memory.c
|
||||
+++ b/xen/common/memory.c
|
||||
@@ -300,6 +300,7 @@ int guest_remove_page(struct domain *d,
|
||||
p2m_type_t p2mt;
|
||||
#endif
|
||||
mfn_t mfn;
|
||||
+ bool *dont_flush_p, dont_flush;
|
||||
int rc;
|
||||
|
||||
#ifdef CONFIG_X86
|
||||
@@ -386,8 +387,18 @@ int guest_remove_page(struct domain *d,
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
+ /*
|
||||
+ * Since we're likely to free the page below, we need to suspend
|
||||
+ * xenmem_add_to_physmap()'s suppressing of IOMMU TLB flushes.
|
||||
+ */
|
||||
+ dont_flush_p = &this_cpu(iommu_dont_flush_iotlb);
|
||||
+ dont_flush = *dont_flush_p;
|
||||
+ *dont_flush_p = false;
|
||||
+
|
||||
rc = guest_physmap_remove_page(d, _gfn(gmfn), mfn, 0);
|
||||
|
||||
+ *dont_flush_p = dont_flush;
|
||||
+
|
||||
/*
|
||||
* With the lack of an IOMMU on some platforms, domains with DMA-capable
|
||||
* device must retrieve the same pfn when the hypercall populate_physmap
|
||||
202
xsa346-4.12-2.patch
Normal file
202
xsa346-4.12-2.patch
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
From: Jan Beulich <jbeulich@suse.com>
|
||||
Subject: IOMMU: hold page ref until after deferred TLB flush
|
||||
|
||||
When moving around a page via XENMAPSPACE_gmfn_range, deferring the TLB
|
||||
flush for the "from" GFN range requires that the page remains allocated
|
||||
to the guest until the TLB flush has actually occurred. Otherwise a
|
||||
parallel hypercall to remove the page would only flush the TLB for the
|
||||
GFN it has been moved to, but not the one is was mapped at originally.
|
||||
|
||||
This is part of XSA-346.
|
||||
|
||||
Fixes: cf95b2a9fd5a ("iommu: Introduce per cpu flag (iommu_dont_flush_iotlb) to avoid unnecessary iotlb... ")
|
||||
Reported-by: Julien Grall <jgrall@amazon.com>
|
||||
Signed-off-by: Jan Beulich <jbeulich@suse.com>
|
||||
Acked-by: Julien Grall <jgrall@amazon.com>
|
||||
|
||||
--- a/xen/arch/arm/mm.c
|
||||
+++ b/xen/arch/arm/mm.c
|
||||
@@ -1211,7 +1211,7 @@ void share_xen_page_with_guest(struct pa
|
||||
int xenmem_add_to_physmap_one(
|
||||
struct domain *d,
|
||||
unsigned int space,
|
||||
- union xen_add_to_physmap_batch_extra extra,
|
||||
+ union add_to_physmap_extra extra,
|
||||
unsigned long idx,
|
||||
gfn_t gfn)
|
||||
{
|
||||
@@ -1284,10 +1284,6 @@ int xenmem_add_to_physmap_one(
|
||||
break;
|
||||
}
|
||||
case XENMAPSPACE_dev_mmio:
|
||||
- /* extra should be 0. Reserved for future use. */
|
||||
- if ( extra.res0 )
|
||||
- return -EOPNOTSUPP;
|
||||
-
|
||||
rc = map_dev_mmio_region(d, gfn, 1, _mfn(idx));
|
||||
return rc;
|
||||
|
||||
--- a/xen/arch/x86/mm.c
|
||||
+++ b/xen/arch/x86/mm.c
|
||||
@@ -4653,7 +4653,7 @@ static int handle_iomem_range(unsigned l
|
||||
int xenmem_add_to_physmap_one(
|
||||
struct domain *d,
|
||||
unsigned int space,
|
||||
- union xen_add_to_physmap_batch_extra extra,
|
||||
+ union add_to_physmap_extra extra,
|
||||
unsigned long idx,
|
||||
gfn_t gpfn)
|
||||
{
|
||||
@@ -4740,9 +4740,20 @@ int xenmem_add_to_physmap_one(
|
||||
rc = guest_physmap_add_page(d, gpfn, mfn, PAGE_ORDER_4K);
|
||||
|
||||
put_both:
|
||||
- /* In the XENMAPSPACE_gmfn case, we took a ref of the gfn at the top. */
|
||||
+ /*
|
||||
+ * In the XENMAPSPACE_gmfn case, we took a ref of the gfn at the top.
|
||||
+ * We also may need to transfer ownership of the page reference to our
|
||||
+ * caller.
|
||||
+ */
|
||||
if ( space == XENMAPSPACE_gmfn )
|
||||
+ {
|
||||
put_gfn(d, gfn);
|
||||
+ if ( !rc && extra.ppage )
|
||||
+ {
|
||||
+ *extra.ppage = page;
|
||||
+ page = NULL;
|
||||
+ }
|
||||
+ }
|
||||
|
||||
if ( page )
|
||||
put_page(page);
|
||||
--- a/xen/common/memory.c
|
||||
+++ b/xen/common/memory.c
|
||||
@@ -824,11 +824,10 @@ int xenmem_add_to_physmap(struct domain
|
||||
{
|
||||
unsigned int done = 0;
|
||||
long rc = 0;
|
||||
- union xen_add_to_physmap_batch_extra extra;
|
||||
+ union add_to_physmap_extra extra = {};
|
||||
+ struct page_info *pages[16];
|
||||
|
||||
- if ( xatp->space != XENMAPSPACE_gmfn_foreign )
|
||||
- extra.res0 = 0;
|
||||
- else
|
||||
+ if ( xatp->space == XENMAPSPACE_gmfn_foreign )
|
||||
extra.foreign_domid = DOMID_INVALID;
|
||||
|
||||
if ( xatp->space != XENMAPSPACE_gmfn_range )
|
||||
@@ -843,7 +842,10 @@ int xenmem_add_to_physmap(struct domain
|
||||
xatp->size -= start;
|
||||
|
||||
if ( has_iommu_pt(d) )
|
||||
+ {
|
||||
this_cpu(iommu_dont_flush_iotlb) = 1;
|
||||
+ extra.ppage = &pages[0];
|
||||
+ }
|
||||
|
||||
while ( xatp->size > done )
|
||||
{
|
||||
@@ -855,8 +857,12 @@ int xenmem_add_to_physmap(struct domain
|
||||
xatp->idx++;
|
||||
xatp->gpfn++;
|
||||
|
||||
+ if ( extra.ppage )
|
||||
+ ++extra.ppage;
|
||||
+
|
||||
/* Check for continuation if it's not the last iteration. */
|
||||
- if ( xatp->size > ++done && hypercall_preempt_check() )
|
||||
+ if ( (++done > ARRAY_SIZE(pages) && extra.ppage) ||
|
||||
+ (xatp->size > done && hypercall_preempt_check()) )
|
||||
{
|
||||
rc = start + done;
|
||||
break;
|
||||
@@ -866,6 +872,7 @@ int xenmem_add_to_physmap(struct domain
|
||||
if ( has_iommu_pt(d) )
|
||||
{
|
||||
int ret;
|
||||
+ unsigned int i;
|
||||
|
||||
this_cpu(iommu_dont_flush_iotlb) = 0;
|
||||
|
||||
@@ -874,6 +881,15 @@ int xenmem_add_to_physmap(struct domain
|
||||
if ( unlikely(ret) && rc >= 0 )
|
||||
rc = ret;
|
||||
|
||||
+ /*
|
||||
+ * Now that the IOMMU TLB flush was done for the original GFN, drop
|
||||
+ * the page references. The 2nd flush below is fine to make later, as
|
||||
+ * whoever removes the page again from its new GFN will have to do
|
||||
+ * another flush anyway.
|
||||
+ */
|
||||
+ for ( i = 0; i < done; ++i )
|
||||
+ put_page(pages[i]);
|
||||
+
|
||||
ret = iommu_iotlb_flush(d, _dfn(xatp->gpfn - done), done,
|
||||
IOMMU_FLUSHF_added | IOMMU_FLUSHF_modified);
|
||||
if ( unlikely(ret) && rc >= 0 )
|
||||
@@ -887,6 +903,8 @@ static int xenmem_add_to_physmap_batch(s
|
||||
struct xen_add_to_physmap_batch *xatpb,
|
||||
unsigned int extent)
|
||||
{
|
||||
+ union add_to_physmap_extra extra = {};
|
||||
+
|
||||
if ( xatpb->size < extent )
|
||||
return -EILSEQ;
|
||||
|
||||
@@ -895,6 +913,19 @@ static int xenmem_add_to_physmap_batch(s
|
||||
!guest_handle_subrange_okay(xatpb->errs, extent, xatpb->size - 1) )
|
||||
return -EFAULT;
|
||||
|
||||
+ switch ( xatpb->space )
|
||||
+ {
|
||||
+ case XENMAPSPACE_dev_mmio:
|
||||
+ /* res0 is reserved for future use. */
|
||||
+ if ( xatpb->u.res0 )
|
||||
+ return -EOPNOTSUPP;
|
||||
+ break;
|
||||
+
|
||||
+ case XENMAPSPACE_gmfn_foreign:
|
||||
+ extra.foreign_domid = xatpb->u.foreign_domid;
|
||||
+ break;
|
||||
+ }
|
||||
+
|
||||
while ( xatpb->size > extent )
|
||||
{
|
||||
xen_ulong_t idx;
|
||||
@@ -907,8 +938,7 @@ static int xenmem_add_to_physmap_batch(s
|
||||
extent, 1)) )
|
||||
return -EFAULT;
|
||||
|
||||
- rc = xenmem_add_to_physmap_one(d, xatpb->space,
|
||||
- xatpb->u,
|
||||
+ rc = xenmem_add_to_physmap_one(d, xatpb->space, extra,
|
||||
idx, _gfn(gpfn));
|
||||
|
||||
if ( unlikely(__copy_to_guest_offset(xatpb->errs, extent, &rc, 1)) )
|
||||
--- a/xen/include/xen/mm.h
|
||||
+++ b/xen/include/xen/mm.h
|
||||
@@ -583,8 +583,22 @@ void scrub_one_page(struct page_info *);
|
||||
&(d)->xenpage_list : &(d)->page_list)
|
||||
#endif
|
||||
|
||||
+union add_to_physmap_extra {
|
||||
+ /*
|
||||
+ * XENMAPSPACE_gmfn: When deferring TLB flushes, a page reference needs
|
||||
+ * to be kept until after the flush, so the page can't get removed from
|
||||
+ * the domain (and re-used for another purpose) beforehand. By passing
|
||||
+ * non-NULL, the caller of xenmem_add_to_physmap_one() indicates it wants
|
||||
+ * to have ownership of such a reference transferred in the success case.
|
||||
+ */
|
||||
+ struct page_info **ppage;
|
||||
+
|
||||
+ /* XENMAPSPACE_gmfn_foreign */
|
||||
+ domid_t foreign_domid;
|
||||
+};
|
||||
+
|
||||
int xenmem_add_to_physmap_one(struct domain *d, unsigned int space,
|
||||
- union xen_add_to_physmap_batch_extra extra,
|
||||
+ union add_to_physmap_extra extra,
|
||||
unsigned long idx, gfn_t gfn);
|
||||
|
||||
int xenmem_add_to_physmap(struct domain *d, struct xen_add_to_physmap *xatp,
|
||||
52
xsa347-4.12-1.patch
Normal file
52
xsa347-4.12-1.patch
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
From: Jan Beulich <jbeulich@suse.com>
|
||||
Subject: AMD/IOMMU: update live PTEs atomically
|
||||
|
||||
Updating a live PTE word by word allows the IOMMU to see a partially
|
||||
updated entry. Construct the new entry fully in a local variable and
|
||||
then write the new entry by a single insn.
|
||||
|
||||
This is part of XSA-347.
|
||||
|
||||
Signed-off-by: Jan Beulich <jbeulich@suse.com>
|
||||
Reviewed-by: Paul Durrant <paul@xen.org>
|
||||
|
||||
--- a/xen/drivers/passthrough/amd/iommu_map.c
|
||||
+++ b/xen/drivers/passthrough/amd/iommu_map.c
|
||||
@@ -49,7 +49,7 @@ static unsigned int clear_iommu_pte_pres
|
||||
IOMMU_PTE_PRESENT_SHIFT) ?
|
||||
IOMMU_FLUSHF_modified : 0;
|
||||
|
||||
- *pte = 0;
|
||||
+ write_atomic(pte, 0);
|
||||
unmap_domain_page(table);
|
||||
|
||||
return flush_flags;
|
||||
@@ -60,7 +60,7 @@ static unsigned int set_iommu_pde_presen
|
||||
unsigned int next_level, bool iw,
|
||||
bool ir)
|
||||
{
|
||||
- uint64_t maddr_next;
|
||||
+ uint64_t maddr_next, full;
|
||||
uint32_t addr_lo, addr_hi, entry;
|
||||
bool old_present;
|
||||
unsigned int flush_flags = IOMMU_FLUSHF_added;
|
||||
@@ -119,7 +119,7 @@ static unsigned int set_iommu_pde_presen
|
||||
if ( next_level == 0 )
|
||||
set_field_in_reg_u32(IOMMU_CONTROL_ENABLED, entry,
|
||||
IOMMU_PTE_FC_MASK, IOMMU_PTE_FC_SHIFT, &entry);
|
||||
- pde[1] = entry;
|
||||
+ full = (uint64_t)entry << 32;
|
||||
|
||||
/* mark next level as 'present' */
|
||||
set_field_in_reg_u32(addr_lo >> PAGE_SHIFT, 0,
|
||||
@@ -131,7 +131,9 @@ static unsigned int set_iommu_pde_presen
|
||||
set_field_in_reg_u32(IOMMU_CONTROL_ENABLED, entry,
|
||||
IOMMU_PDE_PRESENT_MASK,
|
||||
IOMMU_PDE_PRESENT_SHIFT, &entry);
|
||||
- pde[0] = entry;
|
||||
+ full |= entry;
|
||||
+
|
||||
+ write_atomic((uint64_t *)pde, full);
|
||||
|
||||
return flush_flags;
|
||||
}
|
||||
80
xsa347-4.12-2.patch
Normal file
80
xsa347-4.12-2.patch
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
From: Jan Beulich <jbeulich@suse.com>
|
||||
Subject: AMD/IOMMU: ensure suitable ordering of DTE modifications
|
||||
|
||||
DMA and interrupt translation should be enabled only after other
|
||||
applicable DTE fields have been written. Similarly when disabling
|
||||
translation or when moving a device between domains, translation should
|
||||
first be disabled, before other entry fields get modified. Note however
|
||||
that the "moving" aspect doesn't apply to the interrupt remapping side,
|
||||
as domain specifics are maintained in the IRTEs here, not the DTE. We
|
||||
also never disable interrupt remapping once it got enabled for a device
|
||||
(the respective argument passed is always the immutable iommu_intremap).
|
||||
|
||||
This is part of XSA-347.
|
||||
|
||||
Signed-off-by: Jan Beulich <jbeulich@suse.com>
|
||||
Reviewed-by: Paul Durrant <paul@xen.org>
|
||||
|
||||
--- a/xen/drivers/passthrough/amd/iommu_map.c
|
||||
+++ b/xen/drivers/passthrough/amd/iommu_map.c
|
||||
@@ -162,7 +162,22 @@ void amd_iommu_set_root_page_table(uint3
|
||||
uint16_t domain_id, uint8_t paging_mode,
|
||||
uint8_t valid)
|
||||
{
|
||||
- uint32_t addr_hi, addr_lo, entry;
|
||||
+ uint32_t addr_hi, addr_lo, entry, dte0 = dte[0];
|
||||
+
|
||||
+ if ( valid ||
|
||||
+ get_field_from_reg_u32(dte0, IOMMU_DEV_TABLE_VALID_MASK,
|
||||
+ IOMMU_DEV_TABLE_VALID_SHIFT) )
|
||||
+ {
|
||||
+ set_field_in_reg_u32(IOMMU_CONTROL_DISABLED, dte0,
|
||||
+ IOMMU_DEV_TABLE_TRANSLATION_VALID_MASK,
|
||||
+ IOMMU_DEV_TABLE_TRANSLATION_VALID_SHIFT, &dte0);
|
||||
+ set_field_in_reg_u32(IOMMU_CONTROL_ENABLED, dte0,
|
||||
+ IOMMU_DEV_TABLE_VALID_MASK,
|
||||
+ IOMMU_DEV_TABLE_VALID_SHIFT, &dte0);
|
||||
+ dte[0] = dte0;
|
||||
+ smp_wmb();
|
||||
+ }
|
||||
+
|
||||
set_field_in_reg_u32(domain_id, 0,
|
||||
IOMMU_DEV_TABLE_DOMAIN_ID_MASK,
|
||||
IOMMU_DEV_TABLE_DOMAIN_ID_SHIFT, &entry);
|
||||
@@ -181,8 +196,9 @@ void amd_iommu_set_root_page_table(uint3
|
||||
IOMMU_DEV_TABLE_IO_READ_PERMISSION_MASK,
|
||||
IOMMU_DEV_TABLE_IO_READ_PERMISSION_SHIFT, &entry);
|
||||
dte[1] = entry;
|
||||
+ smp_wmb();
|
||||
|
||||
- set_field_in_reg_u32(addr_lo >> PAGE_SHIFT, 0,
|
||||
+ set_field_in_reg_u32(addr_lo >> PAGE_SHIFT, dte0,
|
||||
IOMMU_DEV_TABLE_PAGE_TABLE_PTR_LOW_MASK,
|
||||
IOMMU_DEV_TABLE_PAGE_TABLE_PTR_LOW_SHIFT, &entry);
|
||||
set_field_in_reg_u32(paging_mode, entry,
|
||||
@@ -195,7 +211,7 @@ void amd_iommu_set_root_page_table(uint3
|
||||
IOMMU_CONTROL_DISABLED, entry,
|
||||
IOMMU_DEV_TABLE_VALID_MASK,
|
||||
IOMMU_DEV_TABLE_VALID_SHIFT, &entry);
|
||||
- dte[0] = entry;
|
||||
+ write_atomic(&dte[0], entry);
|
||||
}
|
||||
|
||||
void iommu_dte_set_iotlb(uint32_t *dte, uint8_t i)
|
||||
@@ -226,6 +242,7 @@ void __init amd_iommu_set_intremap_table
|
||||
IOMMU_DEV_TABLE_INT_CONTROL_MASK,
|
||||
IOMMU_DEV_TABLE_INT_CONTROL_SHIFT, &entry);
|
||||
dte[5] = entry;
|
||||
+ smp_wmb();
|
||||
|
||||
set_field_in_reg_u32(addr_lo >> 6, 0,
|
||||
IOMMU_DEV_TABLE_INT_TABLE_PTR_LOW_MASK,
|
||||
@@ -243,7 +260,7 @@ void __init amd_iommu_set_intremap_table
|
||||
IOMMU_CONTROL_DISABLED, entry,
|
||||
IOMMU_DEV_TABLE_INT_VALID_MASK,
|
||||
IOMMU_DEV_TABLE_INT_VALID_SHIFT, &entry);
|
||||
- dte[4] = entry;
|
||||
+ write_atomic(&dte[4], entry);
|
||||
}
|
||||
|
||||
void __init iommu_dte_add_device_entry(uint32_t *dte,
|
||||
Loading…
Add table
Add a link
Reference in a new issue