diff mbox

[RFC/PATCH,2/3] security: Add the Timgad module

Message ID 1486055094-4532-3-git-send-email-djalal@gmail.com (mailing list archive)
State New, archived
Headers show

Commit Message

Djalal Harouni Feb. 2, 2017, 5:04 p.m. UTC
From: Djalal Harouni <tixxdz@gmail.com>

This adds the Timgad module. Timgad allows to apply restrictions on
which task is allowed to load or unload kernel modules. Auto-load module
feature is also handled. The settings can also be applied globally using
a sysctl interface, this allows to complete the core kernel interface
"modules_disable" which has only two modes: allow globally or deny
globally.

The feature is useful for sandboxing, embedded systems and Linux
containers where only some containers/processes that have the
right privileges are allowed to load/unload modules. Unprivileged
processes should not be able to load/unload modules nor trigger the
module auto-load feature. This behaviour was inspired from grsecurity's
GRKERNSEC_MODHARDEN option.

However I still did not complete the check since this has to be
discussed first, so any bug here is not from grsecurity, but my bugs and
on purpose. As this is a preliminary RFC these points are not settled,
discussion has to happen on what should be the best behaviour and what
checks should be in place. Currently the settings:

Timgad module can be controled using a global sysctl setting:
   /proc/sys/kernel/timgad/module_restrict

Or using the prctl() interface:
   prctl(PR_TIMGAD_OPTS, PR_TIGMAD_SET_MOD_RESTRICT, value, 0, 0)

*) The per-process prctl() settings are:
    prctl(PR_TIMGAD_OPTS, PR_TIGMAD_SET_MOD_RESTRICT, value, 0, 0)

    Where value means:

0 - Classic module load and unload permissions, nothing changes.

1 - The current process must have CAP_SYS_MODULE to be able to load and
    unload modules. CAP_NET_ADMIN should allow the current process to
    load and unload only netdev aliased modules, not implemented

2 - Current process can not loaded nor unloaded modules.

*) sysctl interface supports the followin values:

0 - Classic module load and unload permissions, nothing changes.

1 - Only privileged processes with CAP_SYS_MODULE should be able to load and
    unload modules.

    To be added: processes with CAP_NET_ADMIN should be able to
    load and unload only netdev aliased modules, this is currently not
    supported. Other checks for real root without CAP_SYS_MODULE ? ...

    (This should be improved)

2 - Modules can not be loaded nor unloaded. Once set, this sysctl value
    cannot be changed.

Rules:
First the prctl() settings are checked, if the access is not denied
then the global sysctl settings are checked.

As said I will update the permission checks later, this is a preliminary
RFC.

Cc: Kees Cook <keescook@chromium.org>
Signed-off-by: Djalal Harouni <tixxdz@gmail.com>
---
 include/linux/lsm_hooks.h     |   5 +
 include/uapi/linux/prctl.h    |   5 +
 security/Kconfig              |   1 +
 security/Makefile             |   2 +
 security/security.c           |   1 +
 security/timgad/Kconfig       |  10 ++
 security/timgad/Makefile      |   3 +
 security/timgad/timgad_core.c | 306 +++++++++++++++++++++++++++++++++++++++
 security/timgad/timgad_core.h |  53 +++++++
 security/timgad/timgad_lsm.c  | 327 ++++++++++++++++++++++++++++++++++++++++++
 10 files changed, 713 insertions(+)
 create mode 100644 security/timgad/Kconfig
 create mode 100644 security/timgad/Makefile
 create mode 100644 security/timgad/timgad_core.c
 create mode 100644 security/timgad/timgad_core.h
 create mode 100644 security/timgad/timgad_lsm.c

Comments

James Morris Feb. 3, 2017, 1:02 a.m. UTC | #1
On Thu, 2 Feb 2017, Djalal Harouni wrote:

> *) The per-process prctl() settings are:
>     prctl(PR_TIMGAD_OPTS, PR_TIGMAD_SET_MOD_RESTRICT, value, 0, 0)
> 
>     Where value means:
> 
> 0 - Classic module load and unload permissions, nothing changes.
> 
> 1 - The current process must have CAP_SYS_MODULE to be able to load and
>     unload modules. CAP_NET_ADMIN should allow the current process to
>     load and unload only netdev aliased modules, not implemented
> 
> 2 - Current process can not loaded nor unloaded modules.
> 
> *) sysctl interface supports the followin values:
> 
> 0 - Classic module load and unload permissions, nothing changes.
> 
> 1 - Only privileged processes with CAP_SYS_MODULE should be able to load and
>     unload modules.
> 
>     To be added: processes with CAP_NET_ADMIN should be able to
>     load and unload only netdev aliased modules, this is currently not
>     supported. Other checks for real root without CAP_SYS_MODULE ? ...
> 
>     (This should be improved)
> 
> 2 - Modules can not be loaded nor unloaded. Once set, this sysctl value
>     cannot be changed.

How is this different to just using CAP_SYS_MODULE?
Djalal Harouni Feb. 6, 2017, 12:19 p.m. UTC | #2
Hi James,

On Fri, Feb 3, 2017 at 2:02 AM, James Morris <jmorris@namei.org> wrote:
> On Thu, 2 Feb 2017, Djalal Harouni wrote:
>
>> *) The per-process prctl() settings are:
>>     prctl(PR_TIMGAD_OPTS, PR_TIGMAD_SET_MOD_RESTRICT, value, 0, 0)
>>
>>     Where value means:
>>
>> 0 - Classic module load and unload permissions, nothing changes.
>>
>> 1 - The current process must have CAP_SYS_MODULE to be able to load and
>>     unload modules. CAP_NET_ADMIN should allow the current process to
>>     load and unload only netdev aliased modules, not implemented
>>
>> 2 - Current process can not loaded nor unloaded modules.
>>
>> *) sysctl interface supports the followin values:
>>
>> 0 - Classic module load and unload permissions, nothing changes.
>>
>> 1 - Only privileged processes with CAP_SYS_MODULE should be able to load and
>>     unload modules.
>>
>>     To be added: processes with CAP_NET_ADMIN should be able to
>>     load and unload only netdev aliased modules, this is currently not
>>     supported. Other checks for real root without CAP_SYS_MODULE ? ...
>>
>>     (This should be improved)
>>
>> 2 - Modules can not be loaded nor unloaded. Once set, this sysctl value
>>     cannot be changed.
>
> How is this different to just using CAP_SYS_MODULE?

Using only CAP_SYS_MODULE is not sufficient on its own, first we have
the following:

* Some modules can be auto-loaded without any privileges:
  tun, all the tunneling modules, maybe some md drivers, some crypto,
some device drivers... (long list)

* Other network modules require CAP_NET_ADMIN and netdev module prefix.

* Some socket types require CAP_SYS_ADMIN before loading the
corresponding protocol module: phonet sockets...

* Some other operations related to ioctl require CAP_SYS_ADMIN

* Filesystem prefixed modules can be auto-loaded inside user
namespaces, this was discussed in the past, and seems everyone agreed
that there is no harm.

* All the situations where a module requests another module or an
external symbol.

Then comes the rest, manual module loading operations which require
CAP_SYS_MODULE.

Of course all of these features are must have for a usable system.
However as Linux covers lot of use cases, there are situations where
offering users more granularity would be better, restricting the
ability of unprivileged to stack dozens of modules and expose them in
a container/embedded world IMO is worth it. Also CAP_NET_ADMIN is
pretty useful on its own for some services, but at same time maybe we
don't want it to translate to CAP_SYS_MODULE on that node or for that
container.

This is a first RFC to seek feedback, permission checks should be
adapted to cover what everyone think is reasonable. I think that we
definitely need a way to have a usable system/processes but also be
able to restrict *only* a set of processes from auto inserting modules
for various reasons (security, using other net protocols...) We are
already using seccomp, however it does not cover those cases.

Btw do you think it is acceptable to add the security_task_copy() hook
in the first patch ?

The whole approach is based on that hook, otherwise I don't see
another easy way and we have to clash with all other LSMs. This one
make it so easy to stack more minor LSMs.

Thanks!


> --
> James Morris
> <jmorris@namei.org>
>
Kees Cook Feb. 11, 2017, 12:33 a.m. UTC | #3
On Thu, Feb 2, 2017 at 9:04 AM, Djalal Harouni <tixxdz@gmail.com> wrote:
> From: Djalal Harouni <tixxdz@gmail.com>
>
> This adds the Timgad module. Timgad allows to apply restrictions on
> which task is allowed to load or unload kernel modules. Auto-load module
> feature is also handled. The settings can also be applied globally using
> a sysctl interface, this allows to complete the core kernel interface
> "modules_disable" which has only two modes: allow globally or deny
> globally.

To bikeshed on the name: since this is a module loading restriction
LSM, perhaps something more descriptive: ModAutoRestrict or something
like that? (Yes, Yama is poorly named, but initially it was going to
be more than just ptrace restrictions...)

> The feature is useful for sandboxing, embedded systems and Linux
> containers where only some containers/processes that have the
> right privileges are allowed to load/unload modules. Unprivileged

I'd be explicit here and discuss _auto_loading of modules. (Otherwise
people quickly get confused about this vs CAP_SYS_MODULE.) You mention
auto-load later, but I think mentioning it first make this easier to
understand.

> processes should not be able to load/unload modules nor trigger the
> module auto-load feature. This behaviour was inspired from grsecurity's
> GRKERNSEC_MODHARDEN option.
>
> However I still did not complete the check since this has to be
> discussed first, so any bug here is not from grsecurity, but my bugs and
> on purpose. As this is a preliminary RFC these points are not settled,
> discussion has to happen on what should be the best behaviour and what
> checks should be in place. Currently the settings:
>
> Timgad module can be controled using a global sysctl setting:

typo: controlled

>    /proc/sys/kernel/timgad/module_restrict

If this becomes /proc/sys/kernel/mod_autoload_restrict/ then the flag
can just be "enabled". (e.g. see LoadPin LSM.)

> Or using the prctl() interface:
>    prctl(PR_TIMGAD_OPTS, PR_TIGMAD_SET_MOD_RESTRICT, value, 0, 0)
>
> *) The per-process prctl() settings are:
>     prctl(PR_TIMGAD_OPTS, PR_TIGMAD_SET_MOD_RESTRICT, value, 0, 0)

Excellent, yes, please require the trailing zeros. :)

>     Where value means:
>
> 0 - Classic module load and unload permissions, nothing changes.

"Classic module auto-load permissions ..." Nothing can auto-unload as-is.

> 1 - The current process must have CAP_SYS_MODULE to be able to load and
>     unload modules. CAP_NET_ADMIN should allow the current process to
>     load and unload only netdev aliased modules, not implemented

"... to be able to auto-load modules." Same thing about unloading:
everything needs CAP_SYS_MODULE to unload a module.

> 2 - Current process can not loaded nor unloaded modules.

"... cannot auto-load modules."

