// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2013-2020 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 /* struct vm_area_struct */ #include #include "public/mc_user.h" #include "main.h" #include "admin.h" /* tee_object* */ #include "mmu.h" /* tee_mmu* */ #include "client.h" #include "mcp.h" /* mcp_get_version */ #include "protocol.h" #include "user.h" /* * Get client object from file pointer */ static inline struct tee_client *get_client(struct file *file) { return (struct tee_client *)file->private_data; } /* * Callback for system open() * A set of internal client data are created and initialized. * * @inode * @file * Returns 0 if OK or -ENOMEM if no allocation was possible. */ static int user_open(struct inode *inode, struct file *file) { struct tee_client *client; /* Create client */ mc_dev_devel("from %s (%d)", current->comm, current->pid); client = client_create(false, protocol_vm_id()); if (!client) return -ENOMEM; /* Store client in user file */ file->private_data = client; return 0; } /* * Callback for system close() * The client object is freed. * @inode * @file * Returns 0 */ static int user_release(struct inode *inode, struct file *file) { struct tee_client *client = get_client(file); /* Close client */ mc_dev_devel("from %s (%d)", current->comm, current->pid); if (!client) return -EPROTO; /* Detach client from user file */ file->private_data = NULL; /* Destroy client, including remaining sessions */ client_close(client); return 0; } /* * Check r/w access to referenced memory */ static inline int ioctl_check_pointer(unsigned int cmd, int __user *uarg) { int err = 0; if (_IOC_DIR(cmd) & _IOC_READ) #if KERNEL_VERSION(5, 0, 0) > LINUX_VERSION_CODE err = !access_ok(VERIFY_WRITE, uarg, _IOC_SIZE(cmd)); #else err = !access_ok(uarg, _IOC_SIZE(cmd)); #endif else if (_IOC_DIR(cmd) & _IOC_WRITE) #if KERNEL_VERSION(5, 0, 0) > LINUX_VERSION_CODE err = !access_ok(VERIFY_READ, uarg, _IOC_SIZE(cmd)); #else err = !access_ok(uarg, _IOC_SIZE(cmd)); #endif if (err) return -EFAULT; return 0; } /* * Callback for system ioctl() * Implement most of ClientLib API functions * @file pointer to file * @cmd command * @arg arguments * * Returns 0 for OK and an errno in case of error */ static long user_ioctl(struct file *file, unsigned int id, unsigned long arg) { struct tee_client *client = get_client(file); int __user *uarg = (int __user *)arg; int ret = -EINVAL; mc_dev_devel("%u from %s", _IOC_NR(id), current->comm); if (!client) return -EPROTO; if (ioctl_check_pointer(id, uarg)) return -EFAULT; switch (id) { case MC_IO_HAS_SESSIONS: /* Freeze the client */ if (client_has_sessions(client)) ret = -ENOTEMPTY; else ret = 0; break; case MC_IO_OPEN_SESSION: { struct mc_ioctl_open_session session; if (copy_from_user(&session, uarg, sizeof(session))) { ret = -EFAULT; break; } ret = client_mc_open_session(client, &session.uuid, session.tci, session.tci_len, &session.sid); if (ret) break; if (copy_to_user(uarg, &session, sizeof(session))) { ret = -EFAULT; client_remove_session(client, session.sid); break; } break; } case MC_IO_OPEN_TRUSTLET: { struct mc_ioctl_open_trustlet trustlet; if (copy_from_user(&trustlet, uarg, sizeof(trustlet))) { ret = -EFAULT; break; } ret = client_mc_open_trustlet(client, trustlet.tl, trustlet.tl_len, trustlet.tci, trustlet.tci_len, &trustlet.sid); if (ret) break; if (copy_to_user(uarg, &trustlet, sizeof(trustlet))) { ret = -EFAULT; client_remove_session(client, trustlet.sid); break; } break; } case MC_IO_CLOSE_SESSION: { u32 sid = (u32)arg; ret = client_remove_session(client, sid); break; } case MC_IO_NOTIFY: { u32 sid = (u32)arg; ret = client_notify_session(client, sid); break; } case MC_IO_WAIT: { struct mc_ioctl_wait wait; if (copy_from_user(&wait, uarg, sizeof(wait))) { ret = -EFAULT; break; } ret = client_waitnotif_session(client, wait.sid, wait.timeout); break; } case MC_IO_MAP: { struct mc_ioctl_map map; if (copy_from_user(&map, uarg, sizeof(map))) { ret = -EFAULT; break; } ret = client_mc_map(client, map.sid, NULL, &map.buf); if (ret) break; /* Fill in return struct */ if (copy_to_user(uarg, &map, sizeof(map))) { ret = -EFAULT; break; } break; } case MC_IO_UNMAP: { struct mc_ioctl_map map; if (copy_from_user(&map, uarg, sizeof(map))) { ret = -EFAULT; break; } ret = client_mc_unmap(client, map.sid, &map.buf); break; } case MC_IO_ERR: { struct mc_ioctl_geterr __user *uerr = (struct mc_ioctl_geterr __user *)uarg; u32 sid; s32 exit_code; if (get_user(sid, &uerr->sid)) { ret = -EFAULT; break; } ret = client_get_session_exitcode(client, sid, &exit_code); if (ret) break; /* Fill in return struct */ if (put_user(exit_code, &uerr->value)) { ret = -EFAULT; break; } break; } case MC_IO_VERSION: { struct mc_version_info version_info; ret = mcp_get_version(&version_info); if (ret) break; if (copy_to_user(uarg, &version_info, sizeof(version_info))) ret = -EFAULT; break; } case MC_IO_GP_INITIALIZE_CONTEXT: { struct mc_ioctl_gp_initialize_context context; if (copy_from_user(&context, uarg, sizeof(context))) { ret = -EFAULT; break; } ret = client_gp_initialize_context(client, &context.ret); if (copy_to_user(uarg, &context, sizeof(context))) { ret = -EFAULT; break; } break; } case MC_IO_GP_REGISTER_SHARED_MEM: { struct mc_ioctl_gp_register_shared_mem shared_mem; if (copy_from_user(&shared_mem, uarg, sizeof(shared_mem))) { ret = -EFAULT; break; } ret = client_gp_register_shared_mem(client, NULL, NULL, &shared_mem.memref, &shared_mem.ret); if (copy_to_user(uarg, &shared_mem, sizeof(shared_mem))) { ret = -EFAULT; break; } break; } case MC_IO_GP_RELEASE_SHARED_MEM: { struct mc_ioctl_gp_release_shared_mem shared_mem; if (copy_from_user(&shared_mem, uarg, sizeof(shared_mem))) { ret = -EFAULT; break; } ret = client_gp_release_shared_mem(client, &shared_mem.memref); break; } case MC_IO_GP_OPEN_SESSION: { struct mc_ioctl_gp_open_session session; struct tee_object *obj = NULL; struct tee_mmu *ta_mmu = NULL; if (copy_from_user(&session, uarg, sizeof(session))) { ret = -EFAULT; break; } /* Create object from user-space binary */ if (session.ta_len) { obj = tee_object_read(session.ta, session.ta_len); if (IS_ERR(obj)) { ret = PTR_ERR(obj); break; } { struct mc_ioctl_buffer buf = { .va = (uintptr_t)obj->data, .len = obj->length, .flags = MC_IO_MAP_INPUT, }; ta_mmu = tee_mmu_create(NULL, &buf); if (IS_ERR(ta_mmu)) { ret = PTR_ERR(ta_mmu); goto err_mmu; } } } ret = client_gp_open_session(client, &session.uuid, ta_mmu, &session.operation, &session.identity, &session.ret, &session.session_id); if (ta_mmu) tee_mmu_put(ta_mmu); err_mmu: if (obj) tee_object_free(obj); if (copy_to_user(uarg, &session, sizeof(session))) { ret = -EFAULT; break; } break; } case MC_IO_GP_CLOSE_SESSION: { struct mc_ioctl_gp_close_session session; if (copy_from_user(&session, uarg, sizeof(session))) { ret = -EFAULT; break; } ret = client_gp_close_session(client, session.session_id); break; } case MC_IO_GP_INVOKE_COMMAND: { struct mc_ioctl_gp_invoke_command command; if (copy_from_user(&command, uarg, sizeof(command))) { ret = -EFAULT; break; } ret = client_gp_invoke_command(client, command.session_id, command.command_id, &command.operation, &command.ret); if (copy_to_user(uarg, &command, sizeof(command))) { ret = -EFAULT; break; } break; } case MC_IO_GP_REQUEST_CANCELLATION: { struct mc_ioctl_gp_request_cancellation cancel; if (copy_from_user(&cancel, uarg, sizeof(cancel))) { ret = -EFAULT; break; } client_gp_request_cancellation(client, cancel.operation.started); ret = 0; break; } default: ret = -ENOIOCTLCMD; mc_dev_err(ret, "unsupported command no %d", id); } return ret; } static const struct file_operations mc_user_fops = { .owner = THIS_MODULE, .open = user_open, .release = user_release, .unlocked_ioctl = user_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl = user_ioctl, #endif }; int mc_user_init(struct cdev *cdev) { cdev_init(cdev, &mc_user_fops); return 0; }