diff mbox

[net-next,v6,04/11] landlock: Add LSM hooks related to filesystem

Message ID 20170328234650.19695-5-mic@digikod.net
State Changes Requested, archived
Delegated to: David Miller
Headers show

Commit Message

Mickaël Salaün March 28, 2017, 11:46 p.m. UTC
Handle 33 filesystem-related LSM hooks for the Landlock filesystem
event: LANDLOCK_SUBTYPE_EVENT_FS.

A Landlock event wrap LSM hooks for similar kernel object types (e.g.
struct file, struct path...). Multiple LSM hooks can trigger the same
Landlock event.

Landlock handle nine coarse-grained actions: read, write, execute, new,
get, remove, ioctl, lock and fcntl. Each of them abstract LSM hook
access control in a way that can be extended in the future.

The Landlock LSM hook registration is done after other LSM to only run
actions from user-space, via eBPF programs, if the access was granted by
major (privileged) LSMs.

Changes since v5:
* split hooks.[ch] into hooks.[ch] and hooks_fs.[ch]
* add more documentation
* cosmetic fixes

Changes since v4:
* add LSM hook abstraction called Landlock event
  * use the compiler type checking to verify hooks use by an event
  * handle all filesystem related LSM hooks (e.g. file_permission,
    mmap_file, sb_mount...)
* register BPF programs for Landlock just after LSM hooks registration
* move hooks registration after other LSMs
* add failsafes to check if a hook is not used by the kernel
* allow partial raw value access form the context (needed for programs
  generated by LLVM)

Changes since v3:
* split commit
* add hooks dealing with struct inode and struct path pointers:
  inode_permission and inode_getattr
* add abstraction over eBPF helper arguments thanks to wrapping structs

Signed-off-by: Mickaël Salaün <mic@digikod.net>
Cc: Alexei Starovoitov <ast@kernel.org>
Cc: Andy Lutomirski <luto@amacapital.net>
Cc: Daniel Borkmann <daniel@iogearbox.net>
Cc: David S. Miller <davem@davemloft.net>
Cc: James Morris <james.l.morris@oracle.com>
Cc: Kees Cook <keescook@chromium.org>
Cc: Serge E. Hallyn <serge@hallyn.com>
---
 include/linux/lsm_hooks.h    |   5 +
 security/landlock/Makefile   |   4 +-
 security/landlock/hooks.c    | 115 +++++++++
 security/landlock/hooks.h    | 177 ++++++++++++++
 security/landlock/hooks_fs.c | 563 +++++++++++++++++++++++++++++++++++++++++++
 security/landlock/hooks_fs.h |  19 ++
 security/landlock/init.c     |  13 +
 security/security.c          |   7 +-
 8 files changed, 901 insertions(+), 2 deletions(-)
 create mode 100644 security/landlock/hooks.c
 create mode 100644 security/landlock/hooks.h
 create mode 100644 security/landlock/hooks_fs.c
 create mode 100644 security/landlock/hooks_fs.h

Comments

kernel test robot March 29, 2017, 3:18 p.m. UTC | #1
Hi Mickaël,

[auto build test ERROR on net-next/master]

url:    https://github.com/0day-ci/linux/commits/Micka-l-Sala-n/Landlock-LSM-Toward-unprivileged-sandboxing/20170329-211258
config: i386-allmodconfig (attached as .config)
compiler: gcc-6 (Debian 6.2.0-3) 6.2.0 20160901
reproduce:
        # save the attached .config to linux build tree
        make ARCH=i386 

All error/warnings (new ones prefixed by >>):

   In file included from security/landlock/hooks.c:12:0:
   security/landlock/hooks.c: In function 'landlock_decide':
>> arch/x86/include/asm/processor.h:824:39: error: implicit declaration of function 'task_stack_page' [-Werror=implicit-function-declaration]
     unsigned long __ptr = (unsigned long)task_stack_page(task); \
                                          ^
