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. -----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEEzv7L6UO9uDPlPSfHEsHwGGHeVUoFAmF/x7AACgkQEsHwGGHe VUqHXA//YWrukmJ5PQZwWqkXGo6h42JWhIdNfSC2c1SVdz1cioGUCCswALTX4g8l MYYf3eN12GJ296jPh7m9bz8JvlYjdavSm3Y1yzHIjuQ3q6qywHIuYTbsrMD7waUD PkcY1TTYgNJ2+f0AgsC4GZhlcpf9g5DqiftW6wvExx5tLUNsVu3Y3gZy/+fajP4f s/TMjcdr2QmPsjun00KfoIY4/z0u8LkyRMSwyoxSV6wYdL6rRtfYFWsbEUS+W6Nw /VJ0IKl+aBQ1ztsDc4M5h1uy9II2M/Row5k6JjyrdG4X8D6ACSG7cho6qcMjXgcP Gac7Im5IyjPEorxqXAgJiMoAl9lU9a2JMVZqPtihYsQW/ygMTdpzP9sBpcZPMevc gxQD4gyixwzUa3cyVDzTPBdk/DEuGc2nwn2k9nPvmNxKMonX1oLEiP7hu265mvet 56DtwKJF9ddtpepO2zFCg1qX+eZnTuhuZNCPsm/pmdGgzI8cyLznho33OgUSZEQY c1UisT7HXNRVC/1Q8VBDTU/D9LtIk+2+Q5lQkcNeftI5PYKTXIVddkOkqJ4GhGWJ 9EasA4UtnhvsLzJ76gxxuUf677ns+1TCo65e7Hu1+X0eTmBJK3boe3aMHvJeHEWH Asd+SMkYWfxAlW/arAYhR2JgT9wgEH3pSx4eXnpGwpeValxBPRs= =1UYy -----END PGP SIGNATURE----- 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
This commit is contained in:
commit
879dbe9ffe
@ -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 {
|
|||||||
_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)
|
|||||||
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)
|
|||||||
* 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)
|
|||||||
}
|
}
|
||||||
|
|
||||||
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)
|
|||||||
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,
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user