Xen Security Advisory 290 v2 - missing preemption in x86 PV page table unvalidation
Xen.org security team <[email protected]>
| Newsgroups | gmane.comp.emulators.xen.announce |
|---|---|
| Message-ID | <E1h197Q-00049g-17__43276.6614177794$1551788991$gmane$org@xenbits.xenproject.org> |
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256
Xen Security Advisory XSA-290
version 2
missing preemption in x86 PV page table unvalidation
UPDATES IN VERSION 2
====================
Metadata updated to remove dependency on XSA-283.
Public release.
ISSUE DESCRIPTION
=================
XSA-273 changes required, among other things, making any PTE updates
restartable. The changes making PTE updates restartable assumed that L2
pagetables would always be promoted preemptibly; but this turns out not
to be the case when using the 'linear pagetable' feature; the result was
that interrupted operations are not handled properly in certain cases.
Furthermore, previous security work making pagetable update preemptible
failed to account for 'linear pagetables' at L3 and L4 levels, making it
possible for operations to run for longer than acceptable times.
IMPACT
======
Malicious or buggy x86 PV guest kernels can mount a Denial of Service
(DoS) attack affecting the whole system.
VULNERABLE SYSTEMS
==================
All Xen versions are vulnerable.
Only x86 systems are affected. ARM systems are not affected.
Only Xen versions which permit linear page table use by PV guests are
vulnerable.
Only x86 PV guests can leverage this vulnerability. x86 HVM guests
cannot leverage this vulnerability.
MITIGATION
==========
Not permitting linear page table use by PV guests avoids the
vulnerability. This can be done both at build time, by turning off the
PV_LINEAR_PT configure option, or at runtime, by passing specifying
"pv-linear-pt=0" on the hypervisor command line. Doing so would,
however, render PV guests using the functionality, like NetBSD,
unusable.
On systems where the guest kernel is controlled by the host rather than
guest administrator, running only kernels which only issue sane
hypercalls will prevent untrusted guest users from exploiting this
issue. However untrusted guest administrators can still trigger it
unless further steps are taken to prevent them from loading code into
the kernel (e.g by disabling loadable modules etc) or from using other
mechanisms which allow them to run code at kernel privilege.
Running only HVM guests will avoid this vulnerability.
CREDITS
=======
This issue was discovered by Manuel Bouyer.
RESOLUTION
==========
Applying the appropriate pair of attached patches resolves this issue.
xsa290/unstable-?.patch xen-unstable
xsa290/4.11-?.patch Xen 4.11.x
xsa290/4.10-?.patch Xen 4.10.x
xsa290/4.9-?.patch Xen 4.9.x
xsa290/4.8-?.patch Xen 4.8.x
xsa290/4.7-?.patch Xen 4.7.x
$ sha256sum xsa290* xsa290*/*
e74014bf97f223f35dc6142fbfadd8a3df6c7ecf1818d5d04ebb717a1d600959 xsa290.meta
87ffaf9712bfd2283e845d168811e572b9ebc8a580e750128586a48e65ae4c67 xsa290/4.7-1.patch
4137eb15d963a77ff302cb65f9f04e402ea23f69042f89ece4baaf4b7a58d638 xsa290/4.7-2.patch
0f5ce8c13c99431cae69736e117c7420c3202e3a680b42a66027646ae0aa141c xsa290/4.8-1.patch
bb4102dd6f3daf60859a88b6a2f0828bc8aeb224d3d3b6fd2d2cc96b3f131a24 xsa290/4.8-2.patch
a7e4902968529289c63149608d48e1eeac2feffa644e1337b1b5b9a624dc746d xsa290/4.9-1.patch
7798b063a8db95fc18bca1ea25d84937fbe9c6e0add15056841fd97d5aec2885 xsa290/4.9-2.patch
3a0bf44875bb5a8525b4418d6efd49bd6ed6cfaffe669cbdcfde61a65fe9cdea xsa290/4.10-1.patch
1e7dfe1b0c57e245daef1351db855a9312a4c225c05a6720460ea4aa1148ee22 xsa290/4.10-2.patch
3dd47f3bc1a004260d05cba548a80e475f85ffe60b663879de386e32a8e9ffbc xsa290/4.11-1.patch
b3b17546fc553bf60572cf56023d8177f96973fcd072a8adfc622b4030e58d00 xsa290/4.11-2.patch
4ff1d857f46a781fd7483a30297ebf51bf079ccd1d598df799e5779ddc893674 xsa290/unstable-1.patch
3a85ecc426d482052aaf2a84bfde9840eb7a566638dbab042dac84b0019ca473 xsa290/unstable-2.patch
$
DEPLOYMENT DURING EMBARGO
=========================
Deployment of the patches and/or the HVM-only as well as host controlled
kernel 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.
HOWEVER deployment of the "pv-linear-pt=0" mitigation described above is
NOT permitted (except where all the affected systems and VMs are
administered and used only by organisations which are members of the Xen
Project Security Issues Predisclosure List). Specifically, deployment
on public cloud systems is NOT permitted.
This is because in that case the configuration change is visible to the
guest, which could lead to the rediscovery of the vulnerability.
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/4UyVfoK9kFAlx+amwMHHBncEB4ZW4u
b3JnAAoJEIP+FMlX6CvZaP0IALeZ9zd5UEHwM2Xq2VTZdJqTW9blhttrJbmfTiSe
7/wtwsMpRIrxycdouWzAZwo3ZFt3Y6qmk+6awkT23ck0OC1zNnMw9ANYdB2XqW+Q
NGzz/ExDj+40EeaMcx2ZyNUZGya0yJVorzRSPM68bQAW2XXy1oBevTKqMkr3iSJf
I06/J7vtap89F+JjfiBrVXubcjmUvX/MtsD4yz0lckC5Ti07Lcmv0pUGHprxXBgw
QlMhgV3qKG3JBa7h0b11UnrpIPdCbwJIWJd/+Pzd4yD9R3ZXRiGyjOd+/zyXVcY7
vCrh2lCP4WpXvrLDUPt8IgJak8cjxZ2JGAxk3yN/QI6Uro0=
=/yIK
-----END PGP SIGNATURE-----
_______________________________________________
Xen-announce mailing list
[email protected]
https://lists.xenproject.org/mailman/listinfo/xen-announce
xsa290.meta
(application/octet-stream, 2 KB) - not displayed
xsa290/4.7-1.patch
(application/octet-stream, 6.8 KB)
From: Jan Beulich <[email protected]> Subject: x86/mm: also allow L2 (un)validation to be preemptible Besides this having become a requirement as of commit c612481d1c ("x86/mm: Plumbing to allow any PTE update to fail with -ERESTART"), which added an assertion to free_l2_table(), which was now observed to trigger when recursive page tables get cleaned up, it was also a mistake to not extend preemption to L2 when it was added to L3/L4 table handling. This is because in the recursive page table case even there the time taken to unvalidate a single table may be excessive. The validation side additions are done just for symmetry. This is part of XSA-290. Reported-by: Manuel Bouyer <[email protected]> Tested-by: Manuel Bouyer <[email protected]> Signed-off-by: Jan Beulich <[email protected]> Reviewed-by: Andrew Cooper <[email protected]> --- a/xen/arch/x86/mm.c +++ b/xen/arch/x86/mm.c @@ -1239,7 +1239,7 @@ get_page_from_l1e( define_get_linear_pagetable(l2); static int get_page_from_l2e( - l2_pgentry_t l2e, unsigned long pfn, struct domain *d) + l2_pgentry_t l2e, unsigned long pfn, struct domain *d, int partial) { unsigned long mfn = l2e_get_pfn(l2e); int rc; @@ -1255,7 +1255,8 @@ get_page_from_l2e( if ( !(l2e_get_flags(l2e) & _PAGE_PSE) ) { - rc = get_page_and_type_from_pagenr(mfn, PGT_l1_page_table, d, 0, 0); + rc = get_page_and_type_from_pagenr(mfn, PGT_l1_page_table, d, + partial, 0); if ( unlikely(rc == -EINVAL) && get_l2_linear_pagetable(l2e, pfn, d) ) rc = 0; return rc; @@ -1450,8 +1451,11 @@ void put_page_from_l1e(l1_pgentry_t l1e, * NB. Virtual address 'l2e' maps to a machine address within frame 'pfn'. * 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) +static int put_page_from_l2e(l2_pgentry_t l2e, unsigned long pfn, + int partial, bool_t defer) { + int rc = 0; + if ( !(l2e_get_flags(l2e) & _PAGE_PRESENT) || (l2e_get_pfn(l2e) == pfn) ) return 1; @@ -1460,13 +1464,27 @@ static int put_page_from_l2e(l2_pgentry_ else { struct page_info *pg = l2e_get_page(l2e); - int rc = _put_page_type(pg, 0, mfn_to_page(pfn)); + struct page_info *ptpg = mfn_to_page(pfn); - ASSERT(!rc); - put_page(pg); + if ( unlikely(partial > 0) ) + { + ASSERT(!defer); + rc = _put_page_type(pg, 1, ptpg); + } + else if ( defer ) + { + current->arch.old_guest_ptpg = ptpg; + current->arch.old_guest_table = pg; + } + else + { + rc = _put_page_type(pg, 1, ptpg); + if ( likely(!rc) ) + put_page(pg); + } } - return 0; + return rc; } static int put_page_from_l3e(l3_pgentry_t l3e, unsigned long pfn, @@ -1641,11 +1659,12 @@ static int alloc_l2_table(struct page_in unsigned long pfn = page_to_mfn(page); l2_pgentry_t *pl2e; unsigned int i; - int rc = 0; + int rc = 0, partial = page->partial_pte; pl2e = map_domain_page(_mfn(pfn)); - for ( i = page->nr_validated_ptes; i < L2_PAGETABLE_ENTRIES; i++ ) + for ( i = page->nr_validated_ptes; i < L2_PAGETABLE_ENTRIES; + i++, partial = 0 ) { if ( i > page->nr_validated_ptes && hypercall_preempt_check() ) { @@ -1655,23 +1674,33 @@ static int alloc_l2_table(struct page_in } if ( !is_guest_l2_slot(d, type, i) || - (rc = get_page_from_l2e(pl2e[i], pfn, d)) > 0 ) + (rc = get_page_from_l2e(pl2e[i], pfn, d, partial)) > 0 ) continue; - if ( unlikely(rc == -ERESTART) ) + if ( rc == -ERESTART ) { page->nr_validated_ptes = i; - break; + page->partial_pte = partial ?: 1; } - - if ( rc < 0 ) + else if ( rc == -EINTR && i ) + { + page->nr_validated_ptes = i; + page->partial_pte = 0; + rc = -ERESTART; + } + else if ( rc < 0 && rc != -EINTR ) { MEM_LOG("Failure in alloc_l2_table: entry %d", i); - while ( i-- > 0 ) - if ( is_guest_l2_slot(d, type, i) ) - put_page_from_l2e(pl2e[i], pfn); - break; + if ( i ) + { + page->nr_validated_ptes = i; + page->partial_pte = 0; + current->arch.old_guest_ptpg = NULL; + current->arch.old_guest_table = page; + } } + if ( rc < 0 ) + break; adjust_guest_l2e(pl2e[i], d); } @@ -1893,28 +1922,50 @@ static int free_l2_table(struct page_inf struct domain *d = page_get_owner(page); unsigned long pfn = page_to_mfn(page); l2_pgentry_t *pl2e; - unsigned int i = page->nr_validated_ptes - 1; - int err = 0; + int rc = 0, partial = page->partial_pte; + unsigned int i = page->nr_validated_ptes - !partial; pl2e = map_domain_page(_mfn(pfn)); - ASSERT(page->nr_validated_ptes); - do { - if ( is_guest_l2_slot(d, page->u.inuse.type_info, i) && - put_page_from_l2e(pl2e[i], pfn) == 0 && - i && hypercall_preempt_check() ) + for ( ; ; ) + { + if ( is_guest_l2_slot(d, page->u.inuse.type_info, i) ) + rc = put_page_from_l2e(pl2e[i], pfn, partial, 0); + if ( rc < 0 ) + break; + + partial = 0; + + if ( !i-- ) + break; + + if ( hypercall_preempt_check() ) { - page->nr_validated_ptes = i; - err = -ERESTART; + rc = -EINTR; + break; } - } while ( !err && i-- ); + } unmap_domain_page(pl2e); - if ( !err ) + if ( rc >= 0 ) + { page->u.inuse.type_info &= ~PGT_pae_xen_l2; + rc = 0; + } + else if ( rc == -ERESTART ) + { + page->nr_validated_ptes = i; + page->partial_pte = partial ?: -1; + } + else if ( rc == -EINTR && i < L2_PAGETABLE_ENTRIES - 1 ) + { + page->nr_validated_ptes = i + 1; + page->partial_pte = 0; + rc = -ERESTART; + } - return err; + return rc; } static int free_l3_table(struct page_info *page) @@ -2239,7 +2290,7 @@ static int mod_l2_entry(l2_pgentry_t *pl return -EBUSY; } - if ( unlikely((rc = get_page_from_l2e(nl2e, pfn, d)) < 0) ) + if ( unlikely((rc = get_page_from_l2e(nl2e, pfn, d, 0)) < 0) ) return rc; adjust_guest_l2e(nl2e, d); @@ -2258,7 +2309,8 @@ static int mod_l2_entry(l2_pgentry_t *pl return -EBUSY; } - put_page_from_l2e(ol2e, pfn); + put_page_from_l2e(ol2e, pfn, 0, 1); + return rc; }
xsa290/4.7-2.patch
(application/octet-stream, 2.3 KB)
From: Jan Beulich <[email protected]> Subject: x86/mm: add explicit preemption checks to L3 (un)validation When recursive page tables are used at the L3 level, unvalidation of a single L4 table may incur unvalidation of two levels of L3 tables, i.e. a maximum iteration count of 512^3 for unvalidating an L4 table. The preemption check in free_l2_table() as well as the one in _put_page_type() may never be reached, so explicit checking is needed in free_l3_table(). When recursive page tables are used at the L4 level, the iteration count at L4 alone is capped at 512^2. As soon as a present L3 entry is hit which itself needs unvalidation (and hence requiring another nested loop with 512 iterations), the preemption checks added here kick in, so no further preemption checking is needed at L4 (until we decide to permit 5-level paging for PV guests). The validation side additions are done just for symmetry. This is part of XSA-290. Signed-off-by: Jan Beulich <[email protected]> Reviewed-by: Andrew Cooper <[email protected]> --- a/xen/arch/x86/mm.c +++ b/xen/arch/x86/mm.c @@ -1741,6 +1741,13 @@ static int alloc_l3_table(struct page_in for ( i = page->nr_validated_ptes; i < L3_PAGETABLE_ENTRIES; i++, partial = 0 ) { + 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) ) { if ( !(l3e_get_flags(pl3e[i]) & _PAGE_PRESENT) || @@ -1978,18 +1985,28 @@ static int free_l3_table(struct page_inf pl3e = map_domain_page(_mfn(pfn)); - do { + for ( ; ; ) + { if ( is_guest_l3_slot(i) ) { rc = put_page_from_l3e(pl3e[i], pfn, partial, 0); if ( rc < 0 ) break; + partial = 0; - if ( rc > 0 ) - continue; - unadjust_guest_l3e(pl3e[i], d); + if ( rc == 0 ) + unadjust_guest_l3e(pl3e[i], d); } - } while ( i-- ); + + if ( !i-- ) + break; + + if ( hypercall_preempt_check() ) + { + rc = -EINTR; + break; + } + } unmap_domain_page(pl3e);
xsa290/4.8-1.patch
(application/octet-stream, 7.3 KB)
From: Jan Beulich <[email protected]> Subject: x86/mm: also allow L2 (un)validation to be preemptible Commit c612481d1c ("x86/mm: Plumbing to allow any PTE update to fail with -ERESTART") added assertions next to the {alloc,free}_l2_table() invocations to document (and validate in debug builds) that L2 (un)validations are always preemptible. The assertion in free_page_type() was now observed to trigger when recursive L2 page tables get cleaned up. In particular put_page_from_l2e()'s assumption that _put_page_type() would always succeed is now wrong, resulting in a partially un-validated page left in a domain, which has no other means of getting cleaned up later on. If not causing any problems earlier, this would ultimately trigger the check for ->u.inuse.type_info having a zero count when freeing the page during cleanup after the domain has died. As a result it should be considered a mistake to not have extended preemption fully to L2 when it was added to L3/L4 table handling, which this change aims to correct. The validation side additions are done just for symmetry. This is part of XSA-290. Reported-by: Manuel Bouyer <[email protected]> Tested-by: Manuel Bouyer <[email protected]> Signed-off-by: Jan Beulich <[email protected]> Reviewed-by: Andrew Cooper <[email protected]> --- a/xen/arch/x86/mm.c +++ b/xen/arch/x86/mm.c @@ -1240,7 +1240,7 @@ get_page_from_l1e( define_get_linear_pagetable(l2); static int get_page_from_l2e( - l2_pgentry_t l2e, unsigned long pfn, struct domain *d) + l2_pgentry_t l2e, unsigned long pfn, struct domain *d, int partial) { unsigned long mfn = l2e_get_pfn(l2e); int rc; @@ -1256,7 +1256,8 @@ get_page_from_l2e( if ( !(l2e_get_flags(l2e) & _PAGE_PSE) ) { - rc = get_page_and_type_from_pagenr(mfn, PGT_l1_page_table, d, 0, 0); + rc = get_page_and_type_from_pagenr(mfn, PGT_l1_page_table, d, + partial, false); if ( unlikely(rc == -EINVAL) && get_l2_linear_pagetable(l2e, pfn, d) ) rc = 0; return rc; @@ -1451,8 +1452,11 @@ void put_page_from_l1e(l1_pgentry_t l1e, * NB. Virtual address 'l2e' maps to a machine address within frame 'pfn'. * 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) +static int put_page_from_l2e(l2_pgentry_t l2e, unsigned long pfn, + int partial, bool defer) { + int rc = 0; + if ( !(l2e_get_flags(l2e) & _PAGE_PRESENT) || (l2e_get_pfn(l2e) == pfn) ) return 1; @@ -1461,13 +1465,27 @@ static int put_page_from_l2e(l2_pgentry_ else { struct page_info *pg = l2e_get_page(l2e); - int rc = _put_page_type(pg, false, mfn_to_page(pfn)); + struct page_info *ptpg = mfn_to_page(pfn); - ASSERT(!rc); - put_page(pg); + if ( unlikely(partial > 0) ) + { + ASSERT(!defer); + rc = _put_page_type(pg, true, ptpg); + } + else if ( defer ) + { + current->arch.old_guest_ptpg = ptpg; + current->arch.old_guest_table = pg; + } + else + { + rc = _put_page_type(pg, true, ptpg); + if ( likely(!rc) ) + put_page(pg); + } } - return 0; + return rc; } static int put_page_from_l3e(l3_pgentry_t l3e, unsigned long pfn, @@ -1640,11 +1658,12 @@ static int alloc_l2_table(struct page_in unsigned long pfn = page_to_mfn(page); l2_pgentry_t *pl2e; unsigned int i; - int rc = 0; + int rc = 0, partial = page->partial_pte; pl2e = map_domain_page(_mfn(pfn)); - for ( i = page->nr_validated_ptes; i < L2_PAGETABLE_ENTRIES; i++ ) + for ( i = page->nr_validated_ptes; i < L2_PAGETABLE_ENTRIES; + i++, partial = 0 ) { if ( i > page->nr_validated_ptes && hypercall_preempt_check() ) { @@ -1654,23 +1673,33 @@ static int alloc_l2_table(struct page_in } if ( !is_guest_l2_slot(d, type, i) || - (rc = get_page_from_l2e(pl2e[i], pfn, d)) > 0 ) + (rc = get_page_from_l2e(pl2e[i], pfn, d, partial)) > 0 ) continue; - if ( unlikely(rc == -ERESTART) ) + if ( rc == -ERESTART ) { page->nr_validated_ptes = i; - break; + page->partial_pte = partial ?: 1; } - - if ( rc < 0 ) + else if ( rc == -EINTR && i ) + { + page->nr_validated_ptes = i; + page->partial_pte = 0; + rc = -ERESTART; + } + else if ( rc < 0 && rc != -EINTR ) { MEM_LOG("Failure in alloc_l2_table: entry %d", i); - while ( i-- > 0 ) - if ( is_guest_l2_slot(d, type, i) ) - put_page_from_l2e(pl2e[i], pfn); - break; + if ( i ) + { + page->nr_validated_ptes = i; + page->partial_pte = 0; + current->arch.old_guest_ptpg = NULL; + current->arch.old_guest_table = page; + } } + if ( rc < 0 ) + break; adjust_guest_l2e(pl2e[i], d); } @@ -1892,28 +1921,50 @@ static int free_l2_table(struct page_inf struct domain *d = page_get_owner(page); unsigned long pfn = page_to_mfn(page); l2_pgentry_t *pl2e; - unsigned int i = page->nr_validated_ptes - 1; - int err = 0; + int rc = 0, partial = page->partial_pte; + unsigned int i = page->nr_validated_ptes - !partial; pl2e = map_domain_page(_mfn(pfn)); - ASSERT(page->nr_validated_ptes); - do { - if ( is_guest_l2_slot(d, page->u.inuse.type_info, i) && - put_page_from_l2e(pl2e[i], pfn) == 0 && - i && hypercall_preempt_check() ) + for ( ; ; ) + { + if ( is_guest_l2_slot(d, page->u.inuse.type_info, i) ) + rc = put_page_from_l2e(pl2e[i], pfn, partial, false); + if ( rc < 0 ) + break; + + partial = 0; + + if ( !i-- ) + break; + + if ( hypercall_preempt_check() ) { - page->nr_validated_ptes = i; - err = -ERESTART; + rc = -EINTR; + break; } - } while ( !err && i-- ); + } unmap_domain_page(pl2e); - if ( !err ) + if ( rc >= 0 ) + { page->u.inuse.type_info &= ~PGT_pae_xen_l2; + rc = 0; + } + else if ( rc == -ERESTART ) + { + page->nr_validated_ptes = i; + page->partial_pte = partial ?: -1; + } + else if ( rc == -EINTR && i < L2_PAGETABLE_ENTRIES - 1 ) + { + page->nr_validated_ptes = i + 1; + page->partial_pte = 0; + rc = -ERESTART; + } - return err; + return rc; } static int free_l3_table(struct page_info *page) @@ -2238,7 +2289,7 @@ static int mod_l2_entry(l2_pgentry_t *pl return -EBUSY; } - if ( unlikely((rc = get_page_from_l2e(nl2e, pfn, d)) < 0) ) + if ( unlikely((rc = get_page_from_l2e(nl2e, pfn, d, 0)) < 0) ) return rc; adjust_guest_l2e(nl2e, d); @@ -2257,7 +2308,8 @@ static int mod_l2_entry(l2_pgentry_t *pl return -EBUSY; } - put_page_from_l2e(ol2e, pfn); + put_page_from_l2e(ol2e, pfn, 0, true); + return rc; }
xsa290/4.8-2.patch
(application/octet-stream, 2.3 KB)
From: Jan Beulich <[email protected]> Subject: x86/mm: add explicit preemption checks to L3 (un)validation When recursive page tables are used at the L3 level, unvalidation of a single L4 table may incur unvalidation of two levels of L3 tables, i.e. a maximum iteration count of 512^3 for unvalidating an L4 table. The preemption check in free_l2_table() as well as the one in _put_page_type() may never be reached, so explicit checking is needed in free_l3_table(). When recursive page tables are used at the L4 level, the iteration count at L4 alone is capped at 512^2. As soon as a present L3 entry is hit which itself needs unvalidation (and hence requiring another nested loop with 512 iterations), the preemption checks added here kick in, so no further preemption checking is needed at L4 (until we decide to permit 5-level paging for PV guests). The validation side additions are done just for symmetry. This is part of XSA-290. Signed-off-by: Jan Beulich <[email protected]> Reviewed-by: Andrew Cooper <[email protected]> --- a/xen/arch/x86/mm.c +++ b/xen/arch/x86/mm.c @@ -1740,6 +1740,13 @@ static int alloc_l3_table(struct page_in for ( i = page->nr_validated_ptes; i < L3_PAGETABLE_ENTRIES; i++, partial = 0 ) { + 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) ) { if ( !(l3e_get_flags(pl3e[i]) & _PAGE_PRESENT) || @@ -1977,18 +1984,28 @@ static int free_l3_table(struct page_inf pl3e = map_domain_page(_mfn(pfn)); - do { + for ( ; ; ) + { if ( is_guest_l3_slot(i) ) { rc = put_page_from_l3e(pl3e[i], pfn, partial, 0); if ( rc < 0 ) break; + partial = 0; - if ( rc > 0 ) - continue; - unadjust_guest_l3e(pl3e[i], d); + if ( rc == 0 ) + unadjust_guest_l3e(pl3e[i], d); } - } while ( i-- ); + + if ( !i-- ) + break; + + if ( hypercall_preempt_check() ) + { + rc = -EINTR; + break; + } + } unmap_domain_page(pl3e);
xsa290/4.9-1.patch
(application/octet-stream, 7.3 KB)
From: Jan Beulich <[email protected]> Subject: x86/mm: also allow L2 (un)validation to be preemptible Commit c612481d1c ("x86/mm: Plumbing to allow any PTE update to fail with -ERESTART") added assertions next to the {alloc,free}_l2_table() invocations to document (and validate in debug builds) that L2 (un)validations are always preemptible. The assertion in free_page_type() was now observed to trigger when recursive L2 page tables get cleaned up. In particular put_page_from_l2e()'s assumption that _put_page_type() would always succeed is now wrong, resulting in a partially un-validated page left in a domain, which has no other means of getting cleaned up later on. If not causing any problems earlier, this would ultimately trigger the check for ->u.inuse.type_info having a zero count when freeing the page during cleanup after the domain has died. As a result it should be considered a mistake to not have extended preemption fully to L2 when it was added to L3/L4 table handling, which this change aims to correct. The validation side additions are done just for symmetry. This is part of XSA-290. Reported-by: Manuel Bouyer <[email protected]> Tested-by: Manuel Bouyer <[email protected]> Signed-off-by: Jan Beulich <[email protected]> Reviewed-by: Andrew Cooper <[email protected]> --- a/xen/arch/x86/mm.c +++ b/xen/arch/x86/mm.c @@ -1261,7 +1261,7 @@ get_page_from_l1e( define_get_linear_pagetable(l2); static int get_page_from_l2e( - l2_pgentry_t l2e, unsigned long pfn, struct domain *d) + l2_pgentry_t l2e, unsigned long pfn, struct domain *d, int partial) { unsigned long mfn = l2e_get_pfn(l2e); int rc; @@ -1278,7 +1278,8 @@ get_page_from_l2e( if ( !(l2e_get_flags(l2e) & _PAGE_PSE) ) { - rc = get_page_and_type_from_pagenr(mfn, PGT_l1_page_table, d, 0, 0); + rc = get_page_and_type_from_pagenr(mfn, PGT_l1_page_table, d, + partial, false); if ( unlikely(rc == -EINVAL) && get_l2_linear_pagetable(l2e, pfn, d) ) rc = 0; return rc; @@ -1477,8 +1478,11 @@ void put_page_from_l1e(l1_pgentry_t l1e, * NB. Virtual address 'l2e' maps to a machine address within frame 'pfn'. * 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) +static int put_page_from_l2e(l2_pgentry_t l2e, unsigned long pfn, + int partial, bool defer) { + int rc = 0; + if ( !(l2e_get_flags(l2e) & _PAGE_PRESENT) || (l2e_get_pfn(l2e) == pfn) ) return 1; @@ -1487,13 +1491,27 @@ static int put_page_from_l2e(l2_pgentry_ else { struct page_info *pg = l2e_get_page(l2e); - int rc = _put_page_type(pg, false, mfn_to_page(pfn)); + struct page_info *ptpg = mfn_to_page(pfn); - ASSERT(!rc); - put_page(pg); + if ( unlikely(partial > 0) ) + { + ASSERT(!defer); + rc = _put_page_type(pg, true, ptpg); + } + else if ( defer ) + { + current->arch.old_guest_ptpg = ptpg; + current->arch.old_guest_table = pg; + } + else + { + rc = _put_page_type(pg, true, ptpg); + if ( likely(!rc) ) + put_page(pg); + } } - return 0; + return rc; } static int put_page_from_l3e(l3_pgentry_t l3e, unsigned long pfn, @@ -1666,11 +1684,12 @@ static int alloc_l2_table(struct page_in unsigned long pfn = page_to_mfn(page); l2_pgentry_t *pl2e; unsigned int i; - int rc = 0; + int rc = 0, partial = page->partial_pte; pl2e = map_domain_page(_mfn(pfn)); - for ( i = page->nr_validated_ptes; i < L2_PAGETABLE_ENTRIES; i++ ) + for ( i = page->nr_validated_ptes; i < L2_PAGETABLE_ENTRIES; + i++, partial = 0 ) { if ( i > page->nr_validated_ptes && hypercall_preempt_check() ) { @@ -1680,23 +1699,33 @@ static int alloc_l2_table(struct page_in } if ( !is_guest_l2_slot(d, type, i) || - (rc = get_page_from_l2e(pl2e[i], pfn, d)) > 0 ) + (rc = get_page_from_l2e(pl2e[i], pfn, d, partial)) > 0 ) continue; - if ( unlikely(rc == -ERESTART) ) + if ( rc == -ERESTART ) { page->nr_validated_ptes = i; - break; + page->partial_pte = partial ?: 1; } - - if ( rc < 0 ) + else if ( rc == -EINTR && i ) + { + page->nr_validated_ptes = i; + page->partial_pte = 0; + rc = -ERESTART; + } + else if ( rc < 0 && rc != -EINTR ) { gdprintk(XENLOG_WARNING, "Failure in alloc_l2_table: slot %#x\n", i); - while ( i-- > 0 ) - if ( is_guest_l2_slot(d, type, i) ) - put_page_from_l2e(pl2e[i], pfn); - break; + if ( i ) + { + page->nr_validated_ptes = i; + page->partial_pte = 0; + current->arch.old_guest_ptpg = NULL; + current->arch.old_guest_table = page; + } } + if ( rc < 0 ) + break; adjust_guest_l2e(pl2e[i], d); } @@ -1919,28 +1948,50 @@ static int free_l2_table(struct page_inf struct domain *d = page_get_owner(page); unsigned long pfn = page_to_mfn(page); l2_pgentry_t *pl2e; - unsigned int i = page->nr_validated_ptes - 1; - int err = 0; + int rc = 0, partial = page->partial_pte; + unsigned int i = page->nr_validated_ptes - !partial; pl2e = map_domain_page(_mfn(pfn)); - ASSERT(page->nr_validated_ptes); - do { - if ( is_guest_l2_slot(d, page->u.inuse.type_info, i) && - put_page_from_l2e(pl2e[i], pfn) == 0 && - i && hypercall_preempt_check() ) + for ( ; ; ) + { + if ( is_guest_l2_slot(d, page->u.inuse.type_info, i) ) + rc = put_page_from_l2e(pl2e[i], pfn, partial, false); + if ( rc < 0 ) + break; + + partial = 0; + + if ( !i-- ) + break; + + if ( hypercall_preempt_check() ) { - page->nr_validated_ptes = i; - err = -ERESTART; + rc = -EINTR; + break; } - } while ( !err && i-- ); + } unmap_domain_page(pl2e); - if ( !err ) + if ( rc >= 0 ) + { page->u.inuse.type_info &= ~PGT_pae_xen_l2; + rc = 0; + } + else if ( rc == -ERESTART ) + { + page->nr_validated_ptes = i; + page->partial_pte = partial ?: -1; + } + else if ( rc == -EINTR && i < L2_PAGETABLE_ENTRIES - 1 ) + { + page->nr_validated_ptes = i + 1; + page->partial_pte = 0; + rc = -ERESTART; + } - return err; + return rc; } static int free_l3_table(struct page_info *page) @@ -2267,7 +2318,7 @@ static int mod_l2_entry(l2_pgentry_t *pl return -EBUSY; } - if ( unlikely((rc = get_page_from_l2e(nl2e, pfn, d)) < 0) ) + if ( unlikely((rc = get_page_from_l2e(nl2e, pfn, d, 0)) < 0) ) return rc; adjust_guest_l2e(nl2e, d); @@ -2286,7 +2337,8 @@ static int mod_l2_entry(l2_pgentry_t *pl return -EBUSY; } - put_page_from_l2e(ol2e, pfn); + put_page_from_l2e(ol2e, pfn, 0, true); + return rc; }
xsa290/4.9-2.patch
(application/octet-stream, 2.3 KB)
From: Jan Beulich <[email protected]> Subject: x86/mm: add explicit preemption checks to L3 (un)validation When recursive page tables are used at the L3 level, unvalidation of a single L4 table may incur unvalidation of two levels of L3 tables, i.e. a maximum iteration count of 512^3 for unvalidating an L4 table. The preemption check in free_l2_table() as well as the one in _put_page_type() may never be reached, so explicit checking is needed in free_l3_table(). When recursive page tables are used at the L4 level, the iteration count at L4 alone is capped at 512^2. As soon as a present L3 entry is hit which itself needs unvalidation (and hence requiring another nested loop with 512 iterations), the preemption checks added here kick in, so no further preemption checking is needed at L4 (until we decide to permit 5-level paging for PV guests). The validation side additions are done just for symmetry. This is part of XSA-290. Signed-off-by: Jan Beulich <[email protected]> Reviewed-by: Andrew Cooper <[email protected]> --- a/xen/arch/x86/mm.c +++ b/xen/arch/x86/mm.c @@ -1766,6 +1766,13 @@ static int alloc_l3_table(struct page_in for ( i = page->nr_validated_ptes; i < L3_PAGETABLE_ENTRIES; i++, partial = 0 ) { + 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) ) { if ( !(l3e_get_flags(pl3e[i]) & _PAGE_PRESENT) || @@ -2004,18 +2011,28 @@ static int free_l3_table(struct page_inf pl3e = map_domain_page(_mfn(pfn)); - do { + for ( ; ; ) + { if ( is_guest_l3_slot(i) ) { rc = put_page_from_l3e(pl3e[i], pfn, partial, 0); if ( rc < 0 ) break; + partial = 0; - if ( rc > 0 ) - continue; - unadjust_guest_l3e(pl3e[i], d); + if ( rc == 0 ) + unadjust_guest_l3e(pl3e[i], d); } - } while ( i-- ); + + if ( !i-- ) + break; + + if ( hypercall_preempt_check() ) + { + rc = -EINTR; + break; + } + } unmap_domain_page(pl3e);
xsa290/4.10-1.patch
(application/octet-stream, 7.4 KB)
From: Jan Beulich <[email protected]> Subject: x86/mm: also allow L2 (un)validation to be preemptible Commit c612481d1c ("x86/mm: Plumbing to allow any PTE update to fail with -ERESTART") added assertions next to the {alloc,free}_l2_table() invocations to document (and validate in debug builds) that L2 (un)validations are always preemptible. The assertion in free_page_type() was now observed to trigger when recursive L2 page tables get cleaned up. In particular put_page_from_l2e()'s assumption that _put_page_type() would always succeed is now wrong, resulting in a partially un-validated page left in a domain, which has no other means of getting cleaned up later on. If not causing any problems earlier, this would ultimately trigger the check for ->u.inuse.type_info having a zero count when freeing the page during cleanup after the domain has died. As a result it should be considered a mistake to not have extended preemption fully to L2 when it was added to L3/L4 table handling, which this change aims to correct. The validation side additions are done just for symmetry. This is part of XSA-290. Reported-by: Manuel Bouyer <[email protected]> Tested-by: Manuel Bouyer <[email protected]> Signed-off-by: Jan Beulich <[email protected]> Reviewed-by: Andrew Cooper <[email protected]> --- a/xen/arch/x86/mm.c +++ b/xen/arch/x86/mm.c @@ -1168,7 +1168,7 @@ get_page_from_l1e( define_get_linear_pagetable(l2); static int get_page_from_l2e( - l2_pgentry_t l2e, unsigned long pfn, struct domain *d) + l2_pgentry_t l2e, unsigned long pfn, struct domain *d, int partial) { unsigned long mfn = l2e_get_pfn(l2e); int rc; @@ -1185,7 +1185,8 @@ get_page_from_l2e( if ( !(l2e_get_flags(l2e) & _PAGE_PSE) ) { - rc = get_page_and_type_from_mfn(_mfn(mfn), PGT_l1_page_table, d, 0, 0); + rc = get_page_and_type_from_mfn(_mfn(mfn), PGT_l1_page_table, d, + partial, false); if ( unlikely(rc == -EINVAL) && get_l2_linear_pagetable(l2e, pfn, d) ) rc = 0; return rc; @@ -1326,8 +1327,11 @@ void put_page_from_l1e(l1_pgentry_t l1e, * NB. Virtual address 'l2e' maps to a machine address within frame 'pfn'. * 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) +static int put_page_from_l2e(l2_pgentry_t l2e, unsigned long pfn, + int partial, bool defer) { + int rc = 0; + if ( !(l2e_get_flags(l2e) & _PAGE_PRESENT) || (l2e_get_pfn(l2e) == pfn) ) return 1; @@ -1342,13 +1346,27 @@ static int put_page_from_l2e(l2_pgentry_ else { struct page_info *pg = l2e_get_page(l2e); - int rc = _put_page_type(pg, false, mfn_to_page(_mfn(pfn))); + struct page_info *ptpg = mfn_to_page(_mfn(pfn)); - ASSERT(!rc); - put_page(pg); + if ( unlikely(partial > 0) ) + { + ASSERT(!defer); + rc = _put_page_type(pg, true, ptpg); + } + else if ( defer ) + { + current->arch.old_guest_ptpg = ptpg; + current->arch.old_guest_table = pg; + } + else + { + rc = _put_page_type(pg, true, ptpg); + if ( likely(!rc) ) + put_page(pg); + } } - return 0; + return rc; } static int put_page_from_l3e(l3_pgentry_t l3e, unsigned long pfn, @@ -1518,11 +1536,12 @@ static int alloc_l2_table(struct page_in unsigned long pfn = mfn_x(page_to_mfn(page)); l2_pgentry_t *pl2e; unsigned int i; - int rc = 0; + int rc = 0, partial = page->partial_pte; pl2e = map_domain_page(_mfn(pfn)); - for ( i = page->nr_validated_ptes; i < L2_PAGETABLE_ENTRIES; i++ ) + for ( i = page->nr_validated_ptes; i < L2_PAGETABLE_ENTRIES; + i++, partial = 0 ) { if ( i > page->nr_validated_ptes && hypercall_preempt_check() ) { @@ -1532,23 +1551,33 @@ static int alloc_l2_table(struct page_in } if ( !is_guest_l2_slot(d, type, i) || - (rc = get_page_from_l2e(pl2e[i], pfn, d)) > 0 ) + (rc = get_page_from_l2e(pl2e[i], pfn, d, partial)) > 0 ) continue; - if ( unlikely(rc == -ERESTART) ) + if ( rc == -ERESTART ) { page->nr_validated_ptes = i; - break; + page->partial_pte = partial ?: 1; } - - if ( rc < 0 ) + else if ( rc == -EINTR && i ) + { + page->nr_validated_ptes = i; + page->partial_pte = 0; + rc = -ERESTART; + } + else if ( rc < 0 && rc != -EINTR ) { gdprintk(XENLOG_WARNING, "Failure in alloc_l2_table: slot %#x\n", i); - while ( i-- > 0 ) - if ( is_guest_l2_slot(d, type, i) ) - put_page_from_l2e(pl2e[i], pfn); - break; + if ( i ) + { + page->nr_validated_ptes = i; + page->partial_pte = 0; + current->arch.old_guest_ptpg = NULL; + current->arch.old_guest_table = page; + } } + if ( rc < 0 ) + break; pl2e[i] = adjust_guest_l2e(pl2e[i], d); } @@ -1828,28 +1857,50 @@ static int free_l2_table(struct page_inf struct domain *d = page_get_owner(page); unsigned long pfn = mfn_x(page_to_mfn(page)); l2_pgentry_t *pl2e; - unsigned int i = page->nr_validated_ptes - 1; - int err = 0; + int rc = 0, partial = page->partial_pte; + unsigned int i = page->nr_validated_ptes - !partial; pl2e = map_domain_page(_mfn(pfn)); - ASSERT(page->nr_validated_ptes); - do { - if ( is_guest_l2_slot(d, page->u.inuse.type_info, i) && - put_page_from_l2e(pl2e[i], pfn) == 0 && - i && hypercall_preempt_check() ) + for ( ; ; ) + { + if ( is_guest_l2_slot(d, page->u.inuse.type_info, i) ) + rc = put_page_from_l2e(pl2e[i], pfn, partial, false); + if ( rc < 0 ) + break; + + partial = 0; + + if ( !i-- ) + break; + + if ( hypercall_preempt_check() ) { - page->nr_validated_ptes = i; - err = -ERESTART; + rc = -EINTR; + break; } - } while ( !err && i-- ); + } unmap_domain_page(pl2e); - if ( !err ) + if ( rc >= 0 ) + { page->u.inuse.type_info &= ~PGT_pae_xen_l2; + rc = 0; + } + else if ( rc == -ERESTART ) + { + page->nr_validated_ptes = i; + page->partial_pte = partial ?: -1; + } + else if ( rc == -EINTR && i < L2_PAGETABLE_ENTRIES - 1 ) + { + page->nr_validated_ptes = i + 1; + page->partial_pte = 0; + rc = -ERESTART; + } - return err; + return rc; } static int free_l3_table(struct page_info *page) @@ -2115,7 +2166,7 @@ static int mod_l2_entry(l2_pgentry_t *pl return -EBUSY; } - if ( unlikely((rc = get_page_from_l2e(nl2e, pfn, d)) < 0) ) + if ( unlikely((rc = get_page_from_l2e(nl2e, pfn, d, 0)) < 0) ) return rc; nl2e = adjust_guest_l2e(nl2e, d); @@ -2134,7 +2185,8 @@ static int mod_l2_entry(l2_pgentry_t *pl return -EBUSY; } - put_page_from_l2e(ol2e, pfn); + put_page_from_l2e(ol2e, pfn, 0, true); + return rc; }
xsa290/4.10-2.patch
(application/octet-stream, 2.2 KB)
From: Jan Beulich <[email protected]> Subject: x86/mm: add explicit preemption checks to L3 (un)validation When recursive page tables are used at the L3 level, unvalidation of a single L4 table may incur unvalidation of two levels of L3 tables, i.e. a maximum iteration count of 512^3 for unvalidating an L4 table. The preemption check in free_l2_table() as well as the one in _put_page_type() may never be reached, so explicit checking is needed in free_l3_table(). When recursive page tables are used at the L4 level, the iteration count at L4 alone is capped at 512^2. As soon as a present L3 entry is hit which itself needs unvalidation (and hence requiring another nested loop with 512 iterations), the preemption checks added here kick in, so no further preemption checking is needed at L4 (until we decide to permit 5-level paging for PV guests). The validation side additions are done just for symmetry. This is part of XSA-290. Signed-off-by: Jan Beulich <[email protected]> Reviewed-by: Andrew Cooper <[email protected]> --- a/xen/arch/x86/mm.c +++ b/xen/arch/x86/mm.c @@ -1612,6 +1612,13 @@ static int alloc_l3_table(struct page_in for ( i = page->nr_validated_ptes; i < L3_PAGETABLE_ENTRIES; i++, partial = 0 ) { + 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) ) { if ( !(l3e_get_flags(pl3e[i]) & _PAGE_PRESENT) || @@ -1913,15 +1920,25 @@ static int free_l3_table(struct page_inf pl3e = map_domain_page(_mfn(pfn)); - do { + for ( ; ; ) + { rc = put_page_from_l3e(pl3e[i], pfn, partial, 0); if ( rc < 0 ) break; + partial = 0; - if ( rc > 0 ) - continue; - pl3e[i] = unadjust_guest_l3e(pl3e[i], d); - } while ( i-- ); + if ( rc == 0 ) + pl3e[i] = unadjust_guest_l3e(pl3e[i], d); + + if ( !i-- ) + break; + + if ( hypercall_preempt_check() ) + { + rc = -EINTR; + break; + } + } unmap_domain_page(pl3e);
xsa290/4.11-1.patch
(application/octet-stream, 7.3 KB)
From: Jan Beulich <[email protected]> Subject: x86/mm: also allow L2 (un)validation to be preemptible Commit c612481d1c ("x86/mm: Plumbing to allow any PTE update to fail with -ERESTART") added assertions next to the {alloc,free}_l2_table() invocations to document (and validate in debug builds) that L2 (un)validations are always preemptible. The assertion in free_page_type() was now observed to trigger when recursive L2 page tables get cleaned up. In particular put_page_from_l2e()'s assumption that _put_page_type() would always succeed is now wrong, resulting in a partially un-validated page left in a domain, which has no other means of getting cleaned up later on. If not causing any problems earlier, this would ultimately trigger the check for ->u.inuse.type_info having a zero count when freeing the page during cleanup after the domain has died. As a result it should be considered a mistake to not have extended preemption fully to L2 when it was added to L3/L4 table handling, which this change aims to correct. The validation side additions are done just for symmetry. This is part of XSA-290. Reported-by: Manuel Bouyer <[email protected]> Tested-by: Manuel Bouyer <[email protected]> Signed-off-by: Jan Beulich <[email protected]> Reviewed-by: Andrew Cooper <[email protected]> --- a/xen/arch/x86/mm.c +++ b/xen/arch/x86/mm.c @@ -1126,7 +1126,7 @@ get_page_from_l1e( define_get_linear_pagetable(l2); static int get_page_from_l2e( - l2_pgentry_t l2e, unsigned long pfn, struct domain *d) + l2_pgentry_t l2e, unsigned long pfn, struct domain *d, int partial) { unsigned long mfn = l2e_get_pfn(l2e); int rc; @@ -1141,7 +1141,8 @@ get_page_from_l2e( return -EINVAL; } - rc = get_page_and_type_from_mfn(_mfn(mfn), PGT_l1_page_table, d, 0, 0); + rc = get_page_and_type_from_mfn(_mfn(mfn), PGT_l1_page_table, d, + partial, false); if ( unlikely(rc == -EINVAL) && get_l2_linear_pagetable(l2e, pfn, d) ) rc = 0; @@ -1295,8 +1296,11 @@ void put_page_from_l1e(l1_pgentry_t l1e, * NB. Virtual address 'l2e' maps to a machine address within frame 'pfn'. * 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) +static int put_page_from_l2e(l2_pgentry_t l2e, unsigned long pfn, + int partial, bool defer) { + int rc = 0; + if ( !(l2e_get_flags(l2e) & _PAGE_PRESENT) || (l2e_get_pfn(l2e) == pfn) ) return 1; @@ -1311,13 +1315,27 @@ static int put_page_from_l2e(l2_pgentry_ else { struct page_info *pg = l2e_get_page(l2e); - int rc = _put_page_type(pg, false, mfn_to_page(_mfn(pfn))); + struct page_info *ptpg = mfn_to_page(_mfn(pfn)); - ASSERT(!rc); - put_page(pg); + if ( unlikely(partial > 0) ) + { + ASSERT(!defer); + rc = _put_page_type(pg, true, ptpg); + } + else if ( defer ) + { + current->arch.old_guest_ptpg = ptpg; + current->arch.old_guest_table = pg; + } + else + { + rc = _put_page_type(pg, true, ptpg); + if ( likely(!rc) ) + put_page(pg); + } } - return 0; + return rc; } static int put_page_from_l3e(l3_pgentry_t l3e, unsigned long pfn, @@ -1487,11 +1505,12 @@ static int alloc_l2_table(struct page_in unsigned long pfn = mfn_x(page_to_mfn(page)); l2_pgentry_t *pl2e; unsigned int i; - int rc = 0; + int rc = 0, partial = page->partial_pte; pl2e = map_domain_page(_mfn(pfn)); - for ( i = page->nr_validated_ptes; i < L2_PAGETABLE_ENTRIES; i++ ) + for ( i = page->nr_validated_ptes; i < L2_PAGETABLE_ENTRIES; + i++, partial = 0 ) { if ( i > page->nr_validated_ptes && hypercall_preempt_check() ) { @@ -1501,23 +1520,33 @@ static int alloc_l2_table(struct page_in } if ( !is_guest_l2_slot(d, type, i) || - (rc = get_page_from_l2e(pl2e[i], pfn, d)) > 0 ) + (rc = get_page_from_l2e(pl2e[i], pfn, d, partial)) > 0 ) continue; - if ( unlikely(rc == -ERESTART) ) + if ( rc == -ERESTART ) { page->nr_validated_ptes = i; - break; + page->partial_pte = partial ?: 1; } - - if ( rc < 0 ) + else if ( rc == -EINTR && i ) + { + page->nr_validated_ptes = i; + page->partial_pte = 0; + rc = -ERESTART; + } + else if ( rc < 0 && rc != -EINTR ) { gdprintk(XENLOG_WARNING, "Failure in alloc_l2_table: slot %#x\n", i); - while ( i-- > 0 ) - if ( is_guest_l2_slot(d, type, i) ) - put_page_from_l2e(pl2e[i], pfn); - break; + if ( i ) + { + page->nr_validated_ptes = i; + page->partial_pte = 0; + current->arch.old_guest_ptpg = NULL; + current->arch.old_guest_table = page; + } } + if ( rc < 0 ) + break; pl2e[i] = adjust_guest_l2e(pl2e[i], d); } @@ -1797,28 +1826,50 @@ static int free_l2_table(struct page_inf struct domain *d = page_get_owner(page); unsigned long pfn = mfn_x(page_to_mfn(page)); l2_pgentry_t *pl2e; - unsigned int i = page->nr_validated_ptes - 1; - int err = 0; + int rc = 0, partial = page->partial_pte; + unsigned int i = page->nr_validated_ptes - !partial; pl2e = map_domain_page(_mfn(pfn)); - ASSERT(page->nr_validated_ptes); - do { - if ( is_guest_l2_slot(d, page->u.inuse.type_info, i) && - put_page_from_l2e(pl2e[i], pfn) == 0 && - i && hypercall_preempt_check() ) + for ( ; ; ) + { + if ( is_guest_l2_slot(d, page->u.inuse.type_info, i) ) + rc = put_page_from_l2e(pl2e[i], pfn, partial, false); + if ( rc < 0 ) + break; + + partial = 0; + + if ( !i-- ) + break; + + if ( hypercall_preempt_check() ) { - page->nr_validated_ptes = i; - err = -ERESTART; + rc = -EINTR; + break; } - } while ( !err && i-- ); + } unmap_domain_page(pl2e); - if ( !err ) + if ( rc >= 0 ) + { page->u.inuse.type_info &= ~PGT_pae_xen_l2; + rc = 0; + } + else if ( rc == -ERESTART ) + { + page->nr_validated_ptes = i; + page->partial_pte = partial ?: -1; + } + else if ( rc == -EINTR && i < L2_PAGETABLE_ENTRIES - 1 ) + { + page->nr_validated_ptes = i + 1; + page->partial_pte = 0; + rc = -ERESTART; + } - return err; + return rc; } static int free_l3_table(struct page_info *page) @@ -2138,7 +2189,7 @@ static int mod_l2_entry(l2_pgentry_t *pl return -EBUSY; } - if ( unlikely((rc = get_page_from_l2e(nl2e, pfn, d)) < 0) ) + if ( unlikely((rc = get_page_from_l2e(nl2e, pfn, d, 0)) < 0) ) return rc; nl2e = adjust_guest_l2e(nl2e, d); @@ -2157,7 +2208,8 @@ static int mod_l2_entry(l2_pgentry_t *pl return -EBUSY; } - put_page_from_l2e(ol2e, pfn); + put_page_from_l2e(ol2e, pfn, 0, true); + return rc; }
xsa290/4.11-2.patch
(application/octet-stream, 2.2 KB)
From: Jan Beulich <[email protected]> Subject: x86/mm: add explicit preemption checks to L3 (un)validation When recursive page tables are used at the L3 level, unvalidation of a single L4 table may incur unvalidation of two levels of L3 tables, i.e. a maximum iteration count of 512^3 for unvalidating an L4 table. The preemption check in free_l2_table() as well as the one in _put_page_type() may never be reached, so explicit checking is needed in free_l3_table(). When recursive page tables are used at the L4 level, the iteration count at L4 alone is capped at 512^2. As soon as a present L3 entry is hit which itself needs unvalidation (and hence requiring another nested loop with 512 iterations), the preemption checks added here kick in, so no further preemption checking is needed at L4 (until we decide to permit 5-level paging for PV guests). The validation side additions are done just for symmetry. This is part of XSA-290. Signed-off-by: Jan Beulich <[email protected]> Reviewed-by: Andrew Cooper <[email protected]> --- a/xen/arch/x86/mm.c +++ b/xen/arch/x86/mm.c @@ -1581,6 +1581,13 @@ static int alloc_l3_table(struct page_in for ( i = page->nr_validated_ptes; i < L3_PAGETABLE_ENTRIES; i++, partial = 0 ) { + 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) ) { if ( !(l3e_get_flags(pl3e[i]) & _PAGE_PRESENT) || @@ -1882,15 +1889,25 @@ static int free_l3_table(struct page_inf pl3e = map_domain_page(_mfn(pfn)); - do { + for ( ; ; ) + { rc = put_page_from_l3e(pl3e[i], pfn, partial, 0); if ( rc < 0 ) break; + partial = 0; - if ( rc > 0 ) - continue; - pl3e[i] = unadjust_guest_l3e(pl3e[i], d); - } while ( i-- ); + if ( rc == 0 ) + pl3e[i] = unadjust_guest_l3e(pl3e[i], d); + + if ( !i-- ) + break; + + if ( hypercall_preempt_check() ) + { + rc = -EINTR; + break; + } + } unmap_domain_page(pl3e);
xsa290/unstable-1.patch
(application/octet-stream, 7.3 KB)
From: Jan Beulich <[email protected]> Subject: x86/mm: also allow L2 (un)validation to be fully preemptible Commit c612481d1c ("x86/mm: Plumbing to allow any PTE update to fail with -ERESTART") added assertions next to the {alloc,free}_l2_table() invocations to document (and validate in debug builds) that L2 (un)validations are always preemptible. The assertion in free_page_type() was now observed to trigger when recursive L2 page tables get cleaned up. In particular put_page_from_l2e()'s assumption that _put_page_type() would always succeed is now wrong, resulting in a partially un-validated page left in a domain, which has no other means of getting cleaned up later on. If not causing any problems earlier, this would ultimately trigger the check for ->u.inuse.type_info having a zero count when freeing the page during cleanup after the domain has died. As a result it should be considered a mistake to not have extended preemption fully to L2 when it was added to L3/L4 table handling, which this change aims to correct. The validation side additions are done just for symmetry. This is part of XSA-290. Reported-by: Manuel Bouyer <[email protected]> Tested-by: Manuel Bouyer <[email protected]> Signed-off-by: Jan Beulich <[email protected]> Reviewed-by: Andrew Cooper <[email protected]> --- v3: Re-written description. v2: Clarify that it's mainly unvalidation which needs adjustment. --- a/xen/arch/x86/mm.c +++ b/xen/arch/x86/mm.c @@ -1123,7 +1123,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) + l2_pgentry_t l2e, unsigned long pfn, struct domain *d, int partial) { unsigned long mfn = l2e_get_pfn(l2e); int rc; @@ -1135,7 +1135,8 @@ get_page_from_l2e( return -EINVAL; } - rc = get_page_and_type_from_mfn(_mfn(mfn), PGT_l1_page_table, d, 0, 0); + rc = get_page_and_type_from_mfn(_mfn(mfn), PGT_l1_page_table, d, + partial, false); if ( unlikely(rc == -EINVAL) && get_l2_linear_pagetable(l2e, pfn, d) ) rc = 0; @@ -1281,8 +1282,11 @@ static void put_data_page(struct page_in * NB. Virtual address 'l2e' maps to a machine address within frame 'pfn'. * 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) +static int put_page_from_l2e(l2_pgentry_t l2e, unsigned long pfn, + int partial, bool defer) { + int rc = 0; + if ( !(l2e_get_flags(l2e) & _PAGE_PRESENT) || (l2e_get_pfn(l2e) == pfn) ) return 1; @@ -1300,13 +1304,27 @@ static int put_page_from_l2e(l2_pgentry_ else { struct page_info *pg = l2e_get_page(l2e); - int rc = _put_page_type(pg, false, mfn_to_page(_mfn(pfn))); + struct page_info *ptpg = mfn_to_page(_mfn(pfn)); - ASSERT(!rc); - put_page(pg); + if ( unlikely(partial > 0) ) + { + ASSERT(!defer); + rc = _put_page_type(pg, true, ptpg); + } + else if ( defer ) + { + current->arch.old_guest_ptpg = ptpg; + current->arch.old_guest_table = pg; + } + else + { + rc = _put_page_type(pg, true, ptpg); + if ( likely(!rc) ) + put_page(pg); + } } - return 0; + return rc; } static int put_page_from_l3e(l3_pgentry_t l3e, unsigned long pfn, @@ -1480,11 +1498,12 @@ static int alloc_l2_table(struct page_in unsigned long pfn = mfn_x(page_to_mfn(page)); l2_pgentry_t *pl2e; unsigned int i; - int rc = 0; + int rc = 0, partial = page->partial_pte; pl2e = map_domain_page(_mfn(pfn)); - for ( i = page->nr_validated_ptes; i < L2_PAGETABLE_ENTRIES; i++ ) + for ( i = page->nr_validated_ptes; i < L2_PAGETABLE_ENTRIES; + i++, partial = 0 ) { l2_pgentry_t l2e; @@ -1507,22 +1526,32 @@ static int alloc_l2_table(struct page_in rc = -ERESTART; } else - rc = get_page_from_l2e(l2e, pfn, d); + rc = get_page_from_l2e(l2e, pfn, d, partial); - if ( unlikely(rc == -ERESTART) ) + if ( rc == -ERESTART ) { page->nr_validated_ptes = i; - break; + page->partial_pte = partial ?: 1; } - - if ( rc < 0 ) + else if ( rc == -EINTR && i ) + { + page->nr_validated_ptes = i; + page->partial_pte = 0; + rc = -ERESTART; + } + else if ( rc < 0 && rc != -EINTR ) { gdprintk(XENLOG_WARNING, "Failure in alloc_l2_table: slot %#x\n", i); - while ( i-- > 0 ) - if ( is_guest_l2_slot(d, type, i) ) - put_page_from_l2e(pl2e[i], pfn); - break; + if ( i ) + { + page->nr_validated_ptes = i; + page->partial_pte = 0; + current->arch.old_guest_ptpg = NULL; + current->arch.old_guest_table = page; + } } + if ( rc < 0 ) + break; pl2e[i] = adjust_guest_l2e(l2e, d); } @@ -1823,28 +1852,50 @@ static int free_l2_table(struct page_inf struct domain *d = page_get_owner(page); unsigned long pfn = mfn_x(page_to_mfn(page)); l2_pgentry_t *pl2e; - unsigned int i = page->nr_validated_ptes - 1; - int err = 0; + int rc = 0, partial = page->partial_pte; + unsigned int i = page->nr_validated_ptes - !partial; pl2e = map_domain_page(_mfn(pfn)); - ASSERT(page->nr_validated_ptes); - do { - if ( is_guest_l2_slot(d, page->u.inuse.type_info, i) && - put_page_from_l2e(pl2e[i], pfn) == 0 && - i && hypercall_preempt_check() ) + for ( ; ; ) + { + if ( is_guest_l2_slot(d, page->u.inuse.type_info, i) ) + rc = put_page_from_l2e(pl2e[i], pfn, partial, false); + if ( rc < 0 ) + break; + + partial = 0; + + if ( !i-- ) + break; + + if ( hypercall_preempt_check() ) { - page->nr_validated_ptes = i; - err = -ERESTART; + rc = -EINTR; + break; } - } while ( !err && i-- ); + } unmap_domain_page(pl2e); - if ( !err ) + if ( rc >= 0 ) + { page->u.inuse.type_info &= ~PGT_pae_xen_l2; + rc = 0; + } + else if ( rc == -ERESTART ) + { + page->nr_validated_ptes = i; + page->partial_pte = partial ?: -1; + } + else if ( rc == -EINTR && i < L2_PAGETABLE_ENTRIES - 1 ) + { + page->nr_validated_ptes = i + 1; + page->partial_pte = 0; + rc = -ERESTART; + } - return err; + return rc; } static int free_l3_table(struct page_info *page) @@ -2166,7 +2217,7 @@ static int mod_l2_entry(l2_pgentry_t *pl return -EBUSY; } - if ( unlikely((rc = get_page_from_l2e(nl2e, pfn, d)) < 0) ) + if ( unlikely((rc = get_page_from_l2e(nl2e, pfn, d, 0)) < 0) ) return rc; nl2e = adjust_guest_l2e(nl2e, d); @@ -2185,7 +2236,8 @@ static int mod_l2_entry(l2_pgentry_t *pl return -EBUSY; } - put_page_from_l2e(ol2e, pfn); + put_page_from_l2e(ol2e, pfn, 0, true); + return rc; }
xsa290/unstable-2.patch
(application/octet-stream, 2.2 KB)
From: Jan Beulich <[email protected]> Subject: x86/mm: add explicit preemption checks to L3 (un)validation When recursive page tables are used at the L3 level, unvalidation of a single L4 table may incur unvalidation of two levels of L3 tables, i.e. a maximum iteration count of 512^3 for unvalidating an L4 table. The preemption check in free_l2_table() as well as the one in _put_page_type() may never be reached, so explicit checking is needed in free_l3_table(). When recursive page tables are used at the L4 level, the iteration count at L4 alone is capped at 512^2. As soon as a present L3 entry is hit which itself needs unvalidation (and hence requiring another nested loop with 512 iterations), the preemption checks added here kick in, so no further preemption checking is needed at L4 (until we decide to permit 5-level paging for PV guests). The validation side additions are done just for symmetry. This is part of XSA-290. Signed-off-by: Jan Beulich <[email protected]> Reviewed-by: Andrew Cooper <[email protected]> --- v2: Clarify that it's mainly unvalidation which needs adjustment. --- a/xen/arch/x86/mm.c +++ b/xen/arch/x86/mm.c @@ -1588,6 +1588,13 @@ static int alloc_l3_table(struct page_in { 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) ) { if ( !(l3e_get_flags(l3e) & _PAGE_PRESENT) || @@ -1908,15 +1915,25 @@ static int free_l3_table(struct page_inf pl3e = map_domain_page(_mfn(pfn)); - do { + for ( ; ; ) + { rc = put_page_from_l3e(pl3e[i], pfn, partial, 0); if ( rc < 0 ) break; + partial = 0; - if ( rc > 0 ) - continue; - pl3e[i] = unadjust_guest_l3e(pl3e[i], d); - } while ( i-- ); + if ( rc == 0 ) + pl3e[i] = unadjust_guest_l3e(pl3e[i], d); + + if ( !i-- ) + break; + + if ( hypercall_preempt_check() ) + { + rc = -EINTR; + break; + } + } unmap_domain_page(pl3e);