blob: 9e5a727314b022037a2e076c20bca07481aed085 [file]
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2018-2022 The Regents of the University of California (Regents).
* Copyright (c) 2023 Univrsity of Tampere.
*
* Authors:
* Dayeol Lee <dayeol@berkeley.edu>
* Evgeny Pobachienko <evgenyp@berkeley.edu>
* Jarkko Sakkinen <jarkko.sakkinen@tuni.fi>
*/
#include <linux/uaccess.h>
#include <asm/sbi.h>
#include <uapi/asm/keystone.h>
#include "keystone.h"
#include "sbi.h"
static int keystone_ioc_create_enclave(struct file *filep, unsigned long arg)
{
struct keystone_enclave *enclp = (struct keystone_enclave *)arg;
struct enclave *enclave;
struct epm *epm;
ssize_t ret;
enclave = keystone_enclave_create(enclp->min_pages);
if (!enclave)
return -ENOMEM;
ret = keystone_alloc_eid(enclave);
if (ret < 0) {
keystone_enclave_destroy(enclave);
return ret;
}
filep->private_data = enclave;
epm = enclave->epm;
enclp->eid = ret;
enclp->pt_ptr = __pa(epm->ptr);
enclp->epm_size = epm->size;
return 0;
}
static int keystone_ioc_finalize_enclave(struct file *filep, unsigned long arg)
{
struct keystone_enclave *enclp = (struct keystone_enclave *)arg;
struct enclave *enclave = filep->private_data;
struct keystone_sbi_create args;
struct sbiret ret;
struct utm *utm;
if (!enclave)
return -EIO;
enclave->is_init = false;
/* PMP: */
args.epm_region.paddr = enclave->epm->pa;
args.epm_region.size = enclave->epm->size;
pr_debug("Enclave Private Memory (EPM): 0x%016llx - 0x%016llx\n",
args.epm_region.paddr,
args.epm_region.paddr + args.epm_region.size);
/* Untrusted memory: */
utm = enclave->utm;
if (utm) {
args.utm_region.paddr = __pa(utm->ptr);
args.utm_region.size = utm->size;
} else {
args.utm_region.paddr = 0;
args.utm_region.size = 0;
}
pr_debug("Untrusted memory (UTM): 0x%016llx - 0x%016llx\n",
args.utm_region.paddr,
args.utm_region.paddr + args.utm_region.size);
/* Physical addresses: */
args.runtime_paddr = enclp->runtime_paddr;
args.user_paddr = enclp->user_paddr;
args.free_paddr = enclp->free_paddr;
args.params = enclp->params;
ret = sbi_sm_call(SBI_SM_CREATE_ENCLAVE, (unsigned long)&args);
if (ret.error) {
pr_err("SBI_SM_CREATE_ENCLAVE error %ld\n", ret.error);
goto err;
}
enclave->eid = ret.value;
return 0;
err:
keystone_enclave_destroy(enclave);
return -EIO;
}
static int keystone_ioc_run_enclave(struct file *filep, unsigned long data)
{
struct keystone_run *arg = (struct keystone_run *)data;
struct enclave *enclave = filep->private_data;
struct sbiret ret;
if (!enclave)
return -EIO;
ret = sbi_sm_call(SBI_SM_RUN_ENCLAVE, enclave->eid);
arg->error = ret.error;
arg->value = ret.value;
return 0;
}
static int __keystone_ioc_destroy_enclave(struct enclave *enclave)
{
struct sbiret ret;
ret = sbi_sm_call(SBI_SM_DESTROY_ENCLAVE, enclave->eid);
if (ret.error) {
pr_err("SBI_SM_DESTROY_ENCLAVE error %ld\n", ret.error);
return -EIO;
}
keystone_enclave_destroy(enclave);
keystone_free_eid(enclave->eid);
return 0;
}
/*
* Do not free up the software resources (physical memory regions and idr to be
* more specific) in the case of SBI failure because otherwise there would be a
* mismatch between the software and hardware states.
*/
static int keystone_ioc_destroy_enclave(struct file *filep, unsigned long arg)
{
struct enclave *enclave = filep->private_data;
int ret;
if (!enclave) {
pr_debug("%s: enclave already destroyed\n", __func__);
return -EIO;
}
ret = __keystone_ioc_destroy_enclave(enclave);
if (ret)
return ret;
filep->private_data = NULL;
return 0;
}
static int keystone_ioc_resume_enclave(struct file *filep, unsigned long data)
{
struct keystone_run *arg = (struct keystone_run *)data;
struct enclave *enclave = filep->private_data;
struct sbiret ret;
if (!enclave)
return -EIO;
ret = sbi_sm_call(SBI_SM_RESUME_ENCLAVE, enclave->eid);
arg->error = ret.error;
arg->value = ret.value;
return 0;
}
static int keystone_ioc_utm_init(struct file *filep, unsigned long arg)
{
struct keystone_enclave *enclp = (struct keystone_enclave *)arg;
u64 untrusted_size = enclp->params.untrusted_size;
struct enclave *enclave = filep->private_data;
struct utm *utm;
int ret = 0;
if (!enclave)
return -EIO;
utm = kzalloc(sizeof(*utm), GFP_KERNEL);
if (!utm) {
ret = -ENOMEM;
return ret;
}
ret = utm_init(utm, PFN_UP(untrusted_size));
if (ret) {
pr_warn("utm_init() returned %d\n", ret);
kfree(utm);
return ret;
}
/* Prepare for mmap: */
enclave->utm = utm;
enclp->utm_free_ptr = __pa(utm->ptr);
return ret;
}
static int keystone_ioc_finalize_library_enclave(struct file *filep, unsigned long arg)
{
struct keystone_enclave *enclp = (struct keystone_enclave *)arg;
struct enclave *enclave = filep->private_data;
struct keystone_sbi_create create_args;
struct sbiret ret;
if (!enclave)
return -EIO;
enclave->is_init = false;
create_args.epm_region.paddr = enclave->epm->pa;
create_args.epm_region.size = enclave->epm->size;
// Don't need untrusted memory for library enclaves:
create_args.utm_region.paddr = 0;
create_args.utm_region.size = 0;
if (strncpy_from_user(create_args.library_name,
(const char __user *)enclp->library_name,
NAME_MAX) < 0)
return -EFAULT;
ret = sbi_sm_call(SBI_SM_CREATE_LIBRARY_ENCLAVE, (unsigned long)&create_args);
if (ret.error) {
pr_err("SBI_SM_CREATE_LIBRARY_ENCLAVE error %ld\n", ret.error);
goto err;
}
enclave->eid = ret.value;
return 0;
err:
keystone_enclave_destroy(enclave);
return -EIO;
}
/*
* Do not free up the software resources (physical memory regions and idr to be
* more specific) in the case of SBI failure because otherwise there would be a
* mismatch between the software and hardware states.
*/
static int keystone_ioc_destroy_library_enclave(struct file *filep, unsigned long arg)
{
struct enclave *enclave = filep->private_data;
struct sbiret ret;
if (!enclave)
return -EIO;
ret = sbi_sm_call(SBI_SM_DESTROY_LIBRARY_ENCLAVE, enclave->eid);
if (ret.error) {
pr_err("SBI_SM_DESTROY_LIBRARY_ENCLAVE error %ld\n", ret.error);
return -EIO;
}
keystone_enclave_destroy(enclave);
filep->private_data = NULL;
return 0;
}
long keystone_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
{
struct enclave *enclave = filep->private_data;
size_t ioc_size;
char data[512];
long ret;
if (!enclave)
return -EIO;
/* Mutually exclude concurrent threads: */
if (test_and_set_bit(true, &enclave->busy)) {
pr_info("%d busy\n", enclave->eid);
return -EBUSY;
}
ioc_size = _IOC_SIZE(cmd);
ioc_size = ioc_size > sizeof(data) ? sizeof(data) : ioc_size;
if (copy_from_user(data, (void __user *)arg, ioc_size)) {
ret = -EFAULT;
goto err;
}
switch (cmd) {
case KEYSTONE_IOC_CREATE_ENCLAVE:
ret = keystone_ioc_create_enclave(filep, (unsigned long)data);
break;
case KEYSTONE_IOC_FINALIZE_ENCLAVE:
ret = keystone_ioc_finalize_enclave(filep, (unsigned long)data);
break;
case KEYSTONE_IOC_DESTROY_ENCLAVE:
ret = keystone_ioc_destroy_enclave(filep, (unsigned long)data);
break;
case KEYSTONE_IOC_RUN_ENCLAVE:
ret = keystone_ioc_run_enclave(filep, (unsigned long)data);
break;
case KEYSTONE_IOC_RESUME_ENCLAVE:
ret = keystone_ioc_resume_enclave(filep, (unsigned long)data);
break;
case KEYSTONE_IOC_UTM_INIT:
ret = keystone_ioc_utm_init(filep, (unsigned long)data);
break;
case KEYSTONE_IOC_FINALIZE_LIBRARY_ENCLAVE:
ret = keystone_ioc_finalize_library_enclave(filep, (unsigned long)data);
break;
case KEYSTONE_IOC_DESTROY_LIBRARY_ENCLAVE:
ret = keystone_ioc_destroy_library_enclave(filep, (unsigned long)data);
break;
default:
ret = -ENOIOCTLCMD;
break;
}
/* Copy back only on success: */
if (!ret && copy_to_user((void __user *)arg, data, ioc_size))
ret = -EFAULT;
err:
clear_bit(true, &enclave->busy);
return ret;
}
int keystone_release(struct inode *inode, struct file *file)
{
struct enclave *enclave = file->private_data;
if (!enclave) {
pr_warn("%s: enclave was already destroyed\n", __func__);
return 0;
}
if (enclave->close_on_pexit)
return __keystone_ioc_destroy_enclave(enclave);
return 0;
}