>> security/landlock/hooks.c:107:41: note: in expansion of macro 'task_pt_regs'
      .syscall_nr = syscall_get_nr(current, task_pt_regs(current)),
                                            ^~~~~~~~~~~~
   security/landlock/hooks.c:104:26: warning: unused variable 'ctx' [-Wunused-variable]
     struct landlock_context ctx = {
                             ^~~
   security/landlock/hooks.c:102:6: warning: unused variable 'event_idx' [-Wunused-variable]
     u32 event_idx = get_index(event);
         ^~~~~~~~~
   cc1: some warnings being treated as errors

vim +/task_stack_page +824 arch/x86/include/asm/processor.h

2f66dcc9 include/asm-x86/processor.h      Glauber de Oliveira Costa 2008-01-30  818   * Therefore beware: accessing the ss/esp fields of the
2f66dcc9 include/asm-x86/processor.h      Glauber de Oliveira Costa 2008-01-30  819   * "struct pt_regs" is possible, but they may contain the
2f66dcc9 include/asm-x86/processor.h      Glauber de Oliveira Costa 2008-01-30  820   * completely wrong values.
2f66dcc9 include/asm-x86/processor.h      Glauber de Oliveira Costa 2008-01-30  821   */
2f66dcc9 include/asm-x86/processor.h      Glauber de Oliveira Costa 2008-01-30  822  #define task_pt_regs(task) \
2f66dcc9 include/asm-x86/processor.h      Glauber de Oliveira Costa 2008-01-30  823  ({									\
5c39403e arch/x86/include/asm/processor.h Denys Vlasenko            2015-03-13 @824  	unsigned long __ptr = (unsigned long)task_stack_page(task);	\
5c39403e arch/x86/include/asm/processor.h Denys Vlasenko            2015-03-13  825  	__ptr += THREAD_SIZE - TOP_OF_KERNEL_STACK_PADDING;		\
5c39403e arch/x86/include/asm/processor.h Denys Vlasenko            2015-03-13  826  	((struct pt_regs *)__ptr) - 1;					\
2f66dcc9 include/asm-x86/processor.h      Glauber de Oliveira Costa 2008-01-30  827  })

:::::: The code at line 824 was first introduced by commit
:::::: 5c39403e004bec75ce0c549541be5479595d6ad0 x86/asm/entry: Simplify task_pt_regs() macro definition

:::::: TO: Denys Vlasenko <dvlasenk@redhat.com>
:::::: CC: Ingo Molnar <mingo@kernel.org>

---
0-DAY kernel test infrastructure                Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all                   Intel Corporation
Kees Cook April 18, 2017, 10:17 p.m. UTC | #2
On Tue, Mar 28, 2017 at 4:46 PM, Mickaël Salaün <mic@digikod.net> wrote:
> Handle 33 filesystem-related LSM hooks for the Landlock filesystem
> event: LANDLOCK_SUBTYPE_EVENT_FS.
>
> A Landlock event wrap LSM hooks for similar kernel object types (e.g.
> struct file, struct path...). Multiple LSM hooks can trigger the same
> Landlock event.
>
> Landlock handle nine coarse-grained actions: read, write, execute, new,
> get, remove, ioctl, lock and fcntl. Each of them abstract LSM hook
> access control in a way that can be extended in the future.
>
> The Landlock LSM hook registration is done after other LSM to only run
> actions from user-space, via eBPF programs, if the access was granted by
> major (privileged) LSMs.
>
> Changes since v5:
> * split hooks.[ch] into hooks.[ch] and hooks_fs.[ch]
> * add more documentation
> * cosmetic fixes
>
> Changes since v4:
> * add LSM hook abstraction called Landlock event
>   * use the compiler type checking to verify hooks use by an event
>   * handle all filesystem related LSM hooks (e.g. file_permission,
>     mmap_file, sb_mount...)
> * register BPF programs for Landlock just after LSM hooks registration
> * move hooks registration after other LSMs
> * add failsafes to check if a hook is not used by the kernel
> * allow partial raw value access form the context (needed for programs
>   generated by LLVM)
>
> Changes since v3:
> * split commit
> * add hooks dealing with struct inode and struct path pointers:
>   inode_permission and inode_getattr
> * add abstraction over eBPF helper arguments thanks to wrapping structs
>
> Signed-off-by: Mickaël Salaün <mic@digikod.net>
> Cc: Alexei Starovoitov <ast@kernel.org>
> Cc: Andy Lutomirski <luto@amacapital.net>
> Cc: Daniel Borkmann <daniel@iogearbox.net>
> Cc: David S. Miller <davem@davemloft.net>
> Cc: James Morris <james.l.morris@oracle.com>
> Cc: Kees Cook <keescook@chromium.org>
> Cc: Serge E. Hallyn <serge@hallyn.com>
> ---
>  include/linux/lsm_hooks.h    |   5 +
>  security/landlock/Makefile   |   4 +-
>  security/landlock/hooks.c    | 115 +++++++++
>  security/landlock/hooks.h    | 177 ++++++++++++++
>  security/landlock/hooks_fs.c | 563 +++++++++++++++++++++++++++++++++++++++++++
>  security/landlock/hooks_fs.h |  19 ++
>  security/landlock/init.c     |  13 +
>  security/security.c          |   7 +-
>  8 files changed, 901 insertions(+), 2 deletions(-)
>  create mode 100644 security/landlock/hooks.c
>  create mode 100644 security/landlock/hooks.h
>  create mode 100644 security/landlock/hooks_fs.c
>  create mode 100644 security/landlock/hooks_fs.h
>
> diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h
> index e29d4c62a3c8..884289166a0e 100644
> --- a/include/linux/lsm_hooks.h
> +++ b/include/linux/lsm_hooks.h
> @@ -1920,5 +1920,10 @@ void __init loadpin_add_hooks(void);
>  #else
>  static inline void loadpin_add_hooks(void) { };
>  #endif
> +#ifdef CONFIG_SECURITY_LANDLOCK
> +extern void __init landlock_add_hooks(void);
> +#else
> +static inline void __init landlock_add_hooks(void) { }
> +#endif /* CONFIG_SECURITY_LANDLOCK */
>
>  #endif /* ! __LINUX_LSM_HOOKS_H */
> diff --git a/security/landlock/Makefile b/security/landlock/Makefile
> index 7205f9a7a2ee..c0db504a6335 100644
> --- a/security/landlock/Makefile
> +++ b/security/landlock/Makefile
> @@ -1,3 +1,5 @@
> +ccflags-$(CONFIG_SECURITY_LANDLOCK) += -Werror=unused-function

Why is this needed? If it can't be avoided, a comment should exist
here explaining why.

> [...]
> @@ -127,3 +132,11 @@ static struct bpf_prog_type_list bpf_landlock_type __ro_after_init = {
>         .ops = &bpf_landlock_ops,
>         .type = BPF_PROG_TYPE_LANDLOCK,
>  };
> +
> +void __init landlock_add_hooks(void)
> +{
> +       pr_info("landlock: Version %u", LANDLOCK_VERSION);
> +       landlock_add_hooks_fs();
> +       security_add_hooks(NULL, 0, "landlock");
> +       bpf_register_prog_type(&bpf_landlock_type);

I'm confused by the separation of hook registration here. The call to
security_add_hooks is with count=0 is especially weird. Why isn't this
just a single call with security_add_hooks(landlock_hooks,
ARRAY_SIZE(landlock_hooks), "landlock")?

> +}
> diff --git a/security/security.c b/security/security.c
> index d0e07f269b2d..a3e9f4625991 100644
> --- a/security/security.c
> +++ b/security/security.c
> @@ -64,10 +64,15 @@ int __init security_init(void)
>         loadpin_add_hooks();
>
>         /*
> -        * Load all the remaining security modules.
> +        * Load all remaining privileged security modules.
>          */
>         do_security_initcalls();
>
> +       /*
> +        * Load potentially-unprivileged security modules at the end.
> +        */
> +       landlock_add_hooks();

Oh, is this to make it last in the list? Is there a reason it has to be last?

-Kees
Mickaël Salaün April 18, 2017, 10:44 p.m. UTC | #3
On 19/04/2017 00:17, Kees Cook wrote:
> On Tue, Mar 28, 2017 at 4:46 PM, Mickaël Salaün <mic@digikod.net> wrote:
>> Handle 33 filesystem-related LSM hooks for the Landlock filesystem
>> event: LANDLOCK_SUBTYPE_EVENT_FS.
>>
>> A Landlock event wrap LSM hooks for similar kernel object types (e.g.
>> struct file, struct path...). Multiple LSM hooks can trigger the same
>> Landlock event.
>>
>> Landlock handle nine coarse-grained actions: read, write, execute, new,
>> get, remove, ioctl, lock and fcntl. Each of them abstract LSM hook
>> access control in a way that can be extended in the future.
>>
>> The Landlock LSM hook registration is done after other LSM to only run
>> actions from user-space, via eBPF programs, if the access was granted by
>> major (privileged) LSMs.
>>
>> Changes since v5:
>> * split hooks.[ch] into hooks.[ch] and hooks_fs.[ch]
>> * add more documentation
>> * cosmetic fixes
>>
>> Changes since v4:
>> * add LSM hook abstraction called Landlock event
>>   * use the compiler type checking to verify hooks use by an event
>>   * handle all filesystem related LSM hooks (e.g. file_permission,
>>     mmap_file, sb_mount...)
>> * register BPF programs for Landlock just after LSM hooks registration
>> * move hooks registration after other LSMs
>> * add failsafes to check if a hook is not used by the kernel
>> * allow partial raw value access form the context (needed for programs
>>   generated by LLVM)
>>
>> Changes since v3:
>> * split commit
>> * add hooks dealing with struct inode and struct path pointers:
>>   inode_permission and inode_getattr
>> * add abstraction over eBPF helper arguments thanks to wrapping structs
>>
>> Signed-off-by: Mickaël Salaün <mic@digikod.net>
>> Cc: Alexei Starovoitov <ast@kernel.org>
>> Cc: Andy Lutomirski <luto@amacapital.net>
>> Cc: Daniel Borkmann <daniel@iogearbox.net>
>> Cc: David S. Miller <davem@davemloft.net>
>> Cc: James Morris <james.l.morris@oracle.com>
>> Cc: Kees Cook <keescook@chromium.org>
>> Cc: Serge E. Hallyn <serge@hallyn.com>
>> ---
>>  include/linux/lsm_hooks.h    |   5 +
>>  security/landlock/Makefile   |   4 +-
>>  security/landlock/hooks.c    | 115 +++++++++
>>  security/landlock/hooks.h    | 177 ++++++++++++++
>>  security/landlock/hooks_fs.c | 563 +++++++++++++++++++++++++++++++++++++++++++
>>  security/landlock/hooks_fs.h |  19 ++
>>  security/landlock/init.c     |  13 +
>>  security/security.c          |   7 +-
>>  8 files changed, 901 insertions(+), 2 deletions(-)
>>  create mode 100644 security/landlock/hooks.c
>>  create mode 100644 security/landlock/hooks.h
>>  create mode 100644 security/landlock/hooks_fs.c
>>  create mode 100644 security/landlock/hooks_fs.h
>>
>> diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h
>> index e29d4c62a3c8..884289166a0e 100644
>> --- a/include/linux/lsm_hooks.h
>> +++ b/include/linux/lsm_hooks.h
>> @@ -1920,5 +1920,10 @@ void __init loadpin_add_hooks(void);
>>  #else
>>  static inline void loadpin_add_hooks(void) { };
>>  #endif
>> +#ifdef CONFIG_SECURITY_LANDLOCK
>> +extern void __init landlock_add_hooks(void);
>> +#else
>> +static inline void __init landlock_add_hooks(void) { }
>> +#endif /* CONFIG_SECURITY_LANDLOCK */
>>
>>  #endif /* ! __LINUX_LSM_HOOKS_H */
>> diff --git a/security/landlock/Makefile b/security/landlock/Makefile
>> index 7205f9a7a2ee..c0db504a6335 100644
>> --- a/security/landlock/Makefile
>> +++ b/security/landlock/Makefile
>> @@ -1,3 +1,5 @@
>> +ccflags-$(CONFIG_SECURITY_LANDLOCK) += -Werror=unused-function
> 
> Why is this needed? If it can't be avoided, a comment should exist
> here explaining why.

This is useful to catch defined but unused hooks: error out if a
HOOK_NEW_FS(foo) is not used with a HOOK_INIT_FS(foo) in the struct
security_hook_list landlock_hooks.

> 
>> [...]
>> @@ -127,3 +132,11 @@ static struct bpf_prog_type_list bpf_landlock_type __ro_after_init = {
>>         .ops = &bpf_landlock_ops,
>>         .type = BPF_PROG_TYPE_LANDLOCK,
>>  };
>> +
>> +void __init landlock_add_hooks(void)
>> +{
>> +       pr_info("landlock: Version %u", LANDLOCK_VERSION);
>> +       landlock_add_hooks_fs();
>> +       security_add_hooks(NULL, 0, "landlock");
>> +       bpf_register_prog_type(&bpf_landlock_type);
> 
> I'm confused by the separation of hook registration here. The call to
> security_add_hooks is with count=0 is especially weird. Why isn't this
> just a single call with security_add_hooks(landlock_hooks,
> ARRAY_SIZE(landlock_hooks), "landlock")?

Yes, this is ugly with the new security_add_hooks() with three arguments
but I wanted to split the hooks definition in multiple files.

The current security_add_hooks() use lsm_append(lsm, &lsm_names) which
is not exported. Unfortunately, calling multiple security_add_hooks()
with the same LSM name would register multiple names for the same LSM…
Is it OK if I modify this function to not add duplicated entries?


> 
>> +}
>> diff --git a/security/security.c b/security/security.c
>> index d0e07f269b2d..a3e9f4625991 100644
>> --- a/security/security.c
>> +++ b/security/security.c
>> @@ -64,10 +64,15 @@ int __init security_init(void)
>>         loadpin_add_hooks();
>>
>>         /*
>> -        * Load all the remaining security modules.
>> +        * Load all remaining privileged security modules.
>>          */
>>         do_security_initcalls();
>>
>> +       /*
>> +        * Load potentially-unprivileged security modules at the end.
>> +        */
>> +       landlock_add_hooks();
> 
> Oh, is this to make it last in the list? Is there a reason it has to be last?

Right, this is the intend. I'm not sure it is the only way to register
hooks, though.

For an unprivileged access-control, we don't want to give the ability to
any process to do some checks, through an eBPF program, on kernel
objects (e.g. files) if they should not be accessible (because of a
following LSM hook check).

 Mickaël
Casey Schaufler April 18, 2017, 11:16 p.m. UTC | #4
On 4/18/2017 3:44 PM, Mickaël Salaün wrote:
> On 19/04/2017 00:17, Kees Cook wrote:
>> On Tue, Mar 28, 2017 at 4:46 PM, Mickaël Salaün <mic@digikod.net> wrote:
>>> Handle 33 filesystem-related LSM hooks for the Landlock filesystem
>>> event: LANDLOCK_SUBTYPE_EVENT_FS.
>>>
>>> A Landlock event wrap LSM hooks for similar kernel object types (e.g.
>>> struct file, struct path...). Multiple LSM hooks can trigger the same
>>> Landlock event.
>>>
>>> Landlock handle nine coarse-grained actions: read, write, execute, new,
>>> get, remove, ioctl, lock and fcntl. Each of them abstract LSM hook
>>> access control in a way that can be extended in the future.
>>>
>>> The Landlock LSM hook registration is done after other LSM to only run
>>> actions from user-space, via eBPF programs, if the access was granted by
>>> major (privileged) LSMs.
>>>
>>> Changes since v5:
>>> * split hooks.[ch] into hooks.[ch] and hooks_fs.[ch]
>>> * add more documentation
>>> * cosmetic fixes
>>>
>>> Changes since v4:
>>> * add LSM hook abstraction called Landlock event
>>>   * use the compiler type checking to verify hooks use by an event
>>>   * handle all filesystem related LSM hooks (e.g. file_permission,
>>>     mmap_file, sb_mount...)
>>> * register BPF programs for Landlock just after LSM hooks registration
>>> * move hooks registration after other LSMs
>>> * add failsafes to check if a hook is not used by the kernel
>>> * allow partial raw value access form the context (needed for programs
>>>   generated by LLVM)
>>>
>>> Changes since v3:
>>> * split commit
>>> * add hooks dealing with struct inode and struct path pointers:
>>>   inode_permission and inode_getattr
>>> * add abstraction over eBPF helper arguments thanks to wrapping structs
>>>
>>> Signed-off-by: Mickaël Salaün <mic@digikod.net>
>>> Cc: Alexei Starovoitov <ast@kernel.org>
>>> Cc: Andy Lutomirski <luto@amacapital.net>
>>> Cc: Daniel Borkmann <daniel@iogearbox.net>
>>> Cc: David S. Miller <davem@davemloft.net>
>>> Cc: James Morris <james.l.morris@oracle.com>
>>> Cc: Kees Cook <keescook@chromium.org>
>>> Cc: Serge E. Hallyn <serge@hallyn.com>
>>> ---
>>>  include/linux/lsm_hooks.h    |   5 +
>>>  security/landlock/Makefile   |   4 +-
>>>  security/landlock/hooks.c    | 115 +++++++++
>>>  security/landlock/hooks.h    | 177 ++++++++++++++
>>>  security/landlock/hooks_fs.c | 563 +++++++++++++++++++++++++++++++++++++++++++
>>>  security/landlock/hooks_fs.h |  19 ++
>>>  security/landlock/init.c     |  13 +
>>>  security/security.c          |   7 +-
>>>  8 files changed, 901 insertions(+), 2 deletions(-)
>>>  create mode 100644 security/landlock/hooks.c
>>>  create mode 100644 security/landlock/hooks.h
>>>  create mode 100644 security/landlock/hooks_fs.c
>>>  create mode 100644 security/landlock/hooks_fs.h
>>>
>>> diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h
>>> index e29d4c62a3c8..884289166a0e 100644
>>> --- a/include/linux/lsm_hooks.h
>>> +++ b/include/linux/lsm_hooks.h
>>> @@ -1920,5 +1920,10 @@ void __init loadpin_add_hooks(void);
>>>  #else
>>>  static inline void loadpin_add_hooks(void) { };
>>>  #endif
>>> +#ifdef CONFIG_SECURITY_LANDLOCK
>>> +extern void __init landlock_add_hooks(void);
>>> +#else
>>> +static inline void __init landlock_add_hooks(void) { }
>>> +#endif /* CONFIG_SECURITY_LANDLOCK */
>>>
>>>  #endif /* ! __LINUX_LSM_HOOKS_H */
>>> diff --git a/security/landlock/Makefile b/security/landlock/Makefile
>>> index 7205f9a7a2ee..c0db504a6335 100644
>>> --- a/security/landlock/Makefile
>>> +++ b/security/landlock/Makefile
>>> @@ -1,3 +1,5 @@
>>> +ccflags-$(CONFIG_SECURITY_LANDLOCK) += -Werror=unused-function
>> Why is this needed? If it can't be avoided, a comment should exist
>> here explaining why.
> This is useful to catch defined but unused hooks: error out if a
> HOOK_NEW_FS(foo) is not used with a HOOK_INIT_FS(foo) in the struct
> security_hook_list landlock_hooks.
>
>>> [...]
>>> @@ -127,3 +132,11 @@ static struct bpf_prog_type_list bpf_landlock_type __ro_after_init = {
>>>         .ops = &bpf_landlock_ops,
>>>         .type = BPF_PROG_TYPE_LANDLOCK,
>>>  };
>>> +
>>> +void __init landlock_add_hooks(void)
>>> +{
>>> +       pr_info("landlock: Version %u", LANDLOCK_VERSION);
>>> +       landlock_add_hooks_fs();
>>> +       security_add_hooks(NULL, 0, "landlock");
>>> +       bpf_register_prog_type(&bpf_landlock_type);
>> I'm confused by the separation of hook registration here. The call to
>> security_add_hooks is with count=0 is especially weird. Why isn't this
>> just a single call with security_add_hooks(landlock_hooks,
>> ARRAY_SIZE(landlock_hooks), "landlock")?
> Yes, this is ugly with the new security_add_hooks() with three arguments
> but I wanted to split the hooks definition in multiple files.

Why? I'll buy a good argument, but there are dangers in
allowing multiple calls to security_add_hooks(). 

>
> The current security_add_hooks() use lsm_append(lsm, &lsm_names) which
> is not exported. Unfortunately, calling multiple security_add_hooks()
> with the same LSM name would register multiple names for the same LSM…
> Is it OK if I modify this function to not add duplicated entries?

It may seem absurd, but it's conceivable that a module might
have two hooks it wants called. My example is a module that
counts the number of times SELinux denies a process access to
things (which needs to be called before and after SELinux in
order to detect denials) and takes "appropriate action" if
too many denials occur. It would be weird, wonky and hackish,
but that never stopped anybody before.

>
>
>>> +}
>>> diff --git a/security/security.c b/security/security.c
>>> index d0e07f269b2d..a3e9f4625991 100644
>>> --- a/security/security.c
>>> +++ b/security/security.c
>>> @@ -64,10 +64,15 @@ int __init security_init(void)
>>>         loadpin_add_hooks();
>>>
>>>         /*
>>> -        * Load all the remaining security modules.
>>> +        * Load all remaining privileged security modules.
>>>          */
>>>         do_security_initcalls();
>>>
>>> +       /*
>>> +        * Load potentially-unprivileged security modules at the end.
>>> +        */
>>> +       landlock_add_hooks();
>> Oh, is this to make it last in the list? Is there a reason it has to be last?
> Right, this is the intend. I'm not sure it is the only way to register
> hooks, though.
>
> For an unprivileged access-control, we don't want to give the ability to
> any process to do some checks, through an eBPF program, on kernel
> objects (e.g. files) if they should not be accessible (because of a
> following LSM hook check).
>
>  Mickaël
>
Kees Cook April 18, 2017, 11:39 p.m. UTC | #5
On Tue, Apr 18, 2017 at 3:44 PM, Mickaël Salaün <mic@digikod.net> wrote:
>
> On 19/04/2017 00:17, Kees Cook wrote:
>> On Tue, Mar 28, 2017 at 4:46 PM, Mickaël Salaün <mic@digikod.net> wrote:
>>> Handle 33 filesystem-related LSM hooks for the Landlock filesystem
>>> event: LANDLOCK_SUBTYPE_EVENT_FS.
>>>
>>> A Landlock event wrap LSM hooks for similar kernel object types (e.g.
>>> struct file, struct path...). Multiple LSM hooks can trigger the same
>>> Landlock event.
>>>
>>> Landlock handle nine coarse-grained actions: read, write, execute, new,
>>> get, remove, ioctl, lock and fcntl. Each of them abstract LSM hook
>>> access control in a way that can be extended in the future.
>>>
>>> The Landlock LSM hook registration is done after other LSM to only run
>>> actions from user-space, via eBPF programs, if the access was granted by
>>> major (privileged) LSMs.
>>>
>>> Changes since v5:
>>> * split hooks.[ch] into hooks.[ch] and hooks_fs.[ch]
>>> * add more documentation
>>> * cosmetic fixes
>>>
>>> Changes since v4:
>>> * add LSM hook abstraction called Landlock event
>>>   * use the compiler type checking to verify hooks use by an event
>>>   * handle all filesystem related LSM hooks (e.g. file_permission,
>>>     mmap_file, sb_mount...)
>>> * register BPF programs for Landlock just after LSM hooks registration
>>> * move hooks registration after other LSMs
>>> * add failsafes to check if a hook is not used by the kernel
>>> * allow partial raw value access form the context (needed for programs
>>>   generated by LLVM)
>>>
>>> Changes since v3:
>>> * split commit
>>> * add hooks dealing with struct inode and struct path pointers:
>>>   inode_permission and inode_getattr
>>> * add abstraction over eBPF helper arguments thanks to wrapping structs
>>>
>>> Signed-off-by: Mickaël Salaün <mic@digikod.net>
>>> Cc: Alexei Starovoitov <ast@kernel.org>
>>> Cc: Andy Lutomirski <luto@amacapital.net>
>>> Cc: Daniel Borkmann <daniel@iogearbox.net>
>>> Cc: David S. Miller <davem@davemloft.net>
>>> Cc: James Morris <james.l.morris@oracle.com>
>>> Cc: Kees Cook <keescook@chromium.org>
>>> Cc: Serge E. Hallyn <serge@hallyn.com>
>>> ---
>>>  include/linux/lsm_hooks.h    |   5 +
>>>  security/landlock/Makefile   |   4 +-
>>>  security/landlock/hooks.c    | 115 +++++++++
>>>  security/landlock/hooks.h    | 177 ++++++++++++++
>>>  security/landlock/hooks_fs.c | 563 +++++++++++++++++++++++++++++++++++++++++++
>>>  security/landlock/hooks_fs.h |  19 ++
>>>  security/landlock/init.c     |  13 +
>>>  security/security.c          |   7 +-
>>>  8 files changed, 901 insertions(+), 2 deletions(-)
>>>  create mode 100644 security/landlock/hooks.c
>>>  create mode 100644 security/landlock/hooks.h
>>>  create mode 100644 security/landlock/hooks_fs.c
>>>  create mode 100644 security/landlock/hooks_fs.h
>>>
>>> diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h
>>> index e29d4c62a3c8..884289166a0e 100644
>>> --- a/include/linux/lsm_hooks.h
>>> +++ b/include/linux/lsm_hooks.h
>>> @@ -1920,5 +1920,10 @@ void __init loadpin_add_hooks(void);
>>>  #else
>>>  static inline void loadpin_add_hooks(void) { };
>>>  #endif
>>> +#ifdef CONFIG_SECURITY_LANDLOCK
>>> +extern void __init landlock_add_hooks(void);
>>> +#else
>>> +static inline void __init landlock_add_hooks(void) { }
>>> +#endif /* CONFIG_SECURITY_LANDLOCK */
>>>
>>>  #endif /* ! __LINUX_LSM_HOOKS_H */
>>> diff --git a/security/landlock/Makefile b/security/landlock/Makefile
>>> index 7205f9a7a2ee..c0db504a6335 100644
>>> --- a/security/landlock/Makefile
>>> +++ b/security/landlock/Makefile
>>> @@ -1,3 +1,5 @@
>>> +ccflags-$(CONFIG_SECURITY_LANDLOCK) += -Werror=unused-function
>>
>> Why is this needed? If it can't be avoided, a comment should exist
>> here explaining why.
>
> This is useful to catch defined but unused hooks: error out if a
> HOOK_NEW_FS(foo) is not used with a HOOK_INIT_FS(foo) in the struct
> security_hook_list landlock_hooks.

Gotcha. Please convert into a comment for the next revision. :)

>
>>
>>> [...]
>>> @@ -127,3 +132,11 @@ static struct bpf_prog_type_list bpf_landlock_type __ro_after_init = {
>>>         .ops = &bpf_landlock_ops,
>>>         .type = BPF_PROG_TYPE_LANDLOCK,
>>>  };
>>> +
>>> +void __init landlock_add_hooks(void)
>>> +{
>>> +       pr_info("landlock: Version %u", LANDLOCK_VERSION);
>>> +       landlock_add_hooks_fs();
>>> +       security_add_hooks(NULL, 0, "landlock");
>>> +       bpf_register_prog_type(&bpf_landlock_type);
>>
>> I'm confused by the separation of hook registration here. The call to
>> security_add_hooks is with count=0 is especially weird. Why isn't this
>> just a single call with security_add_hooks(landlock_hooks,
>> ARRAY_SIZE(landlock_hooks), "landlock")?
>
> Yes, this is ugly with the new security_add_hooks() with three arguments
> but I wanted to split the hooks definition in multiple files.
>
> The current security_add_hooks() use lsm_append(lsm, &lsm_names) which
> is not exported. Unfortunately, calling multiple security_add_hooks()
> with the same LSM name would register multiple names for the same LSM…
> Is it OK if I modify this function to not add duplicated entries?

I'm not sure which would be more sane: a single table (constructed
from header-defined function declarations), or changes to this core
area.