>
> *) sysctl interface supports the followin values:
>
> 0 - Classic module load and unload permissions, nothing changes.
>
> 1 - Only privileged processes with CAP_SYS_MODULE should be able to load and
>     unload modules.
>
>     To be added: processes with CAP_NET_ADMIN should be able to
>     load and unload only netdev aliased modules, this is currently not
>     supported. Other checks for real root without CAP_SYS_MODULE ? ...
>
>     (This should be improved)
>
> 2 - Modules can not be loaded nor unloaded. Once set, this sysctl value
>     cannot be changed.
>
> Rules:
> First the prctl() settings are checked, if the access is not denied
> then the global sysctl settings are checked.
>
> As said I will update the permission checks later, this is a preliminary
> RFC.
>
> Cc: Kees Cook <keescook@chromium.org>
> Signed-off-by: Djalal Harouni <tixxdz@gmail.com>
> ---
>  include/linux/lsm_hooks.h     |   5 +
>  include/uapi/linux/prctl.h    |   5 +
>  security/Kconfig              |   1 +
>  security/Makefile             |   2 +
>  security/security.c           |   1 +
>  security/timgad/Kconfig       |  10 ++
>  security/timgad/Makefile      |   3 +
>  security/timgad/timgad_core.c | 306 +++++++++++++++++++++++++++++++++++++++
>  security/timgad/timgad_core.h |  53 +++++++
>  security/timgad/timgad_lsm.c  | 327 ++++++++++++++++++++++++++++++++++++++++++
>  10 files changed, 713 insertions(+)
>  create mode 100644 security/timgad/Kconfig
>  create mode 100644 security/timgad/Makefile
>  create mode 100644 security/timgad/timgad_core.c
>  create mode 100644 security/timgad/timgad_core.h
>  create mode 100644 security/timgad/timgad_lsm.c
>
> diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h
> index b37e35e..6b83aaa 100644
> --- a/include/linux/lsm_hooks.h
> +++ b/include/linux/lsm_hooks.h
> @@ -1935,5 +1935,10 @@ void __init loadpin_add_hooks(void);
>  #else
>  static inline void loadpin_add_hooks(void) { };
>  #endif
> +#ifdef CONFIG_SECURITY_TIMGAD
> +extern void __init timgad_add_hooks(void);
> +#else
> +static inline void __init timgad_add_hooks(void) { }
> +#endif
>
>  #endif /* ! __LINUX_LSM_HOOKS_H */
> diff --git a/include/uapi/linux/prctl.h b/include/uapi/linux/prctl.h
> index a8d0759..6d80eed 100644
> --- a/include/uapi/linux/prctl.h
> +++ b/include/uapi/linux/prctl.h
> @@ -197,4 +197,9 @@ struct prctl_mm_map {
>  # define PR_CAP_AMBIENT_LOWER          3
>  # define PR_CAP_AMBIENT_CLEAR_ALL      4
>
> +/* Control Timgad LSM options */
> +#define PR_TIMGAD_OPTS                 48
> +# define PR_TIMGAD_SET_MOD_RESTRICT    1
> +# define PR_TIMGAD_GET_MOD_RESTRICT    2
> +
>  #endif /* _LINUX_PRCTL_H */
> diff --git a/security/Kconfig b/security/Kconfig
> index 118f454..e6d6128 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/Makefile b/security/Makefile
> index f2d71cd..6a8127e 100644
> --- a/security/Makefile
> +++ b/security/Makefile
> @@ -9,6 +9,7 @@ subdir-$(CONFIG_SECURITY_TOMOYO)        += tomoyo
>  subdir-$(CONFIG_SECURITY_APPARMOR)     += apparmor
>  subdir-$(CONFIG_SECURITY_YAMA)         += yama
>  subdir-$(CONFIG_SECURITY_LOADPIN)      += loadpin
> +subdir-$(CONFIG_SECURITY_TIMGAD)       += timgad
>
>  # always enable default capabilities
>  obj-y                                  += commoncap.o
> @@ -25,6 +26,7 @@ obj-$(CONFIG_SECURITY_APPARMOR)               += apparmor/
>  obj-$(CONFIG_SECURITY_YAMA)            += yama/
>  obj-$(CONFIG_SECURITY_LOADPIN)         += loadpin/
>  obj-$(CONFIG_CGROUP_DEVICE)            += device_cgroup.o
> +obj-$(CONFIG_SECURITY_TIMGAD)          += timgad/
>
>  # Object integrity file lists
>  subdir-$(CONFIG_INTEGRITY)             += integrity
> diff --git a/security/security.c b/security/security.c
> index 5c699c8..c6c9349 100644
> --- a/security/security.c
> +++ b/security/security.c
> @@ -61,6 +61,7 @@ int __init security_init(void)
>         capability_add_hooks();
>         yama_add_hooks();
>         loadpin_add_hooks();
> +       timgad_add_hooks();
>
>         /*
>          * Load all the remaining security modules.
> diff --git a/security/timgad/Kconfig b/security/timgad/Kconfig
> new file mode 100644
> index 0000000..93ecdb6
> --- /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 load and unload
> +          modules operations. Further information can be found in
> +          Documentation/security/Timgad.txt.

I'd select a capitalization and stick with it (timgad, Timgad,
TIMGAD?) modulo its renaming, etc.

> +
> +         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 0000000..dca0441
> --- /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 0000000..fa35965
> --- /dev/null
> +++ b/security/timgad/timgad_core.c
> [...]

In the hopes of reusing the task stacking stuff from Casey, I'm going
to skip reading the above code at least for now. ;)

> diff --git a/security/timgad/timgad_core.h b/security/timgad/timgad_core.h
> new file mode 100644
> index 0000000..4096c39
> --- /dev/null
> +++ b/security/timgad/timgad_core.h
> @@ -0,0 +1,53 @@
> +/*
> + * Timgad Linux Security Module
> + *
> + * Author: Djalal Harouni
> + *
> + * Copyright (c) 2017 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_MODULE_OFF      0x00000000
> +#define TIMGAD_MODULE_STRICT   0x00000001
> +#define TIMGAD_MODULE_NO_LOAD  0x00000002
> +
> +struct timgad_task;
> +
> +static inline int timgad_op_to_flag(unsigned long op,
> +                                   unsigned long value,
> +                                   unsigned long *flag)
> +{
> +       if (op != PR_TIMGAD_SET_MOD_RESTRICT || value > TIMGAD_MODULE_NO_LOAD)
> +               return -EINVAL;
> +
> +       *flag = value;
> +       return 0;
> +}
> +
> +unsigned long read_timgad_task_flags(struct timgad_task *timgad_tsk);
> +
> +int timgad_task_set_op_flag(struct timgad_task *timgad_tsk,
> +                           unsigned long op, unsigned long flag,
> +                           unsigned long value);
> +
> +int is_timgad_task_op_set(struct timgad_task *timgad_tsk, unsigned long op,
> +                         unsigned long *flag);
> +
> +struct timgad_task *get_timgad_task(struct task_struct *tsk);
> +void put_timgad_task(struct timgad_task *timgad_tsk, bool *collect);
> +struct timgad_task *lookup_timgad_task(struct task_struct *tsk);
> +
> +void release_timgad_task(struct task_struct *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 0000000..809b7cf
> --- /dev/null
> +++ b/security/timgad/timgad_lsm.c
> @@ -0,0 +1,327 @@
> +/*
> + * Timgad Linux Security Module
> + *
> + * Author: Djalal Harouni
> + *
> + * Copyright (C) 2017 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.
> + *
> + */
> +
> +#include <linux/errno.h>
> +#include <linux/lsm_hooks.h>
> +#include <linux/sysctl.h>
> +#include <linux/prctl.h>
> +#include <linux/types.h>
> +
> +#include "timgad_core.h"
> +
> +static int module_restrict;
> +
> +static int zero;
> +static int max_module_restrict_scope = TIMGAD_MODULE_NO_LOAD;
> +
> +/* TODO:
> + *  complete permission check
> + *  inline function logic with the per-process if possible
> + */
> +static int timgad_has_global_sysctl_perm(unsigned long op)
> +{
> +       int ret = -EINVAL;
> +       struct mm_struct *mm = NULL;
> +
> +       if (op != PR_TIMGAD_GET_MOD_RESTRICT)
> +               return ret;
> +
> +       switch (module_restrict) {
> +       case TIMGAD_MODULE_OFF:
> +               ret = 0;
> +               break;
> +       /* TODO: complete this and handle it later per task too */
> +       case TIMGAD_MODULE_STRICT:
> +               /*
> +                * Are we allowed to sleep here ?
> +                * Also improve this check here
> +                */
> +               ret = -EPERM;
> +               mm = get_task_mm(current);
> +               if (mm) {
> +                       if (capable(CAP_SYS_MODULE))
> +                               ret = 0;
> +                       mmput(mm);
> +               }

Why the mm check here?

> +               break;
> +       case TIMGAD_MODULE_NO_LOAD:
> +               ret = -EPERM;
> +               break;
> +       }
> +
> +       return ret;
> +}
> +
> +/* TODO: simplify me and move me to timgad_core.c file */
> +static int module_timgad_task_perm(struct timgad_task *timgad_tsk,
> +                                  char *kmod_name)
> +{
> +       int ret;
> +       unsigned long flag = 0;
> +
> +       ret = is_timgad_task_op_set(timgad_tsk,
> +                                   PR_TIMGAD_SET_MOD_RESTRICT, &flag);
> +       if (ret < 0)
> +               return ret;
> +
> +       /*
> +        * TODO: complete me
> +        *    * Allow net modules only with CAP_NET_ADMIN and other cases...
> +        *    * Other exotic cases when set to STRICT should be denied...
> +        *    * Inline logic
> +        */
> +       switch (flag) {
> +       case TIMGAD_MODULE_OFF:
> +               ret = 0;
> +               break;
> +       case TIMGAD_MODULE_STRICT:
> +               if (!capable(CAP_SYS_MODULE))
> +                       ret = -EPERM;
> +               else
> +                       ret = 0;
> +               break;
> +       case TIMGAD_MODULE_NO_LOAD:
> +               ret = -EPERM;
> +               break;
> +       }
> +
> +       return ret;
> +}
> +
> +/* Set the given option in a timgad task */
> +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, NULL);
> +       return ret;
> +}
> +
> +/* Get the given option from a timgad task */
> +static int timgad_get_op_value(struct task_struct *tsk, unsigned long op)
> +{
> +       int ret = -EINVAL;
> +       struct timgad_task *ttask;
> +       unsigned long flag = 0;
> +
> +       ttask = get_timgad_task(tsk);
> +       if (!ttask)
> +               return ret;
> +
> +       ret = is_timgad_task_op_set(ttask, op, &flag);
> +       put_timgad_task(ttask, NULL);
> +
> +       return ret < 0 ? ret : flag;
> +}
> +
> +/* Copy Timgad context from parent to child */
> +int timgad_task_copy(struct task_struct *tsk)
> +{
> +       int ret = 0;
> +       struct timgad_task *tparent;
> +       struct timgad_task *ttask;
> +       unsigned long value = 0;
> +
> +       tparent = get_timgad_task(current);
> +
> +       /* Parent does not have a timgad context, nothing to do */
> +       if (tparent == NULL)
> +               return 0;
> +
> +       value = read_timgad_task_flags(tparent);
> +
> +       ttask = give_me_timgad_task(tsk, value);
> +       if (IS_ERR(ttask))
> +               ret = PTR_ERR(ttask);
> +       else
> +               ret = 0;
> +
> +       put_timgad_task(tparent, 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 0;
> +

Actually enforce that arg4 and arg5 == 0 here, and -EINVAL if they
don't. This will let you expand in the future if you need to.

> +       get_task_struct(myself);
> +
> +       switch (arg2) {
> +       case PR_TIMGAD_SET_MOD_RESTRICT:
> +               ret = timgad_set_op_value(myself,
> +                                         PR_TIMGAD_SET_MOD_RESTRICT,
> +                                         arg3);
> +               break;
> +       case PR_TIMGAD_GET_MOD_RESTRICT:
> +               ret = timgad_get_op_value(myself,
> +                                         PR_TIMGAD_SET_MOD_RESTRICT);
> +               break;
> +       }
> +
> +       put_task_struct(myself);
> +       return ret;
> +}
> +
> +/*
> + * Free the specific task attached resources
> + * task_free() can be called from interrupt context
> + */
> +void timgad_task_free(struct task_struct *tsk)
> +{
> +       release_timgad_task(tsk);
> +}
> +
> +static int timgad_kernel_module_file(struct file *file)
> +{
> +       int ret = 0;
> +       struct timgad_task *ttask;
> +       struct task_struct *myself = current;
> +
> +       /* First check if the task allows that */
> +       ttask = get_timgad_task(myself);
> +       if (ttask != NULL) {
> +               ret = module_timgad_task_perm(ttask, NULL);
> +               put_timgad_task(ttask, NULL);
> +       }
> +
> +       if (ret < 0)
> +               return ret;
> +
> +       return timgad_has_global_sysctl_perm(PR_TIMGAD_GET_MOD_RESTRICT);
> +}

This is the kernel_module_file hook, which is an explicit load of a
module by the current process. Is your intention to block
CAP_SYS_MODULE-capable processes from direct kernel module loading? If
so, I misunderstood part of the design here. I'd explicitly call it
out in the commit text, since direct loading and auto loading are
quite different, though I'd argue that you don't want to touch direct
loading privileges because that's already entirely covered by
CAP_SYS_MODULE....

> +static int timgad_kernel_module_request(char *kmod_name)
> +{
> +       int ret = 0;
> +       struct timgad_task *ttask;
> +       struct task_struct *myself = current;
> +
> +       /* First check if the task allows that */
> +       ttask = get_timgad_task(myself);
> +       if (ttask != NULL) {
> +               ret = module_timgad_task_perm(ttask, kmod_name);
> +               put_timgad_task(ttask, NULL);
> +       }
> +
> +       if (ret < 0)
> +               return ret;
> +
> +       return timgad_has_global_sysctl_perm(PR_TIMGAD_GET_MOD_RESTRICT);
> +}

This is the heart of the auto-loading check...

> +static int timgad_kernel_read_file(struct file *file,
> +                                  enum kernel_read_file_id id)
> +{
> +       int ret = 0;
> +
> +       switch (id) {
> +       case READING_MODULE:
> +               ret = timgad_kernel_module_file(file);
> +               break;
> +       default:
> +               break;
> +       }
> +
> +       return ret;
> +}
> +
> +static struct security_hook_list timgad_hooks[] = {
> +       LSM_HOOK_INIT(kernel_module_request, timgad_kernel_module_request),
> +       LSM_HOOK_INIT(kernel_read_file, timgad_kernel_read_file),
> +       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;
> +
> +       table_copy = *table;
> +       if (*(int *)table_copy.data == *(int *)table_copy.extra2)
> +               table_copy.extra1 = table_copy.extra2;

Are you intentionally pinning to the highest setting (like Yama)? This
isn't mentioned in the commit message, and it doesn't seem like
something that matters here?

> +
> +       return proc_dointvec_minmax(&table_copy, write, buffer, lenp, ppos);
> +}
> +
> +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");

That's Yama's catch-phrase. ;) Your LSM should say something else. :)

