The XSM checks for XEN_DOMCTL_createdomain are problematic. There's a split
between xsm_domctl() called early, and flask_domain_create() called quite late
during domain construction.
All XSM implementations except Flask have a simple IS_PRIV check in
xsm_domctl(), and operate as expected when an unprivileged domain tries to
make a hypercall.
Flask however foregoes any action in xsm_domctl() and defers everything,
including the simple "is the caller permitted to create a domain" check, to
flask_domain_create().
As a consequence, when XSM Flask is active, and irrespective of the policy
loaded, all domains irrespective of privilege can:
* Mutate the global 'rover' variable, used to track the next free domid.
Therefore, all domains can cause a domid wraparound, and combined with a
voluntary reboot, choose their own domid.
* Cause a reasonable amount of a domain to be constructed before ultimately
failing for permission reasons, including the use of settings outside of
supported limits.
In order to remediate this, pass the ssidref into xsm_domctl() and at least
check that the calling domain privileged enough to create domains.
Take the opportunity to also fix the sign of the cmd parameter to be unsigned.
This issue has not been assigned an XSA, because Flask is experimental and not
security supported.
Reported-by: Ross Lagerwall <ross.lagerwall@citrix.com>
Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
Reviewed-by: Jan Beulich <jbeulich@suse.com>
Acked-by: Daniel P. Smith <dpsmith@apertussolutions.com>
master commit:
ee32b9b29af449d38aad0a1b3a81aaae586f5ea7
master date: 2024-07-30 17:42:17 +0100
if ( d == NULL )
return -ESRCH;
- ret = xsm_domctl(XSM_OTHER, d, op.cmd);
+ ret = xsm_domctl(XSM_OTHER, d, op.cmd, 0 /* SSIDref not applicable */);
if ( !ret )
{
if ( domctl_lock_acquire() )
return -ESRCH;
}
- ret = xsm_domctl(XSM_OTHER, d, op->cmd);
+ ret = xsm_domctl(XSM_OTHER, d, op->cmd,
+ /* SSIDRef only applicable for cmd == createdomain */
+ op->u.createdomain.ssidref);
if ( ret )
goto domctl_out_unlock_domonly;
}
static XSM_INLINE int cf_check xsm_domctl(
- XSM_DEFAULT_ARG struct domain *d, int cmd)
+ XSM_DEFAULT_ARG struct domain *d, unsigned int cmd, uint32_t ssidref)
{
XSM_ASSERT_ACTION(XSM_OTHER);
switch ( cmd )
int (*domctl_scheduler_op)(struct domain *d, int op);
int (*sysctl_scheduler_op)(int op);
int (*set_target)(struct domain *d, struct domain *e);
- int (*domctl)(struct domain *d, int cmd);
+ int (*domctl)(struct domain *d, unsigned int cmd, uint32_t ssidref);
int (*sysctl)(int cmd);
int (*readconsole)(uint32_t clear);
return alternative_call(xsm_ops.set_target, d, e);
}
-static inline int xsm_domctl(xsm_default_t def, struct domain *d, int cmd)
+static inline int xsm_domctl(xsm_default_t def, struct domain *d,
+ unsigned int cmd, uint32_t ssidref)
{
- return alternative_call(xsm_ops.domctl, d, cmd);
+ return alternative_call(xsm_ops.domctl, d, cmd, ssidref);
}
static inline int xsm_sysctl(xsm_default_t def, int cmd)
return rc;
}
-static int cf_check flask_domctl(struct domain *d, int cmd)
+static int cf_check flask_domctl(struct domain *d, unsigned int cmd,
+ uint32_t ssidref)
{
switch ( cmd )
{
- /* These have individual XSM hooks (common/domctl.c) */
case XEN_DOMCTL_createdomain:
+ /*
+ * There is a later hook too, but at this early point simply check
+ * that the calling domain is privileged enough to create a domain.
+ *
+ * Note that d is NULL because we haven't even allocated memory for it
+ * this early in XEN_DOMCTL_createdomain.
+ */
+ return avc_current_has_perm(ssidref, SECCLASS_DOMAIN, DOMAIN__CREATE, NULL);
+
+ /* These have individual XSM hooks (common/domctl.c) */
case XEN_DOMCTL_getdomaininfo:
case XEN_DOMCTL_scheduler_op:
case XEN_DOMCTL_irq_permission: