Linux Security Modules development
 help / color / mirror / Atom feed
* [PATCH bpf-next 00/13] Signed BPF + IPE Policies
@ 2026-05-22  2:32 KP Singh
  2026-05-22  2:32 ` [PATCH bpf-next 01/13] bpf: expose signature verdict to LSMs via bpf_prog_aux KP Singh
                   ` (12 more replies)
  0 siblings, 13 replies; 14+ messages in thread
From: KP Singh @ 2026-05-22  2:32 UTC (permalink / raw)
  To: linux-security-module, bpf; +Cc: ast, daniel, memxor, James.Bottomley, paul

This series continues the "Signed BPF programs" work and adds
the missing pieces needed for an LSM to do policy enforcement
and addresses the concerns raised by the developers of Hornet.

One signing scheme, please.

BPF does not need a second signing scheme. It needs a policy
framework that consumes the verdict the existing signing pipeline
produces. Two parallel signing stacks is harmful UX for Cilium,
bpftrace, systemd, distros, and everyone shipping signed lskels.
Hornet has been NACK'd repeatedly by the BPF maintainers [1][2]
on layering and TOCTOU grounds.

What this series adds

- prog->aux->sig (verdict + keyring) and prog->aux->is_kernel,
  populated by the syscall path before security_bpf_prog_load
  fires.
- bpf_loader_verify_metadata kfunc -- the metadata check is now
  kernel C code, not BPF bytecode. The verifier injects the
  calling prog->aux as an implicit argument via KF_IMPLICIT_ARGS.
- Loader-side prog BTF with BPF_PSEUDO_KFUNC_CALL_PROG_BTF so
  the kfunc CALL is reproducible across build hosts and resolved
  at load time.
- security_bpf_prog_load_post_integrity LSM hook, fired by the
  kfunc on a successful metadata check.
- IPE properties (bpf_signature, bpf_keyring, bpf_kernel) and
  two ops (BPF_PROG_LOAD, BPF_PROG_LOAD_POST_INTEGRITY).

This series address concerns raised by the Hornet developers:

* The metadata hash check should be in kernel C, not BPF
  bytecode -- Blaise Boscaccy [3]:

  The bpf_loader_verify_metadata kfunc moves the hash check from
  inline BPF instructions into kernel C code.

* LSMs cannot observe the verification result at hook time --
  Paul Moore [4]:

  prog->aux->sig.verdict and sig.keyring are populated before any
  LSM hook runs. Furthermore, security_bpf_prog_load_post_integrity
  hook fires after the in-kernel hash check for consumers that want
  to observe or gate the post-integrity transition.


[1] Alexei Starovoitov, NACK on Hornet (TOCTOU + layering),
    https://lore.kernel.org/all/CAADnVQJ1CRvTXBU771KaYzrx-vRaWF+k164DcFOqOsCxmuL+ig@mail.gmail.com/
[2] Daniel Borkmann, NACK on Hornet v3,
    https://lore.kernel.org/all/798dba24-b5a7-4584-a1f6-793883fe9b5e@iogearbox.net/
[3] Blaise Boscaccy, Hornet v6 (C-side hash verification rationale),
    https://lore.kernel.org/all/20260429191431.2345448-1-bboscaccy@linux.microsoft.com/
[4] Paul Moore, push for post-verifier observability,
    https://lore.kernel.org/all/CACYkzJ4+=3owK+ELD9Nw7Rrm-UajxXEw8kVtOTJJ+SNAXpsOpw@mail.gmail.com/


KP Singh (13):
  bpf: expose signature verdict to LSMs via bpf_prog_aux
  bpf: include prog BTF in the signed loader signature scope
  bpf, libbpf: load prog BTF in the skel_internal loader
  bpf: add bpf_loader_verify_metadata kfunc
  bpf: compute prog->digest at BPF_PROG_LOAD entry
  bpf: resolve loader-style kfunc CALLs against prog BTF
  libbpf: generate prog BTF for loader programs
  bpftool gen: embed loader prog BTF in the lskel header
  lsm: add bpf_prog_load_post_integrity hook
  bpf: invoke security_bpf_prog_load_post_integrity from the metadata
    kfunc
  ipe: add BPF program signature properties
  ipe: gate post-integrity BPF program loads
  selftests/bpf: add IPE BPF policy integration tests

 include/linux/bpf.h                           |  19 +++
 include/linux/bpf_verifier.h                  |   6 +
 include/linux/btf.h                           |   1 +
 include/linux/lsm_hook_defs.h                 |   1 +
 include/linux/security.h                      |   6 +
 include/uapi/linux/bpf.h                      |   5 +
 kernel/bpf/btf.c                              |   8 +
 kernel/bpf/check_btf.c                        |  18 +-
 kernel/bpf/helpers.c                          |  65 ++++++++
 kernel/bpf/syscall.c                          |  76 ++++++++-
 kernel/bpf/verifier.c                         |  58 ++++++-
 security/ipe/Kconfig                          |  14 ++
 security/ipe/audit.c                          |  13 ++
 security/ipe/eval.c                           |  57 +++++++
 security/ipe/eval.h                           |   5 +
 security/ipe/hooks.c                          |  42 +++++
 security/ipe/hooks.h                          |   9 +
 security/ipe/ipe.c                            |   4 +
 security/ipe/policy.h                         |  11 ++
 security/ipe/policy_parser.c                  |  20 +++
 security/security.c                           |  17 ++
 tools/bpf/bpftool/gen.c                       |  21 +++
 tools/bpf/bpftool/sign.c                      |  17 +-
 tools/include/uapi/linux/bpf.h                |   5 +
 tools/lib/bpf/bpf_gen_internal.h              |   2 +
 tools/lib/bpf/gen_loader.c                    | 127 +++++++++++---
 tools/lib/bpf/libbpf.h                        |   4 +-
 tools/lib/bpf/skel_internal.h                 |  67 +++++---
 .../selftests/bpf/test_signed_bpf_ipe.sh      | 156 ++++++++++++++++++
 tools/testing/selftests/bpf/vmtest.sh         |   4 +-
 30 files changed, 775 insertions(+), 83 deletions(-)
 create mode 100755 tools/testing/selftests/bpf/test_signed_bpf_ipe.sh

-- 
2.53.0


^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH bpf-next 01/13] bpf: expose signature verdict to LSMs via bpf_prog_aux
  2026-05-22  2:32 [PATCH bpf-next 00/13] Signed BPF + IPE Policies KP Singh
@ 2026-05-22  2:32 ` KP Singh
  2026-05-22  2:32 ` [PATCH bpf-next 02/13] bpf: include prog BTF in the signed loader signature scope KP Singh
                   ` (11 subsequent siblings)
  12 siblings, 0 replies; 14+ messages in thread
From: KP Singh @ 2026-05-22  2:32 UTC (permalink / raw)
  To: linux-security-module, bpf
  Cc: ast, daniel, memxor, James.Bottomley, paul, KP Singh

BPF_PROG_LOAD verifies the loader signature but does not record the
outcome on the prog. LSMs and audit can read attr->signature and
attr->keyring_id to infer "was this signed, against which keyring",
but there is no canonical state for "loader signature + map content
verified". Only the in-kernel kfunc can confirm the latter.

Add prog->aux->sig (verdict + keyring) and prog->aux->is_kernel,
populated by bpf_prog_load before the LSM hook. Failed verifications
reject the load before the hook runs, so it observes only UNSIGNED
or OK. The bpf_loader_verify_metadata kfunc promotes to
METADATA_VERIFIED later.

Signed-off-by: KP Singh <kpsingh@kernel.org>
---
 include/linux/bpf.h  | 19 +++++++++++++++++++
 kernel/bpf/syscall.c | 20 ++++++++++++++++++++
 2 files changed, 39 insertions(+)

diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index 11bec73db199..14f65259f414 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -1656,6 +1656,20 @@ struct bpf_stream_stage {
 	int len;
 };
 
+enum bpf_sig_verdict {
+	BPF_SIG_UNSIGNED = 0,
+	BPF_SIG_OK,                  /* loader signature verified */
+	BPF_SIG_METADATA_VERIFIED,   /* loader signature + map content verified */
+};
+
+enum bpf_sig_keyring {
+	BPF_SIG_KEYRING_NONE = 0,
+	BPF_SIG_KEYRING_BUILTIN,
+	BPF_SIG_KEYRING_SECONDARY,
+	BPF_SIG_KEYRING_PLATFORM,
+	BPF_SIG_KEYRING_USER,
+};
+
 struct bpf_prog_aux {
 	atomic64_t refcnt;
 	u32 used_map_cnt;
@@ -1698,6 +1712,11 @@ struct bpf_prog_aux {
 	bool changes_pkt_data;
 	bool might_sleep;
 	bool kprobe_write_ctx;
+	struct {
+		u8 verdict;
+		u8 keyring;
+	} sig;
+	bool is_kernel;
 	u64 prog_array_member_cnt; /* counts how many times as member of prog_array */
 	struct mutex ext_mutex; /* mutex for is_extended and prog_array_member_cnt */
 	struct bpf_arena *arena;
diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
index 630d530782fe..51fe8d77bb39 100644
--- a/kernel/bpf/syscall.c
+++ b/kernel/bpf/syscall.c
@@ -2798,6 +2798,20 @@ static bool is_perfmon_prog_type(enum bpf_prog_type prog_type)
 	}
 }
 
+static enum bpf_sig_keyring bpf_classify_keyring(s32 keyring_id)
+{
+	switch (keyring_id) {
+	case 0:
+		return BPF_SIG_KEYRING_BUILTIN;
+	case (s32)(unsigned long)VERIFY_USE_SECONDARY_KEYRING:
+		return BPF_SIG_KEYRING_SECONDARY;
+	case (s32)(unsigned long)VERIFY_USE_PLATFORM_KEYRING:
+		return BPF_SIG_KEYRING_PLATFORM;
+	default:
+		return BPF_SIG_KEYRING_USER;
+	}
+}
+
 static int bpf_prog_verify_signature(struct bpf_prog *prog, union bpf_attr *attr,
 				     bool is_kernel)
 {
@@ -3027,7 +3041,13 @@ static int bpf_prog_load(union bpf_attr *attr, bpfptr_t uattr, u32 uattr_size)
 		err = bpf_prog_verify_signature(prog, attr, uattr.is_kernel);
 		if (err)
 			goto free_prog;
+		prog->aux->sig.verdict  = BPF_SIG_OK;
+		prog->aux->sig.keyring = bpf_classify_keyring(attr->keyring_id);
+	} else {
+		prog->aux->sig.verdict  = BPF_SIG_UNSIGNED;
+		prog->aux->sig.keyring = BPF_SIG_KEYRING_NONE;
 	}
+	prog->aux->is_kernel = uattr.is_kernel;
 
 	prog->orig_prog = NULL;
 	prog->jited = 0;
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH bpf-next 02/13] bpf: include prog BTF in the signed loader signature scope
  2026-05-22  2:32 [PATCH bpf-next 00/13] Signed BPF + IPE Policies KP Singh
  2026-05-22  2:32 ` [PATCH bpf-next 01/13] bpf: expose signature verdict to LSMs via bpf_prog_aux KP Singh
