// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2015-2016, Linaro Limited
*/
#define pr_fmt(fmt) "%s: " fmt, __func__
#include <linux/cdev.h>
#include <linux/cred.h>
#include <linux/fs.h>
#include <linux/idr.h>
#include <linux/module.h>
#include <linux/overflow.h>
#include <linux/slab.h>
#include <linux/tee_core.h>
#include <linux/uaccess.h>
#include <crypto/sha1.h>
#include "tee_private.h"
#define TEE_NUM_DEVICES 32
#define TEE_IOCTL_PARAM_SIZE(x) (size_mul(sizeof(struct tee_param), (x)))
#define TEE_UUID_NS_NAME_SIZE 128
/*
* TEE Client UUID name space identifier (UUIDv4)
*
* Value here is random UUID that is allocated as name space identifier for
* forming Client UUID's for TEE environment using UUIDv5 scheme.
*/
static const uuid_t tee_client_uuid_ns = UUID_INIT(0x58ac9ca0, 0x2086, 0x4683,
0xa1, 0xb8, 0xec, 0x4b,
0xc0, 0x8e, 0x01, 0xb6);
/*
* Unprivileged devices in the lower half range and privileged devices in
* the upper half range.
*/
static DECLARE_BITMAP(dev_mask, TEE_NUM_DEVICES);
static DEFINE_SPINLOCK(driver_lock);
static const struct class tee_class;
static dev_t tee_devt;
struct tee_context *teedev_open(struct tee_device *teedev)
{
int rc;
struct tee_context *ctx;
if (!tee_device_get(teedev))
return ERR_PTR(-EINVAL);
ctx = kzalloc_obj(*ctx);
if (!ctx) {
rc = -ENOMEM;
goto err;
}
kref_init(&ctx->refcount);
ctx->teedev = teedev;
rc = teedev->desc->ops->open(ctx);
if (rc)
goto err;
return ctx;
err:
kfree(ctx);
tee_device_put(teedev);
return ERR_PTR(rc);
}
EXPORT_SYMBOL_GPL(teedev_open);
void teedev_ctx_get(struct tee_context *ctx)
{
if (ctx->releasing)
return;
kref_get(&ctx->refcount);
}
EXPORT_SYMBOL_GPL(teedev_ctx_get);
static void teedev_ctx_release(struct kref *ref)
{
struct tee_context *ctx = container_of(ref, struct tee_context,
refcount);
ctx->releasing = true;
ctx->teedev->desc->ops->release(ctx);
kfree(ctx);
}
void teedev_ctx_put(struct tee_context *ctx)
{
if (ctx->releasing)
return;
kref_put(&ctx->refcount, teedev_ctx_release);
}
EXPORT_SYMBOL_GPL(teedev_ctx_put);
void teedev_close_context(struct tee_context *ctx)
{
struct tee_device *teedev = ctx->teedev;
if (teedev->desc->ops->close_context)
teedev->desc->ops->close_context(ctx);
teedev_ctx_put(ctx);
tee_device_put(teedev);
}
EXPORT_SYMBOL_GPL(teedev_close_context);
static int tee_open(struct inode *inode, struct file *filp)
{
struct tee_context *ctx;
ctx = teedev_open(container_of(inode->i_cdev, struct tee_device, cdev));
if (IS_ERR(ctx))
return PTR_ERR(ctx);
/*
* Default user-space behaviour is to wait for tee-supplicant
* if not present for any requests in this context.
*/
ctx->supp_nowait = false;
filp->private_data = ctx;
return 0;
}
static int tee_release(struct inode *inode, struct file *filp)
{
teedev_close_context(filp->private_data);
return 0;
}
/**
* uuid_v5() - Calculate UUIDv5
* @uuid: Resulting UUID
* @ns: Name space ID for UUIDv5 function
* @name: Name for UUIDv5 function
* @size: Size of name
*
* UUIDv5 is specific in RFC 4122.
*
* This implements section (for SHA-1):
* 4.3. Algorithm for Creating a Name-Based UUID
*/
static void uuid_v5(uuid_t *uuid, const uuid_t *ns, const void *name,
size_t size)
{
unsigned char hash[SHA1_DIGEST_SIZE];
struct sha1_ctx ctx;
sha1_init(&ctx);
sha1_update(&ctx, (const u8 *)ns, sizeof(*ns));
sha1_update(&ctx, (const u8 *)name, size);
sha1_final(&ctx, hash);
memcpy(uuid->b, hash, UUID_SIZE);
/* Tag for version 5 */
uuid->b[6] = (hash[6] & 0x0F) | 0x50;
uuid->b[8] = (hash[8] & 0x3F) | 0x80;
}
int tee_session_calc_client_uuid(uuid_t *uuid, u32 connection_method,
const u8 connection_data[TEE_IOCTL_UUID_LEN])
{
gid_t ns_grp = (gid_t)-1;
kgid_t grp = INVALID_GID;
char *name = NULL;
int name_len;
int rc = 0;
if (connection_method == TEE_IOCTL_LOGIN_PUBLIC ||
connection_method == TEE_IOCTL_LOGIN_REE_KERNEL) {
/* Nil UUID to be passed to TEE environment */
uuid_copy(uuid, &uuid_null);
return 0;
}
/*
* In Linux environment client UUID is based on UUIDv5.
*
* Determine client UUID with following semantics for 'name':
*
* For TEEC_LOGIN_USER:
* uid=<uid>
*
* For TEEC_LOGIN_GROUP:
* gid=<gid>
*
*/
name = kzalloc(TEE_UUID_NS_NAME_SIZE, GFP_KERNEL);
if (!name)
return -ENOMEM;
switch (connection_method) {
case TEE_IOCTL_LOGIN_USER:
name_len = snprintf(name, TEE_UUID_NS_NAME_SIZE, "uid=%x",
current_euid().val);
if (name_len >= TEE_UUID_NS_NAME_SIZE) {
rc = -E2BIG;
goto out_free_name;
}
break;
case TEE_IOCTL_LOGIN_GROUP:
memcpy(&ns_grp, connection_data, sizeof(gid_t));
grp = make_kgid(current_user_ns(), ns_grp);
if (!gid_valid(grp) || !in_egroup_p(grp)) {
rc = -EPERM;
goto out_free_name;
}
name_len = snprintf(name, TEE_UUID_NS_NAME_SIZE, "gid=%x",
grp.val);
if (name_len >= TEE_UUID_NS_NAME_SIZE) {
rc = -E2BIG;
goto out_free_name;
}
break;
default:
rc = -EINVAL;
goto out_free_name;
}
uuid_v5(uuid, &tee_client_uuid_ns, name, name_len);
out_free_name:
kfree(name);
return rc;
}
EXPORT_SYMBOL_GPL(tee_session_calc_client_uuid);
static int tee_ioctl_version(struct tee_context *ctx,
struct tee_ioctl_version_data __user *uvers)
{
struct tee_ioctl_version_data vers;
ctx->teedev->desc->ops->get_version(ctx->teedev, &vers);
if (ctx->teedev->desc->flags & TEE_DESC_PRIVILEGED)
vers.gen_caps |= TEE_GEN_CAP_PRIVILEGED;
if (copy_to_user(uvers, &vers, sizeof(vers)))
return -EFAULT;
return 0;
}
static int tee_
|