Commit 879dbe9f authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'x86_sgx_for_v5.16_rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip

Pull x86 SGX updates from Borislav Petkov:
 "Add a SGX_IOC_VEPC_REMOVE ioctl to the /dev/sgx_vepc virt interface
  with which EPC pages can be put back into their uninitialized state
  without having to reopen /dev/sgx_vepc, which could not be possible
  anymore after startup due to security policies"

* tag 'x86_sgx_for_v5.16_rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip:
  x86/sgx/virt: implement SGX_IOC_VEPC_REMOVE ioctl
  x86/sgx/virt: extract sgx_vepc_remove_page
parents 20273d25 ae095b16
...@@ -250,3 +250,38 @@ user wants to deploy SGX applications both on the host and in guests ...@@ -250,3 +250,38 @@ user wants to deploy SGX applications both on the host and in guests
on the same machine, the user should reserve enough EPC (by taking out on the same machine, the user should reserve enough EPC (by taking out
total virtual EPC size of all SGX VMs from the physical EPC size) for total virtual EPC size of all SGX VMs from the physical EPC size) for
host SGX applications so they can run with acceptable performance. host SGX applications so they can run with acceptable performance.
Architectural behavior is to restore all EPC pages to an uninitialized
state also after a guest reboot. Because this state can be reached only
through the privileged ``ENCLS[EREMOVE]`` instruction, ``/dev/sgx_vepc``
provides the ``SGX_IOC_VEPC_REMOVE_ALL`` ioctl to execute the instruction
on all pages in the virtual EPC.
``EREMOVE`` can fail for three reasons. Userspace must pay attention
to expected failures and handle them as follows:
1. Page removal will always fail when any thread is running in the
enclave to which the page belongs. In this case the ioctl will
return ``EBUSY`` independent of whether it has successfully removed
some pages; userspace can avoid these failures by preventing execution
of any vcpu which maps the virtual EPC.
2. Page removal will cause a general protection fault if two calls to
``EREMOVE`` happen concurrently for pages that refer to the same
"SECS" metadata pages. This can happen if there are concurrent
invocations to ``SGX_IOC_VEPC_REMOVE_ALL``, or if a ``/dev/sgx_vepc``
file descriptor in the guest is closed at the same time as
``SGX_IOC_VEPC_REMOVE_ALL``; it will also be reported as ``EBUSY``.
This can be avoided in userspace by serializing calls to the ioctl()
and to close(), but in general it should not be a problem.
3. Finally, page removal will fail for SECS metadata pages which still
have child pages. Child pages can be removed by executing
``SGX_IOC_VEPC_REMOVE_ALL`` on all ``/dev/sgx_vepc`` file descriptors
mapped into the guest. This means that the ioctl() must be called
twice: an initial set of calls to remove child pages and a subsequent
set of calls to remove SECS pages. The second set of calls is only
required for those mappings that returned a nonzero value from the
first call. It indicates a bug in the kernel or the userspace client
if any of the second round of ``SGX_IOC_VEPC_REMOVE_ALL`` calls has
a return code other than 0.
...@@ -27,6 +27,8 @@ enum sgx_page_flags { ...@@ -27,6 +27,8 @@ enum sgx_page_flags {
_IOW(SGX_MAGIC, 0x02, struct sgx_enclave_init) _IOW(SGX_MAGIC, 0x02, struct sgx_enclave_init)
#define SGX_IOC_ENCLAVE_PROVISION \ #define SGX_IOC_ENCLAVE_PROVISION \
_IOW(SGX_MAGIC, 0x03, struct sgx_enclave_provision) _IOW(SGX_MAGIC, 0x03, struct sgx_enclave_provision)
#define SGX_IOC_VEPC_REMOVE_ALL \
_IO(SGX_MAGIC, 0x04)
/** /**
* struct sgx_enclave_create - parameter structure for the * struct sgx_enclave_create - parameter structure for the
......
...@@ -111,10 +111,8 @@ static int sgx_vepc_mmap(struct file *file, struct vm_area_struct *vma) ...@@ -111,10 +111,8 @@ static int sgx_vepc_mmap(struct file *file, struct vm_area_struct *vma)
return 0; return 0;
} }
static int sgx_vepc_free_page(struct sgx_epc_page *epc_page) static int sgx_vepc_remove_page(struct sgx_epc_page *epc_page)
{ {
int ret;
/* /*
* Take a previously guest-owned EPC page and return it to the * Take a previously guest-owned EPC page and return it to the
* general EPC page pool. * general EPC page pool.
...@@ -124,7 +122,12 @@ static int sgx_vepc_free_page(struct sgx_epc_page *epc_page) ...@@ -124,7 +122,12 @@ static int sgx_vepc_free_page(struct sgx_epc_page *epc_page)
* case that a guest properly EREMOVE'd this page, a superfluous * case that a guest properly EREMOVE'd this page, a superfluous
* EREMOVE is harmless. * EREMOVE is harmless.
*/ */
ret = __eremove(sgx_get_epc_virt_addr(epc_page)); return __eremove(sgx_get_epc_virt_addr(epc_page));
}
static int sgx_vepc_free_page(struct sgx_epc_page *epc_page)
{
int ret = sgx_vepc_remove_page(epc_page);
if (ret) { if (ret) {
/* /*
* Only SGX_CHILD_PRESENT is expected, which is because of * Only SGX_CHILD_PRESENT is expected, which is because of
...@@ -144,10 +147,44 @@ static int sgx_vepc_free_page(struct sgx_epc_page *epc_page) ...@@ -144,10 +147,44 @@ static int sgx_vepc_free_page(struct sgx_epc_page *epc_page)
} }
sgx_free_epc_page(epc_page); sgx_free_epc_page(epc_page);
return 0; return 0;
} }
static long sgx_vepc_remove_all(struct sgx_vepc *vepc)
{
struct sgx_epc_page *entry;
unsigned long index;
long failures = 0;
xa_for_each(&vepc->page_array, index, entry) {
int ret = sgx_vepc_remove_page(entry);
if (ret) {
if (ret == SGX_CHILD_PRESENT) {
/* The page is a SECS, userspace will retry. */
failures++;
} else {
/*
* Report errors due to #GP or SGX_ENCLAVE_ACT; do not
* WARN, as userspace can induce said failures by
* calling the ioctl concurrently on multiple vEPCs or
* while one or more CPUs is running the enclave. Only
* a #PF on EREMOVE indicates a kernel/hardware issue.
*/
WARN_ON_ONCE(encls_faulted(ret) &&
ENCLS_TRAPNR(ret) != X86_TRAP_GP);
return -EBUSY;
}
}
cond_resched();
}
/*
* Return the number of SECS pages that failed to be removed, so
* userspace knows that it has to retry.
*/
return failures;
}
static int sgx_vepc_release(struct inode *inode, struct file *file) static int sgx_vepc_release(struct inode *inode, struct file *file)
{ {
struct sgx_vepc *vepc = file->private_data; struct sgx_vepc *vepc = file->private_data;
...@@ -233,9 +270,27 @@ static int sgx_vepc_open(struct inode *inode, struct file *file) ...@@ -233,9 +270,27 @@ static int sgx_vepc_open(struct inode *inode, struct file *file)
return 0; return 0;
} }
static long sgx_vepc_ioctl(struct file *file,
unsigned int cmd, unsigned long arg)
{
struct sgx_vepc *vepc = file->private_data;
switch (cmd) {
case SGX_IOC_VEPC_REMOVE_ALL:
if (arg)
return -EINVAL;
return sgx_vepc_remove_all(vepc);
default:
return -ENOTTY;
}
}
static const struct file_operations sgx_vepc_fops = { static const struct file_operations sgx_vepc_fops = {
.owner = THIS_MODULE, .owner = THIS_MODULE,
.open = sgx_vepc_open, .open = sgx_vepc_open,
.unlocked_ioctl = sgx_vepc_ioctl,
.compat_ioctl = sgx_vepc_ioctl,
.release = sgx_vepc_release, .release = sgx_vepc_release,
.mmap = sgx_vepc_mmap, .mmap = sgx_vepc_mmap,
}; };
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment