diff --git a/tools/flask/policy/policy/modules/xen/xen.te b/tools/flask/policy/policy/modules/xen/xen.te index 2a2630d15a..daa1315a1f 100644 --- a/tools/flask/policy/policy/modules/xen/xen.te +++ b/tools/flask/policy/policy/modules/xen/xen.te @@ -74,6 +74,7 @@ allow dom0_t xen_t:xen2 { get_symbol get_cpu_levelling_caps get_cpu_featureset + xsplice_op }; # Allow dom0 to use all XENVER_ subops that have checks. diff --git a/xen/common/Kconfig b/xen/common/Kconfig index ad9f7bfe54..692ef51d12 100644 --- a/xen/common/Kconfig +++ b/xen/common/Kconfig @@ -188,4 +188,16 @@ config SCHED_DEFAULT endmenu +# Enable/Disable xsplice support +config XSPLICE + bool "xSplice live patching support (TECH PREVIEW)" + default n + depends on X86 + ---help--- + Allows a running Xen hypervisor to be dynamically patched using + binary patches without rebooting. This is primarily used to binarily + patch in the field an hypervisor with XSA fixes. + + If unsure, say Y. + endmenu diff --git a/xen/common/Makefile b/xen/common/Makefile index 77de27ed17..910ac69418 100644 --- a/xen/common/Makefile +++ b/xen/common/Makefile @@ -57,6 +57,7 @@ obj-y += vsprintf.o obj-y += wait.o obj-$(CONFIG_XENOPROF) += xenoprof.o obj-y += xmalloc_tlsf.o +obj-$(CONFIG_XSPLICE) += xsplice.o obj-bin-$(CONFIG_X86) += $(foreach n,decompress bunzip2 unxz unlzma unlzo unlz4 earlycpio,$(n).init.o) diff --git a/xen/common/sysctl.c b/xen/common/sysctl.c index 253b7c82e2..9a4cc1f08e 100644 --- a/xen/common/sysctl.c +++ b/xen/common/sysctl.c @@ -28,6 +28,7 @@ #include #include #include +#include long do_sysctl(XEN_GUEST_HANDLE_PARAM(xen_sysctl_t) u_sysctl) { @@ -460,6 +461,12 @@ long do_sysctl(XEN_GUEST_HANDLE_PARAM(xen_sysctl_t) u_sysctl) ret = tmem_control(&op->u.tmem_op); break; + case XEN_SYSCTL_xsplice_op: + ret = xsplice_op(&op->u.xsplice); + if ( ret != -ENOSYS && ret != -EOPNOTSUPP ) + copyback = 1; + break; + default: ret = arch_do_sysctl(op, u_sysctl); copyback = 0; diff --git a/xen/common/xsplice.c b/xen/common/xsplice.c new file mode 100644 index 0000000000..73e50f077f --- /dev/null +++ b/xen/common/xsplice.c @@ -0,0 +1,383 @@ +/* + * Copyright (c) 2016 Oracle and/or its affiliates. All rights reserved. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* Protects against payload_list operations. */ +static DEFINE_SPINLOCK(payload_lock); +static LIST_HEAD(payload_list); + +static unsigned int payload_cnt; +static unsigned int payload_version = 1; + +struct payload { + uint32_t state; /* One of the XSPLICE_STATE_*. */ + int32_t rc; /* 0 or -XEN_EXX. */ + struct list_head list; /* Linked to 'payload_list'. */ + char name[XEN_XSPLICE_NAME_SIZE]; /* Name of it. */ +}; + +static int get_name(const xen_xsplice_name_t *name, char *n) +{ + if ( !name->size || name->size > XEN_XSPLICE_NAME_SIZE ) + return -EINVAL; + + if ( name->pad[0] || name->pad[1] || name->pad[2] ) + return -EINVAL; + + if ( copy_from_guest(n, name->name, name->size) ) + return -EFAULT; + + if ( n[name->size - 1] ) + return -EINVAL; + + return 0; +} + +static int verify_payload(const xen_sysctl_xsplice_upload_t *upload, char *n) +{ + if ( get_name(&upload->name, n) ) + return -EINVAL; + + if ( !upload->size ) + return -EINVAL; + + if ( upload->size > MB(2) ) + return -EINVAL; + + if ( !guest_handle_okay(upload->payload, upload->size) ) + return -EFAULT; + + return 0; +} + +static struct payload *find_payload(const char *name) +{ + struct payload *data, *found = NULL; + + ASSERT(spin_is_locked(&payload_lock)); + list_for_each_entry ( data, &payload_list, list ) + { + if ( !strcmp(data->name, name) ) + { + found = data; + break; + } + } + + return found; +} + +static void free_payload(struct payload *data) +{ + ASSERT(spin_is_locked(&payload_lock)); + list_del(&data->list); + payload_cnt--; + payload_version++; + xfree(data); +} + +static int xsplice_upload(xen_sysctl_xsplice_upload_t *upload) +{ + struct payload *data, *found; + char n[XEN_XSPLICE_NAME_SIZE]; + int rc; + + rc = verify_payload(upload, n); + if ( rc ) + return rc; + + data = xzalloc(struct payload); + + spin_lock(&payload_lock); + + found = find_payload(n); + if ( IS_ERR(found) ) + rc = PTR_ERR(found); + else if ( found ) + rc = -EEXIST; + else if ( !data ) + rc = -ENOMEM; + else + { + memcpy(data->name, n, strlen(n)); + data->state = XSPLICE_STATE_CHECKED; + INIT_LIST_HEAD(&data->list); + + list_add_tail(&data->list, &payload_list); + payload_cnt++; + payload_version++; + } + spin_unlock(&payload_lock); + + if ( rc ) + xfree(data); + + return rc; +} + +static int xsplice_get(xen_sysctl_xsplice_get_t *get) +{ + struct payload *data; + int rc; + char n[XEN_XSPLICE_NAME_SIZE]; + + rc = get_name(&get->name, n); + if ( rc ) + return rc; + + spin_lock(&payload_lock); + + data = find_payload(n); + if ( IS_ERR_OR_NULL(data) ) + { + spin_unlock(&payload_lock); + + if ( !data ) + return -ENOENT; + + return PTR_ERR(data); + } + + get->status.state = data->state; + get->status.rc = data->rc; + + spin_unlock(&payload_lock); + + return 0; +} + +static int xsplice_list(xen_sysctl_xsplice_list_t *list) +{ + xen_xsplice_status_t status; + struct payload *data; + unsigned int idx = 0, i = 0; + int rc = 0; + + if ( list->nr > 1024 ) + return -E2BIG; + + if ( list->pad ) + return -EINVAL; + + if ( list->nr && + (!guest_handle_okay(list->status, list->nr) || + !guest_handle_okay(list->name, XEN_XSPLICE_NAME_SIZE * list->nr) || + !guest_handle_okay(list->len, list->nr)) ) + return -EINVAL; + + spin_lock(&payload_lock); + if ( list->idx >= payload_cnt && payload_cnt ) + { + spin_unlock(&payload_lock); + return -EINVAL; + } + + if ( list->nr ) + { + list_for_each_entry( data, &payload_list, list ) + { + uint32_t len; + + if ( list->idx > i++ ) + continue; + + status.state = data->state; + status.rc = data->rc; + len = strlen(data->name) + 1; + + /* N.B. 'idx' != 'i'. */ + if ( __copy_to_guest_offset(list->name, idx * XEN_XSPLICE_NAME_SIZE, + data->name, len) || + __copy_to_guest_offset(list->len, idx, &len, 1) || + __copy_to_guest_offset(list->status, idx, &status, 1) ) + { + rc = -EFAULT; + break; + } + + idx++; + + if ( (idx >= list->nr) || hypercall_preempt_check() ) + break; + } + } + list->nr = payload_cnt - i; /* Remaining amount. */ + list->version = payload_version; + spin_unlock(&payload_lock); + + /* And how many we have processed. */ + return rc ? : idx; +} + +static int xsplice_action(xen_sysctl_xsplice_action_t *action) +{ + struct payload *data; + char n[XEN_XSPLICE_NAME_SIZE]; + int rc; + + rc = get_name(&action->name, n); + if ( rc ) + return rc; + + spin_lock(&payload_lock); + + data = find_payload(n); + if ( IS_ERR_OR_NULL(data) ) + { + spin_unlock(&payload_lock); + + if ( !data ) + return -ENOENT; + + return PTR_ERR(data); + } + + switch ( action->cmd ) + { + case XSPLICE_ACTION_UNLOAD: + if ( data->state == XSPLICE_STATE_CHECKED ) + { + free_payload(data); + /* No touching 'data' from here on! */ + data = NULL; + } + else + rc = -EINVAL; + break; + + case XSPLICE_ACTION_REVERT: + if ( data->state == XSPLICE_STATE_APPLIED ) + { + /* No implementation yet. */ + data->state = XSPLICE_STATE_CHECKED; + data->rc = 0; + } + break; + + case XSPLICE_ACTION_APPLY: + if ( data->state == XSPLICE_STATE_CHECKED ) + { + /* No implementation yet. */ + data->state = XSPLICE_STATE_APPLIED; + data->rc = 0; + } + break; + + case XSPLICE_ACTION_REPLACE: + if ( data->state == XSPLICE_STATE_CHECKED ) + { + /* No implementation yet. */ + data->state = XSPLICE_STATE_CHECKED; + data->rc = 0; + } + break; + + default: + rc = -EOPNOTSUPP; + break; + } + + spin_unlock(&payload_lock); + + return rc; +} + +int xsplice_op(xen_sysctl_xsplice_op_t *xsplice) +{ + int rc; + + if ( xsplice->pad ) + return -EINVAL; + + switch ( xsplice->cmd ) + { + case XEN_SYSCTL_XSPLICE_UPLOAD: + rc = xsplice_upload(&xsplice->u.upload); + break; + + case XEN_SYSCTL_XSPLICE_GET: + rc = xsplice_get(&xsplice->u.get); + break; + + case XEN_SYSCTL_XSPLICE_LIST: + rc = xsplice_list(&xsplice->u.list); + break; + + case XEN_SYSCTL_XSPLICE_ACTION: + rc = xsplice_action(&xsplice->u.action); + break; + + default: + rc = -EOPNOTSUPP; + break; + } + + return rc; +} + +static const char *state2str(unsigned int state) +{ +#define STATE(x) [XSPLICE_STATE_##x] = #x + static const char *const names[] = { + STATE(CHECKED), + STATE(APPLIED), + }; +#undef STATE + + if ( state >= ARRAY_SIZE(names) || !names[state] ) + return "unknown"; + + return names[state]; +} + +static void xsplice_printall(unsigned char key) +{ + struct payload *data; + + printk("'%c' pressed - Dumping all xsplice patches\n", key); + + if ( !spin_trylock(&payload_lock) ) + { + printk("Lock held. Try again.\n"); + return; + } + + list_for_each_entry ( data, &payload_list, list ) + printk(" name=%s state=%s(%d)\n", data->name, + state2str(data->state), data->state); + + spin_unlock(&payload_lock); +} + +static int __init xsplice_init(void) +{ + register_keyhandler('x', xsplice_printall, "print xsplicing info", 1); + return 0; +} +__initcall(xsplice_init); + +/* + * Local variables: + * mode: C + * c-file-style: "BSD" + * c-basic-offset: 4 + * tab-width: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/xen/include/public/sysctl.h b/xen/include/public/sysctl.h index 82a2a3e932..3fa1fe7dd8 100644 --- a/xen/include/public/sysctl.h +++ b/xen/include/public/sysctl.h @@ -848,6 +848,169 @@ struct xen_sysctl_cpu_featureset { typedef struct xen_sysctl_featureset xen_sysctl_featureset_t; DEFINE_XEN_GUEST_HANDLE(xen_sysctl_featureset_t); +/* + * XEN_SYSCTL_XSPLICE_op + * + * Refer to the docs/unstable/misc/xsplice.markdown + * for the design details of this hypercall. + * + * There are four sub-ops: + * XEN_SYSCTL_XSPLICE_UPLOAD (0) + * XEN_SYSCTL_XSPLICE_GET (1) + * XEN_SYSCTL_XSPLICE_LIST (2) + * XEN_SYSCTL_XSPLICE_ACTION (3) + * + * The normal sequence of sub-ops is to: + * 1) XEN_SYSCTL_XSPLICE_UPLOAD to upload the payload. If errors STOP. + * 2) XEN_SYSCTL_XSPLICE_GET to check the `->rc`. If -XEN_EAGAIN spin. + * If zero go to next step. + * 3) XEN_SYSCTL_XSPLICE_ACTION with XSPLICE_ACTION_APPLY to apply the patch. + * 4) XEN_SYSCTL_XSPLICE_GET to check the `->rc`. If in -XEN_EAGAIN spin. + * If zero exit with success. + */ + +/* + * Structure describing an ELF payload. Uniquely identifies the + * payload. Should be human readable. + * Recommended length is upto XEN_XSPLICE_NAME_SIZE. + * Includes the NUL terminator. + */ +#define XEN_XSPLICE_NAME_SIZE 128 +struct xen_xsplice_name { + XEN_GUEST_HANDLE_64(char) name; /* IN: pointer to name. */ + uint16_t size; /* IN: size of name. May be upto + XEN_XSPLICE_NAME_SIZE. */ + uint16_t pad[3]; /* IN: MUST be zero. */ +}; +typedef struct xen_xsplice_name xen_xsplice_name_t; +DEFINE_XEN_GUEST_HANDLE(xen_xsplice_name_t); + +/* + * Upload a payload to the hypervisor. The payload is verified + * against basic checks and if there are any issues the proper return code + * will be returned. The payload is not applied at this time - that is + * controlled by XEN_SYSCTL_XSPLICE_ACTION. + * + * The return value is zero if the payload was succesfully uploaded. + * Otherwise an EXX return value is provided. Duplicate `name` are not + * supported. + * + * The payload at this point is verified against basic checks. + * + * The `payload` is the ELF payload as mentioned in the `Payload format` + * section in the xSplice design document. + */ +#define XEN_SYSCTL_XSPLICE_UPLOAD 0 +struct xen_sysctl_xsplice_upload { + xen_xsplice_name_t name; /* IN, name of the patch. */ + uint64_t size; /* IN, size of the ELF file. */ + XEN_GUEST_HANDLE_64(uint8) payload; /* IN, the ELF file. */ +}; +typedef struct xen_sysctl_xsplice_upload xen_sysctl_xsplice_upload_t; +DEFINE_XEN_GUEST_HANDLE(xen_sysctl_xsplice_upload_t); + +/* + * Retrieve an status of an specific payload. + * + * Upon completion the `struct xen_xsplice_status` is updated. + * + * The return value is zero on success and XEN_EXX on failure. This operation + * is synchronous and does not require preemption. + */ +#define XEN_SYSCTL_XSPLICE_GET 1 + +struct xen_xsplice_status { +#define XSPLICE_STATE_CHECKED 1 +#define XSPLICE_STATE_APPLIED 2 + uint32_t state; /* OUT: XSPLICE_STATE_*. */ + int32_t rc; /* OUT: 0 if no error, otherwise -XEN_EXX. */ +}; +typedef struct xen_xsplice_status xen_xsplice_status_t; +DEFINE_XEN_GUEST_HANDLE(xen_xsplice_status_t); + +struct xen_sysctl_xsplice_get { + xen_xsplice_name_t name; /* IN, name of the payload. */ + xen_xsplice_status_t status; /* IN/OUT, state of it. */ +}; +typedef struct xen_sysctl_xsplice_get xen_sysctl_xsplice_get_t; +DEFINE_XEN_GUEST_HANDLE(xen_sysctl_xsplice_get_t); + +/* + * Retrieve an array of abbreviated status and names of payloads that are + * loaded in the hypervisor. + * + * If the hypercall returns an positive number, it is the number (up to `nr`) + * of the payloads returned, along with `nr` updated with the number of remaining + * payloads, `version` updated (it may be the same across hypercalls. If it + * varies the data is stale and further calls could fail). The `status`, + * `name`, and `len`' are updated at their designed index value (`idx`) with + * the returned value of data. + * + * If the hypercall returns E2BIG the `nr` is too big and should be + * lowered. The upper limit of `nr` is left to the implemention. + * + * Note that due to the asynchronous nature of hypercalls the domain might have + * added or removed the number of payloads making this information stale. It is + * the responsibility of the toolstack to use the `version` field to check + * between each invocation. if the version differs it should discard the stale + * data and start from scratch. It is OK for the toolstack to use the new + * `version` field. + */ +#define XEN_SYSCTL_XSPLICE_LIST 2 +struct xen_sysctl_xsplice_list { + uint32_t version; /* OUT: Hypervisor stamps value. + If varies between calls, we are + * getting stale data. */ + uint32_t idx; /* IN: Index into hypervisor list. */ + uint32_t nr; /* IN: How many status, name, and len + should fill out. Can be zero to get + amount of payloads and version. + OUT: How many payloads left. */ + uint32_t pad; /* IN: Must be zero. */ + XEN_GUEST_HANDLE_64(xen_xsplice_status_t) status; /* OUT. Must have enough + space allocate for nr of them. */ + XEN_GUEST_HANDLE_64(char) name; /* OUT: Array of names. Each member + MUST XEN_XSPLICE_NAME_SIZE in size. + Must have nr of them. */ + XEN_GUEST_HANDLE_64(uint32) len; /* OUT: Array of lengths of name's. + Must have nr of them. */ +}; +typedef struct xen_sysctl_xsplice_list xen_sysctl_xsplice_list_t; +DEFINE_XEN_GUEST_HANDLE(xen_sysctl_xsplice_list_t); + +/* + * Perform an operation on the payload structure referenced by the `name` field. + * The operation request is asynchronous and the status should be retrieved + * by using either XEN_SYSCTL_XSPLICE_GET or XEN_SYSCTL_XSPLICE_LIST hypercall. + */ +#define XEN_SYSCTL_XSPLICE_ACTION 3 +struct xen_sysctl_xsplice_action { + xen_xsplice_name_t name; /* IN, name of the patch. */ +#define XSPLICE_ACTION_UNLOAD 1 +#define XSPLICE_ACTION_REVERT 2 +#define XSPLICE_ACTION_APPLY 3 +#define XSPLICE_ACTION_REPLACE 4 + uint32_t cmd; /* IN: XSPLICE_ACTION_*. */ + uint32_t timeout; /* IN: Zero if no timeout. */ + /* Or upper bound of time (ms) */ + /* for operation to take. */ +}; +typedef struct xen_sysctl_xsplice_action xen_sysctl_xsplice_action_t; +DEFINE_XEN_GUEST_HANDLE(xen_sysctl_xsplice_action_t); + +struct xen_sysctl_xsplice_op { + uint32_t cmd; /* IN: XEN_SYSCTL_XSPLICE_*. */ + uint32_t pad; /* IN: Always zero. */ + union { + xen_sysctl_xsplice_upload_t upload; + xen_sysctl_xsplice_list_t list; + xen_sysctl_xsplice_get_t get; + xen_sysctl_xsplice_action_t action; + } u; +}; +typedef struct xen_sysctl_xsplice_op xen_sysctl_xsplice_op_t; +DEFINE_XEN_GUEST_HANDLE(xen_sysctl_xsplice_op_t); + struct xen_sysctl { uint32_t cmd; #define XEN_SYSCTL_readconsole 1 @@ -875,6 +1038,7 @@ struct xen_sysctl { #define XEN_SYSCTL_tmem_op 24 #define XEN_SYSCTL_get_cpu_levelling_caps 25 #define XEN_SYSCTL_get_cpu_featureset 26 +#define XEN_SYSCTL_xsplice_op 27 uint32_t interface_version; /* XEN_SYSCTL_INTERFACE_VERSION */ union { struct xen_sysctl_readconsole readconsole; @@ -902,6 +1066,7 @@ struct xen_sysctl { struct xen_sysctl_tmem_op tmem_op; struct xen_sysctl_cpu_levelling_caps cpu_levelling_caps; struct xen_sysctl_cpu_featureset cpu_featureset; + struct xen_sysctl_xsplice_op xsplice; uint8_t pad[128]; } u; }; diff --git a/xen/include/xen/xsplice.h b/xen/include/xen/xsplice.h new file mode 100644 index 0000000000..b9f08cd559 --- /dev/null +++ b/xen/include/xen/xsplice.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2016 Oracle and/or its affiliates. All rights reserved. + * + */ + +#ifndef __XEN_XSPLICE_H__ +#define __XEN_XSPLICE_H__ + +struct xen_sysctl_xsplice_op; + +#ifdef CONFIG_XSPLICE + +int xsplice_op(struct xen_sysctl_xsplice_op *); + +#else + +#include /* For -ENOSYS */ +static inline int xsplice_op(struct xen_sysctl_xsplice_op *op) +{ + return -ENOSYS; +} + +#endif /* CONFIG_XSPLICE */ + +#endif /* __XEN_XSPLICE_H__ */ + +/* + * Local variables: + * mode: C + * c-file-style: "BSD" + * c-basic-offset: 4 + * tab-width: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/xen/xsm/flask/hooks.c b/xen/xsm/flask/hooks.c index 62957688d4..c2df48f1b4 100644 --- a/xen/xsm/flask/hooks.c +++ b/xen/xsm/flask/hooks.c @@ -814,6 +814,10 @@ static int flask_sysctl(int cmd) case XEN_SYSCTL_get_cpu_featureset: return domain_has_xen(current->domain, XEN2__GET_CPU_FEATURESET); + case XEN_SYSCTL_xsplice_op: + return avc_current_has_perm(SECINITSID_XEN, SECCLASS_XEN2, + XEN2__XSPLICE_OP, NULL); + default: printk("flask_sysctl: Unknown op %d\n", cmd); return -EPERM; diff --git a/xen/xsm/flask/policy/access_vectors b/xen/xsm/flask/policy/access_vectors index bdb7b8959f..e9ab149db8 100644 --- a/xen/xsm/flask/policy/access_vectors +++ b/xen/xsm/flask/policy/access_vectors @@ -97,6 +97,8 @@ class xen2 get_cpu_levelling_caps # XEN_SYSCTL_get_cpu_featureset get_cpu_featureset +# XEN_SYSCTL_xsplice_op + xsplice_op } # Classes domain and domain2 consist of operations that a domain performs on