Sindbad~EG File Manager
/**
@file
@brief Process (aka task) info storage
@details Copyright (c) 2017-2021 Acronis International GmbH
@author Ivan Matveev (ivan.matveev@acronis.com)
@since $Id: $
*/
#include "task_info_map.h"
#include "transport.h"
#include "compat.h"
#include "debug.h"
#include "memory.h"
#include <linux/mm.h> // get_task_exe_file()
static task_info_map_t task_info_maps[MAX_TASK_INFO_MAP_SIZE] = {0};
const char *task_status_to_string(task_status_t status)
{
switch (status)
{
#define CASE_TS_RETURN(t) case TS_##t: return #t
CASE_TS_RETURN(UNKNOWN);
CASE_TS_RETURN(IGNORE);
CASE_TS_RETURN(WHITE);
CASE_TS_RETURN(BLACK);
CASE_TS_RETURN(GREY);
#undef CASE_TS_RETURN
default: return "?";
}
}
static task_info_t *task_info_init(task_info_t *task_info, pid_t pid)
{
DPRINTF("task_info=%p pid=%i", task_info, pid);
RB_CLEAR_NODE(&task_info->rb_node);
task_info->pid = pid;
atomic_set(&task_info->ref_cnt, 1);
spin_lock_init(&task_info->spinlock);
task_info->status = TS_UNKNOWN;
/*
struct path {
struct vfsmount *mnt;
struct dentry *dentry;
};
*/
task_info->exe_path = (struct path){};
task_info->is_exe_path_changed = true;
INIT_LIST_HEAD(&task_info->exited_list_item);
return task_info;
}
static task_info_t *task_info_new(pid_t pid)
{
task_info_t *task_info = mem_alloc0(sizeof(task_info_t));
if (task_info) {
task_info_init(task_info, pid);
}
return task_info;
}
static task_info_t *task_info_ref(task_info_t *task_info)
{
atomic_inc(&task_info->ref_cnt);
return task_info;
}
static void task_info_free(task_info_t *task_info)
{
DPRINTF("task_info=%p", task_info);
// Note: 'path_put(&path={})' is safe
path_put(&task_info->exe_path);
mem_free(task_info);
}
static void task_info_unref(task_info_t *task_info)
{
DPRINTF("pid=%d ref_cnt=%d", task_info->pid, atomic_read(&task_info->ref_cnt));
if (atomic_dec_and_test(&task_info->ref_cnt)) {
task_info_free(task_info);
}
}
static int task_info_map_init(task_info_map_t *map)
{
spin_lock_init(&map->spinlock);
map->root = RB_ROOT;
spin_lock_init(&map->exited_list_spinlock);
INIT_LIST_HEAD(&map->exited_list);
map->is_acquired = false;
return 0;
}
void task_info_maps_init(void)
{
task_info_map_t *map;
unsigned char i = 0;
DPRINTF("task_info_maps_init");
for (i = 0; i < MAX_TASK_INFO_MAP_SIZE; i++)
{
map = &task_info_maps[i];
task_info_map_init(map);
}
}
static void task_info_map_clear_exited_list(task_info_map_t *map)
{
for (;;) {
task_info_t *task_info;
spin_lock(&map->exited_list_spinlock);
if (list_empty(&map->exited_list)) {
spin_unlock(&map->exited_list_spinlock);
return;
}
task_info = list_entry(map->exited_list.next, task_info_t, exited_list_item);
if (task_info)
{
list_del_init(&task_info->exited_list_item);
spin_unlock(&map->exited_list_spinlock);
if (atomic_read(&task_info->ref_cnt) != 1)
{
WPRINTF("task info [%d] ref_cnf[%d] is not equal to 1 when clearing", task_info->pid, atomic_read(&task_info->ref_cnt));
}
// undo 'inc' done for 'task_info_map.exited_list'
task_info_unref(task_info);
}
else
{
spin_unlock(&map->exited_list_spinlock);
}
}
}
static void task_info_map_clear(task_info_map_t *map)
{
struct rb_root root;
struct rb_node *node;
task_info_map_clear_exited_list(map);
spin_lock(&map->spinlock);
root = map->root;
map->root = RB_ROOT;
spin_unlock(&map->spinlock);
node = root.rb_node;
while (node) {
task_info_t *task_info;
spin_lock(&map->spinlock);
task_info = rb_entry(node, task_info_t, rb_node);
rb_erase(&task_info->rb_node, &root);
node = root.rb_node;
spin_unlock(&map->spinlock);
if (atomic_read(&task_info->ref_cnt) != 1)
{
WPRINTF("task info [%d] ref_cnf[%d] is not equal to 1 when clearing", task_info->pid, atomic_read(&task_info->ref_cnt));
}
task_info_unref(task_info);
}
}
void task_info_maps_clear(void)
{
task_info_map_t *map;
unsigned char i = 0;
DPRINTF("task_info_maps_clear");
for (i = 0; i < MAX_TASK_INFO_MAP_SIZE; i++)
{
map = &task_info_maps[i];
task_info_map_clear(map);
}
}
// Assume that each transport only use it's unique valid map_id after acquiring
unsigned char acquire_task_info_map(void)
{
unsigned char map_id = MAX_TASK_INFO_MAP_SIZE + 1;
unsigned char i = 0;
task_info_map_t *map = NULL;
for (i = 0; i < MAX_TASK_INFO_MAP_SIZE; i++)
{
spin_lock(&task_info_maps[i].spinlock);
if (!task_info_maps[i].is_acquired)
{
map_id = i;
task_info_maps[i].is_acquired = true;
map = &task_info_maps[i];
spin_unlock(&task_info_maps[i].spinlock);
break;
}
spin_unlock(&task_info_maps[i].spinlock);
}
if (map_id < MAX_TASK_INFO_MAP_SIZE)
{
DPRINTF_LEVEL(LOG_LEVEL_DEBUG1, "Acquire task_info_map ok: %u", map_id);
}
else
{
EPRINTF("Failed to acquire task_info_map");
}
return map_id;
}
void release_task_info_map(unsigned char map_id)
{
task_info_map_t *map;
struct list_head task_info_clear_list;
if (map_id >= MAX_TASK_INFO_MAP_SIZE)
{
return;
}
spin_lock(&task_info_maps[map_id].spinlock);
if (!task_info_maps[map_id].is_acquired)
{
spin_unlock(&task_info_maps[map_id].spinlock);
return;
}
INIT_LIST_HEAD(&task_info_clear_list);
map = &task_info_maps[map_id];
map->is_acquired = false;
{
struct rb_root root;
struct rb_node *node;
root = map->root;
map->root = RB_ROOT;
node = root.rb_node;
while (node)
{
task_info_t *task_info = rb_entry(node, task_info_t, rb_node);
rb_erase(&task_info->rb_node, &root);
list_add_tail(&task_info->clear_list_item, &task_info_clear_list);
node = root.rb_node;
}
}
spin_unlock(&task_info_maps[map_id].spinlock);
for (;;)
{
task_info_t *task_info;
if (list_empty(&task_info_clear_list))
{
break;
}
task_info = list_entry(task_info_clear_list.next, task_info_t, clear_list_item);
if (task_info)
{
list_del_init(&task_info->clear_list_item);
if (atomic_read(&task_info->ref_cnt) != 1)
{
WPRINTF("task info [%d] ref_cnf[%d] is not equal to 1 when clearing", task_info->pid, atomic_read(&task_info->ref_cnt));
}
task_info_unref(task_info);
}
}
task_info_map_clear_exited_list(map);
DPRINTF_LEVEL(LOG_LEVEL_DEBUG1, "Release task_info_map: %u", map_id);
}
static task_info_t *task_info_lookup(task_info_map_t *map, pid_t pid)
{
struct rb_node *node;
task_info_t *task_info = NULL;
DPRINTF("pid=%d", pid);
spin_lock(&map->spinlock);
if (map->is_acquired == false)
{
spin_unlock(&map->spinlock);
return NULL;
}
node = map->root.rb_node;
while (node) {
task_info_t *node_task_info = rb_entry(node, task_info_t, rb_node);
pid_t node_pid = node_task_info->pid;
if (pid < node_pid) {
node = node->rb_left;
} else if (pid > node_pid) {
node = node->rb_right;
} else {
task_info = task_info_ref(node_task_info);
break;
}
}
spin_unlock(&map->spinlock);
DPRINTF_RATELIMITED("task_info=%p pid=%d", task_info, pid);
return task_info;
}
static task_info_t *task_info_map_insert(task_info_map_t *map, task_info_t *new_task_info)
{
pid_t pid = new_task_info->pid;
struct rb_node *parent = NULL;
struct rb_node **link;
DPRINTF_RATELIMITED("new_task_info=%p pid=%i", new_task_info, pid);
spin_lock(&map->spinlock);
if (map->is_acquired == false)
{
spin_unlock(&map->spinlock);
return NULL;
}
link = &(map->root.rb_node);
while (*link) {
task_info_t *node_task_info;
pid_t node_pid;
parent = *link;
node_task_info = rb_entry(parent, task_info_t, rb_node);
node_pid = node_task_info->pid;
if (pid < node_pid) {
link = &parent->rb_left;
} else if (pid > node_pid) {
link = &parent->rb_right;
} else {
// collision
DPRINTF_RATELIMITED("collision");
task_info_ref(node_task_info);
spin_unlock(&map->spinlock);
return node_task_info;
}
}
// do 'inc' for 'map->root'
task_info_ref(new_task_info);
rb_link_node(&new_task_info->rb_node, parent, link);
rb_insert_color(&new_task_info->rb_node, &map->root);
DPRINTF_RATELIMITED("inserted");
spin_unlock(&map->spinlock);
return new_task_info;
}
// Warning: May block!
static task_info_t *task_info_add(task_info_map_t *map, pid_t pid)
{
task_info_t *task_info;
task_info_t *new_task_info;
DPRINTF_RATELIMITED("pid=%d", pid);
task_info = task_info_lookup(map, pid);
if (task_info) {
DPRINTF_RATELIMITED("pid=%i is already in the map (task_info=%p)", pid, task_info);
return task_info;
}
new_task_info = task_info_new(pid);
if (!new_task_info) {
DPRINTF_RATELIMITED("out of memory");
return NULL;
}
task_info = task_info_map_insert(map, new_task_info);
if (task_info != new_task_info) {
// collision
DPRINTF_RATELIMITED("collision");
task_info_unref(new_task_info);
}
return task_info;
}
static int task_info_map_del(task_info_map_t *map, pid_t pid)
{
task_info_t *task_info;
DPRINTF("pid=%d", pid);
task_info = task_info_lookup(map, pid);
if (!task_info) {
// task info may be deleted in task_info_map_clear_exited_list()
// so here no need to print warning
DPRINTF("pid=%d is missing in the map", pid);
return -ENOENT;
}
spin_lock(&map->spinlock);
if (map->root.rb_node != NULL)
{
rb_erase(&task_info->rb_node, &map->root);
}
spin_unlock(&map->spinlock);
// undo 'inc' done for 'map->root'
task_info_unref(task_info);
// undo 'inc' done in 'task_info_lookup()'
task_info_unref(task_info);
DPRINTF("pid=%d", pid);
return 0;
}
static void task_info_map_remove(task_info_map_t *map, task_info_t *task_info)
{
spin_lock(&map->spinlock);
if (map->root.rb_node != NULL)
{
rb_erase(&task_info->rb_node, &map->root);
}
if (atomic_read(&task_info->ref_cnt) >= 2)
{
// undo 'inc' done for 'map->root'
task_info_unref(task_info);
}
spin_unlock(&map->spinlock);
}
int task_info_map_del_by_map_id(unsigned char map_id, pid_t pid)
{
task_info_map_t *map = NULL;
int ret = 0;
if (map_id >= MAX_TASK_INFO_MAP_SIZE)
{
return ENOENT;
}
if (!task_info_maps[map_id].is_acquired)
{
return ENOENT;
}
map = &task_info_maps[map_id];
ret = task_info_map_del(map, pid);
return ret;
}
static task_info_t *get_task_info_by_map_id(unsigned char map_id, pid_t pid)
{
task_info_map_t *map = NULL;
task_info_t *task_info = NULL;
if (map_id >= MAX_TASK_INFO_MAP_SIZE)
{
return NULL;
}
if (!task_info_maps[map_id].is_acquired)
{
return NULL;
}
map = &task_info_maps[map_id];
task_info = task_info_lookup(map, pid);
return task_info;
}
// Warning: May block!
int task_info_status_set_by_map_id(unsigned char map_id, pid_t pid, task_status_t status)
{
task_info_t *task_info = NULL;
task_info_map_t *map = NULL;
DPRINTF_LEVEL(LOG_LEVEL_DEBUG1, "map_id:%u pid=%i status=%i/%s", map_id, pid, status, task_status_to_string(status));
if (map_id >= MAX_TASK_INFO_MAP_SIZE)
{
return ENOENT;
}
if (!task_info_maps[map_id].is_acquired)
{
return ENOENT;
}
map = &task_info_maps[map_id];
task_info = task_info_add(map, pid);
if (!task_info) {
WPRINTF("'%s(pid=%i)' failure", "task_info_add", pid);
return -ENOMEM;
}
spin_lock(&task_info->spinlock);
task_info->status = status;
spin_unlock(&task_info->spinlock);
task_info_unref(task_info);
return 0;
}
void task_info_map_on_exit_event(pid_t tgid, pid_t pid)
{
unsigned char i = 0;
task_info_t *task_info = NULL;
task_info_map_t *map = NULL;
for (i = 0; i < MAX_TASK_INFO_MAP_SIZE; i++)
{
task_info = NULL;
if (!task_info_maps[i].is_acquired)
{
continue;
}
map = &task_info_maps[i];
task_info = task_info_lookup(map, pid);
if (!task_info) {
DPRINTF("%u:%u is missing in 'map'", tgid, pid);
} else {
spin_lock(&map->exited_list_spinlock);
if (!list_empty(&task_info->exited_list_item)) {
DPRINTF("%u:%u is already in 'exited list'", tgid, pid);
} else {
// do 'inc' for 'map->exited_list'
task_info_ref(task_info);
list_add_tail(&task_info->exited_list_item, &map->exited_list);
DPRINTF("%u:%u task_info=%p is added to 'exited list'", tgid, pid, task_info);
}
spin_unlock(&map->exited_list_spinlock);
task_info_unref(task_info);
task_info_map_remove(map, task_info);
}
}
}
void task_info_map_clear_exited_list_by_map_id(unsigned char map_id)
{
task_info_map_t *map = NULL;
if (map_id >= MAX_TASK_INFO_MAP_SIZE)
{
return;
}
if (!task_info_maps[map_id].is_acquired)
{
return;
}
map = &task_info_maps[map_id];
task_info_map_clear_exited_list(map);
return;
}
int check_exec_with_task_info(pid_t tgid, struct path exe_path)
{
unsigned char i = 0;
task_info_t *task_info = NULL;
task_info_map_t *map = NULL;
bool is_exe_path_changed = false;
for (i = 0; i < MAX_TASK_INFO_MAP_SIZE; i++)
{
task_info = NULL;
if (!task_info_maps[i].is_acquired)
{
continue;
}
map = &task_info_maps[i];
task_info = task_info_add(map, tgid);
if (task_info)
{
struct path old_path = (struct path){};
spin_lock(&task_info->spinlock);
if (path_equal(&task_info->exe_path, &exe_path))
{
}
else
{
task_info->is_exe_path_changed = true;
old_path = task_info->exe_path;
task_info->exe_path = exe_path;
path_get(&task_info->exe_path);
}
if(task_info->is_exe_path_changed){
is_exe_path_changed = true;
}
spin_unlock(&task_info->spinlock);
// it's ok to call path_put when old_path == {0}
path_put(&old_path); // shouldn't call under spinlock, because might sleep
task_info_unref(task_info);
}
else
{
EPRINTF("Failed to add task info");
}
}
if (is_exe_path_changed)
{
return 1;
}
return 0;
}
static bool msg_should_skip(task_info_t *task_info, msg_type_img_t msg_type)
{
bool ret = false;
if (msg_type == MT_EXEC)
{
}
else if (msg_type == MT_EXIT || msg_type == MT_FORK ||
msg_type == MT_FILE_PRE_CREATE || msg_type == MT_FILE_CREATE_EX ||
msg_type == MT_PRE_RENAME_EX || msg_type == MT_RENAME_EX || msg_type == MT_RENAME ||
msg_type == MT_PRE_UNLINK_EX || msg_type == MT_UNLINK_EX || msg_type == MT_UNLINK)
{
// for event in EXIT, FORK, CREATE, RENAME and UNLINK, always send
}
else
{
// For event not in EXIT, FORK, CREATE, RENAME and UNLINK, check status is TS_WHITE or TS_IGNORE
if (task_info->status == TS_IGNORE || task_info->status == TS_WHITE)
{
ret = true;
}
}
return ret;
}
bool is_exe_path_changed(task_info_t *task_info)
{
struct file *exe_file;
struct path exe_path;
bool ret = false;
exe_file = get_task_exe_file_compat(current);
if (!exe_file)
{
return false;
}
path_get(&exe_file->f_path);
exe_path = exe_file->f_path;
fput(exe_file);
spin_lock(&task_info->spinlock);
if (!path_equal(&task_info->exe_path, &exe_path))
{
ret = true;
}
spin_unlock(&task_info->spinlock);
path_put(&exe_path);
return ret;
}
bool sys_call_fs_events_should_skip(pid_t pid, uint64_t events_mask)
{
unsigned char i = 0;
task_info_t *task_info = NULL;
task_info_map_t *map = NULL;
bool ret = true;
for (i = 0; i < MAX_TASK_INFO_MAP_SIZE; i++)
{
msg_type_img_t msg_type;
task_info = NULL;
if (!task_info_maps[i].is_acquired)
{
continue;
}
map = &task_info_maps[i];
task_info = task_info_lookup(map, pid);
if (!task_info)
{
return false;
}
if (is_exe_path_changed(task_info))
{
task_info_unref(task_info);
return false;
}
for (msg_type = MT_EXEC; msg_type < MT_MAX; msg_type++)
{
uint64_t event_mask = MSG_TYPE_TO_EVENT_MASK(msg_type);
if (event_mask & events_mask)
{
if (!msg_should_skip(task_info, msg_type))
{
ret = false;
break;
}
}
}
task_info_unref(task_info);
if (!ret)
{
break;
}
}
return ret;
}
static bool should_send(unsigned char map_id, msg_type_img_t msg_type)
{
bool ret = true;
task_info_t *task_info = NULL;
task_info = get_task_info_by_map_id(map_id, current->tgid);
if (task_info == NULL)
{
return false;
}
if (msg_type == MT_EXEC)
{
spin_lock(&task_info->spinlock);
if (task_info->is_exe_path_changed)
{
task_info->is_exe_path_changed = false;
}
else
{
ret = false;
}
spin_unlock(&task_info->spinlock);
}
else if (msg_type == MT_EXIT || msg_type == MT_FORK ||
msg_type == MT_FILE_PRE_CREATE || msg_type == MT_FILE_CREATE_EX ||
msg_type == MT_PRE_RENAME_EX || msg_type == MT_RENAME_EX || msg_type == MT_RENAME ||
msg_type == MT_PRE_UNLINK_EX || msg_type == MT_UNLINK_EX || msg_type == MT_UNLINK)
{
// for event in EXIT, FORK, CREATE, RENAME and UNLINK, always send
}
else
{
// For event not in EXIT, FORK, CREATE, RENAME and UNLINK, check status is TS_WHITE or TS_IGNORE
if (task_info->status == TS_IGNORE || task_info->status == TS_WHITE)
{
ret = false;
}
}
task_info_unref(task_info);
return ret;
}
void get_should_send_task_info_map_ids(bool *should_send_map_ids, msg_type_img_t msg_type)
{
int i;
for (i = 0; i < MAX_TASK_INFO_MAP_SIZE; i++)
{
should_send_map_ids[i] = false;
if (should_send(i, msg_type))
{
should_send_map_ids[i] = true;
}
}
}
Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists