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 <m...@digikod.net>
Cc: Alexei Starovoitov <a...@kernel.org>
Cc: Andy Lutomirski <l...@amacapital.net>
Cc: Daniel Borkmann <dan...@iogearbox.net>
Cc: David S. Miller <da...@davemloft.net>
Cc: James Morris <james.l.mor...@oracle.com>
Cc: Kees Cook <keesc...@chromium.org>
Cc: Serge E. Hallyn <se...@hallyn.com>
---

Changes since v6:
* add 3 more sub-events: IOCTL, LOCK, FCNTL
  https://lkml.kernel.org/r/2fbc99a6-f190-f335-bd14-04bdeed35...@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/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 <m...@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 <m...@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 <m...@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 <m...@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;
 }
 
-- 
2.14.1

Reply via email to