ed4ff2768a
XSA333, XSA336, XSA337, XSA338, XSA339, XSA340, XSA342, XSA343, XSA344 bump PKGREVISION
337 lines
10 KiB
Text
337 lines
10 KiB
Text
$NetBSD: patch-XSA344,v 1.1 2020/10/02 13:00:48 bouyer Exp $
|
|
|
|
From: Jan Beulich <jbeulich@suse.com>
|
|
Subject: evtchn: arrange for preemption in evtchn_destroy()
|
|
|
|
Especially closing of fully established interdomain channels can take
|
|
quite some time, due to the locking involved. Therefore we shouldn't
|
|
assume we can clean up still active ports all in one go. Besides adding
|
|
the necessary preemption check, also avoid pointlessly starting from
|
|
(or now really ending at) 0; 1 is the lowest numbered port which may
|
|
need closing.
|
|
|
|
Since we're now reducing ->valid_evtchns, free_xen_event_channel(),
|
|
and (at least to be on the safe side) notify_via_xen_event_channel()
|
|
need to cope with attempts to close / unbind from / send through already
|
|
closed (and no longer valid, as per port_is_valid()) ports.
|
|
|
|
This is part of XSA-344.
|
|
|
|
Signed-off-by: Jan Beulich <jbeulich@suse.com>
|
|
Acked-by: Julien Grall <jgrall@amazon.com>
|
|
Reviewed-by: Stefano Stabellini <sstabellini@kernel.org>
|
|
|
|
--- xen/common/domain.c.orig
|
|
+++ xen/common/domain.c
|
|
@@ -646,7 +646,6 @@ int domain_kill(struct domain *d)
|
|
if ( d->is_dying != DOMDYING_alive )
|
|
return domain_kill(d);
|
|
d->is_dying = DOMDYING_dying;
|
|
- evtchn_destroy(d);
|
|
gnttab_release_mappings(d);
|
|
tmem_destroy(d->tmem_client);
|
|
vnuma_destroy(d->vnuma);
|
|
@@ -654,6 +653,9 @@ int domain_kill(struct domain *d)
|
|
d->tmem_client = NULL;
|
|
/* fallthrough */
|
|
case DOMDYING_dying:
|
|
+ rc = evtchn_destroy(d);
|
|
+ if ( rc )
|
|
+ break;
|
|
rc = domain_relinquish_resources(d);
|
|
if ( rc != 0 )
|
|
break;
|
|
--- xen/common/event_channel.c.orig
|
|
+++ xen/common/event_channel.c
|
|
@@ -1291,7 +1291,16 @@ int alloc_unbound_xen_event_channel(
|
|
|
|
void free_xen_event_channel(struct domain *d, int port)
|
|
{
|
|
- BUG_ON(!port_is_valid(d, port));
|
|
+ if ( !port_is_valid(d, port) )
|
|
+ {
|
|
+ /*
|
|
+ * Make sure ->is_dying is read /after/ ->valid_evtchns, pairing
|
|
+ * with the spin_barrier() and BUG_ON() in evtchn_destroy().
|
|
+ */
|
|
+ smp_rmb();
|
|
+ BUG_ON(!d->is_dying);
|
|
+ return;
|
|
+ }
|
|
|
|
evtchn_close(d, port, 0);
|
|
}
|
|
@@ -1303,7 +1312,17 @@ void notify_via_xen_event_channel(struct
|
|
struct domain *rd;
|
|
unsigned long flags;
|
|
|
|
- ASSERT(port_is_valid(ld, lport));
|
|
+ if ( !port_is_valid(ld, lport) )
|
|
+ {
|
|
+ /*
|
|
+ * Make sure ->is_dying is read /after/ ->valid_evtchns, pairing
|
|
+ * with the spin_barrier() and BUG_ON() in evtchn_destroy().
|
|
+ */
|
|
+ smp_rmb();
|
|
+ ASSERT(ld->is_dying);
|
|
+ return;
|
|
+ }
|
|
+
|
|
lchn = evtchn_from_port(ld, lport);
|
|
|
|
spin_lock_irqsave(&lchn->lock, flags);
|
|
@@ -1375,8 +1394,7 @@ int evtchn_init(struct domain *d)
|
|
return 0;
|
|
}
|
|
|
|
-
|
|
-void evtchn_destroy(struct domain *d)
|
|
+int evtchn_destroy(struct domain *d)
|
|
{
|
|
unsigned int i;
|
|
|
|
@@ -1385,14 +1403,29 @@ void evtchn_destroy(struct domain *d)
|
|
spin_barrier(&d->event_lock);
|
|
|
|
/* Close all existing event channels. */
|
|
- for ( i = 0; port_is_valid(d, i); i++ )
|
|
+ for ( i = d->valid_evtchns; --i; )
|
|
+ {
|
|
evtchn_close(d, i, 0);
|
|
|
|
+ /*
|
|
+ * Avoid preempting when called from domain_create()'s error path,
|
|
+ * and don't check too often (choice of frequency is arbitrary).
|
|
+ */
|
|
+ if ( i && !(i & 0x3f) && d->is_dying != DOMDYING_dead &&
|
|
+ hypercall_preempt_check() )
|
|
+ {
|
|
+ write_atomic(&d->valid_evtchns, i);
|
|
+ return -ERESTART;
|
|
+ }
|
|
+ }
|
|
+
|
|
ASSERT(!d->active_evtchns);
|
|
|
|
clear_global_virq_handlers(d);
|
|
|
|
evtchn_fifo_destroy(d);
|
|
+
|
|
+ return 0;
|
|
}
|
|
|
|
|
|
--- xen/include/xen/sched.h.orig
|
|
+++ xen/include/xen/sched.h
|
|
@@ -135,7 +135,7 @@ struct evtchn
|
|
} __attribute__((aligned(64)));
|
|
|
|
int evtchn_init(struct domain *d); /* from domain_create */
|
|
-void evtchn_destroy(struct domain *d); /* from domain_kill */
|
|
+int evtchn_destroy(struct domain *d); /* from domain_kill */
|
|
void evtchn_destroy_final(struct domain *d); /* from complete_domain_destroy */
|
|
|
|
struct waitqueue_vcpu;
|
|
From: Jan Beulich <jbeulich@suse.com>
|
|
Subject: evtchn: arrange for preemption in evtchn_reset()
|
|
|
|
Like for evtchn_destroy() looping over all possible event channels to
|
|
close them can take a significant amount of time. Unlike done there, we
|
|
can't alter domain properties (i.e. d->valid_evtchns) here. Borrow, in a
|
|
lightweight form, the paging domctl continuation concept, redirecting
|
|
the continuations to different sub-ops. Just like there this is to be
|
|
able to allow for predictable overall results of the involved sub-ops:
|
|
Racing requests should either complete or be refused.
|
|
|
|
Note that a domain can't interfere with an already started (by a remote
|
|
domain) reset, due to being paused. It can prevent a remote reset from
|
|
happening by leaving a reset unfinished, but that's only going to affect
|
|
itself.
|
|
|
|
This is part of XSA-344.
|
|
|
|
Signed-off-by: Jan Beulich <jbeulich@suse.com>
|
|
Acked-by: Julien Grall <jgrall@amazon.com>
|
|
Reviewed-by: Stefano Stabellini <sstabellini@kernel.org>
|
|
|
|
--- xen/common/domain.c.orig
|
|
+++ xen/common/domain.c
|
|
@@ -1105,7 +1105,7 @@ void domain_unpause_except_self(struct d
|
|
domain_unpause(d);
|
|
}
|
|
|
|
-int domain_soft_reset(struct domain *d)
|
|
+int domain_soft_reset(struct domain *d, bool resuming)
|
|
{
|
|
struct vcpu *v;
|
|
int rc;
|
|
@@ -1119,7 +1119,7 @@ int domain_soft_reset(struct domain *d)
|
|
}
|
|
spin_unlock(&d->shutdown_lock);
|
|
|
|
- rc = evtchn_reset(d);
|
|
+ rc = evtchn_reset(d, resuming);
|
|
if ( rc )
|
|
return rc;
|
|
|
|
--- xen/common/domctl.c.orig
|
|
+++ xen/common/domctl.c
|
|
@@ -648,12 +648,22 @@ long do_domctl(XEN_GUEST_HANDLE_PARAM(xe
|
|
}
|
|
|
|
case XEN_DOMCTL_soft_reset:
|
|
+ case XEN_DOMCTL_soft_reset_cont:
|
|
if ( d == current->domain ) /* no domain_pause() */
|
|
{
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
- ret = domain_soft_reset(d);
|
|
+ ret = domain_soft_reset(d, op->cmd == XEN_DOMCTL_soft_reset_cont);
|
|
+ if ( ret == -ERESTART )
|
|
+ {
|
|
+ op->cmd = XEN_DOMCTL_soft_reset_cont;
|
|
+ if ( !__copy_field_to_guest(u_domctl, op, cmd) )
|
|
+ ret = hypercall_create_continuation(__HYPERVISOR_domctl,
|
|
+ "h", u_domctl);
|
|
+ else
|
|
+ ret = -EFAULT;
|
|
+ }
|
|
break;
|
|
|
|
case XEN_DOMCTL_destroydomain:
|
|
--- xen/common/event_channel.c.orig
|
|
+++ xen/common/event_channel.c
|
|
@@ -1051,7 +1051,7 @@ int evtchn_unmask(unsigned int port)
|
|
return 0;
|
|
}
|
|
|
|
-int evtchn_reset(struct domain *d)
|
|
+int evtchn_reset(struct domain *d, bool resuming)
|
|
{
|
|
unsigned int i;
|
|
int rc = 0;
|
|
@@ -1059,11 +1059,40 @@ int evtchn_reset(struct domain *d)
|
|
if ( d != current->domain && !d->controller_pause_count )
|
|
return -EINVAL;
|
|
|
|
- for ( i = 0; port_is_valid(d, i); i++ )
|
|
+ spin_lock(&d->event_lock);
|
|
+
|
|
+ /*
|
|
+ * If we are resuming, then start where we stopped. Otherwise, check
|
|
+ * that a reset operation is not already in progress, and if none is,
|
|
+ * record that this is now the case.
|
|
+ */
|
|
+ i = resuming ? d->next_evtchn : !d->next_evtchn;
|
|
+ if ( i > d->next_evtchn )
|
|
+ d->next_evtchn = i;
|
|
+
|
|
+ spin_unlock(&d->event_lock);
|
|
+
|
|
+ if ( !i )
|
|
+ return -EBUSY;
|
|
+
|
|
+ for ( ; port_is_valid(d, i); i++ )
|
|
+ {
|
|
evtchn_close(d, i, 1);
|
|
|
|
+ /* NB: Choice of frequency is arbitrary. */
|
|
+ if ( !(i & 0x3f) && hypercall_preempt_check() )
|
|
+ {
|
|
+ spin_lock(&d->event_lock);
|
|
+ d->next_evtchn = i;
|
|
+ spin_unlock(&d->event_lock);
|
|
+ return -ERESTART;
|
|
+ }
|
|
+ }
|
|
+
|
|
spin_lock(&d->event_lock);
|
|
|
|
+ d->next_evtchn = 0;
|
|
+
|
|
if ( d->active_evtchns > d->xen_evtchns )
|
|
rc = -EAGAIN;
|
|
else if ( d->evtchn_fifo )
|
|
@@ -1198,7 +1227,8 @@ long do_event_channel_op(int cmd, XEN_GU
|
|
break;
|
|
}
|
|
|
|
- case EVTCHNOP_reset: {
|
|
+ case EVTCHNOP_reset:
|
|
+ case EVTCHNOP_reset_cont: {
|
|
struct evtchn_reset reset;
|
|
struct domain *d;
|
|
|
|
@@ -1211,9 +1241,13 @@ long do_event_channel_op(int cmd, XEN_GU
|
|
|
|
rc = xsm_evtchn_reset(XSM_TARGET, current->domain, d);
|
|
if ( !rc )
|
|
- rc = evtchn_reset(d);
|
|
+ rc = evtchn_reset(d, cmd == EVTCHNOP_reset_cont);
|
|
|
|
rcu_unlock_domain(d);
|
|
+
|
|
+ if ( rc == -ERESTART )
|
|
+ rc = hypercall_create_continuation(__HYPERVISOR_event_channel_op,
|
|
+ "ih", EVTCHNOP_reset_cont, arg);
|
|
break;
|
|
}
|
|
|
|
--- xen/include/public/domctl.h.orig
|
|
+++ xen/include/public/domctl.h
|
|
@@ -1121,7 +1121,10 @@ struct xen_domctl {
|
|
#define XEN_DOMCTL_iomem_permission 20
|
|
#define XEN_DOMCTL_ioport_permission 21
|
|
#define XEN_DOMCTL_hypercall_init 22
|
|
-#define XEN_DOMCTL_arch_setup 23 /* Obsolete IA64 only */
|
|
+#ifdef __XEN__
|
|
+/* #define XEN_DOMCTL_arch_setup 23 Obsolete IA64 only */
|
|
+#define XEN_DOMCTL_soft_reset_cont 23
|
|
+#endif
|
|
#define XEN_DOMCTL_settimeoffset 24
|
|
#define XEN_DOMCTL_getvcpuaffinity 25
|
|
#define XEN_DOMCTL_real_mode_area 26 /* Obsolete PPC only */
|
|
--- xen/include/public/event_channel.h.orig
|
|
+++ xen/include/public/event_channel.h
|
|
@@ -74,6 +74,9 @@
|
|
#define EVTCHNOP_init_control 11
|
|
#define EVTCHNOP_expand_array 12
|
|
#define EVTCHNOP_set_priority 13
|
|
+#ifdef __XEN__
|
|
+#define EVTCHNOP_reset_cont 14
|
|
+#endif
|
|
/* ` } */
|
|
|
|
typedef uint32_t evtchn_port_t;
|
|
--- xen/include/xen/event.h.orig
|
|
+++ xen/include/xen/event.h
|
|
@@ -163,7 +163,7 @@ void evtchn_check_pollers(struct domain
|
|
void evtchn_2l_init(struct domain *d);
|
|
|
|
/* Close all event channels and reset to 2-level ABI. */
|
|
-int evtchn_reset(struct domain *d);
|
|
+int evtchn_reset(struct domain *d, bool resuming);
|
|
|
|
/*
|
|
* Low-level event channel port ops.
|
|
--- xen/include/xen/sched.h.orig
|
|
+++ xen/include/xen/sched.h
|
|
@@ -355,6 +355,8 @@ struct domain
|
|
* EVTCHNOP_reset). Read/write access like for active_evtchns.
|
|
*/
|
|
unsigned int xen_evtchns;
|
|
+ /* Port to resume from in evtchn_reset(), when in a continuation. */
|
|
+ unsigned int next_evtchn;
|
|
spinlock_t event_lock;
|
|
const struct evtchn_port_ops *evtchn_port_ops;
|
|
struct evtchn_fifo_domain *evtchn_fifo;
|
|
@@ -608,7 +610,7 @@ int domain_shutdown(struct domain *d, u8
|
|
void domain_resume(struct domain *d);
|
|
void domain_pause_for_debugger(void);
|
|
|
|
-int domain_soft_reset(struct domain *d);
|
|
+int domain_soft_reset(struct domain *d, bool resuming);
|
|
|
|
int vcpu_start_shutdown_deferral(struct vcpu *v);
|
|
void vcpu_end_shutdown_deferral(struct vcpu *v);
|