FIXME: 64-bits in the doc

This new map store arbitrary values referenced by inode keys.  The map
can be updated from user space with file descriptor pointing to inodes
tied to a file system.  From an eBPF (Landlock) program point of view,
such a map is read-only and can only be used to retrieved a value tied
to a given inode.  This is useful to recognize an inode tagged by user
space, without access right to this inode (i.e. no need to have a write
access to this inode).

Add dedicated BPF functions to handle this type of map:
* bpf_inode_htab_map_update_elem()
* bpf_inode_htab_map_lookup_elem()
* bpf_inode_htab_map_delete_elem()

This new map require a dedicated helper inode_map_lookup_elem() because
of the key which is a pointer to an opaque data (only provided by the
kernel).  This act like a (physical or cryptographic) key, which is why
it is also not allowed to get the next key.

Signed-off-by: Mickaël Salaün <[email protected]>
Cc: Alexei Starovoitov <[email protected]>
Cc: Andy Lutomirski <[email protected]>
Cc: Daniel Borkmann <[email protected]>
Cc: David S. Miller <[email protected]>
Cc: James Morris <[email protected]>
Cc: Kees Cook <[email protected]>
Cc: Serge E. Hallyn <[email protected]>
Cc: Jann Horn <[email protected]>
---

Changes since v9:
* use a hash map for the inode map: integrate inodemap.c into hashtab.c
  * add map_put_key() to struct bpf_map_ops to enable to put an inode
    reference used as key
  * allow arbitrary value size instead of 64-bits
* handle inode and map lifetime with LSM hooks
* check access for inode lookup via syscall: similar to adding xattr,
  except it does not touch the file system (which is handy for read-only
  ones)
* force read-only inode map for Landlock programs
* rename inode_map_lookup() into inode_map_lookup_elem()
* fix inode and mnt checks (suggested by Al Viro)

Changes since v8:
* remove prog chaining and object tagging to ease review
* use bpf_map_init_from_attr()

Changes since v7:
* new design with a dedicated map and a BPF function to tie a value to
  an inode
* add the ability to set or get a tag on an inode from a Landlock
  program

Changes since v6:
* remove WARN_ON() for missing dentry->d_inode
* refactor bpf_landlock_func_proto() (suggested by Kees Cook)

Changes since v5:
* cosmetic fixes and rebase

Changes since v4:
* use a file abstraction (handle) to wrap inode, dentry, path and file
  structs
* remove bpf_landlock_cmp_fs_beneath()
* rename the BPF helper and move it to kernel/bpf/
* tighten helpers accessible by a Landlock rule

Changes since v3:
* remove bpf_landlock_cmp_fs_prop() (suggested by Alexei Starovoitov)
* 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
* add bpf_landlock_get_fs_mode() helper to check file type and mode
* merge WARN_ON() (suggested by Kees Cook)
* fix and update bpf_helpers.h
* use BPF_CALL_* for eBPF helpers (suggested by Alexei Starovoitov)
* make handle arraymap safe (RCU) and remove buggy synchronize_rcu()
* factor out the arraymay walk
* use size_t to index array (suggested by Jann Horn)

Changes since v2:
* add MNT_INTERNAL check to only add file handle from user-visible FS
  (e.g. no anonymous inode)
* replace struct file* with struct path* in map_landlock_handle
* add BPF protos
* fix bpf_landlock_cmp_fs_prop_with_struct_file()
---
 include/linux/bpf.h            |  16 +++
 include/linux/bpf_types.h      |   3 +
 include/linux/landlock.h       |   4 +
 include/uapi/linux/bpf.h       |  12 +-
 kernel/bpf/core.c              |   2 +
 kernel/bpf/hashtab.c           | 253 +++++++++++++++++++++++++++++++++
 kernel/bpf/syscall.c           |  27 +++-
 kernel/bpf/verifier.c          |  14 ++
 security/landlock/common.h     |  14 ++
 security/landlock/hooks_fs.c   |  85 +++++++++++
 security/landlock/init.c       |  13 ++
 tools/include/uapi/linux/bpf.h |  12 +-
 tools/lib/bpf/libbpf_probes.c  |   1 +
 13 files changed, 453 insertions(+), 3 deletions(-)

diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index 6d9c7a08713e..c507438e56b5 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -47,6 +47,7 @@ struct bpf_map_ops {
        void *(*map_fd_get_ptr)(struct bpf_map *map, struct file *map_file,
                                int fd);
        void (*map_fd_put_ptr)(void *ptr);
+       void (*map_put_key)(void *key);
        u32 (*map_gen_lookup)(struct bpf_map *map, struct bpf_insn *insn_buf);
        u32 (*map_fd_sys_lookup_elem)(void *ptr);
        void (*map_seq_show_elem)(struct bpf_map *map, void *key,
@@ -208,6 +209,8 @@ enum bpf_arg_type {
        ARG_PTR_TO_INT,         /* pointer to int */
        ARG_PTR_TO_LONG,        /* pointer to long */
        ARG_PTR_TO_SOCKET,      /* pointer to bpf_sock (fullsock) */
+
+       ARG_PTR_TO_INODE,       /* pointer to a struct inode */
 };
 
 /* type of values returned from helper functions */
@@ -278,6 +281,7 @@ enum bpf_reg_type {
        PTR_TO_TCP_SOCK_OR_NULL, /* reg points to struct tcp_sock or NULL */
        PTR_TO_TP_BUFFER,        /* reg points to a writable raw tp's buffer */
        PTR_TO_XDP_SOCK,         /* reg points to struct xdp_sock */
+       PTR_TO_INODE,            /* reg points to struct inode */
 };
 
 /* The information passed from prog-specific *_is_valid_access
@@ -479,6 +483,7 @@ struct bpf_event_entry {
        struct rcu_head rcu;
 };
 
+
 bool bpf_prog_array_compatible(struct bpf_array *array, const struct bpf_prog 
*fp);
 int bpf_prog_calc_tag(struct bpf_prog *fp);
 
@@ -684,6 +689,16 @@ int bpf_fd_array_map_lookup_elem(struct bpf_map *map, void 
*key, u32 *value);
 int bpf_fd_htab_map_update_elem(struct bpf_map *map, struct file *map_file,
                                void *key, void *value, u64 map_flags);
 int bpf_fd_htab_map_lookup_elem(struct bpf_map *map, void *key, u32 *value);
+int bpf_inode_fd_htab_map_lookup_elem(struct bpf_map *map, int *key, void 
*value);
+int bpf_inode_fd_htab_map_delete_elem(struct bpf_map *map, int *key);
+int bpf_inode_ptr_unlocked_htab_map_delete_elem(struct bpf_map *map,
+                                               struct inode **key,
+                                               bool remove_in_inode);
+int bpf_inode_ptr_locked_htab_map_delete_elem(struct bpf_map *map,
+                                             struct inode **key,
+                                             bool remove_in_inode);
+int bpf_inode_fd_htab_map_update_elem(struct bpf_map *map, int *key,
+                                     void *value, u64 map_flags);
 
 int bpf_get_file_flag(int flags);
 int bpf_check_uarg_tail_zero(void __user *uaddr, size_t expected_size,
@@ -1055,6 +1070,7 @@ extern const struct bpf_func_proto 
bpf_get_local_storage_proto;
 extern const struct bpf_func_proto bpf_strtol_proto;
 extern const struct bpf_func_proto bpf_strtoul_proto;
 extern const struct bpf_func_proto bpf_tcp_sock_proto;
+extern const struct bpf_func_proto bpf_inode_map_lookup_elem_proto;
 
 /* Shared helpers among cBPF and eBPF. */
 void bpf_user_rnd_init_once(void);
diff --git a/include/linux/bpf_types.h b/include/linux/bpf_types.h
index 2ab647323f3a..ea177818d67e 100644
--- a/include/linux/bpf_types.h
+++ b/include/linux/bpf_types.h
@@ -80,3 +80,6 @@ BPF_MAP_TYPE(BPF_MAP_TYPE_REUSEPORT_SOCKARRAY, 
reuseport_array_ops)
 #endif
 BPF_MAP_TYPE(BPF_MAP_TYPE_QUEUE, queue_map_ops)
 BPF_MAP_TYPE(BPF_MAP_TYPE_STACK, stack_map_ops)
+#ifdef CONFIG_SECURITY_LANDLOCK
+BPF_MAP_TYPE(BPF_MAP_TYPE_INODE, htab_inode_ops)
+#endif
diff --git a/include/linux/landlock.h b/include/linux/landlock.h
index 8ac7942f50fc..731b89cdf977 100644
--- a/include/linux/landlock.h
+++ b/include/linux/landlock.h
@@ -9,6 +9,7 @@
 #ifndef _LINUX_LANDLOCK_H
 #define _LINUX_LANDLOCK_H
 
+#include <linux/bpf.h>
 #include <linux/errno.h>
 #include <linux/sched.h> /* task_struct */
 
@@ -31,4 +32,7 @@ static inline void get_seccomp_landlock(struct task_struct 
*tsk)
 }
 #endif /* CONFIG_SECCOMP_FILTER && CONFIG_SECURITY_LANDLOCK */
 
+int landlock_inode_add_map(struct inode *inode, struct bpf_map *map);
+void landlock_inode_remove_map(struct inode *inode, const struct bpf_map *map);
+
 #endif /* _LINUX_LANDLOCK_H */
diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h
index d68613f737f3..2da054ca9c8b 100644
--- a/include/uapi/linux/bpf.h
+++ b/include/uapi/linux/bpf.h
@@ -134,6 +134,7 @@ enum bpf_map_type {
        BPF_MAP_TYPE_QUEUE,
        BPF_MAP_TYPE_STACK,
        BPF_MAP_TYPE_SK_STORAGE,
+       BPF_MAP_TYPE_INODE,
 };
 
 /* Note that tracing related programs such as
@@ -2717,6 +2718,14 @@ union bpf_attr {
  *             **-EPERM** if no permission to send the *sig*.
  *
  *             **-EAGAIN** if bpf program can try again.
+ *
+ * void *bpf_inode_map_lookup_elem(struct bpf_map *map, const void *key)
+ *     Description
+ *             Perform a lookup in *map* for an entry associated to an inode
+ *             *key*.
+ *     Return
+ *             Map value associated to *key*, or **NULL** if no entry was
+ *             found.
  */
 #define __BPF_FUNC_MAPPER(FN)          \
        FN(unspec),                     \
@@ -2828,7 +2837,8 @@ union bpf_attr {
        FN(strtoul),                    \
        FN(sk_storage_get),             \
        FN(sk_storage_delete),          \
-       FN(send_signal),
+       FN(send_signal),                \
+       FN(inode_map_lookup_elem),
 
 /* integer value in 'imm' field of BPF_CALL instruction selects which helper
  * function eBPF program intends to call
diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
index 16079550db6d..4177c818e5cd 100644
--- a/kernel/bpf/core.c
+++ b/kernel/bpf/core.c
@@ -2040,6 +2040,8 @@ const struct bpf_func_proto bpf_get_current_comm_proto 
__weak;
 const struct bpf_func_proto bpf_get_current_cgroup_id_proto __weak;
 const struct bpf_func_proto bpf_get_local_storage_proto __weak;
 
+const struct bpf_func_proto bpf_inode_map_update_proto __weak;
+
 const struct bpf_func_proto * __weak bpf_get_trace_printk_proto(void)
 {
        return NULL;
diff --git a/kernel/bpf/hashtab.c b/kernel/bpf/hashtab.c
index 22066a62c8c9..4fc7755042f0 100644
--- a/kernel/bpf/hashtab.c
+++ b/kernel/bpf/hashtab.c
@@ -1,13 +1,21 @@
 // SPDX-License-Identifier: GPL-2.0-only
 /* Copyright (c) 2011-2014 PLUMgrid, http://plumgrid.com
  * Copyright (c) 2016 Facebook
+ * Copyright (c) 2017-2019 Mickaël Salaün <[email protected]>
+ * Copyright (c) 2019 ANSSI
  */
+#include <asm/resource.h> /* RLIMIT_NOFILE */
 #include <linux/bpf.h>
 #include <linux/btf.h>
+#include <linux/err.h>
 #include <linux/jhash.h>
+#include <linux/fs.h> /* iput() */
 #include <linux/filter.h>
+#include <linux/landlock.h>
+#include <linux/mount.h> /* MNT_INTERNAL */
 #include <linux/rculist_nulls.h>
 #include <linux/random.h>
+#include <linux/sched/signal.h> /* rlimit() */
 #include <uapi/linux/btf.h>
 #include "percpu_freelist.h"
 #include "bpf_lru_list.h"
@@ -684,6 +692,8 @@ static void free_htab_elem(struct bpf_htab *htab, struct 
htab_elem *l)
 
                map->ops->map_fd_put_ptr(ptr);
        }
+       if (map->ops->map_put_key)
+               map->ops->map_put_key(l->key);
 
        if (htab_is_prealloc(htab)) {
                __pcpu_freelist_push(&htab->freelist, &l->fnode);
@@ -1514,3 +1524,246 @@ const struct bpf_map_ops htab_of_maps_map_ops = {
        .map_gen_lookup = htab_of_map_gen_lookup,
        .map_check_btf = map_check_no_btf,
 };
+
+/* inode_htab */
+
+static int inode_htab_map_alloc_check(union bpf_attr *attr)
+{
+       /* only allow root to create this type of map (for now), should be
+        * removed when Landlock will be usable by unprivileged users */
+       if (!capable(CAP_SYS_ADMIN))
+               return -EPERM;
+
+       /* the key is a file descriptor */
+       if (attr->max_entries == 0 || attr->key_size != sizeof(int) ||
+           (attr->map_flags & ~(BPF_F_RDONLY | BPF_F_WRONLY |
+                                BPF_F_RDONLY_PROG)) ||
+           /* for now, force read-only map for eBPF programs because only
+            * bpf_inode_map_lookup_elem() enable to access them */
+           !(attr->map_flags & BPF_F_RDONLY_PROG) ||
+           bpf_map_attr_numa_node(attr) != NUMA_NO_NODE)
+               return -EINVAL;
+
+       /*
+        * Limit number of entries in an inode map to the maximum number of
+        * open files for the current process. The maximum number of file
+        * references (including all inode maps) for a process is then
+        * (RLIMIT_NOFILE - 1) * RLIMIT_NOFILE. If the process' RLIMIT_NOFILE
+        * is 0, then any entry update is forbidden.
+        *
+        * An eBPF program can inherit all the inode map FD. The worse case is
+        * to fill a bunch of arraymaps, create an eBPF program, close the
+        * inode map FDs, and start again. The maximum number of inode map
+        * entries can then be close to RLIMIT_NOFILE^3.
+        */
+       if (attr->max_entries > rlimit(RLIMIT_NOFILE))
+               return -EMFILE;
+
+       /* decorelate UAPI from kernel API */
+       attr->key_size = sizeof(struct inode *);
+
+       return htab_map_alloc_check(attr);
+}
+
+static void inode_htab_put_key(void *key)
+{
+       struct inode **inode = key;
+
+       if ((*inode)->i_state & I_FREEING)
+               return;
+       iput(*inode);
+}
+
+/* called from syscall or (never) from eBPF program */
+static int map_get_next_no_key(struct bpf_map *map, void *key, void *next_key)
+{
+       /* do not leak a file descriptor */
+       return -ENOTSUPP;
+}
+
+/* must call iput(inode) after this call */
+static struct inode *inode_from_fd(int ufd, bool check_access)
+{
+       struct inode *ret;
+       struct fd f;
+       int deny;
+
+       f = fdget(ufd);
+       if (unlikely(!f.file))
+               return ERR_PTR(-EBADF);
+       /* TODO?: add this check when called from an eBPF program too (already
+       * checked by the LSM parent hooks anyway) */
+       if (unlikely(IS_PRIVATE(file_inode(f.file)))) {
+               ret = ERR_PTR(-EINVAL);
+               goto put_fd;
+       }
+       /* check if the FD is tied to a mount point */
+       /* TODO?: add this check when called from an eBPF program too */
+       if (unlikely(f.file->f_path.mnt->mnt_flags & MNT_INTERNAL)) {
+               ret = ERR_PTR(-EINVAL);
+               goto put_fd;
+       }
+       if (check_access) {
+               /*
+               * must be allowed to access attributes from this file to then
+               * be able to compare an inode to its map entry
+               */
+               deny = security_inode_getattr(&f.file->f_path);
+               if (deny) {
+                       ret = ERR_PTR(deny);
+                       goto put_fd;
+               }
+       }
+       ret = file_inode(f.file);
+       ihold(ret);
+
+put_fd:
+       fdput(f);
+       return ret;
+}
+
+/*
+ * The key is a FD when called from a syscall, but an inode address when called
+ * from an eBPF program.
+ */
+
+/* called from syscall */
+int bpf_inode_fd_htab_map_lookup_elem(struct bpf_map *map, int *key, void 
*value)
+{
+       void *ptr;
+       struct inode *inode;
+       int ret;
+
+       /* check inode access */
+       inode = inode_from_fd(*key, true);
+       if (IS_ERR(inode))
+               return PTR_ERR(inode);
+
+       rcu_read_lock();
+       ptr = htab_map_lookup_elem(map, &inode);
+       iput(inode);
+       if (IS_ERR(ptr)) {
+               ret = PTR_ERR(ptr);
+       } else if (!ptr) {
+               ret = -ENOENT;
+       } else {
+               ret = 0;
+               copy_map_value(map, value, ptr);
+       }
+       rcu_read_unlock();
+       return ret;
+}
+
+/* called from kernel */
+int bpf_inode_ptr_locked_htab_map_delete_elem(struct bpf_map *map,
+               struct inode **key, bool remove_in_inode)
+{
+       if (remove_in_inode)
+               landlock_inode_remove_map(*key, map);
+       return htab_map_delete_elem(map, key);
+}
+
+/* called from syscall */
+int bpf_inode_fd_htab_map_delete_elem(struct bpf_map *map, int *key)
+{
+       struct inode *inode;
+       int ret;
+
+       /* do not check inode access (similar to directory check) */
+       inode = inode_from_fd(*key, false);
+       if (IS_ERR(inode))
+               return PTR_ERR(inode);
+       ret = bpf_inode_ptr_locked_htab_map_delete_elem(map, &inode, true);
+       iput(inode);
+       return ret;
+}
+
+/* called from syscall */
+int bpf_inode_fd_htab_map_update_elem(struct bpf_map *map, int *key, void 
*value,
+               u64 map_flags)
+{
+       struct inode *inode;
+       int ret;
+
+       WARN_ON_ONCE(!rcu_read_lock_held());
+
+       /* check inode access */
+       inode = inode_from_fd(*key, true);
+       if (IS_ERR(inode))
+               return PTR_ERR(inode);
+       ret = htab_map_update_elem(map, &inode, value, map_flags);
+       if (!ret)
+               ret = landlock_inode_add_map(inode, map);
+       iput(inode);
+       return ret;
+}
+
+static void inode_htab_map_free(struct bpf_map *map)
+{
+       struct bpf_htab *htab = container_of(map, struct bpf_htab, map);
+       struct hlist_nulls_node *n;
+       struct hlist_nulls_head *head;
+       struct htab_elem *l;
+       int i;
+
+       for (i = 0; i < htab->n_buckets; i++) {
+               head = select_bucket(htab, i);
+               hlist_nulls_for_each_entry_safe(l, n, head, hash_node) {
+                       landlock_inode_remove_map(*((struct inode **)l->key), 
map);
+               }
+       }
+       htab_map_free(map);
+}
+
+/* use the map_inode_lookup_elem() helper instead */
+static void *map_lookup_no_elem(struct bpf_map *map, void *key)
+{
+       WARN_ON_ONCE(1);
+       return NULL;
+}
+
+static int map_delete_no_elem(struct bpf_map *map, void *key)
+{
+       WARN_ON_ONCE(1);
+       return -ENOTSUPP;
+}
+
+static int map_update_no_elem(struct bpf_map *map, void *key, void *value,
+               u64 flags)
+{
+       WARN_ON_ONCE(1);
+       return -ENOTSUPP;
+}
+
+const struct bpf_map_ops htab_inode_ops = {
+       .map_alloc_check = inode_htab_map_alloc_check,
+       .map_alloc = htab_map_alloc,
+       .map_free = inode_htab_map_free,
+       .map_put_key = inode_htab_put_key,
+       .map_get_next_key = map_get_next_no_key,
+       .map_lookup_elem = map_lookup_no_elem,
+       .map_delete_elem = map_delete_no_elem,
+       .map_update_elem = map_update_no_elem,
+       .map_check_btf = map_check_no_btf,
+};
+
+/*
+ * We need a dedicated helper to deal with inode maps because the key is a
+ * pointer to an opaque data, only provided by the kernel.  This really act
+ * like a (physical or cryptographic) key, which is why it is also not allowed
+ * to get the next key with map_get_next_key().
+ */
+BPF_CALL_2(bpf_inode_map_lookup_elem, struct bpf_map *, map, void *, key)
+{
+       WARN_ON_ONCE(!rcu_read_lock_held());
+       return (unsigned long)htab_map_lookup_elem(map, &key);
+}
+
+const struct bpf_func_proto bpf_inode_map_lookup_elem_proto = {
+       .func           = bpf_inode_map_lookup_elem,
+       .gpl_only       = false,
+       .pkt_access     = true,
+       .ret_type       = RET_PTR_TO_MAP_VALUE_OR_NULL,
+       .arg1_type      = ARG_CONST_MAP_PTR,
+       .arg2_type      = ARG_PTR_TO_INODE,
+};
diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
index b2a8cb14f28e..e46441c42b68 100644
--- a/kernel/bpf/syscall.c
+++ b/kernel/bpf/syscall.c
@@ -801,6 +801,8 @@ static int map_lookup_elem(union bpf_attr *attr)
        } else if (map->map_type == BPF_MAP_TYPE_QUEUE ||
                   map->map_type == BPF_MAP_TYPE_STACK) {
                err = map->ops->map_peek_elem(map, value);
+       } else if (map->map_type == BPF_MAP_TYPE_INODE) {
+               err = bpf_inode_fd_htab_map_lookup_elem(map, key, value);
        } else {
                rcu_read_lock();
                if (map->ops->map_lookup_elem_sys_only)
@@ -951,6 +953,10 @@ static int map_update_elem(union bpf_attr *attr)
        } else if (map->map_type == BPF_MAP_TYPE_QUEUE ||
                   map->map_type == BPF_MAP_TYPE_STACK) {
                err = map->ops->map_push_elem(map, value, attr->flags);
+       } else if (map->map_type == BPF_MAP_TYPE_INODE) {
+               rcu_read_lock();
+               err = bpf_inode_fd_htab_map_update_elem(map, key, value, 
attr->flags);
+               rcu_read_unlock();
        } else {
                rcu_read_lock();
                err = map->ops->map_update_elem(map, key, value, attr->flags);
@@ -1006,7 +1012,10 @@ static int map_delete_elem(union bpf_attr *attr)
        preempt_disable();
        __this_cpu_inc(bpf_prog_active);
        rcu_read_lock();
-       err = map->ops->map_delete_elem(map, key);
+       if (map->map_type == BPF_MAP_TYPE_INODE)
+               err = bpf_inode_fd_htab_map_delete_elem(map, key);
+       else
+               err = map->ops->map_delete_elem(map, key);
        rcu_read_unlock();
        __this_cpu_dec(bpf_prog_active);
        preempt_enable();
@@ -1018,6 +1027,22 @@ static int map_delete_elem(union bpf_attr *attr)
        return err;
 }
 
+int bpf_inode_ptr_unlocked_htab_map_delete_elem(struct bpf_map *map,
+                                               struct inode **key, bool 
remove_in_inode)
+{
+       int err;
+
+       preempt_disable();
+       __this_cpu_inc(bpf_prog_active);
+       rcu_read_lock();
+       err = bpf_inode_ptr_locked_htab_map_delete_elem(map, key, 
remove_in_inode);
+       rcu_read_unlock();
+       __this_cpu_dec(bpf_prog_active);
+       preempt_enable();
+       maybe_wait_bpf_programs(map);
+       return err;
+}
+
 /* last field in 'union bpf_attr' used by this command */
 #define BPF_MAP_GET_NEXT_KEY_LAST_FIELD next_key
 
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 026c68cb9116..3972b9f02dac 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -400,6 +400,7 @@ static const char * const reg_type_str[] = {
        [PTR_TO_TCP_SOCK_OR_NULL] = "tcp_sock_or_null",
        [PTR_TO_TP_BUFFER]      = "tp_buffer",
        [PTR_TO_XDP_SOCK]       = "xdp_sock",
+       [PTR_TO_INODE]          = "inode",
 };
 
 static char slot_type_char[] = {
@@ -1846,6 +1847,7 @@ static bool is_spillable_regtype(enum bpf_reg_type type)
        case PTR_TO_TCP_SOCK:
        case PTR_TO_TCP_SOCK_OR_NULL:
        case PTR_TO_XDP_SOCK:
+       case PTR_TO_INODE:
                return true;
        default:
                return false;
@@ -3306,6 +3308,10 @@ static int check_func_arg(struct bpf_verifier_env *env, 
u32 regno,
                        verbose(env, "verifier internal error\n");
                        return -EFAULT;
                }
+       } else if (arg_type == ARG_PTR_TO_INODE) {
+               expected_type = PTR_TO_INODE;
+               if (type != expected_type)
+                       goto err_type;
        } else if (arg_type_is_mem_ptr(arg_type)) {
                expected_type = PTR_TO_STACK;
                /* One exception here. In case function allows for NULL to be
@@ -3511,6 +3517,10 @@ static int check_map_func_compatibility(struct 
bpf_verifier_env *env,
                    func_id != BPF_FUNC_sk_storage_delete)
                        goto error;
                break;
+       case BPF_MAP_TYPE_INODE:
+               if (func_id != BPF_FUNC_inode_map_lookup_elem)
+                       goto error;
+               break;
        default:
                break;
        }
@@ -3579,6 +3589,10 @@ static int check_map_func_compatibility(struct 
bpf_verifier_env *env,
                if (map->map_type != BPF_MAP_TYPE_SK_STORAGE)
                        goto error;
                break;
+       case BPF_FUNC_inode_map_lookup_elem:
+               if (map->map_type != BPF_MAP_TYPE_INODE)
+                       goto error;
+               break;
        default:
                break;
        }
diff --git a/security/landlock/common.h b/security/landlock/common.h
index b2ee018eb6fc..b0ba3f31ac7d 100644
--- a/security/landlock/common.h
+++ b/security/landlock/common.h
@@ -11,6 +11,7 @@
 
 #include <linux/bpf.h> /* enum bpf_attach_type */
 #include <linux/filter.h> /* bpf_prog */
+#include <linux/lsm_hooks.h> /* lsm_blob_sizes */
 #include <linux/refcount.h> /* refcount_t */
 #include <uapi/linux/landlock.h> /* LANDLOCK_TRIGGER_* */
 
@@ -23,6 +24,8 @@
 #define _LANDLOCK_TRIGGER_FS_PICK_LAST LANDLOCK_TRIGGER_FS_PICK_WRITE
 #define _LANDLOCK_TRIGGER_FS_PICK_MASK ((_LANDLOCK_TRIGGER_FS_PICK_LAST << 
1ULL) - 1)
 
