CVE-2020-27673 (C, published 2020-10-22T21:15Z)
Description: value: An issue was discovered in the Linux kernel through 5.9.1, as used with Xen through 4.14.x. Guest OS users can cause a denial of service (host OS hang) via a high rate of events to dom0, aka CID-e99502f76271.
Severity: MEDIUM (CVSS v3 base score: 5.5)
Weakness: NVD-CWE-noinfo \u2014 Insufficient Information
There is insufficient information about the issue to classify it; details are unkown or unspecified.
Repository: https://github.com/torvalds/linux
Files changed:
Documentation/admin-guide/kernel-parameters.txt
drivers/xen/events/events_2l.c
drivers/xen/events/events_base.c
drivers/xen/events/events_fifo.c
drivers/xen/events/events_internal.h
Commit message: xen/events: defer eoi in case of excessive number of events

In case rogue guests are sending events at high frequency it might
happen that xen_evtchn_do_upcall() won't stop processing events in
dom0. As this is done in irq handling a crash might be the result.

In order to avoid that, delay further inter-domain events after some
time in xen_evtchn_do_upcall() by forcing eoi processing into a
worker on the same cpu, thus inhibiting new events coming in.

The time after which eoi processing is to be delayed is configurable
via a new module parameter "event_loop_timeout" which specifies the
maximum event loop time in jiffies (default: 2, the value was chosen
after some tests showing that a value of 2 was the lowest with an
only slight drop of dom0 network throughput while multiple guests
performed an event storm).

How long eoi processing will be delayed can be specified via another
parameter "event_eoi_delay" (again in jiffies, default 10, again the
value was chosen after testing with different delay values).

This is part of XSA-332.

Cc: stable@vger.kernel.org
Reported-by: Julien Grall <julien@xen.org>
Signed-off-by: Juergen Gross <jgross@suse.com>
Reviewed-by: Stefano Stabellini <sstabellini@kernel.org>
Reviewed-by: Wei Liu <wl@xen.org>


Vulnerable code:
static void evtchn_2l_handle_events(unsigned cpu)
			irq = get_evtchn_to_irq(port);

			if (irq != -1)
				generic_handle_irq(irq);
	if (!VALID_EVTCHN(evtchn))
		xen_evtchn_handle_events(cpu);
#undef MODULE_PARAM_PREFIX
#define MODULE_PARAM_PREFIX "xen."

static void handle_irq_for_port(evtchn_port_t port)
{
	int irq;

	irq = get_evtchn_to_irq(port);
	if (irq != -1)
		generic_handle_irq(irq);
}

static void consume_one_event(unsigned cpu,
			      unsigned priority, unsigned long *ready,
			      bool drop)
		if (unlikely(drop))
			handle_irq_for_port(port);
static void __evtchn_fifo_handle_events(unsigned cpu, bool drop)
		consume_one_event(cpu, control_block, q, &ready, drop);
static void evtchn_fifo_handle_events(unsigned cpu)
	__evtchn_fifo_handle_events(cpu, false);
	__evtchn_fifo_handle_events(cpu, true);
	void (*handle_events)(unsigned cpu);
static inline void xen_evtchn_handle_events(unsigned cpu)
	return evtchn_ops->handle_events(cpu);

Fixed code:
	xen.event_eoi_delay=	[XEN]
			How long to delay EOI handling in case of event
			storms (jiffies). Default is 10.

	xen.event_loop_timeout=	[XEN]
			After which time (jiffies) the event handling loop
			should start to delay EOI handling. Default is 2.

static void evtchn_2l_handle_events(unsigned cpu, struct evtchn_loop_ctrl *ctrl)
			handle_irq_for_port(port, ctrl);
#include <linux/atomic.h>
#include <linux/ktime.h>
#undef MODULE_PARAM_PREFIX
#define MODULE_PARAM_PREFIX "xen."

static uint __read_mostly event_loop_timeout = 2;
module_param(event_loop_timeout, uint, 0644);

static uint __read_mostly event_eoi_delay = 10;
module_param(event_eoi_delay, uint, 0644);

 *       percpu eoi_list_lock
static DEFINE_PER_CPU(unsigned int, irq_epoch);

struct lateeoi_work {
	struct delayed_work delayed;
	spinlock_t eoi_list_lock;
	struct list_head eoi_list;
};

static DEFINE_PER_CPU(struct lateeoi_work, lateeoi);

static void lateeoi_list_del(struct irq_info *info)
{
	struct lateeoi_work *eoi = &per_cpu(lateeoi, info->eoi_cpu);
	unsigned long flags;

	spin_lock_irqsave(&eoi->eoi_list_lock, flags);
	list_del_init(&info->eoi_list);
	spin_unlock_irqrestore(&eoi->eoi_list_lock, flags);
}

static void lateeoi_list_add(struct irq_info *info)
{
	struct lateeoi_work *eoi = &per_cpu(lateeoi, info->eoi_cpu);
	struct irq_info *elem;
	u64 now = get_jiffies_64();
	unsigned long delay;
	unsigned long flags;

	if (now < info->eoi_time)
		delay = info->eoi_time - now;
	else
		delay = 1;

	spin_lock_irqsave(&eoi->eoi_list_lock, flags);

	if (list_empty(&eoi->eoi_list)) {
		list_add(&info->eoi_list, &eoi->eoi_list);
		mod_delayed_work_on(info->eoi_cpu, system_wq,
				    &eoi->delayed, delay);
	} else {
		list_for_each_entry_reverse(elem, &eoi->eoi_list, eoi_list) {
			if (elem->eoi_time <= info->eoi_time)
				break;
		}
		list_add(&info->eoi_list, &elem->eoi_list);
	}

	spin_unlock_irqrestore(&eoi->eoi_list_lock, flags);
}

	unsigned int cpu;
	if (!VALID_EVTCHN(evtchn) || !list_empty(&info->eoi_list))
	cpu = info->eoi_cpu;
	if (info->eoi_time && info->irq_epoch == per_cpu(irq_epoch, cpu)) {
		lateeoi_list_add(info);
		return;
	}

	info->eoi_time = 0;
static void xen_irq_lateeoi_worker(struct work_struct *work)
{
	struct lateeoi_work *eoi;
	struct irq_info *info;
	u64 now = get_jiffies_64();
	unsigned long flags;

	eoi = container_of(to_delayed_work(work), struct lateeoi_work, delayed);

	read_lock_irqsave(&evtchn_rwlock, flags);

	while (true) {
		spin_lock(&eoi->eoi_list_lock);

		info = list_first_entry_or_null(&eoi->eoi_list, struct irq_info,
						eoi_list);

		if (info == NULL || now < info->eoi_time) {
			spin_unlock(&eoi->eoi_list_lock);
			break;
		}

		list_del_init(&info->eoi_list);

		spin_unlock(&eoi->eoi_list_lock);

		info->eoi_time = 0;

		xen_irq_lateeoi_locked(info);
	}

	if (info)
		mod_delayed_work_on(info->eoi_cpu, system_wq,
				    &eoi->delayed, info->eoi_time - now);

	read_unlock_irqrestore(&evtchn_rwlock, flags);
}

static void xen_cpu_init_eoi(unsigned int cpu)
{
	struct lateeoi_work *eoi = &per_cpu(lateeoi, cpu);

	INIT_DELAYED_WORK(&eoi->delayed, xen_irq_lateeoi_worker);
	spin_lock_init(&eoi->eoi_list_lock);
	INIT_LIST_HEAD(&eoi->eoi_list);
}


	INIT_LIST_HEAD(&info->eoi_list);
	if (!list_empty(&info->eoi_list))
		lateeoi_list_del(info);

struct evtchn_loop_ctrl {
	ktime_t timeout;
	unsigned count;
	bool defer_eoi;
};

void handle_irq_for_port(evtchn_port_t port, struct evtchn_loop_ctrl *ctrl)
{
	int irq;
	struct irq_info *info;

	irq = get_evtchn_to_irq(port);
	if (irq == -1)
		return;

	/*
	 * Check for timeout every 256 events.
	 * We are setting the timeout value only after the first 256
	 * events in order to not hurt the common case of few loop
	 * iterations. The 256 is basically an arbitrary value.
	 *
	 * In case we are hitting the timeout we need to defer all further
	 * EOIs in order to ensure to leave the event handling loop rather
	 * sooner than later.
	 */
	if (!ctrl->defer_eoi && !(++ctrl->count & 0xff)) {
		ktime_t kt = ktime_get();

		if (!ctrl->timeout) {
			kt = ktime_add_ms(kt,
					  jiffies_to_msecs(event_loop_timeout));
			ctrl->timeout = kt;
		} else if (kt > ctrl->timeout) {
			ctrl->defer_eoi = true;
		}
	}

	info = info_for_irq(irq);

	if (ctrl->defer_eoi) {
		info->eoi_cpu = smp_processor_id();
		info->irq_epoch = __this_cpu_read(irq_epoch);
		info->eoi_time = get_jiffies_64() + event_eoi_delay;
	}

	generic_handle_irq(irq);
}

	struct evtchn_loop_ctrl ctrl = { 0 };
		xen_evtchn_handle_events(cpu, &ctrl);

	/*
	 * Increment irq_epoch only now to defer EOIs only for
	 * xen_irq_lateeoi() invocations occurring from inside the loop
	 * above.
	 */
	__this_cpu_inc(irq_epoch);
	xen_cpu_init_eoi(cpu);

	xen_cpu_init_eoi(smp_processor_id());

static void consume_one_event(unsigned cpu, struct evtchn_loop_ctrl *ctrl,
			      unsigned priority, unsigned long *ready)
		if (unlikely(!ctrl))
			handle_irq_for_port(port, ctrl);
static void __evtchn_fifo_handle_events(unsigned cpu,
					struct evtchn_loop_ctrl *ctrl)
		consume_one_event(cpu, ctrl, control_block, q, &ready);
static void evtchn_fifo_handle_events(unsigned cpu,
				      struct evtchn_loop_ctrl *ctrl)
	__evtchn_fifo_handle_events(cpu, ctrl);
	__evtchn_fifo_handle_events(cpu, NULL);
	struct list_head eoi_list;
	unsigned short eoi_cpu;	/* EOI must happen on this cpu */
	unsigned int irq_epoch;	/* If eoi_cpu valid: irq_epoch of event */
	u64 eoi_time;		/* Time in jiffies when to EOI. */
struct evtchn_loop_ctrl;

	void (*handle_events)(unsigned cpu, struct evtchn_loop_ctrl *ctrl);
void handle_irq_for_port(evtchn_port_t port, struct evtchn_loop_ctrl *ctrl);
static inline void xen_evtchn_handle_events(unsigned cpu,
					    struct evtchn_loop_ctrl *ctrl)
	return evtchn_ops->handle_events(cpu, ctrl);