Commit 9e4e01df authored by KP Singh's avatar KP Singh Committed by Daniel Borkmann

bpf: lsm: Implement attach, detach and execution

JITed BPF programs are dynamically attached to the LSM hooks
using BPF trampolines. The trampoline prologue generates code to handle
conversion of the signature of the hook to the appropriate BPF context.

The allocated trampoline programs are attached to the nop functions
initialized as LSM hooks.

BPF_PROG_TYPE_LSM programs must have a GPL compatible license and
and need CAP_SYS_ADMIN (required for loading eBPF programs).

Upon attachment:

* A BPF fexit trampoline is used for LSM hooks with a void return type.
* A BPF fmod_ret trampoline is used for LSM hooks which return an
  int. The attached programs can override the return value of the
  bpf LSM hook to indicate a MAC Policy decision.
Signed-off-by: default avatarKP Singh <kpsingh@google.com>
Signed-off-by: default avatarDaniel Borkmann <daniel@iogearbox.net>
Reviewed-by: default avatarBrendan Jackman <jackmanb@google.com>
Reviewed-by: default avatarFlorent Revest <revest@google.com>
Acked-by: default avatarAndrii Nakryiko <andriin@fb.com>
Acked-by: default avatarJames Morris <jamorris@linux.microsoft.com>
Link: https://lore.kernel.org/bpf/20200329004356.27286-5-kpsingh@chromium.org
parent 9d3fdea7
...@@ -17,6 +17,17 @@ ...@@ -17,6 +17,17 @@
#include <linux/lsm_hook_defs.h> #include <linux/lsm_hook_defs.h>
#undef LSM_HOOK #undef LSM_HOOK
int bpf_lsm_verify_prog(struct bpf_verifier_log *vlog,
const struct bpf_prog *prog);
#else /* !CONFIG_BPF_LSM */
static inline int bpf_lsm_verify_prog(struct bpf_verifier_log *vlog,
const struct bpf_prog *prog)
{
return -EOPNOTSUPP;
}
#endif /* CONFIG_BPF_LSM */ #endif /* CONFIG_BPF_LSM */
#endif /* _LINUX_BPF_LSM_H */ #endif /* _LINUX_BPF_LSM_H */
...@@ -9,6 +9,8 @@ ...@@ -9,6 +9,8 @@
#include <linux/btf.h> #include <linux/btf.h>
#include <linux/lsm_hooks.h> #include <linux/lsm_hooks.h>
#include <linux/bpf_lsm.h> #include <linux/bpf_lsm.h>
#include <linux/kallsyms.h>
#include <linux/bpf_verifier.h>
/* For every LSM hook that allows attachment of BPF programs, declare a nop /* For every LSM hook that allows attachment of BPF programs, declare a nop
* function where a BPF program can be attached. * function where a BPF program can be attached.
...@@ -22,6 +24,27 @@ noinline RET bpf_lsm_##NAME(__VA_ARGS__) \ ...@@ -22,6 +24,27 @@ noinline RET bpf_lsm_##NAME(__VA_ARGS__) \
#include <linux/lsm_hook_defs.h> #include <linux/lsm_hook_defs.h>
#undef LSM_HOOK #undef LSM_HOOK
#define BPF_LSM_SYM_PREFX "bpf_lsm_"
int bpf_lsm_verify_prog(struct bpf_verifier_log *vlog,
const struct bpf_prog *prog)
{
if (!prog->gpl_compatible) {
bpf_log(vlog,
"LSM programs must have a GPL compatible license\n");
return -EINVAL;
}
if (strncmp(BPF_LSM_SYM_PREFX, prog->aux->attach_func_name,
sizeof(BPF_LSM_SYM_PREFX) - 1)) {
bpf_log(vlog, "attach_btf_id %u points to wrong type name %s\n",
prog->aux->attach_btf_id, prog->aux->attach_func_name);
return -EINVAL;
}
return 0;
}
const struct bpf_prog_ops lsm_prog_ops = { const struct bpf_prog_ops lsm_prog_ops = {
}; };
......
...@@ -3710,7 +3710,21 @@ bool btf_ctx_access(int off, int size, enum bpf_access_type type, ...@@ -3710,7 +3710,21 @@ bool btf_ctx_access(int off, int size, enum bpf_access_type type,
} }
if (arg == nr_args) { if (arg == nr_args) {
if (prog->expected_attach_type == BPF_TRACE_FEXIT) { if (prog->expected_attach_type == BPF_TRACE_FEXIT ||
prog->expected_attach_type == BPF_LSM_MAC) {
/* When LSM programs are attached to void LSM hooks
* they use FEXIT trampolines and when attached to
* int LSM hooks, they use MODIFY_RETURN trampolines.
*
* While the LSM programs are BPF_MODIFY_RETURN-like
* the check:
*
* if (ret_type != 'int')
* return -EINVAL;
*
* is _not_ done here. This is still safe as LSM hooks
* have only void and int return types.
*/
if (!t) if (!t)
return true; return true;
t = btf_type_by_id(btf, t->type); t = btf_type_by_id(btf, t->type);
......
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
#include <linux/nospec.h> #include <linux/nospec.h>
#include <linux/audit.h> #include <linux/audit.h>
#include <uapi/linux/btf.h> #include <uapi/linux/btf.h>
#include <linux/bpf_lsm.h>
#define IS_FD_ARRAY(map) ((map)->map_type == BPF_MAP_TYPE_PERF_EVENT_ARRAY || \ #define IS_FD_ARRAY(map) ((map)->map_type == BPF_MAP_TYPE_PERF_EVENT_ARRAY || \
(map)->map_type == BPF_MAP_TYPE_CGROUP_ARRAY || \ (map)->map_type == BPF_MAP_TYPE_CGROUP_ARRAY || \
...@@ -1935,6 +1936,7 @@ bpf_prog_load_check_attach(enum bpf_prog_type prog_type, ...@@ -1935,6 +1936,7 @@ bpf_prog_load_check_attach(enum bpf_prog_type prog_type,
switch (prog_type) { switch (prog_type) {
case BPF_PROG_TYPE_TRACING: case BPF_PROG_TYPE_TRACING:
case BPF_PROG_TYPE_LSM:
case BPF_PROG_TYPE_STRUCT_OPS: case BPF_PROG_TYPE_STRUCT_OPS:
case BPF_PROG_TYPE_EXT: case BPF_PROG_TYPE_EXT:
break; break;
...@@ -2366,10 +2368,28 @@ static int bpf_tracing_prog_attach(struct bpf_prog *prog) ...@@ -2366,10 +2368,28 @@ static int bpf_tracing_prog_attach(struct bpf_prog *prog)
struct file *link_file; struct file *link_file;
int link_fd, err; int link_fd, err;
switch (prog->type) {
case BPF_PROG_TYPE_TRACING:
if (prog->expected_attach_type != BPF_TRACE_FENTRY && if (prog->expected_attach_type != BPF_TRACE_FENTRY &&
prog->expected_attach_type != BPF_TRACE_FEXIT && prog->expected_attach_type != BPF_TRACE_FEXIT &&
prog->expected_attach_type != BPF_MODIFY_RETURN && prog->expected_attach_type != BPF_MODIFY_RETURN) {
prog->type != BPF_PROG_TYPE_EXT) { err = -EINVAL;
goto out_put_prog;
}
break;
case BPF_PROG_TYPE_EXT:
if (prog->expected_attach_type != 0) {
err = -EINVAL;
goto out_put_prog;
}
break;
case BPF_PROG_TYPE_LSM:
if (prog->expected_attach_type != BPF_LSM_MAC) {
err = -EINVAL;
goto out_put_prog;
}
break;
default:
err = -EINVAL; err = -EINVAL;
goto out_put_prog; goto out_put_prog;
} }
...@@ -2448,16 +2468,10 @@ static int bpf_raw_tracepoint_open(const union bpf_attr *attr) ...@@ -2448,16 +2468,10 @@ static int bpf_raw_tracepoint_open(const union bpf_attr *attr)
if (IS_ERR(prog)) if (IS_ERR(prog))
return PTR_ERR(prog); return PTR_ERR(prog);
if (prog->type != BPF_PROG_TYPE_RAW_TRACEPOINT && switch (prog->type) {
prog->type != BPF_PROG_TYPE_TRACING && case BPF_PROG_TYPE_TRACING:
prog->type != BPF_PROG_TYPE_EXT && case BPF_PROG_TYPE_EXT:
prog->type != BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE) { case BPF_PROG_TYPE_LSM:
err = -EINVAL;
goto out_put_prog;
}
if (prog->type == BPF_PROG_TYPE_TRACING ||
prog->type == BPF_PROG_TYPE_EXT) {
if (attr->raw_tracepoint.name) { if (attr->raw_tracepoint.name) {
/* The attach point for this category of programs /* The attach point for this category of programs
* should be specified via btf_id during program load. * should be specified via btf_id during program load.
...@@ -2465,11 +2479,14 @@ static int bpf_raw_tracepoint_open(const union bpf_attr *attr) ...@@ -2465,11 +2479,14 @@ static int bpf_raw_tracepoint_open(const union bpf_attr *attr)
err = -EINVAL; err = -EINVAL;
goto out_put_prog; goto out_put_prog;
} }
if (prog->expected_attach_type == BPF_TRACE_RAW_TP) if (prog->type == BPF_PROG_TYPE_TRACING &&
prog->expected_attach_type == BPF_TRACE_RAW_TP) {
tp_name = prog->aux->attach_func_name; tp_name = prog->aux->attach_func_name;
else break;
}
return bpf_tracing_prog_attach(prog); return bpf_tracing_prog_attach(prog);
} else { case BPF_PROG_TYPE_RAW_TRACEPOINT:
case BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE:
if (strncpy_from_user(buf, if (strncpy_from_user(buf,
u64_to_user_ptr(attr->raw_tracepoint.name), u64_to_user_ptr(attr->raw_tracepoint.name),
sizeof(buf) - 1) < 0) { sizeof(buf) - 1) < 0) {
...@@ -2478,6 +2495,10 @@ static int bpf_raw_tracepoint_open(const union bpf_attr *attr) ...@@ -2478,6 +2495,10 @@ static int bpf_raw_tracepoint_open(const union bpf_attr *attr)
} }
buf[sizeof(buf) - 1] = 0; buf[sizeof(buf) - 1] = 0;
tp_name = buf; tp_name = buf;
break;
default:
err = -EINVAL;
goto out_put_prog;
} }
btp = bpf_get_raw_tracepoint(tp_name); btp = bpf_get_raw_tracepoint(tp_name);
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
#include <linux/ftrace.h> #include <linux/ftrace.h>
#include <linux/rbtree_latch.h> #include <linux/rbtree_latch.h>
#include <linux/perf_event.h> #include <linux/perf_event.h>
#include <linux/btf.h>
/* dummy _ops. The verifier will operate on target program's ops. */ /* dummy _ops. The verifier will operate on target program's ops. */
const struct bpf_verifier_ops bpf_extension_verifier_ops = { const struct bpf_verifier_ops bpf_extension_verifier_ops = {
...@@ -233,15 +234,23 @@ static int bpf_trampoline_update(struct bpf_trampoline *tr) ...@@ -233,15 +234,23 @@ static int bpf_trampoline_update(struct bpf_trampoline *tr)
return err; return err;
} }
static enum bpf_tramp_prog_type bpf_attach_type_to_tramp(enum bpf_attach_type t) static enum bpf_tramp_prog_type bpf_attach_type_to_tramp(struct bpf_prog *prog)
{ {
switch (t) { switch (prog->expected_attach_type) {
case BPF_TRACE_FENTRY: case BPF_TRACE_FENTRY:
return BPF_TRAMP_FENTRY; return BPF_TRAMP_FENTRY;
case BPF_MODIFY_RETURN: case BPF_MODIFY_RETURN:
return BPF_TRAMP_MODIFY_RETURN; return BPF_TRAMP_MODIFY_RETURN;
case BPF_TRACE_FEXIT: case BPF_TRACE_FEXIT:
return BPF_TRAMP_FEXIT; return BPF_TRAMP_FEXIT;
case BPF_LSM_MAC:
if (!prog->aux->attach_func_proto->type)
/* The function returns void, we cannot modify its
* return value.
*/
return BPF_TRAMP_FEXIT;
else
return BPF_TRAMP_MODIFY_RETURN;
default: default:
return BPF_TRAMP_REPLACE; return BPF_TRAMP_REPLACE;
} }
...@@ -255,7 +264,7 @@ int bpf_trampoline_link_prog(struct bpf_prog *prog) ...@@ -255,7 +264,7 @@ int bpf_trampoline_link_prog(struct bpf_prog *prog)
int cnt; int cnt;
tr = prog->aux->trampoline; tr = prog->aux->trampoline;
kind = bpf_attach_type_to_tramp(prog->expected_attach_type); kind = bpf_attach_type_to_tramp(prog);
mutex_lock(&tr->mutex); mutex_lock(&tr->mutex);
if (tr->extension_prog) { if (tr->extension_prog) {
/* cannot attach fentry/fexit if extension prog is attached. /* cannot attach fentry/fexit if extension prog is attached.
...@@ -305,7 +314,7 @@ int bpf_trampoline_unlink_prog(struct bpf_prog *prog) ...@@ -305,7 +314,7 @@ int bpf_trampoline_unlink_prog(struct bpf_prog *prog)
int err; int err;
tr = prog->aux->trampoline; tr = prog->aux->trampoline;
kind = bpf_attach_type_to_tramp(prog->expected_attach_type); kind = bpf_attach_type_to_tramp(prog);
mutex_lock(&tr->mutex); mutex_lock(&tr->mutex);
if (kind == BPF_TRAMP_REPLACE) { if (kind == BPF_TRAMP_REPLACE) {
WARN_ON_ONCE(!tr->extension_prog); WARN_ON_ONCE(!tr->extension_prog);
......
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
#include <linux/perf_event.h> #include <linux/perf_event.h>
#include <linux/ctype.h> #include <linux/ctype.h>
#include <linux/error-injection.h> #include <linux/error-injection.h>
#include <linux/bpf_lsm.h>
#include "disasm.h" #include "disasm.h"
...@@ -6492,8 +6493,9 @@ static int check_return_code(struct bpf_verifier_env *env) ...@@ -6492,8 +6493,9 @@ static int check_return_code(struct bpf_verifier_env *env)
struct tnum range = tnum_range(0, 1); struct tnum range = tnum_range(0, 1);
int err; int err;
/* The struct_ops func-ptr's return type could be "void" */ /* LSM and struct_ops func-ptr's return type could be "void" */
if (env->prog->type == BPF_PROG_TYPE_STRUCT_OPS && if ((env->prog->type == BPF_PROG_TYPE_STRUCT_OPS ||
env->prog->type == BPF_PROG_TYPE_LSM) &&
!prog->aux->attach_func_proto->type) !prog->aux->attach_func_proto->type)
return 0; return 0;
...@@ -9923,7 +9925,9 @@ static int check_attach_btf_id(struct bpf_verifier_env *env) ...@@ -9923,7 +9925,9 @@ static int check_attach_btf_id(struct bpf_verifier_env *env)
if (prog->type == BPF_PROG_TYPE_STRUCT_OPS) if (prog->type == BPF_PROG_TYPE_STRUCT_OPS)
return check_struct_ops_btf_id(env); return check_struct_ops_btf_id(env);
if (prog->type != BPF_PROG_TYPE_TRACING && !prog_extension) if (prog->type != BPF_PROG_TYPE_TRACING &&
prog->type != BPF_PROG_TYPE_LSM &&
!prog_extension)
return 0; return 0;
if (!btf_id) { if (!btf_id) {
...@@ -10054,8 +10058,16 @@ static int check_attach_btf_id(struct bpf_verifier_env *env) ...@@ -10054,8 +10058,16 @@ static int check_attach_btf_id(struct bpf_verifier_env *env)
return -EINVAL; return -EINVAL;
/* fallthrough */ /* fallthrough */
case BPF_MODIFY_RETURN: case BPF_MODIFY_RETURN:
case BPF_LSM_MAC:
case BPF_TRACE_FENTRY: case BPF_TRACE_FENTRY:
case BPF_TRACE_FEXIT: case BPF_TRACE_FEXIT:
prog->aux->attach_func_name = tname;
if (prog->type == BPF_PROG_TYPE_LSM) {
ret = bpf_lsm_verify_prog(&env->log, prog);
if (ret < 0)
return ret;
}
if (!btf_type_is_func(t)) { if (!btf_type_is_func(t)) {
verbose(env, "attach_btf_id %u is not a function\n", verbose(env, "attach_btf_id %u is not a function\n",
btf_id); btf_id);
...@@ -10070,7 +10082,6 @@ static int check_attach_btf_id(struct bpf_verifier_env *env) ...@@ -10070,7 +10082,6 @@ static int check_attach_btf_id(struct bpf_verifier_env *env)
tr = bpf_trampoline_lookup(key); tr = bpf_trampoline_lookup(key);
if (!tr) if (!tr)
return -ENOMEM; return -ENOMEM;
prog->aux->attach_func_name = tname;
/* t is either vmlinux type or another program's type */ /* t is either vmlinux type or another program's type */
prog->aux->attach_func_proto = t; prog->aux->attach_func_proto = t;
mutex_lock(&tr->mutex); mutex_lock(&tr->mutex);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment