blob: fdb6fac0911187f4f95d4a1903a274ecb0f6f5ce [file] [log] [blame]
/*
* Five Event interface
*
* Copyright (C) 2018 Samsung Electronics, Inc.
* Ivan Vorobiov, <i.vorobiov@samsung.com>
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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 "five_hooks.h"
#include "five_porting.h"
#include <linux/fs.h>
#include <linux/slab.h>
#define call_void_hook(FUNC, ...) \
do { \
struct five_hook_list *P; \
\
list_for_each_entry(P, &five_hook_heads.FUNC, list) \
P->hook.FUNC(__VA_ARGS__); \
} while (0)
struct five_hook_heads five_hook_heads = {
.file_processed =
LIST_HEAD_INIT(five_hook_heads.file_processed),
.file_skipped =
LIST_HEAD_INIT(five_hook_heads.file_skipped),
.file_signed =
LIST_HEAD_INIT(five_hook_heads.file_signed),
.task_forked =
LIST_HEAD_INIT(five_hook_heads.task_forked),
.integrity_reset =
LIST_HEAD_INIT(five_hook_heads.integrity_reset),
.integrity_reset2 =
LIST_HEAD_INIT(five_hook_heads.integrity_reset2),
};
enum five_hook_event {
FILE_PROCESSED,
FILE_SKIPPED,
FILE_SIGNED,
TASK_FORKED,
INTEGRITY_RESET
};
struct hook_wq_event {
enum five_hook_event event;
union {
struct {
struct task_struct *task;
enum task_integrity_value tint_value;
struct file *file;
void *xattr;
size_t xattr_size;
int result;
} processed;
struct {
struct task_struct *task;
enum task_integrity_value tint_value;
struct file *file;
} skipped;
struct {
struct task_struct *parent;
enum task_integrity_value parent_tint_value;
struct task_struct *child;
enum task_integrity_value child_tint_value;
} forked;
struct {
struct task_struct *task;
struct file *file;
enum task_integrity_reset_cause cause;
} reset;
};
};
static void hook_wq_event_destroy(struct hook_wq_event *event)
{
switch (event->event) {
case FILE_PROCESSED: {
fput(event->processed.file);
put_task_struct(event->processed.task);
kfree(event->processed.xattr);
break;
}
case FILE_SKIPPED: {
fput(event->skipped.file);
put_task_struct(event->skipped.task);
break;
}
case FILE_SIGNED: {
fput(event->processed.file);
put_task_struct(event->processed.task);
kfree(event->processed.xattr);
break;
}
case TASK_FORKED: {
put_task_struct(event->forked.parent);
put_task_struct(event->forked.child);
break;
}
case INTEGRITY_RESET: {
if (event->reset.file)
fput(event->reset.file);
put_task_struct(event->reset.task);
break;
}
}
}
struct hook_wq_context {
struct work_struct data_work;
struct hook_wq_event payload;
};
static struct workqueue_struct *g_hook_workqueue;
static void hook_handler(struct work_struct *in_data)
{
struct hook_wq_event *event;
struct hook_wq_context *context = container_of(in_data,
struct hook_wq_context, data_work);
if (unlikely(!context))
return;
event = &context->payload;
switch (event->event) {
case FILE_PROCESSED: {
call_void_hook(file_processed,
event->processed.task,
event->processed.tint_value,
event->processed.file,
event->processed.xattr,
event->processed.xattr_size,
event->processed.result);
break;
}
case FILE_SKIPPED: {
call_void_hook(file_skipped,
event->skipped.task,
event->skipped.tint_value,
event->skipped.file);
break;
}
case FILE_SIGNED: {
call_void_hook(file_signed,
event->processed.task,
event->processed.tint_value,
event->processed.file,
event->processed.xattr,
event->processed.xattr_size,
event->processed.result);
break;
}
case TASK_FORKED: {
call_void_hook(task_forked,
event->forked.parent,
event->forked.parent_tint_value,
event->forked.child,
event->forked.child_tint_value);
break;
}
case INTEGRITY_RESET: {
call_void_hook(integrity_reset,
event->reset.task);
call_void_hook(integrity_reset2, event->reset.task,
event->reset.file, event->reset.cause);
break;
}
}
hook_wq_event_destroy(event);
kfree(context);
}
static int __push_event(struct hook_wq_event *event, gfp_t flags)
{
struct hook_wq_context *context;
if (!g_hook_workqueue)
return -ENAVAIL;
context = kmalloc(sizeof(struct hook_wq_context), flags);
if (unlikely(!context))
return -ENOMEM;
context->payload = *event;
INIT_WORK(&context->data_work, hook_handler);
return queue_work(g_hook_workqueue, &context->data_work) ? 0 : 1;
}
void five_hook_file_processed(struct task_struct *task,
struct file *file, void *xattr,
size_t xattr_size, int result)
{
struct hook_wq_event event = {0};
event.event = FILE_PROCESSED;
get_task_struct(task);
get_file(file);
event.processed.task = task;
event.processed.tint_value = task_integrity_read(task->integrity);
event.processed.file = file;
/*
* xattr parameters are optional, because FIVE could get results
* from cache where xattr absents, so we may ignore kmemdup errors
*/
if (xattr) {
event.processed.xattr = kmemdup(xattr, xattr_size, GFP_KERNEL);
if (event.processed.xattr)
event.processed.xattr_size = xattr_size;
}
event.processed.result = result;
if (__push_event(&event, GFP_KERNEL) < 0)
hook_wq_event_destroy(&event);
}
void five_hook_file_signed(struct task_struct *task,
struct file *file, void *xattr,
size_t xattr_size, int result)
{
struct hook_wq_event event = {0};
event.event = FILE_SIGNED;
get_task_struct(task);
get_file(file);
event.processed.task = task;
event.processed.tint_value = task_integrity_read(task->integrity);
event.processed.file = file;
/* xattr parameters are optional, so we may ignore kmemdup errors */
if (xattr) {
event.processed.xattr = kmemdup(xattr, xattr_size, GFP_KERNEL);
if (event.processed.xattr)
event.processed.xattr_size = xattr_size;
}
event.processed.result = result;
if (__push_event(&event, GFP_KERNEL) < 0)
hook_wq_event_destroy(&event);
}
void five_hook_file_skipped(struct task_struct *task, struct file *file)
{
struct hook_wq_event event = {0};
event.event = FILE_SKIPPED;
get_task_struct(task);
get_file(file);
event.skipped.task = task;
event.skipped.tint_value = task_integrity_read(task->integrity);
event.skipped.file = file;
if (__push_event(&event, GFP_KERNEL) < 0)
hook_wq_event_destroy(&event);
}
void five_hook_task_forked(struct task_struct *parent,
struct task_struct *child)
{
struct hook_wq_event event = {0};
event.event = TASK_FORKED;
get_task_struct(parent);
get_task_struct(child);
event.forked.parent = parent;
event.forked.parent_tint_value = task_integrity_read(parent->integrity);
event.forked.child = child;
event.forked.child_tint_value = task_integrity_read(child->integrity);
if (__push_event(&event, GFP_ATOMIC) < 0)
hook_wq_event_destroy(&event);
}
int five_hook_wq_init(void)
{
g_hook_workqueue = alloc_ordered_workqueue("%s",
WQ_MEM_RECLAIM | WQ_FREEZABLE, "five_hook_wq");
if (!g_hook_workqueue)
return -ENOMEM;
return 0;
}
void five_hook_integrity_reset(struct task_struct *task,
struct file *file,
enum task_integrity_reset_cause cause)
{
struct hook_wq_event event = {0};
if (task == NULL)
return;
event.event = INTEGRITY_RESET;
get_task_struct(task);
if (file)
get_file(file);
event.reset.task = task;
event.reset.file = file;
event.reset.cause = cause;
if (__push_event(&event, GFP_KERNEL) < 0)
hook_wq_event_destroy(&event);
}