diff options
author | Jan Beulich <jbeulich@suse.com> | 2013-05-02 16:34:21 +0200 |
---|---|---|
committer | Jan Beulich <jbeulich@suse.com> | 2013-05-02 16:34:21 +0200 |
commit | 6cdc9be2a5f2a87b4504404fbf648d16d9503c19 (patch) | |
tree | 57244e0df782cc38467cf1045314806967c72ba9 | |
parent | 9626d1c1fafe2da5af6e59478c9e9db6d03144df (diff) | |
download | xen-6cdc9be2a5f2a87b4504404fbf648d16d9503c19.tar.gz xen-6cdc9be2a5f2a87b4504404fbf648d16d9503c19.tar.bz2 xen-6cdc9be2a5f2a87b4504404fbf648d16d9503c19.zip |
x86: make vcpu_destroy_pagetables() preemptible
... as it may take significant amounts of time.
The function, being moved to mm.c as the better home for it anyway, and
to avoid having to make a new helper function there non-static, is
given a "preemptible" parameter temporarily (until, in a subsequent
patch, its other caller is also being made capable of dealing with
preemption).
This is part of CVE-2013-1918 / XSA-45.
Signed-off-by: Jan Beulich <jbeulich@suse.com>
Acked-by: Tim Deegan <tim@xen.org>
-rw-r--r-- | xen/arch/x86/domain.c | 60 | ||||
-rw-r--r-- | xen/arch/x86/mm.c | 86 | ||||
-rw-r--r-- | xen/arch/x86/x86_64/compat/mm.c | 2 | ||||
-rw-r--r-- | xen/include/asm-x86/domain.h | 1 | ||||
-rw-r--r-- | xen/include/asm-x86/mm.h | 1 |
5 files changed, 91 insertions, 59 deletions
diff --git a/xen/arch/x86/domain.c b/xen/arch/x86/domain.c index 3e09f58279..bc1a31f7e6 100644 --- a/xen/arch/x86/domain.c +++ b/xen/arch/x86/domain.c @@ -71,8 +71,6 @@ void (*dead_idle) (void) __read_mostly = default_dead_idle; static void paravirt_ctxt_switch_from(struct vcpu *v); static void paravirt_ctxt_switch_to(struct vcpu *v); -static void vcpu_destroy_pagetables(struct vcpu *v); - static void default_idle(void) { local_irq_disable(); @@ -946,7 +944,7 @@ void arch_vcpu_reset(struct vcpu *v) if ( !is_hvm_vcpu(v) ) { destroy_gdt(v); - vcpu_destroy_pagetables(v); + vcpu_destroy_pagetables(v, 0); } else { @@ -1944,58 +1942,6 @@ static int relinquish_memory( return ret; } -static void vcpu_destroy_pagetables(struct vcpu *v) -{ - struct domain *d = v->domain; - unsigned long pfn = pagetable_get_pfn(v->arch.guest_table); - - if ( is_pv_32on64_vcpu(v) ) - { - l4_pgentry_t *l4tab = map_domain_page(pfn); - - pfn = l4e_get_pfn(*l4tab); - - if ( pfn != 0 ) - { - if ( paging_mode_refcounts(d) ) - put_page(mfn_to_page(pfn)); - else - put_page_and_type(mfn_to_page(pfn)); - } - - l4e_write(l4tab, l4e_empty()); - unmap_domain_page(l4tab); - - v->arch.cr3 = 0; - return; - } - - if ( pfn != 0 ) - { - if ( paging_mode_refcounts(d) ) - put_page(mfn_to_page(pfn)); - else - put_page_and_type(mfn_to_page(pfn)); - v->arch.guest_table = pagetable_null(); - } - - /* Drop ref to guest_table_user (from MMUEXT_NEW_USER_BASEPTR) */ - pfn = pagetable_get_pfn(v->arch.guest_table_user); - if ( pfn != 0 ) - { - if ( !is_pv_32bit_vcpu(v) ) - { - if ( paging_mode_refcounts(d) ) - put_page(mfn_to_page(pfn)); - else - put_page_and_type(mfn_to_page(pfn)); - } - v->arch.guest_table_user = pagetable_null(); - } - - v->arch.cr3 = 0; -} - int domain_relinquish_resources(struct domain *d) { int ret; @@ -2014,7 +1960,9 @@ int domain_relinquish_resources(struct domain *d) /* Drop the in-use references to page-table bases. */ for_each_vcpu ( d, v ) { - vcpu_destroy_pagetables(v); + ret = vcpu_destroy_pagetables(v, 1); + if ( ret ) + return ret; unmap_vcpu_info(v); } diff --git a/xen/arch/x86/mm.c b/xen/arch/x86/mm.c index 0cd4203b62..9a076e9d36 100644 --- a/xen/arch/x86/mm.c +++ b/xen/arch/x86/mm.c @@ -2579,6 +2579,79 @@ static void put_superpage(unsigned long mfn) return; } +static int put_old_guest_table(struct vcpu *v) +{ + int rc; + + if ( !v->arch.old_guest_table ) + return 0; + + switch ( rc = put_page_and_type_preemptible(v->arch.old_guest_table, 1) ) + { + case -EINTR: + case -EAGAIN: + return -EAGAIN; + } + + v->arch.old_guest_table = NULL; + + return rc; +} + +int vcpu_destroy_pagetables(struct vcpu *v, bool_t preemptible) +{ + unsigned long mfn = pagetable_get_pfn(v->arch.guest_table); + struct page_info *page; + l4_pgentry_t *l4tab = NULL; + int rc = put_old_guest_table(v); + + if ( rc ) + return rc; + + if ( is_pv_32on64_vcpu(v) ) + { + l4tab = map_domain_page(mfn); + mfn = l4e_get_pfn(*l4tab); + } + + if ( mfn ) + { + page = mfn_to_page(mfn); + if ( paging_mode_refcounts(v->domain) ) + put_page(page); + else + rc = put_page_and_type_preemptible(page, preemptible); + } + + if ( l4tab ) + { + if ( !rc ) + l4e_write(l4tab, l4e_empty()); + unmap_domain_page(l4tab); + } + else if ( !rc ) + { + v->arch.guest_table = pagetable_null(); + + /* Drop ref to guest_table_user (from MMUEXT_NEW_USER_BASEPTR) */ + mfn = pagetable_get_pfn(v->arch.guest_table_user); + if ( mfn ) + { + page = mfn_to_page(mfn); + if ( paging_mode_refcounts(v->domain) ) + put_page(page); + else + rc = put_page_and_type_preemptible(page, preemptible); + } + if ( !rc ) + v->arch.guest_table_user = pagetable_null(); + } + + v->arch.cr3 = 0; + + return rc; +} + int new_guest_cr3(unsigned long mfn) { struct vcpu *curr = current; @@ -2733,12 +2806,21 @@ long do_mmuext_op( unsigned int foreigndom) { struct mmuext_op op; - int rc = 0, i = 0, okay; unsigned long type; - unsigned int done = 0; + unsigned int i = 0, done = 0; struct vcpu *curr = current; struct domain *d = curr->domain; struct domain *pg_owner; + int okay, rc = put_old_guest_table(curr); + + if ( unlikely(rc) ) + { + if ( likely(rc == -EAGAIN) ) + rc = hypercall_create_continuation( + __HYPERVISOR_mmuext_op, "hihi", uops, count, pdone, + foreigndom); + return rc; + } if ( unlikely(count & MMU_UPDATE_PREEMPTED) ) { diff --git a/xen/arch/x86/x86_64/compat/mm.c b/xen/arch/x86/x86_64/compat/mm.c index 75e422d432..9adc4b472b 100644 --- a/xen/arch/x86/x86_64/compat/mm.c +++ b/xen/arch/x86/x86_64/compat/mm.c @@ -365,7 +365,7 @@ int compat_mmuext_op(XEN_GUEST_HANDLE_PARAM(mmuext_op_compat_t) cmp_uops, : mcs->call.args[1]; unsigned int left = arg1 & ~MMU_UPDATE_PREEMPTED; - BUG_ON(left == arg1); + BUG_ON(left == arg1 && left != i); BUG_ON(left > count); guest_handle_add_offset(nat_ops, i - left); guest_handle_subtract_offset(cmp_uops, left); diff --git a/xen/include/asm-x86/domain.h b/xen/include/asm-x86/domain.h index bdaf714ef6..83fbe58613 100644 --- a/xen/include/asm-x86/domain.h +++ b/xen/include/asm-x86/domain.h @@ -405,6 +405,7 @@ struct arch_vcpu pagetable_t guest_table_user; /* (MFN) x86/64 user-space pagetable */ pagetable_t guest_table; /* (MFN) guest notion of cr3 */ + struct page_info *old_guest_table; /* partially destructed pagetable */ /* guest_table holds a ref to the page, and also a type-count unless * shadow refcounts are in use */ pagetable_t shadow_table[4]; /* (MFN) shadow(s) of guest */ diff --git a/xen/include/asm-x86/mm.h b/xen/include/asm-x86/mm.h index 031525735b..0f7297abec 100644 --- a/xen/include/asm-x86/mm.h +++ b/xen/include/asm-x86/mm.h @@ -556,6 +556,7 @@ void audit_domains(void); int new_guest_cr3(unsigned long pfn); void make_cr3(struct vcpu *v, unsigned long mfn); void update_cr3(struct vcpu *v); +int vcpu_destroy_pagetables(struct vcpu *, bool_t preemptible); void propagate_page_fault(unsigned long addr, u16 error_code); void *do_page_walk(struct vcpu *v, unsigned long addr); |