Patchwork [net-next,v7,05/10] landlock: Add LSM hooks related to filesystem

login
register
mail settings
Submitter Mickaël Salaün
Date Aug. 21, 2017, 12:09 a.m.
Message ID <20170821000933.13024-6-mic@digikod.net>
Download mbox | patch
Permalink /patch/9911497/
State New
Headers show

Comments

Mickaël Salaün - Aug. 21, 2017, 12:09 a.m.
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.

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>
---

Changes since v6:
* add 3 more sub-events: IOCTL, LOCK, FCNTL
  https://lkml.kernel.org/r/2fbc99a6-f190-f335-bd14-04bdeed35571@digikod.net
* use the new security_add_hooks()
* explain the -Werror=unused-function
* constify pointers
* cleanup headers

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

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
---
 include/linux/lsm_hooks.h    |   5 +
 security/landlock/Makefile   |   7 +-
 security/landlock/common.h   |   2 +
 security/landlock/hooks.c    |  83 ++++++
 security/landlock/hooks.h    | 177 +++++++++++++
 security/landlock/hooks_fs.c | 586 +++++++++++++++++++++++++++++++++++++++++++
 security/landlock/hooks_fs.h |  19 ++
 security/landlock/init.c     |  10 +
 security/security.c          |  12 +-
 9 files changed, 899 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
Mickaël Salaün - Aug. 22, 2017, 9:59 p.m.
On 21/08/2017 02:09, Mickaël Salaün 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.
> 
> 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>
> ---
> 
> Changes since v6:
> * add 3 more sub-events: IOCTL, LOCK, FCNTL
>   https://lkml.kernel.org/r/2fbc99a6-f190-f335-bd14-04bdeed35571@digikod.net
> * use the new security_add_hooks()
> * explain the -Werror=unused-function
> * constify pointers
> * cleanup headers
> 
> Changes since v5:
> * split hooks.[ch] into hooks.[ch] and hooks_fs.[ch]
> * add more documentation
> * cosmetic fixes
> * rebase (SCALAR_VALUE)
> 
> 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
> ---
>  include/linux/lsm_hooks.h    |   5 +
>  security/landlock/Makefile   |   7 +-
>  security/landlock/common.h   |   2 +
>  security/landlock/hooks.c    |  83 ++++++
>  security/landlock/hooks.h    | 177 +++++++++++++
>  security/landlock/hooks_fs.c | 586 +++++++++++++++++++++++++++++++++++++++++++
>  security/landlock/hooks_fs.h |  19 ++
>  security/landlock/init.c     |  10 +
>  security/security.c          |  12 +-
>  9 files changed, 899 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/security/landlock/init.c b/security/landlock/init.c
> index 09acbc74abd6..1e6660fed697 100644
> --- a/security/landlock/init.c
> +++ b/security/landlock/init.c
> @@ -10,8 +10,10 @@
>  
>  #include <linux/bpf.h> /* enum bpf_access_type */
>  #include <linux/capability.h> /* capable */
> +#include <linux/lsm_hooks.h>
>  
>  #include "common.h" /* LANDLOCK_* */
> +#include "hooks_fs.h"
>  
>  
>  static inline bool bpf_landlock_is_valid_access(int off, int size,
> @@ -23,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,
> +				&info->reg_type, prog_subtype);

I forgot to handle LANDLOCK_SUBTYPE_EVENT_FS_{IOCTL,LOCK_FCNTL} here and
I included some hunks in the wrong patches. I will fix this in the next
series and add tests for those anyway. :)

Regards,
 Mickaël
Alexei Starovoitov - Aug. 24, 2017, 2:50 a.m.
On Mon, Aug 21, 2017 at 02:09:28AM +0200, Mickaël Salaün 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.
> 
> Signed-off-by: Mickaël Salaün <mic@digikod.net>

...

