1
0
mirror of https://github.com/physwizz/a155-U-u1.git synced 2025-08-15 06:35:18 +00:00
Files
physwizz 99537be4e2 first
2024-03-11 06:53:12 +11:00

390 lines
10 KiB
C

/*
* Copyright (c) 2018 Samsung Electronics Co., Ltd. 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.
*/
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/spinlock.h>
#include <linux/version.h>
#include "include/defex_catch_list.h"
#include "include/defex_internal.h"
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0)
#include <linux/sched/mm.h>
#include <linux/sched/task.h>
#endif
#define MAX_PID_32 32768
#define DEFEX_MEM_CACHE_SIZE 32
#define DEFEX_MEM_CACHE_COUNT 3
#define CACHE_CRED_DATA 0
#define CACHE_CRED_DATA_ID 1
#define CACHE_HTABLE_ITEM 2
struct id_set {
unsigned int uid, fsuid, egid;
};
struct proc_cred_data {
unsigned short cred_flags;
unsigned short tcnt;
struct id_set default_ids;
struct id_set main_ids[];
};
struct hash_item_struct {
struct hlist_node node;
struct proc_cred_data *cred_data;
int id;
};
struct mem_cache_list {
atomic_t count;
char name[8];
struct kmem_cache *allocator;
void *mem_cache_array[DEFEX_MEM_CACHE_SIZE];
};
#ifdef DEFEX_PED_ENABLE
DECLARE_HASHTABLE(creds_hash, 15);
__visible_for_testing DEFINE_SPINLOCK(creds_hash_update_lock);
static struct proc_cred_data *creds_fast_hash[MAX_PID_32 + 1];
__visible_for_testing struct mem_cache_list mem_cache[DEFEX_MEM_CACHE_COUNT];
static int creds_fast_hash_ready __ro_after_init;
__visible_for_testing void mem_cache_alloc(void);
void __init creds_fast_hash_init(void)
{
unsigned int i;
static const int sizes[DEFEX_MEM_CACHE_COUNT] __initdata = {
sizeof(struct proc_cred_data),
sizeof(struct proc_cred_data) + sizeof(struct id_set),
sizeof(struct hash_item_struct)
};
hash_init(creds_hash);
for (i = 0; i <= MAX_PID_32; i++)
creds_fast_hash[i] = NULL;
for(i = 0; i < ARRAY_SIZE(sizes); i++) {
snprintf(mem_cache[i].name, sizeof(mem_cache[i].name), "defex%d", i);
mem_cache[i].allocator = kmem_cache_create(mem_cache[i].name, sizes[i], 0, 0, NULL);
}
for(i = 0; i < (DEFEX_MEM_CACHE_SIZE / 2); i++)
mem_cache_alloc();
creds_fast_hash_ready = 1;
}
int is_task_creds_ready(void)
{
return creds_fast_hash_ready;
}
__visible_for_testing inline void *mem_cache_get(int cache_number)
{
int n;
n = atomic_read(&mem_cache[cache_number].count);
if (n) {
atomic_dec(&mem_cache[cache_number].count);
return mem_cache[cache_number].mem_cache_array[n - 1];
}
return NULL;
}
__visible_for_testing inline void *mem_cache_reclaim(int cache_number, void *ptr)
{
int n;
n = atomic_read(&mem_cache[cache_number].count);
if (n < DEFEX_MEM_CACHE_SIZE) {
atomic_inc(&mem_cache[cache_number].count);
mem_cache[cache_number].mem_cache_array[n] = ptr;
ptr = NULL;
}
return ptr;
}
__visible_for_testing void mem_cache_alloc(void)
{
int mem_allocated = 0;
int i, n;
unsigned long flags;
void *mem_block[DEFEX_MEM_CACHE_COUNT];
for(i = 0; i < DEFEX_MEM_CACHE_COUNT; i++) {
mem_block[i] = NULL;
n = atomic_read(&mem_cache[i].count);
if (n < (DEFEX_MEM_CACHE_SIZE / 2)) {
mem_block[i] = kmem_cache_alloc(mem_cache[i].allocator, in_atomic() ? GFP_ATOMIC:GFP_KERNEL);
mem_allocated++;
}
}
if (!mem_allocated)
return;
spin_lock_irqsave(&creds_hash_update_lock, flags);
for(i = 0; i < DEFEX_MEM_CACHE_COUNT; i++) {
n = atomic_read(&mem_cache[i].count);
if (mem_block[i] && n < DEFEX_MEM_CACHE_SIZE) {
mem_cache[i].mem_cache_array[n] = mem_block[i];
mem_block[i] = NULL;
atomic_inc(&mem_cache[i].count);
mem_allocated--;
}
}
spin_unlock_irqrestore(&creds_hash_update_lock, flags);
if (!mem_allocated)
return;
for(i = 0; i < DEFEX_MEM_CACHE_COUNT; i++) {
if (mem_block[i]) {
kmem_cache_free(mem_cache[i].allocator, mem_block[i]);
}
}
}
__visible_for_testing struct proc_cred_data *get_cred_data(int id)
{
struct proc_cred_data *cred_data = NULL;
struct hash_item_struct *obj;
if (id < 0)
return NULL;
if (id <= MAX_PID_32) {
cred_data = creds_fast_hash[id];
} else {
hash_for_each_possible(creds_hash, obj, node, id) {
if (obj->id == id) {
cred_data = obj->cred_data;
break;
}
}
}
return cred_data;
}
__visible_for_testing struct proc_cred_data **get_cred_ptr(int id)
{
struct proc_cred_data **cred_ptr = NULL;
struct hash_item_struct *obj;
if (id < 0)
return NULL;
if (id <= MAX_PID_32) {
cred_ptr = &creds_fast_hash[id];
} else {
hash_for_each_possible(creds_hash, obj, node, id) {
if (obj->id == id) {
cred_ptr = &obj->cred_data;
break;
}
}
}
return cred_ptr;
}
__visible_for_testing void set_cred_data(int id, struct proc_cred_data **cred_ptr, struct proc_cred_data *cred_data)
{
struct hash_item_struct *obj;
if (id < 0)
return;
if (cred_ptr) {
*cred_ptr = cred_data;
} else {
if (id > MAX_PID_32) {
obj = mem_cache_get(CACHE_HTABLE_ITEM);
if (!obj)
return;
obj->id = id;
obj->cred_data = cred_data;
hash_add(creds_hash, &obj->node, id);
}
}
}
void get_task_creds(struct task_struct *p, unsigned int *uid_ptr, unsigned int *fsuid_ptr, unsigned int *egid_ptr, unsigned short *cred_flags_ptr)
{
struct proc_cred_data *cred_data, *thread_cred_data;
struct id_set *ids_ptr;
unsigned int uid = 0, fsuid = 0, egid = 0;
unsigned short cred_flags = CRED_FLAGS_PROOT;
unsigned long flags;
int tgid = p->tgid, pid = p->pid;
spin_lock_irqsave(&creds_hash_update_lock, flags);
cred_data = get_cred_data(tgid);
if (cred_data) {
if (tgid == pid) {
ids_ptr = (cred_data->cred_flags & CRED_FLAGS_MAIN_UPDATED) ? \
(&cred_data->main_ids[0]) : (&cred_data->default_ids);
} else {
if (cred_data->cred_flags & CRED_FLAGS_SUB_UPDATED) {
thread_cred_data = get_cred_data(pid);
if (thread_cred_data)
cred_data = thread_cred_data;
}
ids_ptr = &cred_data->default_ids;
}
GET_CREDS(ids_ptr, cred_data);
}
spin_unlock_irqrestore(&creds_hash_update_lock, flags);
*uid_ptr = uid;
*fsuid_ptr = fsuid;
*egid_ptr = egid;
*cred_flags_ptr = cred_flags;
}
int set_task_creds(struct task_struct *p, unsigned int uid, unsigned int fsuid, unsigned int egid, unsigned short cred_flags)
{
struct proc_cred_data *cred_data = NULL, *tmp_data, **cred_ptr;
struct id_set *ids_ptr;
unsigned long flags;
int err = -1, tgid = p->tgid, pid = p->pid;
void *free_buff = NULL;
mem_cache_alloc();
spin_lock_irqsave(&creds_hash_update_lock, flags);
/* Search for main proces's data */
cred_ptr = get_cred_ptr(tgid);
cred_data = (cred_ptr) ? (*cred_ptr) : NULL;
if (!cred_data) {
/* Not found? Allocate a new data */
cred_data = mem_cache_get(CACHE_CRED_DATA);
if (!cred_data)
goto set_finish;
cred_data->cred_flags = 0;
cred_data->tcnt = 1;
set_cred_data(tgid, cred_ptr, cred_data);
}
ids_ptr = &cred_data->default_ids;
if (cred_data->tcnt >= 2) {
if (tgid == pid) {
/* Allocate extended data for main process, copy and remove old data */
if (!(cred_data->cred_flags & CRED_FLAGS_MAIN_UPDATED)) {
cred_data->cred_flags |= CRED_FLAGS_MAIN_UPDATED;
tmp_data = mem_cache_get(CACHE_CRED_DATA_ID);
if (!tmp_data)
goto set_finish;
*tmp_data = *cred_data;
free_buff = mem_cache_reclaim(CACHE_CRED_DATA, cred_data);
cred_data = tmp_data;
set_cred_data(tgid, cred_ptr, cred_data);
}
ids_ptr = &cred_data->main_ids[0];
} else {
cred_data->cred_flags |= CRED_FLAGS_SUB_UPDATED;
/* Search for thread's data. Allocate, if not found */
cred_ptr = get_cred_ptr(pid);
cred_data = (cred_ptr) ? (*cred_ptr) : NULL;
if (!cred_data) {
cred_data = mem_cache_get(CACHE_CRED_DATA);
if (!cred_data)
goto set_finish;
set_cred_data(pid, cred_ptr, cred_data);
}
cred_data->cred_flags = 0;
ids_ptr = &cred_data->default_ids;
}
}
SET_CREDS(ids_ptr, cred_data);
err = 0;
set_finish:
spin_unlock_irqrestore(&creds_hash_update_lock, flags);
/* Free the pending pointer */
if (free_buff)
kmem_cache_free(mem_cache[CACHE_CRED_DATA].allocator, free_buff);
mem_cache_alloc();
return err;
}
void set_task_creds_tcnt(struct task_struct *p, int addition)
{
struct hash_item_struct *tgid_obj = NULL, *pid_obj = NULL;
struct proc_cred_data **cred_ptr, *tgid_cred_data = NULL, *pid_cred_data = NULL;
struct proc_cred_data *free_buff1 = NULL, *free_buff2 = NULL;
int tgid = p->tgid, pid = p->pid;
unsigned long flags;
spin_lock_irqsave(&creds_hash_update_lock, flags);
/* Remove the thread's data, if found */
if (tgid != pid && addition == -1) {
cred_ptr = get_cred_ptr(pid);
pid_cred_data = (cred_ptr) ? (*cred_ptr) : NULL;
if (pid_cred_data) {
*cred_ptr = NULL;
/* Return to pre-allocated pool, if possible */
free_buff1 = mem_cache_reclaim(CACHE_CRED_DATA, pid_cred_data);
}
/* Remove the thread's hash container */
if (cred_ptr && pid > MAX_PID_32) {
pid_obj = container_of(cred_ptr, struct hash_item_struct, cred_data);
hash_del(&pid_obj->node);
/* Return to pre-allocated pool, if possible */
pid_obj = mem_cache_reclaim(CACHE_HTABLE_ITEM, pid_obj);
}
}
/* Search for the main process's data */
cred_ptr = get_cred_ptr(tgid);
tgid_cred_data = (cred_ptr) ? (*cred_ptr) : NULL;
if (tgid_cred_data) {
tgid_cred_data->tcnt += addition;
/* No threads, remove process data */
if (!tgid_cred_data->tcnt) {
*cred_ptr = NULL;
/* Return to pre-allocated pool, if possible */
free_buff2 = mem_cache_reclaim((tgid_cred_data->cred_flags & CRED_FLAGS_MAIN_UPDATED) ? \
CACHE_CRED_DATA_ID : CACHE_CRED_DATA, tgid_cred_data);
/* Remove the process's hash container */
if (tgid > MAX_PID_32) {
tgid_obj = container_of(cred_ptr, struct hash_item_struct, cred_data);
hash_del(&tgid_obj->node);
/* Return to pre-allocated pool, if possible */
tgid_obj = mem_cache_reclaim(CACHE_HTABLE_ITEM, tgid_obj);
}
}
}
spin_unlock_irqrestore(&creds_hash_update_lock, flags);
/* Free all pending pointers */
if (free_buff1)
kmem_cache_free(mem_cache[CACHE_CRED_DATA].allocator, free_buff1);
if (free_buff2)
kmem_cache_free(mem_cache[(free_buff2->cred_flags & CRED_FLAGS_MAIN_UPDATED) ? \
CACHE_CRED_DATA_ID : CACHE_CRED_DATA].allocator, free_buff2);
if (pid_obj)
kmem_cache_free(mem_cache[CACHE_HTABLE_ITEM].allocator, pid_obj);
if (tgid_obj)
kmem_cache_free(mem_cache[CACHE_HTABLE_ITEM].allocator, tgid_obj);
return;
}
#else
int is_task_creds_ready(void)
{
return 0;
}
void set_task_creds_tcnt(struct task_struct *p, int addition)
{
(void)p;
(void)addition;
}
#endif /* DEFEX_PED_ENABLE */