>>> diff --git a/security/security.c b/security/security.c
>>> index d0e07f269b2d..a3e9f4625991 100644
>>> --- a/security/security.c
>>> +++ b/security/security.c
>>> @@ -64,10 +64,15 @@ int __init security_init(void)
>>>         loadpin_add_hooks();
>>>
>>>         /*
>>> -        * Load all the remaining security modules.
>>> +        * Load all remaining privileged security modules.
>>>          */
>>>         do_security_initcalls();
>>>
>>> +       /*
>>> +        * Load potentially-unprivileged security modules at the end.
>>> +        */
>>> +       landlock_add_hooks();
>>
>> Oh, is this to make it last in the list? Is there a reason it has to be last?
>
> Right, this is the intend. I'm not sure it is the only way to register
> hooks, though.
>
> For an unprivileged access-control, we don't want to give the ability to
> any process to do some checks, through an eBPF program, on kernel
> objects (e.g. files) if they should not be accessible (because of a
> following LSM hook check).

Ah right, yes. I thought some of that was already going to be handled
by getting handles to things, but yeah, best to run last. (If you can
expand the comment above the call to landlock_add_hooks() that would
be great.)

-Kees
Kees Cook April 18, 2017, 11:40 p.m. UTC | #6
On Tue, Apr 18, 2017 at 4:16 PM, Casey Schaufler <casey@schaufler-ca.com> wrote:
> On 4/18/2017 3:44 PM, Mickaël Salaün wrote:
>> On 19/04/2017 00:17, Kees Cook wrote:
>>> On Tue, Mar 28, 2017 at 4:46 PM, Mickaël Salaün <mic@digikod.net> wrote:
>>>> +void __init landlock_add_hooks(void)
>>>> +{
>>>> +       pr_info("landlock: Version %u", LANDLOCK_VERSION);
>>>> +       landlock_add_hooks_fs();
>>>> +       security_add_hooks(NULL, 0, "landlock");
>>>> +       bpf_register_prog_type(&bpf_landlock_type);
>>> I'm confused by the separation of hook registration here. The call to
>>> security_add_hooks is with count=0 is especially weird. Why isn't this
>>> just a single call with security_add_hooks(landlock_hooks,
>>> ARRAY_SIZE(landlock_hooks), "landlock")?
>> Yes, this is ugly with the new security_add_hooks() with three arguments
>> but I wanted to split the hooks definition in multiple files.
>
> Why? I'll buy a good argument, but there are dangers in
> allowing multiple calls to security_add_hooks().
>
>>
>> The current security_add_hooks() use lsm_append(lsm, &lsm_names) which
>> is not exported. Unfortunately, calling multiple security_add_hooks()
>> with the same LSM name would register multiple names for the same LSM…
>> Is it OK if I modify this function to not add duplicated entries?
>
> It may seem absurd, but it's conceivable that a module might
> have two hooks it wants called. My example is a module that
> counts the number of times SELinux denies a process access to
> things (which needs to be called before and after SELinux in
> order to detect denials) and takes "appropriate action" if
> too many denials occur. It would be weird, wonky and hackish,
> but that never stopped anybody before.

If ends up being sane and clear, I'm fine with allowing multiple calls.

-Kees
Mickaël Salaün April 19, 2017, 10:03 p.m. UTC | #7
On 19/04/2017 01:40, Kees Cook wrote:
> On Tue, Apr 18, 2017 at 4:16 PM, Casey Schaufler <casey@schaufler-ca.com> wrote:
>> On 4/18/2017 3:44 PM, Mickaël Salaün wrote:
>>> On 19/04/2017 00:17, Kees Cook wrote:
>>>> On Tue, Mar 28, 2017 at 4:46 PM, Mickaël Salaün <mic@digikod.net> wrote:
>>>>> +void __init landlock_add_hooks(void)
>>>>> +{
>>>>> +       pr_info("landlock: Version %u", LANDLOCK_VERSION);
>>>>> +       landlock_add_hooks_fs();
>>>>> +       security_add_hooks(NULL, 0, "landlock");
>>>>> +       bpf_register_prog_type(&bpf_landlock_type);
>>>> I'm confused by the separation of hook registration here. The call to
>>>> security_add_hooks is with count=0 is especially weird. Why isn't this
>>>> just a single call with security_add_hooks(landlock_hooks,
>>>> ARRAY_SIZE(landlock_hooks), "landlock")?
>>> Yes, this is ugly with the new security_add_hooks() with three arguments
>>> but I wanted to split the hooks definition in multiple files.
>>
>> Why? I'll buy a good argument, but there are dangers in
>> allowing multiple calls to security_add_hooks().

I prefer to have one file per hook "family" (e.g. filesystem, network,
ptrace…). This reduce the mess with all the included files (needed for
LSM hook argument types) and make the files easier to read, understand
and maintain.

>>
>>>
>>> The current security_add_hooks() use lsm_append(lsm, &lsm_names) which
>>> is not exported. Unfortunately, calling multiple security_add_hooks()
>>> with the same LSM name would register multiple names for the same LSM…
>>> Is it OK if I modify this function to not add duplicated entries?
>>
>> It may seem absurd, but it's conceivable that a module might
>> have two hooks it wants called. My example is a module that
>> counts the number of times SELinux denies a process access to
>> things (which needs to be called before and after SELinux in
>> order to detect denials) and takes "appropriate action" if
>> too many denials occur. It would be weird, wonky and hackish,
>> but that never stopped anybody before.

Right, but now, with the new lsm_append(), module names are concatenated
("%s,%s") in the lsm_names variable. It would be nice to not pollute
this string with multiple time the same module name.

> 
> If ends up being sane and clear, I'm fine with allowing multiple calls.
> 
> -Kees
>
Casey Schaufler April 19, 2017, 11:58 p.m. UTC | #8
On 4/19/2017 3:03 PM, Mickaël Salaün wrote:
> On 19/04/2017 01:40, Kees Cook wrote:
>> On Tue, Apr 18, 2017 at 4:16 PM, Casey Schaufler <casey@schaufler-ca.com> wrote:
>>> On 4/18/2017 3:44 PM, Mickaël Salaün wrote:
>>>> On 19/04/2017 00:17, Kees Cook wrote:
>>>>> On Tue, Mar 28, 2017 at 4:46 PM, Mickaël Salaün <mic@digikod.net> wrote:
>>>>>> +void __init landlock_add_hooks(void)
>>>>>> +{
>>>>>> +       pr_info("landlock: Version %u", LANDLOCK_VERSION);
>>>>>> +       landlock_add_hooks_fs();
>>>>>> +       security_add_hooks(NULL, 0, "landlock");
>>>>>> +       bpf_register_prog_type(&bpf_landlock_type);
>>>>> I'm confused by the separation of hook registration here. The call to
>>>>> security_add_hooks is with count=0 is especially weird. Why isn't this
>>>>> just a single call with security_add_hooks(landlock_hooks,
>>>>> ARRAY_SIZE(landlock_hooks), "landlock")?
>>>> Yes, this is ugly with the new security_add_hooks() with three arguments
>>>> but I wanted to split the hooks definition in multiple files.
>>> Why? I'll buy a good argument, but there are dangers in
>>> allowing multiple calls to security_add_hooks().
> I prefer to have one file per hook "family" (e.g. filesystem, network,
> ptrace…). This reduce the mess with all the included files (needed for
> LSM hook argument types) and make the files easier to read, understand
> and maintain.

Yeah, there's that tradeoff and it really is a matter
of taste I suppose.

>>>> The current security_add_hooks() use lsm_append(lsm, &lsm_names) which
>>>> is not exported. Unfortunately, calling multiple security_add_hooks()
>>>> with the same LSM name would register multiple names for the same LSM…
>>>> Is it OK if I modify this function to not add duplicated entries?
>>> It may seem absurd, but it's conceivable that a module might
>>> have two hooks it wants called. My example is a module that
>>> counts the number of times SELinux denies a process access to
>>> things (which needs to be called before and after SELinux in
>>> order to detect denials) and takes "appropriate action" if
>>> too many denials occur. It would be weird, wonky and hackish,
>>> but that never stopped anybody before.
> Right, but now, with the new lsm_append(), module names are concatenated
> ("%s,%s") in the lsm_names variable. It would be nice to not pollute
> this string with multiple time the same module name.

All it would take is a check that the module name
isn't already on the list. It's a trivial change.

>> If ends up being sane and clear, I'm fine with allowing multiple calls.
>>
>> -Kees
>>
Kees Cook April 20, 2017, 1:48 a.m. UTC | #9
On Wed, Apr 19, 2017 at 3:03 PM, Mickaël Salaün <mic@digikod.net> wrote:
>
> On 19/04/2017 01:40, Kees Cook wrote:
>> On Tue, Apr 18, 2017 at 4:16 PM, Casey Schaufler <casey@schaufler-ca.com> wrote:
>>> On 4/18/2017 3:44 PM, Mickaël Salaün wrote:
>>>> On 19/04/2017 00:17, Kees Cook wrote:
>>>>> On Tue, Mar 28, 2017 at 4:46 PM, Mickaël Salaün <mic@digikod.net> wrote:
>>>>>> +void __init landlock_add_hooks(void)
>>>>>> +{
>>>>>> +       pr_info("landlock: Version %u", LANDLOCK_VERSION);
>>>>>> +       landlock_add_hooks_fs();
>>>>>> +       security_add_hooks(NULL, 0, "landlock");
>>>>>> +       bpf_register_prog_type(&bpf_landlock_type);
>>>>> I'm confused by the separation of hook registration here. The call to
>>>>> security_add_hooks is with count=0 is especially weird. Why isn't this
>>>>> just a single call with security_add_hooks(landlock_hooks,
>>>>> ARRAY_SIZE(landlock_hooks), "landlock")?
>>>> Yes, this is ugly with the new security_add_hooks() with three arguments
>>>> but I wanted to split the hooks definition in multiple files.
>>>
>>> Why? I'll buy a good argument, but there are dangers in
>>> allowing multiple calls to security_add_hooks().
>
> I prefer to have one file per hook "family" (e.g. filesystem, network,
> ptrace…). This reduce the mess with all the included files (needed for
> LSM hook argument types) and make the files easier to read, understand
> and maintain.
>
>>>
>>>>
>>>> The current security_add_hooks() use lsm_append(lsm, &lsm_names) which
>>>> is not exported. Unfortunately, calling multiple security_add_hooks()
>>>> with the same LSM name would register multiple names for the same LSM…
>>>> Is it OK if I modify this function to not add duplicated entries?
>>>
>>> It may seem absurd, but it's conceivable that a module might
>>> have two hooks it wants called. My example is a module that
>>> counts the number of times SELinux denies a process access to
>>> things (which needs to be called before and after SELinux in
>>> order to detect denials) and takes "appropriate action" if
>>> too many denials occur. It would be weird, wonky and hackish,
>>> but that never stopped anybody before.
>
> Right, but now, with the new lsm_append(), module names are concatenated
> ("%s,%s") in the lsm_names variable. It would be nice to not pollute
> this string with multiple time the same module name.

Perhaps security_add_hooks could be modified to accept a NULL lsm to
skip the lsm_append() call, so it could do:

security_add_hooks(hooks1, count1, NULL);
security_add_hooks(hooks2, count2, NULL);
security_add_hooks(NULL, 0, "landlock");

Or, as Casey suggests, disregard adding the name when it already exists:

security_add_hooks(hooks1, count1, "landlock");
security_add_hooks(hooks2, count2, "landlock");

Yeah, I think I prefer this...

-Kees

>
>>
>> If ends up being sane and clear, I'm fine with allowing multiple calls.
>>
>> -Kees
>>
>
diff mbox

Patch

diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h
index e29d4c62a3c8..884289166a0e 100644
--- a/include/linux/lsm_hooks.h
+++ b/include/linux/lsm_hooks.h
@@ -1920,5 +1920,10 @@  void __init loadpin_add_hooks(void);
 #else
 static inline void loadpin_add_hooks(void) { };
 #endif
+#ifdef CONFIG_SECURITY_LANDLOCK
+extern void __init landlock_add_hooks(void);
+#else
+static inline void __init landlock_add_hooks(void) { }
+#endif /* CONFIG_SECURITY_LANDLOCK */
 
 #endif /* ! __LINUX_LSM_HOOKS_H */
diff --git a/security/landlock/Makefile b/security/landlock/Makefile
index 7205f9a7a2ee..c0db504a6335 100644
--- a/security/landlock/Makefile
+++ b/security/landlock/Makefile
@@ -1,3 +1,5 @@ 
+ccflags-$(CONFIG_SECURITY_LANDLOCK) += -Werror=unused-function
+
 obj-$(CONFIG_SECURITY_LANDLOCK) := landlock.o
 
-landlock-y := init.o
+landlock-y := init.o hooks.o hooks_fs.o
diff --git a/security/landlock/hooks.c b/security/landlock/hooks.c
new file mode 100644
index 000000000000..eaee8162ff70
--- /dev/null
+++ b/security/landlock/hooks.c
@@ -0,0 +1,115 @@ 
+/*
+ * Landlock LSM - hooks helpers
+ *
+ * Copyright © 2017 Mickaël Salaün <mic@digikod.net>
+ *
+ * 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 <asm/current.h>
+#include <asm/processor.h> /* task_pt_regs() */
+#include <asm/syscall.h> /* syscall_get_nr(), syscall_get_arch() */
+#include <linux/bpf.h> /* enum bpf_access_type, struct landlock_context */
+#include <linux/err.h> /* EPERM */
+#include <linux/filter.h> /* BPF_PROG_RUN() */
+#include <linux/landlock.h> /* struct landlock_rule */
+#include <linux/lsm_hooks.h>
+#include <linux/rculist.h> /* list_add_tail_rcu */
+#include <linux/stddef.h> /* offsetof */
+
+#include "common.h" /* get_index() */
+#include "hooks.h" /* CTX_ARG_NB */
+
+
+__init void landlock_register_hooks(struct security_hook_list *hooks, int count)
+{
+	int i;
+
+	for (i = 0; i < count; i++) {
+		hooks[i].lsm = "landlock";
+		list_add_tail_rcu(&hooks[i].list, hooks[i].head);
+	}
+}
+
+bool landlock_is_valid_access(int off, int size, enum bpf_access_type type,
+		enum bpf_reg_type *reg_type,
+		enum bpf_reg_type ctx_types[CTX_ARG_NB],
+		union bpf_prog_subtype *prog_subtype)
+{
+	int max_size;
+
+	if (type != BPF_READ)
+		return false;
+	if (off < 0 || off >= sizeof(struct landlock_context))
+		return false;
+	if (size <= 0 || size > sizeof(__u64))
+		return false;
+
+	/* set max size */
+	switch (off) {
+	case offsetof(struct landlock_context, arch):
+	case offsetof(struct landlock_context, syscall_nr):
+	case offsetof(struct landlock_context, syscall_cmd):
+	case offsetof(struct landlock_context, event):
+		max_size = sizeof(__u32);
+		break;
+	case offsetof(struct landlock_context, status):
+	case offsetof(struct landlock_context, arg1):
+	case offsetof(struct landlock_context, arg2):
+		max_size = sizeof(__u64);
+		break;
+	default:
+		return false;
+	}
+
+	/* set register type */
+	switch (off) {
+	case offsetof(struct landlock_context, arg1):
+		*reg_type = ctx_types[0];
+		break;
+	case offsetof(struct landlock_context, arg2):
+		*reg_type = ctx_types[1];
+		break;
+	default:
+		*reg_type = UNKNOWN_VALUE;
+	}
+
+	/* check memory range access */
+	switch (*reg_type) {
+	case NOT_INIT:
+		return false;
+	case UNKNOWN_VALUE:
+	case CONST_IMM:
+		/* allow partial raw value */
+		if (size > max_size)
+			return false;
+		break;
+	default:
+		/* deny partial pointer */
+		if (size != max_size)
+			return false;
+	}
+
+	return true;
+}
+
+int landlock_decide(enum landlock_subtype_event event,
+		__u64 ctx_values[CTX_ARG_NB], u32 cmd, const char *hook)
+{
+	bool deny = false;
+	u32 event_idx = get_index(event);
+
+	struct landlock_context ctx = {
+		.status = 0,
+		.arch = syscall_get_arch(),
+		.syscall_nr = syscall_get_nr(current, task_pt_regs(current)),
+		.syscall_cmd = cmd,
+		.event = event,
+		.arg1 = ctx_values[0],
+		.arg2 = ctx_values[1],
+	};
+
+	return deny ? -EPERM : 0;
+}
diff --git a/security/landlock/hooks.h b/security/landlock/hooks.h
new file mode 100644
index 000000000000..2e180f6ed86b
--- /dev/null
+++ b/security/landlock/hooks.h
@@ -0,0 +1,177 @@ 
+/*
+ * Landlock LSM - hooks helpers
+ *
+ * Copyright © 2017 Mickaël Salaün <mic@digikod.net>
+ *
+ * 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 <asm/current.h>
+#include <linux/bpf.h> /* enum bpf_access_type */
+#include <linux/lsm_hooks.h>
+#include <linux/sched.h> /* struct task_struct */
+
+/* separators */
+#define SEP_COMMA() ,
+#define SEP_SPACE()
+#define SEP_AND() &&
+
+#define MAP2x1(s, m, x1, x2, ...) m(x1, x2)
+#define MAP2x2(s, m, x1, x2, ...) m(x1, x2) s() MAP2x1(s, m, __VA_ARGS__)
+#define MAP2x3(s, m, x1, x2, ...) m(x1, x2) s() MAP2x2(s, m, __VA_ARGS__)
+#define MAP2x4(s, m, x1, x2, ...) m(x1, x2) s() MAP2x3(s, m, __VA_ARGS__)
+#define MAP2x5(s, m, x1, x2, ...) m(x1, x2) s() MAP2x4(s, m, __VA_ARGS__)
+#define MAP2x6(s, m, x1, x2, ...) m(x1, x2) s() MAP2x5(s, m, __VA_ARGS__)
+#define MAP2x(n, ...) MAP2x##n(__VA_ARGS__)
+
+#define MAP1x1(s, m, x1, ...) m(x1)
+#define MAP1x2(s, m, x1, ...) m(x1) s() MAP1x1(s, m, __VA_ARGS__)
+#define MAP1x(n, ...) MAP1x##n(__VA_ARGS__)
+
+#define SKIP2x1(x1, x2, ...) __VA_ARGS__
+#define SKIP2x2(x1, x2, ...) SKIP2x1(__VA_ARGS__)
+#define SKIP2x3(x1, x2, ...) SKIP2x2(__VA_ARGS__)
+#define SKIP2x4(x1, x2, ...) SKIP2x3(__VA_ARGS__)
+#define SKIP2x5(x1, x2, ...) SKIP2x4(__VA_ARGS__)
+#define SKIP2x6(x1, x2, ...) SKIP2x5(__VA_ARGS__)
+#define SKIP2x(n, ...) SKIP2x##n(__VA_ARGS__)
+
+/* LSM hook argument helpers */
+#define MAP_HOOK_COMMA(n, ...) MAP2x(n, SEP_COMMA, __VA_ARGS__)
+
+#define GET_HOOK_TA(t, a) t a
+
+/* Landlock event argument helpers  */
+#define MAP_EVENT_COMMA(h, n, m, ...) MAP2x(n, SEP_COMMA, m, SKIP2x(h, __VA_ARGS__))
+#define MAP_EVENT_SPACE(h, n, m, ...) MAP2x(n, SEP_SPACE, m, SKIP2x(h, __VA_ARGS__))
+#define MAP_EVENT_AND(h, n, m, ...) MAP2x(n, SEP_AND, m, SKIP2x(h, __VA_ARGS__))
+
+#define GET_CMD(h, n, ...) SKIP2x(n, SKIP2x(h, __VA_ARGS__))
+
+#define EXPAND_TYPE(d) d##_TYPE
+#define EXPAND_BPF(d) d##_BPF
+#define EXPAND_C(d) d##_C
+
+#define GET_TYPE_BPF(t) EXPAND_BPF(t)
+#define GET_TYPE_C(t) EXPAND_C(t) *
+
+#define GET_EVENT_C(d, a) GET_TYPE_C(EXPAND_TYPE(d))
+#define GET_EVENT_U64(d, a) ((u64)(d##_VAL(a)))
+#define GET_EVENT_DEC(d, a) d##_DEC(a)
+#define GET_EVENT_OK(d, a) d##_OK(a)
+
+/**
+ * HOOK_ACCESS
+ *
+ * @EVENT: Landlock event name
+ * @NA: number of event arguments
+ *
+ * The __consistent_##EVENT() extern functions and __wrapcheck_* types are
+ * useful to catch inconsistencies in LSM hook definitions thanks to the
+ * compiler type checking.
+ */
+#define HOOK_ACCESS(EVENT, NA, ...)					\
+	inline bool landlock_is_valid_access_event_##EVENT(		\
+			int off, int size, enum bpf_access_type type,	\
+			enum bpf_reg_type *reg_type,			\
+			union bpf_prog_subtype *prog_subtype)		\
+	{								\
+		enum bpf_reg_type _ctx_types[CTX_ARG_NB] = {		\
+			MAP1x(NA, SEP_COMMA, GET_TYPE_BPF, __VA_ARGS__)	\
+		};							\
+		return landlock_is_valid_access(off, size, type,	\
+				reg_type, _ctx_types, prog_subtype);	\
+	}								\
+	extern void __consistent_##EVENT(				\
+			MAP1x(NA, SEP_COMMA, GET_TYPE_C, __VA_ARGS__))
+
+/**
+ * HOOK_NEW
+ *
+ * @INST: event instance for this hook
+ * @EVENT: Landlock event name
+ * @NE: number of event arguments
+ * @HOOK: LSM hook name
+ * @NH: number of hook arguments
+ */
+#define HOOK_NEW(INST, EVENT, NE, HOOK, NH, ...)			\
+	static int landlock_hook_##EVENT##_##HOOK##_##INST(		\
+			MAP_HOOK_COMMA(NH, GET_HOOK_TA, __VA_ARGS__))	\
+	{								\
+		if (!landlocked(current))				\
+			return 0;					\
+		if (!(MAP_EVENT_AND(NH, NE, GET_EVENT_OK,		\
+						__VA_ARGS__)))		\
+			return 0;					\
+		{							\
+		MAP_EVENT_SPACE(NH, NE, GET_EVENT_DEC, __VA_ARGS__)	\
+		__u64 _ctx_values[CTX_ARG_NB] = {			\
+			MAP_EVENT_COMMA(NH, NE, GET_EVENT_U64,		\
+					__VA_ARGS__)			\
+		};							\
+		u32 _cmd = GET_CMD(NH, NE, __VA_ARGS__);		\
+		return landlock_decide(LANDLOCK_SUBTYPE_EVENT_##EVENT,	\
+				_ctx_values, _cmd, #HOOK);		\
+		}							\
+	}								\
+	extern void __consistent_##EVENT(MAP_EVENT_COMMA(		\
+				NH, NE, GET_EVENT_C, __VA_ARGS__))
+
+/*
+ * The WRAP_TYPE_* definitions group the bpf_reg_type enum value and the C
+ * type. This C type may remains unused except to catch inconsistencies in LSM
+ * hook definitions thanks to the compiler type checking.
+ */
+
+/* WRAP_TYPE_NONE */
+#define WRAP_TYPE_NONE_BPF	NOT_INIT
+#define WRAP_TYPE_NONE_C	struct __wrapcheck_none
+WRAP_TYPE_NONE_C;
+
+/* WRAP_TYPE_RAW */
+#define WRAP_TYPE_RAW_BPF	UNKNOWN_VALUE
+#define WRAP_TYPE_RAW_C		struct __wrapcheck_raw
+WRAP_TYPE_RAW_C;
+
+/*
+ * The WRAP_ARG_* definitions group the LSM hook argument type (C and BPF), the
+ * wrapping struct declaration (if any) and the value to copy to the BPF
+ * context. This definitions may be used thanks to the EXPAND_* helpers.
+ *
+ * WRAP_ARG_*_TYPE: type for BPF and C (cf. WRAP_TYPE_*)
+ * WRAP_ARG_*_DEC: declare a wrapper
+ * WRAP_ARG_*_VAL: get this wrapper's address
+ * WRAP_ARG_*_OK: check if the argument is usable
+ */
+
+/* WRAP_ARG_NONE */
+#define WRAP_ARG_NONE_TYPE	WRAP_TYPE_NONE
+#define WRAP_ARG_NONE_DEC(arg)
+#define WRAP_ARG_NONE_VAL(arg)	0
+#define WRAP_ARG_NONE_OK(arg)	(!WARN_ON(true))
+
+/* WRAP_ARG_RAW */
+#define WRAP_ARG_RAW_TYPE	WRAP_TYPE_RAW
+#define WRAP_ARG_RAW_DEC(arg)
+#define WRAP_ARG_RAW_VAL(arg)	arg
+#define WRAP_ARG_RAW_OK(arg)	(true)
+
+
+#define CTX_ARG_NB 2
+
+static inline bool landlocked(const struct task_struct *task)
+{
+	return false;
+}
+
+__init void landlock_register_hooks(struct security_hook_list *hooks, int count);
+
+bool landlock_is_valid_access(int off, int size, enum bpf_access_type type,
+		enum bpf_reg_type *reg_type,
+		enum bpf_reg_type ctx_types[CTX_ARG_NB],
+		union bpf_prog_subtype *prog_subtype);
+
+int landlock_decide(enum landlock_subtype_event event,
+		__u64 ctx_values[CTX_ARG_NB], u32 cmd, const char *hook);
diff --git a/security/landlock/hooks_fs.c b/security/landlock/hooks_fs.c
new file mode 100644
index 000000000000..6578f21783c7
--- /dev/null
+++ b/security/landlock/hooks_fs.c
@@ -0,0 +1,563 @@ 
+/*
+ * Landlock LSM - filesystem hooks
+ *
+ * Copyright © 2017 Mickaël Salaün <mic@digikod.net>
+ *
+ * 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/kernel.h> /* ARRAY_SIZE */
+#include <linux/lsm_hooks.h>
+#include <linux/types.h> /* uintptr_t */
+
+/* permissions translation */
+#include <linux/fs.h> /* MAY_* */
+#include <linux/mman.h> /* PROT_* */
+
+/* hook arguments */
+#include <linux/cred.h>
+#include <linux/dcache.h> /* struct dentry */
+#include <linux/fs.h> /* struct inode, struct iattr */
+#include <linux/mm_types.h> /* struct vm_area_struct */
+#include <linux/mount.h> /* struct vfsmount */
+#include <linux/path.h> /* struct path */
+#include <linux/sched.h> /* struct task_struct */
+#include <linux/time.h> /* struct timespec */
+
+#include "hooks.h"
+
+#include "hooks_fs.h"
+
+
+#define HOOK_NEW_FS(...) HOOK_NEW(1, FS, 2, __VA_ARGS__, 0)
+#define HOOK_NEW_FS2(...) HOOK_NEW(2, FS, 2, __VA_ARGS__, 0)
+#define HOOK_NEW_FS3(...) HOOK_NEW(3, FS, 2, __VA_ARGS__, 0)
+#define HOOK_NEW_FS4(...) HOOK_NEW(4, FS, 2, __VA_ARGS__, 0)
+#define HOOK_NEW_FS_CMD(...) HOOK_NEW(1, FS, 2, __VA_ARGS__)
+#define HOOK_INIT_FS(HOOK) LSM_HOOK_INIT(HOOK, landlock_hook_FS_##HOOK##_1)
+#define HOOK_INIT_FS2(HOOK) LSM_HOOK_INIT(HOOK, landlock_hook_FS_##HOOK##_2)
+#define HOOK_INIT_FS3(HOOK) LSM_HOOK_INIT(HOOK, landlock_hook_FS_##HOOK##_3)
+#define HOOK_INIT_FS4(HOOK) LSM_HOOK_INIT(HOOK, landlock_hook_FS_##HOOK##_4)
+
+/* WRAP_TYPE_FS */
+#define WRAP_TYPE_FS_BPF	CONST_PTR_TO_HANDLE_FS
+#define WRAP_TYPE_FS_C		const struct bpf_handle_fs
+
+/* WRAP_ARG_FILE */
+#define WRAP_ARG_FILE_TYPE	WRAP_TYPE_FS
+#define WRAP_ARG_FILE_DEC(arg)					\
+	EXPAND_C(WRAP_TYPE_FS) wrap_##arg =			\
+	{ .type = BPF_HANDLE_FS_TYPE_FILE, .file = arg };
+#define WRAP_ARG_FILE_VAL(arg)	((uintptr_t)&wrap_##arg)
+#define WRAP_ARG_FILE_OK(arg)	(arg)
+
+/* WRAP_ARG_VMAF */
+#define WRAP_ARG_VMAF_TYPE	WRAP_TYPE_FS
+#define WRAP_ARG_VMAF_DEC(arg)					\
+	EXPAND_C(WRAP_TYPE_FS) wrap_##arg =			\
+	{ .type = BPF_HANDLE_FS_TYPE_FILE, .file = arg->vm_file };
+#define WRAP_ARG_VMAF_VAL(arg)	((uintptr_t)&wrap_##arg)
+#define WRAP_ARG_VMAF_OK(arg)	(arg && arg->vm_file)
+
+/* WRAP_ARG_INODE */
+#define WRAP_ARG_INODE_TYPE	WRAP_TYPE_FS
+#define WRAP_ARG_INODE_DEC(arg)					\
+	EXPAND_C(WRAP_TYPE_FS) wrap_##arg =			\
+	{ .type = BPF_HANDLE_FS_TYPE_INODE, .inode = arg };
+#define WRAP_ARG_INODE_VAL(arg)	((uintptr_t)&wrap_##arg)
+#define WRAP_ARG_INODE_OK(arg)	(arg)
+
+/* WRAP_ARG_PATH */
+#define WRAP_ARG_PATH_TYPE	WRAP_TYPE_FS
+#define WRAP_ARG_PATH_DEC(arg)					\
+	EXPAND_C(WRAP_TYPE_FS) wrap_##arg =			\
+	{ .type = BPF_HANDLE_FS_TYPE_PATH, .path = arg };
+#define WRAP_ARG_PATH_VAL(arg)	((uintptr_t)&wrap_##arg)
+#define WRAP_ARG_PATH_OK(arg)	(arg)
+
+/* WRAP_ARG_DENTRY */
+#define WRAP_ARG_DENTRY_TYPE	WRAP_TYPE_FS
+#define WRAP_ARG_DENTRY_DEC(arg)				\
+	EXPAND_C(WRAP_TYPE_FS) wrap_##arg =			\
+	{ .type = BPF_HANDLE_FS_TYPE_DENTRY, .dentry = arg };
+#define WRAP_ARG_DENTRY_VAL(arg)	((uintptr_t)&wrap_##arg)
+#define WRAP_ARG_DENTRY_OK(arg)	(arg)
+
+/* WRAP_ARG_SB */
+#define WRAP_ARG_SB_TYPE	WRAP_TYPE_FS
+#define WRAP_ARG_SB_DEC(arg)					\
+	EXPAND_C(WRAP_TYPE_FS) wrap_##arg =			\
+	{ .type = BPF_HANDLE_FS_TYPE_DENTRY, .dentry = arg->s_root };
+#define WRAP_ARG_SB_VAL(arg)	((uintptr_t)&wrap_##arg)
+#define WRAP_ARG_SB_OK(arg)	(arg && arg->s_root)
+
+/* WRAP_ARG_MNTROOT */
+#define WRAP_ARG_MNTROOT_TYPE	WRAP_TYPE_FS
+#define WRAP_ARG_MNTROOT_DEC(arg)				\
+	EXPAND_C(WRAP_TYPE_FS) wrap_##arg =			\
+	{ .type = BPF_HANDLE_FS_TYPE_DENTRY, .dentry = arg->mnt_root };
+#define WRAP_ARG_MNTROOT_VAL(arg)	((uintptr_t)&wrap_##arg)
+#define WRAP_ARG_MNTROOT_OK(arg)	(arg && arg->mnt_root)
+
+
+static inline u64 fs_may_to_access(int fs_may)
+{
+	u64 ret = 0;
+
+	if (fs_may & MAY_EXEC)
+		ret |= LANDLOCK_ACTION_FS_EXEC;
+	if (fs_may & MAY_READ)
+		ret |= LANDLOCK_ACTION_FS_READ;
+	if (fs_may & MAY_WRITE)
+		ret |= LANDLOCK_ACTION_FS_WRITE;
+	if (fs_may & MAY_APPEND)
+		ret |= LANDLOCK_ACTION_FS_WRITE;
+	if (fs_may & MAY_OPEN)
+		ret |= LANDLOCK_ACTION_FS_GET;
+	/* ignore MAY_CHDIR and MAY_ACCESS */
+
+	return ret;
+}
+
+static u64 mem_prot_to_access(unsigned long prot, bool private)
+{
+	u64 ret = 0;
+
+	/* private mapping do not write to files */
+	if (!private && (prot & PROT_WRITE))
+		ret |= LANDLOCK_ACTION_FS_WRITE;
+	if (prot & PROT_READ)
+		ret |= LANDLOCK_ACTION_FS_READ;
+	if (prot & PROT_EXEC)
+		ret |= LANDLOCK_ACTION_FS_EXEC;
+
+	return ret;
+}
+
+/* hook definitions */
+
+HOOK_ACCESS(FS, 2, WRAP_TYPE_FS, WRAP_TYPE_RAW);
+
+/* binder_* hooks */
+
+HOOK_NEW_FS(binder_transfer_file, 3,
+	struct task_struct *, from,
+	struct task_struct *, to,
+	struct file *, file,
+	WRAP_ARG_FILE, file,
+	WRAP_ARG_RAW, LANDLOCK_ACTION_FS_READ
+);
+
+/* sb_* hooks */
+
+HOOK_NEW_FS(sb_statfs, 1,
+	struct dentry *, dentry,
+	WRAP_ARG_DENTRY, dentry,
+	WRAP_ARG_RAW, LANDLOCK_ACTION_FS_READ
+);
+
+/*
+ * Being able to mount on a path means being able to override the underlying
+ * filesystem view of this path, hence the need for a write access right.
+ */
+HOOK_NEW_FS(sb_mount, 5,
+	const char *, dev_name,
+	const struct path *, path,
+	const char *, type,
+	unsigned long, flags,
+	void *, data,
+	WRAP_ARG_PATH, path,
+	WRAP_ARG_RAW, LANDLOCK_ACTION_FS_WRITE
+);
+
+HOOK_NEW_FS(sb_remount, 2,
+	struct super_block *, sb,
+	void *, data,
+	WRAP_ARG_SB, sb,
+	WRAP_ARG_RAW, LANDLOCK_ACTION_FS_WRITE
+);
+
+HOOK_NEW_FS(sb_umount, 2,
+	struct vfsmount *, mnt,
+	int, flags,
+	WRAP_ARG_MNTROOT, mnt,
+	WRAP_ARG_RAW, LANDLOCK_ACTION_FS_WRITE
+);
+
+/*
+ * The old_path is similar to a destination mount point.
+ */
+HOOK_NEW_FS(sb_pivotroot, 2,
+	const struct path *, old_path,
+	const struct path *, new_path,
+	WRAP_ARG_PATH, old_path,
+	WRAP_ARG_RAW, LANDLOCK_ACTION_FS_WRITE
+);
+
+/* inode_* hooks */
+
+/* a directory inode contains only one dentry */
+HOOK_NEW_FS(inode_create, 3,
+	struct inode *, dir,
+	struct dentry *, dentry,
+	umode_t, mode,
+	WRAP_ARG_INODE, dir,
+	WRAP_ARG_RAW, LANDLOCK_ACTION_FS_WRITE
+);
+
+HOOK_NEW_FS2(inode_create, 3,
+	struct inode *, dir,
+	struct dentry *, dentry,
+	umode_t, mode,
+	WRAP_ARG_DENTRY, dentry,
+	WRAP_ARG_RAW, LANDLOCK_ACTION_FS_NEW
+);
+
+HOOK_NEW_FS(inode_link, 3,
+	struct dentry *, old_dentry,
+	struct inode *, dir,
+	struct dentry *, new_dentry,
+	WRAP_ARG_DENTRY, old_dentry,
+	WRAP_ARG_RAW, LANDLOCK_ACTION_FS_READ
+);
+
+HOOK_NEW_FS2(inode_link, 3,
+	struct dentry *, old_dentry,
+	struct inode *, dir,
+	struct dentry *, new_dentry,
+	WRAP_ARG_INODE, dir,
+	WRAP_ARG_RAW, LANDLOCK_ACTION_FS_WRITE
+);
+
+HOOK_NEW_FS3(inode_link, 3,
+	struct dentry *, old_dentry,
+	struct inode *, dir,
+	struct dentry *, new_dentry,
+	WRAP_ARG_DENTRY, new_dentry,
+	WRAP_ARG_RAW, LANDLOCK_ACTION_FS_NEW
+);
+
+HOOK_NEW_FS(inode_unlink, 2,
+	struct inode *, dir,
+	struct dentry *, dentry,
+	WRAP_ARG_INODE, dir,
+	WRAP_ARG_RAW, LANDLOCK_ACTION_FS_WRITE
+);
+
+HOOK_NEW_FS2(inode_unlink, 2,
+	struct inode *, dir,
+	struct dentry *, dentry,
+	WRAP_ARG_DENTRY, dentry,
+	WRAP_ARG_RAW, LANDLOCK_ACTION_FS_REMOVE
+);
+
+HOOK_NEW_FS(inode_symlink, 3,
+	struct inode *, dir,
+	struct dentry *, dentry,
+	const char *, old_name,
+	WRAP_ARG_INODE, dir,
+	WRAP_ARG_RAW, LANDLOCK_ACTION_FS_WRITE
+);
+
+HOOK_NEW_FS2(inode_symlink, 3,
+	struct inode *, dir,
+	struct dentry *, dentry,
+	const char *, old_name,
+	WRAP_ARG_DENTRY, dentry,
+	WRAP_ARG_RAW, LANDLOCK_ACTION_FS_NEW
+);
+
+HOOK_NEW_FS(inode_mkdir, 3,
+	struct inode *, dir,
+	struct dentry *, dentry,
+	umode_t, mode,
+	WRAP_ARG_INODE, dir,
+	WRAP_ARG_RAW, LANDLOCK_ACTION_FS_WRITE
+);
+
+HOOK_NEW_FS2(inode_mkdir, 3,
+	struct inode *, dir,
+	struct dentry *, dentry,
+	umode_t, mode,
+	WRAP_ARG_DENTRY, dentry,
+	WRAP_ARG_RAW, LANDLOCK_ACTION_FS_NEW
+);
+
+HOOK_NEW_FS(inode_rmdir, 2,
+	struct inode *, dir,
+	struct dentry *, dentry,
+	WRAP_ARG_INODE, dir,
+	WRAP_ARG_RAW, LANDLOCK_ACTION_FS_WRITE
+);
+
+HOOK_NEW_FS2(inode_rmdir, 2,
+	struct inode *, dir,
+	struct dentry *, dentry,
+	WRAP_ARG_DENTRY, dentry,
+	WRAP_ARG_RAW, LANDLOCK_ACTION_FS_REMOVE
+);
+
+HOOK_NEW_FS(inode_mknod, 4,
+	struct inode *, dir,
+	struct dentry *, dentry,
+	umode_t, mode,
+	dev_t, dev,
+	WRAP_ARG_INODE, dir,
+	WRAP_ARG_RAW, LANDLOCK_ACTION_FS_WRITE
+);
+
+HOOK_NEW_FS2(inode_mknod, 4,
+	struct inode *, dir,
+	struct dentry *, dentry,
+	umode_t, mode,
+	dev_t, dev,
+	WRAP_ARG_DENTRY, dentry,
+	WRAP_ARG_RAW, LANDLOCK_ACTION_FS_NEW
+);
+
+HOOK_NEW_FS(inode_rename, 4,
+	struct inode *, old_dir,
+	struct dentry *, old_dentry,
+	struct inode *, new_dir,
+	struct dentry *, new_dentry,
+	WRAP_ARG_INODE, old_dir,
+	WRAP_ARG_RAW, LANDLOCK_ACTION_FS_WRITE
+);
+
+HOOK_NEW_FS2(inode_rename, 4,
+	struct inode *, old_dir,
+	struct dentry *, old_dentry,
+	struct inode *, new_dir,
+	struct dentry *, new_dentry,
+	WRAP_ARG_DENTRY, old_dentry,
+	WRAP_ARG_RAW, LANDLOCK_ACTION_FS_REMOVE
+);
+
+HOOK_NEW_FS3(inode_rename, 4,
+	struct inode *, old_dir,
+	struct dentry *, old_dentry,
+	struct inode *, new_dir,
+	struct dentry *, new_dentry,
+	WRAP_ARG_INODE, new_dir,
+	WRAP_ARG_RAW, LANDLOCK_ACTION_FS_WRITE
+);
+
+HOOK_NEW_FS4(inode_rename, 4,
+	struct inode *, old_dir,
+	struct dentry *, old_dentry,
+	struct inode *, new_dir,
+	struct dentry *, new_dentry,
+	WRAP_ARG_DENTRY, new_dentry,
+	WRAP_ARG_RAW, LANDLOCK_ACTION_FS_NEW
+);
+
+HOOK_NEW_FS(inode_readlink, 1,
+	struct dentry *, dentry,
+	WRAP_ARG_DENTRY, dentry,
+	WRAP_ARG_RAW, LANDLOCK_ACTION_FS_READ
+);
+
+// XXX: handle inode?
+HOOK_NEW_FS(inode_follow_link, 3,
+	struct dentry *, dentry,
+	struct inode *, inode,
+	bool, rcu,
+	WRAP_ARG_DENTRY, dentry,
+	WRAP_ARG_RAW, LANDLOCK_ACTION_FS_READ
+);
+
+HOOK_NEW_FS(inode_permission, 2,
+	struct inode *, inode,
+	int, mask,
+	WRAP_ARG_INODE, inode,
+	WRAP_ARG_RAW, fs_may_to_access(mask)
+);
+
+HOOK_NEW_FS(inode_setattr, 2,
+	struct dentry *, dentry,
+	struct iattr *, attr,
+	WRAP_ARG_DENTRY, dentry,
+	WRAP_ARG_RAW, LANDLOCK_ACTION_FS_WRITE
+);
+
+HOOK_NEW_FS(inode_getattr, 1,
+	const struct path *, path,
+	WRAP_ARG_PATH, path,
+	WRAP_ARG_RAW, LANDLOCK_ACTION_FS_READ
+);
+
+HOOK_NEW_FS(inode_setxattr, 5,
+	struct dentry *, dentry,
+	const char *, name,
+	const void *, value,
+	size_t, size,
+	int, flags,
+	WRAP_ARG_DENTRY, dentry,
+	WRAP_ARG_RAW, LANDLOCK_ACTION_FS_WRITE
+);
+
+HOOK_NEW_FS(inode_getxattr, 2,
+	struct dentry *, dentry,
+	const char *, name,
+	WRAP_ARG_DENTRY, dentry,
+	WRAP_ARG_RAW, LANDLOCK_ACTION_FS_READ
+);
+
+HOOK_NEW_FS(inode_listxattr, 1,
+	struct dentry *, dentry,
+	WRAP_ARG_DENTRY, dentry,
+	WRAP_ARG_RAW, LANDLOCK_ACTION_FS_READ
+);
+
+HOOK_NEW_FS(inode_removexattr, 2,
+	struct dentry *, dentry,
+	const char *, name,
+	WRAP_ARG_DENTRY, dentry,
+	WRAP_ARG_RAW, LANDLOCK_ACTION_FS_WRITE
+);
+
+HOOK_NEW_FS(inode_getsecurity, 4,
+	struct inode *, inode,
+	const char *, name,
+	void **, buffer,
+	bool, alloc,
+	WRAP_ARG_INODE, inode,
+	WRAP_ARG_RAW, LANDLOCK_ACTION_FS_READ
+);
+
+HOOK_NEW_FS(inode_setsecurity, 5,
+	struct inode *, inode,
+	const char *, name,
+	const void *, value,
+	size_t, size,
+	int, flag,
+	WRAP_ARG_INODE, inode,
+	WRAP_ARG_RAW, LANDLOCK_ACTION_FS_WRITE
+);
+
+/* file_* hooks */
+
+HOOK_NEW_FS(file_permission, 2,
+	struct file *, file,
+	int, mask,
+	WRAP_ARG_FILE, file,
+	WRAP_ARG_RAW, fs_may_to_access(mask)
+);
+
+/*
+ * An ioctl command can be a read or a write. This can be checked with _IOC*()
+ * for some commands but a Landlock rule should check the ioctl command to
+ * whitelist them.
+ */
+HOOK_NEW_FS_CMD(file_ioctl, 3,
+	struct file *, file,
+	unsigned int, cmd,
+	unsigned long, arg,
+	WRAP_ARG_FILE, file,
+	WRAP_ARG_RAW, LANDLOCK_ACTION_FS_IOCTL,
+	cmd
+);
+
+HOOK_NEW_FS_CMD(file_lock, 2,
+	struct file *, file,
+	unsigned int, cmd,
+	WRAP_ARG_FILE, file,
+	WRAP_ARG_RAW, LANDLOCK_ACTION_FS_LOCK,
+	cmd
+);
+
+HOOK_NEW_FS_CMD(file_fcntl, 3,
+	struct file *, file,
+	unsigned int, cmd,
+	unsigned long, arg,
+	WRAP_ARG_FILE, file,
+	WRAP_ARG_RAW, LANDLOCK_ACTION_FS_FCNTL,
+	cmd
+);
+
+HOOK_NEW_FS(mmap_file, 4,
+	struct file *, file,
+	unsigned long, reqprot,
+	unsigned long, prot,
+	unsigned long, flags,
+	WRAP_ARG_FILE, file,
+	WRAP_ARG_RAW, mem_prot_to_access(prot, flags & MAP_PRIVATE)
+);
+
+HOOK_NEW_FS(file_mprotect, 3,
+	struct vm_area_struct *, vma,
+	unsigned long, reqprot,
+	unsigned long, prot,
+	WRAP_ARG_VMAF, vma,
+	WRAP_ARG_RAW, mem_prot_to_access(prot, !(vma->vm_flags & VM_SHARED))
+);
+
+HOOK_NEW_FS(file_receive, 1,
+	struct file *, file,
+	WRAP_ARG_FILE, file,
+	WRAP_ARG_RAW, LANDLOCK_ACTION_FS_GET
+);
+
+HOOK_NEW_FS(file_open, 2,
+	struct file *, file,
+	const struct cred *, cred,
+	WRAP_ARG_FILE, file,
+	WRAP_ARG_RAW, LANDLOCK_ACTION_FS_GET
+);
+
+static struct security_hook_list landlock_hooks[] = {
+	HOOK_INIT_FS(binder_transfer_file),
+
+	HOOK_INIT_FS(sb_statfs),
+	HOOK_INIT_FS(sb_mount),
+	HOOK_INIT_FS(sb_remount),
+	HOOK_INIT_FS(sb_umount),
+	HOOK_INIT_FS(sb_pivotroot),
+
+	HOOK_INIT_FS(inode_create),
+	HOOK_INIT_FS2(inode_create),
+	HOOK_INIT_FS(inode_link),
+	HOOK_INIT_FS2(inode_link),
+	HOOK_INIT_FS3(inode_link),
+	HOOK_INIT_FS(inode_unlink),
+	HOOK_INIT_FS2(inode_unlink),
+	HOOK_INIT_FS(inode_symlink),
+	HOOK_INIT_FS2(inode_symlink),
+	HOOK_INIT_FS(inode_mkdir),
+	HOOK_INIT_FS2(inode_mkdir),
+	HOOK_INIT_FS(inode_rmdir),
+	HOOK_INIT_FS2(inode_rmdir),
+	HOOK_INIT_FS(inode_mknod),
+	HOOK_INIT_FS2(inode_mknod),
+	HOOK_INIT_FS(inode_rename),
+	HOOK_INIT_FS2(inode_rename),
+	HOOK_INIT_FS3(inode_rename),
+	HOOK_INIT_FS4(inode_rename),
+	HOOK_INIT_FS(inode_readlink),
+	HOOK_INIT_FS(inode_follow_link),
+	HOOK_INIT_FS(inode_permission),
+	HOOK_INIT_FS(inode_setattr),
+	HOOK_INIT_FS(inode_getattr),
+	HOOK_INIT_FS(inode_setxattr),
+	HOOK_INIT_FS(inode_getxattr),
+	HOOK_INIT_FS(inode_listxattr),
+	HOOK_INIT_FS(inode_removexattr),
+	HOOK_INIT_FS(inode_getsecurity),
+	HOOK_INIT_FS(inode_setsecurity),
+
+	HOOK_INIT_FS(file_permission),
+	HOOK_INIT_FS(file_ioctl),
+	HOOK_INIT_FS(file_lock),
+	HOOK_INIT_FS(file_fcntl),
+	HOOK_INIT_FS(mmap_file),
+	HOOK_INIT_FS(file_mprotect),
+	HOOK_INIT_FS(file_receive),
+	HOOK_INIT_FS(file_open),
+};
+
+__init void landlock_add_hooks_fs(void)
+{
+	landlock_register_hooks(landlock_hooks, ARRAY_SIZE(landlock_hooks));
+}
diff --git a/security/landlock/hooks_fs.h b/security/landlock/hooks_fs.h
new file mode 100644
index 000000000000..093c72bb91dc
--- /dev/null
+++ b/security/landlock/hooks_fs.h
@@ -0,0 +1,19 @@ 
+/*
+ * Landlock LSM - filesystem hooks
+ *
+ * Copyright © 2017 Mickaël Salaün <mic@digikod.net>
+ *
+ * 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/bpf.h> /* enum bpf_access_type */
+
+
+bool landlock_is_valid_access_event_FS(
+		int off, int size, enum bpf_access_type type,
+		enum bpf_reg_type *reg_type,
+		union bpf_prog_subtype *prog_subtype);
+
+__init void landlock_add_hooks_fs(void);
diff --git a/security/landlock/init.c b/security/landlock/init.c
index 914895d08320..1c2750e12dfa 100644
--- a/security/landlock/init.c
+++ b/security/landlock/init.c
@@ -11,6 +11,9 @@ 
 #include <linux/bpf.h> /* enum bpf_access_type */
 #include <linux/capability.h> /* capable */
 #include <linux/landlock.h> /* LANDLOCK_VERSION */
