diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h index 558adfa5c8a877..b37e35e1364f2d 100644 --- a/include/linux/lsm_hooks.h +++ b/include/linux/lsm_hooks.h @@ -1479,6 +1479,7 @@ union security_list_options { int (*file_open)(struct file *file, const struct cred *cred); int (*task_create)(unsigned long clone_flags); + int (*task_copy)(struct task_struct *task); void (*task_free)(struct task_struct *task); int (*cred_alloc_blank)(struct cred *cred, gfp_t gfp); void (*cred_free)(struct cred *cred); @@ -1745,6 +1746,7 @@ struct security_hook_heads { struct list_head file_receive; struct list_head file_open; struct list_head task_create; + struct list_head task_copy; struct list_head task_free; struct list_head cred_alloc_blank; struct list_head cred_free; diff --git a/include/linux/security.h b/include/linux/security.h index c2125e9093e8e5..748058e6b8f6e3 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -305,6 +305,7 @@ int security_file_send_sigiotask(struct task_struct *tsk, int security_file_receive(struct file *file); int security_file_open(struct file *file, const struct cred *cred); int security_task_create(unsigned long clone_flags); +int security_task_copy(struct task_struct *task); void security_task_free(struct task_struct *task); int security_cred_alloc_blank(struct cred *cred, gfp_t gfp); void security_cred_free(struct cred *cred); @@ -857,6 +858,11 @@ static inline int security_task_create(unsigned long clone_flags) return 0; } +static inline int security_task_copy(struct task_struct *task) +{ + return 0; +} + static inline void security_task_free(struct task_struct *task) { } diff --git a/include/uapi/linux/prctl.h b/include/uapi/linux/prctl.h index a8d0759a9e400c..105668f2ee777a 100644 --- a/include/uapi/linux/prctl.h +++ b/include/uapi/linux/prctl.h @@ -197,4 +197,8 @@ struct prctl_mm_map { # define PR_CAP_AMBIENT_LOWER 3 # define PR_CAP_AMBIENT_CLEAR_ALL 4 +#define PR_TIMGAD_OPTS 48 +# define PR_TIMGAD_SET_MOD_HARDEN 1 +# define PR_TIMGAD_GET_MOD_HARDEN 2 + #endif /* _LINUX_PRCTL_H */ diff --git a/kernel/fork.c b/kernel/fork.c index 11c5c8ab827c4b..81c29d8943f2a1 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -1746,6 +1746,10 @@ static __latent_entropy struct task_struct *copy_process( if (retval) goto bad_fork_free_pid; + retval = security_task_copy(p); + if (retval) + goto bad_fork_cancel_cgroup; + /* * Make it visible to the rest of the system, but dont wake it up yet. * Need tasklist lock for parent etc handling! diff --git a/security/Kconfig b/security/Kconfig index 118f4549404ef2..e6d61285a12098 100644 --- a/security/Kconfig +++ b/security/Kconfig @@ -164,6 +164,7 @@ source security/tomoyo/Kconfig source security/apparmor/Kconfig source security/loadpin/Kconfig source security/yama/Kconfig +source security/timgad/Kconfig source security/integrity/Kconfig diff --git a/security/security.c b/security/security.c index f825304f04a773..5c699c8dda7747 100644 --- a/security/security.c +++ b/security/security.c @@ -892,6 +892,11 @@ int security_task_create(unsigned long clone_flags) return call_int_hook(task_create, 0, clone_flags); } +int security_task_copy(struct task_struct *task) +{ + return call_int_hook(task_copy, 0, task); +} + void security_task_free(struct task_struct *task) { call_void_hook(task_free, task); @@ -1731,6 +1736,7 @@ struct security_hook_heads security_hook_heads = { .file_receive = LIST_HEAD_INIT(security_hook_heads.file_receive), .file_open = LIST_HEAD_INIT(security_hook_heads.file_open), .task_create = LIST_HEAD_INIT(security_hook_heads.task_create), + .task_copy = LIST_HEAD_INIT(security_hook_heads.task_copy), .task_free = LIST_HEAD_INIT(security_hook_heads.task_free), .cred_alloc_blank = LIST_HEAD_INIT(security_hook_heads.cred_alloc_blank), diff --git a/security/timgad/Kconfig b/security/timgad/Kconfig new file mode 100644 index 00000000000000..2c54f0e973e4d8 --- /dev/null +++ b/security/timgad/Kconfig @@ -0,0 +1,10 @@ +config SECURITY_TIMGAD + bool "TIMGAD support" + depends on SECURITY + default n + help + This selects TIMGAD, which applies restrictions on module auto-loading + feature. Further information can be found in + Documentation/security/timgad.txt. + + If you are unsure how to answer this question, answer N. diff --git a/security/timgad/Makefile b/security/timgad/Makefile new file mode 100644 index 00000000000000..dca044100381c5 --- /dev/null +++ b/security/timgad/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_SECURITY_TIMGAD) := timgad.o + +timgad-y := timgad_core.o timgad_lsm.o diff --git a/security/timgad/timgad_core.c b/security/timgad/timgad_core.c new file mode 100644 index 00000000000000..88a0cf84c140c4 --- /dev/null +++ b/security/timgad/timgad_core.c @@ -0,0 +1,214 @@ +/* + * Timgad Linux Security Module + * + * Author: Djalal Harouni + * + * Copyright (C) 2017 Endocode AG. + * Copyright (c) 2016 Djalal Harouni + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct timgad_task { + atomic_t usage; + + struct rhash_head node; + unsigned long key; + + struct task_struct *task; + + int mod_harden:2; + + struct work_struct clean_work; +}; + +static struct rhashtable timgad_tasks_table; + +static inline int cmp_timgad_task(struct rhashtable_compare_arg *arg, + const void *obj) +{ + const unsigned long key = *(unsigned long *)arg->key; + const struct timgad_task *ttask = obj; + + return atomic_read(&ttask->usage) == 0 || ttask->key != key; +} + +static const struct rhashtable_params timgad_tasks_params = { + .nelem_hint = 1024, + .head_offset = offsetof(struct timgad_task, node), + .key_offset = offsetof(struct timgad_task, key), + .key_len = sizeof(unsigned long), + .max_size = 16384, + .min_size = 256, + .obj_cmpfn = cmp_timgad_task, + .automatic_shrinking = true, +}; + +int timgad_tasks_init(void) +{ + return rhashtable_init(&timgad_tasks_table, &timgad_tasks_params); +} + +void timgad_tasks_clean(void) +{ + rhashtable_destroy(&timgad_tasks_table); +} + +static int get_timgad_task_new_flags(unsigned long op, unsigned long used, + unsigned long flag, int *new_flags) +{ + int ret = -EINVAL; + + return ret; +} + +static int update_timgad_task_flags(struct timgad_task *timgad_tsk, + unsigned long op, int new_flags) +{ + int ret = -EINVAL; + + return ret; +} + +int timgad_task_is_op_set(struct timgad_task *timgad_tsk, unsigned long op) +{ + if (op == PR_TIMGAD_SET_MOD_HARDEN) + return timgad_tsk->mod_harden; + + return -EINVAL; +} + +int timgad_task_set_op_flag(struct timgad_task *timgad_tsk, unsigned long op, + unsigned long flag, unsigned long value) +{ + int ret = -EINVAL; + int new_flag = 0; + int used = timgad_task_is_op_set(timgad_tsk, op); + + ret = get_timgad_task_new_flags(op, used, flag, &new_flag); + if (ret < 0) + return ret; + + /* Nothing to do if new flag did not change */ + if (new_flag == used) + return 0; + + return update_timgad_task_flags(timgad_tsk, op, new_flag); +} + +static struct timgad_task *__lookup_timgad_task(struct task_struct *tsk) +{ + return rhashtable_lookup_fast(&timgad_tasks_table, tsk, + timgad_tasks_params); +} + +struct timgad_task *get_timgad_task(struct task_struct *tsk) +{ + struct timgad_task *ttask; + + rcu_read_lock(); + ttask = __lookup_timgad_task(tsk); + if (ttask) + atomic_inc(&ttask->usage); + rcu_read_unlock(); + + return ttask; +} + +void put_timgad_task(struct timgad_task *timgad_tsk) +{ + if (timgad_tsk && atomic_dec_and_test(&timgad_tsk->usage)) + schedule_work(&timgad_tsk->clean_work); +} + +struct timgad_task *lookup_timgad_task(struct task_struct *tsk) +{ + struct timgad_task *ttask; + + rcu_read_lock(); + ttask = __lookup_timgad_task(tsk); + rcu_read_unlock(); + + return ttask; +} + +int insert_timgad_task(struct timgad_task *timgad_tsk) +{ + int ret; + + atomic_inc(&timgad_tsk->usage); + ret = rhashtable_lookup_insert_key(&timgad_tasks_table, + timgad_tsk->task, &timgad_tsk->node, + timgad_tasks_params); + if (ret) + atomic_dec(&timgad_tsk->usage); + + return ret; +} + +static void reclaim_timgad_task(struct work_struct *work) +{ + struct timgad_task *ttask = container_of(work, struct timgad_task, + clean_work); + + WARN_ON(atomic_read(&ttask->usage) != 0); + + rhashtable_remove_fast(&timgad_tasks_table, &ttask->node, + timgad_tasks_params); + + kfree(ttask); +} + +struct timgad_task *init_timgad_task(struct task_struct *tsk, + unsigned long value) +{ + struct timgad_task *ttask; + + ttask = kzalloc(sizeof(*ttask), GFP_KERNEL | __GFP_NOWARN); + if (ttask == NULL) + return ERR_PTR(-ENOMEM); + + ttask->task = tsk; + ttask->mod_harden = value; + + atomic_set(&ttask->usage, 0); + INIT_WORK(&ttask->clean_work, reclaim_timgad_task); + + return ttask; +} + +/* On success, callers have to do put_timgad_task() */ +struct timgad_task *give_me_timgad_task(struct task_struct *tsk, + unsigned long value) +{ + int ret; + struct timgad_task *ttask; + + ttask = init_timgad_task(tsk, value); + if (IS_ERR(ttask)) + return ttask; + + /* Mark it as active */ + ret = insert_timgad_task(ttask); + if (ret) { + kfree(ttask); + return ERR_PTR(ret); + } + + return ttask; +} diff --git a/security/timgad/timgad_core.h b/security/timgad/timgad_core.h new file mode 100644 index 00000000000000..4498138c6177fc --- /dev/null +++ b/security/timgad/timgad_core.h @@ -0,0 +1,48 @@ +/* + * Timgad Linux Security Module + * + * Author: Djalal Harouni + * + * Copyright (c) 2016 Djalal Harouni + * Copyright (c) 2017 Endocode AG + * + * 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. + * + */ + +#define TIMGAD_MOD_HARDEN 0x00000001 +#define TIMGAD_MOD_HARDEN_STRICT 0x00000002 + +#define TIMGAD_OPTS_ALL \ + ((unsigned long) (TIMGAD_MOD_HARDEN | \ + TIMGAD_MOD_HARDEN_STRICT)) + +struct timgad_task; + +static inline int timgad_op_to_flag(unsigned long op, + unsigned long value, + unsigned long *rvalue) +{ + return 0; +} + +int timgad_task_set_op_flag(struct timgad_task *timgad_tsk, + unsigned long op, unsigned long flag, + unsigned long value); + +int timgad_task_is_op_set(struct timgad_task *timgad_tsk, unsigned long op); + +struct timgad_task *get_timgad_task(struct task_struct *tsk); +void put_timgad_task(struct timgad_task *timgad_tsk); +struct timgad_task *lookup_timgad_task(struct task_struct *tsk); +int insert_timgad_task(struct timgad_task *timgad_tsk); + +struct timgad_task *init_timgad_task(struct task_struct *tsk, + unsigned long flag); +struct timgad_task *give_me_timgad_task(struct task_struct *tsk, + unsigned long value); + +int timgad_tasks_init(void); +void timgad_tasks_clean(void); diff --git a/security/timgad/timgad_lsm.c b/security/timgad/timgad_lsm.c new file mode 100644 index 00000000000000..136d2eebee7ab1 --- /dev/null +++ b/security/timgad/timgad_lsm.c @@ -0,0 +1,181 @@ +/* + * Timgad Linux Security Module + * + * Author: Djalal Harouni + * + * Copyright (C) 2017 Endocode AG. + * Copyright (C) 2017 Djalal Harouni + * + * 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 +#include +#include + +#include "timgad_core.h" + +enum { + TIMGAD_MOD_HARDEN_OFF = 0, + TIMGAD_MOD_HARDEN_ON = 1, +}; + +static int module_restrict; + +static int timgad_set_op_value(struct task_struct *tsk, + unsigned long op, unsigned long value) +{ + int ret = 0; + struct timgad_task *ttask; + unsigned long flag = 0; + + ret = timgad_op_to_flag(op, value, &flag); + if (ret < 0) + return ret; + + ttask = get_timgad_task(tsk); + if (!ttask) { + ttask = give_me_timgad_task(tsk, value); + if (IS_ERR(ttask)) + return PTR_ERR(ttask); + + return 0; + } + + ret = timgad_task_set_op_flag(ttask, op, flag, value); + + put_timgad_task(ttask); + return ret; +} + +static int timgad_get_op_value(struct task_struct *tsk, unsigned long op) +{ + int ret = -EINVAL; + struct timgad_task *ttask; + + ttask = get_timgad_task(tsk); + if (!ttask) + return ret; + + ret = timgad_task_is_op_set(ttask, op); + put_timgad_task(ttask); + + return ret; +} + +int timgad_task_copy(struct task_struct *tsk) +{ + int ret = -EINVAL; + struct timgad_task *tparent; + struct timgad_task *ttask = NULL; + + + return ret; +} + +/* + * Return 0 on success, -error on error. -EINVAL is returned when Timgad + * does not handle the given option. + */ +int timgad_task_prctl(int option, unsigned long arg2, unsigned long arg3, + unsigned long arg4, unsigned long arg5) +{ + int ret = -EINVAL; + struct task_struct *myself = current; + + if (option != PR_TIMGAD_OPTS) + return ret; + + get_task_struct(myself); + + switch (arg2) { + case PR_TIMGAD_SET_MOD_HARDEN: + ret = timgad_set_op_value(myself, PR_TIMGAD_SET_MOD_HARDEN, arg3); + break; + case PR_TIMGAD_GET_MOD_HARDEN: + ret = timgad_get_op_value(myself, PR_TIMGAD_SET_MOD_HARDEN); + break; + } + + put_task_struct(myself); + + return ret; +} + +void timgad_task_free(struct task_struct *task) +{ + struct timgad_task *ttask; + + ttask = lookup_timgad_task(task); + if (!ttask) + return; + + put_timgad_task(ttask); +} + +static struct security_hook_list timgad_hooks[] = { + LSM_HOOK_INIT(task_copy, timgad_task_copy), + LSM_HOOK_INIT(task_prctl, timgad_task_prctl), + LSM_HOOK_INIT(task_free, timgad_task_free), +}; + +#ifdef CONFIG_SYSCTL +static int timgad_mod_dointvec_minmax(struct ctl_table *table, int write, + void __user *buffer, size_t *lenp, + loff_t *ppos) +{ + struct ctl_table table_copy; + + if (write && !capable(CAP_SYS_MODULE)) + return -EPERM; + + /* Lock the max value if it ever gets set. */ + table_copy = *table; + if (*(int *)table_copy.data == *(int *)table_copy.extra2) + table_copy.extra1 = table_copy.extra2; + + return proc_dointvec_minmax(&table_copy, write, buffer, lenp, ppos); +} + +static int zero; +static int max_module_restrict_scope = TIMGAD_MOD_HARDEN_ON; + +struct ctl_path timgad_sysctl_path[] = { + { .procname = "kernel", }, + { .procname = "timgad", }, + { } +}; + +static struct ctl_table timgad_sysctl_table[] = { + { + .procname = "module_restrict", + .data = &module_restrict, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = timgad_mod_dointvec_minmax, + .extra1 = &zero, + .extra2 = &max_module_restrict_scope, + }, + { } +}; +static void __init timgad_init_sysctl(void) +{ + if (!register_sysctl_paths(timgad_sysctl_path, timgad_sysctl_table)) + panic("Timgad: sysctl registration failed.\n"); +} +#else +static inline void timgad_init_sysctl(void) { } +#endif /* CONFIG_SYSCTL */ + +void __init timgad_add_hooks(void) +{ + pr_info("Timgad: becoming mindful.\n"); + security_add_hooks(timgad_hooks, ARRAY_SIZE(timgad_hooks)); + timgad_init_sysctl(); + + if (timgad_tasks_init()) + panic("Timgad: tasks initialization failed.\n"); +}