Xen Security Advisory 107 - Mishandling of uninitialised FIFO-based event channel control blocks

Xen.org security team <[email protected]>
Newsgroups gmane.comp.emulators.xen.announce
Message-ID <E1XRKar-0006Os-UL__6800.99400128729$1410266407$gmane$org@xenbits.xen.org>
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

                    Xen Security Advisory XSA-107

    Mishandling of uninitialised FIFO-based event channel control blocks

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)

iQEcBAEBAgAGBQJUDsJxAAoJEIP+FMlX6CvZrs8H/ixMJYY0qJHbmPuCLxUDK+pz
nrZ1mvqTfpN+M31GtHGKNFOBMUe7SaeQe7SJ8ucXwy8vqSwzzydWcu0ctjrLzyh9
cxnTx5Yu5yLHVWRlFT1ZI2+XnxuCLfW3xwXfZIQkSKWAHfCv78uvdc8u8nB8cdPy
8WiwJ77tNLtQXz8Jv5k8znIXLiLoCG3gO7TB7KwhZq1DeY8mL63N16CC3Eohu/1e
pNYGO6KjWSwFLqh/dPaorqHD+IXQUwCosLnqah1/+Qh3L97UB3j779lv3+YHakmZ
Ryu3OxqcjeuMTj4K2Iz2SeXixBz7YXl71zVnZlAq5jEasOA6xjTPFN7f8mUt34k=
=MQuU
-----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)
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.