* access_tokenid.c
*
* Copyright (C) 2022-2023 Huawei Technologies Co., Ltd. All rights reserved.
*
*/
#define pr_fmt(fmt) "access_token_id: " fmt
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/rwlock.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include "access_tokenid.h"
DEFINE_RWLOCK(token_rwlock);
#define ACCESS_TOKEN_UID KUIDT_INIT(3020)
#define MAX_NODE_NUM 500
#define UINT32_T_BITS 32
static struct kmem_cache *g_cache = NULL;
static struct token_perm_node *g_token_perm_root = NULL;
static size_t g_total_node_num = 0;
int access_tokenid_get_tokenid(struct file *file, void __user *uarg)
{
return copy_to_user(uarg, ¤t->token,
sizeof(current->token)) ? -EFAULT : 0;
}
static bool check_permission_for_set_tokenid(struct file *file, unsigned long long tokenid)
{
kuid_t uid = current_uid();
struct inode *inode = file->f_inode;
access_tokenid_inner *tokenid_inner = (access_tokenid_inner *)&tokenid;
if (inode == NULL) {
pr_err("%s: file inode is null\n", __func__);
return false;
}
if (uid_eq(uid, GLOBAL_ROOT_UID) ||
uid_eq(uid, inode->i_uid)) {
return true;
} else if (uid_eq(uid, NWEBSPAWN_UID) && (tokenid_inner->render_flag == 1)) {
return true;
}
return false;
}
int access_tokenid_set_tokenid(struct file *file, void __user *uarg)
{
unsigned long long tmp = 0;
if (copy_from_user(&tmp, uarg, sizeof(tmp)))
return -EFAULT;
if (!check_permission_for_set_tokenid(file, tmp))
return -EPERM;
current->token = tmp;
return 0;
}
static bool check_permission_for_ftokenid(struct file *file)
{
int i;
struct group_info *group_info;
kuid_t uid = current_uid();
struct inode *inode = file->f_inode;
if (inode == NULL) {
pr_err("%s: file inode is null\n", __func__);
return false;
}
if (uid_eq(uid, GLOBAL_ROOT_UID))
return true;
group_info = get_current_groups();
for (i = 0; i < group_info->ngroups; i++) {
kgid_t gid = group_info->gid[i];
if (gid_eq(gid, inode->i_gid)) {
put_group_info(group_info);
return true;
}
}
put_group_info(group_info);
return false;
}
int access_tokenid_get_ftokenid(struct file *file, void __user *uarg)
{
if (!check_permission_for_ftokenid(file))
return -EPERM;
return copy_to_user(uarg, ¤t->ftoken,
sizeof(current->ftoken)) ? -EFAULT : 0;
}
int access_tokenid_set_ftokenid(struct file *file, void __user *uarg)
{
unsigned long long tmp = 0;
if (!check_permission_for_ftokenid(file))
return -EPERM;
if (copy_from_user(&tmp, uarg, sizeof(tmp)))
return -EFAULT;
current->ftoken = tmp;
return 0;
}
static bool check_permission_for_set_token_permission(void)
{
kuid_t uid = current_uid();
return uid_eq(uid, ACCESS_TOKEN_UID) || uid_eq(uid, GLOBAL_ROOT_UID);
}
static void add_node_to_left_tree_tail(struct token_perm_node *root_node, struct token_perm_node *node)
{
if ((root_node == NULL) || (node == NULL))
return;
struct token_perm_node *current_node = root_node;
while (true) {
if (current_node->left == NULL) {
current_node->left = node;
break;
}
current_node = current_node->left;
}
}
static void find_node_by_token(struct token_perm_node *root_node, uint32_t token,
struct token_perm_node **target_node, struct token_perm_node **parent_node)
{
*target_node = NULL;
*parent_node = NULL;
struct token_perm_node *current_node = root_node;
while (current_node != NULL) {
if (current_node->perm_data.token == token) {
*target_node = current_node;
break;
}
*parent_node = current_node;
if (current_node->perm_data.token > token) {
current_node = current_node->left;
} else {
current_node = current_node->right;
}
}
}
static int add_node_to_tree(struct token_perm_node **root_node, struct token_perm_node *node)
{
struct token_perm_node *target_node = NULL;
struct token_perm_node *parent_node = NULL;
find_node_by_token(*root_node, node->perm_data.token, &target_node, &parent_node);
if (target_node != NULL) {
target_node->perm_data = node->perm_data;
return 0;
}
if (g_total_node_num >= MAX_NODE_NUM) {
pr_err("%s: the number of token nodes is exceeded.\n", __func__);
return -EDQUOT;
}
if (parent_node == NULL) {
*root_node = node;
} else if (parent_node->perm_data.token > node->perm_data.token) {
parent_node->left = node;
} else {
parent_node->right = node;
}
g_total_node_num++;
return 1;
}
static struct token_perm_node *remove_node_by_token(struct token_perm_node **root_node, uint32_t token)
{
struct token_perm_node *target_node = NULL;
struct token_perm_node *parent_node = NULL;
find_node_by_token(*root_node, token, &target_node, &parent_node);
if (target_node == NULL) {
pr_err("%s: target token to be removed not found.\n", __func__);
return NULL;
}
struct token_perm_node **new_node_addr = NULL;
if (parent_node == NULL) {
new_node_addr = root_node;
} else if (parent_node->perm_data.token > token) {
new_node_addr = &(parent_node->left);
} else {
new_node_addr = &(parent_node->right);
}
if (target_node->right != NULL) {
*new_node_addr = target_node->right;
add_node_to_left_tree_tail(target_node->right, target_node->left);
} else {
*new_node_addr = target_node->left;
}
g_total_node_num--;
return target_node;
}
int access_tokenid_add_permission(struct file *file, void __user *uarg)
{
if (!check_permission_for_set_token_permission())
return -EPERM;
struct token_perm_node *node = kmem_cache_zalloc(g_cache, GFP_KERNEL);
if (node == NULL)
return -ENOMEM;
if (copy_from_user(&(node->perm_data), uarg, sizeof(ioctl_add_perm_data))) {
kmem_cache_free(g_cache, node);
return -EFAULT;
}
write_lock(&token_rwlock);
int ret = add_node_to_tree(&g_token_perm_root, node);
write_unlock(&token_rwlock);
if (ret <= 0) {
kmem_cache_free(g_cache, node);
return ret;
}
return 0;
}
int access_tokenid_remove_permission(struct file *file, void __user *uarg)
{
if (!check_permission_for_set_token_permission())
return -EPERM;
uint32_t token = 0;
if (copy_from_user(&token, uarg, sizeof(token)))
return -EFAULT;
write_lock(&token_rwlock);
struct token_perm_node *target_node = remove_node_by_token(&g_token_perm_root, token);
write_unlock(&token_rwlock);
if (target_node != NULL)
kmem_cache_free(g_cache, target_node);
return 0;
}
int access_tokenid_set_permission(struct file *file, void __user *uarg)
{
if (!check_permission_for_set_token_permission())
return -EPERM;
ioctl_set_get_perm_data set_perm_data;
if (copy_from_user(&set_perm_data, uarg, sizeof(set_perm_data)))
return -EFAULT;
uint32_t idx = set_perm_data.op_code / UINT32_T_BITS;
if (idx >= MAX_PERM_GROUP_NUM) {
pr_err("%s: invalid op_code.\n", __func__);
return -EINVAL;
}
struct token_perm_node *target_node = NULL;
struct token_perm_node *parent_node = NULL;
write_lock(&token_rwlock);
find_node_by_token(g_token_perm_root, set_perm_data.token, &target_node, &parent_node);
if (target_node == NULL) {
write_unlock(&token_rwlock);
pr_err("%s: token not found.\n", __func__);
return -ENODATA;
}
uint32_t bit_idx = set_perm_data.op_code % UINT32_T_BITS;
if (set_perm_data.is_granted) {
target_node->perm_data.perm[idx] |= (uint32_t)0x01 << bit_idx;
} else {
target_node->perm_data.perm[idx] &= ~((uint32_t)0x01 << bit_idx);
}
write_unlock(&token_rwlock);
return 0;
}
int access_tokenid_get_permission(struct file *file, void __user *uarg)
{
ioctl_set_get_perm_data get_perm_data;
if (copy_from_user(&get_perm_data, uarg, sizeof(get_perm_data)))
return -EFAULT;
uint32_t idx = get_perm_data.op_code / UINT32_T_BITS;
if (idx >= MAX_PERM_GROUP_NUM) {
pr_err("%s: invalid op_code.\n", __func__);
return -EINVAL;
}
struct token_perm_node *target_node = NULL;
struct token_perm_node *parent_node = NULL;
read_lock(&token_rwlock);
find_node_by_token(g_token_perm_root, get_perm_data.token, &target_node, &parent_node);
read_unlock(&token_rwlock);
if (target_node == NULL)
return -ENODATA;
uint32_t bit_idx = get_perm_data.op_code % UINT32_T_BITS;
return (target_node->perm_data.perm[idx] & ((uint32_t)0x01 << bit_idx)) >> bit_idx;
}
static long access_tokenid_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
void __user *uarg = (void __user *)arg;
if (uarg == NULL) {
pr_err("%s: invalid user uarg\n", __func__);
return -EINVAL;
}
long ret;
switch (cmd) {
case ACCESS_TOKENID_GET_TOKENID:
ret = access_tokenid_get_tokenid(file, uarg);
break;
case ACCESS_TOKENID_SET_TOKENID:
ret = access_tokenid_set_tokenid(file, uarg);
break;
case ACCESS_TOKENID_GET_FTOKENID:
ret = access_tokenid_get_ftokenid(file, uarg);
break;
case ACCESS_TOKENID_SET_FTOKENID:
ret = access_tokenid_set_ftokenid(file, uarg);
break;
case ACCESS_TOKENID_ADD_PERMISSIONS:
ret = access_tokenid_add_permission(file, uarg);
break;
case ACCESS_TOKENID_REMOVE_PERMISSIONS:
ret = access_tokenid_remove_permission(file, uarg);
break;
case ACCESS_TOKENID_GET_PERMISSION:
ret = access_tokenid_get_permission(file, uarg);
break;
case ACCESS_TOKENID_SET_PERMISSION:
ret = access_tokenid_set_permission(file, uarg);
break;
default:
pr_err("%s: access tokenid cmd error, cmd:%u\n", __func__, cmd);
ret = -EOPNOTSUPP;
}
return ret;
}
static const struct file_operations access_tokenid_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = access_tokenid_ioctl,
.compat_ioctl = access_tokenid_ioctl,
};
static struct miscdevice access_tokenid_device = {
.minor = MISC_DYNAMIC_MINOR,
.name = "access_token_id",
.fops = &access_tokenid_fops,
};
static int access_tokenid_init_module(void)
{
int err;
err = misc_register(&access_tokenid_device);
if (err < 0) {
pr_err("access_tokenid register failed\n");
return err;
}
g_cache = kmem_cache_create("access_token_node", sizeof(struct token_perm_node), 0, SLAB_HWCACHE_ALIGN, NULL);
if (g_cache == NULL) {
pr_err("access_tokenid kmem_cache create failed\n");
return -ENOMEM;
}
pr_info("access_tokenid init success\n");
return 0;
}
static void access_tokenid_exit_module(void)
{
kmem_cache_destroy(g_cache);
misc_deregister(&access_tokenid_device);
}
module_init(access_tokenid_init_module);
module_exit(access_tokenid_exit_module);