+#include <linux/lsm_hooks.h>
+
+#include "hooks_fs.h"
 
 
 static inline bool bpf_landlock_is_valid_access(int off, int size,
@@ -22,6 +25,8 @@  static inline bool bpf_landlock_is_valid_access(int off, int size,
 
 	switch (prog_subtype->landlock_rule.event) {
 	case LANDLOCK_SUBTYPE_EVENT_FS:
+		return landlock_is_valid_access_event_FS(off, size, type,
+				reg_type, prog_subtype);
 	case LANDLOCK_SUBTYPE_EVENT_UNSPEC:
 	default:
 		return false;
@@ -127,3 +132,11 @@  static struct bpf_prog_type_list bpf_landlock_type __ro_after_init = {
 	.ops = &bpf_landlock_ops,
 	.type = BPF_PROG_TYPE_LANDLOCK,
 };
+
+void __init landlock_add_hooks(void)
+{
+	pr_info("landlock: Version %u", LANDLOCK_VERSION);
+	landlock_add_hooks_fs();
+	security_add_hooks(NULL, 0, "landlock");
+	bpf_register_prog_type(&bpf_landlock_type);
+}
diff --git a/security/security.c b/security/security.c
index d0e07f269b2d..a3e9f4625991 100644
--- a/security/security.c
+++ b/security/security.c
@@ -64,10 +64,15 @@  int __init security_init(void)
 	loadpin_add_hooks();
 
 	/*
-	 * Load all the remaining security modules.
+	 * Load all remaining privileged security modules.
 	 */
 	do_security_initcalls();
 
+	/*
+	 * Load potentially-unprivileged security modules at the end.
+	 */
+	landlock_add_hooks();
+
 	return 0;
 }