M7350/kernel/drivers/gud/MobiCoreDriver/session.c
2024-09-09 08:57:42 +00:00

780 lines
18 KiB
C

/*
* 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 <linux/types.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/jiffies.h>
#include <linux/sched.h>
#include <linux/workqueue.h>
#include <linux/err.h>
#include <linux/mm.h>
#include <crypto/hash.h>
#include <linux/scatterlist.h>
#include <linux/fs.h>
#include "public/mc_linux.h"
#include "public/mc_admin.h"
#include "platform.h" /* MC_UIDGID_OLDSTYLE */
#include "main.h"
#include "debug.h"
#include "mmu.h"
#include "mcp.h"
#include "client.h" /* *cbuf* */
#include "session.h"
#include "mci/mcimcp.h"
#define SHA1_HASH_SIZE 20
struct tbase_wsm {
/* Buffer NWd addr (uva or kva, used only for lookup) */
uintptr_t va;
/* buffer length */
uint32_t len;
/* Buffer SWd addr */
uint32_t sva;
/* mmu L2 table */
struct tbase_mmu *mmu;
/* possibly a pointer to a cbuf */
struct tbase_cbuf *cbuf;
/* list node */
struct list_head list;
};
/*
* Postponed closing for GP TAs.
* Implemented as a worker because cannot be executed from within isr_worker.
*/
static void session_close_worker(struct work_struct *work)
{
struct mcp_session *mcp_session;
struct tbase_session *session;
mcp_session = container_of(work, struct mcp_session, close_work);
session = container_of(mcp_session, struct tbase_session, mcp_session);
session_close(session);
}
/* Forward declarations */
static struct tbase_wsm *wsm_create(struct tbase_session *session,
uintptr_t buf, uint32_t len);
static void wsm_free(struct tbase_wsm *wsm);
static int hash_path_and_data(char *hash, const void *data,
unsigned int data_len)
{
struct mm_struct *mm = current->mm;
struct hash_desc desc;
struct scatterlist sg;
char *buf;
char *path;
unsigned int path_len;
int ret = 0;
buf = (char *)__get_free_page(GFP_KERNEL);
if (!buf)
return -ENOMEM;
down_read(&mm->mmap_sem);
if (!mm->exe_file) {
ret = -ENOENT;
goto end;
}
path = d_path(&mm->exe_file->f_path, buf, PAGE_SIZE);
if (IS_ERR(path)) {
ret = PTR_ERR(path);
goto end;
}
MCDRV_DBG("current process path = ");
{
char *c;
for (c = path; *c; c++)
MCDRV_DBG("%c %d", *c, *c);
}
path_len = strnlen(path, PAGE_SIZE);
MCDRV_DBG("path_len = %u", path_len);
desc.tfm = crypto_alloc_hash("sha1", 0, CRYPTO_ALG_ASYNC);
if (IS_ERR(desc.tfm)) {
ret = PTR_ERR(desc.tfm);
MCDRV_DBG("could not alloc hash = %d", ret);
goto end;
}
desc.flags = 0;
sg_init_one(&sg, path, path_len);
crypto_hash_init(&desc);
crypto_hash_update(&desc, &sg, path_len);
if (data) {
MCDRV_DBG("current process path: hashing additional data\n");
sg_init_one(&sg, data, data_len);
crypto_hash_update(&desc, &sg, data_len);
}
crypto_hash_final(&desc, hash);
crypto_free_hash(desc.tfm);
end:
up_read(&mm->mmap_sem);
free_page((unsigned long)buf);
return ret;
}
static int check_prepare_identity(const struct mc_identity *identity,
struct identity *mcp_identity)
{
struct mc_identity *mcp_id = (struct mc_identity *)mcp_identity;
uint8_t hash[SHA1_HASH_SIZE];
bool application = false;
const void *data;
unsigned int data_len;
/* Mobicore doesn't support GP client authentication. */
if (!g_ctx.f_client_login &&
(identity->login_type != TEEC_LOGIN_PUBLIC)) {
MCDRV_DBG_WARN("Unsupported login type %d",
identity->login_type);
return -EINVAL;
}
/* Copy login type */
mcp_identity->login_type = identity->login_type;
/* Fill in uid field */
if ((identity->login_type == TEEC_LOGIN_USER) ||
(identity->login_type == TEEC_LOGIN_USER_APPLICATION)) {
/* Set euid and ruid of the process. */
#if !defined(KUIDT_INIT) || defined(MC_UIDGID_OLDSTYLE)
mcp_id->uid.euid = current_euid();
mcp_id->uid.ruid = current_uid();
#else
mcp_id->uid.euid = current_euid().val;
mcp_id->uid.ruid = current_uid().val;
#endif
}
/* Check gid field */
if ((identity->login_type == TEEC_LOGIN_GROUP) ||
(identity->login_type == TEEC_LOGIN_GROUP_APPLICATION)) {
#if !defined(KUIDT_INIT) || defined(MC_UIDGID_OLDSTYLE)
gid_t gid = identity->gid;
#else
kgid_t gid = {
.val = identity->gid,
};
#endif
/* Check if gid is one of: egid of the process, its rgid or one
* of its supplementary groups */
if (!in_egroup_p(gid) && !in_group_p(gid)) {
MCDRV_DBG("group %d not allowed", identity->gid);
return -EACCES;
}
MCDRV_DBG("group %d found", identity->gid);
mcp_id->gid = identity->gid;
}
switch (identity->login_type) {
case TEEC_LOGIN_PUBLIC:
case TEEC_LOGIN_USER:
case TEEC_LOGIN_GROUP:
break;
case TEEC_LOGIN_APPLICATION:
application = true;
data = NULL;
data_len = 0;
break;
case TEEC_LOGIN_USER_APPLICATION:
application = true;
data = &mcp_id->uid;
data_len = sizeof(mcp_id->uid);
break;
case TEEC_LOGIN_GROUP_APPLICATION:
application = true;
data = &identity->gid;
data_len = sizeof(identity->gid);
break;
default:
/* Any other login_type value is invalid. */
MCDRV_DBG_WARN("Invalid login type");
return -EINVAL;
}
if (application) {
if (hash_path_and_data(hash, data, data_len)) {
MCDRV_DBG("error in hash calculation");
return -EAGAIN;
}
memcpy(&mcp_id->login_data, hash, sizeof(mcp_id->login_data));
}
return 0;
}
/*
* Create a session object.
* Note: object is not attached to client yet.
*/
struct tbase_session *session_create(struct tbase_client *client, bool is_gp,
struct mc_identity *identity)
{
struct tbase_session *session;
struct identity mcp_identity;
if (is_gp) {
/* Check identity method and data. */
int ret = check_prepare_identity(identity, &mcp_identity);
if (ret)
return ERR_PTR(ret);
}
/* Allocate session object */
session = kzalloc(sizeof(*session), GFP_KERNEL);
if (!session)
return ERR_PTR(-ENOMEM);
mutex_init(&session->close_lock);
/* Initialise object members */
mcp_session_init(&session->mcp_session, is_gp, &mcp_identity);
INIT_WORK(&session->mcp_session.close_work, session_close_worker);
session->client = client;
kref_init(&session->kref);
INIT_LIST_HEAD(&session->list);
mutex_init(&session->wsms_lock);
INIT_LIST_HEAD(&session->wsms);
MCDRV_DBG("created session %p: client %p", session, session->client);
return session;
}
int session_open(struct tbase_session *session, const struct tbase_object *obj,
const struct tbase_mmu *obj_mmu, uintptr_t tci, size_t len)
{
struct mcp_buffer_map map;
tbase_mmu_buffer(obj_mmu, &map);
/* Create wsm object for tci */
if (tci && len) {
struct tbase_wsm *wsm;
struct mcp_buffer_map tci_map;
int ret = 0;
mutex_lock(&session->wsms_lock);
wsm = wsm_create(session, tci, len);
if (IS_ERR(wsm))
ret = PTR_ERR(wsm);
mutex_unlock(&session->wsms_lock);
if (ret)
return ret;
tbase_mmu_buffer(wsm->mmu, &tci_map);
ret = mcp_open_session(&session->mcp_session, obj, &map,
&tci_map);
if (ret) {
mutex_lock(&session->wsms_lock);
wsm_free(wsm);
mutex_unlock(&session->wsms_lock);
}
return ret;
}
if (tci || len) {
MCDRV_ERROR("Tci pointer and length are incoherent");
return -EINVAL;
}
return mcp_open_session(&session->mcp_session, obj, &map, NULL);
}
/*
* Close TA and unreference session object.
* Object will be freed if reference reaches 0.
* Session object is assumed to have been removed from main list, which means
* that session_close cannot be called anymore.
*/
int session_close(struct tbase_session *session)
{
int ret = 0;
if (!session)
return -ENXIO;
mutex_lock(&session->close_lock);
switch (mcp_close_session(&session->mcp_session)) {
case 0:
/* TA is closed, remove from closing list */
mutex_lock(&g_ctx.closing_lock);
list_del(&session->list);
mutex_unlock(&g_ctx.closing_lock);
/* Remove the ref we took on creation, exit if session freed */
if (session_put(session))
return 0;
break;
case -EBUSY:
/*
* (GP) TA needs time to close. The "TA closed" notification
* will trigger a new call to session_close().
* Return OK but do not unref.
*/
break;
default:
MCDRV_ERROR("Failed to close session %x in SWd",
session->mcp_session.id);
ret = -EPERM;
}
mutex_unlock(&session->close_lock);
return ret;
}
/*
* Free session object and all objects it contains (wsm).
*/
static void session_free(struct kref *kref)
{
struct tbase_session *session;
struct tbase_wsm *wsm, *next;
/* Remove remaining shared buffers (unmapped in SWd by mcp_close) */
session = container_of(kref, struct tbase_session, kref);
list_for_each_entry_safe(wsm, next, &session->wsms, list) {
MCDRV_DBG("session %p: free wsm %p", session, wsm);
wsm_free(wsm);
}
MCDRV_DBG("freed session %p: client %p id %x",
session, session->client, session->mcp_session.id);
kfree(session);
}
/*
* Unreference session.
* Free session object if reference reaches 0.
*/
int session_put(struct tbase_session *session)
{
return kref_put(&session->kref, session_free);
}
/*
* Send a notification to TA
*/
int session_notify_swd(struct tbase_session *session)
{
if (!session) {
MCDRV_ERROR("Session pointer is null");
return -EINVAL;
}
return mcp_notify(&session->mcp_session);
}
/*
* Read and clear last notification received from TA
*/
int32_t session_exitcode(struct tbase_session *session)
{
return mcp_session_exitcode(&session->mcp_session);
}
/*
* Free a WSM object
*/
static void wsm_free(struct tbase_wsm *wsm)
{
/* Remove wsm from its parent session's list */
list_del(&wsm->list);
/* Free MMU table */
if (!IS_ERR_OR_NULL(wsm->mmu))
tbase_mmu_delete(wsm->mmu);
/* Unref cbuf if applicable */
if (wsm->cbuf)
tbase_cbuf_put(wsm->cbuf);
/* Delete wsm object */
MCDRV_DBG("freed wsm %p: mmu %p cbuf %p va %lx len %u",
wsm, wsm->mmu, wsm->cbuf, wsm->va, wsm->len);
kfree(wsm);
}
static struct tbase_wsm *wsm_create(struct tbase_session *session,
uintptr_t buf, uint32_t len)
{
struct tbase_wsm *wsm;
struct task_struct *task = NULL;
uintptr_t va;
int ret;
/* Allocate structure */
wsm = kzalloc(sizeof(*wsm), GFP_KERNEL);
if (!wsm) {
ret = -ENOMEM;
goto err_no_wsm;
}
/* Add wsm to list so destroy can find it */
list_add(&wsm->list, &session->wsms);
/* Check if buffer is contained in a cbuf */
wsm->cbuf = tbase_cbuf_get_by_addr(session->client, buf);
if (wsm->cbuf) {
uintptr_t offset;
if (client_is_kernel(session->client))
offset = buf - tbase_cbuf_addr(wsm->cbuf);
else
offset = buf - tbase_cbuf_uaddr(wsm->cbuf);
if ((offset + len) > tbase_cbuf_len(wsm->cbuf)) {
ret = -EINVAL;
MCDRV_ERROR("crosses cbuf boundary");
goto err;
}
/* Provide kernel virtual address */
va = tbase_cbuf_addr(wsm->cbuf) + offset;
} else {
/* Not a cbuf. va is uva or kva depending on client. */
/* Provide "task" if client is user */
va = buf;
if (!client_is_kernel(session->client))
task = current;
}
/* Build MMU table for buffer */
wsm->mmu = tbase_mmu_create(task, (void *)va, len);
if (IS_ERR(wsm->mmu)) {
ret = PTR_ERR(wsm->mmu);
goto err;
}
wsm->va = buf;
wsm->len = len;
MCDRV_DBG("created wsm %p: mmu %p cbuf %p va %lx len %u",
wsm, wsm->mmu, wsm->cbuf, wsm->va, wsm->len);
goto end;
err:
wsm_free(wsm);
err_no_wsm:
wsm = ERR_PTR(ret);
end:
return wsm;
}
static inline int wsm_check(struct tbase_session *session,
struct mc_ioctl_buffer *buf)
{
struct tbase_wsm *wsm;
list_for_each_entry(wsm, &session->wsms, list) {
if ((buf->va < (wsm->va + wsm->len)) &&
((buf->va + buf->len) > wsm->va)) {
MCDRV_ERROR("buffer %lx overlaps with existing wsm",
wsm->va);
return -EADDRINUSE;
}
}
return 0;
}
static inline struct tbase_wsm *wsm_find(struct tbase_session *session,
uintptr_t va)
{
struct tbase_wsm *wsm;
list_for_each_entry(wsm, &session->wsms, list)
if (wsm->va == va)
return wsm;
return NULL;
}
static inline int wsm_info(struct tbase_wsm *wsm, struct kasnprintf_buf *buf)
{
ssize_t ret;
ret = kasnprintf(buf, "\t\twsm %p: mmu %p cbuf %p va %lx len %u\n",
wsm, wsm->mmu, wsm->cbuf, wsm->va, wsm->len);
if (ret < 0)
return ret;
if (wsm->mmu) {
ret = tbase_mmu_info(wsm->mmu, buf);
if (ret < 0)
return ret;
}
return 0;
}
/*
* Share buffers with SWd and add corresponding WSM objects to session.
*/
int session_wsms_add(struct tbase_session *session,
struct mc_ioctl_buffer *bufs)
{
struct mc_ioctl_buffer *buf;
struct mcp_buffer_map maps[MC_MAP_MAX];
struct mcp_buffer_map *map;
int i, ret = 0;
uint32_t n_null_buf = 0;
/* Check parameters */
if (!session)
return -ENXIO;
/* Lock the session */
mutex_lock(&session->wsms_lock);
for (i = 0, buf = bufs, map = maps; i < MC_MAP_MAX; i++, buf++, map++) {
if (!buf->va) {
n_null_buf++;
continue;
}
/* Avoid mapping overlaps */
if (wsm_check(session, buf)) {
ret = -EADDRINUSE;
MCDRV_ERROR("maps[%d] va=%llx already map'd", i,
buf->va);
goto unlock;
}
}
if (n_null_buf >= MC_MAP_MAX) {
ret = -EINVAL;
MCDRV_ERROR("va=NULL");
goto unlock;
}
for (i = 0, buf = bufs, map = maps; i < MC_MAP_MAX; i++, buf++, map++) {
struct tbase_wsm *wsm;
if (!buf->va) {
map->type = WSM_INVALID;
continue;
}
wsm = wsm_create(session, buf->va, buf->len);
if (IS_ERR(wsm)) {
ret = PTR_ERR(wsm);
MCDRV_ERROR("maps[%d] va=%llx create failed: %d", i,
buf->va, ret);
goto end;
}
tbase_mmu_buffer(wsm->mmu, map);
MCDRV_DBG("maps[%d] va=%llx: t:%u a:%llx o:%u l:%u", i, buf->va,
map->type, map->phys_addr, map->offset, map->length);
}
/* Map buffers */
if (g_ctx.f_multimap) {
/* Send MCP message to map buffers in SWd */
ret = mcp_multimap(session->mcp_session.id, maps);
if (ret)
MCDRV_ERROR("multimap failed: %d", ret);
} else {
/* Map each buffer */
for (i = 0, buf = bufs, map = maps; i < MC_MAP_MAX; i++, buf++,
map++) {
if (!buf->va)
continue;
/* Send MCP message to map buffer in SWd */
ret = mcp_map(session->mcp_session.id, map);
if (ret) {
MCDRV_ERROR("maps[%d] va=%llx map failed: %d",
i, buf->va, ret);
break;
}
}
}
end:
for (i = 0, buf = bufs, map = maps; i < MC_MAP_MAX; i++, buf++, map++) {
struct tbase_wsm *wsm = wsm_find(session, buf->va);
if (!buf->va)
continue;
if (ret) {
if (!wsm)
break;
/* Destroy mapping */
wsm_free(wsm);
} else {
/* Store mapping */
buf->sva = map->secure_va;
wsm->sva = buf->sva;
MCDRV_DBG("maps[%d] va=%llx map'd len=%u sva=%llx",
i, buf->va, buf->len, buf->sva);
}
}
unlock:
/* Unlock the session */
mutex_unlock(&session->wsms_lock);
return ret;
}
/*
* Stop sharing buffers and delete corrsponding WSM objects.
*/
int session_wsms_remove(struct tbase_session *session,
const struct mc_ioctl_buffer *bufs)
{
const struct mc_ioctl_buffer *buf;
struct mcp_buffer_map maps[MC_MAP_MAX];
struct mcp_buffer_map *map;
int i, ret = 0;
uint32_t n_null_buf = 0;
if (!session) {
MCDRV_ERROR("session pointer is null");
return -EINVAL;
}
/* Lock the session */
mutex_lock(&session->wsms_lock);
/* Find, check and map buffer */
for (i = 0, buf = bufs, map = maps; i < MC_MAP_MAX; i++, buf++, map++) {
struct tbase_wsm *wsm;
if (!buf->va) {
n_null_buf++;
map->secure_va = 0;
continue;
}
wsm = wsm_find(session, buf->va);
if (!wsm) {
ret = -EADDRNOTAVAIL;
MCDRV_ERROR("maps[%d] va=%llx not found", i,
buf->va);
goto out;
}
/* Check input params consistency */
/* TODO: fix the spec, "len" is NOT ignored anymore */
if ((wsm->sva != buf->sva) || (wsm->len != buf->len)) {
MCDRV_ERROR("maps[%d] va=%llx no match: %x != %llx",
i, buf->va, wsm->sva, buf->sva);
MCDRV_ERROR("maps[%d] va=%llx no match: %u != %u",
i, buf->va, wsm->len, buf->len);
ret = -EINVAL;
goto out;
}
tbase_mmu_buffer(wsm->mmu, map);
map->secure_va = buf->sva;
MCDRV_DBG("maps[%d] va=%llx: t:%u a:%llx o:%u l:%u s:%llx", i,
buf->va, map->type, map->phys_addr, map->offset,
map->length, map->secure_va);
}
if (n_null_buf >= MC_MAP_MAX) {
ret = -EINVAL;
MCDRV_ERROR("va=NULL");
goto out;
}
if (g_ctx.f_multimap) {
/* Send MCP command to unmap buffers in SWd */
ret = mcp_multiunmap(session->mcp_session.id, maps);
if (ret)
MCDRV_ERROR("mcp_multiunmap failed: %d", ret);
} else {
for (i = 0, buf = bufs, map = maps; i < MC_MAP_MAX;
i++, buf++, map++) {
if (!buf->va)
continue;
/* Send MCP command to unmap buffer in SWd */
ret = mcp_unmap(session->mcp_session.id, map);
if (ret) {
MCDRV_ERROR("maps[%d] va=%llx unmap failed: %d",
i, buf->va, ret);
break;
}
}
}
for (i = 0, buf = bufs; i < MC_MAP_MAX; i++, buf++) {
struct tbase_wsm *wsm = wsm_find(session, buf->va);
if (!wsm)
break;
/* Free wsm */
wsm_free(wsm);
MCDRV_DBG("maps[%d] va=%llx unmap'd len=%u sva=%llx", i,
buf->va, buf->len, buf->sva);
}
out:
mutex_unlock(&session->wsms_lock);
return ret;
}
/*
* Sleep until next notification from SWd.
*/
int session_waitnotif(struct tbase_session *session, int32_t timeout)
{
return mcp_session_waitnotif(&session->mcp_session, timeout);
}
int session_info(struct tbase_session *session, struct kasnprintf_buf *buf)
{
struct tbase_wsm *wsm;
int32_t exit_code = mcp_session_exitcode(&session->mcp_session);
int ret;
ret = kasnprintf(buf, "\tsession %p: %x rc %d\n", session,
session->mcp_session.id, exit_code);
if (ret < 0)
return ret;
/* WMSs */
mutex_lock(&session->wsms_lock);
if (list_empty(&session->wsms))
goto done;
list_for_each_entry(wsm, &session->wsms, list) {
ret = wsm_info(wsm, buf);
if (ret < 0)
goto done;
}
done:
mutex_unlock(&session->wsms_lock);
if (ret < 0)
return ret;
return 0;
}