| /* |
| * 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); |
| } |