+extern struct lsm_blob_sizes landlock_blob_sizes;
+
 enum landlock_hook_type {
        LANDLOCK_HOOK_FS_PICK = 1,
        LANDLOCK_HOOK_FS_WALK,
@@ -55,6 +58,17 @@ struct landlock_prog_set {
        refcount_t usage;
 };
 
+struct landlock_inode_map {
+       struct list_head list;
+       struct rcu_head rcu_put;
+       struct bpf_map *map;
+       /*
+        * It would be nice to remove the inode field, but it is necessary for
+        * call_rcu() .
+        */
+       struct inode *inode;
+};
+
 /**
  * get_hook_index - get an index for the programs of struct landlock_prog_set
  *
diff --git a/security/landlock/hooks_fs.c b/security/landlock/hooks_fs.c
index 3f81b7fc2938..8c9d6a333111 100644
--- a/security/landlock/hooks_fs.c
+++ b/security/landlock/hooks_fs.c
@@ -46,6 +46,12 @@ bool landlock_is_valid_access_fs_pick(int off, enum 
bpf_access_type type,
                enum bpf_reg_type *reg_type, int *max_size)
 {
        switch (off) {
+       case offsetof(struct landlock_ctx_fs_pick, inode):
+               if (type != BPF_READ)
+                       return false;
+               *reg_type = PTR_TO_INODE;
+               *max_size = sizeof(u64);
+               return true;
        default:
                return false;
        }
@@ -55,6 +61,12 @@ bool landlock_is_valid_access_fs_walk(int off, enum 
bpf_access_type type,
                enum bpf_reg_type *reg_type, int *max_size)
 {
        switch (off) {
+       case offsetof(struct landlock_ctx_fs_walk, inode):
+               if (type != BPF_READ)
+                       return false;
+               *reg_type = PTR_TO_INODE;
+               *max_size = sizeof(u64);
+               return true;
        default:
                return false;
        }
@@ -237,8 +249,79 @@ static int hook_sb_pivotroot(const struct path *old_path,
                        new_path->dentry->d_inode);
 }
 
+/* inode helpers */
+
+static inline struct list_head *inode_landlock(const struct inode *inode)
+{
+       return inode->i_security + landlock_blob_sizes.lbs_inode;
+}
+
+int landlock_inode_add_map(struct inode *inode, struct bpf_map *map)
+{
+       struct landlock_inode_map *inode_map;
+
+       inode_map = kzalloc(sizeof(*inode_map), GFP_ATOMIC);
+       if (!inode_map)
+               return -ENOMEM;
+       INIT_LIST_HEAD(&inode_map->list);
+       inode_map->map = map;
+       inode_map->inode = inode;
+       list_add_tail(&inode_map->list, inode_landlock(inode));
+       return 0;
+}
+
+static void put_landlock_inode_map(struct rcu_head *head)
+{
+       struct landlock_inode_map *inode_map;
+       int err;
+
+       inode_map = container_of(head, struct landlock_inode_map, rcu_put);
+       err = bpf_inode_ptr_unlocked_htab_map_delete_elem(inode_map->map,
+                       &inode_map->inode, false);
+       bpf_map_put(inode_map->map);
+       kfree(inode_map);
+}
+
+void landlock_inode_remove_map(struct inode *inode, const struct bpf_map *map)
+{
+       struct landlock_inode_map *inode_map;
+       bool found = false;
+
+       rcu_read_lock();
+       list_for_each_entry_rcu(inode_map, inode_landlock(inode), list) {
+               if (inode_map->map == map) {
+                       found = true;
+                       list_del_rcu(&inode_map->list);
+                       kfree_rcu(inode_map, rcu_put);
+                       break;
+               }
+       }
+       rcu_read_unlock();
+       WARN_ON(!found);
+}
+
 /* inode hooks */
 
+static int hook_inode_alloc_security(struct inode *inode)
+{
+       struct list_head *ll_inode = inode_landlock(inode);
+
+       INIT_LIST_HEAD(ll_inode);
+       return 0;
+}
+
+static void hook_inode_free_security(struct inode *inode)
+{
+       struct landlock_inode_map *inode_map;
+
+       rcu_read_lock();
+       list_for_each_entry_rcu(inode_map, inode_landlock(inode), list) {
+               list_del_rcu(&inode_map->list);
+               call_rcu(&inode_map->rcu_put, put_landlock_inode_map);
+       }
+       rcu_read_unlock();
+}
+
 /* a directory inode contains only one dentry */
 static int hook_inode_create(struct inode *dir, struct dentry *dentry,
                umode_t mode)
@@ -517,6 +600,8 @@ static struct security_hook_list landlock_hooks[] = {
        LSM_HOOK_INIT(sb_mount, hook_sb_mount),
        LSM_HOOK_INIT(sb_pivotroot, hook_sb_pivotroot),
 
+       LSM_HOOK_INIT(inode_alloc_security, hook_inode_alloc_security),
+       LSM_HOOK_INIT(inode_free_security, hook_inode_free_security),
        LSM_HOOK_INIT(inode_create, hook_inode_create),
        LSM_HOOK_INIT(inode_link, hook_inode_link),
        LSM_HOOK_INIT(inode_unlink, hook_inode_unlink),
diff --git a/security/landlock/init.c b/security/landlock/init.c
index 391e88bd4d3a..eec4467cb5ee 100644
--- a/security/landlock/init.c
+++ b/security/landlock/init.c
@@ -104,6 +104,18 @@ static const struct bpf_func_proto 
*bpf_landlock_func_proto(
        default:
                break;
        }
+
+       switch (get_hook_type(prog)) {
+       case LANDLOCK_HOOK_FS_WALK:
+       case LANDLOCK_HOOK_FS_PICK:
+               switch (func_id) {
+               case BPF_FUNC_inode_map_lookup_elem:
+                       return &bpf_inode_map_lookup_elem_proto;
+               default:
+                       break;
+               }
+               break;
+       }
        return NULL;
 }
 
@@ -123,6 +135,7 @@ static int __init landlock_init(void)
 }
 
 struct lsm_blob_sizes landlock_blob_sizes __lsm_ro_after_init = {
+       .lbs_inode = sizeof(struct list_head),
 };
 
 DEFINE_LSM(LANDLOCK_NAME) = {
diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h
index 7b7a4f6c3104..7a55535f5dc1 100644
--- a/tools/include/uapi/linux/bpf.h
+++ b/tools/include/uapi/linux/bpf.h
@@ -134,6 +134,7 @@ enum bpf_map_type {
        BPF_MAP_TYPE_QUEUE,
        BPF_MAP_TYPE_STACK,
        BPF_MAP_TYPE_SK_STORAGE,
+       BPF_MAP_TYPE_INODE,
 };
 
 /* Note that tracing related programs such as
@@ -2714,6 +2715,14 @@ union bpf_attr {
  *             **-EPERM** if no permission to send the *sig*.
  *
  *             **-EAGAIN** if bpf program can try again.
+ *
+ * void *bpf_inode_map_lookup_elem(struct bpf_map *map, const void *key)
+ *     Description
+ *             Perform a lookup in *map* for an entry associated to an inode
+ *             *key*.
+ *     Return
+ *             Map value associated to *key*, or **NULL** if no entry was
+ *             found.
  */
 #define __BPF_FUNC_MAPPER(FN)          \
        FN(unspec),                     \
@@ -2825,7 +2834,8 @@ union bpf_attr {
        FN(strtoul),                    \
        FN(sk_storage_get),             \
        FN(sk_storage_delete),          \
-       FN(send_signal),
+       FN(send_signal),                \
+       FN(inode_map_lookup_elem),
 
 /* integer value in 'imm' field of BPF_CALL instruction selects which helper
  * function eBPF program intends to call
diff --git a/tools/lib/bpf/libbpf_probes.c b/tools/lib/bpf/libbpf_probes.c
index 03c910d1f84c..98875221310d 100644
--- a/tools/lib/bpf/libbpf_probes.c
+++ b/tools/lib/bpf/libbpf_probes.c
@@ -250,6 +250,7 @@ bool bpf_probe_map_type(enum bpf_map_type map_type, __u32 
ifindex)
        case BPF_MAP_TYPE_XSKMAP:
        case BPF_MAP_TYPE_SOCKHASH:
        case BPF_MAP_TYPE_REUSEPORT_SOCKARRAY:
+       case BPF_MAP_TYPE_INODE:
        default:
                break;
        }
-- 
2.22.0

Reply via email to