> +/* 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)
...

> +HOOK_NEW_FS(sb_remount, 2,
> +	struct super_block *, sb,
> +	void *, data,
> +	WRAP_ARG_SB, sb,
> +	WRAP_ARG_RAW, LANDLOCK_ACTION_FS_WRITE
> +);

this looks wrong. casting super_block to dentry?

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

more general question: why you're not wrapping all useful
arguments? Like in the above dentry can be acted upon
by the landlock rule and it's readily available...

The limitation of only 2 args looks odd.
Is it a hard limitation ? how hard to extend?
Mickaël Salaün - Aug. 25, 2017, 8:16 a.m.
On 24/08/2017 04:50, Alexei Starovoitov wrote:
> On Mon, Aug 21, 2017 at 02:09:28AM +0200, Mickaël Salaün 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.
>>
>> Signed-off-by: Mickaël Salaün <mic@digikod.net>
> 
> ...
> 
>> +/* 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)
> ...
> 
>> +HOOK_NEW_FS(sb_remount, 2,
>> +	struct super_block *, sb,
>> +	void *, data,
>> +	WRAP_ARG_SB, sb,
>> +	WRAP_ARG_RAW, LANDLOCK_ACTION_FS_WRITE
>> +);
> 
> this looks wrong. casting super_block to dentry?

This is called when remounting a block device. The WRAP_ARG_SB take the
sb->s_root as a dentry, it is not a cast. What do you expect from this hook?

> 
>> +/* 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
>> +);
> 
> more general question: why you're not wrapping all useful
> arguments? Like in the above dentry can be acted upon
> by the landlock rule and it's readily available...

The context used for the FS event must have the exact same types for all
calls. This event is meant to be generic but we can add more specific
ones if needed, like I do with FS_IOCTL.

The idea is to enable people to write simple rules, while being able to
write fine grain rules for special cases (e.g. IOCTL) if needed.

> 
> The limitation of only 2 args looks odd.
> Is it a hard limitation ? how hard to extend?

It's not a hard limit at all. Actually, the FS_FNCTL event should have
three arguments (I'll add them in the next series): FS handle, FCNTL
command and FCNTL argument. I made sure that it's really easy to add
more arguments to the context of an event.
Alexei Starovoitov - Aug. 26, 2017, 1:16 a.m.
On Fri, Aug 25, 2017 at 10:16:39AM +0200, Mickaël Salaün wrote:
> > 
> >> +/* 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)
> > ...
> > 
> >> +HOOK_NEW_FS(sb_remount, 2,
> >> +	struct super_block *, sb,
> >> +	void *, data,
> >> +	WRAP_ARG_SB, sb,
> >> +	WRAP_ARG_RAW, LANDLOCK_ACTION_FS_WRITE
> >> +);
> > 
> > this looks wrong. casting super_block to dentry?
> 
> This is called when remounting a block device. The WRAP_ARG_SB take the
> sb->s_root as a dentry, it is not a cast. What do you expect from this hook?

got it. I missed -> part. Now it makes sense.

> > 
> >> +/* 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
> >> +);
> > 
> > more general question: why you're not wrapping all useful
> > arguments? Like in the above dentry can be acted upon
> > by the landlock rule and it's readily available...
> 
> The context used for the FS event must have the exact same types for all
> calls. This event is meant to be generic but we can add more specific
> ones if needed, like I do with FS_IOCTL.

I see. So all FS events will have dentry as first argument
regardless of how it is in LSM hook ?
I guess that will simplify the rules indeed.
I suspect you're doing it to simplify the LSM->landlock shim layer as well, right?

> The idea is to enable people to write simple rules, while being able to
> write fine grain rules for special cases (e.g. IOCTL) if needed.
> 
> > 
> > The limitation of only 2 args looks odd.
> > Is it a hard limitation ? how hard to extend?
> 
> It's not a hard limit at all. Actually, the FS_FNCTL event should have
> three arguments (I'll add them in the next series): FS handle, FCNTL
> command and FCNTL argument. I made sure that it's really easy to add
> more arguments to the context of an event.

The reason I'm asking, because I'm not completely convinced that
adding another argument to existing event will be backwards compatible.
It looks like you're expecting only two args for all FS events, right?
How can you add 3rd argument? All FS events would have to get it,
but in some LSM hooks such argument will be meaningless, whereas
in other places it will carry useful info that rule can operate on.
Would that mean that we'll have FS_3 event type and only few LSM
hooks will be converted to it. That works, but then we'll lose
compatiblity with old rules written for FS event and that given hook.
Otherwise we'd need to have fancy logic to accept old FS event
into FS_3 LSM hook.
Mickaël Salaün - Aug. 27, 2017, 1:31 p.m.
On 26/08/2017 03:16, Alexei Starovoitov wrote:
> On Fri, Aug 25, 2017 at 10:16:39AM +0200, Mickaël Salaün wrote:

>>>
>>>> +/* 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
>>>> +);
>>>
>>> more general question: why you're not wrapping all useful
>>> arguments? Like in the above dentry can be acted upon
>>> by the landlock rule and it's readily available...
>>
>> The context used for the FS event must have the exact same types for all
>> calls. This event is meant to be generic but we can add more specific
>> ones if needed, like I do with FS_IOCTL.
> 
> I see. So all FS events will have dentry as first argument
> regardless of how it is in LSM hook ?

All FS events will have a const struct bpf_handle_fs pointer as first
argument, which wrap either a struct file, a struct dentry, a struct
path or a struct inode. Having only one type (struct bpf_handle_fs) is
needed for the eBPF type checker to verify if a Landlock rule (tied to
an event) can access a context field and which operation is allowed
(with this pointer).

> I guess that will simplify the rules indeed.
> I suspect you're doing it to simplify the LSM->landlock shim layer as well, right?

That's right. This ABI is independent from the LSM API and much more
simpler to use.

> 
>> The idea is to enable people to write simple rules, while being able to
>> write fine grain rules for special cases (e.g. IOCTL) if needed.
>>
>>>
>>> The limitation of only 2 args looks odd.
>>> Is it a hard limitation ? how hard to extend?
>>
>> It's not a hard limit at all. Actually, the FS_FNCTL event should have
>> three arguments (I'll add them in the next series): FS handle, FCNTL
>> command and FCNTL argument. I made sure that it's really easy to add
>> more arguments to the context of an event.
> 
> The reason I'm asking, because I'm not completely convinced that
> adding another argument to existing event will be backwards compatible.
> It looks like you're expecting only two args for all FS events, right?

There is four events right now: FS, FS_IOCTL, FS_LOCK and FS_FCNTL. Each
of them are independent. Their context fields can be of the same or
different eBPF type (e.g. scalar, file handle) and numbers. Actually,
these four events have the same arg1 field (file handle) and the same
arg2 eBPF type (scalar), even if arg2 does not have the same semantic
(i.e. abstract FS action, IOCTL command…).

For example, if we want to extend the FS_FCNTL's context in the future,
we will just have to add an arg3. The check is performed in
landlock_is_valid_access() and landlock_decide(). If a field is not used
by an event, then this field will have a NOT_INIT type and accessing it
will be denied.

> How can you add 3rd argument? All FS events would have to get it,
> but in some LSM hooks such argument will be meaningless, whereas
> in other places it will carry useful info that rule can operate on.
> Would that mean that we'll have FS_3 event type and only few LSM
> hooks will be converted to it. That works, but then we'll lose
> compatiblity with old rules written for FS event and that given hook.
> Otherwise we'd need to have fancy logic to accept old FS event
> into FS_3 LSM hook.

If we want to add a third argument to the FS event, then it will become
accessible because its type will be different than NOT_INIT. This keep
the compatibility with old rules because this new field was then denied.

If we want to add a new argument but only for a subset of the hooks used
by the FS event, then we need to create a new event, like FS_FCNTL. For
example, we may want to add a FS_RENAME event to be able to tie the
source file and the destination file of a rename call.

Anyway, I added the subtype/ABI version as a safeguard in case of
unexpected future evolution.
Alexei Starovoitov - Aug. 28, 2017, 5:26 a.m.
On Sun, Aug 27, 2017 at 03:31:35PM +0200, Mickaël Salaün wrote:
> 
> > How can you add 3rd argument? All FS events would have to get it,
> > but in some LSM hooks such argument will be meaningless, whereas
> > in other places it will carry useful info that rule can operate on.
> > Would that mean that we'll have FS_3 event type and only few LSM
> > hooks will be converted to it. That works, but then we'll lose
> > compatiblity with old rules written for FS event and that given hook.
> > Otherwise we'd need to have fancy logic to accept old FS event
> > into FS_3 LSM hook.
> 
> If we want to add a third argument to the FS event, then it will become
> accessible because its type will be different than NOT_INIT. This keep
> the compatibility with old rules because this new field was then denied.
> 
> If we want to add a new argument but only for a subset of the hooks used
> by the FS event, then we need to create a new event, like FS_FCNTL. For
> example, we may want to add a FS_RENAME event to be able to tie the
> source file and the destination file of a rename call.

that's exactly my point. To add another argument FS event
to a subset of hooks will require either new FS_FOO and
to be backwards compatible these hooks will call _both_ FS and FS_FOO
or some magic logic on kernel side that will allow old FS rules
to be attached to FS_FOO hooks?
Two calls doesn't scale and if we do 'magic logic' can we do it now
and avoid introducing events altogether?
Like all landlock programs can be landlock type and they would need
to declare what arg1, arg2, argN they expect. Then at attach
time the kernel only needs to verify that hook arg types match
what program requested.

> Anyway, I added the subtype/ABI version as a safeguard in case of
> unexpected future evolution.

I don't think that abi/version field adds anything in this context.
I still think it should simply be removed.

Patch

diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h
index 3a90febadbe2..7614c3d66265 100644
--- a/include/linux/lsm_hooks.h
+++ b/include/linux/lsm_hooks.h
@@ -1982,5 +1982,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..b382be409b3b 100644
--- a/security/landlock/Makefile
+++ b/security/landlock/Makefile
@@ -1,3 +1,8 @@ 
+# Catch defined but unused hooks, e.g. 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.
+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/common.h b/security/landlock/common.h
index c82cbd3fb640..a69c35231d35 100644
--- a/security/landlock/common.h
+++ b/security/landlock/common.h
@@ -18,4 +18,6 @@ 
  */
 #define LANDLOCK_ABI 1
 
