|
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] Re: [Xen-devel] [PATCH 2/2] evtchn/fifo: don't corrupt queues if an old tail is linked
>>> On 10.12.13 at 14:57, David Vrabel <david.vrabel@xxxxxxxxxx> wrote:
> From: David Vrabel <david.vrabel@xxxxxxxxxx>
>
> An event may still be the tail of a queue even if the queue is now
> empty (an 'old tail' event). There is logic to handle the case when
> this old tail event needs to be added to the now empty queue (by
> checking for q->tail == port).
>
> However, this does not cover all cases.
>
> 1. An old tail may be re-added simultaneously with another event.
> LINKED is set on the old tail, and the other CPU may misinterpret
> this as the old tail still being valid and set LINK instead of
> HEAD. All events on this queue will then be lost.
>
> 2. If the old tail event on queue A is moved to a different queue B
> (by changing its VCPU or priority), the event may then be linked
> onto queue B. When another event is linked onto queue A it will
> check the old tail, see that it is linked (but on queue B) and
> overwrite the LINK field, corrupting both queues.
>
> When an event is linked, save the vcpu id and priority of the queue it
> is being linked onto. Use this when linking an event to check if it
> is an unlinked old tail event. If it is an old tail event, the old
> queue is empty and old_q->tail is invalidated to ensure adding another
> event to old_q will update HEAD. The tail is invalidated by setting
> it to 0 since the event 0 is never linked.
>
> The old_q->lock is held while setting LINKED to avoid the race with
> the test of LINKED in evtchn_fifo_set_link().
>
> Since a event channel may move queues after old_q->lock is acquired,
> we must check that we have the correct lock and retry if not. Since
> changing VCPUs or priority is expected to be rare events that are
> serialized in the guest, we try at most 3 times before dropping the
> event. This prevents a malicious guest from repeatedly adjusting
> priority to prevent another domain from acquiring old_q->lock.
>
> Signed-off-by: David Vrabel <david.vrabel@xxxxxxxxxx>
Reviewed-by: Jan Beulich <jbeulich@xxxxxxxx>
> ---
> xen/common/event_fifo.c | 80
> ++++++++++++++++++++++++++++++++++++++++-------
> xen/include/xen/sched.h | 2 +
> 2 files changed, 70 insertions(+), 12 deletions(-)
>
> diff --git a/xen/common/event_fifo.c b/xen/common/event_fifo.c
> index 2ab4c29..fc43e62 100644
> --- a/xen/common/event_fifo.c
> +++ b/xen/common/event_fifo.c
> @@ -50,6 +50,36 @@ static void evtchn_fifo_init(struct domain *d, struct
> evtchn *evtchn)
> d->domain_id, evtchn->port);
> }
>
> +static struct evtchn_fifo_queue *lock_old_queue(const struct domain *d,
> + struct evtchn *evtchn,
> + unsigned long *flags)
> +{
> + struct vcpu *v;
> + struct evtchn_fifo_queue *q, *old_q;
> + unsigned int try;
> +
> + for ( try = 0; try < 3; try++ )
> + {
> + v = d->vcpu[evtchn->last_vcpu_id];
> + old_q = &v->evtchn_fifo->queue[evtchn->last_priority];
> +
> + spin_lock_irqsave(&old_q->lock, *flags);
> +
> + v = d->vcpu[evtchn->last_vcpu_id];
> + q = &v->evtchn_fifo->queue[evtchn->last_priority];
> +
> + if ( old_q == q )
> + return old_q;
> +
> + spin_unlock_irqrestore(&old_q->lock, *flags);
> + }
> +
> + gdprintk(XENLOG_WARNING,
> + "domain %d, port %d lost event (too many queue changes)\n",
> + d->domain_id, evtchn->port);
> + return NULL;
> +}
> +
> static int try_set_link(event_word_t *word, event_word_t *w, uint32_t link)
> {
> event_word_t new, old;
> @@ -119,7 +149,6 @@ static void evtchn_fifo_set_pending(struct vcpu *v,
> struct evtchn *evtchn)
> struct domain *d = v->domain;
> unsigned int port;
> event_word_t *word;
> - struct evtchn_fifo_queue *q;
> unsigned long flags;
> bool_t was_pending;
>
> @@ -136,25 +165,52 @@ static void evtchn_fifo_set_pending(struct vcpu *v,
> struct evtchn *evtchn)
> return;
> }
>
> - /*
> - * 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.
> - */
> - q = &v->evtchn_fifo->queue[evtchn->priority];
> -
> was_pending = test_and_set_bit(EVTCHN_FIFO_PENDING, word);
>
> /*
> * Link the event if it unmasked and not already linked.
> */
> if ( !test_bit(EVTCHN_FIFO_MASKED, word)
> - && !test_and_set_bit(EVTCHN_FIFO_LINKED, word) )
> + && !test_bit(EVTCHN_FIFO_LINKED, word) )
> {
> + struct evtchn_fifo_queue *q, *old_q;
> event_word_t *tail_word;
> bool_t linked = 0;
>
> - spin_lock_irqsave(&q->lock, flags);
> + /*
> + * 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.
> + */
> + q = &v->evtchn_fifo->queue[evtchn->priority];
> +
> + old_q = lock_old_queue(d, evtchn, &flags);
> + if ( !old_q )
> + goto done;
> +
> + if ( test_and_set_bit(EVTCHN_FIFO_LINKED, word) )
> + {
> + spin_unlock_irqrestore(&old_q->lock, flags);
> + goto done;
> + }
> +
> + /*
> + * If this event was a tail, the old queue is now empty and
> + * its tail must be invalidated to prevent adding an event to
> + * the old queue from corrupting the new queue.
> + */
> + if ( old_q->tail == port )
> + old_q->tail = 0;
> +
> + /* Moved to a different queue? */
> + if ( old_q != q )
> + {
> + evtchn->last_vcpu_id = evtchn->notify_vcpu_id;
> + evtchn->last_priority = evtchn->priority;
> +
> + spin_unlock_irqrestore(&old_q->lock, flags);
> + spin_lock_irqsave(&q->lock, flags);
> + }
>
> /*
> * Atomically link the tail to port iff the tail is linked.
> @@ -166,7 +222,7 @@ static void evtchn_fifo_set_pending(struct vcpu *v,
> struct evtchn *evtchn)
> * If the queue is empty (i.e., we haven't linked to the new
> * event), head must be updated.
> */
> - if ( port != q->tail )
> + if ( q->tail )
> {
> tail_word = evtchn_fifo_word_from_port(d, q->tail);
> linked = evtchn_fifo_set_link(d, tail_word, port);
> @@ -182,7 +238,7 @@ static void evtchn_fifo_set_pending(struct vcpu *v,
> struct evtchn *evtchn)
> &v->evtchn_fifo->control_block->ready) )
> vcpu_mark_events_pending(v);
> }
> -
> + done:
> if ( !was_pending )
> evtchn_check_pollers(d, port);
> }
> diff --git a/xen/include/xen/sched.h b/xen/include/xen/sched.h
> index cbdf377..5ab92dd 100644
> --- a/xen/include/xen/sched.h
> +++ b/xen/include/xen/sched.h
> @@ -98,6 +98,8 @@ struct evtchn
> } u;
> u8 priority;
> u8 pending:1;
> + u16 last_vcpu_id;
> + u8 last_priority;
> #ifdef FLASK_ENABLE
> void *ssid;
> #endif
> --
> 1.7.2.5
_______________________________________________
Xen-devel mailing list
Xen-devel@xxxxxxxxxxxxx
http://lists.xen.org/xen-devel
|
![]() |
Lists.xenproject.org is hosted with RackSpace, monitoring our |