Xen Security Advisory 336 v3 (CVE-2020-25604) - race when migrating timers between x86 HVM vCPU-s
Xen.org security team <[email protected]>
| Newsgroups | gmane.comp.emulators.xen.announce |
|---|---|
| Message-ID | <E1kKiTr-0002Ip-K6__7755.57221365801$1600781983$gmane$org@xenbits.xenproject.org> |
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256
Xen Security Advisory CVE-2020-25604 / XSA-336
version 3
race when migrating timers between x86 HVM vCPU-s
UPDATES IN VERSION 3
====================
Public release.
ISSUE DESCRIPTION
=================
When migrating timers of x86 HVM guests between its vCPU-s, the locking
model used allows for a second vCPU of the same guest also operating on
the timers to release a lock that it didn't acquire.
IMPACT
======
The most likely effect of the issue is a hang or crash of the
hypervisor, i.e. a Denial of Service (DoS).
VULNERABLE SYSTEMS
==================
All versions of Xen are affected.
Only x86 systems are vulnerable. Arm systems are not vulnerable.
Only x86 HVM guests can leverage the vulnerability. x86 PV and PVH
cannot leverage the vulnerability.
Only guests with more than one vCPU can exploit the vulnerability.
MITIGATION
==========
Running only PV and PVH guests will avoid the vulnerability.
CREDITS
=======
This issue was discovered by Igor Druzhinin of Citrix.
RESOLUTION
==========
Applying the appropriate attached patch resolves this issue.
Note that patches for released versions are generally prepared to
apply to the stable branches, and may not apply cleanly to the most
recent release tarball. Downstreams are encouraged to update to the
tip of the stable branch before applying these patches.
xsa336.patch Xen 4.12 - xen-unstable
xsa336-4.11.patch Xen 4.10 - 4.11
$ sha256sum xsa336*
6cb13a54c2b0fcb6948a1c4045095da4e43aad262a1dd8993ea2a3bd90d4c72d xsa336.meta
ecb59876fb92cfe0916ed5f3227a30efe038224c1f6ec36bc3706c4e2214552c xsa336.patch
c0c7983bfd70eb54277af9fddfcc3cc95bbd745d92d9ffb71d5b32281c437510 xsa336-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/4UyVfoK9kFAl9p/eYMHHBncEB4ZW4u
b3JnAAoJEIP+FMlX6CvZe28H/3oTZLe4eVhykU7a+BbN7ENJ2WMYsj0VM5wUiQyK
ZrY3CnbO0ne6h0BeAgSNG1XRP9QvwJLOIm6gZkqoNCWyJK2IbCO/mlF4czBlpUBR
FtM2wJz4FLzkiYMozk8TOZk6pCW6gaqxNiYr2L/3ijh2PQCMwnte/u+T3mZAAWxB
nJbVnwux26nvRY/5XBZ7cZ/Qxi1DKed2cyf2A9oZ/AmGIMBT2r6SZ+arf+d4jHRG
yQok+7gdXr1lOL/pPZZWepHtbPJMrrYxQZN/zKGt20c9ksBLiOyyQxTO4tegLx7N
PxRgzy+DgY+xqYFA68xpM6jJxfWYmHpjAtbYtQoPvyPsIag=
=0Kkw
-----END PGP SIGNATURE-----
xsa336.meta
(application/octet-stream, 1.7 KB) - not displayed
xsa336.patch
(application/octet-stream, 8.4 KB)
From: Roger Pau Monné <[email protected]> Subject: x86/vpt: fix race when migrating timers between vCPUs The current vPT code will migrate the emulated timers between vCPUs (change the pt->vcpu field) while just holding the destination lock, either from create_periodic_time or pt_adjust_global_vcpu_target if the global target is adjusted. Changing the periodic_timer vCPU field in this way creates a race where a third party could grab the lock in the unlocked region of pt_adjust_global_vcpu_target (or before create_periodic_time performs the vcpu change) and then release the lock from a different vCPU, creating a locking imbalance. Introduce a per-domain rwlock in order to protect periodic_time migration between vCPU lists. Taking the lock in read mode prevents any timer from being migrated to a different vCPU, while taking it in write mode allows performing migration of timers across vCPUs. The per-vcpu locks are still used to protect all the other fields from the periodic_timer struct. Note that such migration shouldn't happen frequently, and hence there's no performance drop as a result of such locking. This is XSA-336. Reported-by: Igor Druzhinin <[email protected]> Tested-by: Igor Druzhinin <[email protected]> Signed-off-by: Roger Pau Monné <[email protected]> Reviewed-by: Jan Beulich <[email protected]> --- Changes since v2: - Re-order pt_adjust_vcpu to remove one if. - Fix pt_lock to not call pt_vcpu_lock, as we might end up using a stale value of pt->vcpu when taking the per-vcpu lock. Changes since v1: - Use a per-domain rwlock to protect timer vCPU migration. --- a/xen/arch/x86/hvm/hvm.c +++ b/xen/arch/x86/hvm/hvm.c @@ -658,6 +658,8 @@ int hvm_domain_initialise(struct domain /* need link to containing domain */ d->arch.hvm.pl_time->domain = d; + rwlock_init(&d->arch.hvm.pl_time->pt_migrate); + /* Set the default IO Bitmap. */ if ( is_hardware_domain(d) ) { --- a/xen/arch/x86/hvm/vpt.c +++ b/xen/arch/x86/hvm/vpt.c @@ -153,23 +153,32 @@ static int pt_irq_masked(struct periodic return 1; } -static void pt_lock(struct periodic_time *pt) +static void pt_vcpu_lock(struct vcpu *v) { - struct vcpu *v; + read_lock(&v->domain->arch.hvm.pl_time->pt_migrate); + spin_lock(&v->arch.hvm.tm_lock); +} - for ( ; ; ) - { - v = pt->vcpu; - spin_lock(&v->arch.hvm.tm_lock); - if ( likely(pt->vcpu == v) ) - break; - spin_unlock(&v->arch.hvm.tm_lock); - } +static void pt_vcpu_unlock(struct vcpu *v) +{ + spin_unlock(&v->arch.hvm.tm_lock); + read_unlock(&v->domain->arch.hvm.pl_time->pt_migrate); +} + +static void pt_lock(struct periodic_time *pt) +{ + /* + * We cannot use pt_vcpu_lock here, because we need to acquire the + * per-domain lock first and then (re-)fetch the value of pt->vcpu, or + * else we might be using a stale value of pt->vcpu. + */ + read_lock(&pt->vcpu->domain->arch.hvm.pl_time->pt_migrate); + spin_lock(&pt->vcpu->arch.hvm.tm_lock); } static void pt_unlock(struct periodic_time *pt) { - spin_unlock(&pt->vcpu->arch.hvm.tm_lock); + pt_vcpu_unlock(pt->vcpu); } static void pt_process_missed_ticks(struct periodic_time *pt) @@ -219,7 +228,7 @@ void pt_save_timer(struct vcpu *v) if ( v->pause_flags & VPF_blocked ) return; - spin_lock(&v->arch.hvm.tm_lock); + pt_vcpu_lock(v); list_for_each_entry ( pt, head, list ) if ( !pt->do_not_freeze ) @@ -227,7 +236,7 @@ void pt_save_timer(struct vcpu *v) pt_freeze_time(v); - spin_unlock(&v->arch.hvm.tm_lock); + pt_vcpu_unlock(v); } void pt_restore_timer(struct vcpu *v) @@ -235,7 +244,7 @@ void pt_restore_timer(struct vcpu *v) struct list_head *head = &v->arch.hvm.tm_list; struct periodic_time *pt; - spin_lock(&v->arch.hvm.tm_lock); + pt_vcpu_lock(v); list_for_each_entry ( pt, head, list ) { @@ -248,7 +257,7 @@ void pt_restore_timer(struct vcpu *v) pt_thaw_time(v); - spin_unlock(&v->arch.hvm.tm_lock); + pt_vcpu_unlock(v); } static void pt_timer_fn(void *data) @@ -309,7 +318,7 @@ int pt_update_irq(struct vcpu *v) int irq, pt_vector = -1; bool level; - spin_lock(&v->arch.hvm.tm_lock); + pt_vcpu_lock(v); earliest_pt = NULL; max_lag = -1ULL; @@ -339,7 +348,7 @@ int pt_update_irq(struct vcpu *v) if ( earliest_pt == NULL ) { - spin_unlock(&v->arch.hvm.tm_lock); + pt_vcpu_unlock(v); return -1; } @@ -347,7 +356,7 @@ int pt_update_irq(struct vcpu *v) irq = earliest_pt->irq; level = earliest_pt->level; - spin_unlock(&v->arch.hvm.tm_lock); + pt_vcpu_unlock(v); switch ( earliest_pt->source ) { @@ -394,7 +403,7 @@ int pt_update_irq(struct vcpu *v) time_cb *cb = NULL; void *cb_priv; - spin_lock(&v->arch.hvm.tm_lock); + pt_vcpu_lock(v); /* Make sure the timer is still on the list. */ list_for_each_entry ( pt, &v->arch.hvm.tm_list, list ) if ( pt == earliest_pt ) @@ -404,7 +413,7 @@ int pt_update_irq(struct vcpu *v) cb_priv = pt->priv; break; } - spin_unlock(&v->arch.hvm.tm_lock); + pt_vcpu_unlock(v); if ( cb != NULL ) cb(v, cb_priv); @@ -441,12 +450,12 @@ void pt_intr_post(struct vcpu *v, struct if ( intack.source == hvm_intsrc_vector ) return; - spin_lock(&v->arch.hvm.tm_lock); + pt_vcpu_lock(v); pt = is_pt_irq(v, intack); if ( pt == NULL ) { - spin_unlock(&v->arch.hvm.tm_lock); + pt_vcpu_unlock(v); return; } @@ -455,7 +464,7 @@ void pt_intr_post(struct vcpu *v, struct cb = pt->cb; cb_priv = pt->priv; - spin_unlock(&v->arch.hvm.tm_lock); + pt_vcpu_unlock(v); if ( cb != NULL ) cb(v, cb_priv); @@ -466,12 +475,12 @@ void pt_migrate(struct vcpu *v) struct list_head *head = &v->arch.hvm.tm_list; struct periodic_time *pt; - spin_lock(&v->arch.hvm.tm_lock); + pt_vcpu_lock(v); list_for_each_entry ( pt, head, list ) migrate_timer(&pt->timer, v->processor); - spin_unlock(&v->arch.hvm.tm_lock); + pt_vcpu_unlock(v); } void create_periodic_time( @@ -490,7 +499,7 @@ void create_periodic_time( destroy_periodic_time(pt); - spin_lock(&v->arch.hvm.tm_lock); + write_lock(&v->domain->arch.hvm.pl_time->pt_migrate); pt->pending_intr_nr = 0; pt->do_not_freeze = 0; @@ -540,7 +549,7 @@ void create_periodic_time( init_timer(&pt->timer, pt_timer_fn, pt, v->processor); set_timer(&pt->timer, pt->scheduled); - spin_unlock(&v->arch.hvm.tm_lock); + write_unlock(&v->domain->arch.hvm.pl_time->pt_migrate); } void destroy_periodic_time(struct periodic_time *pt) @@ -565,30 +574,20 @@ void destroy_periodic_time(struct period static void pt_adjust_vcpu(struct periodic_time *pt, struct vcpu *v) { - int on_list; - ASSERT(pt->source == PTSRC_isa || pt->source == PTSRC_ioapic); if ( pt->vcpu == NULL ) return; - pt_lock(pt); - on_list = pt->on_list; - if ( pt->on_list ) - list_del(&pt->list); - pt->on_list = 0; - pt_unlock(pt); - - spin_lock(&v->arch.hvm.tm_lock); + write_lock(&pt->vcpu->domain->arch.hvm.pl_time->pt_migrate); pt->vcpu = v; - if ( on_list ) + if ( pt->on_list ) { - pt->on_list = 1; + list_del(&pt->list); list_add(&pt->list, &v->arch.hvm.tm_list); - migrate_timer(&pt->timer, v->processor); } - spin_unlock(&v->arch.hvm.tm_lock); + write_unlock(&pt->vcpu->domain->arch.hvm.pl_time->pt_migrate); } void pt_adjust_global_vcpu_target(struct vcpu *v) --- a/xen/include/asm-x86/hvm/vpt.h +++ b/xen/include/asm-x86/hvm/vpt.h @@ -128,6 +128,13 @@ struct pl_time { /* platform time */ struct RTCState vrtc; struct HPETState vhpet; struct PMTState vpmt; + /* + * rwlock to prevent periodic_time vCPU migration. Take the lock in read + * mode in order to prevent the vcpu field of periodic_time from changing. + * Lock must be taken in write mode when changes to the vcpu field are + * performed, as it allows exclusive access to all the timers of a domain. + */ + rwlock_t pt_migrate; /* guest_time = Xen sys time + stime_offset */ int64_t stime_offset; /* Ensures monotonicity in appropriate timer modes. */
xsa336-4.11.patch
(application/octet-stream, 7.6 KB)
From: Roger Pau Monné <[email protected]> Subject: x86/vpt: fix race when migrating timers between vCPUs The current vPT code will migrate the emulated timers between vCPUs (change the pt->vcpu field) while just holding the destination lock, either from create_periodic_time or pt_adjust_global_vcpu_target if the global target is adjusted. Changing the periodic_timer vCPU field in this way creates a race where a third party could grab the lock in the unlocked region of pt_adjust_global_vcpu_target (or before create_periodic_time performs the vcpu change) and then release the lock from a different vCPU, creating a locking imbalance. Introduce a per-domain rwlock in order to protect periodic_time migration between vCPU lists. Taking the lock in read mode prevents any timer from being migrated to a different vCPU, while taking it in write mode allows performing migration of timers across vCPUs. The per-vcpu locks are still used to protect all the other fields from the periodic_timer struct. Note that such migration shouldn't happen frequently, and hence there's no performance drop as a result of such locking. This is XSA-336. Reported-by: Igor Druzhinin <[email protected]> Tested-by: Igor Druzhinin <[email protected]> Signed-off-by: Roger Pau Monné <[email protected]> Reviewed-by: Jan Beulich <[email protected]> --- a/xen/arch/x86/hvm/hvm.c +++ b/xen/arch/x86/hvm/hvm.c @@ -627,6 +627,8 @@ int hvm_domain_initialise(struct domain /* need link to containing domain */ d->arch.hvm_domain.pl_time->domain = d; + rwlock_init(&d->arch.hvm_domain.pl_time->pt_migrate); + /* Set the default IO Bitmap. */ if ( is_hardware_domain(d) ) { --- a/xen/arch/x86/hvm/vpt.c +++ b/xen/arch/x86/hvm/vpt.c @@ -152,23 +152,32 @@ static int pt_irq_masked(struct periodic return 1; } -static void pt_lock(struct periodic_time *pt) +static void pt_vcpu_lock(struct vcpu *v) { - struct vcpu *v; + read_lock(&v->domain->arch.hvm_domain.pl_time->pt_migrate); + spin_lock(&v->arch.hvm_vcpu.tm_lock); +} - for ( ; ; ) - { - v = pt->vcpu; - spin_lock(&v->arch.hvm_vcpu.tm_lock); - if ( likely(pt->vcpu == v) ) - break; - spin_unlock(&v->arch.hvm_vcpu.tm_lock); - } +static void pt_vcpu_unlock(struct vcpu *v) +{ + spin_unlock(&v->arch.hvm_vcpu.tm_lock); + read_unlock(&v->domain->arch.hvm_domain.pl_time->pt_migrate); +} + +static void pt_lock(struct periodic_time *pt) +{ + /* + * We cannot use pt_vcpu_lock here, because we need to acquire the + * per-domain lock first and then (re-)fetch the value of pt->vcpu, or + * else we might be using a stale value of pt->vcpu. + */ + read_lock(&pt->vcpu->domain->arch.hvm_domain.pl_time->pt_migrate); + spin_lock(&pt->vcpu->arch.hvm_vcpu.tm_lock); } static void pt_unlock(struct periodic_time *pt) { - spin_unlock(&pt->vcpu->arch.hvm_vcpu.tm_lock); + pt_vcpu_unlock(pt->vcpu); } static void pt_process_missed_ticks(struct periodic_time *pt) @@ -218,7 +227,7 @@ void pt_save_timer(struct vcpu *v) if ( v->pause_flags & VPF_blocked ) return; - spin_lock(&v->arch.hvm_vcpu.tm_lock); + pt_vcpu_lock(v); list_for_each_entry ( pt, head, list ) if ( !pt->do_not_freeze ) @@ -226,7 +235,7 @@ void pt_save_timer(struct vcpu *v) pt_freeze_time(v); - spin_unlock(&v->arch.hvm_vcpu.tm_lock); + pt_vcpu_unlock(v); } void pt_restore_timer(struct vcpu *v) @@ -234,7 +243,7 @@ void pt_restore_timer(struct vcpu *v) struct list_head *head = &v->arch.hvm_vcpu.tm_list; struct periodic_time *pt; - spin_lock(&v->arch.hvm_vcpu.tm_lock); + pt_vcpu_lock(v); list_for_each_entry ( pt, head, list ) { @@ -247,7 +256,7 @@ void pt_restore_timer(struct vcpu *v) pt_thaw_time(v); - spin_unlock(&v->arch.hvm_vcpu.tm_lock); + pt_vcpu_unlock(v); } static void pt_timer_fn(void *data) @@ -272,7 +281,7 @@ int pt_update_irq(struct vcpu *v) uint64_t max_lag; int irq, pt_vector = -1; - spin_lock(&v->arch.hvm_vcpu.tm_lock); + pt_vcpu_lock(v); earliest_pt = NULL; max_lag = -1ULL; @@ -300,14 +309,14 @@ int pt_update_irq(struct vcpu *v) if ( earliest_pt == NULL ) { - spin_unlock(&v->arch.hvm_vcpu.tm_lock); + pt_vcpu_unlock(v); return -1; } earliest_pt->irq_issued = 1; irq = earliest_pt->irq; - spin_unlock(&v->arch.hvm_vcpu.tm_lock); + pt_vcpu_unlock(v); switch ( earliest_pt->source ) { @@ -377,12 +386,12 @@ void pt_intr_post(struct vcpu *v, struct if ( intack.source == hvm_intsrc_vector ) return; - spin_lock(&v->arch.hvm_vcpu.tm_lock); + pt_vcpu_lock(v); pt = is_pt_irq(v, intack); if ( pt == NULL ) { - spin_unlock(&v->arch.hvm_vcpu.tm_lock); + pt_vcpu_unlock(v); return; } @@ -421,7 +430,7 @@ void pt_intr_post(struct vcpu *v, struct cb = pt->cb; cb_priv = pt->priv; - spin_unlock(&v->arch.hvm_vcpu.tm_lock); + pt_vcpu_unlock(v); if ( cb != NULL ) cb(v, cb_priv); @@ -432,12 +441,12 @@ void pt_migrate(struct vcpu *v) struct list_head *head = &v->arch.hvm_vcpu.tm_list; struct periodic_time *pt; - spin_lock(&v->arch.hvm_vcpu.tm_lock); + pt_vcpu_lock(v); list_for_each_entry ( pt, head, list ) migrate_timer(&pt->timer, v->processor); - spin_unlock(&v->arch.hvm_vcpu.tm_lock); + pt_vcpu_unlock(v); } void create_periodic_time( @@ -455,7 +464,7 @@ void create_periodic_time( destroy_periodic_time(pt); - spin_lock(&v->arch.hvm_vcpu.tm_lock); + write_lock(&v->domain->arch.hvm_domain.pl_time->pt_migrate); pt->pending_intr_nr = 0; pt->do_not_freeze = 0; @@ -504,7 +513,7 @@ void create_periodic_time( init_timer(&pt->timer, pt_timer_fn, pt, v->processor); set_timer(&pt->timer, pt->scheduled); - spin_unlock(&v->arch.hvm_vcpu.tm_lock); + write_unlock(&v->domain->arch.hvm_domain.pl_time->pt_migrate); } void destroy_periodic_time(struct periodic_time *pt) @@ -529,30 +538,20 @@ void destroy_periodic_time(struct period static void pt_adjust_vcpu(struct periodic_time *pt, struct vcpu *v) { - int on_list; - ASSERT(pt->source == PTSRC_isa || pt->source == PTSRC_ioapic); if ( pt->vcpu == NULL ) return; - pt_lock(pt); - on_list = pt->on_list; - if ( pt->on_list ) - list_del(&pt->list); - pt->on_list = 0; - pt_unlock(pt); - - spin_lock(&v->arch.hvm_vcpu.tm_lock); + write_lock(&pt->vcpu->domain->arch.hvm_domain.pl_time->pt_migrate); pt->vcpu = v; - if ( on_list ) + if ( pt->on_list ) { - pt->on_list = 1; + list_del(&pt->list); list_add(&pt->list, &v->arch.hvm_vcpu.tm_list); - migrate_timer(&pt->timer, v->processor); } - spin_unlock(&v->arch.hvm_vcpu.tm_lock); + write_unlock(&pt->vcpu->domain->arch.hvm_domain.pl_time->pt_migrate); } void pt_adjust_global_vcpu_target(struct vcpu *v) --- a/xen/include/asm-x86/hvm/vpt.h +++ b/xen/include/asm-x86/hvm/vpt.h @@ -133,6 +133,13 @@ struct pl_time { /* platform time */ struct RTCState vrtc; struct HPETState vhpet; struct PMTState vpmt; + /* + * rwlock to prevent periodic_time vCPU migration. Take the lock in read + * mode in order to prevent the vcpu field of periodic_time from changing. + * Lock must be taken in write mode when changes to the vcpu field are + * performed, as it allows exclusive access to all the timers of a domain. + */ + rwlock_t pt_migrate; /* guest_time = Xen sys time + stime_offset */ int64_t stime_offset; /* Ensures monotonicity in appropriate timer modes. */