Author | Tokens | Token Proportion | Commits | Commit Proportion |
---|---|---|---|---|
Jarkko Sakkinen | 2670 | 99.07% | 7 | 53.85% |
Sean Christopherson | 12 | 0.45% | 2 | 15.38% |
Ira Weiny | 8 | 0.30% | 1 | 7.69% |
Kai Huang | 4 | 0.15% | 2 | 15.38% |
Borislav Petkov | 1 | 0.04% | 1 | 7.69% |
Total | 2695 | 13 |
// SPDX-License-Identifier: GPL-2.0 /* Copyright(c) 2016-20 Intel Corporation. */ #include <asm/mman.h> #include <asm/sgx.h> #include <linux/mman.h> #include <linux/delay.h> #include <linux/file.h> #include <linux/hashtable.h> #include <linux/highmem.h> #include <linux/ratelimit.h> #include <linux/sched/signal.h> #include <linux/shmem_fs.h> #include <linux/slab.h> #include <linux/suspend.h> #include "driver.h" #include "encl.h" #include "encls.h" static struct sgx_va_page *sgx_encl_grow(struct sgx_encl *encl) { struct sgx_va_page *va_page = NULL; void *err; BUILD_BUG_ON(SGX_VA_SLOT_COUNT != (SGX_ENCL_PAGE_VA_OFFSET_MASK >> 3) + 1); if (!(encl->page_cnt % SGX_VA_SLOT_COUNT)) { va_page = kzalloc(sizeof(*va_page), GFP_KERNEL); if (!va_page) return ERR_PTR(-ENOMEM); va_page->epc_page = sgx_alloc_va_page(); if (IS_ERR(va_page->epc_page)) { err = ERR_CAST(va_page->epc_page); kfree(va_page); return err; } WARN_ON_ONCE(encl->page_cnt % SGX_VA_SLOT_COUNT); } encl->page_cnt++; return va_page; } static void sgx_encl_shrink(struct sgx_encl *encl, struct sgx_va_page *va_page) { encl->page_cnt--; if (va_page) { sgx_encl_free_epc_page(va_page->epc_page); list_del(&va_page->list); kfree(va_page); } } static int sgx_encl_create(struct sgx_encl *encl, struct sgx_secs *secs) { struct sgx_epc_page *secs_epc; struct sgx_va_page *va_page; struct sgx_pageinfo pginfo; struct sgx_secinfo secinfo; unsigned long encl_size; struct file *backing; long ret; va_page = sgx_encl_grow(encl); if (IS_ERR(va_page)) return PTR_ERR(va_page); else if (va_page) list_add(&va_page->list, &encl->va_pages); /* else the tail page of the VA page list had free slots. */ /* The extra page goes to SECS. */ encl_size = secs->size + PAGE_SIZE; backing = shmem_file_setup("SGX backing", encl_size + (encl_size >> 5), VM_NORESERVE); if (IS_ERR(backing)) { ret = PTR_ERR(backing); goto err_out_shrink; } encl->backing = backing; secs_epc = sgx_alloc_epc_page(&encl->secs, true); if (IS_ERR(secs_epc)) { ret = PTR_ERR(secs_epc); goto err_out_backing; } encl->secs.epc_page = secs_epc; pginfo.addr = 0; pginfo.contents = (unsigned long)secs; pginfo.metadata = (unsigned long)&secinfo; pginfo.secs = 0; memset(&secinfo, 0, sizeof(secinfo)); ret = __ecreate((void *)&pginfo, sgx_get_epc_virt_addr(secs_epc)); if (ret) { ret = -EIO; goto err_out; } if (secs->attributes & SGX_ATTR_DEBUG) set_bit(SGX_ENCL_DEBUG, &encl->flags); encl->secs.encl = encl; encl->base = secs->base; encl->size = secs->size; encl->attributes = secs->attributes; encl->attributes_mask = SGX_ATTR_DEBUG | SGX_ATTR_MODE64BIT | SGX_ATTR_KSS; /* Set only after completion, as encl->lock has not been taken. */ set_bit(SGX_ENCL_CREATED, &encl->flags); return 0; err_out: sgx_encl_free_epc_page(encl->secs.epc_page); encl->secs.epc_page = NULL; err_out_backing: fput(encl->backing); encl->backing = NULL; err_out_shrink: sgx_encl_shrink(encl, va_page); return ret; } /** * sgx_ioc_enclave_create() - handler for %SGX_IOC_ENCLAVE_CREATE * @encl: An enclave pointer. * @arg: The ioctl argument. * * Allocate kernel data structures for the enclave and invoke ECREATE. * * Return: * - 0: Success. * - -EIO: ECREATE failed. * - -errno: POSIX error. */ static long sgx_ioc_enclave_create(struct sgx_encl *encl, void __user *arg) { struct sgx_enclave_create create_arg; void *secs; int ret; if (test_bit(SGX_ENCL_CREATED, &encl->flags)) return -EINVAL; if (copy_from_user(&create_arg, arg, sizeof(create_arg))) return -EFAULT; secs = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!secs) return -ENOMEM; if (copy_from_user(secs, (void __user *)create_arg.src, PAGE_SIZE)) ret = -EFAULT; else ret = sgx_encl_create(encl, secs); kfree(secs); return ret; } static struct sgx_encl_page *sgx_encl_page_alloc(struct sgx_encl *encl, unsigned long offset, u64 secinfo_flags) { struct sgx_encl_page *encl_page; unsigned long prot; encl_page = kzalloc(sizeof(*encl_page), GFP_KERNEL); if (!encl_page) return ERR_PTR(-ENOMEM); encl_page->desc = encl->base + offset; encl_page->encl = encl; prot = _calc_vm_trans(secinfo_flags, SGX_SECINFO_R, PROT_READ) | _calc_vm_trans(secinfo_flags, SGX_SECINFO_W, PROT_WRITE) | _calc_vm_trans(secinfo_flags, SGX_SECINFO_X, PROT_EXEC); /* * TCS pages must always RW set for CPU access while the SECINFO * permissions are *always* zero - the CPU ignores the user provided * values and silently overwrites them with zero permissions. */ if ((secinfo_flags & SGX_SECINFO_PAGE_TYPE_MASK) == SGX_SECINFO_TCS) prot |= PROT_READ | PROT_WRITE; /* Calculate maximum of the VM flags for the page. */ encl_page->vm_max_prot_bits = calc_vm_prot_bits(prot, 0); return encl_page; } static int sgx_validate_secinfo(struct sgx_secinfo *secinfo) { u64 perm = secinfo->flags & SGX_SECINFO_PERMISSION_MASK; u64 pt = secinfo->flags & SGX_SECINFO_PAGE_TYPE_MASK; if (pt != SGX_SECINFO_REG && pt != SGX_SECINFO_TCS) return -EINVAL; if ((perm & SGX_SECINFO_W) && !(perm & SGX_SECINFO_R)) return -EINVAL; /* * CPU will silently overwrite the permissions as zero, which means * that we need to validate it ourselves. */ if (pt == SGX_SECINFO_TCS && perm) return -EINVAL; if (secinfo->flags & SGX_SECINFO_RESERVED_MASK) return -EINVAL; if (memchr_inv(secinfo->reserved, 0, sizeof(secinfo->reserved))) return -EINVAL; return 0; } static int __sgx_encl_add_page(struct sgx_encl *encl, struct sgx_encl_page *encl_page, struct sgx_epc_page *epc_page, struct sgx_secinfo *secinfo, unsigned long src) { struct sgx_pageinfo pginfo; struct vm_area_struct *vma; struct page *src_page; int ret; /* Deny noexec. */ vma = find_vma(current->mm, src); if (!vma) return -EFAULT; if (!(vma->vm_flags & VM_MAYEXEC)) return -EACCES; ret = get_user_pages(src, 1, 0, &src_page, NULL); if (ret < 1) return -EFAULT; pginfo.secs = (unsigned long)sgx_get_epc_virt_addr(encl->secs.epc_page); pginfo.addr = encl_page->desc & PAGE_MASK; pginfo.metadata = (unsigned long)secinfo; pginfo.contents = (unsigned long)kmap_atomic(src_page); ret = __eadd(&pginfo, sgx_get_epc_virt_addr(epc_page)); kunmap_atomic((void *)pginfo.contents); put_page(src_page); return ret ? -EIO : 0; } /* * If the caller requires measurement of the page as a proof for the content, * use EEXTEND to add a measurement for 256 bytes of the page. Repeat this * operation until the entire page is measured." */ static int __sgx_encl_extend(struct sgx_encl *encl, struct sgx_epc_page *epc_page) { unsigned long offset; int ret; for (offset = 0; offset < PAGE_SIZE; offset += SGX_EEXTEND_BLOCK_SIZE) { ret = __eextend(sgx_get_epc_virt_addr(encl->secs.epc_page), sgx_get_epc_virt_addr(epc_page) + offset); if (ret) { if (encls_failed(ret)) ENCLS_WARN(ret, "EEXTEND"); return -EIO; } } return 0; } static int sgx_encl_add_page(struct sgx_encl *encl, unsigned long src, unsigned long offset, struct sgx_secinfo *secinfo, unsigned long flags) { struct sgx_encl_page *encl_page; struct sgx_epc_page *epc_page; struct sgx_va_page *va_page; int ret; encl_page = sgx_encl_page_alloc(encl, offset, secinfo->flags); if (IS_ERR(encl_page)) return PTR_ERR(encl_page); epc_page = sgx_alloc_epc_page(encl_page, true); if (IS_ERR(epc_page)) { kfree(encl_page); return PTR_ERR(epc_page); } va_page = sgx_encl_grow(encl); if (IS_ERR(va_page)) { ret = PTR_ERR(va_page); goto err_out_free; } mmap_read_lock(current->mm); mutex_lock(&encl->lock); /* * Adding to encl->va_pages must be done under encl->lock. Ditto for * deleting (via sgx_encl_shrink()) in the error path. */ if (va_page) list_add(&va_page->list, &encl->va_pages); /* * Insert prior to EADD in case of OOM. EADD modifies MRENCLAVE, i.e. * can't be gracefully unwound, while failure on EADD/EXTEND is limited * to userspace errors (or kernel/hardware bugs). */ ret = xa_insert(&encl->page_array, PFN_DOWN(encl_page->desc), encl_page, GFP_KERNEL); if (ret) goto err_out_unlock; ret = __sgx_encl_add_page(encl, encl_page, epc_page, secinfo, src); if (ret) goto err_out; /* * Complete the "add" before doing the "extend" so that the "add" * isn't in a half-baked state in the extremely unlikely scenario * the enclave will be destroyed in response to EEXTEND failure. */ encl_page->encl = encl; encl_page->epc_page = epc_page; encl->secs_child_cnt++; if (flags & SGX_PAGE_MEASURE) { ret = __sgx_encl_extend(encl, epc_page); if (ret) goto err_out; } sgx_mark_page_reclaimable(encl_page->epc_page); mutex_unlock(&encl->lock); mmap_read_unlock(current->mm); return ret; err_out: xa_erase(&encl->page_array, PFN_DOWN(encl_page->desc)); err_out_unlock: sgx_encl_shrink(encl, va_page); mutex_unlock(&encl->lock); mmap_read_unlock(current->mm); err_out_free: sgx_encl_free_epc_page(epc_page); kfree(encl_page); return ret; } /** * sgx_ioc_enclave_add_pages() - The handler for %SGX_IOC_ENCLAVE_ADD_PAGES * @encl: an enclave pointer * @arg: a user pointer to a struct sgx_enclave_add_pages instance * * Add one or more pages to an uninitialized enclave, and optionally extend the * measurement with the contents of the page. The SECINFO and measurement mask * are applied to all pages. * * A SECINFO for a TCS is required to always contain zero permissions because * CPU silently zeros them. Allowing anything else would cause a mismatch in * the measurement. * * mmap()'s protection bits are capped by the page permissions. For each page * address, the maximum protection bits are computed with the following * heuristics: * * 1. A regular page: PROT_R, PROT_W and PROT_X match the SECINFO permissions. * 2. A TCS page: PROT_R | PROT_W. * * mmap() is not allowed to surpass the minimum of the maximum protection bits * within the given address range. * * The function deinitializes kernel data structures for enclave and returns * -EIO in any of the following conditions: * * - Enclave Page Cache (EPC), the physical memory holding enclaves, has * been invalidated. This will cause EADD and EEXTEND to fail. * - If the source address is corrupted somehow when executing EADD. * * Return: * - 0: Success. * - -EACCES: The source page is located in a noexec partition. * - -ENOMEM: Out of EPC pages. * - -EINTR: The call was interrupted before data was processed. * - -EIO: Either EADD or EEXTEND failed because invalid source address * or power cycle. * - -errno: POSIX error. */ static long sgx_ioc_enclave_add_pages(struct sgx_encl *encl, void __user *arg) { struct sgx_enclave_add_pages add_arg; struct sgx_secinfo secinfo; unsigned long c; int ret; if (!test_bit(SGX_ENCL_CREATED, &encl->flags) || test_bit(SGX_ENCL_INITIALIZED, &encl->flags)) return -EINVAL; if (copy_from_user(&add_arg, arg, sizeof(add_arg))) return -EFAULT; if (!IS_ALIGNED(add_arg.offset, PAGE_SIZE) || !IS_ALIGNED(add_arg.src, PAGE_SIZE)) return -EINVAL; if (!add_arg.length || add_arg.length & (PAGE_SIZE - 1)) return -EINVAL; if (add_arg.offset + add_arg.length - PAGE_SIZE >= encl->size) return -EINVAL; if (copy_from_user(&secinfo, (void __user *)add_arg.secinfo, sizeof(secinfo))) return -EFAULT; if (sgx_validate_secinfo(&secinfo)) return -EINVAL; for (c = 0 ; c < add_arg.length; c += PAGE_SIZE) { if (signal_pending(current)) { if (!c) ret = -ERESTARTSYS; break; } if (need_resched()) cond_resched(); ret = sgx_encl_add_page(encl, add_arg.src + c, add_arg.offset + c, &secinfo, add_arg.flags); if (ret) break; } add_arg.count = c; if (copy_to_user(arg, &add_arg, sizeof(add_arg))) return -EFAULT; return ret; } static int __sgx_get_key_hash(struct crypto_shash *tfm, const void *modulus, void *hash) { SHASH_DESC_ON_STACK(shash, tfm); shash->tfm = tfm; return crypto_shash_digest(shash, modulus, SGX_MODULUS_SIZE, hash); } static int sgx_get_key_hash(const void *modulus, void *hash) { struct crypto_shash *tfm; int ret; tfm = crypto_alloc_shash("sha256", 0, CRYPTO_ALG_ASYNC); if (IS_ERR(tfm)) return PTR_ERR(tfm); ret = __sgx_get_key_hash(tfm, modulus, hash); crypto_free_shash(tfm); return ret; } static int sgx_encl_init(struct sgx_encl *encl, struct sgx_sigstruct *sigstruct, void *token) { u64 mrsigner[4]; int i, j; void *addr; int ret; /* * Deny initializing enclaves with attributes (namely provisioning) * that have not been explicitly allowed. */ if (encl->attributes & ~encl->attributes_mask) return -EACCES; /* * Attributes should not be enforced *only* against what's available on * platform (done in sgx_encl_create) but checked and enforced against * the mask for enforcement in sigstruct. For example an enclave could * opt to sign with AVX bit in xfrm, but still be loadable on a platform * without it if the sigstruct->body.attributes_mask does not turn that * bit on. */ if (sigstruct->body.attributes & sigstruct->body.attributes_mask & sgx_attributes_reserved_mask) return -EINVAL; if (sigstruct->body.miscselect & sigstruct->body.misc_mask & sgx_misc_reserved_mask) return -EINVAL; if (sigstruct->body.xfrm & sigstruct->body.xfrm_mask & sgx_xfrm_reserved_mask) return -EINVAL; ret = sgx_get_key_hash(sigstruct->modulus, mrsigner); if (ret) return ret; mutex_lock(&encl->lock); /* * ENCLS[EINIT] is interruptible because it has such a high latency, * e.g. 50k+ cycles on success. If an IRQ/NMI/SMI becomes pending, * EINIT may fail with SGX_UNMASKED_EVENT so that the event can be * serviced. */ for (i = 0; i < SGX_EINIT_SLEEP_COUNT; i++) { for (j = 0; j < SGX_EINIT_SPIN_COUNT; j++) { addr = sgx_get_epc_virt_addr(encl->secs.epc_page); preempt_disable(); sgx_update_lepubkeyhash(mrsigner); ret = __einit(sigstruct, token, addr); preempt_enable(); if (ret == SGX_UNMASKED_EVENT) continue; else break; } if (ret != SGX_UNMASKED_EVENT) break; msleep_interruptible(SGX_EINIT_SLEEP_TIME); if (signal_pending(current)) { ret = -ERESTARTSYS; goto err_out; } } if (encls_faulted(ret)) { if (encls_failed(ret)) ENCLS_WARN(ret, "EINIT"); ret = -EIO; } else if (ret) { pr_debug("EINIT returned %d\n", ret); ret = -EPERM; } else { set_bit(SGX_ENCL_INITIALIZED, &encl->flags); } err_out: mutex_unlock(&encl->lock); return ret; } /** * sgx_ioc_enclave_init() - handler for %SGX_IOC_ENCLAVE_INIT * @encl: an enclave pointer * @arg: userspace pointer to a struct sgx_enclave_init instance * * Flush any outstanding enqueued EADD operations and perform EINIT. The * Launch Enclave Public Key Hash MSRs are rewritten as necessary to match * the enclave's MRSIGNER, which is caculated from the provided sigstruct. * * Return: * - 0: Success. * - -EPERM: Invalid SIGSTRUCT. * - -EIO: EINIT failed because of a power cycle. * - -errno: POSIX error. */ static long sgx_ioc_enclave_init(struct sgx_encl *encl, void __user *arg) { struct sgx_sigstruct *sigstruct; struct sgx_enclave_init init_arg; void *token; int ret; if (!test_bit(SGX_ENCL_CREATED, &encl->flags) || test_bit(SGX_ENCL_INITIALIZED, &encl->flags)) return -EINVAL; if (copy_from_user(&init_arg, arg, sizeof(init_arg))) return -EFAULT; /* * 'sigstruct' must be on a page boundary and 'token' on a 512 byte * boundary. kmalloc() will give this alignment when allocating * PAGE_SIZE bytes. */ sigstruct = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!sigstruct) return -ENOMEM; token = (void *)((unsigned long)sigstruct + PAGE_SIZE / 2); memset(token, 0, SGX_LAUNCH_TOKEN_SIZE); if (copy_from_user(sigstruct, (void __user *)init_arg.sigstruct, sizeof(*sigstruct))) { ret = -EFAULT; goto out; } /* * A legacy field used with Intel signed enclaves. These used to mean * regular and architectural enclaves. The CPU only accepts these values * but they do not have any other meaning. * * Thus, reject any other values. */ if (sigstruct->header.vendor != 0x0000 && sigstruct->header.vendor != 0x8086) { ret = -EINVAL; goto out; } ret = sgx_encl_init(encl, sigstruct, token); out: kfree(sigstruct); return ret; } /** * sgx_ioc_enclave_provision() - handler for %SGX_IOC_ENCLAVE_PROVISION * @encl: an enclave pointer * @arg: userspace pointer to a struct sgx_enclave_provision instance * * Allow ATTRIBUTE.PROVISION_KEY for an enclave by providing a file handle to * /dev/sgx_provision. * * Return: * - 0: Success. * - -errno: Otherwise. */ static long sgx_ioc_enclave_provision(struct sgx_encl *encl, void __user *arg) { struct sgx_enclave_provision params; if (copy_from_user(¶ms, arg, sizeof(params))) return -EFAULT; return sgx_set_attribute(&encl->attributes_mask, params.fd); } long sgx_ioctl(struct file *filep, unsigned int cmd, unsigned long arg) { struct sgx_encl *encl = filep->private_data; int ret; if (test_and_set_bit(SGX_ENCL_IOCTL, &encl->flags)) return -EBUSY; switch (cmd) { case SGX_IOC_ENCLAVE_CREATE: ret = sgx_ioc_enclave_create(encl, (void __user *)arg); break; case SGX_IOC_ENCLAVE_ADD_PAGES: ret = sgx_ioc_enclave_add_pages(encl, (void __user *)arg); break; case SGX_IOC_ENCLAVE_INIT: ret = sgx_ioc_enclave_init(encl, (void __user *)arg); break; case SGX_IOC_ENCLAVE_PROVISION: ret = sgx_ioc_enclave_provision(encl, (void __user *)arg); break; default: ret = -ENOIOCTLCMD; break; } clear_bit(SGX_ENCL_IOCTL, &encl->flags); return ret; }
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1