/* * Copyright (c) 2013-2015 TRUSTONIC LIMITED * All Rights Reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "public/mc_linux.h" #include "public/mc_admin.h" #include "mci/mcloadformat.h" #include "main.h" #include "debug.h" #include "mmu.h" /* For load_check and load_token */ #include "mcp.h" #include "client.h" #include "api.h" #include "admin.h" /* We need 2 devices for admin and user interface*/ #define MC_DEV_MAX 2 static struct admin_ctx { struct device *dev; atomic_t daemon_counter; /* Define a MobiCore device structure for use with dev_debug() etc */ struct device_driver mc_dev_name; dev_t mc_dev_admin; struct cdev mc_admin_cdev; int (*tee_start_cb)(void); } g_admin_ctx; static struct mc_admin_driver_request { /* Global */ struct mutex mutex; /* Protects access to this struct */ struct mutex states_mutex; /* Protect access to the states */ enum client_state { IDLE, REQUEST_SENT, BUFFERS_READY, } client_state; enum server_state { NOT_CONNECTED, /* Device not open */ READY, /* Waiting for requests */ REQUEST_RECEIVED, /* Got a request, is working */ RESPONSE_SENT, /* Has sent a response header */ DATA_SENT, /* Blocked until data is consumed */ } server_state; /* Request */ uint32_t request_id; struct mc_admin_request request; struct completion client_complete; /* Response */ struct mc_admin_response response; struct completion server_complete; void *buffer; /* Reception buffer (pre-allocated) */ size_t size; /* Size of the reception buffer */ } g_request; static struct tbase_object *tbase_object_alloc(bool is_sp_trustlet, size_t length) { struct tbase_object *obj; size_t size = sizeof(*obj) + length; size_t header_length = 0; /* Determine required size */ if (is_sp_trustlet) { /* Need space for lengths info and containers */ header_length = sizeof(struct mc_blob_len_info); size += header_length + 3 * MAX_SO_CONT_SIZE; } /* Allocate memory */ obj = vzalloc(size); if (!obj) return NULL; /* A non-zero header_length indicates that we have a SP trustlet */ obj->header_length = header_length; obj->length = length; return obj; } void tbase_object_free(struct tbase_object *robj) { vfree(robj); } static inline void client_state_change(enum client_state state) { mutex_lock(&g_request.states_mutex); g_request.client_state = state; mutex_unlock(&g_request.states_mutex); } static inline bool client_state_is(enum client_state state) { bool is; mutex_lock(&g_request.states_mutex); is = g_request.client_state == state; mutex_unlock(&g_request.states_mutex); return is; } static inline void server_state_change(enum server_state state) { mutex_lock(&g_request.states_mutex); g_request.server_state = state; mutex_unlock(&g_request.states_mutex); } static inline bool server_state_is(enum server_state state) { bool is; mutex_lock(&g_request.states_mutex); is = g_request.server_state == state; mutex_unlock(&g_request.states_mutex); return is; } static void request_cancel(void); static int request_send(uint32_t command, const struct mc_uuid_t *uuid, uint32_t is_gp, uint32_t spid) { struct device *dev = g_admin_ctx.dev; int counter = 10; int ret; /* Prepare request */ mutex_lock(&g_request.states_mutex); /* Wait a little for daemon to connect */ while ((g_request.server_state == NOT_CONNECTED) && counter--) { mutex_unlock(&g_request.states_mutex); ssleep(1); mutex_lock(&g_request.states_mutex); } BUG_ON(g_request.client_state != IDLE); if (g_request.server_state != READY) { mutex_unlock(&g_request.states_mutex); if (g_request.server_state != NOT_CONNECTED) { /* TODO: can we recover? */ dev_err(dev, "%s: invalid daemon state %d\n", __func__, g_request.server_state); ret = -EPROTO; goto end; } else { dev_err(dev, "%s: daemon not connected\n", __func__); ret = -ENOTCONN; goto end; } } memset(&g_request.request, 0, sizeof(g_request.request)); memset(&g_request.response, 0, sizeof(g_request.response)); g_request.request.request_id = g_request.request_id++; g_request.request.command = command; if (uuid) memcpy(&g_request.request.uuid, uuid, sizeof(*uuid)); else memset(&g_request.request.uuid, 0, sizeof(*uuid)); g_request.request.is_gp = is_gp; g_request.request.spid = spid; g_request.client_state = REQUEST_SENT; mutex_unlock(&g_request.states_mutex); /* Send request */ complete(&g_request.client_complete); /* Wait for header (could be interruptible, but then needs more work) */ wait_for_completion(&g_request.server_complete); /* Server should be waiting with some data for us */ mutex_lock(&g_request.states_mutex); switch (g_request.server_state) { case NOT_CONNECTED: /* Daemon gone */ ret = -EPIPE; break; case READY: /* No data to come, likely an error */ ret = -g_request.response.error_no; break; case RESPONSE_SENT: case DATA_SENT: /* Normal case, data to come */ ret = 0; break; default: /* Should not happen as complete means the state changed */ dev_err(dev, "%s: daemon is in a bad state: %d\n", __func__, g_request.server_state); ret = -EPIPE; break; } mutex_unlock(&g_request.states_mutex); end: if (ret) request_cancel(); return ret; } static int request_receive(void *address, uint32_t size) { /* * At this point we have received the header and prepared some buffers * to receive data that we know are coming from the server. */ /* Check server state */ bool server_ok; mutex_lock(&g_request.states_mutex); server_ok = (g_request.server_state == RESPONSE_SENT) || (g_request.server_state == DATA_SENT); mutex_unlock(&g_request.states_mutex); if (!server_ok) { /* TODO: can we recover? */ request_cancel(); return -EPIPE; } /* Setup reception buffer */ g_request.buffer = address; g_request.size = size; client_state_change(BUFFERS_READY); /* Unlock write of data */ complete(&g_request.client_complete); /* Wait for data (far too late to be interruptible) */ wait_for_completion(&g_request.server_complete); /* Reset reception buffer */ g_request.buffer = NULL; g_request.size = 0; /* Return to idle state */ client_state_change(IDLE); return 0; } /* Must be called instead of request_receive() to cancel a pending request */ static void request_cancel(void) { /* Unlock write of data */ mutex_lock(&g_request.states_mutex); if (g_request.server_state == DATA_SENT) complete(&g_request.client_complete); /* Return to idle state */ g_request.client_state = IDLE; mutex_unlock(&g_request.states_mutex); } static int admin_get_root_container(void *address) { struct device *dev = g_admin_ctx.dev; int ret = 0; /* Lock communication channel */ mutex_lock(&g_request.mutex); /* Send request and wait for header */ ret = request_send(MC_DRV_GET_ROOT_CONTAINER, 0, 0, 0); if (ret) goto end; /* Check length against max */ if (g_request.response.length >= MAX_SO_CONT_SIZE) { request_cancel(); dev_err(dev, "%s: response length exceeds maximum\n", __func__); ret = EREMOTEIO; goto end; } /* Get data */ ret = request_receive(address, g_request.response.length); if (!ret) ret = g_request.response.length; end: mutex_unlock(&g_request.mutex); return ret; } static int admin_get_sp_container(void *address, uint32_t spid) { struct device *dev = g_admin_ctx.dev; int ret = 0; /* Lock communication channel */ mutex_lock(&g_request.mutex); /* Send request and wait for header */ ret = request_send(MC_DRV_GET_SP_CONTAINER, 0, 0, spid); if (ret) goto end; /* Check length against max */ if (g_request.response.length >= MAX_SO_CONT_SIZE) { request_cancel(); dev_err(dev, "%s: response length exceeds maximum\n", __func__); ret = EREMOTEIO; goto end; } /* Get data */ ret = request_receive(address, g_request.response.length); if (!ret) ret = g_request.response.length; end: mutex_unlock(&g_request.mutex); return ret; } static int admin_get_trustlet_container(void *address, const struct mc_uuid_t *uuid, uint32_t spid) { struct device *dev = g_admin_ctx.dev; int ret = 0; /* Lock communication channel */ mutex_lock(&g_request.mutex); /* Send request and wait for header */ ret = request_send(MC_DRV_GET_TRUSTLET_CONTAINER, uuid, 0, spid); if (ret) goto end; /* Check length against max */ if (g_request.response.length >= MAX_SO_CONT_SIZE) { request_cancel(); dev_err(dev, "%s: response length exceeds maximum\n", __func__); ret = EREMOTEIO; goto end; } /* Get data */ ret = request_receive(address, g_request.response.length); if (!ret) ret = g_request.response.length; end: mutex_unlock(&g_request.mutex); return ret; } static struct tbase_object *admin_get_trustlet(const struct mc_uuid_t *uuid, uint32_t is_gp, uint32_t *spid) { struct tbase_object *obj = NULL; bool is_sp_tl; int ret = 0; /* Lock communication channel */ mutex_lock(&g_request.mutex); /* Send request and wait for header */ ret = request_send(MC_DRV_GET_TRUSTLET, uuid, is_gp, 0); if (ret) goto end; /* Allocate memory */ is_sp_tl = g_request.response.service_type == SERVICE_TYPE_SP_TRUSTLET; obj = tbase_object_alloc(is_sp_tl, g_request.response.length); if (!obj) { request_cancel(); ret = -ENOMEM; goto end; } /* Get data */ ret = request_receive(&obj->data[obj->header_length], obj->length); *spid = g_request.response.spid; end: mutex_unlock(&g_request.mutex); if (ret) return ERR_PTR(ret); return obj; } static void mc_admin_sendcrashdump(void) { int ret = 0; /* Lock communication channel */ mutex_lock(&g_request.mutex); /* Send request and wait for header */ ret = request_send(MC_DRV_SIGNAL_CRASH, NULL, false, 0); if (ret) goto end; /* Done */ request_cancel(); end: mutex_unlock(&g_request.mutex); } static int tbase_object_make(uint32_t spid, struct tbase_object *obj) { struct mc_blob_len_info *l_info = (struct mc_blob_len_info *)obj->data; uint8_t *address = &obj->data[obj->header_length + obj->length]; struct mclf_header_v2 *thdr; int ret; /* Get root container */ ret = admin_get_root_container(address); if (ret < 0) goto err; l_info->root_size = ret; address += ret; /* Get SP container */ ret = admin_get_sp_container(address, spid); if (ret < 0) goto err; l_info->sp_size = ret; address += ret; /* Get trustlet container */ thdr = (struct mclf_header_v2 *)&obj->data[obj->header_length]; ret = admin_get_trustlet_container(address, &thdr->uuid, spid); if (ret < 0) goto err; l_info->ta_size = ret; address += ret; /* Setup lengths information */ l_info->magic = MC_TLBLOBLEN_MAGIC; obj->length += sizeof(*l_info); obj->length += l_info->root_size + l_info->sp_size + l_info->ta_size; ret = 0; err: return ret; } struct tbase_object *tbase_object_read(uint32_t spid, uintptr_t address, size_t length) { struct device *dev = g_admin_ctx.dev; char __user *addr = (char __user *)address; struct tbase_object *obj; uint8_t *data; struct mclf_header_v2 thdr; int ret; /* Check length */ if (length < sizeof(thdr)) { dev_err(dev, "%s: buffer shorter than header size\n", __func__); return ERR_PTR(-EFAULT); } /* Read header */ if (copy_from_user(&thdr, addr, sizeof(thdr))) { dev_err(dev, "%s: header: copy_from_user failed\n", __func__); return ERR_PTR(-EFAULT); } /* Allocate memory */ obj = tbase_object_alloc(thdr.service_type == SERVICE_TYPE_SP_TRUSTLET, length); if (!obj) return ERR_PTR(-ENOMEM); /* Copy header */ data = &obj->data[obj->header_length]; memcpy(data, &thdr, sizeof(thdr)); /* Copy the rest of the data */ data += sizeof(thdr); if (copy_from_user(data, &addr[sizeof(thdr)], length - sizeof(thdr))) { dev_err(dev, "%s: data: copy_from_user failed\n", __func__); vfree(obj); return ERR_PTR(-EFAULT); } if (obj->header_length) { ret = tbase_object_make(spid, obj); if (ret) { vfree(obj); return ERR_PTR(ret); } } return obj; } struct tbase_object *tbase_object_select(const struct mc_uuid_t *uuid) { struct tbase_object *obj; struct mclf_header_v2 *thdr; obj = tbase_object_alloc(false, sizeof(*thdr)); if (!obj) return ERR_PTR(-ENOMEM); thdr = (struct mclf_header_v2 *)&obj->data[obj->header_length]; memcpy(&thdr->uuid, uuid, sizeof(thdr->uuid)); return obj; } struct tbase_object *tbase_object_get(const struct mc_uuid_t *uuid, uint32_t is_gp_uuid) { struct tbase_object *obj; uint32_t spid = 0; /* admin_get_trustlet creates the right object based on service type */ obj = admin_get_trustlet(uuid, is_gp_uuid, &spid); if (IS_ERR(obj)) return obj; /* SP trustlet: create full secure object with all containers */ if (obj->header_length) { int ret; /* Do not return EINVAL in this case as SPID was not found */ if (!spid) { vfree(obj); return ERR_PTR(-ENOENT); } ret = tbase_object_make(spid, obj); if (ret) { vfree(obj); return ERR_PTR(ret); } } return obj; } static inline int load_driver(struct tbase_client *client, struct mc_admin_load_info *info) { struct tbase_object *obj; struct mclf_header_v2 *thdr; struct mc_identity identity = { .login_type = TEEC_LOGIN_PUBLIC, }; uintptr_t dci = 0; uint32_t dci_len = 0; uint32_t sid; int ret; obj = tbase_object_read(info->spid, info->address, info->length); if (IS_ERR(obj)) return PTR_ERR(obj); thdr = (struct mclf_header_v2 *)&obj->data[obj->header_length]; if (!(thdr->flags & MC_SERVICE_HEADER_FLAGS_NO_CONTROL_INTERFACE)) { /* * The driver requires a DCI, although we won't be able to use * it to communicate. */ dci_len = PAGE_SIZE; ret = api_malloc_cbuf(client, dci_len, &dci, NULL); if (ret) goto end; } /* Open session */ ret = client_add_session(client, obj, dci, dci_len, &sid, false, &identity); if (ret) api_free_cbuf(client, dci); else dev_dbg(g_admin_ctx.dev, "driver loaded with sid %x", sid); end: vfree(obj); return ret; } static inline int load_token(struct mc_admin_load_info *token) { struct tbase_mmu *mmu; struct mcp_buffer_map map; int ret; mmu = tbase_mmu_create(current, (void *)(uintptr_t)token->address, token->length); if (IS_ERR(mmu)) return PTR_ERR(mmu); tbase_mmu_buffer(mmu, &map); ret = mcp_load_token(token->address, &map); tbase_mmu_delete(mmu); return ret; } static inline int load_check(struct mc_admin_load_info *info) { struct tbase_object *obj; struct tbase_mmu *mmu; struct mcp_buffer_map map; int ret; obj = tbase_object_read(info->spid, info->address, info->length); if (IS_ERR(obj)) return PTR_ERR(obj); mmu = tbase_mmu_create(NULL, obj->data, obj->length); if (IS_ERR(mmu)) return PTR_ERR(mmu); tbase_mmu_buffer(mmu, &map); ret = mcp_load_check(obj, &map); tbase_mmu_delete(mmu); return ret; } static ssize_t admin_write(struct file *file, const char __user *user, size_t len, loff_t *off) { int ret; /* No offset allowed [yet] */ if (*off) { g_request.response.error_no = EPIPE; ret = -ECOMM; goto err; } if (server_state_is(REQUEST_RECEIVED)) { /* Check client state */ if (!client_state_is(REQUEST_SENT)) { g_request.response.error_no = EPIPE; ret = -EPIPE; goto err; } /* Receive response header */ if (copy_from_user(&g_request.response, user, sizeof(g_request.response))) { g_request.response.error_no = EPIPE; ret = -ECOMM; goto err; } /* Check request ID */ if (g_request.request.request_id != g_request.response.request_id) { g_request.response.error_no = EPIPE; ret = -EBADE; goto err; } /* Response header is acceptable */ ret = sizeof(g_request.response); if (g_request.response.length) server_state_change(RESPONSE_SENT); else server_state_change(READY); goto end; } else if (server_state_is(RESPONSE_SENT)) { /* Server is waiting */ server_state_change(DATA_SENT); /* Get data */ ret = wait_for_completion_interruptible( &g_request.client_complete); /* Server received a signal, let see if it tries again */ if (ret) { server_state_change(RESPONSE_SENT); return ret; } /* Check client state */ if (!client_state_is(BUFFERS_READY)) { g_request.response.error_no = EPIPE; ret = -EPIPE; goto err; } /* TODO deal with several writes */ if (len != g_request.size) len = g_request.size; ret = copy_from_user(g_request.buffer, user, len); if (ret) { g_request.response.error_no = EPIPE; ret = -ECOMM; goto err; } ret = len; server_state_change(READY); goto end; } else { ret = -ECOMM; goto err; } err: server_state_change(READY); end: complete(&g_request.server_complete); return ret; } static long admin_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { struct tbase_client *client = file->private_data; void __user *uarg = (void __user *)arg; int ret = -EINVAL; MCDRV_DBG("%u from %s", _IOC_NR(cmd), current->comm); if (WARN(!client, "No client data available")) return -EFAULT; switch (cmd) { case MC_ADMIN_IO_GET_DRIVER_REQUEST: { /* Block until a request is available */ ret = wait_for_completion_interruptible( &g_request.client_complete); if (ret) /* Interrupted by signal */ break; /* Check client state */ if (!client_state_is(REQUEST_SENT)) { g_request.response.error_no = EPIPE; complete(&g_request.server_complete); ret = -EPIPE; break; } /* Send request (the driver request mutex is held) */ ret = copy_to_user(uarg, &g_request.request, sizeof(g_request.request)); if (ret) { server_state_change(READY); complete(&g_request.server_complete); ret = -EPROTO; break; } server_state_change(REQUEST_RECEIVED); break; } case MC_ADMIN_IO_GET_INFO: { struct mc_admin_driver_info info; info.drv_version = MC_VERSION(MCDRVMODULEAPI_VERSION_MAJOR, MCDRVMODULEAPI_VERSION_MINOR); info.initial_cmd_id = g_request.request_id; ret = copy_to_user(uarg, &info, sizeof(info)); break; } case MC_ADMIN_IO_LOAD_DRIVER: { struct mc_admin_load_info info; ret = copy_from_user(&info, uarg, sizeof(info)); if (ret) ret = -EFAULT; else ret = load_driver(client, &info); break; } case MC_ADMIN_IO_LOAD_TOKEN: { struct mc_admin_load_info info; ret = copy_from_user(&info, uarg, sizeof(info)); if (ret) ret = -EFAULT; else ret = load_token(&info); break; } case MC_ADMIN_IO_LOAD_CHECK: { struct mc_admin_load_info info; ret = copy_from_user(&info, uarg, sizeof(info)); if (ret) ret = -EFAULT; else ret = load_check(&info); break; } default: ret = -ENOIOCTLCMD; } return ret; } /* * mc_fd_release() - This function will be called from user space as close(...) * The client data are freed and the associated memory pages are unreserved. * * @inode * @file * * Returns 0 */ static int admin_release(struct inode *inode, struct file *file) { struct tbase_client *client = file->private_data; struct device *dev = g_admin_ctx.dev; if (!client) return -EPROTO; api_close_device(client); file->private_data = NULL; /* Requests from driver to daemon */ mutex_lock(&g_request.states_mutex); dev_warn(dev, "%s: daemon disconnected\n", __func__); g_request.server_state = NOT_CONNECTED; /* A non-zero command indicates that a thread is waiting */ if (g_request.client_state != IDLE) { g_request.response.error_no = ESHUTDOWN; complete(&g_request.server_complete); } mutex_unlock(&g_request.states_mutex); atomic_set(&g_admin_ctx.daemon_counter, 0); /* * ret is quite irrelevant here as most apps don't care about the * return value from close() and it's quite difficult to recover */ return 0; } static int admin_open(struct inode *inode, struct file *file) { struct device *dev = g_admin_ctx.dev; struct tbase_client *client; int err; /* * If the daemon is already set we can't allow anybody else to open * the admin interface. */ if (atomic_cmpxchg(&g_admin_ctx.daemon_counter, 0, 1) != 0) { MCDRV_ERROR("Daemon is already connected"); return -EPROTO; } /* Any value will do */ g_request.request_id = 42; /* Setup the usual variables */ MCDRV_DBG("accept %s as tbase daemon", current->comm); /* * daemon is connected so now we can safely suppose * the secure world is loaded too */ if (!IS_ERR_OR_NULL(g_admin_ctx.tee_start_cb)) g_admin_ctx.tee_start_cb = ERR_PTR(g_admin_ctx.tee_start_cb()); if (IS_ERR(g_admin_ctx.tee_start_cb)) { MCDRV_ERROR("Failed initializing the SW"); err = PTR_ERR(g_admin_ctx.tee_start_cb); goto fail_connection; } /* Create client */ client = api_open_device(true); if (!client) { err = -ENOMEM; goto fail_connection; } /* Store client in user file */ file->private_data = client; /* Requests from driver to daemon */ server_state_change(READY); dev_info(dev, "%s: daemon connected\n", __func__); return 0; fail_connection: atomic_set(&g_admin_ctx.daemon_counter, 0); return err; } /* function table structure of this device driver. */ static const struct file_operations mc_admin_fops = { .owner = THIS_MODULE, .open = admin_open, .release = admin_release, .unlocked_ioctl = admin_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl = admin_ioctl, #endif .write = admin_write, }; int mc_admin_init(struct class *mc_device_class, dev_t *out_dev, int (*tee_start_cb)(void)) { int err = 0; if (!out_dev || !mc_device_class) return -EINVAL; atomic_set(&g_admin_ctx.daemon_counter, 0); /* Requests from driver to daemon */ mutex_init(&g_request.mutex); mutex_init(&g_request.states_mutex); init_completion(&g_request.client_complete); init_completion(&g_request.server_complete); mcp_register_crashhandler(mc_admin_sendcrashdump); /* Create char device */ cdev_init(&g_admin_ctx.mc_admin_cdev, &mc_admin_fops); err = alloc_chrdev_region(&g_admin_ctx.mc_dev_admin, 0, MC_DEV_MAX, "trustonic_tee"); if (err < 0) { MCDRV_ERROR("failed to allocate char dev region"); goto fail_alloc_chrdev_region; } err = cdev_add(&g_admin_ctx.mc_admin_cdev, g_admin_ctx.mc_dev_admin, 1); if (err) { MCDRV_ERROR("admin device register failed"); goto fail_cdev_add; } g_admin_ctx.mc_admin_cdev.owner = THIS_MODULE; g_admin_ctx.dev = device_create(mc_device_class, NULL, g_admin_ctx.mc_dev_admin, NULL, MC_ADMIN_DEVNODE); if (IS_ERR(g_admin_ctx.dev)) { err = PTR_ERR(g_admin_ctx.dev); goto fail_dev_create; } g_admin_ctx.mc_dev_name.name = "driver = &g_admin_ctx.mc_dev_name; *out_dev = g_admin_ctx.mc_dev_admin; /* Register the call back for starting the secure world */ g_admin_ctx.tee_start_cb = tee_start_cb; MCDRV_DBG("done"); return 0; fail_dev_create: cdev_del(&g_admin_ctx.mc_admin_cdev); fail_cdev_add: unregister_chrdev_region(g_admin_ctx.mc_dev_admin, MC_DEV_MAX); fail_alloc_chrdev_region: MCDRV_ERROR("fail with %d", err); return err; } void mc_admin_exit(struct class *mc_device_class) { device_destroy(mc_device_class, g_admin_ctx.mc_dev_admin); cdev_del(&g_admin_ctx.mc_admin_cdev); unregister_chrdev_region(g_admin_ctx.mc_dev_admin, MC_DEV_MAX); /* Requests from driver to daemon */ mutex_destroy(&g_request.states_mutex); MCDRV_DBG("done"); }