Add a new type of eBPF program used by Landlock rules and the functions to verify and evaluate them. Changes since v3: * split commit Signed-off-by: Mickaël Salaün <mic@xxxxxxxxxxx> Cc: Alexei Starovoitov <ast@xxxxxxxxxx> Cc: Andy Lutomirski <luto@xxxxxxxxxxxxxx> Cc: Daniel Borkmann <daniel@xxxxxxxxxxxxx> Cc: David S. Miller <davem@xxxxxxxxxxxxx> Cc: James Morris <james.l.morris@xxxxxxxxxx> Cc: Kees Cook <keescook@xxxxxxxxxxxx> Cc: Serge E. Hallyn <serge@xxxxxxxxxx> --- include/linux/landlock.h | 76 +++++++++++++++++++ include/uapi/linux/bpf.h | 20 +++++ kernel/bpf/syscall.c | 10 ++- security/Makefile | 2 + security/landlock/Makefile | 3 + security/landlock/common.h | 27 +++++++ security/landlock/lsm.c | 181 +++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 317 insertions(+), 2 deletions(-) create mode 100644 include/linux/landlock.h create mode 100644 security/landlock/Makefile create mode 100644 security/landlock/common.h create mode 100644 security/landlock/lsm.c diff --git a/include/linux/landlock.h b/include/linux/landlock.h new file mode 100644 index 000000000000..2ab2be8e3e6e --- /dev/null +++ b/include/linux/landlock.h @@ -0,0 +1,76 @@ +/* + * Landlock LSM - Public headers + * + * Copyright (C) 2016 Mickaël Salaün <mic@xxxxxxxxxxx> + * + * 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. + */ + +#ifndef _LINUX_LANDLOCK_H +#define _LINUX_LANDLOCK_H +#ifdef CONFIG_SECURITY_LANDLOCK + +#include <linux/bpf.h> /* _LANDLOCK_HOOK_LAST */ +#include <linux/types.h> /* atomic_t */ + +#ifdef CONFIG_SECCOMP_FILTER +#include <linux/seccomp.h> /* struct seccomp_filter */ +#endif /* CONFIG_SECCOMP_FILTER */ + +struct landlock_rule; + +/** + * struct landlock_node - node in the rule hierarchy + * + * This is created when a task insert its first rule in the Landlock rule + * hierarchy. The set of Landlock rules referenced by this node is then + * enforced for all the task that inherit this node. However, if a task is + * cloned before inserting new rules, it doesn't get a dedicated node and its + * children will not inherit this new rules. + * + * @usage: reference count to manage the node lifetime. + * @rule: list of Landlock rules managed by this node. + * @prev: reference the parent node. + * @owner: reference the address of the node in the struct landlock_hooks. This + * is needed to know if we need to append a rule to the current node or + * create a new node. + */ +struct landlock_node { + atomic_t usage; + struct landlock_rule *rule; + struct landlock_node *prev; + struct landlock_node **owner; +}; + +struct landlock_rule { + atomic_t usage; + struct landlock_rule *prev; + struct bpf_prog *prog; +}; + +/** + * struct landlock_hooks - Landlock hook programs enforced on a thread + * + * This is used for low performance impact when forking a process. Instead of + * copying the full array and incrementing the usage field of each entries, + * only create a pointer to struct landlock_hooks and increment the usage + * field. + * + * A new struct landlock_hooks must be created thanks to a call to + * new_landlock_hooks(). + * + * @usage: reference count to manage the object lifetime. When a thread need to + * add Landlock programs and if @usage is greater than 1, then the + * thread must duplicate struct landlock_hooks to not change the + * children' rules as well. FIXME + * @nodes: array of non-NULL struct landlock_node pointers. + */ +struct landlock_hooks { + atomic_t usage; + struct landlock_node *nodes[_LANDLOCK_HOOK_LAST]; +}; + +#endif /* CONFIG_SECURITY_LANDLOCK */ +#endif /* _LINUX_LANDLOCK_H */ diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h index 06621c401bc0..335616ab63ff 100644 --- a/include/uapi/linux/bpf.h +++ b/include/uapi/linux/bpf.h @@ -111,6 +111,7 @@ enum bpf_prog_type { BPF_PROG_TYPE_XDP, BPF_PROG_TYPE_PERF_EVENT, BPF_PROG_TYPE_CGROUP_SKB, + BPF_PROG_TYPE_LANDLOCK, }; enum bpf_attach_type { @@ -559,6 +560,12 @@ struct xdp_md { __u32 data_end; }; +/* LSM hooks */ +enum landlock_hook { + LANDLOCK_HOOK_UNSPEC, +}; +#define _LANDLOCK_HOOK_LAST LANDLOCK_HOOK_UNSPEC + /* eBPF context and functions allowed for a rule */ #define _LANDLOCK_SUBTYPE_ACCESS_MASK ((1ULL << 0) - 1) @@ -577,4 +584,17 @@ struct landlock_handle { }; } __attribute__((aligned(8))); +/** + * struct landlock_data + * + * @hook: LSM hook ID (e.g. BPF_PROG_TYPE_LANDLOCK_FILE_OPEN) + * @args: LSM hook arguments, see include/linux/lsm_hooks.h for there + * description and the LANDLOCK_HOOK* definitions from + * security/landlock/lsm.c for their types. + */ +struct landlock_data { + __u32 hook; /* enum landlock_hook */ + __u64 args[6]; +}; + #endif /* _UAPI__LINUX_BPF_H__ */ diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c index 6eef1da1e8a3..0f7faa9d2262 100644 --- a/kernel/bpf/syscall.c +++ b/kernel/bpf/syscall.c @@ -739,8 +739,14 @@ static int bpf_prog_load(union bpf_attr *attr) attr->kern_version != LINUX_VERSION_CODE) return -EINVAL; - if (type != BPF_PROG_TYPE_SOCKET_FILTER && !capable(CAP_SYS_ADMIN)) - return -EPERM; + switch (type) { + case BPF_PROG_TYPE_SOCKET_FILTER: + case BPF_PROG_TYPE_LANDLOCK: + break; + default: + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + } /* plain bpf_prog allocation */ prog = bpf_prog_alloc(bpf_prog_size(attr->insn_cnt), GFP_USER); diff --git a/security/Makefile b/security/Makefile index f2d71cdb8e19..3fdc2f19dc48 100644 --- a/security/Makefile +++ b/security/Makefile @@ -9,6 +9,7 @@ subdir-$(CONFIG_SECURITY_TOMOYO) += tomoyo subdir-$(CONFIG_SECURITY_APPARMOR) += apparmor subdir-$(CONFIG_SECURITY_YAMA) += yama subdir-$(CONFIG_SECURITY_LOADPIN) += loadpin +subdir-$(CONFIG_SECURITY_LANDLOCK) += landlock # always enable default capabilities obj-y += commoncap.o @@ -24,6 +25,7 @@ obj-$(CONFIG_SECURITY_TOMOYO) += tomoyo/ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor/ obj-$(CONFIG_SECURITY_YAMA) += yama/ obj-$(CONFIG_SECURITY_LOADPIN) += loadpin/ +obj-$(CONFIG_SECURITY_LANDLOCK) += landlock/ obj-$(CONFIG_CGROUP_DEVICE) += device_cgroup.o # Object integrity file lists diff --git a/security/landlock/Makefile b/security/landlock/Makefile new file mode 100644 index 000000000000..59669d70bc7e --- /dev/null +++ b/security/landlock/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_SECURITY_LANDLOCK) := landlock.o + +landlock-y := lsm.o diff --git a/security/landlock/common.h b/security/landlock/common.h new file mode 100644 index 000000000000..0b5aad4a7aaa --- /dev/null +++ b/security/landlock/common.h @@ -0,0 +1,27 @@ +/* + * Landlock LSM - private headers + * + * Copyright (C) 2016 Mickaël Salaün <mic@xxxxxxxxxxx> + * + * 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. + */ + +#ifndef _SECURITY_LANDLOCK_COMMON_H +#define _SECURITY_LANDLOCK_COMMON_H + +#include <linux/bpf.h> /* enum landlock_hook */ + +/** + * get_index - get an index for the rules of struct landlock_hooks + * + * @hook: a Landlock hook ID + */ +static inline int get_index(enum landlock_hook hook) +{ + /* hook ID > 0 for loaded programs */ + return hook - 1; +} + +#endif /* _SECURITY_LANDLOCK_COMMON_H */ diff --git a/security/landlock/lsm.c b/security/landlock/lsm.c new file mode 100644 index 000000000000..d7564540c493 --- /dev/null +++ b/security/landlock/lsm.c @@ -0,0 +1,181 @@ +/* + * Landlock LSM + * + * Copyright (C) 2016 Mickaël Salaün <mic@xxxxxxxxxxx> + * + * 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_reg_type, struct landlock_data */ +#include <linux/cred.h> +#include <linux/err.h> /* MAX_ERRNO */ +#include <linux/filter.h> /* struct bpf_prog, BPF_PROG_RUN() */ +#include <linux/kernel.h> /* FIELD_SIZEOF() */ +#include <linux/landlock.h> +#include <linux/lsm_hooks.h> + +#include "common.h" + +/** + * landlock_run_prog - run Landlock program for a syscall + * + * @hook_idx: hook index in the rules array + * @ctx: non-NULL eBPF context + * @hooks: Landlock hooks pointer + */ +static u32 landlock_run_prog(u32 hook_idx, const struct landlock_data *ctx, + struct landlock_hooks *hooks) +{ + struct landlock_node *node; + u32 ret = 0; + + if (!hooks) + return 0; + + for (node = hooks->nodes[hook_idx]; node; node = node->prev) { + struct landlock_rule *rule; + + for (rule = node->rule; rule; rule = rule->prev) { + if (WARN_ON(!rule->prog)) + continue; + rcu_read_lock(); + ret = BPF_PROG_RUN(rule->prog, (void *)ctx); + rcu_read_unlock(); + if (ret) { + if (ret > MAX_ERRNO) + ret = MAX_ERRNO; + goto out; + } + } + } + +out: + return ret; +} + +static int landlock_enforce(enum landlock_hook hook, __u64 args[6]) +{ + u32 ret = 0; + u32 hook_idx = get_index(hook); + + struct landlock_data ctx = { + .hook = hook, + .args[0] = args[0], + .args[1] = args[1], + .args[2] = args[2], + .args[3] = args[3], + .args[4] = args[4], + .args[5] = args[5], + }; + + /* placeholder for seccomp and cgroup managers */ + ret = landlock_run_prog(hook_idx, &ctx, NULL); + + return -ret; +} + +static const struct bpf_func_proto *bpf_landlock_func_proto( + enum bpf_func_id func_id, union bpf_prog_subtype *prog_subtype) +{ + switch (func_id) { + default: + return NULL; + } +} + +static bool __is_valid_access(int off, int size, enum bpf_access_type type, + enum bpf_reg_type *reg_type, + enum bpf_reg_type arg_types[6], + union bpf_prog_subtype *prog_subtype) +{ + int arg_nb, expected_size; + + if (type != BPF_READ) + return false; + if (off < 0 || off >= sizeof(struct landlock_data)) + return false; + + /* check size */ + switch (off) { + case offsetof(struct landlock_data, hook): + expected_size = sizeof(__u32); + break; + case offsetof(struct landlock_data, args[0]) ... + offsetof(struct landlock_data, args[5]): + expected_size = sizeof(__u64); + break; + default: + return false; + } + if (expected_size != size) + return false; + + /* check pointer access and set pointer type */ + switch (off) { + case offsetof(struct landlock_data, args[0]) ... + offsetof(struct landlock_data, args[5]): + arg_nb = (off - offsetof(struct landlock_data, args[0])) + / FIELD_SIZEOF(struct landlock_data, args[0]); + *reg_type = arg_types[arg_nb]; + if (*reg_type == NOT_INIT) + return false; + break; + } + + return true; +} + +static inline bool bpf_landlock_is_valid_access(int off, int size, + enum bpf_access_type type, enum bpf_reg_type *reg_type, + union bpf_prog_subtype *prog_subtype) +{ + enum landlock_hook hook = prog_subtype->landlock_rule.hook; + + switch (hook) { + case LANDLOCK_HOOK_UNSPEC: + default: + return false; + } +} + +static inline bool bpf_landlock_is_valid_subtype( + union bpf_prog_subtype *prog_subtype) +{ + enum landlock_hook hook = prog_subtype->landlock_rule.hook; + + switch (hook) { + case LANDLOCK_HOOK_UNSPEC: + default: + return false; + } + if (!prog_subtype->landlock_rule.hook || + prog_subtype->landlock_rule.hook > _LANDLOCK_HOOK_LAST) + return false; + if (prog_subtype->landlock_rule.access & ~_LANDLOCK_SUBTYPE_ACCESS_MASK) + return false; + if (prog_subtype->landlock_rule.option & ~_LANDLOCK_SUBTYPE_OPTION_MASK) + return false; + + return true; +} + +static const struct bpf_verifier_ops bpf_landlock_ops = { + .get_func_proto = bpf_landlock_func_proto, + .is_valid_access = bpf_landlock_is_valid_access, + .is_valid_subtype = bpf_landlock_is_valid_subtype, +}; + +static struct bpf_prog_type_list bpf_landlock_type __read_mostly = { + .ops = &bpf_landlock_ops, + .type = BPF_PROG_TYPE_LANDLOCK, +}; + +static int __init register_landlock_filter_ops(void) +{ + bpf_register_prog_type(&bpf_landlock_type); + return 0; +} + +late_initcall(register_landlock_filter_ops); -- 2.9.3 -- To unsubscribe from this list: send the line "unsubscribe linux-api" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html