@ 2026-05-22  2:32 ` KP Singh
  2026-05-22  2:32 ` [PATCH bpf-next 03/13] bpf, libbpf: load prog BTF in the skel_internal loader KP Singh
                   ` (10 subsequent siblings)
  12 siblings, 0 replies; 14+ messages in thread
From: KP Singh @ 2026-05-22  2:32 UTC (permalink / raw)
  To: linux-security-module, bpf
  Cc: ast, daniel, memxor, James.Bottomley, paul, KP Singh

bpf_prog_verify_signature hashes only prog->insnsi, so FUNC names
in prog BTF are not covered by the signature. Since we need to
support kfuncs for loader programs with prog BTF information, the
signature needs to cover BTF so that it can be trusted.

When attr->prog_btf_fd is set, build the verify dynptr over
insns || raw_btf so the signature covers both. Fall back to insns-only
otherwise so existing signed lskels keep loading. Hoist the
prog_btf_fd resolution into bpf_prog_load so the BTF is available
before signature verification.

Signed-off-by: KP Singh <kpsingh@kernel.org>
---
 include/linux/btf.h    |  1 +
 kernel/bpf/btf.c       |  8 +++++++
 kernel/bpf/check_btf.c | 18 +++-------------
 kernel/bpf/syscall.c   | 49 +++++++++++++++++++++++++++++++++++++-----
 4 files changed, 56 insertions(+), 20 deletions(-)

diff --git a/include/linux/btf.h b/include/linux/btf.h
index 48108471c5b1..ab1fe7c8df20 100644
--- a/include/linux/btf.h
+++ b/include/linux/btf.h
@@ -216,6 +216,7 @@ int btf_type_snprintf_show(const struct btf *btf, u32 type_id, void *obj,
 int btf_get_fd_by_id(u32 id);
 u32 btf_obj_id(const struct btf *btf);
 bool btf_is_kernel(const struct btf *btf);
+const void *btf_get_raw_data(const struct btf *btf, u32 *data_size);
 bool btf_is_module(const struct btf *btf);
 bool btf_is_vmlinux(const struct btf *btf);
 struct module *btf_try_get_module(const struct btf *btf);
diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
index a62d78581207..6f1d9b3ba6a3 100644
--- a/kernel/bpf/btf.c
+++ b/kernel/bpf/btf.c
@@ -8322,11 +8322,19 @@ u32 btf_obj_id(const struct btf *btf)
 	return READ_ONCE(btf->id);
 }
 
+const void *btf_get_raw_data(const struct btf *btf, u32 *data_size)
+{
+	if (data_size)
+		*data_size = btf->data_size;
+	return btf->data;
+}
+
 bool btf_is_kernel(const struct btf *btf)
 {
 	return btf->kernel_btf;
 }
 
+
 bool btf_is_module(const struct btf *btf)
 {
 	return btf->kernel_btf && strcmp(btf->name, "vmlinux") != 0;
diff --git a/kernel/bpf/check_btf.c b/kernel/bpf/check_btf.c
index 93bebe6fe12e..c52cc859abac 100644
--- a/kernel/bpf/check_btf.c
+++ b/kernel/bpf/check_btf.c
@@ -411,28 +411,16 @@ int bpf_check_btf_info_early(struct bpf_verifier_env *env,
 			     const union bpf_attr *attr,
 			     bpfptr_t uattr)
 {
-	struct btf *btf;
-	int err;
-
 	if (!attr->func_info_cnt && !attr->line_info_cnt) {
 		if (check_abnormal_return(env))
 			return -EINVAL;
 		return 0;
 	}
 
-	btf = btf_get_by_fd(attr->prog_btf_fd);
-	if (IS_ERR(btf))
-		return PTR_ERR(btf);
-	if (btf_is_kernel(btf)) {
-		btf_put(btf);
-		return -EACCES;
-	}
-	env->prog->aux->btf = btf;
+	if (env->prog->aux->btf)
+		return check_btf_func_early(env, attr, uattr);
 
-	err = check_btf_func_early(env, attr, uattr);
-	if (err)
-		return err;
-	return 0;
+	return -EINVAL;
 }
 
 int bpf_check_btf_info(struct bpf_verifier_env *env,
diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
index 51fe8d77bb39..6d1db5eaad3c 100644
--- a/kernel/bpf/syscall.c
+++ b/kernel/bpf/syscall.c
@@ -2816,9 +2816,11 @@ static int bpf_prog_verify_signature(struct bpf_prog *prog, union bpf_attr *attr
 				     bool is_kernel)
 {
 	bpfptr_t usig = make_bpfptr(attr->signature, is_kernel);
-	struct bpf_dynptr_kern sig_ptr, insns_ptr;
+	struct bpf_dynptr_kern sig_ptr, data_ptr;
 	struct bpf_key *key = NULL;
-	void *sig;
+	u32 insns_sz, btf_sz = 0;
+	const void *btf_data = NULL;
+	void *sig, *data = NULL;
 	int err = 0;
 
 	/*
@@ -2842,14 +2844,34 @@ static int bpf_prog_verify_signature(struct bpf_prog *prog, union bpf_attr *attr
 		return PTR_ERR(sig);
 	}
 
+	insns_sz = prog->len * sizeof(struct bpf_insn);
+
+	if (prog->aux->btf)
+		btf_data = btf_get_raw_data(prog->aux->btf, &btf_sz);
+
+	if (bpf_dynptr_check_size(insns_sz + btf_sz)) {
+		err = -E2BIG;
+		goto out;
+	}
+	data = kvmalloc(insns_sz + btf_sz, GFP_KERNEL);
+	if (!data) {
+		err = -ENOMEM;
+		goto out;
+	}
+	memcpy(data, prog->insnsi, insns_sz);
+	if (btf_sz)
+		memcpy(data + insns_sz, btf_data, btf_sz);
+
+	bpf_dynptr_init(&data_ptr, data, BPF_DYNPTR_TYPE_LOCAL, 0,
+			insns_sz + btf_sz);
 	bpf_dynptr_init(&sig_ptr, sig, BPF_DYNPTR_TYPE_LOCAL, 0,
 			attr->signature_size);
-	bpf_dynptr_init(&insns_ptr, prog->insnsi, BPF_DYNPTR_TYPE_LOCAL, 0,
-			prog->len * sizeof(struct bpf_insn));
 
-	err = bpf_verify_pkcs7_signature((struct bpf_dynptr *)&insns_ptr,
+	err = bpf_verify_pkcs7_signature((struct bpf_dynptr *)&data_ptr,
 					 (struct bpf_dynptr *)&sig_ptr, key);
 
+out:
+	kvfree(data);
 	bpf_key_put(key);
 	kvfree(sig);
 	return err;
@@ -3037,6 +3059,21 @@ static int bpf_prog_load(union bpf_attr *attr, bpfptr_t uattr, u32 uattr_size)
 	/* eBPF programs must be GPL compatible to use GPL-ed functions */
 	prog->gpl_compatible = license_is_gpl_compatible(license) ? 1 : 0;
 
+	if (attr->prog_btf_fd) {
+		struct btf *btf = btf_get_by_fd(attr->prog_btf_fd);
+
+		if (IS_ERR(btf)) {
+			err = PTR_ERR(btf);
+			goto free_prog;
+		}
+		if (btf_is_kernel(btf)) {
+			btf_put(btf);
+			err = -EACCES;
+			goto free_prog;
+		}
+		prog->aux->btf = btf;
+	}
+
 	if (attr->signature) {
 		err = bpf_prog_verify_signature(prog, attr, uattr.is_kernel);
 		if (err)
@@ -3148,6 +3185,8 @@ static int bpf_prog_load(union bpf_attr *attr, bpfptr_t uattr, u32 uattr_size)
 	free_uid(prog->aux->user);
 	if (prog->aux->attach_btf)
 		btf_put(prog->aux->attach_btf);
+	if (prog->aux->btf)
+		btf_put(prog->aux->btf);
 	bpf_prog_free(prog);
 put_token:
 	bpf_token_put(token);
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH bpf-next 03/13] bpf, libbpf: load prog BTF in the skel_internal loader
  2026-05-22  2:32 [PATCH bpf-next 00/13] Signed BPF + IPE Policies KP Singh
  2026-05-22  2:32 ` [PATCH bpf-next 01/13] bpf: expose signature verdict to LSMs via bpf_prog_aux KP Singh
  2026-05-22  2:32 ` [PATCH bpf-next 02/13] bpf: include prog BTF in the signed loader signature scope KP Singh
@ 2026-05-22  2:32 ` KP Singh
  2026-05-22  2:32 ` [PATCH bpf-next 04/13] bpf: add bpf_loader_verify_metadata kfunc KP Singh
                   ` (9 subsequent siblings)
  12 siblings, 0 replies; 14+ messages in thread
From: KP Singh @ 2026-05-22  2:32 UTC (permalink / raw)
  To: linux-security-module, bpf
  Cc: ast, daniel, memxor, James.Bottomley, paul, KP Singh

bpf_load_and_run loads only the loader insns and the metadata map.
To match the kernel's extended signature scope (insns || btf), the
loader needs to BPF_BTF_LOAD the prog BTF before BPF_PROG_LOAD so
attr->prog_btf_fd can be filled in.

Add btf and btf_sz to bpf_load_and_run_opts; when set, do the
BPF_BTF_LOAD before BPF_PROG_LOAD and pass the resulting fd as
attr->prog_btf_fd.

Signed-off-by: KP Singh <kpsingh@kernel.org>
---
 tools/lib/bpf/skel_internal.h | 44 ++++++++++++++++++++++++++++++++++-
 1 file changed, 43 insertions(+), 1 deletion(-)

diff --git a/tools/lib/bpf/skel_internal.h b/tools/lib/bpf/skel_internal.h
index 6a8f5c7a02eb..0b6b1ecedd45 100644
--- a/tools/lib/bpf/skel_internal.h
+++ b/tools/lib/bpf/skel_internal.h
@@ -11,6 +11,8 @@
 #include <linux/bpf.h>
 #else
 #include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
 #include <sys/syscall.h>
 #include <sys/mman.h>
 #include <linux/keyctl.h>
@@ -66,8 +68,10 @@ struct bpf_load_and_run_opts {
 	struct bpf_loader_ctx *ctx;
 	const void *data;
 	const void *insns;
+	const void *btf;
 	__u32 data_sz;
 	__u32 insns_sz;
+	__u32 btf_sz;
 	const char *errstr;
 	void *signature;
 	__u32 signature_sz;
@@ -88,6 +92,22 @@ static inline int skel_sys_bpf(enum bpf_cmd cmd, union bpf_attr *attr,
 #endif
 }
 
+#ifndef __KERNEL__
+static inline int skel_sys_btf_load(union bpf_attr *attr, unsigned int size)
+{
+	int fd;
+
+	fd = skel_sys_bpf(BPF_BTF_LOAD, attr, size);
+	if (fd >= 0 && fd < 3) {
+		int new_fd = fcntl(fd, F_DUPFD_CLOEXEC, 3);
+
+		close(fd);
+		fd = new_fd;
+	}
+	return fd;
+}
+#endif
+
 #ifdef __KERNEL__
 static inline int close(int fd)
 {
@@ -353,8 +373,9 @@ static inline int skel_map_freeze(int fd)
 static inline int bpf_load_and_run(struct bpf_load_and_run_opts *opts)
 {
 	const size_t prog_load_attr_sz = offsetofend(union bpf_attr, keyring_id);
+	const size_t btf_load_attr_sz = offsetofend(union bpf_attr, btf_token_fd);
 	const size_t test_run_attr_sz = offsetofend(union bpf_attr, test);
-	int map_fd = -1, prog_fd = -1, key = 0, err;
+	int map_fd = -1, prog_fd = -1, btf_fd = -1, key = 0, err;
 	union bpf_attr attr;
 
 	err = map_fd = skel_map_create(BPF_MAP_TYPE_ARRAY, "__loader.map", 4, opts->data_sz, 1,
@@ -387,11 +408,30 @@ static inline int bpf_load_and_run(struct bpf_load_and_run_opts *opts)
 	}
 #endif
 
+#ifndef __KERNEL__
+	if (opts->btf && opts->btf_sz) {
+		memset(&attr, 0, btf_load_attr_sz);
+		attr.btf = (long) opts->btf;
+		attr.btf_size = opts->btf_sz;
+		attr.btf_log_level = opts->ctx->log_level;
+		attr.btf_log_size = opts->ctx->log_size;
+		attr.btf_log_buf = opts->ctx->log_buf;
+		err = btf_fd = skel_sys_btf_load(&attr, btf_load_attr_sz);
+		if (btf_fd < 0) {
+			opts->errstr = "failed to load loader BTF";
+			set_err;
+			goto out;
+		}
+	}
+#endif
+
 	memset(&attr, 0, prog_load_attr_sz);
 	attr.prog_type = BPF_PROG_TYPE_SYSCALL;
 	attr.insns = (long) opts->insns;
 	attr.insn_cnt = opts->insns_sz / sizeof(struct bpf_insn);
 	attr.license = (long) "Dual BSD/GPL";
+	if (btf_fd >= 0)
+		attr.prog_btf_fd = btf_fd;
 #ifndef __KERNEL__
 	attr.signature = (long) opts->signature;
 	attr.signature_size = opts->signature_sz;
@@ -437,6 +477,8 @@ static inline int bpf_load_and_run(struct bpf_load_and_run_opts *opts)
 		close(map_fd);
 	if (prog_fd >= 0)
 		close(prog_fd);
+	if (btf_fd >= 0)
+		close(btf_fd);
 	return err;
 }
 
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH bpf-next 04/13] bpf: add bpf_loader_verify_metadata kfunc
  2026-05-22  2:32 [PATCH bpf-next 00/13] Signed BPF + IPE Policies KP Singh
                   ` (2 preceding siblings ...)
  2026-05-22  2:32 ` [PATCH bpf-next 03/13] bpf, libbpf: load prog BTF in the skel_internal loader KP Singh
@ 2026-05-22  2:32 ` KP Singh
  2026-05-22  2:32 ` [PATCH bpf-next 05/13] bpf: compute prog->digest at BPF_PROG_LOAD entry KP Singh
                   ` (8 subsequent siblings)
  12 siblings, 0 replies; 14+ messages in thread
From: KP Singh @ 2026-05-22  2:32 UTC (permalink / raw)
  To: linux-security-module, bpf
  Cc: ast, daniel, memxor, James.Bottomley, paul, KP Singh

A signed loader reaches sig.verdict = BPF_SIG_OK after PKCS#7
verification, but the metadata map content the loader writes has not
been checked against its expected SHA256. The map carries that SHA256
in the signed instruction stream, spilled to the loader's stack at
runtime as four ld_imm64 immediates.

Assert the map is exclusive, compare its frozen content hash against
the spilled SHA256, and promote sig.verdict to
BPF_SIG_METADATA_VERIFIED. The verifier injects the calling
prog->aux as an implicit argument via KF_IMPLICIT_ARGS so the kfunc
can read the prog digest and signature verdict directly.

Drop skel_obj_get_info_by_fd from bpf_load_and_run since the kfunc
computes the map hash itself via map_get_hash; the prior
BPF_OBJ_GET_INFO_BY_FD call to populate map->sha is not
needed.

Signed-off-by: KP Singh <kpsingh@kernel.org>
---
 kernel/bpf/helpers.c          | 57 +++++++++++++++++++++++++++++++++++
 tools/bpf/bpftool/sign.c      | 17 ++++++++++-
 tools/lib/bpf/skel_internal.h | 25 ---------------
 3 files changed, 73 insertions(+), 26 deletions(-)

diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
index b5314c9fed3c..9afa71fbcac3 100644
--- a/kernel/bpf/helpers.c
+++ b/kernel/bpf/helpers.c
@@ -4257,6 +4257,53 @@ __bpf_kfunc int bpf_verify_pkcs7_signature(struct bpf_dynptr *data_p,
 	return -EOPNOTSUPP;
 #endif /* CONFIG_SYSTEM_DATA_VERIFICATION */
 }
+
+/**
+ * bpf_loader_verify_metadata - perform the signed loader's metadata-map check
+ * in a single kernel-side step.
+ *
+ * Asserts the metadata map is exclusive, compares its frozen content hash
+ * against the expected SHA256 carried in the loader's signed instruction
+ * stream, and promotes sig.verdict from BPF_SIG_OK to
+ * BPF_SIG_METADATA_VERIFIED.
+ *
+ * @map:       metadata map bound to this loader via excl_prog_hash at sign time
+ * @hash:      pointer to the expected SHA256, spilled to the loader's BPF stack
+ *             from signed ld_imm64 immediates
+ * @hash__sz:  byte length of @hash (must equal SHA256_DIGEST_SIZE)
+ * @aux__ign:  verifier-supplied prog aux; BPF programs do not set it
+ *
+ * Return: 0 on success, -EPERM if not called from a signed loader,
+ * -EINVAL if the map is not exclusive or the hash buffer is the wrong size,
+ * -EBADMSG if the hash does not match.
+ */
+__bpf_kfunc int bpf_loader_verify_metadata(struct bpf_map *map,
+					   const u64 *hash, u32 hash__sz,
+					   struct bpf_prog_aux *aux__ign)
+{
+	u8 sha[SHA256_DIGEST_SIZE];
+	int err;
+
+	if (!aux__ign || aux__ign->sig.verdict != BPF_SIG_OK)
+		return -EPERM;
+	if (!map->excl_prog_sha || hash__sz != SHA256_DIGEST_SIZE)
+		return -EINVAL;
+	if (memcmp(map->excl_prog_sha, aux__ign->prog->digest, SHA256_DIGEST_SIZE))
+		return -EPERM;
+	if (!READ_ONCE(map->frozen))
+		return -EPERM;
+	if (!map->ops->map_get_hash)
+		return -EINVAL;
+	err = map->ops->map_get_hash(map, SHA256_DIGEST_SIZE, sha);
+	if (err)
+		return err;
+	if (memcmp(sha, hash, SHA256_DIGEST_SIZE))
+		return -EBADMSG;
+
+	aux__ign->sig.verdict = BPF_SIG_METADATA_VERIFIED;
+	return 0;
+}
+
 #endif /* CONFIG_KEYS */
 
 typedef int (*bpf_task_work_callback_t)(struct bpf_map *map, void *key, void *value);
@@ -4764,6 +4811,15 @@ static const struct btf_kfunc_id_set generic_kfunc_set = {
 	.set   = &generic_btf_ids,
 };
 
+BTF_KFUNCS_START(syscall_btf_ids)
+BTF_ID_FLAGS(func, bpf_loader_verify_metadata, KF_SLEEPABLE | KF_IMPLICIT_ARGS)
+BTF_KFUNCS_END(syscall_btf_ids)
+
+static const struct btf_kfunc_id_set syscall_kfunc_set = {
+	.owner = THIS_MODULE,
+	.set   = &syscall_btf_ids,
+};
+
 
 BTF_ID_LIST(generic_dtor_ids)
 BTF_ID(struct, task_struct)
@@ -4893,6 +4949,7 @@ static int __init kfunc_init(void)
 	ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_STRUCT_OPS, &generic_kfunc_set);
 	ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_SYSCALL, &generic_kfunc_set);
 	ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_CGROUP_SKB, &generic_kfunc_set);
+	ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_SYSCALL, &syscall_kfunc_set);
 	ret = ret ?: register_btf_id_dtor_kfuncs(generic_dtors,
 						  ARRAY_SIZE(generic_dtors),
 						  THIS_MODULE);
diff --git a/tools/bpf/bpftool/sign.c b/tools/bpf/bpftool/sign.c
index f9b742f4bb10..13f256546cf0 100644
--- a/tools/bpf/bpftool/sign.c
+++ b/tools/bpf/bpftool/sign.c
@@ -134,10 +134,24 @@ int bpftool_prog_sign(struct bpf_load_and_run_opts *opts)
 	EVP_PKEY *private_key = NULL;
 	CMS_ContentInfo *cms = NULL;
 	long actual_sig_len = 0;
+	void *signed_buf = NULL;
+	size_t signed_sz;
 	X509 *x509 = NULL;
 	int err = 0;
 
-	bd_in = BIO_new_mem_buf(opts->insns, opts->insns_sz);
+	signed_sz = opts->insns_sz + opts->btf_sz;
+	if (opts->btf_sz) {
+		signed_buf = malloc(signed_sz);
+		if (!signed_buf) {
+			err = -ENOMEM;
+			goto cleanup;
+		}
+		memcpy(signed_buf, opts->insns, opts->insns_sz);
+		memcpy(signed_buf + opts->insns_sz, opts->btf, opts->btf_sz);
+		bd_in = BIO_new_mem_buf(signed_buf, signed_sz);
+	} else {
+		bd_in = BIO_new_mem_buf(opts->insns, opts->insns_sz);
+	}
 	if (!bd_in) {
 		err = -ENOMEM;
 		goto cleanup;
@@ -212,6 +226,7 @@ int bpftool_prog_sign(struct bpf_load_and_run_opts *opts)
 	X509_free(x509);
 	EVP_PKEY_free(private_key);
 	BIO_free(bd_in);
+	free(signed_buf);
 	DISPLAY_OSSL_ERR(err < 0);
 	return err;
 }
diff --git a/tools/lib/bpf/skel_internal.h b/tools/lib/bpf/skel_internal.h
index 0b6b1ecedd45..d194f4e23d12 100644
--- a/tools/lib/bpf/skel_internal.h
+++ b/tools/lib/bpf/skel_internal.h
@@ -335,25 +335,6 @@ static inline int skel_link_create(int prog_fd, int target_fd,
 	return skel_sys_bpf(BPF_LINK_CREATE, &attr, attr_sz);
 }
 
-static inline int skel_obj_get_info_by_fd(int fd)
-{
-	const size_t attr_sz = offsetofend(union bpf_attr, info);
-	__u8 sha[SHA256_DIGEST_LENGTH];
-	struct bpf_map_info info;
-	__u32 info_len = sizeof(info);
-	union bpf_attr attr;
-
-	memset(&info, 0, sizeof(info));
-	info.hash = (long) &sha;
-	info.hash_size = SHA256_DIGEST_LENGTH;
-
-	memset(&attr, 0, attr_sz);
-	attr.info.bpf_fd = fd;
-	attr.info.info = (long) &info;
-	attr.info.info_len = info_len;
-	return skel_sys_bpf(BPF_OBJ_GET_INFO_BY_FD, &attr, attr_sz);
-}
-
 static inline int skel_map_freeze(int fd)
 {
 	const size_t attr_sz = offsetofend(union bpf_attr, map_fd);
@@ -400,12 +381,6 @@ static inline int bpf_load_and_run(struct bpf_load_and_run_opts *opts)
 		set_err;
 		goto out;
 	}
-	err = skel_obj_get_info_by_fd(map_fd);
-	if (err < 0) {
-		opts->errstr = "failed to fetch obj info";
-		set_err;
-		goto out;
-	}
 #endif
 
 #ifndef __KERNEL__
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH bpf-next 05/13] bpf: compute prog->digest at BPF_PROG_LOAD entry
  2026-05-22  2:32 [PATCH bpf-next 00/13] Signed BPF + IPE Policies KP Singh
                   ` (3 preceding siblings ...)
  2026-05-22  2:32 ` [PATCH bpf-next 04/13] bpf: add bpf_loader_verify_metadata kfunc KP Singh
@ 2026-05-22  2:32 ` KP Singh
  2026-05-22  2:32 ` [PATCH bpf-next 06/13] bpf: resolve loader-style kfunc CALLs against prog BTF KP Singh
                   ` (7 subsequent siblings)
  12 siblings, 0 replies; 14+ messages in thread
From: KP Singh @ 2026-05-22  2:32 UTC (permalink / raw)
  To: linux-security-module, bpf
  Cc: ast, daniel, memxor, James.Bottomley, paul, KP Singh

add_subprog_and_kfunc relocates kfunc CALLs by patching insn->imm
and src_reg, and bpf_prog_calc_tag has no rule to mask kfunc CALL
fields. The tag has to be computed over the unmodified user-supplied
insns to match the excl_prog_hash userspace signed, so move the call
to bpf_prog_load right after signature verification.

Signed-off-by: KP Singh <kpsingh@kernel.org>
---
 kernel/bpf/syscall.c  | 7 +++++++
 kernel/bpf/verifier.c | 4 ----
 2 files changed, 7 insertions(+), 4 deletions(-)

diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
index 6d1db5eaad3c..39ebd825c136 100644
--- a/kernel/bpf/syscall.c
+++ b/kernel/bpf/syscall.c
@@ -3086,6 +3086,13 @@ static int bpf_prog_load(union bpf_attr *attr, bpfptr_t uattr, u32 uattr_size)
 	}
 	prog->aux->is_kernel = uattr.is_kernel;
 
+	/* Hash insns now, before any verifier-side rewrite, so prog->digest
+	 * matches the excl_prog_hash userspace computed.
+	 */
+	err = bpf_prog_calc_tag(prog);
+	if (err)
+		goto free_prog;
+
 	prog->orig_prog = NULL;
 	prog->jited = 0;
 
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 7fb88e1cd7c4..f0e45cfa5b34 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -18434,10 +18434,6 @@ static int check_and_resolve_insns(struct bpf_verifier_env *env)
 	int insn_cnt = env->prog->len;
 	int i, err;
 
-	err = bpf_prog_calc_tag(env->prog);
-	if (err)
-		return err;
-
 	for (i = 0; i < insn_cnt; i++, insn++) {
 		if (insn->dst_reg >= MAX_BPF_REG) {
 			verbose(env, "R%d is invalid\n", insn->dst_reg);
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH bpf-next 06/13] bpf: resolve loader-style kfunc CALLs against prog BTF
  2026-05-22  2:32 [PATCH bpf-next 00/13] Signed BPF + IPE Policies KP Singh
                   ` (4 preceding siblings ...)
  2026-05-22  2:32 ` [PATCH bpf-next 05/13] bpf: compute prog->digest at BPF_PROG_LOAD entry KP Singh
@ 2026-05-22  2:32 ` KP Singh
  2026-05-22  2:32 ` [PATCH bpf-next 07/13] libbpf: generate prog BTF for loader programs KP Singh
                   ` (6 subsequent siblings)
  12 siblings, 0 replies; 14+ messages in thread
From: KP Singh @ 2026-05-22  2:32 UTC (permalink / raw)
  To: linux-security-module, bpf
  Cc: ast, daniel, memxor, James.Bottomley, paul, KP Singh

gen_loader-emitted signed loaders cannot bake vmlinux BTF ids into
kfunc CALL imm at sign time. Add a new pseudo
BPF_PSEUDO_KFUNC_CALL_PROG_BTF that gen_loader emits in src_reg, with
imm holding the FUNC type id in the loader's own prog BTF.

In add_subprog_and_kfunc, look the FUNC up by name in vmlinux BTF,
patch imm with the resolved id, and rewrite src_reg back to
BPF_PSEUDO_KFUNC_CALL so downstream passes see a normal kfunc CALL.
Leave standard src_reg calls unchanged.

Signed-off-by: KP Singh <kpsingh@kernel.org>
---
 include/linux/bpf_verifier.h   |  6 ++++
 include/uapi/linux/bpf.h       |  5 ++++
 kernel/bpf/verifier.c          | 54 ++++++++++++++++++++++++++++++++--
 tools/include/uapi/linux/bpf.h |  5 ++++
 4 files changed, 67 insertions(+), 3 deletions(-)

diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
index 185b2aa43a42..396b85830996 100644
--- a/include/linux/bpf_verifier.h
+++ b/include/linux/bpf_verifier.h
@@ -959,6 +959,12 @@ static inline bool bpf_pseudo_kfunc_call(const struct bpf_insn *insn)
 	       insn->src_reg == BPF_PSEUDO_KFUNC_CALL;
 }
 
+static inline bool bpf_pseudo_kfunc_call_prog_btf(const struct bpf_insn *insn)
+{
+	return insn->code == (BPF_JMP | BPF_CALL) &&
+	       insn->src_reg == BPF_PSEUDO_KFUNC_CALL_PROG_BTF;
+}
+
 __printf(2, 0) void bpf_verifier_vlog(struct bpf_verifier_log *log,
 				      const char *fmt, va_list args);
 __printf(2, 3) void bpf_verifier_log_write(struct bpf_verifier_env *env,
diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h
index 552bc5d9afbd..06056e714e8e 100644
--- a/include/uapi/linux/bpf.h
+++ b/include/uapi/linux/bpf.h
@@ -1382,6 +1382,11 @@ enum {
  * bpf_call->imm == btf_id of a BTF_KIND_FUNC in the running kernel
  */
 #define BPF_PSEUDO_KFUNC_CALL	2
+/* when bpf_call->src_reg == BPF_PSEUDO_KFUNC_CALL_PROG_BTF,
+ * bpf_call->imm == btf_id of a BTF_KIND_FUNC in the program's
+ * prog BTF. The verifier resolves it to a vmlinux btf_id by name.
+ */
+#define BPF_PSEUDO_KFUNC_CALL_PROG_BTF	3
 
 enum bpf_addr_space_cast {
 	BPF_ADDR_SPACE_CAST = 1,
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index f0e45cfa5b34..1b5d06b9d74a 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -3088,6 +3088,47 @@ bool bpf_prog_has_kfunc_call(const struct bpf_prog *prog)
 	return !!prog->aux->kfunc_tab;
 }
 
+/*
+ * Resolve a gen_loader-emitted kfunc CALL by FUNC name in vmlinux BTF,
+ * then rewrite src_reg back to BPF_PSEUDO_KFUNC_CALL. Caller must have
+ * already filtered for BPF_PSEUDO_KFUNC_CALL_PROG_BTF.
+ */
+static int resolve_loader_kfunc(struct bpf_verifier_env *env,
+				struct bpf_insn *insn, int insn_idx)
+{
+	struct btf *prog_btf = env->prog->aux->btf;
+	const struct btf_type *t;
+	const char *name;
+	s32 vmlinux_id;
+
+	if (!prog_btf || !btf_vmlinux || insn->off) {
+		verbose(env, "kfunc call insn %d: PROG_BTF resolution requires prog BTF and insn->off == 0\n",
+			insn_idx);
+		return -EINVAL;
+	}
+	t = btf_type_by_id(prog_btf, insn->imm);
+	if (!t || !btf_type_is_func(t)) {
+		verbose(env, "kfunc call insn %d: imm %d is not a FUNC in prog BTF\n",
+			insn_idx, insn->imm);
+		return -EINVAL;
+	}
+	name = btf_name_by_offset(prog_btf, t->name_off);
+	if (!name || !name[0]) {
+		verbose(env, "kfunc call insn %d: prog-BTF FUNC has no name\n",
+			insn_idx);
+		return -EINVAL;
+	}
+	vmlinux_id = btf_find_by_name_kind(btf_vmlinux, name, BTF_KIND_FUNC);
+	if (vmlinux_id < 0) {
+		verbose(env, "kfunc call insn %d: %s not found in vmlinux BTF\n",
+			insn_idx, name);
+		return vmlinux_id;
+	}
+	insn->imm = vmlinux_id;
+	insn->src_reg = BPF_PSEUDO_KFUNC_CALL;
+	return 0;
+}
+
 static int add_subprog_and_kfunc(struct bpf_verifier_env *env)
 {
 	struct bpf_subprog_info *subprog = env->subprog_info;
@@ -3101,7 +3142,8 @@ static int add_subprog_and_kfunc(struct bpf_verifier_env *env)
 
 	for (i = 0; i < insn_cnt; i++, insn++) {
 		if (!bpf_pseudo_func(insn) && !bpf_pseudo_call(insn) &&
-		    !bpf_pseudo_kfunc_call(insn))
+		    !bpf_pseudo_kfunc_call(insn) &&
+		    !bpf_pseudo_kfunc_call_prog_btf(insn))
 			continue;
 
 		if (!env->bpf_capable) {
@@ -3109,10 +3151,16 @@ static int add_subprog_and_kfunc(struct bpf_verifier_env *env)
 			return -EPERM;
 		}
 
-		if (bpf_pseudo_func(insn) || bpf_pseudo_call(insn))
+		if (bpf_pseudo_func(insn) || bpf_pseudo_call(insn)) {
 			ret = add_subprog(env, i + insn->imm + 1);
-		else
+		} else {
+			if (bpf_pseudo_kfunc_call_prog_btf(insn)) {
+				ret = resolve_loader_kfunc(env, insn, i);
+				if (ret < 0)
+					return ret;
+			}
 			ret = bpf_add_kfunc_call(env, insn->imm, insn->off);
+		}
 
 		if (ret < 0)
 			return ret;
diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h
index 677be9a47347..d4f7f3e0aaa3 100644
--- a/tools/include/uapi/linux/bpf.h
+++ b/tools/include/uapi/linux/bpf.h
@@ -1382,6 +1382,11 @@ enum {
  * bpf_call->imm == btf_id of a BTF_KIND_FUNC in the running kernel
  */
 #define BPF_PSEUDO_KFUNC_CALL	2
+/* when bpf_call->src_reg == BPF_PSEUDO_KFUNC_CALL_PROG_BTF,
+ * bpf_call->imm == btf_id of a BTF_KIND_FUNC in the program's
+ * prog BTF. The verifier resolves it to a vmlinux btf_id by name.
+ */
+#define BPF_PSEUDO_KFUNC_CALL_PROG_BTF	3
 
 enum bpf_addr_space_cast {
 	BPF_ADDR_SPACE_CAST = 1,
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH bpf-next 07/13] libbpf: generate prog BTF for loader programs
  2026-05-22  2:32 [PATCH bpf-next 00/13] Signed BPF + IPE Policies KP Singh
                   ` (5 preceding siblings ...)
  2026-05-22  2:32 ` [PATCH bpf-next 06/13] bpf: resolve loader-style kfunc CALLs against prog BTF KP Singh
@ 2026-05-22  2:32 ` KP Singh
  2026-05-22  2:32 ` [PATCH bpf-next 08/13] bpftool gen: embed loader prog BTF in the lskel header KP Singh
                   ` (5 subsequent siblings)
  12 siblings, 0 replies; 14+ messages in thread
From: KP Singh @ 2026-05-22  2:32 UTC (permalink / raw)
  To: linux-security-module, bpf
  Cc: ast, daniel, memxor, James.Bottomley, paul, KP Singh

For signed loaders, build a minimal prog BTF containing one FUNC entry
for bpf_loader_verify_metadata with a FUNC_PROTO of primitives (int,
void *) so the bytes are reproducible across build hosts.

In emit_verify_metadata, spill four signed ld_imm64 immediates into
a 32-byte stack buffer and invoke the kfunc once with (map, &buf, 32),
replacing ~24 inline dword comparisons. Expose the serialized BTF on
gen_loader_opts from bpf_gen__finish so bpftool gen can embed it in
the lskel.

Signed-off-by: KP Singh <kpsingh@kernel.org>
---
 tools/lib/bpf/bpf_gen_internal.h |   2 +
 tools/lib/bpf/gen_loader.c       | 127 ++++++++++++++++++++++++-------
 tools/lib/bpf/libbpf.h           |   4 +-
 3 files changed, 105 insertions(+), 28 deletions(-)

diff --git a/tools/lib/bpf/bpf_gen_internal.h b/tools/lib/bpf/bpf_gen_internal.h
index 49af4260b8e6..e4aed9e99b56 100644
--- a/tools/lib/bpf/bpf_gen_internal.h
+++ b/tools/lib/bpf/bpf_gen_internal.h
@@ -52,6 +52,8 @@ struct bpf_gen {
 	int fd_array;
 	int nr_fd_array;
 	int hash_insn_offset[SHA256_DWORD_SIZE];
+	struct btf *loader_btf;
+	int loader_btf_func_id;
 };
 
 void bpf_gen__init(struct bpf_gen *gen, int log_level, int nr_progs, int nr_maps);
diff --git a/tools/lib/bpf/gen_loader.c b/tools/lib/bpf/gen_loader.c
index fee35c26deb8..48ac25c058e3 100644
--- a/tools/lib/bpf/gen_loader.c
+++ b/tools/lib/bpf/gen_loader.c
@@ -35,6 +35,7 @@ struct loader_stack {
 	__u32 btf_fd;
 	__u32 inner_map_fd;
 	__u32 prog_fd[MAX_USED_PROGS];
+	__u64 metadata_hash[SHA256_DWORD_SIZE];
 };
 
 #define stack_off(field) \
@@ -109,7 +110,7 @@ static void emit2(struct bpf_gen *gen, struct bpf_insn insn1, struct bpf_insn in
 
 static int add_data(struct bpf_gen *gen, const void *data, __u32 size);
 static void emit_sys_close_blob(struct bpf_gen *gen, int blob_off);
-static void emit_signature_match(struct bpf_gen *gen);
+static void emit_verify_metadata(struct bpf_gen *gen);
 
 void bpf_gen__init(struct bpf_gen *gen, int log_level, int nr_progs, int nr_maps)
 {
@@ -152,8 +153,68 @@ void bpf_gen__init(struct bpf_gen *gen, int log_level, int nr_progs, int nr_maps
 	/* R7 contains the error code from sys_bpf. Copy it into R0 and exit. */
 	emit(gen, BPF_MOV64_REG(BPF_REG_0, BPF_REG_7));
 	emit(gen, BPF_EXIT_INSN());
-	if (OPTS_GET(gen->opts, gen_hash, false))
-		emit_signature_match(gen);
+	if (OPTS_GET(gen->opts, gen_hash, false)) {
+		int int_id, u32_id, ptr_id, proto_id, err;
+
+		/* Prog BTF built from primitives so the bytes are reproducible
+		 * across build hosts.
+		 */
+		gen->loader_btf = btf__new_empty();
+		if (libbpf_get_error(gen->loader_btf)) {
+			gen->error = -ENOMEM;
+			gen->loader_btf = NULL;
+			return;
+		}
+		if (gen->swapped_endian) {
+			enum btf_endianness target =
+				btf__endianness(gen->loader_btf) == BTF_LITTLE_ENDIAN
+				? BTF_BIG_ENDIAN : BTF_LITTLE_ENDIAN;
+
+			err = btf__set_endianness(gen->loader_btf, target);
+			if (err) {
+				gen->error = err;
+				return;
+			}
+		}
+		int_id = btf__add_int(gen->loader_btf, "int", 4, BTF_INT_SIGNED);
+		if (int_id < 0) {
+			gen->error = int_id;
+			return;
+		}
+		u32_id = btf__add_int(gen->loader_btf, "u32", 4, 0);
+		if (u32_id < 0) {
+			gen->error = u32_id;
+			return;
+		}
+		ptr_id = btf__add_ptr(gen->loader_btf, 0);
+		if (ptr_id < 0) {
+			gen->error = ptr_id;
+			return;
+		}
+		proto_id = btf__add_func_proto(gen->loader_btf, int_id);
+		if (proto_id < 0) {
+			gen->error = proto_id;
+			return;
+		}
+		err = btf__add_func_param(gen->loader_btf, "map", ptr_id);
+		if (!err)
+			err = btf__add_func_param(gen->loader_btf, "hash", ptr_id);
+		if (!err)
+			err = btf__add_func_param(gen->loader_btf, "hash__sz", u32_id);
+		if (err) {
+			gen->error = err;
+			return;
+		}
+		gen->loader_btf_func_id = btf__add_func(gen->loader_btf,
+							"bpf_loader_verify_metadata",
+							BTF_FUNC_GLOBAL, proto_id);
+		if (gen->loader_btf_func_id < 0) {
+			gen->error = gen->loader_btf_func_id;
+			gen->loader_btf_func_id = 0;
+			return;
+		}
+		emit_verify_metadata(gen);
+	}
 }
 
 static int add_data(struct bpf_gen *gen, const void *data, __u32 size)
@@ -398,7 +459,7 @@ int bpf_gen__finish(struct bpf_gen *gen, int nr_progs, int nr_maps)
 			      blob_fd_array_off(gen, i));
 	emit(gen, BPF_MOV64_IMM(BPF_REG_0, 0));
 	emit(gen, BPF_EXIT_INSN());
-	if (OPTS_GET(gen->opts, gen_hash, false))
+	if (!gen->error && OPTS_GET(gen->opts, gen_hash, false))
 		compute_sha_update_offsets(gen);
 
 	pr_debug("gen: finish %s\n", errstr(gen->error));
@@ -410,6 +471,19 @@ int bpf_gen__finish(struct bpf_gen *gen, int nr_progs, int nr_maps)
 		opts->data = gen->data_start;
 		opts->data_sz = gen->data_cur - gen->data_start;
 
+		if (gen->loader_btf) {
+			__u32 btf_sz = 0;
+			const void *btf_data;
+
+			btf_data = btf__raw_data(gen->loader_btf, &btf_sz);
+			if (!btf_data) {
+				gen->error = -ENOMEM;
+				return gen->error;
+			}
+			OPTS_SET(opts, btf, btf_data);
+			OPTS_SET(opts, btf_sz, btf_sz);
+		}
+
 		/* use target endianness for embedded loader */
 		if (gen->swapped_endian) {
 			struct bpf_insn *insn = (struct bpf_insn *)opts->insns;
@@ -426,6 +500,7 @@ void bpf_gen__free(struct bpf_gen *gen)
 {
 	if (!gen)
 		return;
+	btf__free(gen->loader_btf);
 	free(gen->data_start);
 	free(gen->insn_start);
 	free(gen);
@@ -580,39 +655,37 @@ void bpf_gen__map_create(struct bpf_gen *gen,
 		emit_sys_close_stack(gen, stack_off(inner_map_fd));
 }
 
-static void emit_signature_match(struct bpf_gen *gen)
+static void emit_verify_metadata(struct bpf_gen *gen)
 {
 	__s64 off;
 	int i;
 
+	/* arg1: metadata struct bpf_map */
+	emit2(gen, BPF_LD_IMM64_RAW_FULL(BPF_REG_1, BPF_PSEUDO_MAP_IDX,
+					 0, 0, 0, 0));
+
+	/* arg2: hash buffer on our BPF stack, populated from ld_imm64
+	 * immediates patched in by compute_sha_update_offsets() before signing.
+	 */
+	emit(gen, BPF_MOV64_REG(BPF_REG_2, BPF_REG_10));
+	emit(gen, BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, stack_off(metadata_hash)));
 	for (i = 0; i < SHA256_DWORD_SIZE; i++) {
-		emit2(gen, BPF_LD_IMM64_RAW_FULL(BPF_REG_1, BPF_PSEUDO_MAP_IDX,
-						 0, 0, 0, 0));
-		emit(gen, BPF_LDX_MEM(BPF_DW, BPF_REG_2, BPF_REG_1, i * sizeof(__u64)));
 		gen->hash_insn_offset[i] = gen->insn_cur - gen->insn_start;
 		emit2(gen, BPF_LD_IMM64_RAW_FULL(BPF_REG_3, 0, 0, 0, 0, 0));
-
-		off = -(gen->insn_cur - gen->insn_start - gen->cleanup_label) / 8 - 2;
-		if (is_simm16(off)) {
-			emit(gen, BPF_MOV64_IMM(BPF_REG_7, -EINVAL));
-			emit(gen, BPF_JMP_REG(BPF_JNE, BPF_REG_2, BPF_REG_3, off));
-		} else {
-			gen->error = -ERANGE;
-		}
+		emit(gen, BPF_STX_MEM(BPF_DW, BPF_REG_2, BPF_REG_3,
+				      i * sizeof(__u64)));
 	}
 
-	/* Reject if the metadata map is not exclusive. Without exclusivity
-	 * the cached map->sha[] verified above can be stale: another BPF
-	 * program with map access could have mutated the contents between
-	 * BPF_OBJ_GET_INFO_BY_FD and loader execution.
-	 */
-	emit2(gen, BPF_LD_IMM64_RAW_FULL(BPF_REG_1, BPF_PSEUDO_MAP_IDX,
-					 0, 0, 0, 0));
-	emit(gen, BPF_LDX_MEM(BPF_DW, BPF_REG_2, BPF_REG_1, SHA256_DWORD_SIZE * sizeof(__u64)));
-	off = -(gen->insn_cur - gen->insn_start - gen->cleanup_label) / 8 - 2;
+	/* arg3: hash length */
+	emit(gen, BPF_MOV64_IMM(BPF_REG_3, SHA256_DWORD_SIZE * sizeof(__u64)));
+
+	emit(gen, BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0,
+			       BPF_PSEUDO_KFUNC_CALL_PROG_BTF, 0,
+			       gen->loader_btf_func_id));
+	emit(gen, BPF_MOV64_REG(BPF_REG_7, BPF_REG_0));
+	off = -(gen->insn_cur - gen->insn_start - gen->cleanup_label) / 8 - 1;
 	if (is_simm16(off)) {
-		emit(gen, BPF_MOV64_IMM(BPF_REG_7, -EINVAL));
-		emit(gen, BPF_JMP_IMM(BPF_JEQ, BPF_REG_2, 0, off));
+		emit(gen, BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, off));
 	} else {
 		gen->error = -ERANGE;
 	}
diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h
index bba4e8464396..25906fb695f9 100644
--- a/tools/lib/bpf/libbpf.h
+++ b/tools/lib/bpf/libbpf.h
@@ -1899,9 +1899,11 @@ struct gen_loader_opts {
 	__u32 data_sz;
 	__u32 insns_sz;
 	bool gen_hash;
+	const void *btf;
+	__u32 btf_sz;
 };
 
-#define gen_loader_opts__last_field gen_hash
+#define gen_loader_opts__last_field btf_sz
 LIBBPF_API int bpf_object__gen_loader(struct bpf_object *obj,
 				      struct gen_loader_opts *opts);
 
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH bpf-next 08/13] bpftool gen: embed loader prog BTF in the lskel header
  2026-05-22  2:32 [PATCH bpf-next 00/13] Signed BPF + IPE Policies KP Singh
                   ` (6 preceding siblings ...)
  2026-05-22  2:32 ` [PATCH bpf-next 07/13] libbpf: generate prog BTF for loader programs KP Singh
@ 2026-05-22  2:32 ` KP Singh
  2026-05-22  2:32 ` [PATCH bpf-next 09/13] lsm: add bpf_prog_load_post_integrity hook KP Singh
                   ` (4 subsequent siblings)
  12 siblings, 0 replies; 14+ messages in thread
From: KP Singh @ 2026-05-22  2:32 UTC (permalink / raw)
  To: linux-security-module, bpf
  Cc: ast, daniel, memxor, James.Bottomley, paul, KP Singh

Extend the signed lskel codegen to include the loader prog BTF in
the generated header and the PKCS#7 signature scope. The loader
needs this BTF at load time so the kernel can resolve kfunc calls
by name.

Lskels generated without -S are unchanged; the new codegen is gated
on opts.btf_sz.

Signed-off-by: KP Singh <kpsingh@kernel.org>
---
 tools/bpf/bpftool/gen.c | 21 +++++++++++++++++++++
 1 file changed, 21 insertions(+)

diff --git a/tools/bpf/bpftool/gen.c b/tools/bpf/bpftool/gen.c
index 2f9e10752e28..95dba5c53ef4 100644
--- a/tools/bpf/bpftool/gen.c
+++ b/tools/bpf/bpftool/gen.c
@@ -793,6 +793,8 @@ static int gen_trace(struct bpf_object *obj, const char *obj_name, const char *h
 	if (sign_progs) {
 		sopts.insns = opts.insns;
 		sopts.insns_sz = opts.insns_sz;
+		sopts.btf = opts.btf;
+		sopts.btf_sz = opts.btf_sz;
 		sopts.excl_prog_hash = prog_sha;
 		sopts.excl_prog_hash_sz = sizeof(prog_sha);
 		sopts.signature = sig_buf;
@@ -822,6 +824,17 @@ static int gen_trace(struct bpf_object *obj, const char *obj_name, const char *h
 		\n\
 		\";\n");
 
+		if (opts.btf_sz) {
+			codegen("\
+			\n\
+				static const char opts_btf[] __attribute__((__aligned__(8))) = \"\\\n\
+			");
+			print_hex(opts.btf, opts.btf_sz);
+			codegen("\
+			\n\
+			\";\n");
+		}
+
 		codegen("\
 		\n\
 			opts.signature = (void *)opts_sig;			\n\
@@ -830,6 +843,14 @@ static int gen_trace(struct bpf_object *obj, const char *obj_name, const char *h
 			opts.excl_prog_hash_sz = sizeof(opts_excl_hash) - 1;	\n\
 			opts.keyring_id = skel->keyring_id;			\n\
 		");
+
+		if (opts.btf_sz) {
+			codegen("\
+			\n\
+				opts.btf = (void *)opts_btf;			    \n\
+				opts.btf_sz = sizeof(opts_btf) - 1;		    \n\
+			");
+		}
 	}
 
 	codegen("\
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH bpf-next 09/13] lsm: add bpf_prog_load_post_integrity hook
  2026-05-22  2:32 [PATCH bpf-next 00/13] Signed BPF + IPE Policies KP Singh
                   ` (7 preceding siblings ...)
  2026-05-22  2:32 ` [PATCH bpf-next 08/13] bpftool gen: embed loader prog BTF in the lskel header KP Singh
@ 2026-05-22  2:32 ` KP Singh
  2026-05-22  2:32 ` [PATCH bpf-next 10/13] bpf: invoke security_bpf_prog_load_post_integrity from the metadata kfunc KP Singh
                   ` (3 subsequent siblings)
  12 siblings, 0 replies; 14+ messages in thread
From: KP Singh @ 2026-05-22  2:32 UTC (permalink / raw)
  To: linux-security-module, bpf
  Cc: ast, daniel, memxor, James.Bottomley, paul, KP Singh

Add a companion to security_bpf_prog_load. The existing hook fires
at PROG_LOAD entry where the verdict is at most BPF_SIG_OK; the new
hook fires from bpf_loader_verify_metadata after the in-kernel
metadata check, just before sig.verdict is promoted to
BPF_SIG_METADATA_VERIFIED. Policy LSMs that want to gate on
metadata verification (not just signature presence) register here.

Signed-off-by: KP Singh <kpsingh@kernel.org>
---
 include/linux/lsm_hook_defs.h |  1 +
 include/linux/security.h      |  6 ++++++
 security/security.c           | 17 +++++++++++++++++
 3 files changed, 24 insertions(+)

diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
index 2b8dfb35caed..c0e7899756d4 100644
--- a/include/linux/lsm_hook_defs.h
+++ b/include/linux/lsm_hook_defs.h
@@ -446,6 +446,7 @@ LSM_HOOK(int, 0, bpf_map_create, struct bpf_map *map, union bpf_attr *attr,
 LSM_HOOK(void, LSM_RET_VOID, bpf_map_free, struct bpf_map *map)
 LSM_HOOK(int, 0, bpf_prog_load, struct bpf_prog *prog, union bpf_attr *attr,
 	 struct bpf_token *token, bool kernel)
+LSM_HOOK(int, 0, bpf_prog_load_post_integrity, struct bpf_prog *prog)
 LSM_HOOK(void, LSM_RET_VOID, bpf_prog_free, struct bpf_prog *prog)
 LSM_HOOK(int, 0, bpf_token_create, struct bpf_token *token, union bpf_attr *attr,
 	 const struct path *path)
diff --git a/include/linux/security.h b/include/linux/security.h
index 41d7367cf403..3a8f2c50f7be 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -2305,6 +2305,7 @@ extern int security_bpf_map_create(struct bpf_map *map, union bpf_attr *attr,
 extern void security_bpf_map_free(struct bpf_map *map);
 extern int security_bpf_prog_load(struct bpf_prog *prog, union bpf_attr *attr,
 				  struct bpf_token *token, bool kernel);
+extern int security_bpf_prog_load_post_integrity(struct bpf_prog *prog);
 extern void security_bpf_prog_free(struct bpf_prog *prog);
 extern int security_bpf_token_create(struct bpf_token *token, union bpf_attr *attr,
 				     const struct path *path);
@@ -2343,6 +2344,11 @@ static inline int security_bpf_prog_load(struct bpf_prog *prog, union bpf_attr *
 	return 0;
 }
 
+static inline int security_bpf_prog_load_post_integrity(struct bpf_prog *prog)
+{
+	return 0;
+}
+
 static inline void security_bpf_prog_free(struct bpf_prog *prog)
 { }
 
diff --git a/security/security.c b/security/security.c
index 4e999f023651..05153e8496c9 100644
--- a/security/security.c
+++ b/security/security.c
@@ -5383,6 +5383,23 @@ int security_bpf_prog_load(struct bpf_prog *prog, union bpf_attr *attr,
 	return rc;
 }
 
+/**
+ * security_bpf_prog_load_post_integrity() - Notify LSMs that a signed loader
+ * has just verified its metadata map.
+ * @prog: the loader BPF program whose metadata check passed.
+ *
+ * Invoked by bpf_loader_verify_metadata() after the kernel-side hash check
+ * succeeds, before prog->aux->sig_verdict is promoted to
+ * BPF_SIG_METADATA_VERIFIED. A non-zero return aborts the kfunc and leaves
+ * the verdict at BPF_SIG_OK.
+ *
+ * Return: 0 on success, negative errno to deny.
+ */
+int security_bpf_prog_load_post_integrity(struct bpf_prog *prog)
+{
+	return call_int_hook(bpf_prog_load_post_integrity, prog);
+}
+
 /**
  * security_bpf_token_create() - Check if creating of BPF token is allowed
  * @token: BPF token object
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH bpf-next 10/13] bpf: invoke security_bpf_prog_load_post_integrity from the metadata kfunc
  2026-05-22  2:32 [PATCH bpf-next 00/13] Signed BPF + IPE Policies KP Singh
                   ` (8 preceding siblings ...)
  2026-05-22  2:32 ` [PATCH bpf-next 09/13] lsm: add bpf_prog_load_post_integrity hook KP Singh
@ 2026-05-22  2:32 ` KP Singh
  2026-05-22  2:32 ` [PATCH bpf-next 11/13] ipe: add BPF program signature properties KP Singh
                   ` (2 subsequent siblings)
  12 siblings, 0 replies; 14+ messages in thread
From: KP Singh @ 2026-05-22  2:32 UTC (permalink / raw)
  To: linux-security-module, bpf
  Cc: ast, daniel, memxor, James.Bottomley, paul, KP Singh

Call security_bpf_prog_load_post_integrity from
bpf_loader_verify_metadata just before promoting
prog->aux->sig.verdict from BPF_SIG_OK to BPF_SIG_METADATA_VERIFIED.
This lets policy LSMs deny the metadata-verified transition.

A non-zero return aborts the kfunc and leaves the verdict at
BPF_SIG_OK; observers that key off METADATA_VERIFIED never see a
verdict the LSM denied.

Signed-off-by: KP Singh <kpsingh@kernel.org>
---
 kernel/bpf/helpers.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
index 9afa71fbcac3..52e71fb6e200 100644
--- a/kernel/bpf/helpers.c
+++ b/kernel/bpf/helpers.c
@@ -4300,6 +4300,14 @@ __bpf_kfunc int bpf_loader_verify_metadata(struct bpf_map *map,
 	if (memcmp(sha, hash, SHA256_DIGEST_SIZE))
 		return -EBADMSG;
 
+	/* Metadata integrity is decided by the checks above; the LSM hook
+	 * is an observer of that verdict and may apply policy (e.g. deny),
+	 * but cannot vouch for integrity it did not verify itself.
+	 */
+	err = security_bpf_prog_load_post_integrity(aux__ign->prog);
+	if (err)
+		return err;
+
 	aux__ign->sig.verdict = BPF_SIG_METADATA_VERIFIED;
 	return 0;
 }
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH bpf-next 11/13] ipe: add BPF program signature properties
  2026-05-22  2:32 [PATCH bpf-next 00/13] Signed BPF + IPE Policies KP Singh
                   ` (9 preceding siblings ...)
  2026-05-22  2:32 ` [PATCH bpf-next 10/13] bpf: invoke security_bpf_prog_load_post_integrity from the metadata kfunc KP Singh
@ 2026-05-22  2:32 ` KP Singh
  2026-05-22  2:32 ` [PATCH bpf-next 12/13] ipe: gate post-integrity BPF program loads KP Singh
  2026-05-22  2:32 ` [PATCH bpf-next 13/13] selftests/bpf: add IPE BPF policy integration tests KP Singh
  12 siblings, 0 replies; 14+ messages in thread
From: KP Singh @ 2026-05-22  2:32 UTC (permalink / raw)
  To: linux-security-module, bpf
  Cc: ast, daniel, memxor, James.Bottomley, paul, KP Singh

Wire IPE to the BPF signing verdict stored in prog->aux->sig before
security_bpf_prog_load fires. Add three properties on a new IPE op
BPF_PROG_LOAD:

  bpf_signature= UNSIGNED | OK | METADATA_VERIFIED
  bpf_keyring=   BUILTIN | SECONDARY | PLATFORM | USER
  bpf_kernel=    TRUE | FALSE

Example policy:

  op=BPF_PROG_LOAD bpf_signature=UNSIGNED action=DENY
  op=BPF_PROG_LOAD bpf_signature=OK action=ALLOW

Gated by CONFIG_IPE_PROP_BPF_SIGNATURE (depends on BPF_SYSCALL).

Signed-off-by: KP Singh <kpsingh@kernel.org>
---
 security/ipe/Kconfig         | 14 +++++++++
 security/ipe/audit.c         | 11 +++++++
 security/ipe/eval.c          | 57 ++++++++++++++++++++++++++++++++++++
 security/ipe/eval.h          |  5 ++++
 security/ipe/hooks.c         | 36 +++++++++++++++++++++++
 security/ipe/hooks.h         |  7 +++++
 security/ipe/ipe.c           |  3 ++
 security/ipe/policy.h        | 10 +++++++
 security/ipe/policy_parser.c | 19 ++++++++++++
 9 files changed, 162 insertions(+)

diff --git a/security/ipe/Kconfig b/security/ipe/Kconfig
index a110a6cd848b..204517c60a34 100644
--- a/security/ipe/Kconfig
+++ b/security/ipe/Kconfig
@@ -13,6 +13,7 @@ menuconfig SECURITY_IPE
 	select IPE_PROP_DM_VERITY_SIGNATURE if DM_VERITY && DM_VERITY_VERIFY_ROOTHASH_SIG
 	select IPE_PROP_FS_VERITY if FS_VERITY
 	select IPE_PROP_FS_VERITY_BUILTIN_SIG if FS_VERITY && FS_VERITY_BUILTIN_SIGNATURES
+	select IPE_PROP_BPF_SIGNATURE if BPF_SYSCALL
 	help
 	  This option enables the Integrity Policy Enforcement LSM
 	  allowing users to define a policy to enforce a trust-based access
@@ -95,6 +96,19 @@ config IPE_PROP_FS_VERITY_BUILTIN_SIG
 
 	  if unsure, answer Y.
 
+config IPE_PROP_BPF_SIGNATURE
+	bool "Enable support for BPF program signature verdicts"
+	depends on BPF_SYSCALL
+	help
+	  This option enables the 'bpf_signature', 'bpf_keyring' and
+	  'bpf_kernel' properties within IPE policies. The properties
+	  These allow
+	  policy rules to gate BPF program loads based on the loader's
+	  signature verdict, the keyring used for verification, and
+	  whether the load originated in kernel mode.
+
+	  if unsure, answer Y.
+
 endmenu
 
 config SECURITY_IPE_KUNIT_TEST
diff --git a/security/ipe/audit.c b/security/ipe/audit.c
index 93fb59fbddd6..fec98c396d49 100644
--- a/security/ipe/audit.c
+++ b/security/ipe/audit.c
@@ -41,6 +41,7 @@ static const char *const audit_op_names[__IPE_OP_MAX + 1] = {
 	"KEXEC_INITRAMFS",
 	"POLICY",
 	"X509_CERT",
+	"BPF_PROG_LOAD",
 	"UNKNOWN",
 };
 
@@ -51,6 +52,7 @@ static const char *const audit_hook_names[__IPE_HOOK_MAX] = {
 	"MPROTECT",
 	"KERNEL_READ",
 	"KERNEL_LOAD",
+	"BPF_PROG_LOAD",
 };
 
 static const char *const audit_prop_names[__IPE_PROP_MAX] = {
@@ -62,6 +64,15 @@ static const char *const audit_prop_names[__IPE_PROP_MAX] = {
 	"fsverity_digest=",
 	"fsverity_signature=FALSE",
 	"fsverity_signature=TRUE",
+	"bpf_signature=UNSIGNED",
+	"bpf_signature=OK",
+	"bpf_signature=METADATA_VERIFIED",
+	"bpf_keyring=BUILTIN",
+	"bpf_keyring=SECONDARY",
+	"bpf_keyring=PLATFORM",
+	"bpf_keyring=USER",
+	"bpf_kernel=FALSE",
+	"bpf_kernel=TRUE",
 };
 
 /**
diff --git a/security/ipe/eval.c b/security/ipe/eval.c
index 21439c5be336..e4b4e3723fc8 100644
--- a/security/ipe/eval.c
+++ b/security/ipe/eval.c
@@ -11,6 +11,7 @@
 #include <linux/rcupdate.h>
 #include <linux/moduleparam.h>
 #include <linux/fsverity.h>
+#include <linux/bpf.h>
 
 #include "ipe.h"
 #include "eval.h"
@@ -265,6 +266,44 @@ static bool evaluate_fsv_sig_true(const struct ipe_eval_ctx *const ctx)
 }
 #endif /* CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG */
 
+#ifdef CONFIG_IPE_PROP_BPF_SIGNATURE
+static bool evaluate_bpf_verdict(const struct ipe_eval_ctx *const ctx,
+				 enum bpf_sig_verdict expected)
+{
+	return ctx->bpf_verdict == expected;
+}
+
+static bool evaluate_bpf_keyring(const struct ipe_eval_ctx *const ctx,
+				 enum bpf_sig_keyring expected)
+{
+	return ctx->bpf_keyring == expected;
+}
+
+static bool evaluate_bpf_kernel(const struct ipe_eval_ctx *const ctx,
+				bool expected)
+{
+	return ctx->bpf_kernel == expected;
+}
+#else
+static bool evaluate_bpf_verdict(const struct ipe_eval_ctx *const ctx,
+				 enum bpf_sig_verdict expected)
+{
+	return false;
+}
+
+static bool evaluate_bpf_keyring(const struct ipe_eval_ctx *const ctx,
+				 enum bpf_sig_keyring expected)
+{
+	return false;
+}
+
+static bool evaluate_bpf_kernel(const struct ipe_eval_ctx *const ctx,
+				bool expected)
+{
+	return false;
+}
+#endif /* CONFIG_IPE_PROP_BPF_SIGNATURE */
+
 /**
  * evaluate_property() - Analyze @ctx against a rule property.
  * @ctx: Supplies a pointer to the context to be evaluated.
@@ -297,6 +336,24 @@ static bool evaluate_property(const struct ipe_eval_ctx *const ctx,
 		return evaluate_fsv_sig_false(ctx);
 	case IPE_PROP_FSV_SIG_TRUE:
 		return evaluate_fsv_sig_true(ctx);
+	case IPE_PROP_BPF_SIG_UNSIGNED:
+		return evaluate_bpf_verdict(ctx, BPF_SIG_UNSIGNED);
+	case IPE_PROP_BPF_SIG_OK:
+		return evaluate_bpf_verdict(ctx, BPF_SIG_OK);
+	case IPE_PROP_BPF_SIG_METADATA_VERIFIED:
+		return evaluate_bpf_verdict(ctx, BPF_SIG_METADATA_VERIFIED);
+	case IPE_PROP_BPF_KEYRING_BUILTIN:
+		return evaluate_bpf_keyring(ctx, BPF_SIG_KEYRING_BUILTIN);
+	case IPE_PROP_BPF_KEYRING_SECONDARY:
+		return evaluate_bpf_keyring(ctx, BPF_SIG_KEYRING_SECONDARY);
+	case IPE_PROP_BPF_KEYRING_PLATFORM:
+		return evaluate_bpf_keyring(ctx, BPF_SIG_KEYRING_PLATFORM);
+	case IPE_PROP_BPF_KEYRING_USER:
+		return evaluate_bpf_keyring(ctx, BPF_SIG_KEYRING_USER);
+	case IPE_PROP_BPF_KERNEL_FALSE:
+		return evaluate_bpf_kernel(ctx, false);
+	case IPE_PROP_BPF_KERNEL_TRUE:
+		return evaluate_bpf_kernel(ctx, true);
 	default:
 		return false;
 	}
diff --git a/security/ipe/eval.h b/security/ipe/eval.h
index fef65a36468c..2ddf89695818 100644
--- a/security/ipe/eval.h
+++ b/security/ipe/eval.h
@@ -52,6 +52,11 @@ struct ipe_eval_ctx {
 #ifdef CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG
 	const struct ipe_inode *ipe_inode;
 #endif /* CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG */
+#ifdef CONFIG_IPE_PROP_BPF_SIGNATURE
+	u8 bpf_verdict;		/* enum bpf_sig_verdict */
+	u8 bpf_keyring;		/* enum bpf_sig_keyring */
+	bool bpf_kernel;
+#endif /* CONFIG_IPE_PROP_BPF_SIGNATURE */
 };
 
 enum ipe_match {
diff --git a/security/ipe/hooks.c b/security/ipe/hooks.c
index 0ae54a880405..bdc1b634bb08 100644
--- a/security/ipe/hooks.c
+++ b/security/ipe/hooks.c
@@ -312,6 +312,42 @@ int ipe_bdev_setintegrity(struct block_device *bdev, enum lsm_integrity_type typ
 }
 #endif /* CONFIG_IPE_PROP_DM_VERITY */
 
+#ifdef CONFIG_IPE_PROP_BPF_SIGNATURE
+/**
+ * ipe_bpf_prog_load() - IPE hook for BPF program load.
+ * @prog: The BPF program being loaded.
+ * @attr: BPF syscall attributes.
+ * @token: BPF token.
+ * @kernel: Whether the load originated in kernel mode.
+ *
+ * Return: %0 on success, %-EACCES if denied by policy.
+ */
+int ipe_bpf_prog_load(struct bpf_prog *prog, union bpf_attr *attr,
+		      struct bpf_token *token, bool kernel)
+{
+	struct ipe_eval_ctx ctx = IPE_EVAL_CTX_INIT;
+
+	ipe_build_eval_ctx(&ctx, NULL, IPE_OP_BPF_PROG_LOAD,
+			   IPE_HOOK_BPF_PROG_LOAD);
+	ctx.bpf_verdict = prog->aux->sig.verdict;
+	ctx.bpf_keyring = prog->aux->sig.keyring;
+	ctx.bpf_kernel = kernel;
+	return ipe_evaluate_event(&ctx);
+}
+
+int ipe_bpf_prog_load_post_integrity(struct bpf_prog *prog)
+{
+	struct ipe_eval_ctx ctx = IPE_EVAL_CTX_INIT;
+
+	ipe_build_eval_ctx(&ctx, NULL, IPE_OP_BPF_PROG_LOAD_POST_INTEGRITY,
+			   IPE_HOOK_BPF_PROG_LOAD_POST_INTEGRITY);
+	ctx.bpf_verdict = BPF_SIG_METADATA_VERIFIED;
+	ctx.bpf_keyring = prog->aux->sig.keyring;
+	ctx.bpf_kernel = prog->aux->is_kernel;
+	return ipe_evaluate_event(&ctx);
+}
+#endif /* CONFIG_IPE_PROP_BPF_SIGNATURE */
+
 #ifdef CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG
 /**
  * ipe_inode_setintegrity() - save integrity data from a inode to IPE's LSM blob.
diff --git a/security/ipe/hooks.h b/security/ipe/hooks.h
index 07db37332740..abdedd436aa8 100644
--- a/security/ipe/hooks.h
+++ b/security/ipe/hooks.h
@@ -10,6 +10,7 @@
 #include <linux/security.h>
 #include <linux/blk_types.h>
 #include <linux/fsverity.h>
+#include <linux/bpf.h>
 
 enum ipe_hook_type {
 	IPE_HOOK_BPRM_CHECK = 0,
@@ -18,6 +19,7 @@ enum ipe_hook_type {
 	IPE_HOOK_MPROTECT,
 	IPE_HOOK_KERNEL_READ,
 	IPE_HOOK_KERNEL_LOAD,
+	IPE_HOOK_BPF_PROG_LOAD,
 	__IPE_HOOK_MAX
 };
 
@@ -52,4 +54,9 @@ int ipe_inode_setintegrity(const struct inode *inode, enum lsm_integrity_type ty
 			   const void *value, size_t size);
 #endif /* CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG */
 
+#ifdef CONFIG_IPE_PROP_BPF_SIGNATURE
+int ipe_bpf_prog_load(struct bpf_prog *prog, union bpf_attr *attr,
+		      struct bpf_token *token, bool kernel);
+#endif /* CONFIG_IPE_PROP_BPF_SIGNATURE */
+
 #endif /* _IPE_HOOKS_H */
diff --git a/security/ipe/ipe.c b/security/ipe/ipe.c
index 495bb765de1b..17ace9236253 100644
--- a/security/ipe/ipe.c
+++ b/security/ipe/ipe.c
@@ -60,6 +60,9 @@ static struct security_hook_list ipe_hooks[] __ro_after_init = {
 #ifdef CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG
 	LSM_HOOK_INIT(inode_setintegrity, ipe_inode_setintegrity),
 #endif /* CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG */
+#ifdef CONFIG_IPE_PROP_BPF_SIGNATURE
+	LSM_HOOK_INIT(bpf_prog_load, ipe_bpf_prog_load),
+#endif /* CONFIG_IPE_PROP_BPF_SIGNATURE */
 };
 
 /**
diff --git a/security/ipe/policy.h b/security/ipe/policy.h
index 5bfbdbddeef8..eb066750a48b 100644
--- a/security/ipe/policy.h
+++ b/security/ipe/policy.h
@@ -17,6 +17,7 @@ enum ipe_op_type {
 	IPE_OP_KEXEC_INITRAMFS,
 	IPE_OP_POLICY,
 	IPE_OP_X509,
+	IPE_OP_BPF_PROG_LOAD,
 	__IPE_OP_MAX,
 };
 
@@ -39,6 +40,15 @@ enum ipe_prop_type {
 	IPE_PROP_FSV_DIGEST,
 	IPE_PROP_FSV_SIG_FALSE,
 	IPE_PROP_FSV_SIG_TRUE,
+	IPE_PROP_BPF_SIG_UNSIGNED,
+	IPE_PROP_BPF_SIG_OK,
+	IPE_PROP_BPF_SIG_METADATA_VERIFIED,
+	IPE_PROP_BPF_KEYRING_BUILTIN,
+	IPE_PROP_BPF_KEYRING_SECONDARY,
+	IPE_PROP_BPF_KEYRING_PLATFORM,
+	IPE_PROP_BPF_KEYRING_USER,
+	IPE_PROP_BPF_KERNEL_FALSE,
+	IPE_PROP_BPF_KERNEL_TRUE,
 	__IPE_PROP_MAX
 };
 
diff --git a/security/ipe/policy_parser.c b/security/ipe/policy_parser.c
index 6fa5bebf8471..c1e374d2ec34 100644
--- a/security/ipe/policy_parser.c
+++ b/security/ipe/policy_parser.c
@@ -237,6 +237,7 @@ static const match_table_t operation_tokens = {
 	{IPE_OP_KEXEC_INITRAMFS,	"op=KEXEC_INITRAMFS"},
 	{IPE_OP_POLICY,			"op=POLICY"},
 	{IPE_OP_X509,			"op=X509_CERT"},
+	{IPE_OP_BPF_PROG_LOAD,		"op=BPF_PROG_LOAD"},
 	{IPE_OP_INVALID,		NULL}
 };
 
@@ -281,6 +282,15 @@ static const match_table_t property_tokens = {
 	{IPE_PROP_FSV_DIGEST,		"fsverity_digest=%s"},
 	{IPE_PROP_FSV_SIG_FALSE,	"fsverity_signature=FALSE"},
 	{IPE_PROP_FSV_SIG_TRUE,		"fsverity_signature=TRUE"},
+	{IPE_PROP_BPF_SIG_UNSIGNED,	"bpf_signature=UNSIGNED"},
+	{IPE_PROP_BPF_SIG_OK,		"bpf_signature=OK"},
+	{IPE_PROP_BPF_SIG_METADATA_VERIFIED, "bpf_signature=METADATA_VERIFIED"},
+	{IPE_PROP_BPF_KEYRING_BUILTIN,	"bpf_keyring=BUILTIN"},
+	{IPE_PROP_BPF_KEYRING_SECONDARY, "bpf_keyring=SECONDARY"},
+	{IPE_PROP_BPF_KEYRING_PLATFORM,	"bpf_keyring=PLATFORM"},
+	{IPE_PROP_BPF_KEYRING_USER,	"bpf_keyring=USER"},
+	{IPE_PROP_BPF_KERNEL_FALSE,	"bpf_kernel=FALSE"},
+	{IPE_PROP_BPF_KERNEL_TRUE,	"bpf_kernel=TRUE"},
 	{IPE_PROP_INVALID,		NULL}
 };
 
@@ -331,6 +341,15 @@ static int parse_property(char *t, struct ipe_rule *r)
 	case IPE_PROP_DMV_SIG_TRUE:
 	case IPE_PROP_FSV_SIG_FALSE:
 	case IPE_PROP_FSV_SIG_TRUE:
+	case IPE_PROP_BPF_SIG_UNSIGNED:
+	case IPE_PROP_BPF_SIG_OK:
+	case IPE_PROP_BPF_SIG_METADATA_VERIFIED:
+	case IPE_PROP_BPF_KEYRING_BUILTIN:
+	case IPE_PROP_BPF_KEYRING_SECONDARY:
+	case IPE_PROP_BPF_KEYRING_PLATFORM:
+	case IPE_PROP_BPF_KEYRING_USER:
+	case IPE_PROP_BPF_KERNEL_FALSE:
+	case IPE_PROP_BPF_KERNEL_TRUE:
 		p->type = token;
 		break;
 	default:
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH bpf-next 12/13] ipe: gate post-integrity BPF program loads
  2026-05-22  2:32 [PATCH bpf-next 00/13] Signed BPF + IPE Policies KP Singh
                   ` (10 preceding siblings ...)
  2026-05-22  2:32 ` [PATCH bpf-next 11/13] ipe: add BPF program signature properties KP Singh
@ 2026-05-22  2:32 ` KP Singh
  2026-05-22  2:32 ` [PATCH bpf-next 13/13] selftests/bpf: add IPE BPF policy integration tests KP Singh
  12 siblings, 0 replies; 14+ messages in thread
From: KP Singh @ 2026-05-22  2:32 UTC (permalink / raw)
  To: linux-security-module, bpf
  Cc: ast, daniel, memxor, James.Bottomley, paul, KP Singh

Register on security_bpf_prog_load_post_integrity and expose a new
IPE op BPF_PROG_LOAD_POST_INTEGRITY. Kept distinct from
BPF_PROG_LOAD so policies need not reason about the same rule
firing at two timings with different verdict states.

Signed-off-by: KP Singh <kpsingh@kernel.org>
---
 security/ipe/audit.c         | 2 ++
 security/ipe/hooks.c         | 6 ++++++
 security/ipe/hooks.h         | 2 ++
 security/ipe/ipe.c           | 1 +
 security/ipe/policy.h        | 1 +
 security/ipe/policy_parser.c | 1 +
 6 files changed, 13 insertions(+)

diff --git a/security/ipe/audit.c b/security/ipe/audit.c
index fec98c396d49..bcb3e6c0a310 100644
--- a/security/ipe/audit.c
+++ b/security/ipe/audit.c
@@ -42,6 +42,7 @@ static const char *const audit_op_names[__IPE_OP_MAX + 1] = {
 	"POLICY",
 	"X509_CERT",
 	"BPF_PROG_LOAD",
+	"BPF_PROG_LOAD_POST_INTEGRITY",
 	"UNKNOWN",
 };
 
@@ -53,6 +54,7 @@ static const char *const audit_hook_names[__IPE_HOOK_MAX] = {
 	"KERNEL_READ",
 	"KERNEL_LOAD",
 	"BPF_PROG_LOAD",
+	"BPF_PROG_LOAD_POST_INTEGRITY",
 };
 
 static const char *const audit_prop_names[__IPE_PROP_MAX] = {
diff --git a/security/ipe/hooks.c b/security/ipe/hooks.c
index bdc1b634bb08..3f6e260a8787 100644
--- a/security/ipe/hooks.c
+++ b/security/ipe/hooks.c
@@ -335,6 +335,12 @@ int ipe_bpf_prog_load(struct bpf_prog *prog, union bpf_attr *attr,
 	return ipe_evaluate_event(&ctx);
 }
 
+/**
+ * ipe_bpf_prog_load_post_integrity() - IPE hook for post-integrity verdict.
+ * @prog: The loader BPF program.
+ *
+ * Return: %0 on success, %-EACCES if denied by policy.
+ */
 int ipe_bpf_prog_load_post_integrity(struct bpf_prog *prog)
 {
 	struct ipe_eval_ctx ctx = IPE_EVAL_CTX_INIT;
diff --git a/security/ipe/hooks.h b/security/ipe/hooks.h
index abdedd436aa8..bd24067705ea 100644
--- a/security/ipe/hooks.h
+++ b/security/ipe/hooks.h
@@ -20,6 +20,7 @@ enum ipe_hook_type {
 	IPE_HOOK_KERNEL_READ,
 	IPE_HOOK_KERNEL_LOAD,
 	IPE_HOOK_BPF_PROG_LOAD,
+	IPE_HOOK_BPF_PROG_LOAD_POST_INTEGRITY,
 	__IPE_HOOK_MAX
 };
 
@@ -57,6 +58,7 @@ int ipe_inode_setintegrity(const struct inode *inode, enum lsm_integrity_type ty
 #ifdef CONFIG_IPE_PROP_BPF_SIGNATURE
 int ipe_bpf_prog_load(struct bpf_prog *prog, union bpf_attr *attr,
 		      struct bpf_token *token, bool kernel);
+int ipe_bpf_prog_load_post_integrity(struct bpf_prog *prog);
 #endif /* CONFIG_IPE_PROP_BPF_SIGNATURE */
 
 #endif /* _IPE_HOOKS_H */
diff --git a/security/ipe/ipe.c b/security/ipe/ipe.c
index 17ace9236253..d5e6f339639a 100644
--- a/security/ipe/ipe.c
+++ b/security/ipe/ipe.c
@@ -62,6 +62,7 @@ static struct security_hook_list ipe_hooks[] __ro_after_init = {
 #endif /* CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG */
 #ifdef CONFIG_IPE_PROP_BPF_SIGNATURE
 	LSM_HOOK_INIT(bpf_prog_load, ipe_bpf_prog_load),
+	LSM_HOOK_INIT(bpf_prog_load_post_integrity, ipe_bpf_prog_load_post_integrity),
 #endif /* CONFIG_IPE_PROP_BPF_SIGNATURE */
 };
 
diff --git a/security/ipe/policy.h b/security/ipe/policy.h
index eb066750a48b..84b3e69e618d 100644
--- a/security/ipe/policy.h
+++ b/security/ipe/policy.h
@@ -18,6 +18,7 @@ enum ipe_op_type {
 	IPE_OP_POLICY,
 	IPE_OP_X509,
 	IPE_OP_BPF_PROG_LOAD,
+	IPE_OP_BPF_PROG_LOAD_POST_INTEGRITY,
 	__IPE_OP_MAX,
 };
 
diff --git a/security/ipe/policy_parser.c b/security/ipe/policy_parser.c
index c1e374d2ec34..350cc93a1af1 100644
--- a/security/ipe/policy_parser.c
+++ b/security/ipe/policy_parser.c
@@ -238,6 +238,7 @@ static const match_table_t operation_tokens = {
 	{IPE_OP_POLICY,			"op=POLICY"},
 	{IPE_OP_X509,			"op=X509_CERT"},
 	{IPE_OP_BPF_PROG_LOAD,		"op=BPF_PROG_LOAD"},
+	{IPE_OP_BPF_PROG_LOAD_POST_INTEGRITY, "op=BPF_PROG_LOAD_POST_INTEGRITY"},
 	{IPE_OP_INVALID,		NULL}
 };
 
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH bpf-next 13/13] selftests/bpf: add IPE BPF policy integration tests
  2026-05-22  2:32 [PATCH bpf-next 00/13] Signed BPF + IPE Policies KP Singh
                   ` (11 preceding siblings ...)
  2026-05-22  2:32 ` [PATCH bpf-next 12/13] ipe: gate post-integrity BPF program loads KP Singh
@ 2026-05-22  2:32 ` KP Singh
  12 siblings, 0 replies; 14+ messages in thread
From: KP Singh @ 2026-05-22  2:32 UTC (permalink / raw)
  To: linux-security-module, bpf
  Cc: ast, daniel, memxor, James.Bottomley, paul, KP Singh

Smoke test for the IPE BPF signing properties and ops added in the
preceding patches. The test primes the kernel config with IPE
knobs, writes a boot policy, and boots vmtest.sh to verify that
signed lskels load, unsigned loads are denied, and the policy
audit lines match.

Manual only for now, not wired into the selftests Makefile.

Signed-off-by: KP Singh <kpsingh@kernel.org>
---
 .../selftests/bpf/test_signed_bpf_ipe.sh      | 156 ++++++++++++++++++
 tools/testing/selftests/bpf/vmtest.sh         |   4 +-
 2 files changed, 158 insertions(+), 2 deletions(-)
 create mode 100755 tools/testing/selftests/bpf/test_signed_bpf_ipe.sh

diff --git a/tools/testing/selftests/bpf/test_signed_bpf_ipe.sh b/tools/testing/selftests/bpf/test_signed_bpf_ipe.sh
new file mode 100755
index 000000000000..aaa259ddb917
--- /dev/null
+++ b/tools/testing/selftests/bpf/test_signed_bpf_ipe.sh
@@ -0,0 +1,156 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# IPE BPF policy integration tests.
+
+if [ "${1:-}" = "--in-vm" ]; then
+	set -u
+	fail=0
+	record() {
+		id=$1; verdict=$2; shift 2
+		printf '%s %s %s\n' "$verdict" "$id" "$*"
+		[ "$verdict" = FAIL ] && fail=$((fail + 1))
+		return 0
+	}
+
+	mountpoint -q /sys/kernel/security 2>/dev/null \
+		|| mount -t securityfs none /sys/kernel/security 2>/dev/null || true
+	if [ -d /sys/kernel/security/ipe ]; then
+		record T0 PASS "securityfs/ipe present"
+	elif grep -qw ipe /sys/kernel/security/lsm 2>/dev/null; then
+		record T0 PASS "ipe in /sys/kernel/security/lsm"
+	elif [ -r /sys/module/ipe/parameters/enforce ]; then
+		record T0 PASS "ipe module parameters present"
+	else
+		record T0 FAIL "no sign that IPE LSM is loaded"
+	fi
+
+	if ./test_progs -t atomics 2>&1 | grep -qE '^#[0-9]+[[:space:]]+atomics:OK$'; then
+		record T1 PASS "atomics signed lskel loaded"
+	else
+		record T1 FAIL "atomics signed lskel did not load"
+	fi
+
+	if dmesg | grep -q 'ipe_op=BPF_PROG_LOAD ipe_hook=BPF_PROG_LOAD'; then
+		record T2 PASS "ipe_op=BPF_PROG_LOAD audit line found"
+	else
+		record T2 FAIL "no ipe_op=BPF_PROG_LOAD audit lines in dmesg"
+	fi
+
+	if dmesg | grep 'ipe_op=BPF_PROG_LOAD' \
+		| grep -q 'bpf_signature=UNSIGNED action=DENY'; then
+		record T3 PASS "deny-UNSIGNED rule matched"
+	else
+		record T3 FAIL "deny-UNSIGNED rule never matched"
+	fi
+
+	mountpoint -q /sys/kernel/security 2>/dev/null \
+		|| mount -t securityfs none /sys/kernel/security 2>/dev/null || true
+	content=
+	for d in /sys/kernel/security/ipe/policies/*/; do
+		[ -d "$d" ] || continue
+		if [ -r "$d/policy" ]; then
+			content=$(cat "$d/policy" 2>/dev/null || true)
+			break
+		fi
+	done
+	if [ -z "$content" ]; then
+		record T5 SKIP "could not read /sys/kernel/security/ipe/policies/*/policy"
+	elif echo "$content" | grep -q '^op=BPF_PROG_LOAD_POST_INTEGRITY'; then
+		record T5 PASS "post-integrity rule present in active policy"
+	else
+		record T5 FAIL "active policy does not contain a BPF_PROG_LOAD_POST_INTEGRITY rule"
+	fi
+
+	ENFORCE=/sys/kernel/security/ipe/enforce
+	mountpoint -q /sys/kernel/security 2>/dev/null \
+		|| mount -t securityfs none /sys/kernel/security 2>/dev/null || true
+
+	if [ ! -w "$ENFORCE" ]; then
+		record T4 SKIP "$ENFORCE not writable"
+	elif ! echo 1 > "$ENFORCE" 2>/dev/null; then
+		record T4 SKIP "could not toggle IPE to enforce=1"
+	else
+		out=$(./test_progs -t bind_perm 2>&1 || true)
+		if echo "$out" | grep -q -- '-EACCES\|errno 13'; then
+			record T4 PASS "unsigned load denied with -EACCES under enforce=1"
+		else
+			record T4 FAIL "no -EACCES seen from unsigned load under enforce=1"
+			echo "$out" | tail -10 | sed 's/^/  T4: /'
+		fi
+		echo 0 > "$ENFORCE" 2>/dev/null || true
+	fi
+
+	if [ "$fail" -eq 0 ]; then
+		echo ALL_TESTS_PASSED
+	else
+		echo FAIL_COUNT "$fail"
+	fi
+	exit 0
+fi
+
+set -euo pipefail
+
+SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
+KERNEL_DIR=$(cd "$SCRIPT_DIR/../../../.." && pwd)
+VMTEST="$SCRIPT_DIR/vmtest.sh"
+
+POLICY_PATH=${IPE_TEST_POLICY:-/tmp/ipe_bpf_test_policy}
+
+red()   { printf '\033[31m%s\033[0m\n' "$*"; }
+green() { printf '\033[32m%s\033[0m\n' "$*"; }
+bold()  { printf '\033[1m%s\033[0m\n' "$*"; }
+
+bold "=== IPE BPF policy integration tests ==="
+
+tmpfile=$(mktemp -p "$(dirname "$POLICY_PATH")" \
+		  ".$(basename "$POLICY_PATH").XXXXXX")
+cat > "$tmpfile" <<EOF
+policy_name="ipe_bpf_test" policy_version=0.0.1
+op=BPF_PROG_LOAD bpf_signature=OK                action=ALLOW
+op=BPF_PROG_LOAD bpf_signature=UNSIGNED          action=DENY
+op=BPF_PROG_LOAD_POST_INTEGRITY                  action=ALLOW
+DEFAULT op=BPF_PROG_LOAD                         action=ALLOW
+DEFAULT op=BPF_PROG_LOAD_POST_INTEGRITY          action=ALLOW
+DEFAULT action=ALLOW
+EOF
+mv "$tmpfile" "$POLICY_PATH"
+echo "policy written to $POLICY_PATH"
+
+export OUTPUT_DIR=${OUTPUT_DIR:-$HOME/.bpf_selftests_ipe}
+CFG="$OUTPUT_DIR/latest.config"
+mkdir -p "$OUTPUT_DIR"
+if [ ! -f "$CFG" ]; then
+	PLATFORM=$(uname -m)
+	for src in "$KERNEL_DIR/tools/testing/selftests/bpf/config" \
+		   "$KERNEL_DIR/tools/testing/selftests/bpf/config.vm" \
+		   "$KERNEL_DIR/tools/testing/selftests/bpf/config.$PLATFORM"; do
+		[ -f "$src" ] && cat "$src" >> "$CFG"
+	done
+fi
+"$KERNEL_DIR/scripts/config" --file "$CFG" \
+	--enable SECURITY_IPE \
+	--enable IPE_PROP_BPF_SIGNATURE \
+	--set-str IPE_BOOT_POLICY "$POLICY_PATH"
+lsm=$("$KERNEL_DIR/scripts/config" --file "$CFG" --state LSM 2>/dev/null)
+if [ -z "$lsm" ] || [ "$lsm" = "undef" ]; then
+	"$KERNEL_DIR/scripts/config" --file "$CFG" --set-str LSM "bpf,ipe"
+elif ! echo ",${lsm}," | grep -q ',ipe,'; then
+	"$KERNEL_DIR/scripts/config" --file "$CFG" --set-str LSM "${lsm},ipe"
+fi
+touch "$CFG"
+
+RUN_OUT=$(mktemp -t ipe_test_run.XXXXXX)
+trap 'rm -f "$RUN_OUT"' EXIT
+export VMTEST_EXTRA_CMDLINE="ipe.enforce=0 ipe.success_audit=1"
+"$VMTEST" -- ./test_signed_bpf_ipe.sh --in-vm 2>&1 | tee "$RUN_OUT"
+
+if grep -q 'ALL_TESTS_PASSED' "$RUN_OUT"; then
+	green "=== all IPE policy integration tests PASSED ==="
+	grep -E '^(PASS|FAIL|SKIP) T[0-9]+' "$RUN_OUT" || true
+	exit 0
+else
+	red "=== IPE policy integration tests FAILED ==="
+	grep -E '^(PASS|FAIL|SKIP) T[0-9]+' "$RUN_OUT" || true
+	exit 1
+fi
diff --git a/tools/testing/selftests/bpf/vmtest.sh b/tools/testing/selftests/bpf/vmtest.sh
index 2f869daf8a06..b30e2b359413 100755
--- a/tools/testing/selftests/bpf/vmtest.sh
+++ b/tools/testing/selftests/bpf/vmtest.sh
@@ -61,7 +61,7 @@ DEFAULT_COMMAND="./test_progs"
 MOUNT_DIR="mnt"
 LOCAL_ROOTFS_IMAGE=""
 ROOTFS_IMAGE="root.img"
-OUTPUT_DIR="$HOME/.bpf_selftests"
+OUTPUT_DIR="${OUTPUT_DIR:-$HOME/.bpf_selftests}"
 KCONFIG_REL_PATHS=("tools/testing/selftests/bpf/config"
 	"tools/testing/selftests/bpf/config.vm"
 	"tools/testing/selftests/bpf/config.${PLATFORM}")
@@ -294,7 +294,7 @@ EOF
 		-m 4G \
 		-drive file="${rootfs_img}",format=raw,index=1,media=disk,if=virtio,cache=none \
 		-kernel "${kernel_bzimage}" \
-		-append "root=/dev/vda rw console=${QEMU_CONSOLE}"
+		-append "root=/dev/vda rw console=${QEMU_CONSOLE} ${VMTEST_EXTRA_CMDLINE:-}"
 }
 
 copy_logs()
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 14+ messages in thread

end of thread, other threads:[~2026-05-22  2:33 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-22  2:32 [PATCH bpf-next 00/13] Signed BPF + IPE Policies KP Singh
2026-05-22  2:32 ` [PATCH bpf-next 01/13] bpf: expose signature verdict to LSMs via bpf_prog_aux KP Singh
2026-05-22  2:32 ` [PATCH bpf-next 02/13] bpf: include prog BTF in the signed loader signature scope KP Singh
2026-05-22  2:32 ` [PATCH bpf-next 03/13] bpf, libbpf: load prog BTF in the skel_internal loader KP Singh
2026-05-22  2:32 ` [PATCH bpf-next 04/13] bpf: add bpf_loader_verify_metadata kfunc KP Singh
2026-05-22  2:32 ` [PATCH bpf-next 05/13] bpf: compute prog->digest at BPF_PROG_LOAD entry KP Singh
2026-05-22  2:32 ` [PATCH bpf-next 06/13] bpf: resolve loader-style kfunc CALLs against prog BTF KP Singh
2026-05-22  2:32 ` [PATCH bpf-next 07/13] libbpf: generate prog BTF for loader programs KP Singh
2026-05-22  2:32 ` [PATCH bpf-next 08/13] bpftool gen: embed loader prog BTF in the lskel header KP Singh
2026-05-22  2:32 ` [PATCH bpf-next 09/13] lsm: add bpf_prog_load_post_integrity hook KP Singh
2026-05-22  2:32 ` [PATCH bpf-next 10/13] bpf: invoke security_bpf_prog_load_post_integrity from the metadata kfunc KP Singh
2026-05-22  2:32 ` [PATCH bpf-next 11/13] ipe: add BPF program signature properties KP Singh
2026-05-22  2:32 ` [PATCH bpf-next 12/13] ipe: gate post-integrity BPF program loads KP Singh
2026-05-22  2:32 ` [PATCH bpf-next 13/13] selftests/bpf: add IPE BPF policy integration tests KP Singh

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox