Xen Security Advisory 107 (CVE-2014-6268) - Mishandling of uninitialised FIFO-based event channel control blocks
Xen.org security team <[email protected]>
| Newsgroups | gmane.comp.emulators.xen.announce |
|---|---|
| Message-ID | <E1XS3yv-0000pi-6I__23717.7860261456$1410440996$gmane$org@xenbits.xen.org> |
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
Xen Security Advisory CVE-2014-6268 / XSA-107
version 2
Mishandling of uninitialised FIFO-based event channel control blocks
UPDATES IN VERSION 2
====================
CVE assigned.
ISSUE DESCRIPTION
=================
When using the FIFO-based event channels, there are no checks for the
existence of a control block when binding an event or moving it to a
different VCPU. This is because events may be bound when the ABI is
in 2-level mode (e.g., by the toolstack before the domain is started).
The guest may trigger a Xen crash in evtchn_fifo_set_pending() if:
a) the event is bound to a VCPU without a control block; or
b) VCPU 0 does not have a control block.
In case (a), Xen will crash when looking up the current queue. In
(b), Xen will crash when looking up the old queue (which defaults to a
queue on VCPU 0).
IMPACT
======
A buggy or malicious guest can crash the host.
VULNERABLE SYSTEMS
==================
Xen 4.4 and onward are vulnerable.
MITIGATION
==========
None.
CREDITS
=======
This issue was originally reported by Vitaly Kuznetsov at Red Hat and
diagnosed as a security issue by David Vrabel at Citrix.
NOTE REGARDING LACK OF EMBARGO
==============================
This bug was publicly reported on xen-devel, before it was appreciated
that there was a security problem.
RESOLUTION
==========
Applying the appropriate attached patch resolves this issue.
xsa107-unstable.patch xen-unstable
xsa107-4.4.patch Xen 4.4.x
$ sha256sum xsa107*.patch
b92ba8085b6684abbc8b012ae1a580b9e7ed7c8e67071a9e70381d4c1009638b xsa107-4.4.patch
cd954a5bd742c751f8db884a3f31bd636a8c5850acddf5f1160dd6be1f706a09 xsa107-unstable.patch
$
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.12 (GNU/Linux)
iQEcBAEBAgAGBQJUEXRHAAoJEIP+FMlX6CvZknQIAIzPCOwG07XrKR7yu00lhCin
TSppBKJ3y7XkIdmBF/3QSnev61yJ4MYdpWl7qiK4xpDP3IyH0mrtIYBQVwxKCV/R
l/E2ztiEMugq86eCwvX5p/fAoyfqf1pBoVplqwcarS4vcmnnkOpK278TD2dPdw69
G5VaFxOqVo4Z6xQyFIGHtinN00tbb/lVQTpldah7ZfqXknPAcSeZqEBuqmVSLGIo
o9EgTAQm1wbh4tNn+O2KHeAbejjOTM7NYoidRqQY3qfN4m13MdAKliUbXIRdGggQ
aMKU2n7eNga4Aly720cD6hkJAOKxG/dGUb8lm1qHsG01VjhP2zqGn41tkqsiSAs=
=cld0
-----END PGP SIGNATURE-----
_______________________________________________
Xen-announce mailing list
[email protected]
http://lists.xen.org/xen-announce
xsa107-4.4.patch
(application/octet-stream, 4.6 KB)
evtchn: check control block exists when using FIFO-based events When using the FIFO-based event channels, there are no checks for the existance of a control block when binding an event or moving it to a different VCPU. This is because events may be bound when the ABI is in 2-level mode (e.g., by the toolstack before the domain is started). The guest may trigger a Xen crash in evtchn_fifo_set_pending() if: a) the event is bound to a VCPU without a control block; or b) VCPU 0 does not have a control block. In case (a), Xen will crash when looking up the current queue. In (b), Xen will crash when looking up the old queue (which defaults to a queue on VCPU 0). By allocating all the per-VCPU structures when enabling the FIFO ABI, we can be sure that v->evtchn_fifo is always valid. EVTCHNOP_init_control_block for all the other CPUs need only map the shared control block. A single check in evtchn_fifo_set_pending() before accessing the control block fixes all cases where the guest has not initialized some control blocks. This is XSA-107. Reported-by: Vitaly Kuznetsov <[email protected]> Signed-off-by: David Vrabel <[email protected]> Reviewed-by: Jan Beulich <[email protected]> --- a/xen/common/event_fifo.c +++ b/xen/common/event_fifo.c @@ -178,6 +178,19 @@ static void evtchn_fifo_set_pending(stru bool_t linked = 0; /* + * Control block not mapped. The guest must not unmask an + * event until the control block is initialized, so we can + * just drop the event. + */ + if ( unlikely(!v->evtchn_fifo->control_block) ) + { + printk(XENLOG_G_WARNING + "d%dv%d has no FIFO event channel control block\n", + d->domain_id, v->vcpu_id); + goto done; + } + + /* * No locking around getting the queue. This may race with * changing the priority but we are allowed to signal the * event once on the old priority. @@ -385,36 +398,42 @@ static void init_queue(struct vcpu *v, s { spin_lock_init(&q->lock); q->priority = i; - q->head = &v->evtchn_fifo->control_block->head[i]; } -static int setup_control_block(struct vcpu *v, uint64_t gfn, uint32_t offset) +static int setup_control_block(struct vcpu *v) { - struct domain *d = v->domain; struct evtchn_fifo_vcpu *efv; - void *virt; unsigned int i; - int rc; - - if ( v->evtchn_fifo ) - return -EINVAL; efv = xzalloc(struct evtchn_fifo_vcpu); if ( !efv ) return -ENOMEM; - rc = map_guest_page(d, gfn, &virt); + for ( i = 0; i <= EVTCHN_FIFO_PRIORITY_MIN; i++ ) + init_queue(v, &efv->queue[i], i); + + v->evtchn_fifo = efv; + + return 0; +} + +static int map_control_block(struct vcpu *v, uint64_t gfn, uint32_t offset) +{ + void *virt; + unsigned int i; + int rc; + + if ( v->evtchn_fifo->control_block ) + return -EINVAL; + + rc = map_guest_page(v->domain, gfn, &virt); if ( rc < 0 ) - { - xfree(efv); return rc; - } - v->evtchn_fifo = efv; v->evtchn_fifo->control_block = virt + offset; for ( i = 0; i <= EVTCHN_FIFO_PRIORITY_MIN; i++ ) - init_queue(v, &v->evtchn_fifo->queue[i], i); + v->evtchn_fifo->queue[i].head = &v->evtchn_fifo->control_block->head[i]; return 0; } @@ -508,28 +527,43 @@ int evtchn_fifo_init_control(struct evtc spin_lock(&d->event_lock); - rc = setup_control_block(v, gfn, offset); - /* * If this is the first control block, setup an empty event array * and switch to the fifo port ops. */ - if ( rc == 0 && !d->evtchn_fifo ) + if ( !d->evtchn_fifo ) { + struct vcpu *vcb; + + for_each_vcpu ( d, vcb ) { + rc = setup_control_block(vcb); + if ( rc < 0 ) + goto error; + } + rc = setup_event_array(d); if ( rc < 0 ) - cleanup_control_block(v); - else - { - d->evtchn_port_ops = &evtchn_port_ops_fifo; - d->max_evtchns = EVTCHN_FIFO_NR_CHANNELS; - setup_ports(d); - } + goto error; + + rc = map_control_block(v, gfn, offset); + if ( rc < 0 ) + goto error; + + d->evtchn_port_ops = &evtchn_port_ops_fifo; + d->max_evtchns = EVTCHN_FIFO_NR_CHANNELS; + setup_ports(d); } + else + rc = map_control_block(v, gfn, offset); spin_unlock(&d->event_lock); return rc; + + error: + evtchn_fifo_destroy(d); + spin_unlock(&d->event_lock); + return rc; } static int add_page_to_event_array(struct domain *d, unsigned long gfn)
xsa107-unstable.patch
(application/octet-stream, 4.6 KB)
evtchn: check control block exists when using FIFO-based events When using the FIFO-based event channels, there are no checks for the existance of a control block when binding an event or moving it to a different VCPU. This is because events may be bound when the ABI is in 2-level mode (e.g., by the toolstack before the domain is started). The guest may trigger a Xen crash in evtchn_fifo_set_pending() if: a) the event is bound to a VCPU without a control block; or b) VCPU 0 does not have a control block. In case (a), Xen will crash when looking up the current queue. In (b), Xen will crash when looking up the old queue (which defaults to a queue on VCPU 0). By allocating all the per-VCPU structures when enabling the FIFO ABI, we can be sure that v->evtchn_fifo is always valid. EVTCHNOP_init_control_block for all the other CPUs need only map the shared control block. A single check in evtchn_fifo_set_pending() before accessing the control block fixes all cases where the guest has not initialized some control blocks. This is XSA-107. Reported-by: Vitaly Kuznetsov <[email protected]> Signed-off-by: David Vrabel <[email protected]> Reviewed-by: Jan Beulich <[email protected]> --- a/xen/common/event_fifo.c +++ b/xen/common/event_fifo.c @@ -178,6 +178,18 @@ static void evtchn_fifo_set_pending(stru bool_t linked = 0; /* + * Control block not mapped. The guest must not unmask an + * event until the control block is initialized, so we can + * just drop the event. + */ + if ( unlikely(!v->evtchn_fifo->control_block) ) + { + printk(XENLOG_G_WARNING + "%pv has no FIFO event channel control block\n", v); + goto done; + } + + /* * No locking around getting the queue. This may race with * changing the priority but we are allowed to signal the * event once on the old priority. @@ -385,36 +397,42 @@ static void init_queue(struct vcpu *v, s { spin_lock_init(&q->lock); q->priority = i; - q->head = &v->evtchn_fifo->control_block->head[i]; } -static int setup_control_block(struct vcpu *v, uint64_t gfn, uint32_t offset) +static int setup_control_block(struct vcpu *v) { - struct domain *d = v->domain; struct evtchn_fifo_vcpu *efv; - void *virt; unsigned int i; - int rc; - - if ( v->evtchn_fifo ) - return -EINVAL; efv = xzalloc(struct evtchn_fifo_vcpu); if ( !efv ) return -ENOMEM; - rc = map_guest_page(d, gfn, &virt); + for ( i = 0; i <= EVTCHN_FIFO_PRIORITY_MIN; i++ ) + init_queue(v, &efv->queue[i], i); + + v->evtchn_fifo = efv; + + return 0; +} + +static int map_control_block(struct vcpu *v, uint64_t gfn, uint32_t offset) +{ + void *virt; + unsigned int i; + int rc; + + if ( v->evtchn_fifo->control_block ) + return -EINVAL; + + rc = map_guest_page(v->domain, gfn, &virt); if ( rc < 0 ) - { - xfree(efv); return rc; - } - v->evtchn_fifo = efv; v->evtchn_fifo->control_block = virt + offset; for ( i = 0; i <= EVTCHN_FIFO_PRIORITY_MIN; i++ ) - init_queue(v, &v->evtchn_fifo->queue[i], i); + v->evtchn_fifo->queue[i].head = &v->evtchn_fifo->control_block->head[i]; return 0; } @@ -509,28 +527,43 @@ int evtchn_fifo_init_control(struct evtc spin_lock(&d->event_lock); - rc = setup_control_block(v, gfn, offset); - /* * If this is the first control block, setup an empty event array * and switch to the fifo port ops. */ - if ( rc == 0 && !d->evtchn_fifo ) + if ( !d->evtchn_fifo ) { + struct vcpu *vcb; + + for_each_vcpu ( d, vcb ) { + rc = setup_control_block(vcb); + if ( rc < 0 ) + goto error; + } + rc = setup_event_array(d); if ( rc < 0 ) - cleanup_control_block(v); - else - { - d->evtchn_port_ops = &evtchn_port_ops_fifo; - d->max_evtchns = EVTCHN_FIFO_NR_CHANNELS; - setup_ports(d); - } + goto error; + + rc = map_control_block(v, gfn, offset); + if ( rc < 0 ) + goto error; + + d->evtchn_port_ops = &evtchn_port_ops_fifo; + d->max_evtchns = EVTCHN_FIFO_NR_CHANNELS; + setup_ports(d); } + else + rc = map_control_block(v, gfn, offset); spin_unlock(&d->event_lock); return rc; + + error: + evtchn_fifo_destroy(d); + spin_unlock(&d->event_lock); + return rc; } static int add_page_to_event_array(struct domain *d, unsigned long gfn)