From: KP Singh <kpsingh@kernel.org>
To: linux-security-module@vger.kernel.org, bpf@vger.kernel.org
Cc: ast@kernel.org, daniel@iogearbox.net, memxor@gmail.com,
James.Bottomley@HansenPartnership.com, paul@paul-moore.com,
KP Singh <kpsingh@kernel.org>
Subject: [PATCH bpf-next 07/13] libbpf: generate prog BTF for loader programs
Date: Fri, 22 May 2026 04:32:27 +0200 [thread overview]
Message-ID: <20260522023234.3778588-8-kpsingh@kernel.org> (raw)
In-Reply-To: <20260522023234.3778588-1-kpsingh@kernel.org>
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
next prev parent reply other threads:[~2026-05-22 2:32 UTC|newest]
Thread overview: 16+ messages / expand[flat|nested] mbox.gz Atom feed top
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 ` KP Singh [this message]
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
2026-05-22 18:56 ` [PATCH bpf-next 00/13] Signed BPF + IPE Policies Paul Moore
2026-05-22 20:46 ` KP Singh
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260522023234.3778588-8-kpsingh@kernel.org \
--to=kpsingh@kernel.org \
--cc=James.Bottomley@HansenPartnership.com \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=daniel@iogearbox.net \
--cc=linux-security-module@vger.kernel.org \
--cc=memxor@gmail.com \
--cc=paul@paul-moore.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox