diff --git a/security/Makefile b/security/Makefile index 4601230ba442a..dee8ff2185488 100644 --- a/security/Makefile +++ b/security/Makefile @@ -12,6 +12,7 @@ obj-$(CONFIG_MMU) += min_addr.o # Object file lists obj-$(CONFIG_SECURITY) += security.o lsm_notifier.o lsm_init.o +obj-$(CONFIG_BPF_SYSCALL) += lsm_kfuncs.o obj-$(CONFIG_SECURITYFS) += inode.o obj-$(CONFIG_SECURITY_SELINUX) += selinux/ obj-$(CONFIG_SECURITY_SMACK) += smack/ diff --git a/security/lsm_kfuncs.c b/security/lsm_kfuncs.c new file mode 100644 index 0000000000000..a324e7d978cad --- /dev/null +++ b/security/lsm_kfuncs.c @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * kfuncs exposing LSM interfaces to BPF programs. + * + * Copyright (C) 2026 Justin Suess + */ +#include +#include +#include +#include +#include + +__bpf_kfunc_start_defs(); + +/** + * bpf_security_locked_down - Call the security_locked_down() LSM hook + * @what: lockdown reason to query + * + * Return: 0 if @what is not locked down, -EPERM if it is, or -EINVAL if + * @what is outside (LOCKDOWN_NONE, LOCKDOWN_CONFIDENTIALITY_MAX). + */ +__bpf_kfunc int bpf_security_locked_down(enum lockdown_reason what) +{ + if (what <= LOCKDOWN_NONE || what >= LOCKDOWN_CONFIDENTIALITY_MAX) + return -EINVAL; + return security_locked_down(what); +} + +__bpf_kfunc_end_defs(); + +BTF_KFUNCS_START(lsm_kfunc_ids) +BTF_ID_FLAGS(func, bpf_security_locked_down) +BTF_KFUNCS_END(lsm_kfunc_ids) + +#ifdef CONFIG_BPF_LSM +BTF_ID_LIST_SINGLE(lsm_locked_down_hook_id, func, bpf_lsm_locked_down) +#endif + +static int lsm_kfunc_filter(const struct bpf_prog *prog, u32 kfunc_id) +{ + /* Filters run for every kfunc resolved through the hook. */ + if (!btf_id_set8_contains(&lsm_kfunc_ids, kfunc_id)) + return 0; + + /* + * Raw prog->type: keep out the rest of the shared tracing kfunc + * set (incl. perf/NMI) and extension programs. + */ + switch (prog->type) { + case BPF_PROG_TYPE_SYSCALL: + return 0; +#ifdef CONFIG_BPF_LSM + case BPF_PROG_TYPE_LSM: + /* + * A locked_down program calling this kfunc would recurse. + * Match on attach_btf_id: attach_func_name is not yet set + * when the filter runs from check_cfg. + */ + if (prog->aux->attach_btf_id == lsm_locked_down_hook_id[0]) + return -EACCES; + return 0; +#endif + default: + return -EACCES; + } +} + +static const struct btf_kfunc_id_set lsm_kfunc_set = { + .owner = THIS_MODULE, + .set = &lsm_kfunc_ids, + .filter = lsm_kfunc_filter, +}; + +static int __init lsm_kfuncs_init(void) +{ + int err; + + err = register_btf_kfunc_id_set(BPF_PROG_TYPE_LSM, &lsm_kfunc_set); + err = err ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_SYSCALL, &lsm_kfunc_set); + if (err) + pr_warn("lsm_kfuncs: kfunc registration failed: %d\n", err); + return err; +} +late_initcall(lsm_kfuncs_init); diff --git a/tools/testing/selftests/bpf/prog_tests/lsm_kfuncs.c b/tools/testing/selftests/bpf/prog_tests/lsm_kfuncs.c new file mode 100644 index 0000000000000..c836851d8397d --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/lsm_kfuncs.c @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include "lsm_kfuncs.skel.h" +#include "lsm_kfuncs_fail.skel.h" + +void test_lsm_kfuncs(void) +{ + LIBBPF_OPTS(bpf_test_run_opts, opts); + struct lsm_kfuncs *skel; + + RUN_TESTS(lsm_kfuncs_fail); + + skel = lsm_kfuncs__open_and_load(); + if (!ASSERT_OK_PTR(skel, "open_and_load")) + return; + if (!ASSERT_OK(lsm_kfuncs__attach(skel), "attach")) + goto out; + + if (!ASSERT_OK(bpf_prog_test_run_opts(bpf_program__fd(skel->progs.query), + &opts), "test_run")) + goto out; + ASSERT_EQ(skel->data->ret_clear, 0, "not locked down"); + ASSERT_EQ(skel->data->ret_denied, -EPERM, "locked down"); + ASSERT_EQ(skel->data->ret_invalid_low, -EINVAL, "LOCKDOWN_NONE invalid"); + ASSERT_EQ(skel->data->ret_invalid_high, -EINVAL, "CONFIDENTIALITY_MAX invalid"); +out: + lsm_kfuncs__destroy(skel); +} diff --git a/tools/testing/selftests/bpf/progs/lsm_kfuncs.c b/tools/testing/selftests/bpf/progs/lsm_kfuncs.c new file mode 100644 index 0000000000000..2637b9bc90256 --- /dev/null +++ b/tools/testing/selftests/bpf/progs/lsm_kfuncs.c @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "vmlinux.h" +#include +#include +#include + +char _license[] SEC("license") = "GPL"; + +extern int bpf_security_locked_down(enum lockdown_reason what) __ksym; + +/* Reason nothing in the test environment genuinely queries or locks. */ +#define DENY_REASON LOCKDOWN_HIBERNATION +#define ALLOW_REASON LOCKDOWN_KEXEC + +int ret_clear = 1; +int ret_denied = 1; +int ret_invalid_low = 1; +int ret_invalid_high = 1; + +SEC("lsm/locked_down") +int BPF_PROG(lockdown_hook, enum lockdown_reason what) +{ + return what == DENY_REASON ? -EPERM : 0; +} + +SEC("syscall") +int query(void *ctx) +{ + ret_clear = bpf_security_locked_down(ALLOW_REASON); + ret_denied = bpf_security_locked_down(DENY_REASON); + ret_invalid_low = bpf_security_locked_down(LOCKDOWN_NONE); + ret_invalid_high = bpf_security_locked_down(LOCKDOWN_CONFIDENTIALITY_MAX); + return 0; +} diff --git a/tools/testing/selftests/bpf/progs/lsm_kfuncs_fail.c b/tools/testing/selftests/bpf/progs/lsm_kfuncs_fail.c new file mode 100644 index 0000000000000..b9861d9c4f169 --- /dev/null +++ b/tools/testing/selftests/bpf/progs/lsm_kfuncs_fail.c @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "vmlinux.h" +#include +#include +#include "bpf_misc.h" + +char _license[] SEC("license") = "GPL"; + +extern int bpf_security_locked_down(enum lockdown_reason what) __ksym; + +/* Tracing programs must be rejected by the kfunc filter. */ +SEC("fentry/bpf_fentry_test1") +__failure __msg("calling kernel function bpf_security_locked_down is not allowed") +int BPF_PROG(tracing_caller, int a) +{ + bpf_security_locked_down(LOCKDOWN_KEXEC); + return 0; +} + +/* As must locked_down programs, which would recurse into the dispatch. */ +SEC("lsm/locked_down") +__failure __msg("calling kernel function bpf_security_locked_down is not allowed") +int BPF_PROG(recursive_caller, enum lockdown_reason what) +{ + return bpf_security_locked_down(what); +}