+#define LANDLOCK_NAME "landlock"
+
 #endif /* _SECURITY_LANDLOCK_COMMON_H */
diff --git a/security/landlock/hooks.c b/security/landlock/hooks.c
new file mode 100644
index 000000000000..b48caeb0a49a
--- /dev/null
+++ b/security/landlock/hooks.c
@@ -0,0 +1,83 @@ 
+/*
+ * Landlock LSM - hook helpers
+ *
+ * Copyright © 2016-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, struct landlock_context */
+#include <linux/errno.h>
+#include <linux/filter.h> /* BPF_PROG_RUN() */
+#include <linux/rculist.h> /* list_add_tail_rcu */
+#include <linux/stddef.h> /* offsetof */
+
+#include "hooks.h" /* CTX_ARG_NB */
+
+
+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],
+		const 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, status):
+	case offsetof(struct landlock_context, event):
+	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 = SCALAR_VALUE;
+	}
+
+	/* check memory range access */
+	switch (*reg_type) {
+	case NOT_INIT:
+		return false;
+	case SCALAR_VALUE:
+		/* 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], const char *hook)
+{
+	bool deny = false;
+
+	return deny ? -EPERM : 0;
+}
diff --git a/security/landlock/hooks.h b/security/landlock/hooks.h
new file mode 100644
index 000000000000..51957211b67d
--- /dev/null
+++ b/security/landlock/hooks.h
@@ -0,0 +1,177 @@ 
+/*
+ * Landlock LSM - hooks helpers
+ *
+ * Copyright © 2016-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 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,			\
+			const 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__)			\
+		};							\
+		return landlock_decide(LANDLOCK_SUBTYPE_EVENT_##EVENT,	\
+				_ctx_values, #HOOK);			\
+		}							\
+	}								\
+	extern void __consistent_##EVENT(MAP_EVENT_COMMA(		\
+				NH, NE, GET_EVENT_C, __VA_ARGS__))
+
+#define HOOK_INIT(EVENT, HOOK, ID) \
+	LSM_HOOK_INIT(HOOK, landlock_hook_##EVENT##_##HOOK##_##ID)
+
+/*
+ * 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	SCALAR_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],
+		const union bpf_prog_subtype *prog_subtype);
+
+int landlock_decide(enum landlock_subtype_event event,
+		__u64 ctx_values[CTX_ARG_NB], const char *hook);
diff --git a/security/landlock/hooks_fs.c b/security/landlock/hooks_fs.c
new file mode 100644
index 000000000000..fa80b35a269d
--- /dev/null
+++ b/security/landlock/hooks_fs.c
@@ -0,0 +1,586 @@ 
+/*
+ * Landlock LSM - filesystem hooks
+ *
+ * Copyright © 2016-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 "common.h"
+#include "hooks.h"
+#include "hooks_fs.h"
+
+
+#define HOOK_NEW_FS(...) HOOK_NEW(1, FS, 2, __VA_ARGS__)
+#define HOOK_NEW_FS2(...) HOOK_NEW(2, FS, 2, __VA_ARGS__)
+#define HOOK_NEW_FS3(...) HOOK_NEW(3, FS, 2, __VA_ARGS__)
+#define HOOK_NEW_FS4(...) HOOK_NEW(4, FS, 2, __VA_ARGS__)
+
+#define HOOK_INIT_FS(HOOK) HOOK_INIT(FS, HOOK, 1)
+#define HOOK_INIT_FS2(HOOK) HOOK_INIT(FS, HOOK, 2)
+#define HOOK_INIT_FS3(HOOK) HOOK_INIT(FS, HOOK, 3)
+#define HOOK_INIT_FS4(HOOK) HOOK_INIT(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(file_ioctl, 3,
+	struct file *, file,
+	unsigned int, cmd,
+	unsigned long, arg,
+	WRAP_ARG_FILE, file,
+	WRAP_ARG_RAW, LANDLOCK_ACTION_FS_IOCTL
+);
+
+HOOK_NEW(1, FS_IOCTL, 2, file_ioctl, 3,
+	struct file *, file,
+	unsigned int, cmd,
+	unsigned long, arg,
+	WRAP_ARG_FILE, file,
+	WRAP_ARG_RAW, cmd
+);
+
+HOOK_NEW_FS(file_lock, 2,
+	struct file *, file,
+	unsigned int, cmd,
+	WRAP_ARG_FILE, file,
+	WRAP_ARG_RAW, LANDLOCK_ACTION_FS_LOCK
+);
+
+HOOK_NEW(1, FS_LOCK, 2, file_lock, 2,
+	struct file *, file,
+	unsigned int, cmd,
+	WRAP_ARG_FILE, file,
+	WRAP_ARG_RAW, cmd
+);
+
+HOOK_NEW_FS(file_fcntl, 3,
+	struct file *, file,
+	unsigned int, cmd,
+	unsigned long, arg,
+	WRAP_ARG_FILE, file,
+	WRAP_ARG_RAW, LANDLOCK_ACTION_FS_FCNTL
+);
+
+HOOK_NEW(1, FS_FCNTL, 2, file_fcntl, 3,
+	struct file *, file,
+	unsigned int, cmd,
+	unsigned long, arg,
+	WRAP_ARG_FILE, file,
+	WRAP_ARG_RAW, 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_IOCTL, file_ioctl, 1),
+	HOOK_INIT_FS(file_lock),
+	HOOK_INIT(FS_LOCK, file_lock, 1),
+	HOOK_INIT_FS(file_fcntl),
+	HOOK_INIT(FS_FCNTL, file_fcntl, 1),
+	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)
+{
+	security_add_hooks(landlock_hooks, ARRAY_SIZE(landlock_hooks), LANDLOCK_NAME);
+}
diff --git a/security/landlock/hooks_fs.h b/security/landlock/hooks_fs.h
new file mode 100644
index 000000000000..aab07c018f38
--- /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,
+		const 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 09acbc74abd6..1e6660fed697 100644
--- a/security/landlock/init.c
+++ b/security/landlock/init.c
@@ -10,8 +10,10 @@ 
 
 #include <linux/bpf.h> /* enum bpf_access_type */
 #include <linux/capability.h> /* capable */
+#include <linux/lsm_hooks.h>
 
 #include "common.h" /* LANDLOCK_* */
+#include "hooks_fs.h"
 
 
 static inline bool bpf_landlock_is_valid_access(int off, int size,
@@ -23,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,
+				&info->reg_type, prog_subtype);
 	case LANDLOCK_SUBTYPE_EVENT_UNSPEC:
 	default:
 		return false;
@@ -113,3 +117,9 @@  const struct bpf_verifier_ops bpf_landlock_ops = {
 	.is_valid_access = bpf_landlock_is_valid_access,
 	.is_valid_subtype = bpf_landlock_is_valid_subtype,
 };
+
+void __init landlock_add_hooks(void)
+{
+	pr_info("%s: ABI %u", LANDLOCK_NAME, LANDLOCK_ABI);
+	landlock_add_hooks_fs();
+}
diff --git a/security/security.c b/security/security.c
index 30132378d103..000d95d53902 100644
--- a/security/security.c
+++ b/security/security.c
@@ -75,10 +75,20 @@  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.
+	 *
+	 * For an unprivileged access-control, we don't want to give the
+	 * ability to any process to do some checks (e.g. through an eBPF
+	 * program) on kernel objects (e.g. files) if a privileged security
+	 * policy forbid their access.
+	 */
+	landlock_add_hooks();
+
 	return 0;
 }