> +       security_add_hooks(timgad_hooks, ARRAY_SIZE(timgad_hooks));
> +       timgad_init_sysctl();
> +
> +       if (timgad_tasks_init())
> +               panic("Timgad: tasks initialization failed.\n");
> +}
> --
> 2.5.5
>

Sorry for the delay in review! It's been a busy week. :)

Thanks!

-Kees
Djalal Harouni Feb. 14, 2017, 12:19 p.m. UTC | #4
On Sat, Feb 11, 2017 at 1:33 AM, Kees Cook <keescook@chromium.org> wrote:
> On Thu, Feb 2, 2017 at 9:04 AM, Djalal Harouni <tixxdz@gmail.com> wrote:
>> From: Djalal Harouni <tixxdz@gmail.com>
>>
>> This adds the Timgad module. Timgad allows to apply restrictions on
>> which task is allowed to load or unload kernel modules. Auto-load module
>> feature is also handled. The settings can also be applied globally using
>> a sysctl interface, this allows to complete the core kernel interface
>> "modules_disable" which has only two modes: allow globally or deny
>> globally.
>
> To bikeshed on the name: since this is a module loading restriction
> LSM, perhaps something more descriptive: ModAutoRestrict or something
> like that? (Yes, Yama is poorly named, but initially it was going to
> be more than just ptrace restrictions...)

Yes, same thing here, I was also thinking maybe I can add an other
restriction here if necessary that apply using prctl() interface (I've
another case need to discuss it first). It would make little sense to
add another module for one prctl() use case and have to duplicate
bookkeeping of task<->security blob where there is already some
infrastructure. I also read your response and Casey's security blob
stacking work so yes lets see, otherwise I will update the name to
ModAutoRestrict or another one, no problem ;-)


>> The feature is useful for sandboxing, embedded systems and Linux
>> containers where only some containers/processes that have the
>> right privileges are allowed to load/unload modules. Unprivileged
>
> I'd be explicit here and discuss _auto_loading of modules. (Otherwise
> people quickly get confused about this vs CAP_SYS_MODULE.) You mention
> auto-load later, but I think mentioning it first make this easier to
> understand.

Ok.

>> processes should not be able to load/unload modules nor trigger the
>> module auto-load feature. This behaviour was inspired from grsecurity's
>> GRKERNSEC_MODHARDEN option.
>>
>> However I still did not complete the check since this has to be
>> discussed first, so any bug here is not from grsecurity, but my bugs and
>> on purpose. As this is a preliminary RFC these points are not settled,
>> discussion has to happen on what should be the best behaviour and what
>> checks should be in place. Currently the settings:
>>
>> Timgad module can be controled using a global sysctl setting:
>
> typo: controlled
>
>>    /proc/sys/kernel/timgad/module_restrict
>
> If this becomes /proc/sys/kernel/mod_autoload_restrict/ then the flag
> can just be "enabled". (e.g. see LoadPin LSM.)

I see but I guess we need at least 3 values.

>> Or using the prctl() interface:
>>    prctl(PR_TIMGAD_OPTS, PR_TIGMAD_SET_MOD_RESTRICT, value, 0, 0)
>>
>> *) The per-process prctl() settings are:
>>     prctl(PR_TIMGAD_OPTS, PR_TIGMAD_SET_MOD_RESTRICT, value, 0, 0)
>
> Excellent, yes, please require the trailing zeros. :)
>
>>     Where value means:
>>
>> 0 - Classic module load and unload permissions, nothing changes.
>
> "Classic module auto-load permissions ..." Nothing can auto-unload as-is.
>
>> 1 - The current process must have CAP_SYS_MODULE to be able to load and
>>     unload modules. CAP_NET_ADMIN should allow the current process to
>>     load and unload only netdev aliased modules, not implemented
>
> "... to be able to auto-load modules." Same thing about unloading:
> everything needs CAP_SYS_MODULE to unload a module.
>
>> 2 - Current process can not loaded nor unloaded modules.
>
> "... cannot auto-load modules."

Ok.


>>
>> *) sysctl interface supports the followin values:
>>
>> 0 - Classic module load and unload permissions, nothing changes.
>>
>> 1 - Only privileged processes with CAP_SYS_MODULE should be able to load and
>>     unload modules.
>>
>>     To be added: processes with CAP_NET_ADMIN should be able to
>>     load and unload only netdev aliased modules, this is currently not
>>     supported. Other checks for real root without CAP_SYS_MODULE ? ...
>>
>>     (This should be improved)
>>
>> 2 - Modules can not be loaded nor unloaded. Once set, this sysctl value
>>      cannot be changed.
>>
>> Rules:
>> First the prctl() settings are checked, if the access is not denied
>> then the global sysctl settings are checked.
>>
>> As said I will update the permission checks later, this is a preliminary
>> RFC.
>>
>> Cc: Kees Cook <keescook@chromium.org>
>> Signed-off-by: Djalal Harouni <tixxdz@gmail.com>
>> ---
>>  include/linux/lsm_hooks.h     |   5 +
>>  include/uapi/linux/prctl.h    |   5 +
>>  security/Kconfig              |   1 +
>>  security/Makefile             |   2 +
>>  security/security.c           |   1 +
>>  security/timgad/Kconfig       |  10 ++
>>  security/timgad/Makefile      |   3 +
>>  security/timgad/timgad_core.c | 306 +++++++++++++++++++++++++++++++++++++++
>>  security/timgad/timgad_core.h |  53 +++++++
>>  security/timgad/timgad_lsm.c  | 327 ++++++++++++++++++++++++++++++++++++++++++
>>  10 files changed, 713 insertions(+)
>>  create mode 100644 security/timgad/Kconfig
>>  create mode 100644 security/timgad/Makefile
>>  create mode 100644 security/timgad/timgad_core.c
>>  create mode 100644 security/timgad/timgad_core.h
>>  create mode 100644 security/timgad/timgad_lsm.c
>>
>> diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h
>> index b37e35e..6b83aaa 100644
>> --- a/include/linux/lsm_hooks.h
>> +++ b/include/linux/lsm_hooks.h
>> @@ -1935,5 +1935,10 @@ void __init loadpin_add_hooks(void);
>>  #else
>>  static inline void loadpin_add_hooks(void) { };
>>  #endif
>> +#ifdef CONFIG_SECURITY_TIMGAD
>> +extern void __init timgad_add_hooks(void);
>> +#else
>> +static inline void __init timgad_add_hooks(void) { }
>> +#endif
>>
>>  #endif /* ! __LINUX_LSM_HOOKS_H */
>> diff --git a/include/uapi/linux/prctl.h b/include/uapi/linux/prctl.h
>> index a8d0759..6d80eed 100644
>> --- a/include/uapi/linux/prctl.h
>> +++ b/include/uapi/linux/prctl.h
>> @@ -197,4 +197,9 @@ struct prctl_mm_map {
>>  # define PR_CAP_AMBIENT_LOWER          3
>>  # define PR_CAP_AMBIENT_CLEAR_ALL      4
>>
>> +/* Control Timgad LSM options */
>> +#define PR_TIMGAD_OPTS                 48
>> +# define PR_TIMGAD_SET_MOD_RESTRICT    1
>> +# define PR_TIMGAD_GET_MOD_RESTRICT    2
>> +
>>  #endif /* _LINUX_PRCTL_H */
>> diff --git a/security/Kconfig b/security/Kconfig
>> index 118f454..e6d6128 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/Makefile b/security/Makefile
>> index f2d71cd..6a8127e 100644
>> --- a/security/Makefile
>> +++ b/security/Makefile
>> @@ -9,6 +9,7 @@ subdir-$(CONFIG_SECURITY_TOMOYO)        += tomoyo
>>  subdir-$(CONFIG_SECURITY_APPARMOR)     += apparmor
>>  subdir-$(CONFIG_SECURITY_YAMA)         += yama
>>  subdir-$(CONFIG_SECURITY_LOADPIN)      += loadpin
>> +subdir-$(CONFIG_SECURITY_TIMGAD)       += timgad
>>
>>  # always enable default capabilities
>>  obj-y                                  += commoncap.o
>> @@ -25,6 +26,7 @@ obj-$(CONFIG_SECURITY_APPARMOR)               += apparmor/
>>  obj-$(CONFIG_SECURITY_YAMA)            += yama/
>>  obj-$(CONFIG_SECURITY_LOADPIN)         += loadpin/
>>  obj-$(CONFIG_CGROUP_DEVICE)            += device_cgroup.o
>> +obj-$(CONFIG_SECURITY_TIMGAD)          += timgad/
>>
>>  # Object integrity file lists
>>  subdir-$(CONFIG_INTEGRITY)             += integrity
>> diff --git a/security/security.c b/security/security.c
>> index 5c699c8..c6c9349 100644
>> --- a/security/security.c
>> +++ b/security/security.c
>> @@ -61,6 +61,7 @@ int __init security_init(void)
>>         capability_add_hooks();
>>         yama_add_hooks();
>>         loadpin_add_hooks();
>> +       timgad_add_hooks();
>>
>>         /*
>>          * Load all the remaining security modules.
>> diff --git a/security/timgad/Kconfig b/security/timgad/Kconfig
>> new file mode 100644
>> index 0000000..93ecdb6
>> --- /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 load and unload
>> +          modules operations. Further information can be found in
>> +          Documentation/security/Timgad.txt.
>
> I'd select a capitalization and stick with it (timgad, Timgad,
> TIMGAD?) modulo its renaming, etc.

Noted.

>> +
>> +         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 0000000..dca0441
>> --- /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 0000000..fa35965
>> --- /dev/null
>> +++ b/security/timgad/timgad_core.c
>> [...]
>
> In the hopes of reusing the task stacking stuff from Casey, I'm going
> to skip reading the above code at least for now. ;)

Ok :-) , also on another note a quick test with RCU readers, a lookup
on more than 15000 tasks with a timgad context:
    11.33%  ps           [kernel.kallsyms]        [k] lock_acquire
    10.72%  bash         [kernel.kallsyms]        [k]
page_check_address_transhuge
     8.11%  ps           [kernel.kallsyms]        [k]
_raw_spin_unlock_irqrestore
     6.52%  ps           [kernel.kallsyms]        [k] lock_release
     3.45%  ps           [kernel.kallsyms]        [k] lock_is_held_type
     ...
     0.01%  bash         [kernel.kallsyms]        [k] get_timgad_task
     0.01%  bash         [kernel.kallsyms]        [k] getname_flags
     0.01%  bash         [kernel.kallsyms]        [k] hashtab_search
     0.01%  bash         [kernel.kallsyms]        [k] iput


>> diff --git a/security/timgad/timgad_core.h b/security/timgad/timgad_core.h
>> new file mode 100644
>> index 0000000..4096c39
>> --- /dev/null
>> +++ b/security/timgad/timgad_core.h
>> @@ -0,0 +1,53 @@
>> +/*
>> + * Timgad Linux Security Module
>> + *
>> + * Author: Djalal Harouni
>> + *
>> + * Copyright (c) 2017 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_MODULE_OFF      0x00000000
>> +#define TIMGAD_MODULE_STRICT   0x00000001
>> +#define TIMGAD_MODULE_NO_LOAD  0x00000002
>> +
>> +struct timgad_task;
>> +
>> +static inline int timgad_op_to_flag(unsigned long op,
>> +                                   unsigned long value,
>> +                                   unsigned long *flag)
>> +{
>> +       if (op != PR_TIMGAD_SET_MOD_RESTRICT || value > TIMGAD_MODULE_NO_LOAD)
>> +               return -EINVAL;
>> +
>> +       *flag = value;
>> +       return 0;
>> +}
>> +
>> +unsigned long read_timgad_task_flags(struct timgad_task *timgad_tsk);
>> +
>> +int timgad_task_set_op_flag(struct timgad_task *timgad_tsk,
>> +                           unsigned long op, unsigned long flag,
>> +                           unsigned long value);
>> +
>> +int is_timgad_task_op_set(struct timgad_task *timgad_tsk, unsigned long op,
>> +                         unsigned long *flag);
>> +
>> +struct timgad_task *get_timgad_task(struct task_struct *tsk);
>> +void put_timgad_task(struct timgad_task *timgad_tsk, bool *collect);
>> +struct timgad_task *lookup_timgad_task(struct task_struct *tsk);
>> +
>> +void release_timgad_task(struct task_struct *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 0000000..809b7cf
>> --- /dev/null
>> +++ b/security/timgad/timgad_lsm.c
>> @@ -0,0 +1,327 @@
>> +/*
>> + * Timgad Linux Security Module
>> + *
>> + * Author: Djalal Harouni
>> + *
>> + * Copyright (C) 2017 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.
>> + *
>> + */
>> +
>> +#include <linux/errno.h>
>> +#include <linux/lsm_hooks.h>
>> +#include <linux/sysctl.h>
>> +#include <linux/prctl.h>
>> +#include <linux/types.h>
>> +
>> +#include "timgad_core.h"
>> +
>> +static int module_restrict;
>> +
>> +static int zero;
>> +static int max_module_restrict_scope = TIMGAD_MODULE_NO_LOAD;
>> +
>> +/* TODO:
>> + *  complete permission check
>> + *  inline function logic with the per-process if possible
>> + */
>> +static int timgad_has_global_sysctl_perm(unsigned long op)
>> +{
>> +       int ret = -EINVAL;
>> +       struct mm_struct *mm = NULL;
>> +
>> +       if (op != PR_TIMGAD_GET_MOD_RESTRICT)
>> +               return ret;
>> +
>> +       switch (module_restrict) {
>> +       case TIMGAD_MODULE_OFF:
>> +               ret = 0;
>> +               break;
>> +       /* TODO: complete this and handle it later per task too */
>> +       case TIMGAD_MODULE_STRICT:
>> +               /*
>> +                * Are we allowed to sleep here ?
>> +                * Also improve this check here
>> +                */
>> +               ret = -EPERM;
>> +               mm = get_task_mm(current);
>> +               if (mm) {
>> +                       if (capable(CAP_SYS_MODULE))
>> +                               ret = 0;
>> +                       mmput(mm);
>> +               }
>
> Why the mm check here?

Due to lack of time, I could not check this so I just put this here in
case we have drivers pushed through a work job from a kthread... So to
not forget about it. I'm not sure about this, I will revisit it of
course.


>> +               break;
>> +       case TIMGAD_MODULE_NO_LOAD:
>> +               ret = -EPERM;
>> +               break;
>> +       }
>> +
>> +       return ret;
>> +}
>> +
>> +/* TODO: simplify me and move me to timgad_core.c file */
>> +static int module_timgad_task_perm(struct timgad_task *timgad_tsk,
>> +                                  char *kmod_name)
>> +{
>> +       int ret;
>> +       unsigned long flag = 0;
>> +
>> +       ret = is_timgad_task_op_set(timgad_tsk,
>> +                                   PR_TIMGAD_SET_MOD_RESTRICT, &flag);
>> +       if (ret < 0)
>> +               return ret;
>> +
>> +       /*
>> +        * TODO: complete me
>> +        *    * Allow net modules only with CAP_NET_ADMIN and other cases...
>> +        *    * Other exotic cases when set to STRICT should be denied...
>> +        *    * Inline logic
>> +        */
>> +       switch (flag) {
>> +       case TIMGAD_MODULE_OFF:
>> +               ret = 0;
>> +               break;
>> +       case TIMGAD_MODULE_STRICT:
>> +               if (!capable(CAP_SYS_MODULE))
>> +                       ret = -EPERM;
>> +               else
>> +                       ret = 0;
>> +               break;
>> +       case TIMGAD_MODULE_NO_LOAD:
>> +               ret = -EPERM;
>> +               break;
>> +       }
>> +
>> +       return ret;
>> +}
>> +
>> +/* Set the given option in a timgad task */
>> +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, NULL);
>> +       return ret;
>> +}
>> +
>> +/* Get the given option from a timgad task */
>> +static int timgad_get_op_value(struct task_struct *tsk, unsigned long op)
>> +{
>> +       int ret = -EINVAL;
>> +       struct timgad_task *ttask;
>> +       unsigned long flag = 0;
>> +
>> +       ttask = get_timgad_task(tsk);
>> +       if (!ttask)
>> +               return ret;
>> +
>> +       ret = is_timgad_task_op_set(ttask, op, &flag);
>> +       put_timgad_task(ttask, NULL);
>> +
>> +       return ret < 0 ? ret : flag;
>> +}
>> +
>> +/* Copy Timgad context from parent to child */
>> +int timgad_task_copy(struct task_struct *tsk)
>> +{
>> +       int ret = 0;
>> +       struct timgad_task *tparent;
>> +       struct timgad_task *ttask;
>> +       unsigned long value = 0;
>> +
>> +       tparent = get_timgad_task(current);
>> +
>> +       /* Parent does not have a timgad context, nothing to do */
>> +       if (tparent == NULL)
>> +               return 0;
>> +
>> +       value = read_timgad_task_flags(tparent);
>> +
>> +       ttask = give_me_timgad_task(tsk, value);
>> +       if (IS_ERR(ttask))
>> +               ret = PTR_ERR(ttask);
>> +       else
>> +               ret = 0;
>> +
>> +       put_timgad_task(tparent, 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 0;
>> +
>
> Actually enforce that arg4 and arg5 == 0 here, and -EINVAL if they
> don't. This will let you expand in the future if you need to.

Ok, thanks will do.

>
>> +       get_task_struct(myself);
>> +
>> +       switch (arg2) {
>> +       case PR_TIMGAD_SET_MOD_RESTRICT:
>> +               ret = timgad_set_op_value(myself,
>> +                                         PR_TIMGAD_SET_MOD_RESTRICT,
>> +                                         arg3);
>> +               break;
>> +       case PR_TIMGAD_GET_MOD_RESTRICT:
>> +               ret = timgad_get_op_value(myself,
>> +                                         PR_TIMGAD_SET_MOD_RESTRICT);
>> +               break;
>> +       }
>> +
>> +       put_task_struct(myself);
>> +       return ret;
>> +}
>> +
>> +/*
>> + * Free the specific task attached resources
>> + * task_free() can be called from interrupt context
>> + */
>> +void timgad_task_free(struct task_struct *tsk)
>> +{
>> +       release_timgad_task(tsk);
>> +}
>> +
>> +static int timgad_kernel_module_file(struct file *file)
>> +{
>> +       int ret = 0;
>> +       struct timgad_task *ttask;
>> +       struct task_struct *myself = current;
>> +
>> +       /* First check if the task allows that */
>> +       ttask = get_timgad_task(myself);
>> +       if (ttask != NULL) {
>> +               ret = module_timgad_task_perm(ttask, NULL);
>> +               put_timgad_task(ttask, NULL);
>> +       }
>> +
>> +       if (ret < 0)
>> +               return ret;
>> +
>> +       return timgad_has_global_sysctl_perm(PR_TIMGAD_GET_MOD_RESTRICT);
>> +}
>
> This is the kernel_module_file hook, which is an explicit load of a
> module by the current process. Is your intention to block
> CAP_SYS_MODULE-capable processes from direct kernel module loading? If
> so, I misunderstood part of the design here. I'd explicitly call it
> out in the commit text, since direct loading and auto loading are
> quite different, though I'd argue that you don't want to touch direct
> loading privileges because that's already entirely covered by
> CAP_SYS_MODULE....

Makes sense, I'll update and confirm that we don't need this in case
we don't have context info about modprobe caller, otherwise we may
need this to fill the gap using a global sysctl policy, or maybe have
semantics based on module types and aliases... for auto-load, the
higher value will deny all auto-load operations.


>
>> +static int timgad_kernel_module_request(char *kmod_name)
>> +{
>> +       int ret = 0;
>> +       struct timgad_task *ttask;
>> +       struct task_struct *myself = current;
>> +
>> +       /* First check if the task allows that */
>> +       ttask = get_timgad_task(myself);
>> +       if (ttask != NULL) {
>> +               ret = module_timgad_task_perm(ttask, kmod_name);
>> +               put_timgad_task(ttask, NULL);
>> +       }
>> +
>> +       if (ret < 0)
>> +               return ret;
>> +
>> +       return timgad_has_global_sysctl_perm(PR_TIMGAD_GET_MOD_RESTRICT);
>> +}
>
> This is the heart of the auto-loading check...
>
>> +static int timgad_kernel_read_file(struct file *file,
>> +                                  enum kernel_read_file_id id)
>> +{
>> +       int ret = 0;
>> +
>> +       switch (id) {
>> +       case READING_MODULE:
>> +               ret = timgad_kernel_module_file(file);
>> +               break;
>> +       default:
>> +               break;
>> +       }
>> +
>> +       return ret;
>> +}
>> +
>> +static struct security_hook_list timgad_hooks[] = {
>> +       LSM_HOOK_INIT(kernel_module_request, timgad_kernel_module_request),
>> +       LSM_HOOK_INIT(kernel_read_file, timgad_kernel_read_file),
>> +       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;
>> +
>> +       table_copy = *table;
>> +       if (*(int *)table_copy.data == *(int *)table_copy.extra2)
>> +               table_copy.extra1 = table_copy.extra2;
>
> Are you intentionally pinning to the highest setting (like Yama)? This
> isn't mentioned in the commit message, and it doesn't seem like
> something that matters here?


Yes, I forget to document that. Thanks!

>
>> +
>> +       return proc_dointvec_minmax(&table_copy, write, buffer, lenp, ppos);
>> +}
>> +
>> +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");
>
> That's Yama's catch-phrase. ;) Your LSM should say something else. :)

