@@ -40,7 +40,7 @@ allow dom0_t dom0_t:domain {
};
allow dom0_t dom0_t:domain2 {
set_cpu_policy gettsc settsc setscheduler set_vnumainfo
- get_vnumainfo psr_cmt_op psr_alloc get_cpu_policy
+ get_vnumainfo psr_cmt_op psr_alloc get_cpu_policy get_domain_state
};
allow dom0_t dom0_t:resource { add remove };
@@ -54,7 +54,7 @@ define(`create_domain_common', `
allow $1 $2:domain2 { set_cpu_policy settsc setscheduler setclaim
set_vnumainfo get_vnumainfo cacheflush
psr_cmt_op psr_alloc soft_reset
- resource_map get_cpu_policy };
+ resource_map get_cpu_policy get_domain_state };
allow $1 $2:security check_context;
allow $1 $2:shadow enable;
allow $1 $2:mmu { map_read map_write adjust memorymap physmap pinpage mmuext_op updatemp };
@@ -94,7 +94,7 @@ define(`manage_domain', `
getaddrsize pause unpause trigger shutdown destroy
setaffinity setdomainmaxmem getscheduler resume
setpodtarget getpodtarget getpagingmempool setpagingmempool };
- allow $1 $2:domain2 set_vnumainfo;
+ allow $1 $2:domain2 { set_vnumainfo get_domain_state };
')
# migrate_domain_out(priv, target)
@@ -13,6 +13,7 @@ allow dom0_t xenstore_t:domain set_virq_handler;
allow xenstore_t xen_t:xen writeconsole;
# Xenstore queries domaininfo on all domains
allow xenstore_t domain_type:domain getdomaininfo;
+allow xenstore_t domain_type:domain2 get_domain_state;
# As a shortcut, the following 3 rules are used instead of adding a domain_comms
# rule between xenstore_t and every domain type that talks to xenstore
@@ -193,6 +193,76 @@ static void domain_changed_state(const struct domain *d)
spin_unlock(&dom_state_changed_lock);
}
+static void set_domain_state_info(struct xen_domctl_get_domain_state *info,
+ const struct domain *d)
+{
+ info->state = XEN_DOMCTL_GETDOMSTATE_STATE_EXIST;
+ if ( d->is_shut_down )
+ info->state |= XEN_DOMCTL_GETDOMSTATE_STATE_SHUTDOWN;
+ if ( d->is_dying == DOMDYING_dying )
+ info->state |= XEN_DOMCTL_GETDOMSTATE_STATE_DYING;
+ if ( d->is_dying == DOMDYING_dead )
+ info->state |= XEN_DOMCTL_GETDOMSTATE_STATE_DEAD;
+ info->unique_id = d->unique_id;
+}
+
+int get_domain_state(struct xen_domctl_get_domain_state *info, struct domain *d,
+ domid_t *domid)
+{
+ unsigned int dom;
+ int rc = -ENOENT;
+
+ if ( info->pad0 || info->pad1 )
+ return -EINVAL;
+
+ if ( d )
+ {
+ set_domain_state_info(info, d);
+
+ return 0;
+ }
+
+ /*
+ * Only domain registered for VIRQ_DOM_EXC event is allowed to query
+ * domains having changed state.
+ */
+ if ( !domain_handles_global_virq(current->domain, VIRQ_DOM_EXC) )
+ return -EACCES;
+
+ spin_lock(&dom_state_changed_lock);
+
+ if ( dom_state_changed )
+ {
+ dom = find_first_bit(dom_state_changed, DOMID_MASK + 1);
+ if ( dom < DOMID_FIRST_RESERVED )
+ {
+ __clear_bit(dom, dom_state_changed);
+
+ *domid = dom;
+
+ d = rcu_lock_domain_by_id(dom);
+
+ if ( d )
+ {
+ set_domain_state_info(info, d);
+
+ rcu_unlock_domain(d);
+ }
+ else
+ {
+ info->state = 0;
+ info->unique_id = 0;
+ }
+
+ rc = 0;
+ }
+ }
+
+ spin_unlock(&dom_state_changed_lock);
+
+ return rc;
+}
+
static void __domain_finalise_shutdown(struct domain *d)
{
struct vcpu *v;
@@ -278,6 +278,11 @@ static struct vnuma_info *vnuma_init(const struct xen_domctl_vnuma *uinfo,
return ERR_PTR(ret);
}
+static bool is_stable_domctl(uint32_t cmd)
+{
+ return cmd == XEN_DOMCTL_get_domain_state;
+}
+
long do_domctl(XEN_GUEST_HANDLE_PARAM(xen_domctl_t) u_domctl)
{
long ret = 0;
@@ -288,7 +293,8 @@ long do_domctl(XEN_GUEST_HANDLE_PARAM(xen_domctl_t) u_domctl)
if ( copy_from_guest(op, u_domctl, 1) )
return -EFAULT;
- if ( op->interface_version != XEN_DOMCTL_INTERFACE_VERSION )
+ if ( op->interface_version !=
+ (is_stable_domctl(op->cmd) ? 0 : XEN_DOMCTL_INTERFACE_VERSION) )
return -EACCES;
switch ( op->cmd )
@@ -309,6 +315,7 @@ long do_domctl(XEN_GUEST_HANDLE_PARAM(xen_domctl_t) u_domctl)
fallthrough;
case XEN_DOMCTL_test_assign_device:
case XEN_DOMCTL_vm_event_op:
+ case XEN_DOMCTL_get_domain_state:
if ( op->domain == DOMID_INVALID )
{
d = NULL;
@@ -866,6 +873,15 @@ long do_domctl(XEN_GUEST_HANDLE_PARAM(xen_domctl_t) u_domctl)
__HYPERVISOR_domctl, "h", u_domctl);
break;
+ case XEN_DOMCTL_get_domain_state:
+ ret = xsm_get_domain_state(XSM_XS_PRIV, d);
+ if ( ret )
+ break;
+
+ copyback = 1;
+ ret = get_domain_state(&op->u.get_domain_state, d, &op->domain);
+ break;
+
default:
ret = arch_do_domctl(op, d, u_domctl);
break;
@@ -993,6 +993,13 @@ void send_global_virq(uint32_t virq)
send_guest_global_virq(global_virq_handlers[virq] ?: hardware_domain, virq);
}
+bool domain_handles_global_virq(struct domain *d, uint32_t virq)
+{
+ ASSERT(virq_is_global(virq));
+
+ return global_virq_handlers[virq] == d;
+}
+
int set_global_virq_handler(struct domain *d, uint32_t virq)
{
struct domain *old;
@@ -1002,7 +1009,7 @@ int set_global_virq_handler(struct domain *d, uint32_t virq)
if (!virq_is_global(virq))
return -EINVAL;
- if (global_virq_handlers[virq] == d)
+ if ( domain_handles_global_virq(d, virq) )
return 0;
if (unlikely(!get_domain(d)))
@@ -28,6 +28,7 @@
* Pure additions (e.g. new sub-commands) or compatible interface changes
* (e.g. adding semantics to 0-checked input fields or data to zeroed output
* fields) don't require a change of the version.
+ * Stable ops are NOT covered by XEN_DOMCTL_INTERFACE_VERSION!
*
* Last version bump: Xen 4.19
*/
@@ -1236,7 +1237,30 @@ struct xen_domctl_dt_overlay {
};
#endif
+/*
+ * XEN_DOMCTL_get_domain_state (stable interface)
+ *
+ * Get state information of a domain.
+ *
+ * In case domain is DOMID_INVALID, return information about a domain having
+ * changed state and reset the state change indicator for that domain. This
+ * function is usable only by a domain having registered the VIRQ_DOM_EXC
+ * event (normally Xenstore).
+ * NB. xen_domctl.domain is an IN/OUT parameter for this operation.
+ */
+struct xen_domctl_get_domain_state {
+ uint16_t state;
+#define XEN_DOMCTL_GETDOMSTATE_STATE_EXIST 0x0001 /* Domain is existing. */
+#define XEN_DOMCTL_GETDOMSTATE_STATE_SHUTDOWN 0x0002 /* Shutdown finished. */
+#define XEN_DOMCTL_GETDOMSTATE_STATE_DYING 0x0004 /* Domain dying. */
+#define XEN_DOMCTL_GETDOMSTATE_STATE_DEAD 0x0008 /* Domain dead. */
+ uint16_t pad0; /* Must be 0 on input, returned as 0. */
+ uint32_t pad1; /* Must be 0 on input, returned as 0. */
+ uint64_t unique_id; /* Unique domain identifier. */
+};
+
struct xen_domctl {
+/* Stable domctl ops: interface_version is required to be 0. */
uint32_t cmd;
#define XEN_DOMCTL_createdomain 1
#define XEN_DOMCTL_destroydomain 2
@@ -1325,6 +1349,7 @@ struct xen_domctl {
#define XEN_DOMCTL_set_paging_mempool_size 86
#define XEN_DOMCTL_dt_overlay 87
#define XEN_DOMCTL_gsi_permission 88
+#define XEN_DOMCTL_get_domain_state 89 /* stable interface */
#define XEN_DOMCTL_gdbsx_guestmemio 1000
#define XEN_DOMCTL_gdbsx_pausevcpu 1001
#define XEN_DOMCTL_gdbsx_unpausevcpu 1002
@@ -1391,6 +1416,7 @@ struct xen_domctl {
#if defined(__arm__) || defined(__aarch64__)
struct xen_domctl_dt_overlay dt_overlay;
#endif
+ struct xen_domctl_get_domain_state get_domain_state;
uint8_t pad[128];
} u;
};
@@ -36,6 +36,13 @@ void send_global_virq(uint32_t virq);
*/
void send_guest_global_virq(struct domain *d, uint32_t virq);
+/*
+ * domain_handles_global_virq:
+ * @d: Suspected target domain for this VIRQ
+ * @virq: Virtual IRQ number (VIRQ_*), must be global
+ */
+bool domain_handles_global_virq(struct domain *d, uint32_t virq);
+
/*
* sent_global_virq_handler: Set a global VIRQ handler.
* @d: New target domain for this VIRQ
@@ -802,6 +802,8 @@ int domain_soft_reset(struct domain *d, bool resuming);
int domain_init_states(void);
void domain_deinit_states(void);
+int get_domain_state(struct xen_domctl_get_domain_state *info,
+ struct domain *d, domid_t *domid);
int vcpu_start_shutdown_deferral(struct vcpu *v);
void vcpu_end_shutdown_deferral(struct vcpu *v);
@@ -173,6 +173,7 @@ static XSM_INLINE int cf_check xsm_domctl(
case XEN_DOMCTL_unbind_pt_irq:
return xsm_default_action(XSM_DM_PRIV, current->domain, d);
case XEN_DOMCTL_getdomaininfo:
+ case XEN_DOMCTL_get_domain_state:
return xsm_default_action(XSM_XS_PRIV, current->domain, d);
default:
return xsm_default_action(XSM_PRIV, current->domain, d);
@@ -815,6 +816,13 @@ static XSM_INLINE int cf_check xsm_argo_send(
#endif /* CONFIG_ARGO */
+static XSM_INLINE int cf_check xsm_get_domain_state(
+ XSM_DEFAULT_ARG struct domain *d)
+{
+ XSM_ASSERT_ACTION(XSM_XS_PRIV);
+ return xsm_default_action(action, current->domain, d);
+}
+
#include <public/version.h>
static XSM_INLINE int cf_check xsm_xen_version(XSM_DEFAULT_ARG uint32_t op)
{
@@ -200,6 +200,7 @@ struct xsm_ops {
int (*argo_register_any_source)(const struct domain *d);
int (*argo_send)(const struct domain *d, const struct domain *t);
#endif
+ int (*get_domain_state)(struct domain *d);
};
#ifdef CONFIG_XSM
@@ -774,6 +775,11 @@ static inline int xsm_argo_send(const struct domain *d, const struct domain *t)
#endif /* CONFIG_ARGO */
+static inline int xsm_get_domain_state(struct domain *d)
+{
+ return alternative_call(xsm_ops.get_domain_state, d);
+}
+
#endif /* XSM_NO_WRAPPERS */
#ifdef CONFIG_MULTIBOOT
@@ -148,6 +148,7 @@ static const struct xsm_ops __initconst_cf_clobber dummy_ops = {
.argo_register_any_source = xsm_argo_register_any_source,
.argo_send = xsm_argo_send,
#endif
+ .get_domain_state = xsm_get_domain_state,
};
void __init xsm_fixup_ops(struct xsm_ops *ops)
@@ -688,6 +688,7 @@ static int cf_check flask_domctl(struct domain *d, unsigned int cmd,
case XEN_DOMCTL_memory_mapping:
case XEN_DOMCTL_set_target:
case XEN_DOMCTL_vm_event_op:
+ case XEN_DOMCTL_get_domain_state:
/* These have individual XSM hooks (arch/../domctl.c) */
case XEN_DOMCTL_bind_pt_irq:
@@ -1856,6 +1857,11 @@ static int cf_check flask_argo_send(
#endif
+static int cf_check flask_get_domain_state(struct domain *d)
+{
+ return current_has_perm(d, SECCLASS_DOMAIN2, DOMAIN2__GET_DOMAIN_STATE);
+}
+
static const struct xsm_ops __initconst_cf_clobber flask_ops = {
.set_system_active = flask_set_system_active,
.security_domaininfo = flask_security_domaininfo,
@@ -1992,6 +1998,7 @@ static const struct xsm_ops __initconst_cf_clobber flask_ops = {
.argo_register_any_source = flask_argo_register_any_source,
.argo_send = flask_argo_send,
#endif
+ .get_domain_state = flask_get_domain_state,
};
const struct xsm_ops *__init flask_init(
@@ -251,6 +251,8 @@ class domain2
resource_map
# XEN_DOMCTL_get_cpu_policy
get_cpu_policy
+# XEN_DOMCTL_get_domain_state
+ get_domain_state
}
# Similar to class domain, but primarily contains domctls related to HVM domains
Add a new domctl sub-function to get data of a domain having changed state (this is needed by Xenstore). The returned state just contains the domid, the domain unique id, and some flags (existing, shutdown, dying). In order to enable Xenstore stubdom being built for multiple Xen versions, make this domctl stable. For stable domctls the interface_version is always 0. Signed-off-by: Juergen Gross <jgross@suse.com> --- V1: - use a domctl subop for the new interface (Jan Beulich) V2: - fix XSM hooks (Daniel P. Smith) - remove versioning of stable sub-ops (Jan Beulich) - use domctl.domain for retuning domid of a changed domain (Jan Beulich) - simplify locking in get_domain_state() (Jan Beulich) - undo stray change in event_channel.c (Jan Beulich) V3: - have disjunct states "dying" and "dead" (Jan Beulich) - check padding fields to be 0 (Jan Beulich) - drop memset() (Jan Beulich) V4: - add locking in get_domain_state() (Jan Beulich) - only allow querying domain having changed state by domain receiving VIRQ_DOM_EXC events (Jan Beulich) --- tools/flask/policy/modules/dom0.te | 2 +- tools/flask/policy/modules/xen.if | 4 +- tools/flask/policy/modules/xenstore.te | 1 + xen/common/domain.c | 70 ++++++++++++++++++++++++++ xen/common/domctl.c | 18 ++++++- xen/common/event_channel.c | 9 +++- xen/include/public/domctl.h | 26 ++++++++++ xen/include/xen/event.h | 7 +++ xen/include/xen/sched.h | 2 + xen/include/xsm/dummy.h | 8 +++ xen/include/xsm/xsm.h | 6 +++ xen/xsm/dummy.c | 1 + xen/xsm/flask/hooks.c | 7 +++ xen/xsm/flask/policy/access_vectors | 2 + 14 files changed, 158 insertions(+), 5 deletions(-)