Xen Security Advisory 287 v3 (CVE-2019-17342) - x86: steal_page violates page_struct access discipline
Xen.org security team <[email protected]>
| Newsgroups | gmane.comp.emulators.xen.announce |
|---|---|
| Message-ID | <E1iNxUH-0002hA-3u__46115.042925679$1572002064$gmane$org@xenbits.xenproject.org> |
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256
Xen Security Advisory CVE-2019-17342 / XSA-287
version 3
x86: steal_page violates page_struct access discipline
UPDATES IN VERSION 3
====================
CVE assigned.
ISSUE DESCRIPTION
=================
Xen's reference counting rules were designed to allow pages to change
owner and state without requiring a global lock. Each page has a page
structure, and a very specific set of access disciplines must be
observed to ensure that pages are freed properly, and that no writable
mappings exist for PV pagetable pages.
Unfortunately, when the XENMEM_exchange hypercall was introduced,
these access disciplines were violated, opening up several potential
race conditions.
IMPACT
======
A single PV guest can leak arbitrary amounts of memory, leading to a
denial of service.
A cooperating pair of PV and HVM/PVH guests can get a writable
pagetable entry, leading to information disclosure or privilege
escalation.
Privilege escalation attacks using only a single PV guest or a pair of
PV guests have not been ruled out.
Note that both of these attacks require very precise timing, which may
be difficult to exploit in practice.
VULNERABLE SYSTEMS
==================
Only x86 systems are vulnerable.
Only systems which run PV guests are vulnerable. Systems which run
only HVM/PVH guests are not vulnerable.
MITIGATION
==========
Running only HVM or PVH guests will avoid these vulnerabilities.
CREDITS
=======
This issue was discovered by Jan Beulich of SUSE.
RESOLUTION
==========
Applying the appropriate attached patch resolves this issue.
xsa287.patch xen-unstable
xsa287-4.11.patch Xen 4.11.x
xsa287-4.10.patch Xen 4.10.x
xsa287-4.9.patch Xen 4.9.x
xsa287-4.8.patch Xen 4.8.x
xsa287-4.7.patch Xen 4.7.x
$ sha256sum xsa287*
ae2b9261e26df871693478629c63970ba30817ee1dcb2266b89d8b067833c1b3 xsa287.meta
7de1b886d69dd7c497f88d41adf9a6f7cf9a305fd8ae9d714e1125e2a22208ab xsa287.patch
55f40f2f9bb41c85ac80dac775352e28b25fada80dae574e9d10300d5e2b91ce xsa287-4.7.patch
57312ff131eb6b51235723e862adf42ad3529ed13135375875c054fa0b55f80b xsa287-4.8.patch
34f4b835766a38bcf4066ccbab74676eda176e15ed2a6bd7884678a64507f89a xsa287-4.9.patch
c7eaf8a325011dda84b02ee097ddbc7b5f2f4d3399de545a3a7b14e2d23f4278 xsa287-4.10.patch
6793315f714a249a4fad12b36559640b2f97f19f5b85f0d58694c6e78aa3d567 xsa287-4.11.patch
$
DEPLOYMENT DURING EMBARGO
=========================
Deployment of the patches and/or mitigations described above (or
others which are substantially similar) is permitted during the
embargo, even on public-facing systems with untrusted guest users and
administrators.
But: Distribution of updated software is prohibited (except to other
members of the predisclosure list).
Predisclosure list members who wish to deploy significantly different
patches and/or mitigations, please contact the Xen Project Security
Team.
(Note: this during-embargo deployment notice is retained in
post-embargo publicly released Xen Project advisories, even though it
is then no longer applicable. This is to enable the community to have
oversight of the Xen Project Security Team's decisionmaking.)
For more information about permissible uses of embargoed information,
consult the Xen Project community's agreed Security Policy:
http://www.xenproject.org/security-policy.html
-----BEGIN PGP SIGNATURE-----
iQFABAEBCAAqFiEEI+MiLBRfRHX6gGCng/4UyVfoK9kFAl2y18cMHHBncEB4ZW4u
b3JnAAoJEIP+FMlX6CvZMbcIAKcMpCX29ANW9/W2cnGremzicicGAQW9KvmZVK5e
weLBItv9pTqIGeVm71/X2dXt5KeRryh+Py53zYtUhy4pFQXQAezEzlRs+Y4TtX3l
+XVsfDFqks+bfyduBKMerwJpqr2Hd3DOdvir8iSqH2jHLLd5JqTYho+m0L0HPD9J
Smn43rwurMChSjSFR4H+TnrOcX/1iUWgj3BVUkswGn3CrUdBJFe5mp6QeoYlyiL1
CN6rmx5+CWLvBTwMkEiA8/3GX322qv4f2P0woOnaFW+aNgj1VRcyB2l1V0ParYYw
0Yfj32XNIhdzNfUanenRAUNnTYSzVFFdbTMgV2sgwZjXNgE=
=7jA5
-----END PGP SIGNATURE-----
_______________________________________________
Xen-announce mailing list
[email protected]
https://lists.xenproject.org/mailman/listinfo/xen-announce
xsa287.meta
(application/octet-stream, 1.8 KB) - not displayed
xsa287.patch
(application/octet-stream, 11.5 KB)
From c0b89c3d3a58b7c88532b36b9d1277c274554313 Mon Sep 17 00:00:00 2001 From: George Dunlap <[email protected]> Date: Fri, 18 Jan 2019 15:00:34 +0000 Subject: [PATCH] steal_page: Get rid of bogus struct page states The original rules for `struct page` required the following invariants at all times: - refcount > 0 implies owner != NULL - PGC_allocated implies refcount > 0 steal_page, in a misguided attempt to protect against unknown races, violates both of these rules, thus introducing other races: - Temporarily, the count_info has the refcount go to 0 while PGC_allocated is set - It explicitly returns the page PGC_allocated set, but owner == NULL and page not on the page_list. The second one meant that page_get_owner_and_reference() could return NULL even after having successfully grabbed a reference on the page, leading the caller to leak the reference (since "couldn't get ref" and "got ref but no owner" look the same). Furthermore, rather than grabbing a page reference to ensure that the owner doesn't change under its feet, it appears to rely on holding d->page_alloc lock to prevent this. Unfortunately, this is ineffective: page->owner remains non-NULL for some time after the count has been set to 0; meaning that it would be entirely possible for the page to be freed and re-allocated to a different domain between the page_get_owner() check and the count_info check. Modify steal_page to instead follow the appropriate access discipline, taking the page through series of states similar to being freed and then re-allocated with MEMF_no_owner: - Grab an extra reference to make sure we don't race with anyone else freeing the page - Drop both references and PGC_allocated atomically, so that (if successful), anyone else trying to grab a reference will fail - Attempt to reset Xen's mappings - Reset the rest of the state. Then, modify the two callers appropriately: - Leave count_info alone (it's already been cleared) - Call free_domheap_page() directly if appropriate - Call assign_pages() rather than open-coding a partial assign With all callers to assign_pages() now passing in pages with the type_info field clear, tighten the respective assertion there. This is XSA-287. Signed-off-by: George Dunlap <[email protected]> Signed-off-by: Jan Beulich <[email protected]> --- xen/arch/x86/mm.c | 84 ++++++++++++++++++++++++++++------------ xen/common/grant_table.c | 20 +++++----- xen/common/memory.c | 19 +++++---- xen/common/page_alloc.c | 2 +- 4 files changed, 83 insertions(+), 42 deletions(-) diff --git a/xen/arch/x86/mm.c b/xen/arch/x86/mm.c index 7ec5954b03..4d3b17f3a8 100644 --- a/xen/arch/x86/mm.c +++ b/xen/arch/x86/mm.c @@ -3989,70 +3989,106 @@ int donate_page( return -EINVAL; } +/* + * Steal page will attempt to remove `page` from domain `d`. Upon + * return, `page` will be in a state similar to the state of a page + * returned from alloc_domheap_page() with MEMF_no_owner set: + * - refcount 0 + * - type count cleared + * - owner NULL + * - page caching attributes cleaned up + * - removed from the domain's page_list + * + * If MEMF_no_refcount is not set, the domain's tot_pages will be + * adjusted. If this results in the page count falling to 0, + * put_domain() will be called. + * + * The caller should either call free_domheap_page() to free the + * page, or assign_pages() to put it back on some domain's page list. + */ int steal_page( struct domain *d, struct page_info *page, unsigned int memflags) { unsigned long x, y; bool drop_dom_ref = false; - const struct domain *owner = dom_xen; + const struct domain *owner; + int rc; if ( paging_mode_external(d) ) return -EOPNOTSUPP; - spin_lock(&d->page_alloc_lock); - - if ( is_xen_heap_page(page) || ((owner = page_get_owner(page)) != d) ) + /* Grab a reference to make sure the page doesn't change under our feet */ + rc = -EINVAL; + if ( !(owner = page_get_owner_and_reference(page)) ) goto fail; + if ( owner != d || is_xen_heap_page(page) ) + goto fail_put; + /* - * We require there is just one reference (PGC_allocated). We temporarily - * drop this reference now so that we can safely swizzle the owner. + * We require there are exactly two references -- the one we just + * took, and PGC_allocated. We temporarily drop both these + * references so that the page becomes effectively non-"live" for + * the domain. */ y = page->count_info; do { x = y; - if ( (x & (PGC_count_mask|PGC_allocated)) != (1 | PGC_allocated) ) - goto fail; - y = cmpxchg(&page->count_info, x, x & ~PGC_count_mask); + if ( (x & (PGC_count_mask|PGC_allocated)) != (2 | PGC_allocated) ) + goto fail_put; + y = cmpxchg(&page->count_info, x, x & ~(PGC_count_mask|PGC_allocated)); } while ( y != x ); /* - * With the sole reference dropped temporarily, no-one can update type - * information. Type count also needs to be zero in this case, but e.g. - * PGT_seg_desc_page may still have PGT_validated set, which we need to - * clear before transferring ownership (as validation criteria vary - * depending on domain type). + * NB this is safe even if the page ends up being given back to + * the domain, because the count is zero: subsequent mappings will + * cause the cache attributes to be re-instated inside + * get_page_from_l1e(). + */ + if ( (rc = cleanup_page_cacheattr(page)) ) + { + /* + * Couldn't fixup Xen's mappings; put things the way we found + * it and return an error + */ + page->count_info |= PGC_allocated | 1; + goto fail; + } + + /* + * With the reference count now zero, nobody can grab references + * to do anything else with the page. Return the page to a state + * that it might be upon return from alloc_domheap_pages with + * MEMF_no_owner set. */ + spin_lock(&d->page_alloc_lock); + BUG_ON(page->u.inuse.type_info & (PGT_count_mask | PGT_locked | PGT_pinned)); page->u.inuse.type_info = 0; - - /* Swizzle the owner then reinstate the PGC_allocated reference. */ page_set_owner(page, NULL); - y = page->count_info; - do { - x = y; - BUG_ON((x & (PGC_count_mask|PGC_allocated)) != PGC_allocated); - } while ( (y = cmpxchg(&page->count_info, x, x | 1)) != x ); + page_list_del(page, &d->page_list); /* Unlink from original owner. */ if ( !(memflags & MEMF_no_refcount) && !domain_adjust_tot_pages(d, -1) ) drop_dom_ref = true; - page_list_del(page, &d->page_list); spin_unlock(&d->page_alloc_lock); + if ( unlikely(drop_dom_ref) ) put_domain(d); + return 0; + fail_put: + put_page(page); fail: - spin_unlock(&d->page_alloc_lock); gdprintk(XENLOG_WARNING, "Bad steal mfn %" PRI_mfn " from d%d (owner d%d) caf=%08lx taf=%" PRtype_info "\n", mfn_x(page_to_mfn(page)), d->domain_id, owner ? owner->domain_id : DOMID_INVALID, page->count_info, page->u.inuse.type_info); - return -EINVAL; + return rc; } #ifdef CONFIG_PV diff --git a/xen/common/grant_table.c b/xen/common/grant_table.c index db22d6b441..80728ea57d 100644 --- a/xen/common/grant_table.c +++ b/xen/common/grant_table.c @@ -2157,7 +2157,7 @@ gnttab_transfer( #ifdef CONFIG_X86 put_gfn(d, gop.mfn); #endif - page->count_info &= ~(PGC_count_mask|PGC_allocated); + /* The count_info has already been cleaned */ free_domheap_page(page); goto copyback; } @@ -2180,10 +2180,9 @@ gnttab_transfer( copy_domain_page(page_to_mfn(new_page), mfn); - page->count_info &= ~(PGC_count_mask|PGC_allocated); + /* The count_info has already been cleared */ free_domheap_page(page); page = new_page; - page->count_info = PGC_allocated | 1; mfn = page_to_mfn(page); } @@ -2223,12 +2222,17 @@ gnttab_transfer( */ spin_unlock(&e->page_alloc_lock); okay = gnttab_prepare_for_transfer(e, d, gop.ref); - spin_lock(&e->page_alloc_lock); - if ( unlikely(!okay) || unlikely(e->is_dying) ) + if ( unlikely(!okay || assign_pages(e, page, 0, MEMF_no_refcount)) ) { - bool_t drop_dom_ref = !domain_adjust_tot_pages(e, -1); + bool drop_dom_ref; + /* + * Need to grab this again to safely free our "reserved" + * page in the page total + */ + spin_lock(&e->page_alloc_lock); + drop_dom_ref = !domain_adjust_tot_pages(e, -1); spin_unlock(&e->page_alloc_lock); if ( okay /* i.e. e->is_dying due to the surrounding if() */ ) @@ -2241,10 +2245,6 @@ gnttab_transfer( goto unlock_and_copyback; } - page_list_add_tail(page, &e->page_list); - page_set_owner(page, e); - - spin_unlock(&e->page_alloc_lock); #ifdef CONFIG_X86 put_gfn(d, gop.mfn); #endif diff --git a/xen/common/memory.c b/xen/common/memory.c index b6cf09585c..5387769987 100644 --- a/xen/common/memory.c +++ b/xen/common/memory.c @@ -694,20 +694,22 @@ static long memory_exchange(XEN_GUEST_HANDLE_PARAM(xen_memory_exchange_t) arg) * Success! Beyond this point we cannot fail for this chunk. */ - /* Destroy final reference to each input page. */ + /* + * These pages have already had owner and reference cleared. + * Do the final two steps: Remove from the physmap, and free + * them. + */ while ( (page = page_list_remove_head(&in_chunk_list)) ) { unsigned long gfn; - if ( !test_and_clear_bit(_PGC_allocated, &page->count_info) ) - BUG(); mfn = page_to_mfn(page); gfn = mfn_to_gmfn(d, mfn_x(mfn)); /* Pages were unshared above */ BUG_ON(SHARED_M2P(gfn)); if ( guest_physmap_remove_page(d, _gfn(gfn), mfn, 0) ) domain_crash(d); - put_page(page); + free_domheap_page(page); } /* Assign each output page to the domain. */ @@ -780,13 +782,16 @@ static long memory_exchange(XEN_GUEST_HANDLE_PARAM(xen_memory_exchange_t) arg) * chunks succeeded. */ fail: - /* Reassign any input pages we managed to steal. */ + /* + * Reassign any input pages we managed to steal. NB that if the assign + * fails again, we're on the hook for freeing the page, since we've already + * cleared PGC_allocated. + */ while ( (page = page_list_remove_head(&in_chunk_list)) ) if ( assign_pages(d, page, 0, MEMF_no_refcount) ) { BUG_ON(!d->is_dying); - if ( test_and_clear_bit(_PGC_allocated, &page->count_info) ) - put_page(page); + free_domheap_page(page); } dying: diff --git a/xen/common/page_alloc.c b/xen/common/page_alloc.c index f71d3bb7a1..be44158033 100644 --- a/xen/common/page_alloc.c +++ b/xen/common/page_alloc.c @@ -2279,7 +2279,7 @@ int assign_pages( for ( i = 0; i < (1 << order); i++ ) { ASSERT(page_get_owner(&pg[i]) == NULL); - ASSERT((pg[i].count_info & ~(PGC_allocated | 1)) == 0); + ASSERT(!pg[i].count_info); page_set_owner(&pg[i], d); smp_wmb(); /* Domain pointer must be visible before updating refcnt. */ pg[i].count_info = PGC_allocated | 1; -- 2.20.1
xsa287-4.7.patch
(application/octet-stream, 11.6 KB)
From 6f59bce0081e60912265f6e27343ed55a9c3068d Mon Sep 17 00:00:00 2001 From: George Dunlap <[email protected]> Date: Fri, 18 Jan 2019 15:00:34 +0000 Subject: [PATCH] steal_page: Get rid of bogus struct page states The original rules for `struct page` required the following invariants at all times: - refcount > 0 implies owner != NULL - PGC_allocated implies refcount > 0 steal_page, in a misguided attempt to protect against unknown races, violates both of these rules, thus introducing other races: - Temporarily, the count_info has the refcount go to 0 while PGC_allocated is set - It explicitly returns the page PGC_allocated set, but owner == NULL and page not on the page_list. The second one meant that page_get_owner_and_reference() could return NULL even after having successfully grabbed a reference on the page, leading the caller to leak the reference (since "couldn't get ref" and "got ref but no owner" look the same). Furthermore, rather than grabbing a page reference to ensure that the owner doesn't change under its feet, it appears to rely on holding d->page_alloc lock to prevent this. Unfortunately, this is ineffective: page->owner remains non-NULL for some time after the count has been set to 0; meaning that it would be entirely possible for the page to be freed and re-allocated to a different domain between the page_get_owner() check and the count_info check. Modify steal_page to instead follow the appropriate access discipline, taking the page through series of states similar to being freed and then re-allocated with MEMF_no_owner: - Grab an extra reference to make sure we don't race with anyone else freeing the page - Drop both references and PGC_allocated atomically, so that (if successful), anyone else trying to grab a reference will fail - Attempt to reset Xen's mappings - Reset the rest of the state. Then, modify the two callers appropriately: - Leave count_info alone (it's already been cleared) - Call free_domheap_page() directly if appropriate - Call assign_pages() rather than open-coding a partial assign With all callers to assign_pages() now passing in pages with the type_info field clear, tighten the respective assertion there. This is XSA-287. Signed-off-by: George Dunlap <[email protected]> Signed-off-by: Jan Beulich <[email protected]> Backport: Return -EOPNOTSUPP in paging_mode_external() for consistency. --- xen/arch/x86/mm.c | 86 ++++++++++++++++++++++++++++------------ xen/common/grant_table.c | 20 +++++----- xen/common/memory.c | 19 +++++---- xen/common/page_alloc.c | 2 +- 4 files changed, 84 insertions(+), 43 deletions(-) diff --git a/xen/arch/x86/mm.c b/xen/arch/x86/mm.c index c9c6fc9dc8..47f4aa4f6a 100644 --- a/xen/arch/x86/mm.c +++ b/xen/arch/x86/mm.c @@ -4717,69 +4717,105 @@ int donate_page( return -1; } +/* + * Steal page will attempt to remove `page` from domain `d`. Upon + * return, `page` will be in a state similar to the state of a page + * returned from alloc_domheap_page() with MEMF_no_owner set: + * - refcount 0 + * - type count cleared + * - owner NULL + * - page caching attributes cleaned up + * - removed from the domain's page_list + * + * If MEMF_no_refcount is not set, the domain's tot_pages will be + * adjusted. If this results in the page count falling to 0, + * put_domain() will be called. + * + * The caller should either call free_domheap_page() to free the + * page, or assign_pages() to put it back on some domain's page list. + */ int steal_page( struct domain *d, struct page_info *page, unsigned int memflags) { unsigned long x, y; bool_t drop_dom_ref = 0; - const struct domain *owner = dom_xen; + const struct domain *owner; + int rc; if ( paging_mode_external(d) ) - return -1; - - spin_lock(&d->page_alloc_lock); + return -EOPNOTSUPP; - if ( is_xen_heap_page(page) || ((owner = page_get_owner(page)) != d) ) + /* Grab a reference to make sure the page doesn't change under our feet */ + rc = -EINVAL; + if ( !(owner = page_get_owner_and_reference(page)) ) goto fail; + if ( owner != d || is_xen_heap_page(page) ) + goto fail_put; + /* - * We require there is just one reference (PGC_allocated). We temporarily - * drop this reference now so that we can safely swizzle the owner. + * We require there are exactly two references -- the one we just + * took, and PGC_allocated. We temporarily drop both these + * references so that the page becomes effectively non-"live" for + * the domain. */ y = page->count_info; do { x = y; - if ( (x & (PGC_count_mask|PGC_allocated)) != (1 | PGC_allocated) ) - goto fail; - y = cmpxchg(&page->count_info, x, x & ~PGC_count_mask); + if ( (x & (PGC_count_mask|PGC_allocated)) != (2 | PGC_allocated) ) + goto fail_put; + y = cmpxchg(&page->count_info, x, x & ~(PGC_count_mask|PGC_allocated)); } while ( y != x ); /* - * With the sole reference dropped temporarily, no-one can update type - * information. Type count also needs to be zero in this case, but e.g. - * PGT_seg_desc_page may still have PGT_validated set, which we need to - * clear before transferring ownership (as validation criteria vary - * depending on domain type). + * NB this is safe even if the page ends up being given back to + * the domain, because the count is zero: subsequent mappings will + * cause the cache attributes to be re-instated inside + * get_page_from_l1e(). */ + if ( (rc = cleanup_page_cacheattr(page)) ) + { + /* + * Couldn't fixup Xen's mappings; put things the way we found + * it and return an error + */ + page->count_info |= PGC_allocated | 1; + goto fail; + } + + /* + * With the reference count now zero, nobody can grab references + * to do anything else with the page. Return the page to a state + * that it might be upon return from alloc_domheap_pages with + * MEMF_no_owner set. + */ + spin_lock(&d->page_alloc_lock); + BUG_ON(page->u.inuse.type_info & (PGT_count_mask | PGT_locked | PGT_pinned)); page->u.inuse.type_info = 0; - - /* Swizzle the owner then reinstate the PGC_allocated reference. */ page_set_owner(page, NULL); - y = page->count_info; - do { - x = y; - BUG_ON((x & (PGC_count_mask|PGC_allocated)) != PGC_allocated); - } while ( (y = cmpxchg(&page->count_info, x, x | 1)) != x ); + page_list_del(page, &d->page_list); /* Unlink from original owner. */ if ( !(memflags & MEMF_no_refcount) && !domain_adjust_tot_pages(d, -1) ) drop_dom_ref = 1; - page_list_del(page, &d->page_list); spin_unlock(&d->page_alloc_lock); + if ( unlikely(drop_dom_ref) ) put_domain(d); + return 0; + fail_put: + put_page(page); fail: - spin_unlock(&d->page_alloc_lock); MEM_LOG("Bad page %lx: ed=%d sd=%d caf=%08lx taf=%" PRtype_info, page_to_mfn(page), d->domain_id, owner ? owner->domain_id : DOMID_INVALID, page->count_info, page->u.inuse.type_info); - return -1; + return rc; } static int __do_update_va_mapping( diff --git a/xen/common/grant_table.c b/xen/common/grant_table.c index ad44b96303..709e3871f5 100644 --- a/xen/common/grant_table.c +++ b/xen/common/grant_table.c @@ -1985,7 +1985,7 @@ gnttab_transfer( rcu_unlock_domain(e); put_gfn_and_copyback: put_gfn(d, gop.mfn); - page->count_info &= ~(PGC_count_mask|PGC_allocated); + /* The count_info has already been cleaned */ free_domheap_page(page); goto copyback; } @@ -2008,10 +2008,9 @@ gnttab_transfer( copy_domain_page(_mfn(page_to_mfn(new_page)), _mfn(mfn)); - page->count_info &= ~(PGC_count_mask|PGC_allocated); + /* The count_info has already been cleared */ free_domheap_page(page); page = new_page; - page->count_info = PGC_allocated | 1; mfn = page_to_mfn(page); } @@ -2051,12 +2050,17 @@ gnttab_transfer( */ spin_unlock(&e->page_alloc_lock); okay = gnttab_prepare_for_transfer(e, d, gop.ref); - spin_lock(&e->page_alloc_lock); - if ( unlikely(!okay) || unlikely(e->is_dying) ) + if ( unlikely(!okay || assign_pages(e, page, 0, MEMF_no_refcount)) ) { - bool_t drop_dom_ref = !domain_adjust_tot_pages(e, -1); + bool_t drop_dom_ref; + /* + * Need to grab this again to safely free our "reserved" + * page in the page total + */ + spin_lock(&e->page_alloc_lock); + drop_dom_ref = !domain_adjust_tot_pages(e, -1); spin_unlock(&e->page_alloc_lock); if ( okay /* i.e. e->is_dying due to the surrounding if() */ ) @@ -2069,10 +2073,6 @@ gnttab_transfer( goto unlock_and_copyback; } - page_list_add_tail(page, &e->page_list); - page_set_owner(page, e); - - spin_unlock(&e->page_alloc_lock); put_gfn(d, gop.mfn); TRACE_1D(TRC_MEM_PAGE_GRANT_TRANSFER, e->domain_id); diff --git a/xen/common/memory.c b/xen/common/memory.c index 18ce62a705..9581f81902 100644 --- a/xen/common/memory.c +++ b/xen/common/memory.c @@ -589,20 +589,22 @@ static long memory_exchange(XEN_GUEST_HANDLE_PARAM(xen_memory_exchange_t) arg) * Success! Beyond this point we cannot fail for this chunk. */ - /* Destroy final reference to each input page. */ + /* + * These pages have already had owner and reference cleared. + * Do the final two steps: Remove from the physmap, and free + * them. + */ while ( (page = page_list_remove_head(&in_chunk_list)) ) { unsigned long gfn; - if ( !test_and_clear_bit(_PGC_allocated, &page->count_info) ) - BUG(); mfn = page_to_mfn(page); gfn = mfn_to_gmfn(d, mfn); /* Pages were unshared above */ BUG_ON(SHARED_M2P(gfn)); if ( guest_physmap_remove_page(d, gfn, mfn, 0) ) domain_crash(d); - put_page(page); + free_domheap_page(page); } /* Assign each output page to the domain. */ @@ -674,13 +676,16 @@ static long memory_exchange(XEN_GUEST_HANDLE_PARAM(xen_memory_exchange_t) arg) * chunks succeeded. */ fail: - /* Reassign any input pages we managed to steal. */ + /* + * Reassign any input pages we managed to steal. NB that if the assign + * fails again, we're on the hook for freeing the page, since we've already + * cleared PGC_allocated. + */ while ( (page = page_list_remove_head(&in_chunk_list)) ) if ( assign_pages(d, page, 0, MEMF_no_refcount) ) { BUG_ON(!d->is_dying); - if ( test_and_clear_bit(_PGC_allocated, &page->count_info) ) - put_page(page); + free_domheap_page(page); } dying: diff --git a/xen/common/page_alloc.c b/xen/common/page_alloc.c index d027819f7b..59fa5b974f 100644 --- a/xen/common/page_alloc.c +++ b/xen/common/page_alloc.c @@ -1780,7 +1780,7 @@ int assign_pages( for ( i = 0; i < (1 << order); i++ ) { ASSERT(page_get_owner(&pg[i]) == NULL); - ASSERT((pg[i].count_info & ~(PGC_allocated | 1)) == 0); + ASSERT(!pg[i].count_info); page_set_owner(&pg[i], d); smp_wmb(); /* Domain pointer must be visible before updating refcnt. */ pg[i].count_info = PGC_allocated | 1; -- 2.20.1
xsa287-4.8.patch
(application/octet-stream, 11.6 KB)
From d73323fed78e4bda11644b7aa9b902a75f3d705c Mon Sep 17 00:00:00 2001 From: George Dunlap <[email protected]> Date: Fri, 18 Jan 2019 15:00:34 +0000 Subject: [PATCH] steal_page: Get rid of bogus struct page states The original rules for `struct page` required the following invariants at all times: - refcount > 0 implies owner != NULL - PGC_allocated implies refcount > 0 steal_page, in a misguided attempt to protect against unknown races, violates both of these rules, thus introducing other races: - Temporarily, the count_info has the refcount go to 0 while PGC_allocated is set - It explicitly returns the page PGC_allocated set, but owner == NULL and page not on the page_list. The second one meant that page_get_owner_and_reference() could return NULL even after having successfully grabbed a reference on the page, leading the caller to leak the reference (since "couldn't get ref" and "got ref but no owner" look the same). Furthermore, rather than grabbing a page reference to ensure that the owner doesn't change under its feet, it appears to rely on holding d->page_alloc lock to prevent this. Unfortunately, this is ineffective: page->owner remains non-NULL for some time after the count has been set to 0; meaning that it would be entirely possible for the page to be freed and re-allocated to a different domain between the page_get_owner() check and the count_info check. Modify steal_page to instead follow the appropriate access discipline, taking the page through series of states similar to being freed and then re-allocated with MEMF_no_owner: - Grab an extra reference to make sure we don't race with anyone else freeing the page - Drop both references and PGC_allocated atomically, so that (if successful), anyone else trying to grab a reference will fail - Attempt to reset Xen's mappings - Reset the rest of the state. Then, modify the two callers appropriately: - Leave count_info alone (it's already been cleared) - Call free_domheap_page() directly if appropriate - Call assign_pages() rather than open-coding a partial assign With all callers to assign_pages() now passing in pages with the type_info field clear, tighten the respective assertion there. This is XSA-287. Signed-off-by: George Dunlap <[email protected]> Signed-off-by: Jan Beulich <[email protected]> Backport: Return -EOPNOTSUPP in paging_mode_external() for consistency. --- xen/arch/x86/mm.c | 86 ++++++++++++++++++++++++++++------------ xen/common/grant_table.c | 20 +++++----- xen/common/memory.c | 19 +++++---- xen/common/page_alloc.c | 2 +- 4 files changed, 84 insertions(+), 43 deletions(-) diff --git a/xen/arch/x86/mm.c b/xen/arch/x86/mm.c index 2b93efb504..ef3b208fb0 100644 --- a/xen/arch/x86/mm.c +++ b/xen/arch/x86/mm.c @@ -4722,69 +4722,105 @@ int donate_page( return -1; } +/* + * Steal page will attempt to remove `page` from domain `d`. Upon + * return, `page` will be in a state similar to the state of a page + * returned from alloc_domheap_page() with MEMF_no_owner set: + * - refcount 0 + * - type count cleared + * - owner NULL + * - page caching attributes cleaned up + * - removed from the domain's page_list + * + * If MEMF_no_refcount is not set, the domain's tot_pages will be + * adjusted. If this results in the page count falling to 0, + * put_domain() will be called. + * + * The caller should either call free_domheap_page() to free the + * page, or assign_pages() to put it back on some domain's page list. + */ int steal_page( struct domain *d, struct page_info *page, unsigned int memflags) { unsigned long x, y; bool_t drop_dom_ref = 0; - const struct domain *owner = dom_xen; + const struct domain *owner; + int rc; if ( paging_mode_external(d) ) - return -1; - - spin_lock(&d->page_alloc_lock); + return -EOPNOTSUPP; - if ( is_xen_heap_page(page) || ((owner = page_get_owner(page)) != d) ) + /* Grab a reference to make sure the page doesn't change under our feet */ + rc = -EINVAL; + if ( !(owner = page_get_owner_and_reference(page)) ) goto fail; + if ( owner != d || is_xen_heap_page(page) ) + goto fail_put; + /* - * We require there is just one reference (PGC_allocated). We temporarily - * drop this reference now so that we can safely swizzle the owner. + * We require there are exactly two references -- the one we just + * took, and PGC_allocated. We temporarily drop both these + * references so that the page becomes effectively non-"live" for + * the domain. */ y = page->count_info; do { x = y; - if ( (x & (PGC_count_mask|PGC_allocated)) != (1 | PGC_allocated) ) - goto fail; - y = cmpxchg(&page->count_info, x, x & ~PGC_count_mask); + if ( (x & (PGC_count_mask|PGC_allocated)) != (2 | PGC_allocated) ) + goto fail_put; + y = cmpxchg(&page->count_info, x, x & ~(PGC_count_mask|PGC_allocated)); } while ( y != x ); /* - * With the sole reference dropped temporarily, no-one can update type - * information. Type count also needs to be zero in this case, but e.g. - * PGT_seg_desc_page may still have PGT_validated set, which we need to - * clear before transferring ownership (as validation criteria vary - * depending on domain type). + * NB this is safe even if the page ends up being given back to + * the domain, because the count is zero: subsequent mappings will + * cause the cache attributes to be re-instated inside + * get_page_from_l1e(). */ + if ( (rc = cleanup_page_cacheattr(page)) ) + { + /* + * Couldn't fixup Xen's mappings; put things the way we found + * it and return an error + */ + page->count_info |= PGC_allocated | 1; + goto fail; + } + + /* + * With the reference count now zero, nobody can grab references + * to do anything else with the page. Return the page to a state + * that it might be upon return from alloc_domheap_pages with + * MEMF_no_owner set. + */ + spin_lock(&d->page_alloc_lock); + BUG_ON(page->u.inuse.type_info & (PGT_count_mask | PGT_locked | PGT_pinned)); page->u.inuse.type_info = 0; - - /* Swizzle the owner then reinstate the PGC_allocated reference. */ page_set_owner(page, NULL); - y = page->count_info; - do { - x = y; - BUG_ON((x & (PGC_count_mask|PGC_allocated)) != PGC_allocated); - } while ( (y = cmpxchg(&page->count_info, x, x | 1)) != x ); + page_list_del(page, &d->page_list); /* Unlink from original owner. */ if ( !(memflags & MEMF_no_refcount) && !domain_adjust_tot_pages(d, -1) ) drop_dom_ref = 1; - page_list_del(page, &d->page_list); spin_unlock(&d->page_alloc_lock); + if ( unlikely(drop_dom_ref) ) put_domain(d); + return 0; + fail_put: + put_page(page); fail: - spin_unlock(&d->page_alloc_lock); MEM_LOG("Bad page %lx: ed=%d sd=%d caf=%08lx taf=%" PRtype_info, page_to_mfn(page), d->domain_id, owner ? owner->domain_id : DOMID_INVALID, page->count_info, page->u.inuse.type_info); - return -1; + return rc; } static int __do_update_va_mapping( diff --git a/xen/common/grant_table.c b/xen/common/grant_table.c index 27cce176a3..03fe38acb5 100644 --- a/xen/common/grant_table.c +++ b/xen/common/grant_table.c @@ -1985,7 +1985,7 @@ gnttab_transfer( rcu_unlock_domain(e); put_gfn_and_copyback: put_gfn(d, gop.mfn); - page->count_info &= ~(PGC_count_mask|PGC_allocated); + /* The count_info has already been cleaned */ free_domheap_page(page); goto copyback; } @@ -2008,10 +2008,9 @@ gnttab_transfer( copy_domain_page(_mfn(page_to_mfn(new_page)), _mfn(mfn)); - page->count_info &= ~(PGC_count_mask|PGC_allocated); + /* The count_info has already been cleared */ free_domheap_page(page); page = new_page; - page->count_info = PGC_allocated | 1; mfn = page_to_mfn(page); } @@ -2051,12 +2050,17 @@ gnttab_transfer( */ spin_unlock(&e->page_alloc_lock); okay = gnttab_prepare_for_transfer(e, d, gop.ref); - spin_lock(&e->page_alloc_lock); - if ( unlikely(!okay) || unlikely(e->is_dying) ) + if ( unlikely(!okay || assign_pages(e, page, 0, MEMF_no_refcount)) ) { - bool_t drop_dom_ref = !domain_adjust_tot_pages(e, -1); + bool drop_dom_ref; + /* + * Need to grab this again to safely free our "reserved" + * page in the page total + */ + spin_lock(&e->page_alloc_lock); + drop_dom_ref = !domain_adjust_tot_pages(e, -1); spin_unlock(&e->page_alloc_lock); if ( okay /* i.e. e->is_dying due to the surrounding if() */ ) @@ -2069,10 +2073,6 @@ gnttab_transfer( goto unlock_and_copyback; } - page_list_add_tail(page, &e->page_list); - page_set_owner(page, e); - - spin_unlock(&e->page_alloc_lock); put_gfn(d, gop.mfn); TRACE_1D(TRC_MEM_PAGE_GRANT_TRANSFER, e->domain_id); diff --git a/xen/common/memory.c b/xen/common/memory.c index cda79185b9..1c0cd0e314 100644 --- a/xen/common/memory.c +++ b/xen/common/memory.c @@ -611,20 +611,22 @@ static long memory_exchange(XEN_GUEST_HANDLE_PARAM(xen_memory_exchange_t) arg) * Success! Beyond this point we cannot fail for this chunk. */ - /* Destroy final reference to each input page. */ + /* + * These pages have already had owner and reference cleared. + * Do the final two steps: Remove from the physmap, and free + * them. + */ while ( (page = page_list_remove_head(&in_chunk_list)) ) { unsigned long gfn; - if ( !test_and_clear_bit(_PGC_allocated, &page->count_info) ) - BUG(); mfn = page_to_mfn(page); gfn = mfn_to_gmfn(d, mfn); /* Pages were unshared above */ BUG_ON(SHARED_M2P(gfn)); if ( guest_physmap_remove_page(d, _gfn(gfn), _mfn(mfn), 0) ) domain_crash(d); - put_page(page); + free_domheap_page(page); } /* Assign each output page to the domain. */ @@ -697,13 +699,16 @@ static long memory_exchange(XEN_GUEST_HANDLE_PARAM(xen_memory_exchange_t) arg) * chunks succeeded. */ fail: - /* Reassign any input pages we managed to steal. */ + /* + * Reassign any input pages we managed to steal. NB that if the assign + * fails again, we're on the hook for freeing the page, since we've already + * cleared PGC_allocated. + */ while ( (page = page_list_remove_head(&in_chunk_list)) ) if ( assign_pages(d, page, 0, MEMF_no_refcount) ) { BUG_ON(!d->is_dying); - if ( test_and_clear_bit(_PGC_allocated, &page->count_info) ) - put_page(page); + free_domheap_page(page); } dying: diff --git a/xen/common/page_alloc.c b/xen/common/page_alloc.c index 2b53a25f1c..8d41b5affa 100644 --- a/xen/common/page_alloc.c +++ b/xen/common/page_alloc.c @@ -1770,7 +1770,7 @@ int assign_pages( for ( i = 0; i < (1 << order); i++ ) { ASSERT(page_get_owner(&pg[i]) == NULL); - ASSERT((pg[i].count_info & ~(PGC_allocated | 1)) == 0); + ASSERT(!pg[i].count_info); page_set_owner(&pg[i], d); smp_wmb(); /* Domain pointer must be visible before updating refcnt. */ pg[i].count_info = PGC_allocated | 1; -- 2.20.1
xsa287-4.9.patch
(application/octet-stream, 11.7 KB)
From a51575ee3f574a73ccb76ba31e9c66d1f034a731 Mon Sep 17 00:00:00 2001 From: George Dunlap <[email protected]> Date: Fri, 18 Jan 2019 15:00:34 +0000 Subject: [PATCH] steal_page: Get rid of bogus struct page states The original rules for `struct page` required the following invariants at all times: - refcount > 0 implies owner != NULL - PGC_allocated implies refcount > 0 steal_page, in a misguided attempt to protect against unknown races, violates both of these rules, thus introducing other races: - Temporarily, the count_info has the refcount go to 0 while PGC_allocated is set - It explicitly returns the page PGC_allocated set, but owner == NULL and page not on the page_list. The second one meant that page_get_owner_and_reference() could return NULL even after having successfully grabbed a reference on the page, leading the caller to leak the reference (since "couldn't get ref" and "got ref but no owner" look the same). Furthermore, rather than grabbing a page reference to ensure that the owner doesn't change under its feet, it appears to rely on holding d->page_alloc lock to prevent this. Unfortunately, this is ineffective: page->owner remains non-NULL for some time after the count has been set to 0; meaning that it would be entirely possible for the page to be freed and re-allocated to a different domain between the page_get_owner() check and the count_info check. Modify steal_page to instead follow the appropriate access discipline, taking the page through series of states similar to being freed and then re-allocated with MEMF_no_owner: - Grab an extra reference to make sure we don't race with anyone else freeing the page - Drop both references and PGC_allocated atomically, so that (if successful), anyone else trying to grab a reference will fail - Attempt to reset Xen's mappings - Reset the rest of the state. Then, modify the two callers appropriately: - Leave count_info alone (it's already been cleared) - Call free_domheap_page() directly if appropriate - Call assign_pages() rather than open-coding a partial assign With all callers to assign_pages() now passing in pages with the type_info field clear, tighten the respective assertion there. This is XSA-287. Signed-off-by: George Dunlap <[email protected]> Signed-off-by: Jan Beulich <[email protected]> Backport: Return -EOPNOTSUPP in paging_mode_external() for consistency. --- xen/arch/x86/mm.c | 86 ++++++++++++++++++++++++++++------------ xen/common/grant_table.c | 20 +++++----- xen/common/memory.c | 19 +++++---- xen/common/page_alloc.c | 2 +- 4 files changed, 84 insertions(+), 43 deletions(-) diff --git a/xen/arch/x86/mm.c b/xen/arch/x86/mm.c index 2982eec43e..476ef8ee85 100644 --- a/xen/arch/x86/mm.c +++ b/xen/arch/x86/mm.c @@ -4775,70 +4775,106 @@ int donate_page( return -1; } +/* + * Steal page will attempt to remove `page` from domain `d`. Upon + * return, `page` will be in a state similar to the state of a page + * returned from alloc_domheap_page() with MEMF_no_owner set: + * - refcount 0 + * - type count cleared + * - owner NULL + * - page caching attributes cleaned up + * - removed from the domain's page_list + * + * If MEMF_no_refcount is not set, the domain's tot_pages will be + * adjusted. If this results in the page count falling to 0, + * put_domain() will be called. + * + * The caller should either call free_domheap_page() to free the + * page, or assign_pages() to put it back on some domain's page list. + */ int steal_page( struct domain *d, struct page_info *page, unsigned int memflags) { unsigned long x, y; bool_t drop_dom_ref = 0; - const struct domain *owner = dom_xen; + const struct domain *owner; + int rc; if ( paging_mode_external(d) ) - return -1; - - spin_lock(&d->page_alloc_lock); + return -EOPNOTSUPP; - if ( is_xen_heap_page(page) || ((owner = page_get_owner(page)) != d) ) + /* Grab a reference to make sure the page doesn't change under our feet */ + rc = -EINVAL; + if ( !(owner = page_get_owner_and_reference(page)) ) goto fail; + if ( owner != d || is_xen_heap_page(page) ) + goto fail_put; + /* - * We require there is just one reference (PGC_allocated). We temporarily - * drop this reference now so that we can safely swizzle the owner. + * We require there are exactly two references -- the one we just + * took, and PGC_allocated. We temporarily drop both these + * references so that the page becomes effectively non-"live" for + * the domain. */ y = page->count_info; do { x = y; - if ( (x & (PGC_count_mask|PGC_allocated)) != (1 | PGC_allocated) ) - goto fail; - y = cmpxchg(&page->count_info, x, x & ~PGC_count_mask); + if ( (x & (PGC_count_mask|PGC_allocated)) != (2 | PGC_allocated) ) + goto fail_put; + y = cmpxchg(&page->count_info, x, x & ~(PGC_count_mask|PGC_allocated)); } while ( y != x ); /* - * With the sole reference dropped temporarily, no-one can update type - * information. Type count also needs to be zero in this case, but e.g. - * PGT_seg_desc_page may still have PGT_validated set, which we need to - * clear before transferring ownership (as validation criteria vary - * depending on domain type). + * NB this is safe even if the page ends up being given back to + * the domain, because the count is zero: subsequent mappings will + * cause the cache attributes to be re-instated inside + * get_page_from_l1e(). */ + if ( (rc = cleanup_page_cacheattr(page)) ) + { + /* + * Couldn't fixup Xen's mappings; put things the way we found + * it and return an error + */ + page->count_info |= PGC_allocated | 1; + goto fail; + } + + /* + * With the reference count now zero, nobody can grab references + * to do anything else with the page. Return the page to a state + * that it might be upon return from alloc_domheap_pages with + * MEMF_no_owner set. + */ + spin_lock(&d->page_alloc_lock); + BUG_ON(page->u.inuse.type_info & (PGT_count_mask | PGT_locked | PGT_pinned)); page->u.inuse.type_info = 0; - - /* Swizzle the owner then reinstate the PGC_allocated reference. */ page_set_owner(page, NULL); - y = page->count_info; - do { - x = y; - BUG_ON((x & (PGC_count_mask|PGC_allocated)) != PGC_allocated); - } while ( (y = cmpxchg(&page->count_info, x, x | 1)) != x ); + page_list_del(page, &d->page_list); /* Unlink from original owner. */ if ( !(memflags & MEMF_no_refcount) && !domain_adjust_tot_pages(d, -1) ) drop_dom_ref = 1; - page_list_del(page, &d->page_list); spin_unlock(&d->page_alloc_lock); + if ( unlikely(drop_dom_ref) ) put_domain(d); + return 0; + fail_put: + put_page(page); fail: - spin_unlock(&d->page_alloc_lock); gdprintk(XENLOG_WARNING, "Bad steal mfn %" PRI_mfn " from d%d (owner d%d) caf=%08lx taf=%" PRtype_info "\n", page_to_mfn(page), d->domain_id, owner ? owner->domain_id : DOMID_INVALID, page->count_info, page->u.inuse.type_info); - return -1; + return rc; } static int __do_update_va_mapping( diff --git a/xen/common/grant_table.c b/xen/common/grant_table.c index 5ee0981832..5b338e95ff 100644 --- a/xen/common/grant_table.c +++ b/xen/common/grant_table.c @@ -1985,7 +1985,7 @@ gnttab_transfer( rcu_unlock_domain(e); put_gfn_and_copyback: put_gfn(d, gop.mfn); - page->count_info &= ~(PGC_count_mask|PGC_allocated); + /* The count_info has already been cleaned */ free_domheap_page(page); goto copyback; } @@ -2008,10 +2008,9 @@ gnttab_transfer( copy_domain_page(_mfn(page_to_mfn(new_page)), _mfn(mfn)); - page->count_info &= ~(PGC_count_mask|PGC_allocated); + /* The count_info has already been cleared */ free_domheap_page(page); page = new_page; - page->count_info = PGC_allocated | 1; mfn = page_to_mfn(page); } @@ -2051,12 +2050,17 @@ gnttab_transfer( */ spin_unlock(&e->page_alloc_lock); okay = gnttab_prepare_for_transfer(e, d, gop.ref); - spin_lock(&e->page_alloc_lock); - if ( unlikely(!okay) || unlikely(e->is_dying) ) + if ( unlikely(!okay || assign_pages(e, page, 0, MEMF_no_refcount)) ) { - bool_t drop_dom_ref = !domain_adjust_tot_pages(e, -1); + bool drop_dom_ref; + /* + * Need to grab this again to safely free our "reserved" + * page in the page total + */ + spin_lock(&e->page_alloc_lock); + drop_dom_ref = !domain_adjust_tot_pages(e, -1); spin_unlock(&e->page_alloc_lock); if ( okay /* i.e. e->is_dying due to the surrounding if() */ ) @@ -2069,10 +2073,6 @@ gnttab_transfer( goto unlock_and_copyback; } - page_list_add_tail(page, &e->page_list); - page_set_owner(page, e); - - spin_unlock(&e->page_alloc_lock); put_gfn(d, gop.mfn); TRACE_1D(TRC_MEM_PAGE_GRANT_TRANSFER, e->domain_id); diff --git a/xen/common/memory.c b/xen/common/memory.c index c1d0da318e..1ade62a309 100644 --- a/xen/common/memory.c +++ b/xen/common/memory.c @@ -610,20 +610,22 @@ static long memory_exchange(XEN_GUEST_HANDLE_PARAM(xen_memory_exchange_t) arg) * Success! Beyond this point we cannot fail for this chunk. */ - /* Destroy final reference to each input page. */ + /* + * These pages have already had owner and reference cleared. + * Do the final two steps: Remove from the physmap, and free + * them. + */ while ( (page = page_list_remove_head(&in_chunk_list)) ) { unsigned long gfn; - if ( !test_and_clear_bit(_PGC_allocated, &page->count_info) ) - BUG(); mfn = page_to_mfn(page); gfn = mfn_to_gmfn(d, mfn); /* Pages were unshared above */ BUG_ON(SHARED_M2P(gfn)); if ( guest_physmap_remove_page(d, _gfn(gfn), _mfn(mfn), 0) ) domain_crash(d); - put_page(page); + free_domheap_page(page); } /* Assign each output page to the domain. */ @@ -696,13 +698,16 @@ static long memory_exchange(XEN_GUEST_HANDLE_PARAM(xen_memory_exchange_t) arg) * chunks succeeded. */ fail: - /* Reassign any input pages we managed to steal. */ + /* + * Reassign any input pages we managed to steal. NB that if the assign + * fails again, we're on the hook for freeing the page, since we've already + * cleared PGC_allocated. + */ while ( (page = page_list_remove_head(&in_chunk_list)) ) if ( assign_pages(d, page, 0, MEMF_no_refcount) ) { BUG_ON(!d->is_dying); - if ( test_and_clear_bit(_PGC_allocated, &page->count_info) ) - put_page(page); + free_domheap_page(page); } dying: diff --git a/xen/common/page_alloc.c b/xen/common/page_alloc.c index 7d8c37ef36..52b5301c0f 100644 --- a/xen/common/page_alloc.c +++ b/xen/common/page_alloc.c @@ -1766,7 +1766,7 @@ int assign_pages( for ( i = 0; i < (1 << order); i++ ) { ASSERT(page_get_owner(&pg[i]) == NULL); - ASSERT((pg[i].count_info & ~(PGC_allocated | 1)) == 0); + ASSERT(!pg[i].count_info); page_set_owner(&pg[i], d); smp_wmb(); /* Domain pointer must be visible before updating refcnt. */ pg[i].count_info = PGC_allocated | 1; -- 2.20.1
xsa287-4.10.patch
(application/octet-stream, 11.6 KB)
From 9b1f6192e4b0366dc09cea905d3e0a4b82cad45b Mon Sep 17 00:00:00 2001 From: George Dunlap <[email protected]> Date: Fri, 18 Jan 2019 15:00:34 +0000 Subject: [PATCH] steal_page: Get rid of bogus struct page states The original rules for `struct page` required the following invariants at all times: - refcount > 0 implies owner != NULL - PGC_allocated implies refcount > 0 steal_page, in a misguided attempt to protect against unknown races, violates both of these rules, thus introducing other races: - Temporarily, the count_info has the refcount go to 0 while PGC_allocated is set - It explicitly returns the page PGC_allocated set, but owner == NULL and page not on the page_list. The second one meant that page_get_owner_and_reference() could return NULL even after having successfully grabbed a reference on the page, leading the caller to leak the reference (since "couldn't get ref" and "got ref but no owner" look the same). Furthermore, rather than grabbing a page reference to ensure that the owner doesn't change under its feet, it appears to rely on holding d->page_alloc lock to prevent this. Unfortunately, this is ineffective: page->owner remains non-NULL for some time after the count has been set to 0; meaning that it would be entirely possible for the page to be freed and re-allocated to a different domain between the page_get_owner() check and the count_info check. Modify steal_page to instead follow the appropriate access discipline, taking the page through series of states similar to being freed and then re-allocated with MEMF_no_owner: - Grab an extra reference to make sure we don't race with anyone else freeing the page - Drop both references and PGC_allocated atomically, so that (if successful), anyone else trying to grab a reference will fail - Attempt to reset Xen's mappings - Reset the rest of the state. Then, modify the two callers appropriately: - Leave count_info alone (it's already been cleared) - Call free_domheap_page() directly if appropriate - Call assign_pages() rather than open-coding a partial assign With all callers to assign_pages() now passing in pages with the type_info field clear, tighten the respective assertion there. This is XSA-287. Signed-off-by: George Dunlap <[email protected]> Signed-off-by: Jan Beulich <[email protected]> --- xen/arch/x86/mm.c | 84 ++++++++++++++++++++++++++++------------ xen/common/grant_table.c | 20 +++++----- xen/common/memory.c | 19 +++++---- xen/common/page_alloc.c | 2 +- 4 files changed, 83 insertions(+), 42 deletions(-) diff --git a/xen/arch/x86/mm.c b/xen/arch/x86/mm.c index c20edb954d..67f6cfc1d5 100644 --- a/xen/arch/x86/mm.c +++ b/xen/arch/x86/mm.c @@ -3924,70 +3924,106 @@ int donate_page( return -EINVAL; } +/* + * Steal page will attempt to remove `page` from domain `d`. Upon + * return, `page` will be in a state similar to the state of a page + * returned from alloc_domheap_page() with MEMF_no_owner set: + * - refcount 0 + * - type count cleared + * - owner NULL + * - page caching attributes cleaned up + * - removed from the domain's page_list + * + * If MEMF_no_refcount is not set, the domain's tot_pages will be + * adjusted. If this results in the page count falling to 0, + * put_domain() will be called. + * + * The caller should either call free_domheap_page() to free the + * page, or assign_pages() to put it back on some domain's page list. + */ int steal_page( struct domain *d, struct page_info *page, unsigned int memflags) { unsigned long x, y; bool drop_dom_ref = false; - const struct domain *owner = dom_xen; + const struct domain *owner; + int rc; if ( paging_mode_external(d) ) return -EOPNOTSUPP; - spin_lock(&d->page_alloc_lock); - - if ( is_xen_heap_page(page) || ((owner = page_get_owner(page)) != d) ) + /* Grab a reference to make sure the page doesn't change under our feet */ + rc = -EINVAL; + if ( !(owner = page_get_owner_and_reference(page)) ) goto fail; + if ( owner != d || is_xen_heap_page(page) ) + goto fail_put; + /* - * We require there is just one reference (PGC_allocated). We temporarily - * drop this reference now so that we can safely swizzle the owner. + * We require there are exactly two references -- the one we just + * took, and PGC_allocated. We temporarily drop both these + * references so that the page becomes effectively non-"live" for + * the domain. */ y = page->count_info; do { x = y; - if ( (x & (PGC_count_mask|PGC_allocated)) != (1 | PGC_allocated) ) - goto fail; - y = cmpxchg(&page->count_info, x, x & ~PGC_count_mask); + if ( (x & (PGC_count_mask|PGC_allocated)) != (2 | PGC_allocated) ) + goto fail_put; + y = cmpxchg(&page->count_info, x, x & ~(PGC_count_mask|PGC_allocated)); } while ( y != x ); /* - * With the sole reference dropped temporarily, no-one can update type - * information. Type count also needs to be zero in this case, but e.g. - * PGT_seg_desc_page may still have PGT_validated set, which we need to - * clear before transferring ownership (as validation criteria vary - * depending on domain type). + * NB this is safe even if the page ends up being given back to + * the domain, because the count is zero: subsequent mappings will + * cause the cache attributes to be re-instated inside + * get_page_from_l1e(). + */ + if ( (rc = cleanup_page_cacheattr(page)) ) + { + /* + * Couldn't fixup Xen's mappings; put things the way we found + * it and return an error + */ + page->count_info |= PGC_allocated | 1; + goto fail; + } + + /* + * With the reference count now zero, nobody can grab references + * to do anything else with the page. Return the page to a state + * that it might be upon return from alloc_domheap_pages with + * MEMF_no_owner set. */ + spin_lock(&d->page_alloc_lock); + BUG_ON(page->u.inuse.type_info & (PGT_count_mask | PGT_locked | PGT_pinned)); page->u.inuse.type_info = 0; - - /* Swizzle the owner then reinstate the PGC_allocated reference. */ page_set_owner(page, NULL); - y = page->count_info; - do { - x = y; - BUG_ON((x & (PGC_count_mask|PGC_allocated)) != PGC_allocated); - } while ( (y = cmpxchg(&page->count_info, x, x | 1)) != x ); + page_list_del(page, &d->page_list); /* Unlink from original owner. */ if ( !(memflags & MEMF_no_refcount) && !domain_adjust_tot_pages(d, -1) ) drop_dom_ref = true; - page_list_del(page, &d->page_list); spin_unlock(&d->page_alloc_lock); + if ( unlikely(drop_dom_ref) ) put_domain(d); + return 0; + fail_put: + put_page(page); fail: - spin_unlock(&d->page_alloc_lock); gdprintk(XENLOG_WARNING, "Bad steal mfn %" PRI_mfn " from d%d (owner d%d) caf=%08lx taf=%" PRtype_info "\n", mfn_x(page_to_mfn(page)), d->domain_id, owner ? owner->domain_id : DOMID_INVALID, page->count_info, page->u.inuse.type_info); - return -EINVAL; + return rc; } static int __do_update_va_mapping( diff --git a/xen/common/grant_table.c b/xen/common/grant_table.c index e6790d5fac..42ea50f485 100644 --- a/xen/common/grant_table.c +++ b/xen/common/grant_table.c @@ -2175,7 +2175,7 @@ gnttab_transfer( rcu_unlock_domain(e); put_gfn_and_copyback: put_gfn(d, gop.mfn); - page->count_info &= ~(PGC_count_mask|PGC_allocated); + /* The count_info has already been cleaned */ free_domheap_page(page); goto copyback; } @@ -2198,10 +2198,9 @@ gnttab_transfer( copy_domain_page(_mfn(page_to_mfn(new_page)), _mfn(mfn)); - page->count_info &= ~(PGC_count_mask|PGC_allocated); + /* The count_info has already been cleared */ free_domheap_page(page); page = new_page; - page->count_info = PGC_allocated | 1; mfn = page_to_mfn(page); } @@ -2241,12 +2240,17 @@ gnttab_transfer( */ spin_unlock(&e->page_alloc_lock); okay = gnttab_prepare_for_transfer(e, d, gop.ref); - spin_lock(&e->page_alloc_lock); - if ( unlikely(!okay) || unlikely(e->is_dying) ) + if ( unlikely(!okay || assign_pages(e, page, 0, MEMF_no_refcount)) ) { - bool_t drop_dom_ref = !domain_adjust_tot_pages(e, -1); + bool drop_dom_ref; + /* + * Need to grab this again to safely free our "reserved" + * page in the page total + */ + spin_lock(&e->page_alloc_lock); + drop_dom_ref = !domain_adjust_tot_pages(e, -1); spin_unlock(&e->page_alloc_lock); if ( okay /* i.e. e->is_dying due to the surrounding if() */ ) @@ -2259,10 +2263,6 @@ gnttab_transfer( goto unlock_and_copyback; } - page_list_add_tail(page, &e->page_list); - page_set_owner(page, e); - - spin_unlock(&e->page_alloc_lock); put_gfn(d, gop.mfn); TRACE_1D(TRC_MEM_PAGE_GRANT_TRANSFER, e->domain_id); diff --git a/xen/common/memory.c b/xen/common/memory.c index 088083e4d1..8aaecb4495 100644 --- a/xen/common/memory.c +++ b/xen/common/memory.c @@ -631,20 +631,22 @@ static long memory_exchange(XEN_GUEST_HANDLE_PARAM(xen_memory_exchange_t) arg) * Success! Beyond this point we cannot fail for this chunk. */ - /* Destroy final reference to each input page. */ + /* + * These pages have already had owner and reference cleared. + * Do the final two steps: Remove from the physmap, and free + * them. + */ while ( (page = page_list_remove_head(&in_chunk_list)) ) { unsigned long gfn; - if ( !test_and_clear_bit(_PGC_allocated, &page->count_info) ) - BUG(); mfn = page_to_mfn(page); gfn = mfn_to_gmfn(d, mfn); /* Pages were unshared above */ BUG_ON(SHARED_M2P(gfn)); if ( guest_physmap_remove_page(d, _gfn(gfn), _mfn(mfn), 0) ) domain_crash(d); - put_page(page); + free_domheap_page(page); } /* Assign each output page to the domain. */ @@ -717,13 +719,16 @@ static long memory_exchange(XEN_GUEST_HANDLE_PARAM(xen_memory_exchange_t) arg) * chunks succeeded. */ fail: - /* Reassign any input pages we managed to steal. */ + /* + * Reassign any input pages we managed to steal. NB that if the assign + * fails again, we're on the hook for freeing the page, since we've already + * cleared PGC_allocated. + */ while ( (page = page_list_remove_head(&in_chunk_list)) ) if ( assign_pages(d, page, 0, MEMF_no_refcount) ) { BUG_ON(!d->is_dying); - if ( test_and_clear_bit(_PGC_allocated, &page->count_info) ) - put_page(page); + free_domheap_page(page); } dying: diff --git a/xen/common/page_alloc.c b/xen/common/page_alloc.c index 598c3432c9..15a6f018f8 100644 --- a/xen/common/page_alloc.c +++ b/xen/common/page_alloc.c @@ -2219,7 +2219,7 @@ int assign_pages( for ( i = 0; i < (1 << order); i++ ) { ASSERT(page_get_owner(&pg[i]) == NULL); - ASSERT((pg[i].count_info & ~(PGC_allocated | 1)) == 0); + ASSERT(!pg[i].count_info); page_set_owner(&pg[i], d); smp_wmb(); /* Domain pointer must be visible before updating refcnt. */ pg[i].count_info = PGC_allocated | 1; -- 2.20.1
xsa287-4.11.patch
(application/octet-stream, 11.6 KB)
From 67620c1ccb13f7b58645f48248ba1f408b021fdc Mon Sep 17 00:00:00 2001 From: George Dunlap <[email protected]> Date: Fri, 18 Jan 2019 15:00:34 +0000 Subject: [PATCH] steal_page: Get rid of bogus struct page states The original rules for `struct page` required the following invariants at all times: - refcount > 0 implies owner != NULL - PGC_allocated implies refcount > 0 steal_page, in a misguided attempt to protect against unknown races, violates both of these rules, thus introducing other races: - Temporarily, the count_info has the refcount go to 0 while PGC_allocated is set - It explicitly returns the page PGC_allocated set, but owner == NULL and page not on the page_list. The second one meant that page_get_owner_and_reference() could return NULL even after having successfully grabbed a reference on the page, leading the caller to leak the reference (since "couldn't get ref" and "got ref but no owner" look the same). Furthermore, rather than grabbing a page reference to ensure that the owner doesn't change under its feet, it appears to rely on holding d->page_alloc lock to prevent this. Unfortunately, this is ineffective: page->owner remains non-NULL for some time after the count has been set to 0; meaning that it would be entirely possible for the page to be freed and re-allocated to a different domain between the page_get_owner() check and the count_info check. Modify steal_page to instead follow the appropriate access discipline, taking the page through series of states similar to being freed and then re-allocated with MEMF_no_owner: - Grab an extra reference to make sure we don't race with anyone else freeing the page - Drop both references and PGC_allocated atomically, so that (if successful), anyone else trying to grab a reference will fail - Attempt to reset Xen's mappings - Reset the rest of the state. Then, modify the two callers appropriately: - Leave count_info alone (it's already been cleared) - Call free_domheap_page() directly if appropriate - Call assign_pages() rather than open-coding a partial assign With all callers to assign_pages() now passing in pages with the type_info field clear, tighten the respective assertion there. This is XSA-287. Signed-off-by: George Dunlap <[email protected]> Signed-off-by: Jan Beulich <[email protected]> --- xen/arch/x86/mm.c | 84 ++++++++++++++++++++++++++++------------ xen/common/grant_table.c | 20 +++++----- xen/common/memory.c | 19 +++++---- xen/common/page_alloc.c | 2 +- 4 files changed, 83 insertions(+), 42 deletions(-) diff --git a/xen/arch/x86/mm.c b/xen/arch/x86/mm.c index 6509035a5c..d8ff58c901 100644 --- a/xen/arch/x86/mm.c +++ b/xen/arch/x86/mm.c @@ -3966,70 +3966,106 @@ int donate_page( return -EINVAL; } +/* + * Steal page will attempt to remove `page` from domain `d`. Upon + * return, `page` will be in a state similar to the state of a page + * returned from alloc_domheap_page() with MEMF_no_owner set: + * - refcount 0 + * - type count cleared + * - owner NULL + * - page caching attributes cleaned up + * - removed from the domain's page_list + * + * If MEMF_no_refcount is not set, the domain's tot_pages will be + * adjusted. If this results in the page count falling to 0, + * put_domain() will be called. + * + * The caller should either call free_domheap_page() to free the + * page, or assign_pages() to put it back on some domain's page list. + */ int steal_page( struct domain *d, struct page_info *page, unsigned int memflags) { unsigned long x, y; bool drop_dom_ref = false; - const struct domain *owner = dom_xen; + const struct domain *owner; + int rc; if ( paging_mode_external(d) ) return -EOPNOTSUPP; - spin_lock(&d->page_alloc_lock); - - if ( is_xen_heap_page(page) || ((owner = page_get_owner(page)) != d) ) + /* Grab a reference to make sure the page doesn't change under our feet */ + rc = -EINVAL; + if ( !(owner = page_get_owner_and_reference(page)) ) goto fail; + if ( owner != d || is_xen_heap_page(page) ) + goto fail_put; + /* - * We require there is just one reference (PGC_allocated). We temporarily - * drop this reference now so that we can safely swizzle the owner. + * We require there are exactly two references -- the one we just + * took, and PGC_allocated. We temporarily drop both these + * references so that the page becomes effectively non-"live" for + * the domain. */ y = page->count_info; do { x = y; - if ( (x & (PGC_count_mask|PGC_allocated)) != (1 | PGC_allocated) ) - goto fail; - y = cmpxchg(&page->count_info, x, x & ~PGC_count_mask); + if ( (x & (PGC_count_mask|PGC_allocated)) != (2 | PGC_allocated) ) + goto fail_put; + y = cmpxchg(&page->count_info, x, x & ~(PGC_count_mask|PGC_allocated)); } while ( y != x ); /* - * With the sole reference dropped temporarily, no-one can update type - * information. Type count also needs to be zero in this case, but e.g. - * PGT_seg_desc_page may still have PGT_validated set, which we need to - * clear before transferring ownership (as validation criteria vary - * depending on domain type). + * NB this is safe even if the page ends up being given back to + * the domain, because the count is zero: subsequent mappings will + * cause the cache attributes to be re-instated inside + * get_page_from_l1e(). + */ + if ( (rc = cleanup_page_cacheattr(page)) ) + { + /* + * Couldn't fixup Xen's mappings; put things the way we found + * it and return an error + */ + page->count_info |= PGC_allocated | 1; + goto fail; + } + + /* + * With the reference count now zero, nobody can grab references + * to do anything else with the page. Return the page to a state + * that it might be upon return from alloc_domheap_pages with + * MEMF_no_owner set. */ + spin_lock(&d->page_alloc_lock); + BUG_ON(page->u.inuse.type_info & (PGT_count_mask | PGT_locked | PGT_pinned)); page->u.inuse.type_info = 0; - - /* Swizzle the owner then reinstate the PGC_allocated reference. */ page_set_owner(page, NULL); - y = page->count_info; - do { - x = y; - BUG_ON((x & (PGC_count_mask|PGC_allocated)) != PGC_allocated); - } while ( (y = cmpxchg(&page->count_info, x, x | 1)) != x ); + page_list_del(page, &d->page_list); /* Unlink from original owner. */ if ( !(memflags & MEMF_no_refcount) && !domain_adjust_tot_pages(d, -1) ) drop_dom_ref = true; - page_list_del(page, &d->page_list); spin_unlock(&d->page_alloc_lock); + if ( unlikely(drop_dom_ref) ) put_domain(d); + return 0; + fail_put: + put_page(page); fail: - spin_unlock(&d->page_alloc_lock); gdprintk(XENLOG_WARNING, "Bad steal mfn %" PRI_mfn " from d%d (owner d%d) caf=%08lx taf=%" PRtype_info "\n", mfn_x(page_to_mfn(page)), d->domain_id, owner ? owner->domain_id : DOMID_INVALID, page->count_info, page->u.inuse.type_info); - return -EINVAL; + return rc; } static int __do_update_va_mapping( diff --git a/xen/common/grant_table.c b/xen/common/grant_table.c index c0585d33f4..656fad1b42 100644 --- a/xen/common/grant_table.c +++ b/xen/common/grant_table.c @@ -2179,7 +2179,7 @@ gnttab_transfer( rcu_unlock_domain(e); put_gfn_and_copyback: put_gfn(d, gop.mfn); - page->count_info &= ~(PGC_count_mask|PGC_allocated); + /* The count_info has already been cleaned */ free_domheap_page(page); goto copyback; } @@ -2202,10 +2202,9 @@ gnttab_transfer( copy_domain_page(page_to_mfn(new_page), mfn); - page->count_info &= ~(PGC_count_mask|PGC_allocated); + /* The count_info has already been cleared */ free_domheap_page(page); page = new_page; - page->count_info = PGC_allocated | 1; mfn = page_to_mfn(page); } @@ -2245,12 +2244,17 @@ gnttab_transfer( */ spin_unlock(&e->page_alloc_lock); okay = gnttab_prepare_for_transfer(e, d, gop.ref); - spin_lock(&e->page_alloc_lock); - if ( unlikely(!okay) || unlikely(e->is_dying) ) + if ( unlikely(!okay || assign_pages(e, page, 0, MEMF_no_refcount)) ) { - bool_t drop_dom_ref = !domain_adjust_tot_pages(e, -1); + bool drop_dom_ref; + /* + * Need to grab this again to safely free our "reserved" + * page in the page total + */ + spin_lock(&e->page_alloc_lock); + drop_dom_ref = !domain_adjust_tot_pages(e, -1); spin_unlock(&e->page_alloc_lock); if ( okay /* i.e. e->is_dying due to the surrounding if() */ ) @@ -2263,10 +2267,6 @@ gnttab_transfer( goto unlock_and_copyback; } - page_list_add_tail(page, &e->page_list); - page_set_owner(page, e); - - spin_unlock(&e->page_alloc_lock); put_gfn(d, gop.mfn); TRACE_1D(TRC_MEM_PAGE_GRANT_TRANSFER, e->domain_id); diff --git a/xen/common/memory.c b/xen/common/memory.c index 4fb7962c79..f71163221f 100644 --- a/xen/common/memory.c +++ b/xen/common/memory.c @@ -675,20 +675,22 @@ static long memory_exchange(XEN_GUEST_HANDLE_PARAM(xen_memory_exchange_t) arg) * Success! Beyond this point we cannot fail for this chunk. */ - /* Destroy final reference to each input page. */ + /* + * These pages have already had owner and reference cleared. + * Do the final two steps: Remove from the physmap, and free + * them. + */ while ( (page = page_list_remove_head(&in_chunk_list)) ) { unsigned long gfn; - if ( !test_and_clear_bit(_PGC_allocated, &page->count_info) ) - BUG(); mfn = page_to_mfn(page); gfn = mfn_to_gmfn(d, mfn_x(mfn)); /* Pages were unshared above */ BUG_ON(SHARED_M2P(gfn)); if ( guest_physmap_remove_page(d, _gfn(gfn), mfn, 0) ) domain_crash(d); - put_page(page); + free_domheap_page(page); } /* Assign each output page to the domain. */ @@ -761,13 +763,16 @@ static long memory_exchange(XEN_GUEST_HANDLE_PARAM(xen_memory_exchange_t) arg) * chunks succeeded. */ fail: - /* Reassign any input pages we managed to steal. */ + /* + * Reassign any input pages we managed to steal. NB that if the assign + * fails again, we're on the hook for freeing the page, since we've already + * cleared PGC_allocated. + */ while ( (page = page_list_remove_head(&in_chunk_list)) ) if ( assign_pages(d, page, 0, MEMF_no_refcount) ) { BUG_ON(!d->is_dying); - if ( test_and_clear_bit(_PGC_allocated, &page->count_info) ) - put_page(page); + free_domheap_page(page); } dying: diff --git a/xen/common/page_alloc.c b/xen/common/page_alloc.c index 482f0988f7..52da7762e3 100644 --- a/xen/common/page_alloc.c +++ b/xen/common/page_alloc.c @@ -2221,7 +2221,7 @@ int assign_pages( for ( i = 0; i < (1 << order); i++ ) { ASSERT(page_get_owner(&pg[i]) == NULL); - ASSERT((pg[i].count_info & ~(PGC_allocated | 1)) == 0); + ASSERT(!pg[i].count_info); page_set_owner(&pg[i], d); smp_wmb(); /* Domain pointer must be visible before updating refcnt. */ pg[i].count_info = PGC_allocated | 1; -- 2.20.1