oups, hehe I hope I can find something!

>
>> +       security_add_hooks(timgad_hooks, ARRAY_SIZE(timgad_hooks));
>> +       timgad_init_sysctl();
>> +
>> +       if (timgad_tasks_init())
>> +               panic("Timgad: tasks initialization failed.\n");
>> +}
>> --
>> 2.5.5
>>
>
> Sorry for the delay in review! It's been a busy week. :)
>
> Thanks!

Thanks for the review Kees, I will revisit all this, fix based on
suggestions and update after next merge window. In mean time I'll
follow also Casey's stacking effort.

> -Kees
>
> --
> Kees Cook
> Pixel Security
diff mbox

Patch

diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h
index b37e35e..6b83aaa 100644
--- a/include/linux/lsm_hooks.h
+++ b/include/linux/lsm_hooks.h
@@ -1935,5 +1935,10 @@  void __init loadpin_add_hooks(void);
 #else
 static inline void loadpin_add_hooks(void) { };
 #endif
+#ifdef CONFIG_SECURITY_TIMGAD
+extern void __init timgad_add_hooks(void);
+#else
+static inline void __init timgad_add_hooks(void) { }
+#endif
 
 #endif /* ! __LINUX_LSM_HOOKS_H */
diff --git a/include/uapi/linux/prctl.h b/include/uapi/linux/prctl.h
index a8d0759..6d80eed 100644
--- a/include/uapi/linux/prctl.h
+++ b/include/uapi/linux/prctl.h
@@ -197,4 +197,9 @@  struct prctl_mm_map {
 # define PR_CAP_AMBIENT_LOWER		3
 # define PR_CAP_AMBIENT_CLEAR_ALL	4
 
+/* Control Timgad LSM options */
+#define PR_TIMGAD_OPTS			48
+# define PR_TIMGAD_SET_MOD_RESTRICT	1
+# define PR_TIMGAD_GET_MOD_RESTRICT	2
+
 #endif /* _LINUX_PRCTL_H */
diff --git a/security/Kconfig b/security/Kconfig
index 118f454..e6d6128 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/Makefile b/security/Makefile
index f2d71cd..6a8127e 100644
--- a/security/Makefile
+++ b/security/Makefile
@@ -9,6 +9,7 @@  subdir-$(CONFIG_SECURITY_TOMOYO)        += tomoyo
 subdir-$(CONFIG_SECURITY_APPARMOR)	+= apparmor
 subdir-$(CONFIG_SECURITY_YAMA)		+= yama
 subdir-$(CONFIG_SECURITY_LOADPIN)	+= loadpin
+subdir-$(CONFIG_SECURITY_TIMGAD)	+= timgad
 
 # always enable default capabilities
 obj-y					+= commoncap.o
@@ -25,6 +26,7 @@  obj-$(CONFIG_SECURITY_APPARMOR)		+= apparmor/
 obj-$(CONFIG_SECURITY_YAMA)		+= yama/
 obj-$(CONFIG_SECURITY_LOADPIN)		+= loadpin/
 obj-$(CONFIG_CGROUP_DEVICE)		+= device_cgroup.o
+obj-$(CONFIG_SECURITY_TIMGAD)		+= timgad/
 
 # Object integrity file lists
 subdir-$(CONFIG_INTEGRITY)		+= integrity
diff --git a/security/security.c b/security/security.c
index 5c699c8..c6c9349 100644
--- a/security/security.c
+++ b/security/security.c
@@ -61,6 +61,7 @@  int __init security_init(void)
 	capability_add_hooks();
 	yama_add_hooks();
 	loadpin_add_hooks();
+	timgad_add_hooks();
 
 	/*
 	 * Load all the remaining security modules.
diff --git a/security/timgad/Kconfig b/security/timgad/Kconfig
new file mode 100644
index 0000000..93ecdb6
--- /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 load and unload
+          modules operations. 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 0000000..dca0441
--- /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 0000000..fa35965
--- /dev/null
+++ b/security/timgad/timgad_core.c
@@ -0,0 +1,306 @@ 
+/*
+ * Timgad Linux Security Module
+ *
+ * Author: Djalal Harouni
+ *
+ * Copyright (c) 2017 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.
+ *
+ */
+
+#include <linux/ctype.h>
+#include <linux/errno.h>
+#include <linux/list.h>
+#include <linux/prctl.h>
+#include <linux/rhashtable.h>
+#include <linux/sched.h>
+#include <linux/security.h>
+#include <linux/spinlock.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/workqueue.h>
+
+enum {
+	TIMGAD_TASK_INITIALIZED = 0,	/* Initialized, not active */
+	TIMGAD_TASK_ACTIVE = 1,		/* Linked in hash, active */
+	TIMGAD_TASK_INVALID = -1,	/* Scheduled to be removed from hash */
+};
+
+struct timgad_task_map {
+	unsigned long key_addr;
+};
+
+struct timgad_task {
+	struct rhash_head t_rhash_head;	/* timgad task hash node */
+	unsigned long key;
+
+	atomic_t usage;
+	u32 flags;
+
+	struct work_struct clean_work;
+};
+
+static struct rhashtable timgad_tasks_table;
+static DEFINE_SPINLOCK(timgad_tasks_lock);
+
+static inline int _cmp_timgad_task(struct rhashtable_compare_arg *arg,
+				   const void *obj)
+{
+	const struct timgad_task_map *tmap = arg->key;
+	const struct timgad_task *ttask = obj;
+
+	if (ttask->key != tmap->key_addr)
+		return 1;
+
+	/* Did we hit an entry that was invalidated ? */
+	if (atomic_read(&ttask->usage) == TIMGAD_TASK_INVALID)
+		return 1;
+
+	return 0;
+}
+
+/* TODO: optimize me */
+static const struct rhashtable_params timgad_tasks_hash_params = {
+	.nelem_hint = 512,
+	.head_offset = offsetof(struct timgad_task, t_rhash_head),
+	.key_offset = offsetof(struct timgad_task, key),
+	.key_len = sizeof(unsigned long),
+	.max_size = 8192,
+	.min_size = 256,
+	.obj_cmpfn = _cmp_timgad_task,
+	.automatic_shrinking = true,
+};
+
+int timgad_tasks_init(void)
+{
+	return rhashtable_init(&timgad_tasks_table, &timgad_tasks_hash_params);
+}
+
+void timgad_tasks_clean(void)
+{
+	rhashtable_destroy(&timgad_tasks_table);
+}
+
+unsigned long read_timgad_task_flags(struct timgad_task *timgad_tsk)
+{
+	return timgad_tsk->flags;
+}
+
+static inline int new_timgad_task_flags(unsigned long op,
+					unsigned long used_flag,
+					unsigned long passed_flag,
+					unsigned long *new_flag)
+{
+	if (passed_flag < used_flag)
+		return -EPERM;
+
+	*new_flag = passed_flag;
+	return 0;
+}
+
+static inline int update_timgad_task_flags(struct timgad_task *timgad_tsk,
+					   unsigned long op,
+					   unsigned long new_flag)
+{
+	if (op != PR_TIMGAD_SET_MOD_RESTRICT)
+		return -EINVAL;
+
+	timgad_tsk->flags = new_flag;
+	return 0;
+}
+
+int is_timgad_task_op_set(struct timgad_task *timgad_tsk, unsigned long op,
+			  unsigned long *flag)
+{
+	if (op != PR_TIMGAD_SET_MOD_RESTRICT)
+		return -EINVAL;
+
+	*flag = timgad_tsk->flags;
+	return 0;
+}
+
+int timgad_task_set_op_flag(struct timgad_task *timgad_tsk, unsigned long op,
+			    unsigned long flag, unsigned long value)
+{
+	int ret;
+	unsigned long new_flag = 0;
+	unsigned long used_flag;
+
+	ret = is_timgad_task_op_set(timgad_tsk, op, &used_flag);
+	if (ret < 0)
+		return ret;
+
+	ret = new_timgad_task_flags(op, used_flag, flag, &new_flag);
+	if (ret < 0)
+		return ret;
+
+	/* Nothing to do if the flag did not change */
+	if (new_flag == used_flag)
+		return 0;
+
+	return update_timgad_task_flags(timgad_tsk, op, new_flag);
+}
+
+static struct timgad_task *__lookup_timgad_task(struct task_struct *tsk)
+{
+	struct timgad_task_map tmap = { .key_addr = (unsigned long)(uintptr_t)tsk };
+
+	return rhashtable_lookup_fast(&timgad_tasks_table, &tmap,
+				      timgad_tasks_hash_params);
+}
+
+static inline struct timgad_task *__get_timgad_task(struct timgad_task *timgad_tsk)
+{
+	if (atomic_inc_not_zero(&timgad_tsk->usage))
+		return timgad_tsk;
+
+	return NULL;
+}
+
+static inline void __put_timgad_task(struct timgad_task *timgad_tsk,
+				     bool *collect)
+{
+	/* First check if we have not been interrupted */
+	if (atomic_read(&timgad_tsk->usage) <= TIMGAD_TASK_INITIALIZED ||
+	    atomic_dec_and_test(&timgad_tsk->usage)) {
+		if (collect)
+			*collect = true;
+		/*
+		 * Invalidate entry as early as possible so we
+		 * do not collide
+		 */
+		atomic_set(&timgad_tsk->usage, TIMGAD_TASK_INVALID);
+	}
+}
+
+/* We do take a reference count */
+struct timgad_task *get_timgad_task(struct task_struct *tsk)
+{
+	struct timgad_task *ttask;
+
+	rcu_read_lock();
+	ttask = __lookup_timgad_task(tsk);
+	if (ttask)
+		ttask = __get_timgad_task(ttask);
+	rcu_read_unlock();
+
+	return ttask;
+}
+
+void put_timgad_task(struct timgad_task *timgad_tsk, bool *collect)
+{
+	if (timgad_tsk)
+		__put_timgad_task(timgad_tsk, collect);
+}
+
+/*
+ * We return all timgad tasks that are not in the TIMGAD_TASK_INVALID state.
+ * We do not take reference count on timgad tasks here
+ */
+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;
+}
+
+static int insert_timgad_task(struct timgad_task *timgad_tsk)
+{
+	int ret;
+	struct timgad_task *ttask = timgad_tsk;
+
+	/* TODO: improve me */
+	if (unlikely(atomic_read(&timgad_tasks_table.nelems) >= INT_MAX))
+		return -ENOMEM;
+
+	atomic_set(&ttask->usage, TIMGAD_TASK_ACTIVE);
+	spin_lock(&timgad_tasks_lock);
+	ret = rhashtable_insert_fast(&timgad_tasks_table,
+				     &timgad_tsk->t_rhash_head,
+				     timgad_tasks_hash_params);
+	spin_unlock(&timgad_tasks_lock);
+	if (ret != 0 && ret != -EEXIST) {
+		atomic_set(&ttask->usage, TIMGAD_TASK_INITIALIZED);
+		return ret;
+	}
+
+	return 0;
+}
+
+void release_timgad_task(struct task_struct *tsk)
+{
+	struct timgad_task *ttask;
+	bool reclaim = false;
+
+	rcu_read_lock();
+	/* We do not take a ref count here */
+	ttask = __lookup_timgad_task(tsk);
+	if (ttask)
+		put_timgad_task(ttask, &reclaim);
+	rcu_read_unlock();
+
+	if (reclaim)
+		schedule_work(&ttask->clean_work);
+}
+
+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) != TIMGAD_TASK_INVALID);
+
+	spin_lock(&timgad_tasks_lock);
+	rhashtable_remove_fast(&timgad_tasks_table, &ttask->t_rhash_head,
+			       timgad_tasks_hash_params);
+	spin_unlock(&timgad_tasks_lock);
+
+	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->key = (unsigned long)(uintptr_t)tsk;
+	ttask->flags = value;
+
+	atomic_set(&ttask->usage, TIMGAD_TASK_INITIALIZED);
+	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 0000000..4096c39
--- /dev/null
+++ b/security/timgad/timgad_core.h
@@ -0,0 +1,53 @@ 
+/*
+ * Timgad Linux Security Module
+ *
+ * Author: Djalal Harouni
+ *
+ * Copyright (c) 2017 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_MODULE_OFF	0x00000000
+#define TIMGAD_MODULE_STRICT	0x00000001
+#define TIMGAD_MODULE_NO_LOAD	0x00000002
+
+struct timgad_task;
+
+static inline int timgad_op_to_flag(unsigned long op,
+				    unsigned long value,
+				    unsigned long *flag)
+{
+	if (op != PR_TIMGAD_SET_MOD_RESTRICT || value > TIMGAD_MODULE_NO_LOAD)
+		return -EINVAL;
+
+	*flag = value;
+	return 0;
+}
+
+unsigned long read_timgad_task_flags(struct timgad_task *timgad_tsk);
+
+int timgad_task_set_op_flag(struct timgad_task *timgad_tsk,
+			    unsigned long op, unsigned long flag,
+			    unsigned long value);
+
+int is_timgad_task_op_set(struct timgad_task *timgad_tsk, unsigned long op,
+			  unsigned long *flag);
+
+struct timgad_task *get_timgad_task(struct task_struct *tsk);
+void put_timgad_task(struct timgad_task *timgad_tsk, bool *collect);
+struct timgad_task *lookup_timgad_task(struct task_struct *tsk);
+
+void release_timgad_task(struct task_struct *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 0000000..809b7cf
--- /dev/null
+++ b/security/timgad/timgad_lsm.c
@@ -0,0 +1,327 @@ 
+/*
+ * Timgad Linux Security Module
+ *
+ * Author: Djalal Harouni
+ *
+ * Copyright (C) 2017 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.
+ *
+ */
+
+#include <linux/errno.h>
+#include <linux/lsm_hooks.h>
+#include <linux/sysctl.h>
+#include <linux/prctl.h>
+#include <linux/types.h>
+
+#include "timgad_core.h"
+
+static int module_restrict;
+
+static int zero;
+static int max_module_restrict_scope = TIMGAD_MODULE_NO_LOAD;
+
+/* TODO:
+ *  complete permission check
+ *  inline function logic with the per-process if possible
+ */
+static int timgad_has_global_sysctl_perm(unsigned long op)
+{
+	int ret = -EINVAL;
+	struct mm_struct *mm = NULL;
+
+	if (op != PR_TIMGAD_GET_MOD_RESTRICT)
+		return ret;
+
+	switch (module_restrict) {
+	case TIMGAD_MODULE_OFF:
+		ret = 0;
+		break;
+	/* TODO: complete this and handle it later per task too */
+	case TIMGAD_MODULE_STRICT:
+		/*
+		 * Are we allowed to sleep here ?
+		 * Also improve this check here
+		 */
+		ret = -EPERM;
+		mm = get_task_mm(current);
+		if (mm) {
+			if (capable(CAP_SYS_MODULE))
+				ret = 0;
+			mmput(mm);
+		}
+		break;
+	case TIMGAD_MODULE_NO_LOAD:
+		ret = -EPERM;
+		break;
+	}
+
+	return ret;
+}
+
+/* TODO: simplify me and move me to timgad_core.c file */
+static int module_timgad_task_perm(struct timgad_task *timgad_tsk,
+				   char *kmod_name)
+{
+	int ret;
+	unsigned long flag = 0;
+
+	ret = is_timgad_task_op_set(timgad_tsk,
+				    PR_TIMGAD_SET_MOD_RESTRICT, &flag);
+	if (ret < 0)
+		return ret;
+
+	/*
+	 * TODO: complete me
+	 *    * Allow net modules only with CAP_NET_ADMIN and other cases...
+	 *    * Other exotic cases when set to STRICT should be denied...
+	 *    * Inline logic
+	 */
+	switch (flag) {
+	case TIMGAD_MODULE_OFF:
+		ret = 0;
+		break;
+	case TIMGAD_MODULE_STRICT:
+		if (!capable(CAP_SYS_MODULE))
+			ret = -EPERM;
+		else
+			ret = 0;
+		break;
+	case TIMGAD_MODULE_NO_LOAD:
+		ret = -EPERM;
+		break;
+	}
+
+	return ret;
+}
+
+/* Set the given option in a timgad task */
+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, NULL);
+	return ret;
+}
+
+/* Get the given option from a timgad task */
+static int timgad_get_op_value(struct task_struct *tsk, unsigned long op)
+{
+	int ret = -EINVAL;
+	struct timgad_task *ttask;
+	unsigned long flag = 0;
+
+	ttask = get_timgad_task(tsk);
+	if (!ttask)
+		return ret;
+
+	ret = is_timgad_task_op_set(ttask, op, &flag);
+	put_timgad_task(ttask, NULL);
+
+	return ret < 0 ? ret : flag;
+}
+
+/* Copy Timgad context from parent to child */
+int timgad_task_copy(struct task_struct *tsk)
+{
+	int ret = 0;
+	struct timgad_task *tparent;
+	struct timgad_task *ttask;
+	unsigned long value = 0;
+
+	tparent = get_timgad_task(current);
+
+	/* Parent does not have a timgad context, nothing to do */
+	if (tparent == NULL)
+		return 0;
+
+	value = read_timgad_task_flags(tparent);
+
+	ttask = give_me_timgad_task(tsk, value);
+	if (IS_ERR(ttask))
+		ret = PTR_ERR(ttask);
+	else
+		ret = 0;
+
+	put_timgad_task(tparent, 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 0;
+
+	get_task_struct(myself);
+
+	switch (arg2) {
+	case PR_TIMGAD_SET_MOD_RESTRICT:
+		ret = timgad_set_op_value(myself,
+					  PR_TIMGAD_SET_MOD_RESTRICT,
+					  arg3);
+		break;
+	case PR_TIMGAD_GET_MOD_RESTRICT:
+		ret = timgad_get_op_value(myself,
+					  PR_TIMGAD_SET_MOD_RESTRICT);
+		break;
+	}
+
+	put_task_struct(myself);
+	return ret;
+}
+
+/*
+ * Free the specific task attached resources
+ * task_free() can be called from interrupt context
+ */
+void timgad_task_free(struct task_struct *tsk)
+{
+	release_timgad_task(tsk);
+}
+
+static int timgad_kernel_module_file(struct file *file)
+{
+	int ret = 0;
+	struct timgad_task *ttask;
+	struct task_struct *myself = current;
+
+	/* First check if the task allows that */
+	ttask = get_timgad_task(myself);
+	if (ttask != NULL) {
+		ret = module_timgad_task_perm(ttask, NULL);
+		put_timgad_task(ttask, NULL);
+	}
+
+	if (ret < 0)
+		return ret;
+
+	return timgad_has_global_sysctl_perm(PR_TIMGAD_GET_MOD_RESTRICT);
+}
+
+static int timgad_kernel_module_request(char *kmod_name)
+{
+	int ret = 0;
+	struct timgad_task *ttask;
+	struct task_struct *myself = current;
+
+	/* First check if the task allows that */
+	ttask = get_timgad_task(myself);
+	if (ttask != NULL) {
+		ret = module_timgad_task_perm(ttask, kmod_name);
+		put_timgad_task(ttask, NULL);
+	}
+
+	if (ret < 0)
+		return ret;
+
+	return timgad_has_global_sysctl_perm(PR_TIMGAD_GET_MOD_RESTRICT);
+}
+
+static int timgad_kernel_read_file(struct file *file,
+				   enum kernel_read_file_id id)
+{
+	int ret = 0;
+
+	switch (id) {
+	case READING_MODULE:
+		ret = timgad_kernel_module_file(file);
+		break;
+	default:
+		break;
+	}
+
+	return ret;
+}
+
+static struct security_hook_list timgad_hooks[] = {
+	LSM_HOOK_INIT(kernel_module_request, timgad_kernel_module_request),
+	LSM_HOOK_INIT(kernel_read_file, timgad_kernel_read_file),
+	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;
+
+	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);
+}
+
+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");
+}