Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions security/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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/
Expand Down
84 changes: 84 additions & 0 deletions security/lsm_kfuncs.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// SPDX-License-Identifier: GPL-2.0
/*
* kfuncs exposing LSM interfaces to BPF programs.
*
* Copyright (C) 2026 Justin Suess
*/
#include <linux/bpf.h>
#include <linux/btf.h>
#include <linux/btf_ids.h>
#include <linux/init.h>
#include <linux/security.h>

__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);
28 changes: 28 additions & 0 deletions tools/testing/selftests/bpf/prog_tests/lsm_kfuncs.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// SPDX-License-Identifier: GPL-2.0
#include <test_progs.h>
#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);
}
34 changes: 34 additions & 0 deletions tools/testing/selftests/bpf/progs/lsm_kfuncs.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// SPDX-License-Identifier: GPL-2.0
#include "vmlinux.h"
#include <errno.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>

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;
}
26 changes: 26 additions & 0 deletions tools/testing/selftests/bpf/progs/lsm_kfuncs_fail.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: GPL-2.0
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#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);
}
Loading