| /* |
| * PROCA LSM module |
| * |
| * 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 "proca_identity.h" |
| #include "proca_certificate.h" |
| #include "proca_task_descr.h" |
| #include "proca_table.h" |
| #include "proca_log.h" |
| #include "proca_config.h" |
| |
| #include "five_hooks.h" |
| |
| #include <linux/module.h> |
| #include <linux/file.h> |
| #include <linux/task_integrity.h> |
| #include <linux/xattr.h> |
| #include <linux/fs.h> |
| #include <linux/proca.h> |
| |
| #include "proca_porting.h" |
| |
| static void proca_task_free_hook(struct task_struct *task); |
| |
| static void proca_file_free_security_hook(struct file *file); |
| |
| #ifdef LINUX_LSM_SUPPORTED |
| static struct security_hook_list proca_ops[] = { |
| LSM_HOOK_INIT(task_free, proca_task_free_hook), |
| LSM_HOOK_INIT(file_free_security, proca_file_free_security_hook), |
| }; |
| #endif |
| |
| static void proca_hook_task_forked(struct task_struct *parent, |
| enum task_integrity_value parent_tint_value, |
| struct task_struct *child, |
| enum task_integrity_value child_tint_value); |
| |
| static void proca_hook_file_processed(struct task_struct *task, |
| enum task_integrity_value tint_value, |
| struct file *file, void *xattr, |
| size_t xattr_size, int result); |
| |
| static void proca_hook_file_signed(struct task_struct *task, |
| enum task_integrity_value tint_value, |
| struct file *file, void *xattr, |
| size_t xattr_size, int result); |
| |
| static void proca_hook_file_skipped(struct task_struct *task, |
| enum task_integrity_value tint_value, |
| struct file *file); |
| |
| static struct five_hook_list five_ops[] = { |
| FIVE_HOOK_INIT(task_forked, proca_hook_task_forked), |
| FIVE_HOOK_INIT(file_processed, proca_hook_file_processed), |
| FIVE_HOOK_INIT(file_signed, proca_hook_file_signed), |
| FIVE_HOOK_INIT(file_skipped, proca_hook_file_skipped), |
| }; |
| |
| static struct proca_table g_proca_table; |
| struct proca_config g_proca_config; |
| |
| static int g_proca_inited; |
| |
| static int read_xattr(struct dentry *dentry, const char *name, |
| char **xattr_value) |
| { |
| ssize_t ret; |
| void *buffer = NULL; |
| |
| dentry = d_real_comp(dentry); |
| |
| *xattr_value = NULL; |
| ret = __vfs_getxattr(dentry, dentry->d_inode, name, NULL, 0); |
| if (ret <= 0) |
| return 0; |
| |
| buffer = kmalloc(ret + 1, GFP_NOFS); |
| if (!buffer) |
| return 0; |
| |
| ret = __vfs_getxattr(dentry, dentry->d_inode, name, |
| buffer, ret + 1); |
| |
| if (ret <= 0) { |
| ret = 0; |
| kfree(buffer); |
| } else { |
| *xattr_value = buffer; |
| } |
| |
| return ret; |
| } |
| |
| static struct proca_task_descr *prepare_unsigned_proca_task_descr( |
| struct task_struct *task, |
| struct file *file) |
| { |
| struct proca_identity ident; |
| struct proca_task_descr *task_descr = NULL; |
| |
| if (init_proca_identity(&ident, file, NULL, 0, NULL)) |
| return task_descr; |
| |
| task_descr = create_proca_task_descr(task, &ident); |
| if (!task_descr) |
| deinit_proca_identity(&ident); |
| |
| return task_descr; |
| } |
| |
| static struct proca_task_descr *prepare_proca_task_descr( |
| struct task_struct *task, struct file *file, |
| const enum task_integrity_value tint_value, |
| void *xattr, size_t xattr_size, |
| char **out_five_xattr_value) |
| { |
| struct proca_certificate parsed_cert; |
| struct proca_identity ident; |
| char *pa_xattr_value = NULL; |
| size_t pa_xattr_size; |
| char *five_sign_xattr_value = NULL; |
| size_t five_sign_xattr_size; |
| struct proca_task_descr *task_descr = NULL; |
| |
| pa_xattr_size = read_xattr(file->f_path.dentry, |
| XATTR_NAME_PA, &pa_xattr_value); |
| |
| if (!pa_xattr_value) { |
| if (task_integrity_value_allow_sign(tint_value)) |
| return prepare_unsigned_proca_task_descr(task, file); |
| else |
| return NULL; |
| } |
| |
| if (xattr) { |
| five_sign_xattr_value = kmemdup( |
| xattr, xattr_size, GFP_KERNEL); |
| five_sign_xattr_size = xattr_size; |
| } else { |
| five_sign_xattr_size = read_xattr(file->f_path.dentry, |
| XATTR_NAME_FIVE, |
| &five_sign_xattr_value); |
| } |
| |
| if (!five_sign_xattr_value) { |
| PROCA_INFO_LOG( |
| "Failed to read five xattr, pid %d, integrity 0x%x\n", |
| task->pid, tint_value); |
| goto pa_xattr_cleanup; |
| } |
| |
| if (parse_proca_certificate(pa_xattr_value, pa_xattr_size, |
| &parsed_cert)) |
| goto five_xattr_cleanup; |
| |
| if (!is_certificate_relevant_to_task(&parsed_cert, task)) |
| goto proca_cert_cleanup; |
| |
| PROCA_DEBUG_LOG("%s xattr was found for task %d\n", XATTR_NAME_PA, |
| task->pid); |
| |
| if (!compare_with_five_signature(&parsed_cert, five_sign_xattr_value, |
| five_sign_xattr_size)) { |
| PROCA_INFO_LOG( |
| "Comparison with five signature for %s failed.\n", |
| parsed_cert.app_name); |
| goto proca_cert_cleanup; |
| } |
| |
| if (init_proca_identity(&ident, file, |
| pa_xattr_value, pa_xattr_size, |
| &parsed_cert)) |
| goto proca_cert_cleanup; |
| |
| task_descr = create_proca_task_descr(task, &ident); |
| if (!task_descr) |
| goto proca_identity_cleanup; |
| |
| *out_five_xattr_value = five_sign_xattr_value; |
| |
| return task_descr; |
| |
| proca_identity_cleanup:; |
| deinit_proca_identity(&ident); |
| |
| proca_cert_cleanup:; |
| deinit_proca_certificate(&parsed_cert); |
| |
| five_xattr_cleanup:; |
| kfree(five_sign_xattr_value); |
| |
| pa_xattr_cleanup:; |
| kfree(pa_xattr_value); |
| |
| return NULL; |
| } |
| |
| static bool is_bprm(struct task_struct *task, struct file *old_file, |
| struct file *new_file) |
| { |
| struct file *exe; |
| bool res; |
| |
| exe = get_task_exe_file(task); |
| if (!exe) |
| return false; |
| |
| res = locks_inode(exe) == locks_inode(new_file) && |
| locks_inode(old_file) != locks_inode(new_file); |
| |
| fput(exe); |
| return res; |
| } |
| |
| static struct file *get_real_file(struct file *file) |
| { |
| if (locks_inode(file)->i_sb->s_magic == OVERLAYFS_SUPER_MAGIC && |
| file->private_data) |
| file = (struct file *)file->private_data; |
| |
| return file; |
| } |
| |
| static void proca_hook_file_processed(struct task_struct *task, |
| enum task_integrity_value tint_value, |
| struct file *file, void *xattr, |
| size_t xattr_size, int result) |
| { |
| char *five_xattr_value = NULL; |
| bool need_set_five = false; |
| struct proca_task_descr *target_task_descr = NULL; |
| |
| file = get_real_file(file); |
| if (!file) |
| return; |
| |
| if (task->flags & PF_KTHREAD) |
| return; |
| |
| target_task_descr = proca_table_get_by_task(&g_proca_table, task); |
| if (target_task_descr && |
| is_bprm(task, target_task_descr->proca_identity.file, file)) { |
| PROCA_DEBUG_LOG( |
| "Task descr for task %d already exists before exec\n", |
| task->pid); |
| |
| proca_table_remove_task_descr(&g_proca_table, |
| target_task_descr); |
| destroy_proca_task_descr(target_task_descr); |
| target_task_descr = NULL; |
| } |
| |
| if (!target_task_descr) { |
| target_task_descr = prepare_proca_task_descr( |
| task, file, tint_value, |
| xattr, xattr_size, |
| &five_xattr_value); |
| if (target_task_descr) |
| proca_table_add_task_descr(&g_proca_table, |
| target_task_descr); |
| } |
| |
| need_set_five |= task_integrity_value_allow_sign(tint_value); |
| |
| if ((five_xattr_value || need_set_five) && !file->f_signature) { |
| if (!five_xattr_value && xattr) |
| five_xattr_value = kmemdup( |
| xattr, xattr_size, GFP_KERNEL); |
| else if (!five_xattr_value && !xattr) |
| read_xattr(file->f_path.dentry, XATTR_NAME_FIVE, |
| &five_xattr_value); |
| file->f_signature = five_xattr_value; |
| } else if (five_xattr_value && file->f_signature) { |
| kfree(five_xattr_value); |
| } |
| } |
| |
| static void proca_hook_file_signed(struct task_struct *task, |
| enum task_integrity_value tint_value, |
| struct file *file, void *xattr, |
| size_t xattr_size, int result) |
| { |
| char *xattr_value = NULL; |
| |
| if (!file || result != 0 || !xattr) |
| return; |
| |
| file = get_real_file(file); |
| if (!file) |
| return; |
| |
| kfree(file->f_signature); |
| |
| xattr_value = kmemdup(xattr, xattr_size, GFP_KERNEL); |
| file->f_signature = xattr_value; |
| } |
| |
| static void proca_hook_file_skipped(struct task_struct *task, |
| enum task_integrity_value tint_value, |
| struct file *file) |
| { |
| char *xattr_value = NULL; |
| struct dentry *dentry; |
| |
| if (!task || !file) |
| return; |
| |
| if (file->f_signature) |
| return; |
| |
| file = get_real_file(file); |
| if (!file) |
| return; |
| |
| dentry = file->f_path.dentry; |
| |
| if (task_integrity_value_allow_sign(tint_value) && |
| read_xattr(dentry, XATTR_NAME_FIVE, &xattr_value) != 0) { |
| // PROCA get FIVE signature for runtime provisioning |
| // from kernel, so |
| // we should set f_signature for each signed file |
| file->f_signature = xattr_value; |
| } else if (__vfs_getxattr(dentry, dentry->d_inode, XATTR_NAME_PA, |
| NULL, 0) > 0) { |
| // Workaround for Android applications. |
| // If file has user.pa - check it. |
| five_file_verify(task, file); |
| } |
| } |
| |
| static void proca_hook_task_forked(struct task_struct *parent, |
| enum task_integrity_value parent_tint_value, |
| struct task_struct *child, |
| enum task_integrity_value child_tint_value) |
| { |
| struct proca_task_descr *target_task_descr = NULL; |
| struct proca_identity ident; |
| |
| if (!parent || !child) |
| return; |
| |
| target_task_descr = proca_table_get_by_task(&g_proca_table, parent); |
| if (!target_task_descr) |
| return; |
| |
| PROCA_DEBUG_LOG("Going to clone proca identity from task %d to %d\n", |
| parent->pid, child->pid); |
| |
| if (proca_identity_copy(&ident, &target_task_descr->proca_identity)) |
| return; |
| |
| target_task_descr = create_proca_task_descr(child, &ident); |
| if (!target_task_descr) { |
| deinit_proca_identity(&ident); |
| return; |
| } |
| |
| proca_table_add_task_descr(&g_proca_table, target_task_descr); |
| } |
| |
| static void proca_task_free_hook(struct task_struct *task) |
| { |
| struct proca_task_descr *target_task_descr = NULL; |
| |
| target_task_descr = proca_table_remove_by_task(&g_proca_table, task); |
| |
| destroy_proca_task_descr(target_task_descr); |
| } |
| |
| static void proca_file_free_security_hook(struct file *file) |
| { |
| kfree(file->f_signature); |
| file->f_signature = NULL; |
| } |
| |
| #ifndef LINUX_LSM_SUPPORTED |
| void proca_compat_task_free_hook(struct task_struct *task) |
| { |
| if (unlikely(!g_proca_inited)) |
| return; |
| |
| proca_task_free_hook(task); |
| } |
| |
| void proca_compat_file_free_security_hook(struct file *file) |
| { |
| if (unlikely(!g_proca_inited)) |
| return; |
| |
| proca_file_free_security_hook(file); |
| } |
| #endif |
| |
| int proca_get_task_cert(const struct task_struct *task, |
| const char **cert, size_t *cert_size) |
| { |
| struct proca_task_descr *task_descr = NULL; |
| |
| BUG_ON(!task || !cert || !cert_size); |
| |
| task_descr = proca_table_get_by_task(&g_proca_table, task); |
| if (!task_descr) |
| return -ESRCH; |
| |
| *cert = task_descr->proca_identity.certificate; |
| *cert_size = task_descr->proca_identity.certificate_size; |
| return 0; |
| } |
| |
| static __init int proca_module_init(void) |
| { |
| int ret; |
| |
| ret = init_proca_config(&g_proca_config, &g_proca_table); |
| if (ret) |
| return ret; |
| |
| ret = init_certificate_validation_hash(); |
| if (ret) |
| return ret; |
| |
| proca_table_init(&g_proca_table); |
| |
| security_add_hooks(proca_ops, ARRAY_SIZE(proca_ops), "proca_lsm"); |
| five_add_hooks(five_ops, ARRAY_SIZE(five_ops)); |
| |
| PROCA_INFO_LOG("LSM module was initialized\n"); |
| g_proca_inited = 1; |
| |
| return 0; |
| } |
| late_initcall(proca_module_init); |
| |
| MODULE_DESCRIPTION("PROCA LSM module"); |
| MODULE_LICENSE("GPL"); |
| |