bpf.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v2 00/13] Signed BPF programs
@ 2025-07-21 21:19 KP Singh
  2025-07-21 21:19 ` [PATCH v2 01/13] bpf: Update the bpf_prog_calc_tag to use SHA256 KP Singh
                   ` (12 more replies)
  0 siblings, 13 replies; 33+ messages in thread
From: KP Singh @ 2025-07-21 21:19 UTC (permalink / raw)
  To: bpf, linux-security-module; +Cc: bboscaccy, paul, kys, ast, daniel, andrii

#v1 -> v2

* Addressed feedback on excl maps and their implementation
* fixed s390x and other tests that were failing in the CI.
* using the kernel's sha256 API since it now uses acceleration if available
* simple signing test case, this can be extended to inject a false SHA into
  the loader

BPF Signing has gone over multiple discussions in various conferences with the
kernel and BPF community and the following patch series is a culmination
of the current of discussion and signed BPF programs. Once signing is
implemented, the next focus would be to implement the right security policies
for all BPF use-cases (dynamically generated bpf programs, simple non CO-RE
programs).

Signing also paves the way for allowing unrivileged users to
load vetted BPF programs and helps in adhering to the principle of least
privlege by avoiding unnecessary elevation of privileges to CAP_BPF and
CAP_SYS_ADMIN (ofcourse, with the appropriate security policy active).

A early version of this design was proposed in [1]:

# General Idea: Trusted Hash Chain

The key idea of the design is to use a signing algorithm that allows
us to integrity-protect a number of future payloads, including their
order, by creating a chain of trust.

Consider that Alice needs to send messages M_1, M_2, ..., M_n to Bob.
We define blocks of data such that:

    B_n = M_n || H(termination_marker)

(Each block contains its corresponding message and the hash of the
*next* block in the chain.)

    B_{n-1} = M_{n-1} || H(B_n)
    B_{n-2} = M_{n-2} || H(B_{n-1})

  ...

    B_2 = M_2 || H(B_3)
    B_1 = M_1 || H(B_2)

Alice does the following (e.g., on a build system where all payloads
are available):

  * Assembles the blocks B_1, B_2, ..., B_n.
  * Calculates H(B_1) and signs it, yielding Sig(H(B_1)).

Alice sends the following to Bob:

    M_1, H(B_2), Sig(H(B_1))

Bob receives this payload and does the following:

    * Reconstructs B_1 as B_1' using the received M_1 and H(B_2)
(i.e., B_1' = M_1 || H(B_2)).
    * Recomputes H(B_1') and verifies the signature against the
received Sig(H(B_1)).
    * If the signature verifies, it establishes the integrity of M_1
and H(B_2) (and transitively, the integrity of the entire chain). Bob
now stores the verified H(B_2) until it receives the next message.
    * When Bob receives M_2 (and H(B_3) if n > 2), it reconstructs
B_2' (e.g., B_2' = M_2 || H(B_3), or if n=2, B_2' = M_2 ||
H(termination_marker)). Bob then computes H(B_2') and compares it
against the stored H(B_2) that was verified in the previous step.

This process continues until the last block is received and verified.

Now, applying this to the BPF signing use-case, we simplify to two messages:

    M_1 = I_loader (the instructions of the loader program)
    M_2 = M_metadata (the metadata for the loader program, passed in a
map, which includes the programs to be loaded and other context)

For this specific BPF case, we will directly sign a composite of the
first message and the hash of the second. Let H_meta = H(M_metadata).
The block to be signed is effectively:

    B_signed = I_loader || H_meta

The signature generated is Sig(B_signed).

The process then follows a similar pattern to the Alice and Bob model,
where the kernel (Bob) verifies I_loader and H_meta using the
signature. Then, the trusted I_loader is responsible for verifying
M_metadata against the trusted H_meta.

From an implementation standpoint:

# Build

bpftool (or some other tool in a trusted build environment) knows
about the metadata (M_metadata) and the loader program (I_loader). It
first calculates H_meta = H(M_metadata). Then it constructs the object
to be signed and computes the signature:

    Sig(I_loader || H_meta)

# Loader

The loader program and the metadata are a hermetic representation of the source
of the eBPF program, its maps and context. The loader program is generated by
libbpf as a part of a standard API i.e. bpf_object__gen_loader.

## Supply chain

While users can use light skeletons as a convenient method to use signing
support, they can directly use the loader program generation using libbpf
(bpf_object__gen_loader) into their own trusted toolchains.

libbpf, which has access to the program's instruction buffer is a key part of
the TCB of the build environment

An advanced threat model that does not intend to depend on libbpf (or any provenant
userspace BPF libraries) due to supply chain risks despite it being developed
in the kernel source and by the kernel community will require reimplmenting a
lot of the core BPF userspace support (like instruction relocation, map handling).

Such an advanced user would also need to integrate the generation of the loader
into their toolchain.

Given that many use-cases (e.g. Cilium) generate trusted BPF programs,
trusted loaders are an inevitability and a requirement for signing support, a
entrusting loader programs will be a fundamental requirement for an security
policy.

The initial instructions of the loader program verify the SHA256 hash
of the metadata (M_metadata) that will be passed in a map. These instructions
effectively embed the precomputed H_meta as immediate values.

    ld_imm64 r1, const_ptr_to_map // insn[0].src_reg == BPF_PSEUDO_MAP_IDX
    r2 = *(u64 *)(r1 + 0);
    ld_imm64 r3, sha256_of_map_part1 // precomputed by bpf_object__gen_load/libbpf (H_meta_1)
    if r2 != r3 goto out;

    r2 = *(u64 *)(r1 + 8);
    ld_imm64 r3, sha256_of_map_part2 // precomputed by bpf_object__gen_load/libbpf (H_meta_2)
    if r2 != r3 goto out;

    r2 = *(u64 *)(r1 + 16);
    ld_imm64 r3, sha256_of_map_part3 // precomputed by bpf_object__gen_load/libbpf (H_meta_3)
    if r2 != r3 goto out;

    r2 = *(u64 *)(r1 + 24);
    ld_imm64 r3, sha256_of_map_part4 // precomputed by bpf_object__gen_load/libbpf (H_meta_4)
    if r2 != r3 goto out;
    ...

This implicitly makes the payload equivalent to the signed block (B_signed)

    I_loader || H_meta

bpftool then generates the signature of this I_loader payload (which
now contains the expected H_meta) using a key and an identity:

This signature is stored in bpf_attr, which is extended as follows for
the BPF_PROG_LOAD command:

    __aligned_u64 signature;
    __u32 signature_size;
    __u32 keyring_id;

The reasons for a simpler UAPI is that it's more future proof (e.g.) with more
stable instruction buffers, loader programs being directly into the compilers.
A simple API also allows simple programs e.g. for networking that don't need
loader programs to directly use signing.

# Extending OBJ_GET_INFO_BY_FD for hashes

OBJ_GET_INFO_BY_FD is used to get information about BPF objects (maps, programs, links) and
returning the hash of the map is a natural extension of the UAPI as it can be
helpful for debugging, fingerprinting etc.

Currently, it's only implemented for BPF_MAP_TYPE_ARRAY. It can be trivially
extended for BPF programs to return the complete SHA256 along with the tag.

The SHA is stored in struct bpf_map for exclusive and frozen maps

    struct bpf_map {
    +   u64 sha[4];
        const struct bpf_map_ops *ops;
        struct bpf_map *inner_map_meta;
    };

## Exclusive BPF maps

Exclusivity ensures that the map can only be used by a future BPF
program whose SHA256 hash matches sha256_of_future_prog.

First, bpf_prog_calc_tag() is updated to compute the SHA256 instead of
SHA1, and this hash is stored in struct bpf_prog_aux:

    @@ -1588,6 +1588,7 @@ struct bpf_prog_aux {
         int cgroup_atype; /* enum cgroup_bpf_attach_type */
         struct bpf_map *cgroup_storage[MAX_BPF_CGROUP_STORAGE_TYPE];
         char name[BPF_OBJ_NAME_LEN];
    +    u64 sha[4];
         u64 (*bpf_exception_cb)(u64 cookie, u64 sp, u64 bp, u64, u64);
         // ...
    };

An exclusive is created by passing an excl_prog_hash
(and excl_prog_hash_size) in the BPF_MAP_CREATE command.
When a BPF program is subsequently loaded and it attempts to use this map,
the kernel will compare the program's own SHA256 hash against the one
registered with the map, if matching, it will be added to prog->used_maps[].

The program load will fail if the hashes do not match or if the map is
already in use by another (non-matching) exclusive program.

Exclusive maps ensure that no other BPF programs and compromise the intergity of
the map post the signature verification.

NOTE: Exclusive maps cannot be added as inner maps.

# Light Skeleton Sequence (Userspace Example)

	err = map_fd = skel_map_create(BPF_MAP_TYPE_ARRAY, "__loader.map",
				       opts->excl_prog_hash,
				       opts->excl_prog_hash_sz, 4,
				       opts->data_sz, 1);
	err = skel_map_update_elem(map_fd, &key, opts->data, 0);

	err = skel_map_freeze(map_fd);

	// Kernel computes the hash of the map.
	err = skel_obj_get_info_by_fd(map_fd);

	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.signature = (long) opts->signature;
	attr.signature_size = opts->signature_sz;
	attr.keyring_id = opts->keyring_id;
	attr.license = (long) "Dual BSD/GPL";

The kernel will:

    * Compute the hash of the provided I_loader bytecode.
    * Verify the signature against this computed hash.
    * Check if the metadata map (now exclusive) is intended for this
      program's hash.

The signature check happens in BPF_PROG_LOAD before the security_bpf_prog
LSM hook.

This ensures that the loaded loader program (I_loader), including the
embedded expected hash of the metadata (H_meta), is trusted.
Since the loader program is now trusted, it can be entrusted to verify
the actual metadata (M_metadata) read from the (now exclusive and
frozen) map against the embedded (and trusted) H_meta. There is no
Time-of-Check-Time-of-Use (TOCTOU) vulnerability here because:

    * The signature covers the I_loader and its embedded H_meta.
    * The metadata map M_metadata is frozen before the loader program is loaded
      and associated with it.
    * The map is made exclusive to the specific (signed and verified)
      loader program.

[1] https://lore.kernel.org/bpf/CACYkzJ6VQUExfyt0=-FmXz46GHJh3d=FXh5j4KfexcEFbHV-vg@mail.gmail.com/#t


KP Singh (13):
  bpf: Update the bpf_prog_calc_tag to use SHA256
  bpf: Implement exclusive map creation
  libbpf: Implement SHA256 internal helper
  libbpf: Support exclusive map creation
  selftests/bpf: Add tests for exclusive maps
  bpf: Return hashes of maps in BPF_OBJ_GET_INFO_BY_FD
  bpf: Move the signature kfuncs to helpers.c
  bpf: Implement signature verification for BPF programs
  libbpf: Update light skeleton for signing
  libbpf: Embed and verify the metadata hash in the loader
  bpftool: Add support for signing BPF programs
  selftests/bpf: Enable signature verification for all lskel tests
  selftests/bpf: Add test for signed programs

 crypto/asymmetric_keys/pkcs7_verify.c         |   1 +
 include/linux/bpf.h                           |  42 +++-
 include/linux/filter.h                        |   6 -
 include/linux/verification.h                  |   1 +
 include/uapi/linux/bpf.h                      |  14 ++
 kernel/bpf/Kconfig                            |   2 +-
 kernel/bpf/arraymap.c                         |  13 ++
 kernel/bpf/core.c                             |  50 +----
 kernel/bpf/helpers.c                          | 166 ++++++++++++++
 kernel/bpf/syscall.c                          |  96 +++++++-
 kernel/bpf/verifier.c                         |   6 +
 kernel/trace/bpf_trace.c                      | 183 ---------------
 .../bpf/bpftool/Documentation/bpftool-gen.rst |  12 +
 .../bpftool/Documentation/bpftool-prog.rst    |  12 +
 tools/bpf/bpftool/Makefile                    |   6 +-
 tools/bpf/bpftool/cgroup.c                    |   5 +-
 tools/bpf/bpftool/gen.c                       |  58 ++++-
 tools/bpf/bpftool/main.c                      |  21 +-
 tools/bpf/bpftool/main.h                      |  11 +
 tools/bpf/bpftool/prog.c                      |  25 +++
 tools/bpf/bpftool/sign.c                      | 210 ++++++++++++++++++
 tools/include/uapi/linux/bpf.h                |  14 ++
 tools/lib/bpf/bpf.c                           |   6 +-
 tools/lib/bpf/bpf.h                           |   4 +-
 tools/lib/bpf/bpf_gen_internal.h              |   2 +
 tools/lib/bpf/gen_loader.c                    |  55 +++++
 tools/lib/bpf/libbpf.c                        | 125 +++++++++++
 tools/lib/bpf/libbpf.h                        |  21 +-
 tools/lib/bpf/libbpf.map                      |   2 +
 tools/lib/bpf/libbpf_internal.h               |   4 +
 tools/lib/bpf/skel_internal.h                 |  75 ++++++-
 tools/testing/selftests/bpf/.gitignore        |   1 +
 tools/testing/selftests/bpf/Makefile          |  19 +-
 .../selftests/bpf/prog_tests/map_excl.c       |  56 +++++
 .../selftests/bpf/prog_tests/signing.c        |  36 +++
 tools/testing/selftests/bpf/progs/map_excl.c  |  34 +++
 tools/testing/selftests/bpf/progs/signing.c   |  16 ++
 .../selftests/bpf/progs/verifier_map_ptr.c    |   7 +-
 tools/testing/selftests/bpf/test_progs.c      |  13 ++
 .../testing/selftests/bpf/verify_sig_setup.sh |  13 +-
 40 files changed, 1183 insertions(+), 260 deletions(-)
 create mode 100644 tools/bpf/bpftool/sign.c
 create mode 100644 tools/testing/selftests/bpf/prog_tests/map_excl.c
 create mode 100644 tools/testing/selftests/bpf/prog_tests/signing.c
 create mode 100644 tools/testing/selftests/bpf/progs/map_excl.c
 create mode 100644 tools/testing/selftests/bpf/progs/signing.c

-- 
2.43.0


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

* [PATCH v2 01/13] bpf: Update the bpf_prog_calc_tag to use SHA256
  2025-07-21 21:19 [PATCH v2 00/13] Signed BPF programs KP Singh
@ 2025-07-21 21:19 ` KP Singh
  2025-07-21 21:19 ` [PATCH v2 02/13] bpf: Implement exclusive map creation KP Singh
                   ` (11 subsequent siblings)
  12 siblings, 0 replies; 33+ messages in thread
From: KP Singh @ 2025-07-21 21:19 UTC (permalink / raw)
  To: bpf, linux-security-module
  Cc: bboscaccy, paul, kys, ast, daniel, andrii, KP Singh

Exclusive maps restrict map access to specific programs using a hash.
The current hash used for this is SHA1, which is prone to collisions.
This patch uses SHA256, which  is more resilient against
collisions. This new hash is stored in bpf_prog and used by the verifier
to determine if a program can access a given exclusive map.

The original 64-bit tags are kept, as they are used by users as a short,
possibly colliding program identifier for non-security purposes.

Signed-off-by: KP Singh <kpsingh@kernel.org>
---
 include/linux/bpf.h    |  6 ++++-
 include/linux/filter.h |  6 -----
 kernel/bpf/Kconfig     |  2 +-
 kernel/bpf/core.c      | 50 +++++++-----------------------------------
 4 files changed, 14 insertions(+), 50 deletions(-)

diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index f9cd2164ed23..06cb73df94b3 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -31,6 +31,7 @@
 #include <linux/memcontrol.h>
 #include <linux/cfi.h>
 #include <asm/rqspinlock.h>
+#include <crypto/sha2.h>
 
 struct bpf_verifier_env;
 struct bpf_verifier_log;
@@ -1701,7 +1702,10 @@ struct bpf_prog {
 	enum bpf_attach_type	expected_attach_type; /* For some prog types */
 	u32			len;		/* Number of filter blocks */
 	u32			jited_len;	/* Size of jited insns in bytes */
-	u8			tag[BPF_TAG_SIZE];
+	union {
+		u8 digest[SHA256_DIGEST_SIZE];
+		u8 tag[BPF_TAG_SIZE];
+	};
 	struct bpf_prog_stats __percpu *stats;
 	int __percpu		*active;
 	unsigned int		(*bpf_func)(const void *ctx,
diff --git a/include/linux/filter.h b/include/linux/filter.h
index eca229752cbe..403bbf3320a4 100644
--- a/include/linux/filter.h
+++ b/include/linux/filter.h
@@ -997,12 +997,6 @@ static inline u32 bpf_prog_insn_size(const struct bpf_prog *prog)
 	return prog->len * sizeof(struct bpf_insn);
 }
 
-static inline u32 bpf_prog_tag_scratch_size(const struct bpf_prog *prog)
-{
-	return round_up(bpf_prog_insn_size(prog) +
-			sizeof(__be64) + 1, SHA1_BLOCK_SIZE);
-}
-
 static inline unsigned int bpf_prog_size(unsigned int proglen)
 {
 	return max(sizeof(struct bpf_prog),
diff --git a/kernel/bpf/Kconfig b/kernel/bpf/Kconfig
index 17067dcb4386..eb3de35734f0 100644
--- a/kernel/bpf/Kconfig
+++ b/kernel/bpf/Kconfig
@@ -3,7 +3,7 @@
 # BPF interpreter that, for example, classic socket filters depend on.
 config BPF
 	bool
-	select CRYPTO_LIB_SHA1
+	select CRYPTO_LIB_SHA256
 
 # Used by archs to tell that they support BPF JIT compiler plus which
 # flavour. Only one of the two can be selected for a specific arch since
diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
index 61613785bdd0..b298f3726922 100644
--- a/kernel/bpf/core.c
+++ b/kernel/bpf/core.c
@@ -38,6 +38,7 @@
 #include <linux/bpf_mem_alloc.h>
 #include <linux/memcontrol.h>
 #include <linux/execmem.h>
+#include <crypto/sha2.h>
 
 #include <asm/barrier.h>
 #include <linux/unaligned.h>
@@ -293,28 +294,18 @@ void __bpf_prog_free(struct bpf_prog *fp)
 
 int bpf_prog_calc_tag(struct bpf_prog *fp)
 {
-	const u32 bits_offset = SHA1_BLOCK_SIZE - sizeof(__be64);
-	u32 raw_size = bpf_prog_tag_scratch_size(fp);
-	u32 digest[SHA1_DIGEST_WORDS];
-	u32 ws[SHA1_WORKSPACE_WORDS];
-	u32 i, bsize, psize, blocks;
+	u32 insn_size = bpf_prog_insn_size(fp);
 	struct bpf_insn *dst;
 	bool was_ld_map;
-	u8 *raw, *todo;
-	__be32 *result;
-	__be64 *bits;
+	int i, ret = 0;
 
-	raw = vmalloc(raw_size);
-	if (!raw)
+	dst = vmalloc(insn_size);
+	if (!dst)
 		return -ENOMEM;
 
-	sha1_init(digest);
-	memset(ws, 0, sizeof(ws));
-
 	/* We need to take out the map fd for the digest calculation
 	 * since they are unstable from user space side.
 	 */
-	dst = (void *)raw;
 	for (i = 0, was_ld_map = false; i < fp->len; i++) {
 		dst[i] = fp->insnsi[i];
 		if (!was_ld_map &&
@@ -334,34 +325,9 @@ int bpf_prog_calc_tag(struct bpf_prog *fp)
 			was_ld_map = false;
 		}
 	}
-
-	psize = bpf_prog_insn_size(fp);
-	memset(&raw[psize], 0, raw_size - psize);
-	raw[psize++] = 0x80;
-
-	bsize  = round_up(psize, SHA1_BLOCK_SIZE);
-	blocks = bsize / SHA1_BLOCK_SIZE;
-	todo   = raw;
-	if (bsize - psize >= sizeof(__be64)) {
-		bits = (__be64 *)(todo + bsize - sizeof(__be64));
-	} else {
-		bits = (__be64 *)(todo + bsize + bits_offset);
-		blocks++;
-	}
-	*bits = cpu_to_be64((psize - 1) << 3);
-
-	while (blocks--) {
-		sha1_transform(digest, todo, ws);
-		todo += SHA1_BLOCK_SIZE;
-	}
-
-	result = (__force __be32 *)digest;
-	for (i = 0; i < SHA1_DIGEST_WORDS; i++)
-		result[i] = cpu_to_be32(digest[i]);
-	memcpy(fp->tag, result, sizeof(fp->tag));
-
-	vfree(raw);
-	return 0;
+	sha256((u8 *)dst, insn_size, fp->digest);
+	vfree(dst);
+	return ret;
 }
 
 static int bpf_adj_delta_to_imm(struct bpf_insn *insn, u32 pos, s32 end_old,
-- 
2.43.0


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

* [PATCH v2 02/13] bpf: Implement exclusive map creation
  2025-07-21 21:19 [PATCH v2 00/13] Signed BPF programs KP Singh
  2025-07-21 21:19 ` [PATCH v2 01/13] bpf: Update the bpf_prog_calc_tag to use SHA256 KP Singh
@ 2025-07-21 21:19 ` KP Singh
  2025-07-29 22:59   ` Fan Wu
  2025-07-21 21:19 ` [PATCH v2 03/13] libbpf: Implement SHA256 internal helper KP Singh
                   ` (10 subsequent siblings)
  12 siblings, 1 reply; 33+ messages in thread
From: KP Singh @ 2025-07-21 21:19 UTC (permalink / raw)
  To: bpf, linux-security-module
  Cc: bboscaccy, paul, kys, ast, daniel, andrii, KP Singh

Exclusive maps allow maps to only be accessed by program with a
program with a matching hash which is specified in the excl_prog_hash
attr.

For the signing use-case, this allows the trusted loader program
to load the map and verify the integrity

Signed-off-by: KP Singh <kpsingh@kernel.org>
---
 include/linux/bpf.h            |  1 +
 include/uapi/linux/bpf.h       |  2 ++
 kernel/bpf/syscall.c           | 32 ++++++++++++++++++++++++++++----
 kernel/bpf/verifier.c          |  6 ++++++
 tools/include/uapi/linux/bpf.h |  2 ++
 5 files changed, 39 insertions(+), 4 deletions(-)

diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index 06cb73df94b3..f1b259517bd6 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -311,6 +311,7 @@ struct bpf_map {
 	bool free_after_rcu_gp;
 	atomic64_t sleepable_refcnt;
 	s64 __percpu *elem_count;
+	char *excl_prog_sha;
 };
 
 static inline const char *btf_field_type_name(enum btf_field_type type)
diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h
index 233de8677382..7873ba7b9468 100644
--- a/include/uapi/linux/bpf.h
+++ b/include/uapi/linux/bpf.h
@@ -1522,6 +1522,8 @@ union bpf_attr {
 		 * If provided, map_flags should have BPF_F_TOKEN_FD flag set.
 		 */
 		__s32	map_token_fd;
+		__u32 excl_prog_hash_size;
+		__aligned_u64 excl_prog_hash;
 	};
 
 	struct { /* anonymous struct used by BPF_MAP_*_ELEM and BPF_MAP_FREEZE commands */
diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
index e63039817af3..6ebcb7e4afbb 100644
--- a/kernel/bpf/syscall.c
+++ b/kernel/bpf/syscall.c
@@ -858,6 +858,7 @@ static void bpf_map_free(struct bpf_map *map)
 	 * the free of values or special fields allocated from bpf memory
 	 * allocator.
 	 */
+	kfree(map->excl_prog_sha);
 	migrate_disable();
 	map->ops->map_free(map);
 	migrate_enable();
@@ -1335,9 +1336,9 @@ static bool bpf_net_capable(void)
 	return capable(CAP_NET_ADMIN) || capable(CAP_SYS_ADMIN);
 }
 
-#define BPF_MAP_CREATE_LAST_FIELD map_token_fd
+#define BPF_MAP_CREATE_LAST_FIELD excl_prog_hash
 /* called via syscall */
-static int map_create(union bpf_attr *attr, bool kernel)
+static int map_create(union bpf_attr *attr, bpfptr_t uattr)
 {
 	const struct bpf_map_ops *ops;
 	struct bpf_token *token = NULL;
@@ -1527,7 +1528,30 @@ static int map_create(union bpf_attr *attr, bool kernel)
 			attr->btf_vmlinux_value_type_id;
 	}
 
-	err = security_bpf_map_create(map, attr, token, kernel);
+	if (attr->excl_prog_hash) {
+		bpfptr_t uprog_hash = make_bpfptr(attr->excl_prog_hash, uattr.is_kernel);
+
+		map->excl_prog_sha = kzalloc(SHA256_DIGEST_SIZE, GFP_KERNEL);
+		if (!map->excl_prog_sha) {
+			err = -ENOMEM;
+			goto free_map;
+		}
+
+		if (attr->excl_prog_hash_size != SHA256_DIGEST_SIZE) {
+			err = -EINVAL;
+			goto free_map;
+		}
+
+		if (copy_from_bpfptr(map->excl_prog_sha, uprog_hash,
+				     SHA256_DIGEST_SIZE)) {
+			err = -EFAULT;
+			goto free_map;
+		}
+	} else if (attr->excl_prog_hash_size) {
+		return -EINVAL;
+	}
+
+	err = security_bpf_map_create(map, attr, token, uattr.is_kernel);
 	if (err)
 		goto free_map_sec;
 
@@ -6001,7 +6025,7 @@ static int __sys_bpf(enum bpf_cmd cmd, bpfptr_t uattr, unsigned int size)
 
 	switch (cmd) {
 	case BPF_MAP_CREATE:
-		err = map_create(&attr, uattr.is_kernel);
+		err = map_create(&attr, uattr);
 		break;
 	case BPF_MAP_LOOKUP_ELEM:
 		err = map_lookup_elem(&attr);
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index e2fcea860755..e062f9672da9 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -20321,6 +20321,12 @@ static int check_map_prog_compatibility(struct bpf_verifier_env *env,
 {
 	enum bpf_prog_type prog_type = resolve_prog_type(prog);
 
+	if (map->excl_prog_sha &&
+	    memcmp(map->excl_prog_sha, prog->digest, SHA256_DIGEST_SIZE)) {
+		verbose(env, "program's hash doesn't match map's excl_prog_hash\n");
+		return -EACCES;
+	}
+
 	if (btf_record_has_field(map->record, BPF_LIST_HEAD) ||
 	    btf_record_has_field(map->record, BPF_RB_ROOT)) {
 		if (is_tracing_prog_type(prog_type)) {
diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h
index 233de8677382..7873ba7b9468 100644
--- a/tools/include/uapi/linux/bpf.h
+++ b/tools/include/uapi/linux/bpf.h
@@ -1522,6 +1522,8 @@ union bpf_attr {
 		 * If provided, map_flags should have BPF_F_TOKEN_FD flag set.
 		 */
 		__s32	map_token_fd;
+		__u32 excl_prog_hash_size;
+		__aligned_u64 excl_prog_hash;
 	};
 
 	struct { /* anonymous struct used by BPF_MAP_*_ELEM and BPF_MAP_FREEZE commands */
-- 
2.43.0


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

* [PATCH v2 03/13] libbpf: Implement SHA256 internal helper
  2025-07-21 21:19 [PATCH v2 00/13] Signed BPF programs KP Singh
  2025-07-21 21:19 ` [PATCH v2 01/13] bpf: Update the bpf_prog_calc_tag to use SHA256 KP Singh
  2025-07-21 21:19 ` [PATCH v2 02/13] bpf: Implement exclusive map creation KP Singh
@ 2025-07-21 21:19 ` KP Singh
  2025-07-21 21:19 ` [PATCH v2 04/13] libbpf: Support exclusive map creation KP Singh
                   ` (9 subsequent siblings)
  12 siblings, 0 replies; 33+ messages in thread
From: KP Singh @ 2025-07-21 21:19 UTC (permalink / raw)
  To: bpf, linux-security-module
  Cc: bboscaccy, paul, kys, ast, daniel, andrii, KP Singh

Use AF_ALG sockets to not have libbpf depend on OpenSSL. The helper is
used for the loader generation code to embed the metadata hash in the
loader program and also by the bpf_map__make_exclusive API to calculate
the hash of the program the map is exclusive to.

Signed-off-by: KP Singh <kpsingh@kernel.org>
---
 tools/lib/bpf/libbpf.c          | 59 +++++++++++++++++++++++++++++++++
 tools/lib/bpf/libbpf_internal.h |  4 +++
 2 files changed, 63 insertions(+)

diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
index e067cb5776bd..8b766dfa02bc 100644
--- a/tools/lib/bpf/libbpf.c
+++ b/tools/lib/bpf/libbpf.c
@@ -43,6 +43,9 @@
 #include <sys/vfs.h>
 #include <sys/utsname.h>
 #include <sys/resource.h>
+#include <sys/socket.h>
+#include <linux/if_alg.h>
+#include <linux/socket.h>
 #include <libelf.h>
 #include <gelf.h>
 #include <zlib.h>
@@ -14204,3 +14207,59 @@ void bpf_object__destroy_skeleton(struct bpf_object_skeleton *s)
 	free(s->progs);
 	free(s);
 }
+
+int libbpf_sha256(const void *data, size_t data_sz, void *sha_out, size_t sha_out_sz)
+{
+	struct sockaddr_alg sa = {
+		.salg_family = AF_ALG,
+		.salg_type   = "hash",
+		.salg_name   = "sha256"
+	};
+	int sock_fd = -1;
+	int op_fd = -1;
+	int err = 0;
+
+	if (sha_out_sz != SHA256_DIGEST_LENGTH) {
+		pr_warn("sha_out_sz should be exactly 32 bytes for a SHA256 digest");
+		return libbpf_err(-EINVAL);
+	}
+
+	sock_fd = socket(AF_ALG, SOCK_SEQPACKET, 0);
+	if (sock_fd < 0) {
+		err = -errno;
+		pr_warn("failed to create AF_ALG socket for SHA256: %s\n", errstr(err));
+		return libbpf_err(err);
+	}
+
+	if (bind(sock_fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
+		err = -errno;
+		pr_warn("failed to bind to AF_ALG socket for SHA256: %s\n", errstr(err));
+		goto out;
+	}
+
+	op_fd = accept(sock_fd, NULL, 0);
+	if (op_fd < 0) {
+		err = -errno;
+		pr_warn("failed to accept from AF_ALG socket for SHA256: %s\n", errstr(err));
+		goto out;
+	}
+
+	if (write(op_fd, data, data_sz) != data_sz) {
+		err = -errno;
+		pr_warn("failed to write data to AF_ALG socket for SHA256: %s\n", errstr(err));
+		goto out;
+	}
+
+	if (read(op_fd, sha_out, SHA256_DIGEST_LENGTH) != SHA256_DIGEST_LENGTH) {
+		err = -errno;
+		pr_warn("failed to read SHA256 from AF_ALG socket: %s\n", errstr(err));
+		goto out;
+	}
+
+out:
+	if (op_fd >= 0)
+		close(op_fd);
+	if (sock_fd >= 0)
+		close(sock_fd);
+	return libbpf_err(err);
+}
diff --git a/tools/lib/bpf/libbpf_internal.h b/tools/lib/bpf/libbpf_internal.h
index 477a3b3389a0..8a055de0d324 100644
--- a/tools/lib/bpf/libbpf_internal.h
+++ b/tools/lib/bpf/libbpf_internal.h
@@ -736,4 +736,8 @@ int elf_resolve_pattern_offsets(const char *binary_path, const char *pattern,
 
 int probe_fd(int fd);
 
+#define SHA256_DIGEST_LENGTH 32
+#define SHA256_DWORD_SIZE SHA256_DIGEST_LENGTH / sizeof(__u64)
+
+int libbpf_sha256(const void *data, size_t data_sz, void *sha_out, size_t sha_out_sz);
 #endif /* __LIBBPF_LIBBPF_INTERNAL_H */
-- 
2.43.0


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

* [PATCH v2 04/13] libbpf: Support exclusive map creation
  2025-07-21 21:19 [PATCH v2 00/13] Signed BPF programs KP Singh
                   ` (2 preceding siblings ...)
  2025-07-21 21:19 ` [PATCH v2 03/13] libbpf: Implement SHA256 internal helper KP Singh
@ 2025-07-21 21:19 ` KP Singh
  2025-07-29  2:25   ` Alexei Starovoitov
  2025-07-21 21:19 ` [PATCH v2 05/13] selftests/bpf: Add tests for exclusive maps KP Singh
                   ` (8 subsequent siblings)
  12 siblings, 1 reply; 33+ messages in thread
From: KP Singh @ 2025-07-21 21:19 UTC (permalink / raw)
  To: bpf, linux-security-module
  Cc: bboscaccy, paul, kys, ast, daniel, andrii, KP Singh

Implement setters and getters that allow map to be registers as
exclusive to the specified program. The registration should be done
before the exclusive program is loaded.

Signed-off-by: KP Singh <kpsingh@kernel.org>
---
 tools/lib/bpf/bpf.c      |  4 ++-
 tools/lib/bpf/bpf.h      |  4 ++-
 tools/lib/bpf/libbpf.c   | 66 ++++++++++++++++++++++++++++++++++++++++
 tools/lib/bpf/libbpf.h   | 18 +++++++++++
 tools/lib/bpf/libbpf.map |  2 ++
 5 files changed, 92 insertions(+), 2 deletions(-)

diff --git a/tools/lib/bpf/bpf.c b/tools/lib/bpf/bpf.c
index ab40dbf9f020..6a08a1559237 100644
--- a/tools/lib/bpf/bpf.c
+++ b/tools/lib/bpf/bpf.c
@@ -172,7 +172,7 @@ int bpf_map_create(enum bpf_map_type map_type,
 		   __u32 max_entries,
 		   const struct bpf_map_create_opts *opts)
 {
-	const size_t attr_sz = offsetofend(union bpf_attr, map_token_fd);
+	const size_t attr_sz = offsetofend(union bpf_attr, excl_prog_hash);
 	union bpf_attr attr;
 	int fd;
 
@@ -203,6 +203,8 @@ int bpf_map_create(enum bpf_map_type map_type,
 	attr.map_ifindex = OPTS_GET(opts, map_ifindex, 0);
 
 	attr.map_token_fd = OPTS_GET(opts, token_fd, 0);
+	attr.excl_prog_hash = ptr_to_u64(OPTS_GET(opts, excl_prog_hash, NULL));
+	attr.excl_prog_hash_size = OPTS_GET(opts, excl_prog_hash_size, 0);
 
 	fd = sys_bpf_fd(BPF_MAP_CREATE, &attr, attr_sz);
 	return libbpf_err_errno(fd);
diff --git a/tools/lib/bpf/bpf.h b/tools/lib/bpf/bpf.h
index 7252150e7ad3..675a09bb7d2f 100644
--- a/tools/lib/bpf/bpf.h
+++ b/tools/lib/bpf/bpf.h
@@ -54,9 +54,11 @@ struct bpf_map_create_opts {
 	__s32 value_type_btf_obj_fd;
 
 	__u32 token_fd;
+	__u32 excl_prog_hash_size;
+	const void *excl_prog_hash;
 	size_t :0;
 };
-#define bpf_map_create_opts__last_field token_fd
+#define bpf_map_create_opts__last_field excl_prog_hash
 
 LIBBPF_API int bpf_map_create(enum bpf_map_type map_type,
 			      const char *map_name,
diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
index 8b766dfa02bc..be142f96df66 100644
--- a/tools/lib/bpf/libbpf.c
+++ b/tools/lib/bpf/libbpf.c
@@ -499,6 +499,7 @@ struct bpf_program {
 	__u32 line_info_rec_size;
 	__u32 line_info_cnt;
 	__u32 prog_flags;
+	__u8  hash[SHA256_DIGEST_LENGTH];
 };
 
 struct bpf_struct_ops {
@@ -578,6 +579,7 @@ struct bpf_map {
 	bool autocreate;
 	bool autoattach;
 	__u64 map_extra;
+	struct bpf_program *excl_prog;
 };
 
 enum extern_type {
@@ -4488,6 +4490,43 @@ bpf_object__section_to_libbpf_map_type(const struct bpf_object *obj, int shndx)
 	}
 }
 
+static int bpf_program__compute_hash(struct bpf_program *prog)
+{
+	struct bpf_insn *purged;
+	int i, err;
+
+	purged = calloc(1, BPF_INSN_SZ * prog->insns_cnt);
+	if (!purged)
+		return -ENOMEM;
+
+	/* If relocations have been done, the map_fd needs to be
+	 * discarded for the digest calculation.
+	 */
+	for (i = 0; i < prog->insns_cnt; i++) {
+		purged[i] = prog->insns[i];
+		if (purged[i].code == (BPF_LD | BPF_IMM | BPF_DW) &&
+		    (purged[i].src_reg == BPF_PSEUDO_MAP_FD ||
+		     purged[i].src_reg == BPF_PSEUDO_MAP_VALUE)) {
+			purged[i].imm = 0;
+			i++;
+			if (i >= prog->insns_cnt ||
+			    prog->insns[i].code != 0 ||
+			    prog->insns[i].dst_reg != 0 ||
+			    prog->insns[i].src_reg != 0 ||
+			    prog->insns[i].off != 0) {
+				err = -EINVAL;
+				goto out;
+			}
+			purged[i] = prog->insns[i];
+			purged[i].imm = 0;
+		}
+	}
+	err = libbpf_sha256(purged, prog->insns_cnt * sizeof(struct bpf_insn), prog->hash, SHA256_DIGEST_LENGTH);
+out:
+	free(purged);
+	return err;
+}
+
 static int bpf_program__record_reloc(struct bpf_program *prog,
 				     struct reloc_desc *reloc_desc,
 				     __u32 insn_idx, const char *sym_name,
@@ -5227,6 +5266,18 @@ static int bpf_object__create_map(struct bpf_object *obj, struct bpf_map *map, b
 	create_attr.token_fd = obj->token_fd;
 	if (obj->token_fd)
 		create_attr.map_flags |= BPF_F_TOKEN_FD;
+	if (map->excl_prog) {
+		if (map->excl_prog->obj->state == OBJ_LOADED) {
+			pr_warn("exclusive program already loaded\n");
+			return libbpf_err(-EINVAL);
+		}
+		err = bpf_program__compute_hash(map->excl_prog);
+		if (err)
+			return err;
+
+		create_attr.excl_prog_hash = map->excl_prog->hash;
+		create_attr.excl_prog_hash_size = SHA256_DIGEST_LENGTH;
+	}
 
 	if (bpf_map__is_struct_ops(map)) {
 		create_attr.btf_vmlinux_value_type_id = map->btf_vmlinux_value_type_id;
@@ -10517,6 +10568,21 @@ int bpf_map__set_inner_map_fd(struct bpf_map *map, int fd)
 	return 0;
 }
 
+int bpf_map__set_exclusive_program(struct bpf_map *map, struct bpf_program *prog)
+{
+	if (map_is_created(map)) {
+		pr_warn("exclusive programs must be set before map creation\n");
+		return libbpf_err(-EINVAL);
+	}
+	map->excl_prog = prog;
+	return 0;
+}
+
+struct bpf_program *bpf_map__get_exclusive_program(struct bpf_map *map)
+{
+	return map->excl_prog;
+}
+
 static struct bpf_map *
 __bpf_map__iter(const struct bpf_map *m, const struct bpf_object *obj, int i)
 {
diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h
index d1cf813a057b..60552fa5401d 100644
--- a/tools/lib/bpf/libbpf.h
+++ b/tools/lib/bpf/libbpf.h
@@ -1264,7 +1264,25 @@ LIBBPF_API int bpf_map__lookup_and_delete_elem(const struct bpf_map *map,
  */
 LIBBPF_API int bpf_map__get_next_key(const struct bpf_map *map,
 				     const void *cur_key, void *next_key, size_t key_sz);
+/**
+ * @brief **bpf_map__set_exclusive_program()** sets map to be exclusive to the
+ * to the specified program. The program must not be loaded yet.
+ * @param map BPF map to make exclusive.
+ * @param prog BPF program to be the exclusive user of the map.
+ * @return 0 on success; a negative error code otherwise.
+ *
+ * Once a map is made exclusive, only the specified program can access its
+ * contents.
+ */
+LIBBPF_API int bpf_map__set_exclusive_program(struct bpf_map *map, struct bpf_program *prog);
 
+/**
+ * @brief **bpf_map__get_exclusive_program()** returns the exclusive program
+ * that is registered with the map (if any).
+ * @param map BPF map to which the exclusive program is registered.
+ * @return the registered exclusive program.
+ */
+LIBBPF_API struct bpf_program *bpf_map__get_exclusive_program(struct bpf_map *map);
 struct bpf_xdp_set_link_opts {
 	size_t sz;
 	int old_fd;
diff --git a/tools/lib/bpf/libbpf.map b/tools/lib/bpf/libbpf.map
index d7bd463e7017..a5c5d0f2db5c 100644
--- a/tools/lib/bpf/libbpf.map
+++ b/tools/lib/bpf/libbpf.map
@@ -436,6 +436,8 @@ LIBBPF_1.6.0 {
 		bpf_linker__add_buf;
 		bpf_linker__add_fd;
 		bpf_linker__new_fd;
+		bpf_map__set_exclusive_program;
+		bpf_map__get_exclusive_program;
 		bpf_object__prepare;
 		bpf_prog_stream_read;
 		bpf_program__attach_cgroup_opts;
-- 
2.43.0


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

* [PATCH v2 05/13] selftests/bpf: Add tests for exclusive maps
  2025-07-21 21:19 [PATCH v2 00/13] Signed BPF programs KP Singh
                   ` (3 preceding siblings ...)
  2025-07-21 21:19 ` [PATCH v2 04/13] libbpf: Support exclusive map creation KP Singh
@ 2025-07-21 21:19 ` KP Singh
  2025-07-21 21:19 ` [PATCH v2 06/13] bpf: Return hashes of maps in BPF_OBJ_GET_INFO_BY_FD KP Singh
                   ` (7 subsequent siblings)
  12 siblings, 0 replies; 33+ messages in thread
From: KP Singh @ 2025-07-21 21:19 UTC (permalink / raw)
  To: bpf, linux-security-module
  Cc: bboscaccy, paul, kys, ast, daniel, andrii, KP Singh

Check if access is denied to another program for an exclusive map

Signed-off-by: KP Singh <kpsingh@kernel.org>
---
 .../selftests/bpf/prog_tests/map_excl.c       | 56 +++++++++++++++++++
 tools/testing/selftests/bpf/progs/map_excl.c  | 34 +++++++++++
 2 files changed, 90 insertions(+)
 create mode 100644 tools/testing/selftests/bpf/prog_tests/map_excl.c
 create mode 100644 tools/testing/selftests/bpf/progs/map_excl.c

diff --git a/tools/testing/selftests/bpf/prog_tests/map_excl.c b/tools/testing/selftests/bpf/prog_tests/map_excl.c
new file mode 100644
index 000000000000..7a49917c691a
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/map_excl.c
@@ -0,0 +1,56 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2023. Huawei Technologies Co., Ltd */
+#define _GNU_SOURCE
+#include <unistd.h>
+#include <sys/syscall.h>
+#include <test_progs.h>
+#include <bpf/btf.h>
+
+#include "map_excl.skel.h"
+
+static void test_map_excl_allowed(void)
+{
+	struct map_excl *skel = map_excl__open();
+	int err;
+
+	err = bpf_map__set_exclusive_program(skel->maps.excl_map, skel->progs.should_have_access);
+	if (!ASSERT_OK(err, "bpf_map__set_exclusive_program"))
+		goto out;
+
+	bpf_program__set_autoload(skel->progs.should_have_access, true);
+	bpf_program__set_autoload(skel->progs.should_not_have_access, false);
+
+	err = map_excl__load(skel);
+	ASSERT_OK(err, "map_excl__load");
+out:
+	map_excl__destroy(skel);
+}
+
+static void test_map_excl_denied(void)
+{
+	struct map_excl *skel = map_excl__open();
+	int err;
+
+	err = bpf_map__set_exclusive_program(skel->maps.excl_map, skel->progs.should_have_access);
+	if (!ASSERT_OK(err, "bpf_map__make_exclusive"))
+		goto out;
+
+	bpf_program__set_autoload(skel->progs.should_have_access, false);
+	bpf_program__set_autoload(skel->progs.should_not_have_access, true);
+
+	err = map_excl__load(skel);
+	ASSERT_EQ(err, -EACCES, "exclusive map Paccess not denied\n");
+out:
+	map_excl__destroy(skel);
+
+}
+
+void test_map_excl(void)
+{
+	start_libbpf_log_capture();
+	if (test__start_subtest("map_excl_allowed"))
+		test_map_excl_allowed();
+	stop_libbpf_log_capture();
+	if (test__start_subtest("map_excl_denied"))
+		test_map_excl_denied();
+}
diff --git a/tools/testing/selftests/bpf/progs/map_excl.c b/tools/testing/selftests/bpf/progs/map_excl.c
new file mode 100644
index 000000000000..26c32b4f2ce0
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/map_excl.c
@@ -0,0 +1,34 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2023. Huawei Technologies Co., Ltd */
+#include <linux/bpf.h>
+#include <time.h>
+#include <bpf/bpf_helpers.h>
+
+#include "bpf_misc.h"
+
+struct {
+	__uint(type, BPF_MAP_TYPE_ARRAY);
+	__type(key, __u32);
+	__type(value, __u32);
+	__uint(max_entries, 1);
+} excl_map SEC(".maps");
+
+char _license[] SEC("license") = "GPL";
+
+SEC("?fentry.s/" SYS_PREFIX "sys_getpgid")
+int should_have_access(void *ctx)
+{
+	int key = 0, value = 0xdeadbeef;
+
+	bpf_map_update_elem(&excl_map, &key, &value, 0);
+	return 0;
+}
+
+SEC("?fentry.s/" SYS_PREFIX "sys_getpgid")
+int should_not_have_access(void *ctx)
+{
+	int key = 0, value = 0xdeadbeef;
+
+	bpf_map_update_elem(&excl_map, &key, &value, 0);
+	return 0;
+}
-- 
2.43.0


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

* [PATCH v2 06/13] bpf: Return hashes of maps in BPF_OBJ_GET_INFO_BY_FD
  2025-07-21 21:19 [PATCH v2 00/13] Signed BPF programs KP Singh
                   ` (4 preceding siblings ...)
  2025-07-21 21:19 ` [PATCH v2 05/13] selftests/bpf: Add tests for exclusive maps KP Singh
@ 2025-07-21 21:19 ` KP Singh
  2025-07-21 21:19 ` [PATCH v2 07/13] bpf: Move the signature kfuncs to helpers.c KP Singh
                   ` (6 subsequent siblings)
  12 siblings, 0 replies; 33+ messages in thread
From: KP Singh @ 2025-07-21 21:19 UTC (permalink / raw)
  To: bpf, linux-security-module
  Cc: bboscaccy, paul, kys, ast, daniel, andrii, KP Singh

Currently only array maps are supported, but the implementation can be
extended for other maps and objects. The hash is memoized only for
exclusive and frozen maps as their content is stable until the exclusive
program modifies the map.

This is required  for BPF signing, enabling a trusted loader program to
verify a map's integrity. The loader retrieves
the map's runtime hash from the kernel and compares it against an
expected hash computed at build time.

Signed-off-by: KP Singh <kpsingh@kernel.org>
---
 include/linux/bpf.h                           |  3 +++
 include/uapi/linux/bpf.h                      |  2 ++
 kernel/bpf/arraymap.c                         | 13 +++++++++++
 kernel/bpf/syscall.c                          | 23 +++++++++++++++++++
 tools/include/uapi/linux/bpf.h                |  2 ++
 .../selftests/bpf/progs/verifier_map_ptr.c    |  7 ++++--
 6 files changed, 48 insertions(+), 2 deletions(-)

diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index f1b259517bd6..a0af63d56985 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -7,6 +7,7 @@
 #include <uapi/linux/bpf.h>
 #include <uapi/linux/filter.h>
 
+#include <crypto/sha2.h>
 #include <linux/workqueue.h>
 #include <linux/file.h>
 #include <linux/percpu.h>
@@ -110,6 +111,7 @@ struct bpf_map_ops {
 	long (*map_pop_elem)(struct bpf_map *map, void *value);
 	long (*map_peek_elem)(struct bpf_map *map, void *value);
 	void *(*map_lookup_percpu_elem)(struct bpf_map *map, void *key, u32 cpu);
+	int (*map_get_hash)(struct bpf_map *map, u32 hash_buf_size, void *hash_buf);
 
 	/* funcs called by prog_array and perf_event_array map */
 	void *(*map_fd_get_ptr)(struct bpf_map *map, struct file *map_file,
@@ -262,6 +264,7 @@ struct bpf_list_node_kern {
 } __attribute__((aligned(8)));
 
 struct bpf_map {
+	u8 sha[SHA256_DIGEST_SIZE];
 	const struct bpf_map_ops *ops;
 	struct bpf_map *inner_map_meta;
 #ifdef CONFIG_SECURITY
diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h
index 7873ba7b9468..fd3b895ebebf 100644
--- a/include/uapi/linux/bpf.h
+++ b/include/uapi/linux/bpf.h
@@ -6668,6 +6668,8 @@ struct bpf_map_info {
 	__u32 btf_value_type_id;
 	__u32 btf_vmlinux_id;
 	__u64 map_extra;
+	__aligned_u64 hash;
+	__u32 hash_size;
 } __attribute__((aligned(8)));
 
 struct bpf_btf_info {
diff --git a/kernel/bpf/arraymap.c b/kernel/bpf/arraymap.c
index 3d080916faf9..26d5dda989bc 100644
--- a/kernel/bpf/arraymap.c
+++ b/kernel/bpf/arraymap.c
@@ -12,6 +12,7 @@
 #include <uapi/linux/btf.h>
 #include <linux/rcupdate_trace.h>
 #include <linux/btf_ids.h>
+#include <crypto/sha2.h>
 
 #include "map_in_map.h"
 
@@ -174,6 +175,17 @@ static void *array_map_lookup_elem(struct bpf_map *map, void *key)
 	return array->value + (u64)array->elem_size * (index & array->index_mask);
 }
 
+static int array_map_get_hash(struct bpf_map *map, u32 hash_buf_size,
+			       void *hash_buf)
+{
+	struct bpf_array *array = container_of(map, struct bpf_array, map);
+
+	sha256(array->value, (u64)array->elem_size * array->map.max_entries,
+	       hash_buf);
+	memcpy(array->map.sha, hash_buf, sizeof(array->map.sha));
+	return 0;
+}
+
 static int array_map_direct_value_addr(const struct bpf_map *map, u64 *imm,
 				       u32 off)
 {
@@ -800,6 +812,7 @@ const struct bpf_map_ops array_map_ops = {
 	.map_mem_usage = array_map_mem_usage,
 	.map_btf_id = &array_map_btf_ids[0],
 	.iter_seq_info = &iter_seq_info,
+	.map_get_hash = &array_map_get_hash,
 };
 
 const struct bpf_map_ops percpu_array_map_ops = {
diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
index 6ebcb7e4afbb..22fda92ab7ce 100644
--- a/kernel/bpf/syscall.c
+++ b/kernel/bpf/syscall.c
@@ -1,6 +1,7 @@
 // SPDX-License-Identifier: GPL-2.0-only
 /* Copyright (c) 2011-2014 PLUMgrid, http://plumgrid.com
  */
+#include <crypto/sha2.h>
 #include <linux/bpf.h>
 #include <linux/bpf-cgroup.h>
 #include <linux/bpf_trace.h>
@@ -5178,6 +5179,9 @@ static int bpf_map_get_info_by_fd(struct file *file,
 	info_len = min_t(u32, sizeof(info), info_len);
 
 	memset(&info, 0, sizeof(info));
+	if (copy_from_user(&info, uinfo, info_len))
+		return -EFAULT;
+
 	info.type = map->map_type;
 	info.id = map->id;
 	info.key_size = map->key_size;
@@ -5202,6 +5206,25 @@ static int bpf_map_get_info_by_fd(struct file *file,
 			return err;
 	}
 
+	if (info.hash) {
+		char __user *uhash = u64_to_user_ptr(info.hash);
+
+		if (!map->ops->map_get_hash)
+			return -EINVAL;
+
+		if (info.hash_size != SHA256_DIGEST_SIZE)
+			return -EINVAL;
+
+		err = map->ops->map_get_hash(map, SHA256_DIGEST_SIZE, map->sha);
+		if (err != 0)
+			return err;
+
+		if (copy_to_user(uhash, map->sha, SHA256_DIGEST_SIZE) != 0)
+			return -EFAULT;
+	} else if (info.hash_size) {
+		return -EINVAL;
+	}
+
 	if (copy_to_user(uinfo, &info, info_len) ||
 	    put_user(info_len, &uattr->info.info_len))
 		return -EFAULT;
diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h
index 7873ba7b9468..fd3b895ebebf 100644
--- a/tools/include/uapi/linux/bpf.h
+++ b/tools/include/uapi/linux/bpf.h
@@ -6668,6 +6668,8 @@ struct bpf_map_info {
 	__u32 btf_value_type_id;
 	__u32 btf_vmlinux_id;
 	__u64 map_extra;
+	__aligned_u64 hash;
+	__u32 hash_size;
 } __attribute__((aligned(8)));
 
 struct bpf_btf_info {
diff --git a/tools/testing/selftests/bpf/progs/verifier_map_ptr.c b/tools/testing/selftests/bpf/progs/verifier_map_ptr.c
index 11a079145966..e2767d27d8aa 100644
--- a/tools/testing/selftests/bpf/progs/verifier_map_ptr.c
+++ b/tools/testing/selftests/bpf/progs/verifier_map_ptr.c
@@ -70,10 +70,13 @@ __naked void bpf_map_ptr_write_rejected(void)
 	: __clobber_all);
 }
 
+/* The first element of struct bpf_map is a SHA256 hash of 32 bytes, accessing
+ * into this array is valid. The opts field is now at offset 33.
+ */
 SEC("socket")
 __description("bpf_map_ptr: read non-existent field rejected")
 __failure
-__msg("cannot access ptr member ops with moff 0 in struct bpf_map with off 1 size 4")
+__msg("cannot access ptr member ops with moff 32 in struct bpf_map with off 33 size 4")
 __failure_unpriv
 __msg_unpriv("access is allowed only to CAP_PERFMON and CAP_SYS_ADMIN")
 __flag(BPF_F_ANY_ALIGNMENT)
@@ -82,7 +85,7 @@ __naked void read_non_existent_field_rejected(void)
 	asm volatile ("					\
 	r6 = 0;						\
 	r1 = %[map_array_48b] ll;			\
-	r6 = *(u32*)(r1 + 1);				\
+	r6 = *(u32*)(r1 + 33);				\
 	r0 = 1;						\
 	exit;						\
 "	:
-- 
2.43.0


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

* [PATCH v2 07/13] bpf: Move the signature kfuncs to helpers.c
  2025-07-21 21:19 [PATCH v2 00/13] Signed BPF programs KP Singh
                   ` (5 preceding siblings ...)
  2025-07-21 21:19 ` [PATCH v2 06/13] bpf: Return hashes of maps in BPF_OBJ_GET_INFO_BY_FD KP Singh
@ 2025-07-21 21:19 ` KP Singh
  2025-07-23 16:47   ` James Bottomley
  2025-07-21 21:19 ` [PATCH v2 08/13] bpf: Implement signature verification for BPF programs KP Singh
                   ` (5 subsequent siblings)
  12 siblings, 1 reply; 33+ messages in thread
From: KP Singh @ 2025-07-21 21:19 UTC (permalink / raw)
  To: bpf, linux-security-module
  Cc: bboscaccy, paul, kys, ast, daniel, andrii, KP Singh

No functional changes, except for the addition of the headers for the
kfuncs so that they can be used for signature verification.

Signed-off-by: KP Singh <kpsingh@kernel.org>
---
 include/linux/bpf.h      |  32 +++++++
 kernel/bpf/helpers.c     | 166 +++++++++++++++++++++++++++++++++++
 kernel/trace/bpf_trace.c | 183 ---------------------------------------
 3 files changed, 198 insertions(+), 183 deletions(-)

diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index a0af63d56985..52f918403935 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -3391,6 +3391,38 @@ static inline int bpf_fd_reuseport_array_update_elem(struct bpf_map *map,
 #endif /* CONFIG_BPF_SYSCALL */
 #endif /* defined(CONFIG_INET) && defined(CONFIG_BPF_SYSCALL) */
 
+#if defined(CONFIG_KEYS) && defined(CONFIG_BPF_SYSCALL)
+
+struct bpf_key *bpf_lookup_user_key(s32 serial, u64 flags);
+struct bpf_key *bpf_lookup_system_key(u64 id);
+void bpf_key_put(struct bpf_key *bkey);
+int bpf_verify_pkcs7_signature(struct bpf_dynptr *data_p,
+			       struct bpf_dynptr *sig_p,
+			       struct bpf_key *trusted_keyring);
+
+#else
+static inline struct bpf_key *bpf_lookup_user_key(u32 serial, u64 flags)
+{
+	return NULL;
+}
+
+static inline struct bpf_key *bpf_lookup_system_key(u64 id)
+{
+	return NULL;
+}
+
+static inline void bpf_key_put(struct bpf_key *bkey)
+{
+}
+
+static inline int bpf_verify_pkcs7_signature(struct bpf_dynptr *data_p,
+					     struct bpf_dynptr *sig_p,
+					     struct bpf_key *trusted_keyring)
+{
+	return -EOPNOTSUPP;
+}
+#endif /* defined(CONFIG_KEYS) && defined(CONFIG_BPF_SYSCALL) */
+
 /* verifier prototypes for helper functions called from eBPF programs */
 extern const struct bpf_func_proto bpf_map_lookup_elem_proto;
 extern const struct bpf_func_proto bpf_map_update_elem_proto;
diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
index 6b4877e85a68..a052bbbcbfc5 100644
--- a/kernel/bpf/helpers.c
+++ b/kernel/bpf/helpers.c
@@ -25,6 +25,7 @@
 #include <linux/kasan.h>
 #include <linux/bpf_verifier.h>
 #include <linux/uaccess.h>
+#include <linux/verification.h>
 
 #include "../../lib/kstrtox.h"
 
@@ -3702,6 +3703,163 @@ __bpf_kfunc int bpf_strstr(const char *s1__ign, const char *s2__ign)
 {
 	return bpf_strnstr(s1__ign, s2__ign, XATTR_SIZE_MAX);
 }
+#ifdef CONFIG_KEYS
+/**
+ * bpf_lookup_user_key - lookup a key by its serial
+ * @serial: key handle serial number
+ * @flags: lookup-specific flags
+ *
+ * Search a key with a given *serial* and the provided *flags*.
+ * If found, increment the reference count of the key by one, and
+ * return it in the bpf_key structure.
+ *
+ * The bpf_key structure must be passed to bpf_key_put() when done
+ * with it, so that the key reference count is decremented and the
+ * bpf_key structure is freed.
+ *
+ * Permission checks are deferred to the time the key is used by
+ * one of the available key-specific kfuncs.
+ *
+ * Set *flags* with KEY_LOOKUP_CREATE, to attempt creating a requested
+ * special keyring (e.g. session keyring), if it doesn't yet exist.
+ * Set *flags* with KEY_LOOKUP_PARTIAL, to lookup a key without waiting
+ * for the key construction, and to retrieve uninstantiated keys (keys
+ * without data attached to them).
+ *
+ * Return: a bpf_key pointer with a valid key pointer if the key is found, a
+ *         NULL pointer otherwise.
+ */
+__bpf_kfunc struct bpf_key *bpf_lookup_user_key(s32 serial, u64 flags)
+{
+	key_ref_t key_ref;
+	struct bpf_key *bkey;
+
+	if (flags & ~KEY_LOOKUP_ALL)
+		return NULL;
+
+	/*
+	 * Permission check is deferred until the key is used, as the
+	 * intent of the caller is unknown here.
+	 */
+	key_ref = lookup_user_key(serial, flags, KEY_DEFER_PERM_CHECK);
+	if (IS_ERR(key_ref))
+		return NULL;
+
+	bkey = kmalloc(sizeof(*bkey), GFP_KERNEL);
+	if (!bkey) {
+		key_put(key_ref_to_ptr(key_ref));
+		return NULL;
+	}
+
+	bkey->key = key_ref_to_ptr(key_ref);
+	bkey->has_ref = true;
+
+	return bkey;
+}
+
+/**
+ * bpf_lookup_system_key - lookup a key by a system-defined ID
+ * @id: key ID
+ *
+ * Obtain a bpf_key structure with a key pointer set to the passed key ID.
+ * The key pointer is marked as invalid, to prevent bpf_key_put() from
+ * attempting to decrement the key reference count on that pointer. The key
+ * pointer set in such way is currently understood only by
+ * verify_pkcs7_signature().
+ *
+ * Set *id* to one of the values defined in include/linux/verification.h:
+ * 0 for the primary keyring (immutable keyring of system keys);
+ * VERIFY_USE_SECONDARY_KEYRING for both the primary and secondary keyring
+ * (where keys can be added only if they are vouched for by existing keys
+ * in those keyrings); VERIFY_USE_PLATFORM_KEYRING for the platform
+ * keyring (primarily used by the integrity subsystem to verify a kexec'ed
+ * kerned image and, possibly, the initramfs signature).
+ *
+ * Return: a bpf_key pointer with an invalid key pointer set from the
+ *         pre-determined ID on success, a NULL pointer otherwise
+ */
+__bpf_kfunc struct bpf_key *bpf_lookup_system_key(u64 id)
+{
+	struct bpf_key *bkey;
+
+	if (system_keyring_id_check(id) < 0)
+		return NULL;
+
+	bkey = kmalloc(sizeof(*bkey), GFP_ATOMIC);
+	if (!bkey)
+		return NULL;
+
+	bkey->key = (struct key *)(unsigned long)id;
+	bkey->has_ref = false;
+
+	return bkey;
+}
+
+/**
+ * bpf_key_put - decrement key reference count if key is valid and free bpf_key
+ * @bkey: bpf_key structure
+ *
+ * Decrement the reference count of the key inside *bkey*, if the pointer
+ * is valid, and free *bkey*.
+ */
+__bpf_kfunc void bpf_key_put(struct bpf_key *bkey)
+{
+	if (bkey->has_ref)
+		key_put(bkey->key);
+
+	kfree(bkey);
+}
+
+/**
+ * bpf_verify_pkcs7_signature - verify a PKCS#7 signature
+ * @data_p: data to verify
+ * @sig_p: signature of the data
+ * @trusted_keyring: keyring with keys trusted for signature verification
+ *
+ * Verify the PKCS#7 signature *sig_ptr* against the supplied *data_ptr*
+ * with keys in a keyring referenced by *trusted_keyring*.
+ *
+ * Return: 0 on success, a negative value on error.
+ */
+__bpf_kfunc int bpf_verify_pkcs7_signature(struct bpf_dynptr *data_p,
+			       struct bpf_dynptr *sig_p,
+			       struct bpf_key *trusted_keyring)
+{
+#ifdef CONFIG_SYSTEM_DATA_VERIFICATION
+	struct bpf_dynptr_kern *data_ptr = (struct bpf_dynptr_kern *)data_p;
+	struct bpf_dynptr_kern *sig_ptr = (struct bpf_dynptr_kern *)sig_p;
+	const void *data, *sig;
+	u32 data_len, sig_len;
+	int ret;
+
+	if (trusted_keyring->has_ref) {
+		/*
+		 * Do the permission check deferred in bpf_lookup_user_key().
+		 * See bpf_lookup_user_key() for more details.
+		 *
+		 * A call to key_task_permission() here would be redundant, as
+		 * it is already done by keyring_search() called by
+		 * find_asymmetric_key().
+		 */
+		ret = key_validate(trusted_keyring->key);
+		if (ret < 0)
+			return ret;
+	}
+
+	data_len = __bpf_dynptr_size(data_ptr);
+	data = __bpf_dynptr_data(data_ptr, data_len);
+	sig_len = __bpf_dynptr_size(sig_ptr);
+	sig = __bpf_dynptr_data(sig_ptr, sig_len);
+
+	return verify_pkcs7_signature(data, data_len, sig, sig_len,
+				      trusted_keyring->key,
+				      VERIFYING_UNSPECIFIED_SIGNATURE, NULL,
+				      NULL);
+#else
+	return -EOPNOTSUPP;
+#endif /* CONFIG_SYSTEM_DATA_VERIFICATION */
+}
+#endif /* CONFIG_KEYS */
 
 __bpf_kfunc_end_defs();
 
@@ -3743,6 +3901,14 @@ BTF_ID_FLAGS(func, bpf_throw)
 #ifdef CONFIG_BPF_EVENTS
 BTF_ID_FLAGS(func, bpf_send_signal_task, KF_TRUSTED_ARGS)
 #endif
+#ifdef CONFIG_KEYS
+BTF_ID_FLAGS(func, bpf_lookup_user_key, KF_ACQUIRE | KF_RET_NULL | KF_SLEEPABLE)
+BTF_ID_FLAGS(func, bpf_lookup_system_key, KF_ACQUIRE | KF_RET_NULL)
+BTF_ID_FLAGS(func, bpf_key_put, KF_RELEASE)
+#ifdef CONFIG_SYSTEM_DATA_VERIFICATION
+BTF_ID_FLAGS(func, bpf_verify_pkcs7_signature, KF_SLEEPABLE)
+#endif
+#endif
 BTF_KFUNCS_END(generic_btf_ids)
 
 static const struct btf_kfunc_id_set generic_kfunc_set = {
diff --git a/kernel/trace/bpf_trace.c b/kernel/trace/bpf_trace.c
index 3ae52978cae6..02c3f610420d 100644
--- a/kernel/trace/bpf_trace.c
+++ b/kernel/trace/bpf_trace.c
@@ -22,7 +22,6 @@
 #include <linux/bsearch.h>
 #include <linux/sort.h>
 #include <linux/key.h>
-#include <linux/verification.h>
 #include <linux/namei.h>
 
 #include <net/bpf_sk_storage.h>
@@ -1241,188 +1240,6 @@ static const struct bpf_func_proto bpf_get_func_arg_cnt_proto = {
 	.arg1_type	= ARG_PTR_TO_CTX,
 };
 
-#ifdef CONFIG_KEYS
-__bpf_kfunc_start_defs();
-
-/**
- * bpf_lookup_user_key - lookup a key by its serial
- * @serial: key handle serial number
- * @flags: lookup-specific flags
- *
- * Search a key with a given *serial* and the provided *flags*.
- * If found, increment the reference count of the key by one, and
- * return it in the bpf_key structure.
- *
- * The bpf_key structure must be passed to bpf_key_put() when done
- * with it, so that the key reference count is decremented and the
- * bpf_key structure is freed.
- *
- * Permission checks are deferred to the time the key is used by
- * one of the available key-specific kfuncs.
- *
- * Set *flags* with KEY_LOOKUP_CREATE, to attempt creating a requested
- * special keyring (e.g. session keyring), if it doesn't yet exist.
- * Set *flags* with KEY_LOOKUP_PARTIAL, to lookup a key without waiting
- * for the key construction, and to retrieve uninstantiated keys (keys
- * without data attached to them).
- *
- * Return: a bpf_key pointer with a valid key pointer if the key is found, a
- *         NULL pointer otherwise.
- */
-__bpf_kfunc struct bpf_key *bpf_lookup_user_key(s32 serial, u64 flags)
-{
-	key_ref_t key_ref;
-	struct bpf_key *bkey;
-
-	if (flags & ~KEY_LOOKUP_ALL)
-		return NULL;
-
-	/*
-	 * Permission check is deferred until the key is used, as the
-	 * intent of the caller is unknown here.
-	 */
-	key_ref = lookup_user_key(serial, flags, KEY_DEFER_PERM_CHECK);
-	if (IS_ERR(key_ref))
-		return NULL;
-
-	bkey = kmalloc(sizeof(*bkey), GFP_KERNEL);
-	if (!bkey) {
-		key_put(key_ref_to_ptr(key_ref));
-		return NULL;
-	}
-
-	bkey->key = key_ref_to_ptr(key_ref);
-	bkey->has_ref = true;
-
-	return bkey;
-}
-
-/**
- * bpf_lookup_system_key - lookup a key by a system-defined ID
- * @id: key ID
- *
- * Obtain a bpf_key structure with a key pointer set to the passed key ID.
- * The key pointer is marked as invalid, to prevent bpf_key_put() from
- * attempting to decrement the key reference count on that pointer. The key
- * pointer set in such way is currently understood only by
- * verify_pkcs7_signature().
- *
- * Set *id* to one of the values defined in include/linux/verification.h:
- * 0 for the primary keyring (immutable keyring of system keys);
- * VERIFY_USE_SECONDARY_KEYRING for both the primary and secondary keyring
- * (where keys can be added only if they are vouched for by existing keys
- * in those keyrings); VERIFY_USE_PLATFORM_KEYRING for the platform
- * keyring (primarily used by the integrity subsystem to verify a kexec'ed
- * kerned image and, possibly, the initramfs signature).
- *
- * Return: a bpf_key pointer with an invalid key pointer set from the
- *         pre-determined ID on success, a NULL pointer otherwise
- */
-__bpf_kfunc struct bpf_key *bpf_lookup_system_key(u64 id)
-{
-	struct bpf_key *bkey;
-
-	if (system_keyring_id_check(id) < 0)
-		return NULL;
-
-	bkey = kmalloc(sizeof(*bkey), GFP_ATOMIC);
-	if (!bkey)
-		return NULL;
-
-	bkey->key = (struct key *)(unsigned long)id;
-	bkey->has_ref = false;
-
-	return bkey;
-}
-
-/**
- * bpf_key_put - decrement key reference count if key is valid and free bpf_key
- * @bkey: bpf_key structure
- *
- * Decrement the reference count of the key inside *bkey*, if the pointer
- * is valid, and free *bkey*.
- */
-__bpf_kfunc void bpf_key_put(struct bpf_key *bkey)
-{
-	if (bkey->has_ref)
-		key_put(bkey->key);
-
-	kfree(bkey);
-}
-
-#ifdef CONFIG_SYSTEM_DATA_VERIFICATION
-/**
- * bpf_verify_pkcs7_signature - verify a PKCS#7 signature
- * @data_p: data to verify
- * @sig_p: signature of the data
- * @trusted_keyring: keyring with keys trusted for signature verification
- *
- * Verify the PKCS#7 signature *sig_ptr* against the supplied *data_ptr*
- * with keys in a keyring referenced by *trusted_keyring*.
- *
- * Return: 0 on success, a negative value on error.
- */
-__bpf_kfunc int bpf_verify_pkcs7_signature(struct bpf_dynptr *data_p,
-			       struct bpf_dynptr *sig_p,
-			       struct bpf_key *trusted_keyring)
-{
-	struct bpf_dynptr_kern *data_ptr = (struct bpf_dynptr_kern *)data_p;
-	struct bpf_dynptr_kern *sig_ptr = (struct bpf_dynptr_kern *)sig_p;
-	const void *data, *sig;
-	u32 data_len, sig_len;
-	int ret;
-
-	if (trusted_keyring->has_ref) {
-		/*
-		 * Do the permission check deferred in bpf_lookup_user_key().
-		 * See bpf_lookup_user_key() for more details.
-		 *
-		 * A call to key_task_permission() here would be redundant, as
-		 * it is already done by keyring_search() called by
-		 * find_asymmetric_key().
-		 */
-		ret = key_validate(trusted_keyring->key);
-		if (ret < 0)
-			return ret;
-	}
-
-	data_len = __bpf_dynptr_size(data_ptr);
-	data = __bpf_dynptr_data(data_ptr, data_len);
-	sig_len = __bpf_dynptr_size(sig_ptr);
-	sig = __bpf_dynptr_data(sig_ptr, sig_len);
-
-	return verify_pkcs7_signature(data, data_len, sig, sig_len,
-				      trusted_keyring->key,
-				      VERIFYING_UNSPECIFIED_SIGNATURE, NULL,
-				      NULL);
-}
-#endif /* CONFIG_SYSTEM_DATA_VERIFICATION */
-
-__bpf_kfunc_end_defs();
-
-BTF_KFUNCS_START(key_sig_kfunc_set)
-BTF_ID_FLAGS(func, bpf_lookup_user_key, KF_ACQUIRE | KF_RET_NULL | KF_SLEEPABLE)
-BTF_ID_FLAGS(func, bpf_lookup_system_key, KF_ACQUIRE | KF_RET_NULL)
-BTF_ID_FLAGS(func, bpf_key_put, KF_RELEASE)
-#ifdef CONFIG_SYSTEM_DATA_VERIFICATION
-BTF_ID_FLAGS(func, bpf_verify_pkcs7_signature, KF_SLEEPABLE)
-#endif
-BTF_KFUNCS_END(key_sig_kfunc_set)
-
-static const struct btf_kfunc_id_set bpf_key_sig_kfunc_set = {
-	.owner = THIS_MODULE,
-	.set = &key_sig_kfunc_set,
-};
-
-static int __init bpf_key_sig_kfuncs_init(void)
-{
-	return register_btf_kfunc_id_set(BPF_PROG_TYPE_TRACING,
-					 &bpf_key_sig_kfunc_set);
-}
-
-late_initcall(bpf_key_sig_kfuncs_init);
-#endif /* CONFIG_KEYS */
-
 static const struct bpf_func_proto *
 bpf_tracing_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog)
 {
-- 
2.43.0


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

* [PATCH v2 08/13] bpf: Implement signature verification for BPF programs
  2025-07-21 21:19 [PATCH v2 00/13] Signed BPF programs KP Singh
                   ` (6 preceding siblings ...)
  2025-07-21 21:19 ` [PATCH v2 07/13] bpf: Move the signature kfuncs to helpers.c KP Singh
@ 2025-07-21 21:19 ` KP Singh
  2025-07-23 17:11   ` James Bottomley
                     ` (2 more replies)
  2025-07-21 21:19 ` [PATCH v2 09/13] libbpf: Update light skeleton for signing KP Singh
                   ` (4 subsequent siblings)
  12 siblings, 3 replies; 33+ messages in thread
From: KP Singh @ 2025-07-21 21:19 UTC (permalink / raw)
  To: bpf, linux-security-module
  Cc: bboscaccy, paul, kys, ast, daniel, andrii, KP Singh

This patch extends the BPF_PROG_LOAD command by adding three new fields
to `union bpf_attr` in the user-space API:

  - signature: A pointer to the signature blob.
  - signature_size: The size of the signature blob.
  - keyring_id: The serial number of a loaded kernel keyring (e.g.,
    the user or session keyring) containing the trusted public keys.

When a BPF program is loaded with a signature, the kernel:

1.  Retrieves the trusted keyring using the provided `keyring_id`.
2.  Verifies the supplied signature against the BPF program's
    instruction buffer.
3.  If the signature is valid and was generated by a key in the trusted
    keyring, the program load proceeds.
4.  If no signature is provided, the load proceeds as before, allowing
    for backward compatibility. LSMs can chose to restrict unsigned
    programs and implement a security policy.
5.  If signature verification fails for any reason,
    the program is not loaded.

Signed-off-by: KP Singh <kpsingh@kernel.org>
---
 crypto/asymmetric_keys/pkcs7_verify.c |  1 +
 include/linux/verification.h          |  1 +
 include/uapi/linux/bpf.h              | 10 +++++++
 kernel/bpf/helpers.c                  |  2 +-
 kernel/bpf/syscall.c                  | 41 ++++++++++++++++++++++++++-
 tools/include/uapi/linux/bpf.h        | 10 +++++++
 tools/lib/bpf/bpf.c                   |  2 +-
 7 files changed, 64 insertions(+), 3 deletions(-)

diff --git a/crypto/asymmetric_keys/pkcs7_verify.c b/crypto/asymmetric_keys/pkcs7_verify.c
index f0d4ff3c20a8..6d6475e3a9bf 100644
--- a/crypto/asymmetric_keys/pkcs7_verify.c
+++ b/crypto/asymmetric_keys/pkcs7_verify.c
@@ -429,6 +429,7 @@ int pkcs7_verify(struct pkcs7_message *pkcs7,
 		/* Authattr presence checked in parser */
 		break;
 	case VERIFYING_UNSPECIFIED_SIGNATURE:
+	case VERIFYING_BPF_SIGNATURE:
 		if (pkcs7->data_type != OID_data) {
 			pr_warn("Invalid unspecified sig (not pkcs7-data)\n");
 			return -EKEYREJECTED;
diff --git a/include/linux/verification.h b/include/linux/verification.h
index 4f3022d081c3..dec7f2beabfd 100644
--- a/include/linux/verification.h
+++ b/include/linux/verification.h
@@ -36,6 +36,7 @@ enum key_being_used_for {
 	VERIFYING_KEY_SIGNATURE,
 	VERIFYING_KEY_SELF_SIGNATURE,
 	VERIFYING_UNSPECIFIED_SIGNATURE,
+	VERIFYING_BPF_SIGNATURE,
 	NR__KEY_BEING_USED_FOR
 };
 #ifdef CONFIG_SYSTEM_DATA_VERIFICATION
diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h
index fd3b895ebebf..b42c3740e053 100644
--- a/include/uapi/linux/bpf.h
+++ b/include/uapi/linux/bpf.h
@@ -1607,6 +1607,16 @@ union bpf_attr {
 		 * continuous.
 		 */
 		__u32		fd_array_cnt;
+		/* Pointer to a buffer containing the signature of the BPF
+		 * program.
+		 */
+		__aligned_u64   signature;
+		/* Size of the signature buffer in bytes. */
+		__u32 		signature_size;
+		/* ID of the kernel keyring to be used for signature
+		 * verification.
+		 */
+		__u32 		keyring_id;
 	};
 
 	struct { /* anonymous struct used by BPF_OBJ_* commands */
diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
index a052bbbcbfc5..e883c91b3633 100644
--- a/kernel/bpf/helpers.c
+++ b/kernel/bpf/helpers.c
@@ -3853,7 +3853,7 @@ __bpf_kfunc int bpf_verify_pkcs7_signature(struct bpf_dynptr *data_p,
 
 	return verify_pkcs7_signature(data, data_len, sig, sig_len,
 				      trusted_keyring->key,
-				      VERIFYING_UNSPECIFIED_SIGNATURE, NULL,
+				      VERIFYING_BPF_SIGNATURE, NULL,
 				      NULL);
 #else
 	return -EOPNOTSUPP;
diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
index 22fda92ab7ce..111f91a99166 100644
--- a/kernel/bpf/syscall.c
+++ b/kernel/bpf/syscall.c
@@ -2779,8 +2779,41 @@ static bool is_perfmon_prog_type(enum bpf_prog_type prog_type)
 	}
 }
 
+static noinline 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_key *key = NULL;
+	void *sig;
+	int err = 0;
+
+	key = bpf_lookup_user_key(attr->keyring_id, 0);
+	if (!key)
+		return -ENOKEY;
+
+	sig = kvmemdup_bpfptr(usig, attr->signature_size);
+	if (!sig) {
+		bpf_key_put(key);
+		return -ENOMEM;
+	}
+
+	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,
+					 (struct bpf_dynptr *)&sig_ptr, key);
+
+	bpf_key_put(key);
+	kvfree(sig);
+	return err;
+}
+
 /* last field in 'union bpf_attr' used by this command */
-#define BPF_PROG_LOAD_LAST_FIELD fd_array_cnt
+#define BPF_PROG_LOAD_LAST_FIELD keyring_id
 
 static int bpf_prog_load(union bpf_attr *attr, bpfptr_t uattr, u32 uattr_size)
 {
@@ -2944,6 +2977,12 @@ 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->signature) {
+		err = bpf_prog_verify_signature(prog, attr, uattr.is_kernel);
+		if (err)
+			goto free_prog;
+	}
+
 	prog->orig_prog = NULL;
 	prog->jited = 0;
 
diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h
index fd3b895ebebf..b42c3740e053 100644
--- a/tools/include/uapi/linux/bpf.h
+++ b/tools/include/uapi/linux/bpf.h
@@ -1607,6 +1607,16 @@ union bpf_attr {
 		 * continuous.
 		 */
 		__u32		fd_array_cnt;
+		/* Pointer to a buffer containing the signature of the BPF
+		 * program.
+		 */
+		__aligned_u64   signature;
+		/* Size of the signature buffer in bytes. */
+		__u32 		signature_size;
+		/* ID of the kernel keyring to be used for signature
+		 * verification.
+		 */
+		__u32 		keyring_id;
 	};
 
 	struct { /* anonymous struct used by BPF_OBJ_* commands */
diff --git a/tools/lib/bpf/bpf.c b/tools/lib/bpf/bpf.c
index 6a08a1559237..9c7815ddd829 100644
--- a/tools/lib/bpf/bpf.c
+++ b/tools/lib/bpf/bpf.c
@@ -240,7 +240,7 @@ int bpf_prog_load(enum bpf_prog_type prog_type,
 		  const struct bpf_insn *insns, size_t insn_cnt,
 		  struct bpf_prog_load_opts *opts)
 {
-	const size_t attr_sz = offsetofend(union bpf_attr, fd_array_cnt);
+	const size_t attr_sz = offsetofend(union bpf_attr, keyring_id);
 	void *finfo = NULL, *linfo = NULL;
 	const char *func_info, *line_info;
 	__u32 log_size, log_level, attach_prog_fd, attach_btf_obj_fd;
-- 
2.43.0


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

* [PATCH v2 09/13] libbpf: Update light skeleton for signing
  2025-07-21 21:19 [PATCH v2 00/13] Signed BPF programs KP Singh
                   ` (7 preceding siblings ...)
  2025-07-21 21:19 ` [PATCH v2 08/13] bpf: Implement signature verification for BPF programs KP Singh
@ 2025-07-21 21:19 ` KP Singh
  2025-07-21 21:19 ` [PATCH v2 10/13] libbpf: Embed and verify the metadata hash in the loader KP Singh
                   ` (3 subsequent siblings)
  12 siblings, 0 replies; 33+ messages in thread
From: KP Singh @ 2025-07-21 21:19 UTC (permalink / raw)
  To: bpf, linux-security-module
  Cc: bboscaccy, paul, kys, ast, daniel, andrii, KP Singh

* The metadata map is created with as an exclusive map (with an
excl_prog_hash) This restricts map access exclusively to the signed
loader program, preventing tampering by other processes.

* The map is then frozen, making it read-only from userspace.

* BPF_OBJ_GET_INFO_BY_ID instructs the kernel to compute the hash of the
  metadata map (H') and store it in bpf_map->sha.

* The loader is then loaded with the signature which is then verified by
  the kernel.

The sekeleton currently uses the session keyring
(KEY_SPEC_SESSION_KEYRING) by default but this can
be overridden by the user of the skeleton.

loading signed programs prebuilt into the kernel are not currently
supported. These can supported by enabling BPF_OBJ_GET_INFO_BY_ID to be
called from the kernel.

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

diff --git a/tools/lib/bpf/skel_internal.h b/tools/lib/bpf/skel_internal.h
index 4d5fa079b5d6..5b6d1b09dc8a 100644
--- a/tools/lib/bpf/skel_internal.h
+++ b/tools/lib/bpf/skel_internal.h
@@ -13,10 +13,15 @@
 #include <unistd.h>
 #include <sys/syscall.h>
 #include <sys/mman.h>
+#include <linux/keyctl.h>
 #include <stdlib.h>
 #include "bpf.h"
 #endif
 
+#ifndef SHA256_DIGEST_LENGTH
+#define SHA256_DIGEST_LENGTH 32
+#endif
+
 #ifndef __NR_bpf
 # if defined(__mips__) && defined(_ABIO32)
 #  define __NR_bpf 4355
@@ -64,6 +69,11 @@ struct bpf_load_and_run_opts {
 	__u32 data_sz;
 	__u32 insns_sz;
 	const char *errstr;
+	void *signature;
+	__u32 signature_sz;
+	__u32 keyring_id;
+	void * excl_prog_hash;
+	__u32 excl_prog_hash_sz;
 };
 
 long kern_sys_bpf(__u32 cmd, void *attr, __u32 attr_size);
@@ -220,14 +230,19 @@ static inline int skel_map_create(enum bpf_map_type map_type,
 				  const char *map_name,
 				  __u32 key_size,
 				  __u32 value_size,
-				  __u32 max_entries)
+				  __u32 max_entries,
+				  const void *excl_prog_hash,
+				  __u32 excl_prog_hash_sz)
 {
-	const size_t attr_sz = offsetofend(union bpf_attr, map_extra);
+	const size_t attr_sz = offsetofend(union bpf_attr, excl_prog_hash);
 	union bpf_attr attr;
 
 	memset(&attr, 0, attr_sz);
 
 	attr.map_type = map_type;
+	attr.excl_prog_hash = (unsigned long) excl_prog_hash;
+	attr.excl_prog_hash_size = excl_prog_hash_sz;
+
 	strncpy(attr.map_name, map_name, sizeof(attr.map_name));
 	attr.key_size = key_size;
 	attr.value_size = value_size;
@@ -300,6 +315,34 @@ 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;
+
+	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);
+	union bpf_attr attr;
+
+	memset(&attr, 0, attr_sz);
+	attr.map_fd = fd;
+
+	return skel_sys_bpf(BPF_MAP_FREEZE, &attr, attr_sz);
+}
 #ifdef __KERNEL__
 #define set_err
 #else
@@ -308,12 +351,13 @@ static inline int skel_link_create(int prog_fd, int target_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, fd_array);
+	const size_t prog_load_attr_sz = offsetofend(union bpf_attr, keyring_id);
 	const size_t test_run_attr_sz = offsetofend(union bpf_attr, test);
 	int map_fd = -1, prog_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);
+	err = map_fd = skel_map_create(BPF_MAP_TYPE_ARRAY, "__loader.map", 4, opts->data_sz, 1,
+				       opts->excl_prog_hash, opts->excl_prog_hash_sz);
 	if (map_fd < 0) {
 		opts->errstr = "failed to create loader map";
 		set_err;
@@ -327,11 +371,34 @@ static inline int bpf_load_and_run(struct bpf_load_and_run_opts *opts)
 		goto out;
 	}
 
+#ifndef __KERNEL__
+	err = skel_map_freeze(map_fd);
+	if (err < 0) {
+		opts->errstr = "failed to freeze map";
+		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
+
 	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";
+#ifndef __KERNEL__
+	attr.signature = (long) opts->signature;
+	attr.signature_size = opts->signature_sz;
+#else
+	if (opts->signature || opts->signature_sz)
+		pr_warn("signatures are not supported from bpf_preload\n");
+#endif
+	attr.keyring_id = opts->keyring_id;
 	memcpy(attr.prog_name, "__loader.prog", sizeof("__loader.prog"));
 	attr.fd_array = (long) &map_fd;
 	attr.log_level = opts->ctx->log_level;
-- 
2.43.0


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

* [PATCH v2 10/13] libbpf: Embed and verify the metadata hash in the loader
  2025-07-21 21:19 [PATCH v2 00/13] Signed BPF programs KP Singh
                   ` (8 preceding siblings ...)
  2025-07-21 21:19 ` [PATCH v2 09/13] libbpf: Update light skeleton for signing KP Singh
@ 2025-07-21 21:19 ` KP Singh
  2025-07-21 21:19 ` [PATCH v2 11/13] bpftool: Add support for signing BPF programs KP Singh
                   ` (2 subsequent siblings)
  12 siblings, 0 replies; 33+ messages in thread
From: KP Singh @ 2025-07-21 21:19 UTC (permalink / raw)
  To: bpf, linux-security-module
  Cc: bboscaccy, paul, kys, ast, daniel, andrii, KP Singh

To fulfill the BPF signing contract, represented as Sig(I_loader ||
H_meta), the generated trusted loader program must verify the integrity
of the metadata. This signature cryptographically binds the loader's
instructions (I_loader) to a hash of the metadata (H_meta).

The verification process is embedded directly into the loader program.
Upon execution, the loader loads the runtime hash from struct bpf_map
i.e. BPF_PSEUDO_MAP_IDX and compares this runtime hash against an
expected hash value that has been hardcoded directly by
bpf_obj__gen_loader.

The load from bpf_map can be improved by calling
BPF_OBJ_GET_INFO_BY_FD from the kernel context after BPF_OBJ_GET_INFO_BY_FD
has been updated for being called from the kernel context.

The following instructions are generated:

    ld_imm64 r1, const_ptr_to_map // insn[0].src_reg == BPF_PSEUDO_MAP_IDX
    r2 = *(u64 *)(r1 + 0);
    ld_imm64 r3, sha256_of_map_part1 // constant precomputed by
bpftool (part of H_meta)
    if r2 != r3 goto out;

    r2 = *(u64 *)(r1 + 8);
    ld_imm64 r3, sha256_of_map_part2 // (part of H_meta)
    if r2 != r3 goto out;

    r2 = *(u64 *)(r1 + 16);
    ld_imm64 r3, sha256_of_map_part3 // (part of H_meta)
    if r2 != r3 goto out;

    r2 = *(u64 *)(r1 + 24);
    ld_imm64 r3, sha256_of_map_part4 // (part of H_meta)
    if r2 != r3 goto out;
    ...

Signed-off-by: KP Singh <kpsingh@kernel.org>
---
 tools/lib/bpf/bpf_gen_internal.h |  2 ++
 tools/lib/bpf/gen_loader.c       | 55 ++++++++++++++++++++++++++++++++
 tools/lib/bpf/libbpf.h           |  3 +-
 3 files changed, 59 insertions(+), 1 deletion(-)

diff --git a/tools/lib/bpf/bpf_gen_internal.h b/tools/lib/bpf/bpf_gen_internal.h
index 6ff963a491d9..49af4260b8e6 100644
--- a/tools/lib/bpf/bpf_gen_internal.h
+++ b/tools/lib/bpf/bpf_gen_internal.h
@@ -4,6 +4,7 @@
 #define __BPF_GEN_INTERNAL_H
 
 #include "bpf.h"
+#include "libbpf_internal.h"
 
 struct ksym_relo_desc {
 	const char *name;
@@ -50,6 +51,7 @@ struct bpf_gen {
 	__u32 nr_ksyms;
 	int fd_array;
 	int nr_fd_array;
+	int hash_insn_offset[SHA256_DWORD_SIZE];
 };
 
 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 113ae4abd345..8eba7c1514ef 100644
--- a/tools/lib/bpf/gen_loader.c
+++ b/tools/lib/bpf/gen_loader.c
@@ -110,6 +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 bpf_gen__signature_match(struct bpf_gen *gen);
 
 void bpf_gen__init(struct bpf_gen *gen, int log_level, int nr_progs, int nr_maps)
 {
@@ -152,6 +153,8 @@ 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))
+		bpf_gen__signature_match(gen);
 }
 
 static int add_data(struct bpf_gen *gen, const void *data, __u32 size)
@@ -368,6 +371,8 @@ static void emit_sys_close_blob(struct bpf_gen *gen, int blob_off)
 	__emit_sys_close(gen);
 }
 
+static int compute_sha_udpate_offsets(struct bpf_gen *gen);
+
 int bpf_gen__finish(struct bpf_gen *gen, int nr_progs, int nr_maps)
 {
 	int i;
@@ -394,6 +399,12 @@ 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)) {
+		gen->error = compute_sha_udpate_offsets(gen);
+		if (gen->error)
+			return gen->error;
+	}
+
 	pr_debug("gen: finish %s\n", errstr(gen->error));
 	if (!gen->error) {
 		struct gen_loader_opts *opts = gen->opts;
@@ -446,6 +457,27 @@ void bpf_gen__free(struct bpf_gen *gen)
 	_val;							\
 })
 
+static int compute_sha_udpate_offsets(struct bpf_gen *gen)
+{
+	__u64 sha[SHA256_DWORD_SIZE];
+	__u64 sha_dw;
+	int i, err;
+
+	err = libbpf_sha256(gen->data_start, gen->data_cur - gen->data_start, sha, SHA256_DIGEST_LENGTH);
+	if (err < 0) {
+		pr_warn("sha256 computation of the metadata failed");
+		return err;
+	}
+	for (i = 0; i < SHA256_DWORD_SIZE; i++) {
+		struct bpf_insn *insn =
+			(struct bpf_insn *)(gen->insn_start + gen->hash_insn_offset[i]);
+		sha_dw = tgt_endian(sha[i]);
+		insn[0].imm = (__u32)sha_dw;
+		insn[1].imm = sha_dw >> 32;
+	}
+	return 0;
+}
+
 void bpf_gen__load_btf(struct bpf_gen *gen, const void *btf_raw_data,
 		       __u32 btf_raw_size)
 {
@@ -557,6 +589,29 @@ void bpf_gen__map_create(struct bpf_gen *gen,
 		emit_sys_close_stack(gen, stack_off(inner_map_fd));
 }
 
+static void bpf_gen__signature_match(struct bpf_gen *gen)
+{
+	__s64 off;
+	int i;
+
+	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 - 1;
+		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_JMP_IMM(BPF_JA, 0, 0, -1));
+		}
+	}
+}
+
 void bpf_gen__record_attach_target(struct bpf_gen *gen, const char *attach_name,
 				   enum bpf_attach_type type)
 {
diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h
index 60552fa5401d..7cad8470d9eb 100644
--- a/tools/lib/bpf/libbpf.h
+++ b/tools/lib/bpf/libbpf.h
@@ -1826,9 +1826,10 @@ struct gen_loader_opts {
 	const char *insns;
 	__u32 data_sz;
 	__u32 insns_sz;
+	bool gen_hash;
 };
 
-#define gen_loader_opts__last_field insns_sz
+#define gen_loader_opts__last_field gen_hash
 LIBBPF_API int bpf_object__gen_loader(struct bpf_object *obj,
 				      struct gen_loader_opts *opts);
 
-- 
2.43.0


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

* [PATCH v2 11/13] bpftool: Add support for signing BPF programs
  2025-07-21 21:19 [PATCH v2 00/13] Signed BPF programs KP Singh
                   ` (9 preceding siblings ...)
  2025-07-21 21:19 ` [PATCH v2 10/13] libbpf: Embed and verify the metadata hash in the loader KP Singh
@ 2025-07-21 21:19 ` KP Singh
  2025-07-22 15:51   ` Quentin Monnet
  2025-07-21 21:19 ` [PATCH v2 12/13] selftests/bpf: Enable signature verification for all lskel tests KP Singh
  2025-07-21 21:19 ` [PATCH v2 13/13] selftests/bpf: Add test for signed programs KP Singh
  12 siblings, 1 reply; 33+ messages in thread
From: KP Singh @ 2025-07-21 21:19 UTC (permalink / raw)
  To: bpf, linux-security-module
  Cc: bboscaccy, paul, kys, ast, daniel, andrii, KP Singh

Two modes of operation being added:

Add two modes of operation:

* For prog load, allow signing a program immediately before loading. This
  is essential for command-line testing and administration.

      bpftool prog load -S -k <private_key> -i <identity_cert> fentry_test.bpf.o

* For gen skeleton, embed a pre-generated signature into the C skeleton
  file. This supports the use of signed programs in compiled applications.

      bpftool gen skeleton -S -k <private_key> -i <identity_cert> fentry_test.bpf.o

Generation of the loader program and its metadata map is implemented in
libbpf (bpf_obj__gen_loader). bpftool generates a skeleton that loads
the program and automates the required steps: freezing the map, creating
an exclusive map, loading, and running. Users can use standard libbpf
APIs directly or integrate loader program generation into their own
toolchains.

Signed-off-by: KP Singh <kpsingh@kernel.org>
---
 .../bpf/bpftool/Documentation/bpftool-gen.rst |  12 +
 .../bpftool/Documentation/bpftool-prog.rst    |  12 +
 tools/bpf/bpftool/Makefile                    |   6 +-
 tools/bpf/bpftool/cgroup.c                    |   5 +-
 tools/bpf/bpftool/gen.c                       |  58 ++++-
 tools/bpf/bpftool/main.c                      |  21 +-
 tools/bpf/bpftool/main.h                      |  11 +
 tools/bpf/bpftool/prog.c                      |  25 +++
 tools/bpf/bpftool/sign.c                      | 210 ++++++++++++++++++
 9 files changed, 352 insertions(+), 8 deletions(-)
 create mode 100644 tools/bpf/bpftool/sign.c

diff --git a/tools/bpf/bpftool/Documentation/bpftool-gen.rst b/tools/bpf/bpftool/Documentation/bpftool-gen.rst
index ca860fd97d8d..2997313003b1 100644
--- a/tools/bpf/bpftool/Documentation/bpftool-gen.rst
+++ b/tools/bpf/bpftool/Documentation/bpftool-gen.rst
@@ -185,6 +185,18 @@ OPTIONS
     For skeletons, generate a "light" skeleton (also known as "loader"
     skeleton). A light skeleton contains a loader eBPF program. It does not use
     the majority of the libbpf infrastructure, and does not need libelf.
+-S, --sign
+    For skeletons, generate a signed skeleton. This option must be used with
+    **-k** and **-i**. Using this flag implicitly enables **--use-loader**.
+    See the "Signed Skeletons" section in the description of the
+    **gen skeleton** command for more details.
+
+-k <private_key.pem>
+    Path to the private key file in PEM format, required for signing.
+
+-i <certificate.x509>
+    Path to the X.509 certificate file in PEM or DER format, required for
+    signing.
 
 EXAMPLES
 ========
diff --git a/tools/bpf/bpftool/Documentation/bpftool-prog.rst b/tools/bpf/bpftool/Documentation/bpftool-prog.rst
index f69fd92df8d8..dc2ca196137e 100644
--- a/tools/bpf/bpftool/Documentation/bpftool-prog.rst
+++ b/tools/bpf/bpftool/Documentation/bpftool-prog.rst
@@ -248,6 +248,18 @@ OPTIONS
     creating the maps, and loading the programs (see **bpftool prog tracelog**
     as a way to dump those messages).
 
+-S, --sign
+    Enable signing of the BPF program before loading. This option must be
+    used with **-k** and **-i**. Using this flag implicitly enables
+    **--use-loader**.
+
+-k <private_key.pem>
+    Path to the private key file in PEM format, required when signing.
+
+-i <certificate.x509>
+    Path to the X.509 certificate file in PEM or DER format, required when
+    signing.
+
 EXAMPLES
 ========
 **# bpftool prog show**
diff --git a/tools/bpf/bpftool/Makefile b/tools/bpf/bpftool/Makefile
index 9e9a5f006cd2..586d1b2595d1 100644
--- a/tools/bpf/bpftool/Makefile
+++ b/tools/bpf/bpftool/Makefile
@@ -130,8 +130,8 @@ include $(FEATURES_DUMP)
 endif
 endif
 
-LIBS = $(LIBBPF) -lelf -lz
-LIBS_BOOTSTRAP = $(LIBBPF_BOOTSTRAP) -lelf -lz
+LIBS = $(LIBBPF) -lelf -lz -lcrypto
+LIBS_BOOTSTRAP = $(LIBBPF_BOOTSTRAP) -lelf -lz -lcrypto
 
 ifeq ($(feature-libelf-zstd),1)
 LIBS += -lzstd
@@ -194,7 +194,7 @@ endif
 
 BPFTOOL_BOOTSTRAP := $(BOOTSTRAP_OUTPUT)bpftool
 
-BOOTSTRAP_OBJS = $(addprefix $(BOOTSTRAP_OUTPUT),main.o common.o json_writer.o gen.o btf.o)
+BOOTSTRAP_OBJS = $(addprefix $(BOOTSTRAP_OUTPUT),main.o common.o json_writer.o gen.o btf.o sign.o)
 $(BOOTSTRAP_OBJS): $(LIBBPF_BOOTSTRAP)
 
 OBJS = $(patsubst %.c,$(OUTPUT)%.o,$(SRCS)) $(OUTPUT)disasm.o
diff --git a/tools/bpf/bpftool/cgroup.c b/tools/bpf/bpftool/cgroup.c
index 944ebe21a216..90c9aa297806 100644
--- a/tools/bpf/bpftool/cgroup.c
+++ b/tools/bpf/bpftool/cgroup.c
@@ -1,7 +1,10 @@
 // SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
 // Copyright (C) 2017 Facebook
 // Author: Roman Gushchin <guro@fb.com>
-
+#undef GCC_VERSION
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
 #define _XOPEN_SOURCE 500
 #include <errno.h>
 #include <fcntl.h>
diff --git a/tools/bpf/bpftool/gen.c b/tools/bpf/bpftool/gen.c
index 67a60114368f..ab6fc86598ad 100644
--- a/tools/bpf/bpftool/gen.c
+++ b/tools/bpf/bpftool/gen.c
@@ -688,10 +688,17 @@ static void codegen_destroy(struct bpf_object *obj, const char *obj_name)
 static int gen_trace(struct bpf_object *obj, const char *obj_name, const char *header_guard)
 {
 	DECLARE_LIBBPF_OPTS(gen_loader_opts, opts);
+	struct bpf_load_and_run_opts sopts = {};
+	char sig_buf[MAX_SIG_SIZE];
+	__u8 prog_sha[SHA256_DIGEST_LENGTH];
 	struct bpf_map *map;
+
 	char ident[256];
 	int err = 0;
 
+	if (sign_progs)
+		opts.gen_hash = true;
+
 	err = bpf_object__gen_loader(obj, &opts);
 	if (err)
 		return err;
@@ -701,6 +708,7 @@ static int gen_trace(struct bpf_object *obj, const char *obj_name, const char *h
 		p_err("failed to load object file");
 		goto out;
 	}
+
 	/* If there was no error during load then gen_loader_opts
 	 * are populated with the loader program.
 	 */
@@ -780,8 +788,51 @@ static int gen_trace(struct bpf_object *obj, const char *obj_name, const char *h
 	print_hex(opts.insns, opts.insns_sz);
 	codegen("\
 		\n\
-		\";							    \n\
-									    \n\
+		\";\n");
+
+	if (sign_progs) {
+		sopts.insns = opts.insns;
+		sopts.insns_sz = opts.insns_sz;
+		sopts.excl_prog_hash = prog_sha;
+		sopts.excl_prog_hash_sz = sizeof(prog_sha);
+		sopts.signature = sig_buf;
+		sopts.signature_sz = MAX_SIG_SIZE;
+		sopts.keyring_id = KEY_SPEC_SESSION_KEYRING;
+
+		err = bpftool_prog_sign(&sopts);
+		if (err < 0)
+			return err;
+
+		codegen("\
+		\n\
+			static const char opts_sig[] __attribute__((__aligned__(8))) = \"\\\n\
+		");
+		print_hex((const void *)sig_buf, sopts.signature_sz);
+		codegen("\
+		\n\
+		\";\n");
+
+		codegen("\
+		\n\
+			static const char opts_excl_hash[] __attribute__((__aligned__(8))) = \"\\\n\
+		");
+		print_hex((const void *)prog_sha, sizeof(prog_sha));
+		codegen("\
+		\n\
+		\";\n");
+
+		codegen("\
+		\n\
+			opts.signature = (void *)opts_sig;			\n\
+			opts.signature_sz = sizeof(opts_sig) - 1;		\n\
+			opts.excl_prog_hash = (void *)opts_excl_hash;		\n\
+			opts.excl_prog_hash_sz = sizeof(opts_excl_hash) - 1;	\n\
+			opts.keyring_id = KEY_SPEC_SESSION_KEYRING;		\n\
+		");
+	}
+
+	codegen("\
+		\n\
 			opts.ctx = (struct bpf_loader_ctx *)skel;	    \n\
 			opts.data_sz = sizeof(opts_data) - 1;		    \n\
 			opts.data = (void *)opts_data;			    \n\
@@ -1240,7 +1291,7 @@ static int do_skeleton(int argc, char **argv)
 		err = -errno;
 		libbpf_strerror(err, err_buf, sizeof(err_buf));
 		p_err("failed to open BPF object file: %s", err_buf);
-		goto out;
+		goto out_obj;
 	}
 
 	bpf_object__for_each_map(map, obj) {
@@ -1552,6 +1603,7 @@ static int do_skeleton(int argc, char **argv)
 	err = 0;
 out:
 	bpf_object__close(obj);
+out_obj:
 	if (obj_data)
 		munmap(obj_data, mmap_sz);
 	close(fd);
diff --git a/tools/bpf/bpftool/main.c b/tools/bpf/bpftool/main.c
index 2b7f2bd3a7db..fc25bb390ec7 100644
--- a/tools/bpf/bpftool/main.c
+++ b/tools/bpf/bpftool/main.c
@@ -33,6 +33,9 @@ bool relaxed_maps;
 bool use_loader;
 struct btf *base_btf;
 struct hashmap *refs_table;
+bool sign_progs;
+const char *private_key_path;
+const char *cert_path;
 
 static void __noreturn clean_and_exit(int i)
 {
@@ -447,6 +450,7 @@ int main(int argc, char **argv)
 		{ "nomount",	no_argument,	NULL,	'n' },
 		{ "debug",	no_argument,	NULL,	'd' },
 		{ "use-loader",	no_argument,	NULL,	'L' },
+		{ "sign",	required_argument, NULL, 'S'},
 		{ "base-btf",	required_argument, NULL, 'B' },
 		{ 0 }
 	};
@@ -473,7 +477,7 @@ int main(int argc, char **argv)
 	bin_name = "bpftool";
 
 	opterr = 0;
-	while ((opt = getopt_long(argc, argv, "VhpjfLmndB:l",
+	while ((opt = getopt_long(argc, argv, "VhpjfLmndSi:k:B:l",
 				  options, NULL)) >= 0) {
 		switch (opt) {
 		case 'V':
@@ -519,6 +523,16 @@ int main(int argc, char **argv)
 		case 'L':
 			use_loader = true;
 			break;
+		case 'S':
+			sign_progs = true;
+			use_loader = true;
+			break;
+		case 'k':
+			private_key_path = optarg;
+			break;
+		case 'i':
+			cert_path = optarg;
+			break;
 		default:
 			p_err("unrecognized option '%s'", argv[optind - 1]);
 			if (json_output)
@@ -533,6 +547,11 @@ int main(int argc, char **argv)
 	if (argc < 0)
 		usage();
 
+	if (sign_progs && (private_key_path == NULL || cert_path == NULL)) {
+		p_err("-i <identity_x509_cert> and -k <private> key must be supplied with -S for signing");
+		return -EINVAL;
+	}
+
 	if (version_requested)
 		ret = do_version(argc, argv);
 	else
diff --git a/tools/bpf/bpftool/main.h b/tools/bpf/bpftool/main.h
index 6db704fda5c0..f921af3cda87 100644
--- a/tools/bpf/bpftool/main.h
+++ b/tools/bpf/bpftool/main.h
@@ -6,9 +6,14 @@
 
 /* BFD and kernel.h both define GCC_VERSION, differently */
 #undef GCC_VERSION
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
 #include <stdbool.h>
 #include <stdio.h>
+#include <errno.h>
 #include <stdlib.h>
+#include <bpf/skel_internal.h>
 #include <linux/bpf.h>
 #include <linux/compiler.h>
 #include <linux/kernel.h>
@@ -52,6 +57,7 @@ static inline void *u64_to_ptr(__u64 ptr)
 	})
 
 #define ERR_MAX_LEN	1024
+#define MAX_SIG_SIZE	4096
 
 #define BPF_TAG_FMT	"%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
 
@@ -85,6 +91,9 @@ extern bool relaxed_maps;
 extern bool use_loader;
 extern struct btf *base_btf;
 extern struct hashmap *refs_table;
+extern bool sign_progs;
+extern const char *private_key_path;
+extern const char *cert_path;
 
 void __printf(1, 2) p_err(const char *fmt, ...);
 void __printf(1, 2) p_info(const char *fmt, ...);
@@ -274,4 +283,6 @@ int pathname_concat(char *buf, int buf_sz, const char *path,
 /* print netfilter bpf_link info */
 void netfilter_dump_plain(const struct bpf_link_info *info);
 void netfilter_dump_json(const struct bpf_link_info *info, json_writer_t *wtr);
+int bpftool_prog_sign(struct bpf_load_and_run_opts *opts);
+__u32 register_session_key(const char *key_der_path);
 #endif
diff --git a/tools/bpf/bpftool/prog.c b/tools/bpf/bpftool/prog.c
index 9722d841abc0..0b67612a2ce8 100644
--- a/tools/bpf/bpftool/prog.c
+++ b/tools/bpf/bpftool/prog.c
@@ -23,6 +23,7 @@
 #include <linux/err.h>
 #include <linux/perf_event.h>
 #include <linux/sizes.h>
+#include <linux/keyctl.h>
 
 #include <bpf/bpf.h>
 #include <bpf/btf.h>
@@ -1930,6 +1931,8 @@ static int try_loader(struct gen_loader_opts *gen)
 {
 	struct bpf_load_and_run_opts opts = {};
 	struct bpf_loader_ctx *ctx;
+	char sig_buf[MAX_SIG_SIZE];
+	__u8 prog_sha[SHA256_DIGEST_LENGTH];
 	int ctx_sz = sizeof(*ctx) + 64 * max(sizeof(struct bpf_map_desc),
 					     sizeof(struct bpf_prog_desc));
 	int log_buf_sz = (1u << 24) - 1;
@@ -1953,6 +1956,24 @@ static int try_loader(struct gen_loader_opts *gen)
 	opts.insns = gen->insns;
 	opts.insns_sz = gen->insns_sz;
 	fds_before = count_open_fds();
+
+	if (sign_progs) {
+		opts.excl_prog_hash = prog_sha;
+		opts.excl_prog_hash_sz = sizeof(prog_sha);
+		opts.signature = sig_buf;
+		opts.signature_sz = MAX_SIG_SIZE;
+		opts.keyring_id = KEY_SPEC_SESSION_KEYRING;
+
+		err = bpftool_prog_sign(&opts);
+		if (err < 0)
+			return err;
+
+		err = register_session_key(cert_path);
+		if (err < 0) {
+			p_err("failed to add session key");
+			goto out;
+		}
+	}
 	err = bpf_load_and_run(&opts);
 	fd_delta = count_open_fds() - fds_before;
 	if (err < 0 || verifier_logs) {
@@ -1961,6 +1982,7 @@ static int try_loader(struct gen_loader_opts *gen)
 			fprintf(stderr, "loader prog leaked %d FDs\n",
 				fd_delta);
 	}
+out:
 	free(log_buf);
 	return err;
 }
@@ -1988,6 +2010,9 @@ static int do_loader(int argc, char **argv)
 		goto err_close_obj;
 	}
 
+	if (sign_progs)
+		gen.gen_hash = true;
+
 	err = bpf_object__gen_loader(obj, &gen);
 	if (err)
 		goto err_close_obj;
diff --git a/tools/bpf/bpftool/sign.c b/tools/bpf/bpftool/sign.c
new file mode 100644
index 000000000000..f0b5dd10a46b
--- /dev/null
+++ b/tools/bpf/bpftool/sign.c
@@ -0,0 +1,210 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Copyright (C) 2022 Google LLC.
+ */
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <string.h>
+#include <getopt.h>
+#include <err.h>
+#include <openssl/opensslv.h>
+#include <openssl/bio.h>
+#include <openssl/evp.h>
+#include <openssl/pem.h>
+#include <openssl/err.h>
+#include <openssl/cms.h>
+#include <linux/keyctl.h>
+#include <errno.h>
+
+#include <bpf/skel_internal.h>
+
+#include "main.h"
+
+#define OPEN_SSL_ERR_BUF_LEN 256
+
+static void display_openssl_errors(int l)
+{
+	char buf[OPEN_SSL_ERR_BUF_LEN];
+	const char *file;
+	const char *data;
+	unsigned long e;
+	int flags;
+	int line;
+
+	while ((e = ERR_get_error_all(&file, &line, NULL, &data, &flags))) {
+		ERR_error_string_n(e, buf, sizeof(buf));
+		if (data && (flags & ERR_TXT_STRING)) {
+			p_err("OpenSSL %s: %s:%d: %s\n", buf, file, line, data);
+		} else {
+			p_err("OpenSSL %s: %s:%d\n", buf, file, line);
+		}
+	}
+}
+
+#define DISPLAY_OSSL_ERR(cond)				 \
+	do {						 \
+		bool __cond = (cond);			 \
+		if (__cond && ERR_peek_error())		 \
+			display_openssl_errors(__LINE__);\
+	} while (0)
+
+static EVP_PKEY *read_private_key(const char *pkey_path)
+{
+	EVP_PKEY *private_key = NULL;
+	BIO *b;
+
+	b = BIO_new_file(pkey_path, "rb");
+	private_key = PEM_read_bio_PrivateKey(b, NULL, NULL, NULL);
+	BIO_free(b);
+	DISPLAY_OSSL_ERR(!private_key);
+	return private_key;
+}
+
+static X509 *read_x509(const char *x509_name)
+{
+	unsigned char buf[2];
+	X509 *x509 = NULL;
+	BIO *b;
+	int n;
+
+	b = BIO_new_file(x509_name, "rb");
+	if (!b)
+		goto cleanup;
+
+	/* Look at the first two bytes of the file to determine the encoding */
+	n = BIO_read(b, buf, 2);
+	if (n != 2)
+		goto cleanup;
+
+	if (BIO_reset(b) != 0)
+		goto cleanup;
+
+	if (buf[0] == 0x30 && buf[1] >= 0x81 && buf[1] <= 0x84)
+		/* Assume raw DER encoded X.509 */
+		x509 = d2i_X509_bio(b, NULL);
+	else
+		/* Assume PEM encoded X.509 */
+		x509 = PEM_read_bio_X509(b, NULL, NULL, NULL);
+
+cleanup:
+	BIO_free(b);
+	DISPLAY_OSSL_ERR(!x509);
+	return x509;
+}
+
+__u32 register_session_key(const char *key_der_path)
+{
+	unsigned char *der_buf = NULL;
+	X509 *x509 = NULL;
+	int key_id = -1;
+	int der_len;
+
+	if (!key_der_path)
+		return key_id;
+	x509 = read_x509(key_der_path);
+	if (!x509)
+		goto cleanup;
+	der_len = i2d_X509(x509, &der_buf);
+	if (der_len < 0)
+		goto cleanup;
+	key_id = syscall(__NR_add_key, "asymmetric", key_der_path, der_buf,
+			     (size_t)der_len, KEY_SPEC_SESSION_KEYRING);
+cleanup:
+	X509_free(x509);
+	OPENSSL_free(der_buf);
+	DISPLAY_OSSL_ERR(key_id == -1);
+	return key_id;
+}
+
+int bpftool_prog_sign(struct bpf_load_and_run_opts *opts)
+{
+	BIO *bd_in = NULL, *bd_out = NULL;
+	EVP_PKEY *private_key = NULL;
+	CMS_ContentInfo *cms = NULL;
+	long actual_sig_len = 0;
+	X509 *x509 = NULL;
+	int err = 0;
+
+	bd_in = BIO_new_mem_buf(opts->insns, opts->insns_sz);
+	if (!bd_in) {
+		err = -ENOMEM;
+		goto cleanup;
+	}
+
+	private_key = read_private_key(private_key_path);
+	if (!private_key) {
+		err = -EINVAL;
+		goto cleanup;
+	}
+
+	x509 = read_x509(cert_path);
+	if (!x509) {
+		err = -EINVAL;
+		goto cleanup;
+	}
+
+	cms = CMS_sign(NULL, NULL, NULL, NULL,
+		       CMS_NOCERTS | CMS_PARTIAL | CMS_BINARY | CMS_DETACHED |
+			       CMS_STREAM);
+	if (!cms) {
+		err = -EINVAL;
+		goto cleanup;
+	}
+
+	if (!CMS_add1_signer(cms, x509, private_key, EVP_sha256(),
+			     CMS_NOCERTS | CMS_BINARY | CMS_NOSMIMECAP |
+			     CMS_USE_KEYID | CMS_NOATTR)) {
+		err = -EINVAL;
+		goto cleanup;
+	}
+
+	if (CMS_final(cms, bd_in, NULL, CMS_NOCERTS | CMS_BINARY) != 1) {
+		err = -EIO;
+		goto cleanup;
+	}
+
+	EVP_Digest(opts->insns, opts->insns_sz, opts->excl_prog_hash,
+		   &opts->excl_prog_hash_sz, EVP_sha256(), NULL);
+
+		bd_out = BIO_new(BIO_s_mem());
+	if (!bd_out) {
+		err = -ENOMEM;
+		goto cleanup;
+	}
+
+	if (!i2d_CMS_bio_stream(bd_out, cms, NULL, 0)) {
+		err = -EIO;
+		goto cleanup;
+	}
+
+	actual_sig_len = BIO_get_mem_data(bd_out, NULL);
+	if (actual_sig_len <= 0) {
+		err = -EIO;
+		goto cleanup;
+	}
+
+	if ((size_t)actual_sig_len > opts->signature_sz) {
+		err = -ENOSPC;
+		goto cleanup;
+	}
+
+	if (BIO_read(bd_out, opts->signature, actual_sig_len) != actual_sig_len) {
+		err = -EIO;
+		goto cleanup;
+	}
+
+	opts->signature_sz = actual_sig_len;
+cleanup:
+	BIO_free(bd_out);
+	CMS_ContentInfo_free(cms);
+	X509_free(x509);
+	EVP_PKEY_free(private_key);
+	BIO_free(bd_in);
+	DISPLAY_OSSL_ERR(err < 0);
+	return err;
+}
-- 
2.43.0


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

* [PATCH v2 12/13] selftests/bpf: Enable signature verification for all lskel tests
  2025-07-21 21:19 [PATCH v2 00/13] Signed BPF programs KP Singh
                   ` (10 preceding siblings ...)
  2025-07-21 21:19 ` [PATCH v2 11/13] bpftool: Add support for signing BPF programs KP Singh
@ 2025-07-21 21:19 ` KP Singh
  2025-07-29  2:27   ` Alexei Starovoitov
  2025-07-21 21:19 ` [PATCH v2 13/13] selftests/bpf: Add test for signed programs KP Singh
  12 siblings, 1 reply; 33+ messages in thread
From: KP Singh @ 2025-07-21 21:19 UTC (permalink / raw)
  To: bpf, linux-security-module
  Cc: bboscaccy, paul, kys, ast, daniel, andrii, KP Singh

Convert the kernel's generated verification certificate into a C header
file using xxd. Finally, update the main test runner to load this
certificate into the session keyring via the add_key() syscall before
executing any tests.

The test harness uses the verify_sig_setup.sh to generate the required
key material for program signing.

Signed-off-by: KP Singh <kpsingh@kernel.org>
---
 tools/testing/selftests/bpf/.gitignore        |  1 +
 tools/testing/selftests/bpf/Makefile          | 19 +++++++++++++++++--
 tools/testing/selftests/bpf/test_progs.c      | 13 +++++++++++++
 .../testing/selftests/bpf/verify_sig_setup.sh | 13 +++++++++++--
 4 files changed, 42 insertions(+), 4 deletions(-)

diff --git a/tools/testing/selftests/bpf/.gitignore b/tools/testing/selftests/bpf/.gitignore
index 3d8378972d26..be1ee7ba7ce0 100644
--- a/tools/testing/selftests/bpf/.gitignore
+++ b/tools/testing/selftests/bpf/.gitignore
@@ -44,3 +44,4 @@ xdp_redirect_multi
 xdp_synproxy
 xdp_hw_metadata
 xdp_features
+verification_cert.h
diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile
index 4863106034df..1295ff8f26ff 100644
--- a/tools/testing/selftests/bpf/Makefile
+++ b/tools/testing/selftests/bpf/Makefile
@@ -535,6 +535,7 @@ HEADERS_FOR_BPF_OBJS := $(wildcard $(BPFDIR)/*.bpf.h)		\
 # $2 - test runner extra "flavor" (e.g., no_alu32, cpuv4, bpf_gcc, etc)
 define DEFINE_TEST_RUNNER
 
+LSKEL_SIGN := -S -k $(PRIVATE_KEY) -i $(VERIFICATION_CERT)
 TRUNNER_OUTPUT := $(OUTPUT)$(if $2,/)$2
 TRUNNER_BINARY := $1$(if $2,-)$2
 TRUNNER_TEST_OBJS := $$(patsubst %.c,$$(TRUNNER_OUTPUT)/%.test.o,	\
@@ -601,7 +602,7 @@ $(TRUNNER_BPF_LSKELS): %.lskel.h: %.bpf.o $(BPFTOOL) | $(TRUNNER_OUTPUT)
 	$(Q)$$(BPFTOOL) gen object $$(<:.o=.llinked2.o) $$(<:.o=.llinked1.o)
 	$(Q)$$(BPFTOOL) gen object $$(<:.o=.llinked3.o) $$(<:.o=.llinked2.o)
 	$(Q)diff $$(<:.o=.llinked2.o) $$(<:.o=.llinked3.o)
-	$(Q)$$(BPFTOOL) gen skeleton -L $$(<:.o=.llinked3.o) name $$(notdir $$(<:.bpf.o=_lskel)) > $$@
+	$(Q)$$(BPFTOOL) gen skeleton $(LSKEL_SIGN) $$(<:.o=.llinked3.o) name $$(notdir $$(<:.bpf.o=_lskel)) > $$@
 	$(Q)rm -f $$(<:.o=.llinked1.o) $$(<:.o=.llinked2.o) $$(<:.o=.llinked3.o)
 
 $(LINKED_BPF_OBJS): %: $(TRUNNER_OUTPUT)/%
@@ -667,6 +668,7 @@ $(foreach N,$(patsubst $(TRUNNER_OUTPUT)/%.o,%,$(TRUNNER_EXTRA_OBJS)),	\
 $(TRUNNER_EXTRA_OBJS): $(TRUNNER_OUTPUT)/%.o:				\
 		       %.c						\
 		       $(TRUNNER_EXTRA_HDRS)				\
+		       $(VERIFY_SIG_HDR)				\
 		       $(TRUNNER_TESTS_HDR)				\
 		       $$(BPFOBJ) | $(TRUNNER_OUTPUT)
 	$$(call msg,EXT-OBJ,$(TRUNNER_BINARY),$$@)
@@ -697,6 +699,18 @@ $(OUTPUT)/$(TRUNNER_BINARY): $(TRUNNER_TEST_OBJS)			\
 
 endef
 
+VERIFY_SIG_SETUP := $(CURDIR)/verify_sig_setup.sh
+VERIFY_SIG_HDR := verification_cert.h
+VERIFICATION_CERT   := $(BUILD_DIR)/signing_key.der
+PRIVATE_KEY := $(BUILD_DIR)/signing_key.pem
+
+$(VERIFICATION_CERT) $(PRIVATE_KEY): $(VERIFY_SIG_SETUP)
+	$(Q)mkdir -p $(BUILD_DIR)
+	$(Q)$(VERIFY_SIG_SETUP) genkey $(BUILD_DIR)
+
+$(VERIFY_SIG_HDR): $(VERIFICATION_CERT)
+	$(Q)xxd -i -n test_progs_verification_cert $< > $@
+
 # Define test_progs test runner.
 TRUNNER_TESTS_DIR := prog_tests
 TRUNNER_BPF_PROGS_DIR := progs
@@ -716,6 +730,7 @@ TRUNNER_EXTRA_SOURCES := test_progs.c		\
 			 disasm.c		\
 			 disasm_helpers.c	\
 			 json_writer.c 		\
+			 $(VERIFY_SIG_HDR)		\
 			 flow_dissector_load.h	\
 			 ip_check_defrag_frags.h
 TRUNNER_EXTRA_FILES := $(OUTPUT)/urandom_read				\
@@ -725,7 +740,7 @@ TRUNNER_EXTRA_FILES := $(OUTPUT)/urandom_read				\
 		       $(OUTPUT)/uprobe_multi				\
 		       $(TEST_KMOD_TARGETS)				\
 		       ima_setup.sh 					\
-		       verify_sig_setup.sh				\
+		       $(VERIFY_SIG_SETUP)				\
 		       $(wildcard progs/btf_dump_test_case_*.c)		\
 		       $(wildcard progs/*.bpf.o)
 TRUNNER_BPF_BUILD_RULE := CLANG_BPF_BUILD_RULE
diff --git a/tools/testing/selftests/bpf/test_progs.c b/tools/testing/selftests/bpf/test_progs.c
index 309d9d4a8ace..02a85dda30e6 100644
--- a/tools/testing/selftests/bpf/test_progs.c
+++ b/tools/testing/selftests/bpf/test_progs.c
@@ -14,12 +14,14 @@
 #include <netinet/in.h>
 #include <sys/select.h>
 #include <sys/socket.h>
+#include <linux/keyctl.h>
 #include <sys/un.h>
 #include <bpf/btf.h>
 #include <time.h>
 #include "json_writer.h"
 
 #include "network_helpers.h"
+#include "verification_cert.h"
 
 /* backtrace() and backtrace_symbols_fd() are glibc specific,
  * use header file when glibc is available and provide stub
@@ -1928,6 +1930,13 @@ static void free_test_states(void)
 	}
 }
 
+static __u32 register_session_key(const char *key_data, size_t key_data_size)
+{
+	return syscall(__NR_add_key, "asymmetric", "libbpf_session_key",
+			(const void *)key_data, key_data_size,
+			KEY_SPEC_SESSION_KEYRING);
+}
+
 int main(int argc, char **argv)
 {
 	static const struct argp argp = {
@@ -1961,6 +1970,10 @@ int main(int argc, char **argv)
 	/* Use libbpf 1.0 API mode */
 	libbpf_set_strict_mode(LIBBPF_STRICT_ALL);
 	libbpf_set_print(libbpf_print_fn);
+	err = register_session_key((const char *)test_progs_verification_cert,
+				   test_progs_verification_cert_len);
+	if (err < 0)
+		return err;
 
 	traffic_monitor_set_print(traffic_monitor_print_fn);
 
diff --git a/tools/testing/selftests/bpf/verify_sig_setup.sh b/tools/testing/selftests/bpf/verify_sig_setup.sh
index f2cac42298ba..0834f504f66d 100755
--- a/tools/testing/selftests/bpf/verify_sig_setup.sh
+++ b/tools/testing/selftests/bpf/verify_sig_setup.sh
@@ -32,7 +32,7 @@ usage()
 	exit 1
 }
 
-setup()
+genkey()
 {
 	local tmp_dir="$1"
 
@@ -46,8 +46,15 @@ setup()
 	openssl x509 -in ${tmp_dir}/signing_key.pem -out \
 		${tmp_dir}/signing_key.der -outform der
 
-	key_id=$(cat ${tmp_dir}/signing_key.der | keyctl padd asymmetric ebpf_testing_key @s)
+}
 
+setup()
+{
+	local tmp_dir="$1"
+
+	genkey "${tmp_dir}"
+
+	key_id=$(cat ${tmp_dir}/signing_key.der | keyctl padd asymmetric ebpf_testing_key @s)
 	keyring_id=$(keyctl newring ebpf_testing_keyring @s)
 	keyctl link $key_id $keyring_id
 }
@@ -105,6 +112,8 @@ main()
 
 	if [[ "${action}" == "setup" ]]; then
 		setup "${tmp_dir}"
+	elif [[ "${action}" == "genkey" ]]; then
+		genkey "${tmp_dir}"
 	elif [[ "${action}" == "cleanup" ]]; then
 		cleanup "${tmp_dir}"
 	elif [[ "${action}" == "fsverity-create-sign" ]]; then
-- 
2.43.0


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

* [PATCH v2 13/13] selftests/bpf: Add test for signed programs
  2025-07-21 21:19 [PATCH v2 00/13] Signed BPF programs KP Singh
                   ` (11 preceding siblings ...)
  2025-07-21 21:19 ` [PATCH v2 12/13] selftests/bpf: Enable signature verification for all lskel tests KP Singh
@ 2025-07-21 21:19 ` KP Singh
  2025-07-29  2:30   ` Alexei Starovoitov
  12 siblings, 1 reply; 33+ messages in thread
From: KP Singh @ 2025-07-21 21:19 UTC (permalink / raw)
  To: bpf, linux-security-module
  Cc: bboscaccy, paul, kys, ast, daniel, andrii, KP Singh

This is a basic test that checks of bpf_prog_verify_signature is called
and returns a success for a valid program by loading a program that
captures the return value of bpf_prog_verify_signature and then loading
a signed skeleton

Signed-off-by: KP Singh <kpsingh@kernel.org>
---
 .../selftests/bpf/prog_tests/signing.c        | 36 +++++++++++++++++++
 tools/testing/selftests/bpf/progs/signing.c   | 16 +++++++++
 2 files changed, 52 insertions(+)
 create mode 100644 tools/testing/selftests/bpf/prog_tests/signing.c
 create mode 100644 tools/testing/selftests/bpf/progs/signing.c

diff --git a/tools/testing/selftests/bpf/prog_tests/signing.c b/tools/testing/selftests/bpf/prog_tests/signing.c
new file mode 100644
index 000000000000..0c4fca8cd86f
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/signing.c
@@ -0,0 +1,36 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2025 Google */
+#include <test_progs.h>
+#include "signing.skel.h"
+#include "fentry_test.lskel.h"
+
+void test_signing(void)
+{
+	struct signing *skel = NULL;
+	struct fentry_test_lskel *lskel = NULL;
+	int err;
+
+	/* load a program that verifies the result of signing */
+	skel = signing__open_and_load();
+	if (!ASSERT_OK_PTR(skel, "signing_skel_load"))
+		goto close_prog;
+
+	err = signing__attach(skel);
+	if (!ASSERT_OK(err, "signing_attach"))
+		goto close_prog;
+
+	/* Load a signed light skeleton */
+	lskel = fentry_test_lskel__open_and_load();
+	if (!ASSERT_OK_PTR(lskel, "signing_skel_load"))
+		goto close_prog;
+
+	err = fentry_test_lskel__attach(lskel);
+	if (!ASSERT_OK(err, "signing_attach"))
+		goto close_prog;
+
+	ASSERT_OK(skel->data->sig_verify_retval, "bpf_prog_verify_signature");
+
+close_prog:
+	signing__destroy(skel);
+	fentry_test_lskel__destroy(lskel);
+}
diff --git a/tools/testing/selftests/bpf/progs/signing.c b/tools/testing/selftests/bpf/progs/signing.c
new file mode 100644
index 000000000000..cc03f6363975
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/signing.c
@@ -0,0 +1,16 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2025 Google */
+#include "vmlinux.h"
+#include <limits.h>
+#include <bpf/bpf_tracing.h>
+
+char _license[] SEC("license") = "GPL";
+
+__u64 sig_verify_retval = -INT_MAX;
+
+SEC("fexit/bpf_prog_verify_signature")
+int BPF_PROG(bpf_sign, struct bpf_prog *prog, union bpf_attr *attr, bool is_kernel, int ret)
+{
+	sig_verify_retval = ret;
+	return 0;
+}
-- 
2.43.0


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

* Re: [PATCH v2 11/13] bpftool: Add support for signing BPF programs
  2025-07-21 21:19 ` [PATCH v2 11/13] bpftool: Add support for signing BPF programs KP Singh
@ 2025-07-22 15:51   ` Quentin Monnet
  2025-07-24 17:07     ` KP Singh
  0 siblings, 1 reply; 33+ messages in thread
From: Quentin Monnet @ 2025-07-22 15:51 UTC (permalink / raw)
  To: KP Singh, bpf, linux-security-module
  Cc: bboscaccy, paul, kys, ast, daniel, andrii

2025-07-21 23:19 UTC+0200 ~ KP Singh <kpsingh@kernel.org>
> Two modes of operation being added:
> 
> Add two modes of operation:
> 
> * For prog load, allow signing a program immediately before loading. This
>   is essential for command-line testing and administration.
> 
>       bpftool prog load -S -k <private_key> -i <identity_cert> fentry_test.bpf.o
> 
> * For gen skeleton, embed a pre-generated signature into the C skeleton
>   file. This supports the use of signed programs in compiled applications.
> 
>       bpftool gen skeleton -S -k <private_key> -i <identity_cert> fentry_test.bpf.o
> 
> Generation of the loader program and its metadata map is implemented in
> libbpf (bpf_obj__gen_loader). bpftool generates a skeleton that loads
> the program and automates the required steps: freezing the map, creating
> an exclusive map, loading, and running. Users can use standard libbpf
> APIs directly or integrate loader program generation into their own
> toolchains.


Thanks KP! Some bpftool-related comments below. Looks good overall, I
mostly have minor comments.

One concern might be the license for the new file, GPL-2.0 in your
patch, whereas bpftool is dual-licensed. I hope this is simply an oversight?


> 
> Signed-off-by: KP Singh <kpsingh@kernel.org>
> ---
>  .../bpf/bpftool/Documentation/bpftool-gen.rst |  12 +
>  .../bpftool/Documentation/bpftool-prog.rst    |  12 +
>  tools/bpf/bpftool/Makefile                    |   6 +-
>  tools/bpf/bpftool/cgroup.c                    |   5 +-
>  tools/bpf/bpftool/gen.c                       |  58 ++++-
>  tools/bpf/bpftool/main.c                      |  21 +-
>  tools/bpf/bpftool/main.h                      |  11 +
>  tools/bpf/bpftool/prog.c                      |  25 +++
>  tools/bpf/bpftool/sign.c                      | 210 ++++++++++++++++++
>  9 files changed, 352 insertions(+), 8 deletions(-)
>  create mode 100644 tools/bpf/bpftool/sign.c
> 
> diff --git a/tools/bpf/bpftool/Documentation/bpftool-gen.rst b/tools/bpf/bpftool/Documentation/bpftool-gen.rst
> index ca860fd97d8d..2997313003b1 100644
> --- a/tools/bpf/bpftool/Documentation/bpftool-gen.rst
> +++ b/tools/bpf/bpftool/Documentation/bpftool-gen.rst
> @@ -185,6 +185,18 @@ OPTIONS
>      For skeletons, generate a "light" skeleton (also known as "loader"
>      skeleton). A light skeleton contains a loader eBPF program. It does not use
>      the majority of the libbpf infrastructure, and does not need libelf.


Blank line separator, please


> +-S, --sign
> +    For skeletons, generate a signed skeleton. This option must be used with
> +    **-k** and **-i**. Using this flag implicitly enables **--use-loader**.
> +    See the "Signed Skeletons" section in the description of the
> +    **gen skeleton** command for more details.
> +
> +-k <private_key.pem>
> +    Path to the private key file in PEM format, required for signing.
> +
> +-i <certificate.x509>
> +    Path to the X.509 certificate file in PEM or DER format, required for
> +    signing.


Please also update the options list in the SYNOPSIS section at the top
of the page; and the option list at the bottom of gen.c (just like for
"--use-loader").

Can you also please take a look at the bash completion update? It
shouldn't be too hard if you look at how it deals with other options, in
particular --base-btf that also takes one argument - and I can help if
necessary.


>  
>  EXAMPLES
>  ========
> diff --git a/tools/bpf/bpftool/Documentation/bpftool-prog.rst b/tools/bpf/bpftool/Documentation/bpftool-prog.rst
> index f69fd92df8d8..dc2ca196137e 100644
> --- a/tools/bpf/bpftool/Documentation/bpftool-prog.rst
> +++ b/tools/bpf/bpftool/Documentation/bpftool-prog.rst
> @@ -248,6 +248,18 @@ OPTIONS
>      creating the maps, and loading the programs (see **bpftool prog tracelog**
>      as a way to dump those messages).
>  
> +-S, --sign
> +    Enable signing of the BPF program before loading. This option must be
> +    used with **-k** and **-i**. Using this flag implicitly enables
> +    **--use-loader**.
> +
> +-k <private_key.pem>
> +    Path to the private key file in PEM format, required when signing.
> +
> +-i <certificate.x509>
> +    Path to the X.509 certificate file in PEM or DER format, required when
> +    signing.


Same as for skeletons: please update the list of options in the synopsis
and at the bottom of prog.c (bash completion for skeletons' options
should also cover this case, so no additional work required here).


> +
>  EXAMPLES
>  ========
>  **# bpftool prog show**
> diff --git a/tools/bpf/bpftool/Makefile b/tools/bpf/bpftool/Makefile
> index 9e9a5f006cd2..586d1b2595d1 100644
> --- a/tools/bpf/bpftool/Makefile
> +++ b/tools/bpf/bpftool/Makefile
> @@ -130,8 +130,8 @@ include $(FEATURES_DUMP)
>  endif
>  endif
>  
> -LIBS = $(LIBBPF) -lelf -lz
> -LIBS_BOOTSTRAP = $(LIBBPF_BOOTSTRAP) -lelf -lz
> +LIBS = $(LIBBPF) -lelf -lz -lcrypto
> +LIBS_BOOTSTRAP = $(LIBBPF_BOOTSTRAP) -lelf -lz -lcrypto
>  
>  ifeq ($(feature-libelf-zstd),1)
>  LIBS += -lzstd
> @@ -194,7 +194,7 @@ endif
>  
>  BPFTOOL_BOOTSTRAP := $(BOOTSTRAP_OUTPUT)bpftool
>  
> -BOOTSTRAP_OBJS = $(addprefix $(BOOTSTRAP_OUTPUT),main.o common.o json_writer.o gen.o btf.o)
> +BOOTSTRAP_OBJS = $(addprefix $(BOOTSTRAP_OUTPUT),main.o common.o json_writer.o gen.o btf.o sign.o)
>  $(BOOTSTRAP_OBJS): $(LIBBPF_BOOTSTRAP)
>  
>  OBJS = $(patsubst %.c,$(OUTPUT)%.o,$(SRCS)) $(OUTPUT)disasm.o
> diff --git a/tools/bpf/bpftool/cgroup.c b/tools/bpf/bpftool/cgroup.c
> index 944ebe21a216..90c9aa297806 100644
> --- a/tools/bpf/bpftool/cgroup.c
> +++ b/tools/bpf/bpftool/cgroup.c
> @@ -1,7 +1,10 @@
>  // SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
>  // Copyright (C) 2017 Facebook
>  // Author: Roman Gushchin <guro@fb.com>
> -


Let's keep the blank line


> +#undef GCC_VERSION
> +#ifndef _GNU_SOURCE
> +#define _GNU_SOURCE
> +#endif


What are these for?


>  #define _XOPEN_SOURCE 500
>  #include <errno.h>
>  #include <fcntl.h>

[...]

> diff --git a/tools/bpf/bpftool/main.c b/tools/bpf/bpftool/main.c
> index 2b7f2bd3a7db..fc25bb390ec7 100644
> --- a/tools/bpf/bpftool/main.c
> +++ b/tools/bpf/bpftool/main.c
> @@ -33,6 +33,9 @@ bool relaxed_maps;
>  bool use_loader;
>  struct btf *base_btf;
>  struct hashmap *refs_table;
> +bool sign_progs;
> +const char *private_key_path;
> +const char *cert_path;
>  
>  static void __noreturn clean_and_exit(int i)
>  {
> @@ -447,6 +450,7 @@ int main(int argc, char **argv)
>  		{ "nomount",	no_argument,	NULL,	'n' },
>  		{ "debug",	no_argument,	NULL,	'd' },
>  		{ "use-loader",	no_argument,	NULL,	'L' },
> +		{ "sign",	required_argument, NULL, 'S'},
>  		{ "base-btf",	required_argument, NULL, 'B' },
>  		{ 0 }
>  	};
> @@ -473,7 +477,7 @@ int main(int argc, char **argv)
>  	bin_name = "bpftool";
>  
>  	opterr = 0;
> -	while ((opt = getopt_long(argc, argv, "VhpjfLmndB:l",
> +	while ((opt = getopt_long(argc, argv, "VhpjfLmndSi:k:B:l",
>  				  options, NULL)) >= 0) {
>  		switch (opt) {
>  		case 'V':
> @@ -519,6 +523,16 @@ int main(int argc, char **argv)
>  		case 'L':
>  			use_loader = true;
>  			break;
> +		case 'S':
> +			sign_progs = true;
> +			use_loader = true;
> +			break;
> +		case 'k':
> +			private_key_path = optarg;
> +			break;
> +		case 'i':
> +			cert_path = optarg;
> +			break;
>  		default:
>  			p_err("unrecognized option '%s'", argv[optind - 1]);
>  			if (json_output)
> @@ -533,6 +547,11 @@ int main(int argc, char **argv)
>  	if (argc < 0)
>  		usage();
>  
> +	if (sign_progs && (private_key_path == NULL || cert_path == NULL)) {
> +		p_err("-i <identity_x509_cert> and -k <private> key must be supplied with -S for signing");
> +		return -EINVAL;
> +	}


What if -i and/or -k are passed without -S?


> +
>  	if (version_requested)
>  		ret = do_version(argc, argv);
>  	else
> diff --git a/tools/bpf/bpftool/main.h b/tools/bpf/bpftool/main.h
> index 6db704fda5c0..f921af3cda87 100644
> --- a/tools/bpf/bpftool/main.h
> +++ b/tools/bpf/bpftool/main.h
> @@ -6,9 +6,14 @@
>  
>  /* BFD and kernel.h both define GCC_VERSION, differently */
>  #undef GCC_VERSION
> +#ifndef _GNU_SOURCE
> +#define _GNU_SOURCE
> +#endif
>  #include <stdbool.h>
>  #include <stdio.h>
> +#include <errno.h>
>  #include <stdlib.h>
> +#include <bpf/skel_internal.h>


Wnat do you need these includes (and _GNU_SOURCE) in main.h for?


>  #include <linux/bpf.h>
>  #include <linux/compiler.h>
>  #include <linux/kernel.h>

[...]

> diff --git a/tools/bpf/bpftool/sign.c b/tools/bpf/bpftool/sign.c
> new file mode 100644
> index 000000000000..f0b5dd10a46b
> --- /dev/null
> +++ b/tools/bpf/bpftool/sign.c
> @@ -0,0 +1,210 @@
> +// SPDX-License-Identifier: GPL-2.0


Please consider making this file dual-licensed like the rest of
bpftool's source code, "(GPL-2.0-only OR BSD-2-Clause)".


> +
> +/*
> + * Copyright (C) 2022 Google LLC.


2025?


> + */
> +#define _GNU_SOURCE


Please guard this:

	#ifndef _GNU_SOURCE
	#define _GNU_SOURCE
	#endif

This is because "llvm-config --cflags" passes -D_GNU_SOURCE and we may
end up with a duplicate definition, otherwise.


> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <stdint.h>
> +#include <stdbool.h>
> +#include <string.h>
> +#include <string.h>
> +#include <getopt.h>
> +#include <err.h>
> +#include <openssl/opensslv.h>
> +#include <openssl/bio.h>
> +#include <openssl/evp.h>
> +#include <openssl/pem.h>
> +#include <openssl/err.h>
> +#include <openssl/cms.h>
> +#include <linux/keyctl.h>
> +#include <errno.h>
> +
> +#include <bpf/skel_internal.h>
> +
> +#include "main.h"
> +
> +#define OPEN_SSL_ERR_BUF_LEN 256
> +
> +static void display_openssl_errors(int l)
> +{
> +	char buf[OPEN_SSL_ERR_BUF_LEN];
> +	const char *file;
> +	const char *data;
> +	unsigned long e;
> +	int flags;
> +	int line;
> +
> +	while ((e = ERR_get_error_all(&file, &line, NULL, &data, &flags))) {
> +		ERR_error_string_n(e, buf, sizeof(buf));
> +		if (data && (flags & ERR_TXT_STRING)) {
> +			p_err("OpenSSL %s: %s:%d: %s\n", buf, file, line, data);


Please remove the trailing '\n', p_err() handles it already.


> +		} else {
> +			p_err("OpenSSL %s: %s:%d\n", buf, file, line);


Same here.

[...]

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

* Re: [PATCH v2 07/13] bpf: Move the signature kfuncs to helpers.c
  2025-07-21 21:19 ` [PATCH v2 07/13] bpf: Move the signature kfuncs to helpers.c KP Singh
@ 2025-07-23 16:47   ` James Bottomley
  0 siblings, 0 replies; 33+ messages in thread
From: James Bottomley @ 2025-07-23 16:47 UTC (permalink / raw)
  To: KP Singh, bpf, linux-security-module
  Cc: bboscaccy, paul, kys, ast, daniel, andrii, Roberto Sassu

On Mon, 2025-07-21 at 23:19 +0200, KP Singh wrote:
> No functional changes, except for the addition of the headers for the
> kfuncs so that they can be used for signature verification.

Not really anything to do with this code, but looking at the whole of
the keyrings handing, it does seem to be a bit pointlessly redundant. 
There's actually no need for the

bool has_ref;

in struct bpf_key because there's already an exported macro that can
tell you that based on struct key *
(verification.h:system_id_keyring_check), so if you replaced that, you
could drop has_ref and then, I think you could eliminate the pointer
allocation as well meaning bpf_key simply becomes an alias for struct
key ... it should simplify the code quite a lot.  I can put a patch set
together and see what it looks like.

Regards,

James


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

* Re: [PATCH v2 08/13] bpf: Implement signature verification for BPF programs
  2025-07-21 21:19 ` [PATCH v2 08/13] bpf: Implement signature verification for BPF programs KP Singh
@ 2025-07-23 17:11   ` James Bottomley
  2025-07-24 17:22     ` KP Singh
  2025-07-31 15:57   ` Dan Carpenter
  2025-08-05 18:28   ` Blaise Boscaccy
  2 siblings, 1 reply; 33+ messages in thread
From: James Bottomley @ 2025-07-23 17:11 UTC (permalink / raw)
  To: KP Singh, bpf, linux-security-module
  Cc: bboscaccy, paul, kys, ast, daniel, andrii

On Mon, 2025-07-21 at 23:19 +0200, KP Singh wrote:
[...]

> diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h
> index fd3b895ebebf..b42c3740e053 100644
> --- a/include/uapi/linux/bpf.h
> +++ b/include/uapi/linux/bpf.h
> @@ -1607,6 +1607,16 @@ union bpf_attr {
>  		 * continuous.
>  		 */
>  		__u32		fd_array_cnt;
> +		/* Pointer to a buffer containing the signature of
> the BPF
> +		 * program.
> +		 */
> +		__aligned_u64   signature;
> +		/* Size of the signature buffer in bytes. */
> +		__u32 		signature_size;
> +		/* ID of the kernel keyring to be used for signature
> +		 * verification.
> +		 */
> +		__u32 		keyring_id;

This should become __s32 to match the value passed in to
bpf_lookup_user_key().

[...]
> diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
> index 22fda92ab7ce..111f91a99166 100644
> --- a/kernel/bpf/syscall.c
> +++ b/kernel/bpf/syscall.c
> @@ -2779,8 +2779,41 @@ static bool is_perfmon_prog_type(enum
> bpf_prog_type prog_type)
>  	}
>  }
>  
> +static noinline 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_key *key = NULL;
> +	void *sig;
> +	int err = 0;
> +
> +	key = bpf_lookup_user_key(attr->keyring_id, 0);
> +	if (!key)
> +		return -ENOKEY;

This still only checks against user keyrings and not system trusted
keyrings as was pointed out in v1.  Since user keyrings are negative
and user key serials begin at 3 or more, there's no overlap with the
system keyring specifiers and you can just overload attr->keyring_id,
like the below.

Regards,

James

---

diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
index 111f91a99166..10fd3ea5d91f 100644
--- a/kernel/bpf/syscall.c
+++ b/kernel/bpf/syscall.c
@@ -13,6 +13,7 @@
 #include <linux/slab.h>
 #include <linux/sched/signal.h>
 #include <linux/vmalloc.h>
+#include <linux/verification.h>
 #include <linux/mmzone.h>
 #include <linux/anon_inodes.h>
 #include <linux/fdtable.h>
@@ -2789,7 +2790,10 @@ static noinline int bpf_prog_verify_signature(struct bpf_prog *prog,
 	void *sig;
 	int err = 0;
 
-	key = bpf_lookup_user_key(attr->keyring_id, 0);
+	if (system_keyring_id_check(attr->keyring_id) == 0)
+		key = bpf_lookup_system_key(attr->keyring_id);
+	else
+		key = bpf_lookup_user_key(attr->keyring_id, 0);
 	if (!key)
 		return -ENOKEY;
 







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

* Re: [PATCH v2 11/13] bpftool: Add support for signing BPF programs
  2025-07-22 15:51   ` Quentin Monnet
@ 2025-07-24 17:07     ` KP Singh
  2025-08-11 14:23       ` KP Singh
  0 siblings, 1 reply; 33+ messages in thread
From: KP Singh @ 2025-07-24 17:07 UTC (permalink / raw)
  To: Quentin Monnet
  Cc: bpf, linux-security-module, bboscaccy, paul, kys, ast, daniel,
	andrii

On Tue, Jul 22, 2025 at 5:51 PM Quentin Monnet <qmo@kernel.org> wrote:
>
> 2025-07-21 23:19 UTC+0200 ~ KP Singh <kpsingh@kernel.org>
> > Two modes of operation being added:
> >
> > Add two modes of operation:
> >
> > * For prog load, allow signing a program immediately before loading. This
> >   is essential for command-line testing and administration.
> >
> >       bpftool prog load -S -k <private_key> -i <identity_cert> fentry_test.bpf.o
> >
> > * For gen skeleton, embed a pre-generated signature into the C skeleton
> >   file. This supports the use of signed programs in compiled applications.
> >
> >       bpftool gen skeleton -S -k <private_key> -i <identity_cert> fentry_test.bpf.o
> >
> > Generation of the loader program and its metadata map is implemented in
> > libbpf (bpf_obj__gen_loader). bpftool generates a skeleton that loads
> > the program and automates the required steps: freezing the map, creating
> > an exclusive map, loading, and running. Users can use standard libbpf
> > APIs directly or integrate loader program generation into their own
> > toolchains.
>
>
> Thanks KP! Some bpftool-related comments below. Looks good overall, I
> mostly have minor comments.
>
> One concern might be the license for the new file, GPL-2.0 in your
> patch, whereas bpftool is dual-licensed. I hope this is simply an oversight?

An oversight, fixed.

>
>
> >
> > Signed-off-by: KP Singh <kpsingh@kernel.org>
> > ---
> >  .../bpf/bpftool/Documentation/bpftool-gen.rst |  12 +
> >  .../bpftool/Documentation/bpftool-prog.rst    |  12 +
> >  tools/bpf/bpftool/Makefile                    |   6 +-
> >  tools/bpf/bpftool/cgroup.c                    |   5 +-
> >  tools/bpf/bpftool/gen.c                       |  58 ++++-
> >  tools/bpf/bpftool/main.c                      |  21 +-
> >  tools/bpf/bpftool/main.h                      |  11 +
> >  tools/bpf/bpftool/prog.c                      |  25 +++
> >  tools/bpf/bpftool/sign.c                      | 210 ++++++++++++++++++
> >  9 files changed, 352 insertions(+), 8 deletions(-)
> >  create mode 100644 tools/bpf/bpftool/sign.c
> >
> > diff --git a/tools/bpf/bpftool/Documentation/bpftool-gen.rst b/tools/bpf/bpftool/Documentation/bpftool-gen.rst
> > index ca860fd97d8d..2997313003b1 100644
> > --- a/tools/bpf/bpftool/Documentation/bpftool-gen.rst
> > +++ b/tools/bpf/bpftool/Documentation/bpftool-gen.rst
> > @@ -185,6 +185,18 @@ OPTIONS
> >      For skeletons, generate a "light" skeleton (also known as "loader"
> >      skeleton). A light skeleton contains a loader eBPF program. It does not use
> >      the majority of the libbpf infrastructure, and does not need libelf.
>
>
> Blank line separator, please

done

>
>
> > +-S, --sign
> > +    For skeletons, generate a signed skeleton. This option must be used with
> > +    **-k** and **-i**. Using this flag implicitly enables **--use-loader**.
> > +    See the "Signed Skeletons" section in the description of the
> > +    **gen skeleton** command for more details.
> > +
> > +-k <private_key.pem>
> > +    Path to the private key file in PEM format, required for signing.
> > +
> > +-i <certificate.x509>
> > +    Path to the X.509 certificate file in PEM or DER format, required for
> > +    signing.
>
>
> Please also update the options list in the SYNOPSIS section at the top
> of the page; and the option list at the bottom of gen.c (just like for
> "--use-loader").

done also, isn't this the right formatting for the SYNOPSIS given that
some of these are optional?

**bpftool** [*OPTIONS*] **prog** *COMMAND*
*OPTIONS* := { |COMMON_OPTIONS| [ { **-f** | **--bpffs** } ] [ {
**-m** | **--mapcompat** } ]
[ { **-n** | **--nomount** } ] [ { **-L** | **--use-loader** } ]
[ { { **-S** | **--sign** } **-k** <private_key.pem> **-i**
<certificate.x509> } ] }

not an expert here but I vaguely remember.

Also do you think we need to:

                { "use-loader", no_argument,    NULL,   'L' },
-               { "sign",       required_argument, NULL, 'S'},
+               { "sign",       no_argument,    NULL,   'S' },


Now that we don't use an argument blob for --sign?


>
> Can you also please take a look at the bash completion update? It
> shouldn't be too hard if you look at how it deals with other options, in
> particular --base-btf that also takes one argument - and I can help if
> necessary.

I will give it a go.

>
>
> >
> >  EXAMPLES
> >  ========
> > diff --git a/tools/bpf/bpftool/Documentation/bpftool-prog.rst b/tools/bpf/bpftool/Documentation/bpftool-prog.rst
> > index f69fd92df8d8..dc2ca196137e 100644
> > --- a/tools/bpf/bpftool/Documentation/bpftool-prog.rst
> > +++ b/tools/bpf/bpftool/Documentation/bpftool-prog.rst
> > @@ -248,6 +248,18 @@ OPTIONS
> >      creating the maps, and loading the programs (see **bpftool prog tracelog**
> >      as a way to dump those messages).
> >
> > +-S, --sign
> > +    Enable signing of the BPF program before loading. This option must be
> > +    used with **-k** and **-i**. Using this flag implicitly enables
> > +    **--use-loader**.
> > +
> > +-k <private_key.pem>
> > +    Path to the private key file in PEM format, required when signing.
> > +
> > +-i <certificate.x509>
> > +    Path to the X.509 certificate file in PEM or DER format, required when
> > +    signing.
>
>
> Same as for skeletons: please update the list of options in the synopsis
> and at the bottom of prog.c (bash completion for skeletons' options
> should also cover this case, so no additional work required here).
>
>
> > +
> >  EXAMPLES
> >  ========
> >  **# bpftool prog show**
> > diff --git a/tools/bpf/bpftool/Makefile b/tools/bpf/bpftool/Makefile
> > index 9e9a5f006cd2..586d1b2595d1 100644
> > --- a/tools/bpf/bpftool/Makefile
> > +++ b/tools/bpf/bpftool/Makefile
> > @@ -130,8 +130,8 @@ include $(FEATURES_DUMP)
> >  endif
> >  endif
> >
> > -LIBS = $(LIBBPF) -lelf -lz
> > -LIBS_BOOTSTRAP = $(LIBBPF_BOOTSTRAP) -lelf -lz
> > +LIBS = $(LIBBPF) -lelf -lz -lcrypto
> > +LIBS_BOOTSTRAP = $(LIBBPF_BOOTSTRAP) -lelf -lz -lcrypto
> >
> >  ifeq ($(feature-libelf-zstd),1)
> >  LIBS += -lzstd
> > @@ -194,7 +194,7 @@ endif
> >
> >  BPFTOOL_BOOTSTRAP := $(BOOTSTRAP_OUTPUT)bpftool
> >
> > -BOOTSTRAP_OBJS = $(addprefix $(BOOTSTRAP_OUTPUT),main.o common.o json_writer.o gen.o btf.o)
> > +BOOTSTRAP_OBJS = $(addprefix $(BOOTSTRAP_OUTPUT),main.o common.o json_writer.o gen.o btf.o sign.o)
> >  $(BOOTSTRAP_OBJS): $(LIBBPF_BOOTSTRAP)
> >
> >  OBJS = $(patsubst %.c,$(OUTPUT)%.o,$(SRCS)) $(OUTPUT)disasm.o
> > diff --git a/tools/bpf/bpftool/cgroup.c b/tools/bpf/bpftool/cgroup.c
> > index 944ebe21a216..90c9aa297806 100644
> > --- a/tools/bpf/bpftool/cgroup.c
> > +++ b/tools/bpf/bpftool/cgroup.c
> > @@ -1,7 +1,10 @@
> >  // SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> >  // Copyright (C) 2017 Facebook
> >  // Author: Roman Gushchin <guro@fb.com>
> > -
>
>
> Let's keep the blank line

Done

>
>
> > +#undef GCC_VERSION
> > +#ifndef _GNU_SOURCE
> > +#define _GNU_SOURCE
> > +#endif
>
>
> What are these for?

kpsingh@kpsingh-genoa:~/projects/linux/tools/bpf/bpftool$ vmk

Auto-detecting system features:
...                         clang-bpf-co-re: [ on  ]
...                                    llvm: [ on  ]
...                                  libcap: [ on  ]
...                                  libbfd: [ OFF ]

In file included from cgroup.c:19:
In file included from ./main.h:16:
/home/kpsingh/projects/linux/tools/bpf/bpftool/libbpf/include/bpf/skel_internal.h:87:9:
error: call to undeclared function 'syscall'; ISO C99 and later do not
support implicit function declarations
[-Wimplicit-function-declaration]
   87 |         return syscall(__NR_bpf, cmd, attr, size);
      |                ^
1 error generated.

>
>
> >  #define _XOPEN_SOURCE 500
> >  #include <errno.h>
> >  #include <fcntl.h>
>
> [...]
>
> > diff --git a/tools/bpf/bpftool/main.c b/tools/bpf/bpftool/main.c
> > index 2b7f2bd3a7db..fc25bb390ec7 100644
> > --- a/tools/bpf/bpftool/main.c
> > +++ b/tools/bpf/bpftool/main.c
> > @@ -33,6 +33,9 @@ bool relaxed_maps;
> >  bool use_loader;
> >  struct btf *base_btf;
> >  struct hashmap *refs_table;
> > +bool sign_progs;
> > +const char *private_key_path;
> > +const char *cert_path;
> >
> >  static void __noreturn clean_and_exit(int i)
> >  {
> > @@ -447,6 +450,7 @@ int main(int argc, char **argv)
> >               { "nomount",    no_argument,    NULL,   'n' },
> >               { "debug",      no_argument,    NULL,   'd' },
> >               { "use-loader", no_argument,    NULL,   'L' },
> > +             { "sign",       required_argument, NULL, 'S'},
> >               { "base-btf",   required_argument, NULL, 'B' },
> >               { 0 }
> >       };
> > @@ -473,7 +477,7 @@ int main(int argc, char **argv)
> >       bin_name = "bpftool";
> >
> >       opterr = 0;
> > -     while ((opt = getopt_long(argc, argv, "VhpjfLmndB:l",
> > +     while ((opt = getopt_long(argc, argv, "VhpjfLmndSi:k:B:l",
> >                                 options, NULL)) >= 0) {
> >               switch (opt) {
> >               case 'V':
> > @@ -519,6 +523,16 @@ int main(int argc, char **argv)
> >               case 'L':
> >                       use_loader = true;
> >                       break;
> > +             case 'S':
> > +                     sign_progs = true;
> > +                     use_loader = true;
> > +                     break;
> > +             case 'k':
> > +                     private_key_path = optarg;
> > +                     break;
> > +             case 'i':
> > +                     cert_path = optarg;
> > +                     break;
> >               default:
> >                       p_err("unrecognized option '%s'", argv[optind - 1]);
> >                       if (json_output)
> > @@ -533,6 +547,11 @@ int main(int argc, char **argv)
> >       if (argc < 0)
> >               usage();
> >
> > +     if (sign_progs && (private_key_path == NULL || cert_path == NULL)) {
> > +             p_err("-i <identity_x509_cert> and -k <private> key must be supplied with -S for signing");
> > +             return -EINVAL;
> > +     }
>
>
> What if -i and/or -k are passed without -S?

We can either print a warning or error out

A) User does not want to sign removes --sign and forgets to remove -i
-k (better with warning)
B) User wants to sign but forgets to --sign (better with error)

I'd say we print an error so that we don't accidentally not sign, WDYT?

The reason why I think we should keep an explicit --sign is because we
can also extend this to have e.g. --verify.

- KP

>
>
> > +
> >       if (version_requested)
> >               ret = do_version(argc, argv);
> >       else
> > diff --git a/tools/bpf/bpftool/main.h b/tools/bpf/bpftool/main.h
> > index 6db704fda5c0..f921af3cda87 100644
> > --- a/tools/bpf/bpftool/main.h
> > +++ b/tools/bpf/bpftool/main.h
> > @@ -6,9 +6,14 @@
> >
> >  /* BFD and kernel.h both define GCC_VERSION, differently */
> >  #undef GCC_VERSION
> > +#ifndef _GNU_SOURCE
> > +#define _GNU_SOURCE
> > +#endif
> >  #include <stdbool.h>
> >  #include <stdio.h>
> > +#include <errno.h>
> >  #include <stdlib.h>
> > +#include <bpf/skel_internal.h>
>
>
> Wnat do you need these includes (and _GNU_SOURCE) in main.h for?

Explained above, let me know if you have better ideas on where to place these.

>
>
> >  #include <linux/bpf.h>
> >  #include <linux/compiler.h>
> >  #include <linux/kernel.h>
>
> [...]
>
> > diff --git a/tools/bpf/bpftool/sign.c b/tools/bpf/bpftool/sign.c
> > new file mode 100644
> > index 000000000000..f0b5dd10a46b
> > --- /dev/null
> > +++ b/tools/bpf/bpftool/sign.c
> > @@ -0,0 +1,210 @@
> > +// SPDX-License-Identifier: GPL-2.0
>
>
> Please consider making this file dual-licensed like the rest of
> bpftool's source code, "(GPL-2.0-only OR BSD-2-Clause)".

Done.

>
>
> > +
> > +/*
> > + * Copyright (C) 2022 Google LLC.
>
>
> 2025?

Let's keep it 2022, nah just kidding :) Thanks.

>
>
> > + */
> > +#define _GNU_SOURCE
>
>
> Please guard this:
>
>         #ifndef _GNU_SOURCE
>         #define _GNU_SOURCE
>         #endif
>
> This is because "llvm-config --cflags" passes -D_GNU_SOURCE and we may
> end up with a duplicate definition, otherwise.

ack, done.

>
>
> > +#include <stdio.h>
> > +#include <stdlib.h>
> > +#include <stdint.h>
> > +#include <stdbool.h>
> > +#include <string.h>
> > +#include <string.h>
> > +#include <getopt.h>
> > +#include <err.h>
> > +#include <openssl/opensslv.h>
> > +#include <openssl/bio.h>
> > +#include <openssl/evp.h>
> > +#include <openssl/pem.h>
> > +#include <openssl/err.h>
> > +#include <openssl/cms.h>
> > +#include <linux/keyctl.h>
> > +#include <errno.h>
> > +
> > +#include <bpf/skel_internal.h>
> > +
> > +#include "main.h"
> > +
> > +#define OPEN_SSL_ERR_BUF_LEN 256
> > +
> > +static void display_openssl_errors(int l)
> > +{
> > +     char buf[OPEN_SSL_ERR_BUF_LEN];
> > +     const char *file;
> > +     const char *data;
> > +     unsigned long e;
> > +     int flags;
> > +     int line;
> > +
> > +     while ((e = ERR_get_error_all(&file, &line, NULL, &data, &flags))) {
> > +             ERR_error_string_n(e, buf, sizeof(buf));
> > +             if (data && (flags & ERR_TXT_STRING)) {
> > +                     p_err("OpenSSL %s: %s:%d: %s\n", buf, file, line, data);
>
>
> Please remove the trailing '\n', p_err() handles it already.
>
>
> > +             } else {
> > +                     p_err("OpenSSL %s: %s:%d\n", buf, file, line);
>
>
> Same here.

done.

- KP

>
> [...]

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

* Re: [PATCH v2 08/13] bpf: Implement signature verification for BPF programs
  2025-07-23 17:11   ` James Bottomley
@ 2025-07-24 17:22     ` KP Singh
  0 siblings, 0 replies; 33+ messages in thread
From: KP Singh @ 2025-07-24 17:22 UTC (permalink / raw)
  To: James Bottomley
  Cc: bpf, linux-security-module, bboscaccy, paul, kys, ast, daniel,
	andrii

On Wed, Jul 23, 2025 at 7:11 PM James Bottomley
<James.Bottomley@hansenpartnership.com> wrote:
>
> On Mon, 2025-07-21 at 23:19 +0200, KP Singh wrote:
> [...]
>
> > diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h
> > index fd3b895ebebf..b42c3740e053 100644
> > --- a/include/uapi/linux/bpf.h
> > +++ b/include/uapi/linux/bpf.h
> > @@ -1607,6 +1607,16 @@ union bpf_attr {
> >                * continuous.
> >                */
> >               __u32           fd_array_cnt;
> > +             /* Pointer to a buffer containing the signature of
> > the BPF
> > +              * program.
> > +              */
> > +             __aligned_u64   signature;
> > +             /* Size of the signature buffer in bytes. */
> > +             __u32           signature_size;
> > +             /* ID of the kernel keyring to be used for signature
> > +              * verification.
> > +              */
> > +             __u32           keyring_id;
>
> This should become __s32 to match the value passed in to
> bpf_lookup_user_key().
>
> [...]
> > diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
> > index 22fda92ab7ce..111f91a99166 100644
> > --- a/kernel/bpf/syscall.c
> > +++ b/kernel/bpf/syscall.c
> > @@ -2779,8 +2779,41 @@ static bool is_perfmon_prog_type(enum
> > bpf_prog_type prog_type)
> >       }
> >  }
> >
> > +static noinline 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_key *key = NULL;
> > +     void *sig;
> > +     int err = 0;
> > +
> > +     key = bpf_lookup_user_key(attr->keyring_id, 0);
> > +     if (!key)
> > +             return -ENOKEY;
>
> This still only checks against user keyrings and not system trusted
> keyrings as was pointed out in v1.  Since user keyrings are negative
> and user key serials begin at 3 or more, there's no overlap with the
> system keyring specifiers and you can just overload attr->keyring_id,
> like the below.
>
> Regards,
>
> James
>
> ---
>
> diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
> index 111f91a99166..10fd3ea5d91f 100644
> --- a/kernel/bpf/syscall.c
> +++ b/kernel/bpf/syscall.c
> @@ -13,6 +13,7 @@
>  #include <linux/slab.h>
>  #include <linux/sched/signal.h>
>  #include <linux/vmalloc.h>
> +#include <linux/verification.h>
>  #include <linux/mmzone.h>
>  #include <linux/anon_inodes.h>
>  #include <linux/fdtable.h>
> @@ -2789,7 +2790,10 @@ static noinline int bpf_prog_verify_signature(struct bpf_prog *prog,
>         void *sig;
>         int err = 0;
>
> -       key = bpf_lookup_user_key(attr->keyring_id, 0);
> +       if (system_keyring_id_check(attr->keyring_id) == 0)
> +               key = bpf_lookup_system_key(attr->keyring_id);
> +       else
> +               key = bpf_lookup_user_key(attr->keyring_id, 0);
>         if (!key)
>                 return -ENOKEY;
>

Thanks James, updated.

- KP

>

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

* Re: [PATCH v2 04/13] libbpf: Support exclusive map creation
  2025-07-21 21:19 ` [PATCH v2 04/13] libbpf: Support exclusive map creation KP Singh
@ 2025-07-29  2:25   ` Alexei Starovoitov
  2025-08-11 22:18     ` KP Singh
  0 siblings, 1 reply; 33+ messages in thread
From: Alexei Starovoitov @ 2025-07-29  2:25 UTC (permalink / raw)
  To: KP Singh
  Cc: bpf, LSM List, Blaise Boscaccy, Paul Moore, K. Y. Srinivasan,
	Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko

On Mon, Jul 21, 2025 at 2:20 PM KP Singh <kpsingh@kernel.org> wrote:
>
>
> +/**
> + * @brief **bpf_map__get_exclusive_program()** returns the exclusive program
> + * that is registered with the map (if any).
> + * @param map BPF map to which the exclusive program is registered.
> + * @return the registered exclusive program.
> + */
> +LIBBPF_API struct bpf_program *bpf_map__get_exclusive_program(struct bpf_map *map);

I couldn't find patches where it's used.
Do we actually need it?

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

* Re: [PATCH v2 12/13] selftests/bpf: Enable signature verification for all lskel tests
  2025-07-21 21:19 ` [PATCH v2 12/13] selftests/bpf: Enable signature verification for all lskel tests KP Singh
@ 2025-07-29  2:27   ` Alexei Starovoitov
  2025-08-11 22:20     ` KP Singh
  0 siblings, 1 reply; 33+ messages in thread
From: Alexei Starovoitov @ 2025-07-29  2:27 UTC (permalink / raw)
  To: KP Singh
  Cc: bpf, LSM List, Blaise Boscaccy, Paul Moore, K. Y. Srinivasan,
	Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko

On Mon, Jul 21, 2025 at 2:20 PM KP Singh <kpsingh@kernel.org> wrote:
>

>
> +LSKEL_SIGN := -S -k $(PRIVATE_KEY) -i $(VERIFICATION_CERT)
>  TRUNNER_OUTPUT := $(OUTPUT)$(if $2,/)$2
>  TRUNNER_BINARY := $1$(if $2,-)$2
>  TRUNNER_TEST_OBJS := $$(patsubst %.c,$$(TRUNNER_OUTPUT)/%.test.o,      \
> @@ -601,7 +602,7 @@ $(TRUNNER_BPF_LSKELS): %.lskel.h: %.bpf.o $(BPFTOOL) | $(TRUNNER_OUTPUT)
>         $(Q)$$(BPFTOOL) gen object $$(<:.o=.llinked2.o) $$(<:.o=.llinked1.o)
>         $(Q)$$(BPFTOOL) gen object $$(<:.o=.llinked3.o) $$(<:.o=.llinked2.o)
>         $(Q)diff $$(<:.o=.llinked2.o) $$(<:.o=.llinked3.o)
> -       $(Q)$$(BPFTOOL) gen skeleton -L $$(<:.o=.llinked3.o) name $$(notdir $$(<:.bpf.o=_lskel)) > $$@
> +       $(Q)$$(BPFTOOL) gen skeleton $(LSKEL_SIGN) $$(<:.o=.llinked3.o) name $$(notdir $$(<:.bpf.o=_lskel)) > $$@
>         $(Q)rm -f $$(<:.o=.llinked1.o) $$(<:.o=.llinked2.o) $$(<:.o=.llinked3.o)

Does it mean that it makes all lskel tests to be signed tests ?
It's great that CI green lights it, but imo it's an overkill.
Let's have a few signed tests instead of making all of them.

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

* Re: [PATCH v2 13/13] selftests/bpf: Add test for signed programs
  2025-07-21 21:19 ` [PATCH v2 13/13] selftests/bpf: Add test for signed programs KP Singh
@ 2025-07-29  2:30   ` Alexei Starovoitov
  2025-08-11 14:24     ` KP Singh
  0 siblings, 1 reply; 33+ messages in thread
From: Alexei Starovoitov @ 2025-07-29  2:30 UTC (permalink / raw)
  To: KP Singh
  Cc: bpf, LSM List, Blaise Boscaccy, Paul Moore, K. Y. Srinivasan,
	Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko

On Mon, Jul 21, 2025 at 2:20 PM KP Singh <kpsingh@kernel.org> wrote:
> +
> +SEC("fexit/bpf_prog_verify_signature")
> +int BPF_PROG(bpf_sign, struct bpf_prog *prog, union bpf_attr *attr, bool is_kernel, int ret)

I don't understand why it needs to peek into the kernel to
verify that it goes well. The exposed uapi should be good enough.
If the signature was specified and it is loaded fine we're good.
Double checking the kernel decisions goes too far.
Especially since this function can be inlined by the compiler.

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

* Re: [PATCH v2 02/13] bpf: Implement exclusive map creation
  2025-07-21 21:19 ` [PATCH v2 02/13] bpf: Implement exclusive map creation KP Singh
@ 2025-07-29 22:59   ` Fan Wu
  2025-08-11 22:48     ` KP Singh
  0 siblings, 1 reply; 33+ messages in thread
From: Fan Wu @ 2025-07-29 22:59 UTC (permalink / raw)
  To: KP Singh
  Cc: bpf, linux-security-module, bboscaccy, paul, kys, ast, daniel,
	andrii

On Mon, Jul 21, 2025 at 2:35 PM KP Singh <kpsingh@kernel.org> wrote:
>
> Exclusive maps allow maps to only be accessed by program with a
> program with a matching hash which is specified in the excl_prog_hash
> attr.
>
> For the signing use-case, this allows the trusted loader program
> to load the map and verify the integrity
>
> Signed-off-by: KP Singh <kpsingh@kernel.org>
> ---
>  include/linux/bpf.h            |  1 +
>  include/uapi/linux/bpf.h       |  2 ++
>  kernel/bpf/syscall.c           | 32 ++++++++++++++++++++++++++++----
>  kernel/bpf/verifier.c          |  6 ++++++
>  tools/include/uapi/linux/bpf.h |  2 ++
>  5 files changed, 39 insertions(+), 4 deletions(-)
>

...

> -static int map_create(union bpf_attr *attr, bool kernel)
> +static int map_create(union bpf_attr *attr, bpfptr_t uattr)
>  {
>         const struct bpf_map_ops *ops;
>         struct bpf_token *token = NULL;
> @@ -1527,7 +1528,30 @@ static int map_create(union bpf_attr *attr, bool kernel)
>                         attr->btf_vmlinux_value_type_id;
>         }
>
> -       err = security_bpf_map_create(map, attr, token, kernel);
> +       if (attr->excl_prog_hash) {
> +               bpfptr_t uprog_hash = make_bpfptr(attr->excl_prog_hash, uattr.is_kernel);
> +
> +               map->excl_prog_sha = kzalloc(SHA256_DIGEST_SIZE, GFP_KERNEL);
> +               if (!map->excl_prog_sha) {
> +                       err = -ENOMEM;
> +                       goto free_map;
> +               }
> +
> +               if (attr->excl_prog_hash_size != SHA256_DIGEST_SIZE) {
> +                       err = -EINVAL;
> +                       goto free_map;
> +               }

Nit: Maybe check the size first to avoid unncessary kzalloc?

-Fan

> +
> +               if (copy_from_bpfptr(map->excl_prog_sha, uprog_hash,
> +                                    SHA256_DIGEST_SIZE)) {
> +                       err = -EFAULT;
> +                       goto free_map;
> +               }
> +       } else if (attr->excl_prog_hash_size) {
> +               return -EINVAL;
> +       }
> +
> +       err = security_bpf_map_create(map, attr, token, uattr.is_kernel);
>         if (err)
>                 goto free_map_sec;
>

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

* Re: [PATCH v2 08/13] bpf: Implement signature verification for BPF programs
  2025-07-21 21:19 ` [PATCH v2 08/13] bpf: Implement signature verification for BPF programs KP Singh
  2025-07-23 17:11   ` James Bottomley
@ 2025-07-31 15:57   ` Dan Carpenter
  2025-08-11 22:22     ` KP Singh
  2025-08-05 18:28   ` Blaise Boscaccy
  2 siblings, 1 reply; 33+ messages in thread
From: Dan Carpenter @ 2025-07-31 15:57 UTC (permalink / raw)
  To: oe-kbuild, KP Singh, bpf, linux-security-module
  Cc: lkp, oe-kbuild-all, bboscaccy, paul, kys, ast, daniel, andrii,
	KP Singh

Hi KP,

kernel test robot noticed the following build warnings:

https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/KP-Singh/bpf-Update-the-bpf_prog_calc_tag-to-use-SHA256/20250722-052316
base:   https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git master
patch link:    https://lore.kernel.org/r/20250721211958.1881379-9-kpsingh%40kernel.org
patch subject: [PATCH v2 08/13] bpf: Implement signature verification for BPF programs
config: m68k-randconfig-r073-20250723 (https://download.01.org/0day-ci/archive/20250723/202507231202.8rYZJ8D1-lkp@intel.com/config)
compiler: m68k-linux-gcc (GCC) 14.3.0

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Reported-by: Dan Carpenter <dan.carpenter@linaro.org>
| Closes: https://lore.kernel.org/r/202507231202.8rYZJ8D1-lkp@intel.com/

smatch warnings:
kernel/bpf/syscall.c:2797 bpf_prog_verify_signature() warn: 'sig' is an error pointer or valid

vim +/sig +2797 kernel/bpf/syscall.c

c83b0ba795b625 KP Singh           2025-07-21  2782  static noinline int bpf_prog_verify_signature(struct bpf_prog *prog,
c83b0ba795b625 KP Singh           2025-07-21  2783  					      union bpf_attr *attr,
c83b0ba795b625 KP Singh           2025-07-21  2784  					      bool is_kernel)
c83b0ba795b625 KP Singh           2025-07-21  2785  {
c83b0ba795b625 KP Singh           2025-07-21  2786  	bpfptr_t usig = make_bpfptr(attr->signature, is_kernel);
c83b0ba795b625 KP Singh           2025-07-21  2787  	struct bpf_dynptr_kern sig_ptr, insns_ptr;
c83b0ba795b625 KP Singh           2025-07-21  2788  	struct bpf_key *key = NULL;
c83b0ba795b625 KP Singh           2025-07-21  2789  	void *sig;
c83b0ba795b625 KP Singh           2025-07-21  2790  	int err = 0;
c83b0ba795b625 KP Singh           2025-07-21  2791  
c83b0ba795b625 KP Singh           2025-07-21  2792  	key = bpf_lookup_user_key(attr->keyring_id, 0);
c83b0ba795b625 KP Singh           2025-07-21  2793  	if (!key)
c83b0ba795b625 KP Singh           2025-07-21  2794  		return -ENOKEY;
c83b0ba795b625 KP Singh           2025-07-21  2795  
c83b0ba795b625 KP Singh           2025-07-21  2796  	sig = kvmemdup_bpfptr(usig, attr->signature_size);
c83b0ba795b625 KP Singh           2025-07-21 @2797  	if (!sig) {

This should be an if (!IS_ERR(sig)) { check.

c83b0ba795b625 KP Singh           2025-07-21  2798  		bpf_key_put(key);
c83b0ba795b625 KP Singh           2025-07-21  2799  		return -ENOMEM;
c83b0ba795b625 KP Singh           2025-07-21  2800  	}
c83b0ba795b625 KP Singh           2025-07-21  2801  
c83b0ba795b625 KP Singh           2025-07-21  2802  	bpf_dynptr_init(&sig_ptr, sig, BPF_DYNPTR_TYPE_LOCAL, 0,
c83b0ba795b625 KP Singh           2025-07-21  2803  			attr->signature_size);

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki


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

* Re: [PATCH v2 08/13] bpf: Implement signature verification for BPF programs
  2025-07-21 21:19 ` [PATCH v2 08/13] bpf: Implement signature verification for BPF programs KP Singh
  2025-07-23 17:11   ` James Bottomley
  2025-07-31 15:57   ` Dan Carpenter
@ 2025-08-05 18:28   ` Blaise Boscaccy
  2025-08-13  2:20     ` Paul Moore
  2 siblings, 1 reply; 33+ messages in thread
From: Blaise Boscaccy @ 2025-08-05 18:28 UTC (permalink / raw)
  To: KP Singh, bpf, linux-security-module
  Cc: paul, kys, ast, daniel, andrii, KP Singh, James.Bottomley, wufan

KP Singh <kpsingh@kernel.org> writes:

> This patch extends the BPF_PROG_LOAD command by adding three new fields
> to `union bpf_attr` in the user-space API:
>
>   - signature: A pointer to the signature blob.
>   - signature_size: The size of the signature blob.
>   - keyring_id: The serial number of a loaded kernel keyring (e.g.,
>     the user or session keyring) containing the trusted public keys.
>
> When a BPF program is loaded with a signature, the kernel:
>
> 1.  Retrieves the trusted keyring using the provided `keyring_id`.
> 2.  Verifies the supplied signature against the BPF program's
>     instruction buffer.
> 3.  If the signature is valid and was generated by a key in the trusted
>     keyring, the program load proceeds.
> 4.  If no signature is provided, the load proceeds as before, allowing
>     for backward compatibility. LSMs can chose to restrict unsigned
>     programs and implement a security policy.
> 5.  If signature verification fails for any reason,
>     the program is not loaded.
[...]

The following is what we propose to build on top of this to implement
in-kernel hash chain verification. This allows for signature
verification of arbitrary maps and isn't coupled to light-skeletons or
any specific implementation.


From: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
Date: Mon, 28 Jul 2025 08:14:57 -0700
Subject: bpf: Add hash chain signature support for arbitrary maps

This patch introduces hash chain support for signature verification of
arbitrary bpf map objects which was described here:
https://lore.kernel.org/linux-security-module/20250721211958.1881379-1-kpsingh@kernel.org/

The UAPI is extended to allow for in-kernel checking of maps passed in
via the fd_array. A hash chain is constructed from the maps, in order
specified by the signature_maps field. The hash chain is terminated
with the hash of the program itself.

Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
---
 include/uapi/linux/bpf.h       |  6 +++
 kernel/bpf/syscall.c           | 75 ++++++++++++++++++++++++++++++++--
 tools/include/uapi/linux/bpf.h |  6 +++
 3 files changed, 83 insertions(+), 4 deletions(-)

diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h
index b42c3740e053e..c83f2a34674fd 100644
--- a/include/uapi/linux/bpf.h
+++ b/include/uapi/linux/bpf.h
@@ -1617,6 +1617,12 @@ union bpf_attr {
 		 * verification.
 		 */
 		__u32 		keyring_id;
+		/* Pointer to a buffer containing the maps used in the signature
+		 * hash chain of the BPF program.
+		 */
+		__aligned_u64   signature_maps;
+		/* Size of the signature maps buffer. */
+		__u32		signature_maps_size;
 	};
 
 	struct { /* anonymous struct used by BPF_OBJ_* commands */
diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
index 10fd3ea5d91fd..f7e9bcabd9dcc 100644
--- a/kernel/bpf/syscall.c
+++ b/kernel/bpf/syscall.c
@@ -2780,15 +2780,36 @@ static bool is_perfmon_prog_type(enum bpf_prog_type prog_type)
 	}
 }
 
+static inline int bpf_map_get_hash(int map_fd, void *buffer)
+{
+	struct bpf_map *map;
+
+	CLASS(fd, f)(map_fd);
+	map = __bpf_map_get(f);
+	if (IS_ERR(map))
+		return PTR_ERR(map);
+
+	if (!map->ops->map_get_hash)
+		return -EINVAL;
+
+	return map->ops->map_get_hash(map, SHA256_DIGEST_SIZE, buffer);
+}
+
 static noinline 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;
+	bpfptr_t umaps;
+	struct bpf_dynptr_kern sig_ptr, insns_ptr, hash_ptr;
 	struct bpf_key *key = NULL;
 	void *sig;
+	int *maps;
+	int map_fd;
 	int err = 0;
+	u64 buffer[8];
+	u64 hash[4];
+	int n;
 
 	if (system_keyring_id_check(attr->keyring_id) == 0)
 		key = bpf_lookup_system_key(attr->keyring_id);
@@ -2808,16 +2829,62 @@ static noinline int bpf_prog_verify_signature(struct bpf_prog *prog,
 	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,
-					 (struct bpf_dynptr *)&sig_ptr, key);
+	if (!attr->signature_maps_size) {
+		err = bpf_verify_pkcs7_signature((struct bpf_dynptr *)&insns_ptr,
+						 (struct bpf_dynptr *)&sig_ptr, key);
+	} else {
+		bpf_dynptr_init(&hash_ptr, hash, BPF_DYNPTR_TYPE_LOCAL, 0,
+				sizeof(hash));
+		umaps = make_bpfptr(attr->signature_maps, is_kernel);
+		maps = kvmemdup_bpfptr(umaps, attr->signature_maps_size * sizeof(*maps));
+		if (!maps) {
+			err = -ENOMEM;
+			goto out;
+		}
+		n = attr->signature_maps_size - 1;
+		err = copy_from_bpfptr_offset(&map_fd, make_bpfptr(attr->fd_array, is_kernel),
+					      maps[n] * sizeof(map_fd),
+					      sizeof(map_fd));
+		if (err < 0)
+			goto free_maps;
+
+		err = bpf_map_get_hash(map_fd, hash);
+		if (err != 0)
+			goto free_maps;
+
+		n--;
+		while (n >= 0) {
+			memcpy(buffer, hash, sizeof(hash));
+			err = copy_from_bpfptr_offset(&map_fd,
+						      make_bpfptr(attr->fd_array, is_kernel),
+						      maps[n] * sizeof(map_fd),
+						      sizeof(map_fd));
+			if (err < 0)
+				goto free_maps;
+
+			err = bpf_map_get_hash(map_fd, buffer+4);
+			if (err != 0)
+				goto free_maps;
+			sha256((u8 *)buffer, sizeof(buffer), (u8 *)&hash);
+			n--;
+		}
+		sha256((u8 *)prog->insnsi, prog->len * sizeof(struct bpf_insn), (u8 *)&buffer);
+		memcpy(buffer+4, hash, sizeof(hash));
+		sha256((u8 *)buffer, sizeof(buffer), (u8 *)&hash);
+		err = bpf_verify_pkcs7_signature((struct bpf_dynptr *)&hash_ptr,
+						 (struct bpf_dynptr *)&sig_ptr, key);
 
+free_maps:
+		kvfree(maps);
+	}
+out:
 	bpf_key_put(key);
 	kvfree(sig);
 	return err;
 }
 
 /* last field in 'union bpf_attr' used by this command */
-#define BPF_PROG_LOAD_LAST_FIELD keyring_id
+#define BPF_PROG_LOAD_LAST_FIELD signature_maps_size
 
 static int bpf_prog_load(union bpf_attr *attr, bpfptr_t uattr, u32 uattr_size)
 {
diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h
index b42c3740e053e..c83f2a34674fd 100644
--- a/tools/include/uapi/linux/bpf.h
+++ b/tools/include/uapi/linux/bpf.h
@@ -1617,6 +1617,12 @@ union bpf_attr {
 		 * verification.
 		 */
 		__u32 		keyring_id;
+		/* Pointer to a buffer containing the maps used in the signature
+		 * hash chain of the BPF program.
+		 */
+		__aligned_u64   signature_maps;
+		/* Size of the signature maps buffer. */
+		__u32		signature_maps_size;
 	};
 
 	struct { /* anonymous struct used by BPF_OBJ_* commands */
-- 
2.48.1


And here is a reference usage implementation:

From: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
Date: Mon, 28 Jul 2025 21:30:56 -0700
Subject: libbpf: Add hash chain signing support to light  skeletons.

This patch introduces a hash chain signing support for light-skeleton
assets. A new flag '-M' is added which constructs a hash chain with
the loader program and the target payload.

Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
---
 tools/bpf/bpftool/gen.c       | 25 +++++++++++++++++++++++++
 tools/bpf/bpftool/main.c      |  8 +++++++-
 tools/bpf/bpftool/main.h      |  1 +
 tools/bpf/bpftool/sign.c      | 17 ++++++++++++++---
 tools/lib/bpf/libbpf.h        |  3 ++-
 tools/lib/bpf/skel_internal.h |  6 +++++-
 6 files changed, 54 insertions(+), 6 deletions(-)

diff --git a/tools/bpf/bpftool/gen.c b/tools/bpf/bpftool/gen.c
index ab6fc86598ad3..e660fbc701c5d 100644
--- a/tools/bpf/bpftool/gen.c
+++ b/tools/bpf/bpftool/gen.c
@@ -699,6 +699,9 @@ static int gen_trace(struct bpf_object *obj, const char *obj_name, const char *h
 	if (sign_progs)
 		opts.gen_hash = true;
 
+	if (sign_maps)
+		opts.sign_maps = true;
+
 	err = bpf_object__gen_loader(obj, &opts);
 	if (err)
 		return err;
@@ -793,6 +796,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.data = opts.data;
+		sopts.data_sz = opts.data_sz;
 		sopts.excl_prog_hash = prog_sha;
 		sopts.excl_prog_hash_sz = sizeof(prog_sha);
 		sopts.signature = sig_buf;
@@ -821,6 +826,13 @@ static int gen_trace(struct bpf_object *obj, const char *obj_name, const char *h
 		\n\
 		\";\n");
 
+		if (sign_maps) {
+			codegen("\
+			\n\
+				static const int opts_signature_maps[1] __attribute__((__aligned__(8))) = {0}; \n\
+			");
+		}
+
 		codegen("\
 		\n\
 			opts.signature = (void *)opts_sig;			\n\
@@ -829,6 +841,19 @@ 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 = KEY_SPEC_SESSION_KEYRING;		\n\
 		");
+		if (sign_maps) {
+			codegen("\
+			\n\
+				opts.signature_maps = (void *)opts_signature_maps;	\n\
+				opts.signature_maps_sz = 1; 				\n\
+			");
+		} else {
+			codegen("\
+			\n\
+				opts.signature_maps = (void *)NULL;		\n\
+				opts.signature_maps_sz = 0;			\n\
+			");
+		}
 	}
 
 	codegen("\
diff --git a/tools/bpf/bpftool/main.c b/tools/bpf/bpftool/main.c
index fc25bb390ec71..287e8205494cb 100644
--- a/tools/bpf/bpftool/main.c
+++ b/tools/bpf/bpftool/main.c
@@ -34,6 +34,7 @@ bool use_loader;
 struct btf *base_btf;
 struct hashmap *refs_table;
 bool sign_progs;
+bool sign_maps;
 const char *private_key_path;
 const char *cert_path;
 
@@ -477,7 +478,7 @@ int main(int argc, char **argv)
 	bin_name = "bpftool";
 
 	opterr = 0;
-	while ((opt = getopt_long(argc, argv, "VhpjfLmndSi:k:B:l",
+	while ((opt = getopt_long(argc, argv, "VhpjfLmndSMi:k:B:l",
 				  options, NULL)) >= 0) {
 		switch (opt) {
 		case 'V':
@@ -527,6 +528,11 @@ int main(int argc, char **argv)
 			sign_progs = true;
 			use_loader = true;
 			break;
+		case 'M':
+			sign_maps = true;
+			sign_progs = true;
+			use_loader = true;
+			break;
 		case 'k':
 			private_key_path = optarg;
 			break;
diff --git a/tools/bpf/bpftool/main.h b/tools/bpf/bpftool/main.h
index f921af3cda87f..805c3d87a1330 100644
--- a/tools/bpf/bpftool/main.h
+++ b/tools/bpf/bpftool/main.h
@@ -92,6 +92,7 @@ extern bool use_loader;
 extern struct btf *base_btf;
 extern struct hashmap *refs_table;
 extern bool sign_progs;
+extern bool sign_maps;
 extern const char *private_key_path;
 extern const char *cert_path;
 
diff --git a/tools/bpf/bpftool/sign.c b/tools/bpf/bpftool/sign.c
index f0b5dd10a46b2..d5514b7d2b82d 100644
--- a/tools/bpf/bpftool/sign.c
+++ b/tools/bpf/bpftool/sign.c
@@ -22,6 +22,7 @@
 #include <errno.h>
 
 #include <bpf/skel_internal.h>
+#include <bpf/libbpf_internal.h>
 
 #include "main.h"
 
@@ -129,8 +130,18 @@ int bpftool_prog_sign(struct bpf_load_and_run_opts *opts)
 	long actual_sig_len = 0;
 	X509 *x509 = NULL;
 	int err = 0;
-
-	bd_in = BIO_new_mem_buf(opts->insns, opts->insns_sz);
+	unsigned char hash[SHA256_DIGEST_LENGTH  * 2];
+	unsigned char term[SHA256_DIGEST_LENGTH];
+
+	if (sign_maps) {
+		libbpf_sha256(opts->insns, opts->insns_sz, hash, SHA256_DIGEST_LENGTH);
+		libbpf_sha256(opts->data, opts->data_sz, hash + SHA256_DIGEST_LENGTH,
+			      SHA256_DIGEST_LENGTH);
+		libbpf_sha256(hash, sizeof(hash), term, sizeof(term));
+		bd_in = BIO_new_mem_buf(term, sizeof(term));
+	} else {
+		bd_in = BIO_new_mem_buf(opts->insns, opts->insns_sz);
+	}
 	if (!bd_in) {
 		err = -ENOMEM;
 		goto cleanup;
@@ -171,7 +182,7 @@ int bpftool_prog_sign(struct bpf_load_and_run_opts *opts)
 	EVP_Digest(opts->insns, opts->insns_sz, opts->excl_prog_hash,
 		   &opts->excl_prog_hash_sz, EVP_sha256(), NULL);
 
-		bd_out = BIO_new(BIO_s_mem());
+	bd_out = BIO_new(BIO_s_mem());
 	if (!bd_out) {
 		err = -ENOMEM;
 		goto cleanup;
diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h
index 7cad8470d9ebe..aad0288cd05e3 100644
--- a/tools/lib/bpf/libbpf.h
+++ b/tools/lib/bpf/libbpf.h
@@ -1827,9 +1827,10 @@ struct gen_loader_opts {
 	__u32 data_sz;
 	__u32 insns_sz;
 	bool gen_hash;
+	bool sign_maps;
 };
 
-#define gen_loader_opts__last_field gen_hash
+#define gen_loader_opts__last_field sign_maps
 LIBBPF_API int bpf_object__gen_loader(struct bpf_object *obj,
 				      struct gen_loader_opts *opts);
 
diff --git a/tools/lib/bpf/skel_internal.h b/tools/lib/bpf/skel_internal.h
index 5b6d1b09dc8a6..c25a4f1308e44 100644
--- a/tools/lib/bpf/skel_internal.h
+++ b/tools/lib/bpf/skel_internal.h
@@ -74,6 +74,8 @@ struct bpf_load_and_run_opts {
 	__u32 keyring_id;
 	void * excl_prog_hash;
 	__u32 excl_prog_hash_sz;
+	const int *signature_maps;
+	__u32 signature_maps_sz;
 };
 
 long kern_sys_bpf(__u32 cmd, void *attr, __u32 attr_size);
@@ -351,7 +353,7 @@ 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 prog_load_attr_sz = offsetofend(union bpf_attr, signature_maps_size);
 	const size_t test_run_attr_sz = offsetofend(union bpf_attr, test);
 	int map_fd = -1, prog_fd = -1, key = 0, err;
 	union bpf_attr attr;
@@ -394,6 +396,8 @@ static inline int bpf_load_and_run(struct bpf_load_and_run_opts *opts)
 #ifndef __KERNEL__
 	attr.signature = (long) opts->signature;
 	attr.signature_size = opts->signature_sz;
+	attr.signature_maps = (long) opts->signature_maps;
+	attr.signature_maps_size = opts->signature_maps_sz;
 #else
 	if (opts->signature || opts->signature_sz)
 		pr_warn("signatures are not supported from bpf_preload\n");
-- 
2.48.1

-blaise

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

* Re: [PATCH v2 11/13] bpftool: Add support for signing BPF programs
  2025-07-24 17:07     ` KP Singh
@ 2025-08-11 14:23       ` KP Singh
  2025-08-11 14:39         ` Quentin Monnet
  0 siblings, 1 reply; 33+ messages in thread
From: KP Singh @ 2025-08-11 14:23 UTC (permalink / raw)
  To: Quentin Monnet
  Cc: bpf, linux-security-module, bboscaccy, paul, kys, ast, daniel,
	andrii

On Thu, Jul 24, 2025 at 7:07 PM KP Singh <kpsingh@kernel.org> wrote:
>
> On Tue, Jul 22, 2025 at 5:51 PM Quentin Monnet <qmo@kernel.org> wrote:
> >
> > 2025-07-21 23:19 UTC+0200 ~ KP Singh <kpsingh@kernel.org>
> > > Two modes of operation being added:
> > >
> > > Add two modes of operation:
> > >
> > > * For prog load, allow signing a program immediately before loading. This
> > >   is essential for command-line testing and administration.
> > >
> > >       bpftool prog load -S -k <private_key> -i <identity_cert> fentry_test.bpf.o
> > >
> > > * For gen skeleton, embed a pre-generated signature into the C skeleton
> > >   file. This supports the use of signed programs in compiled applications.
> > >
> > >       bpftool gen skeleton -S -k <private_key> -i <identity_cert> fentry_test.bpf.o
> > >
> > > Generation of the loader program and its metadata map is implemented in
> > > libbpf (bpf_obj__gen_loader). bpftool generates a skeleton that loads
> > > the program and automates the required steps: freezing the map, creating
> > > an exclusive map, loading, and running. Users can use standard libbpf
> > > APIs directly or integrate loader program generation into their own
> > > toolchains.
> >
> >
> > Thanks KP! Some bpftool-related comments below. Looks good overall, I
> > mostly have minor comments.
> >
> > One concern might be the license for the new file, GPL-2.0 in your
> > patch, whereas bpftool is dual-licensed. I hope this is simply an oversight?
>
> An oversight, fixed.
>
> >
> >
> > >
> > > Signed-off-by: KP Singh <kpsingh@kernel.org>
> > > ---
> > >  .../bpf/bpftool/Documentation/bpftool-gen.rst |  12 +
> > >  .../bpftool/Documentation/bpftool-prog.rst    |  12 +
> > >  tools/bpf/bpftool/Makefile                    |   6 +-
> > >  tools/bpf/bpftool/cgroup.c                    |   5 +-
> > >  tools/bpf/bpftool/gen.c                       |  58 ++++-
> > >  tools/bpf/bpftool/main.c                      |  21 +-
> > >  tools/bpf/bpftool/main.h                      |  11 +
> > >  tools/bpf/bpftool/prog.c                      |  25 +++
> > >  tools/bpf/bpftool/sign.c                      | 210 ++++++++++++++++++
> > >  9 files changed, 352 insertions(+), 8 deletions(-)
> > >  create mode 100644 tools/bpf/bpftool/sign.c
> > >
> > > diff --git a/tools/bpf/bpftool/Documentation/bpftool-gen.rst b/tools/bpf/bpftool/Documentation/bpftool-gen.rst
> > > index ca860fd97d8d..2997313003b1 100644
> > > --- a/tools/bpf/bpftool/Documentation/bpftool-gen.rst
> > > +++ b/tools/bpf/bpftool/Documentation/bpftool-gen.rst
> > > @@ -185,6 +185,18 @@ OPTIONS
> > >      For skeletons, generate a "light" skeleton (also known as "loader"
> > >      skeleton). A light skeleton contains a loader eBPF program. It does not use
> > >      the majority of the libbpf infrastructure, and does not need libelf.
> >
> >
> > Blank line separator, please
>
> done
>
> >
> >
> > > +-S, --sign
> > > +    For skeletons, generate a signed skeleton. This option must be used with
> > > +    **-k** and **-i**. Using this flag implicitly enables **--use-loader**.
> > > +    See the "Signed Skeletons" section in the description of the
> > > +    **gen skeleton** command for more details.
> > > +
> > > +-k <private_key.pem>
> > > +    Path to the private key file in PEM format, required for signing.
> > > +
> > > +-i <certificate.x509>
> > > +    Path to the X.509 certificate file in PEM or DER format, required for
> > > +    signing.
> >
> >
> > Please also update the options list in the SYNOPSIS section at the top
> > of the page; and the option list at the bottom of gen.c (just like for
> > "--use-loader").
>
> done also, isn't this the right formatting for the SYNOPSIS given that
> some of these are optional?
>
> **bpftool** [*OPTIONS*] **prog** *COMMAND*
> *OPTIONS* := { |COMMON_OPTIONS| [ { **-f** | **--bpffs** } ] [ {
> **-m** | **--mapcompat** } ]
> [ { **-n** | **--nomount** } ] [ { **-L** | **--use-loader** } ]
> [ { { **-S** | **--sign** } **-k** <private_key.pem> **-i**
> <certificate.x509> } ] }
>
> not an expert here but I vaguely remember.
>
> Also do you think we need to:
>
>                 { "use-loader", no_argument,    NULL,   'L' },
> -               { "sign",       required_argument, NULL, 'S'},
> +               { "sign",       no_argument,    NULL,   'S' },
>
>
> Now that we don't use an argument blob for --sign?
>
>
> >
> > Can you also please take a look at the bash completion update? It
> > shouldn't be too hard if you look at how it deals with other options, in
> > particular --base-btf that also takes one argument - and I can help if
> > necessary.
>
> I will give it a go.
>
> >
> >
> > >
> > >  EXAMPLES
> > >  ========
> > > diff --git a/tools/bpf/bpftool/Documentation/bpftool-prog.rst b/tools/bpf/bpftool/Documentation/bpftool-prog.rst
> > > index f69fd92df8d8..dc2ca196137e 100644
> > > --- a/tools/bpf/bpftool/Documentation/bpftool-prog.rst
> > > +++ b/tools/bpf/bpftool/Documentation/bpftool-prog.rst
> > > @@ -248,6 +248,18 @@ OPTIONS
> > >      creating the maps, and loading the programs (see **bpftool prog tracelog**
> > >      as a way to dump those messages).
> > >
> > > +-S, --sign
> > > +    Enable signing of the BPF program before loading. This option must be
> > > +    used with **-k** and **-i**. Using this flag implicitly enables
> > > +    **--use-loader**.
> > > +
> > > +-k <private_key.pem>
> > > +    Path to the private key file in PEM format, required when signing.
> > > +
> > > +-i <certificate.x509>
> > > +    Path to the X.509 certificate file in PEM or DER format, required when
> > > +    signing.
> >
> >
> > Same as for skeletons: please update the list of options in the synopsis
> > and at the bottom of prog.c (bash completion for skeletons' options
> > should also cover this case, so no additional work required here).
> >
> >
> > > +
> > >  EXAMPLES
> > >  ========
> > >  **# bpftool prog show**
> > > diff --git a/tools/bpf/bpftool/Makefile b/tools/bpf/bpftool/Makefile
> > > index 9e9a5f006cd2..586d1b2595d1 100644
> > > --- a/tools/bpf/bpftool/Makefile
> > > +++ b/tools/bpf/bpftool/Makefile
> > > @@ -130,8 +130,8 @@ include $(FEATURES_DUMP)
> > >  endif
> > >  endif
> > >
> > > -LIBS = $(LIBBPF) -lelf -lz
> > > -LIBS_BOOTSTRAP = $(LIBBPF_BOOTSTRAP) -lelf -lz
> > > +LIBS = $(LIBBPF) -lelf -lz -lcrypto
> > > +LIBS_BOOTSTRAP = $(LIBBPF_BOOTSTRAP) -lelf -lz -lcrypto
> > >
> > >  ifeq ($(feature-libelf-zstd),1)
> > >  LIBS += -lzstd
> > > @@ -194,7 +194,7 @@ endif
> > >
> > >  BPFTOOL_BOOTSTRAP := $(BOOTSTRAP_OUTPUT)bpftool
> > >
> > > -BOOTSTRAP_OBJS = $(addprefix $(BOOTSTRAP_OUTPUT),main.o common.o json_writer.o gen.o btf.o)
> > > +BOOTSTRAP_OBJS = $(addprefix $(BOOTSTRAP_OUTPUT),main.o common.o json_writer.o gen.o btf.o sign.o)
> > >  $(BOOTSTRAP_OBJS): $(LIBBPF_BOOTSTRAP)
> > >
> > >  OBJS = $(patsubst %.c,$(OUTPUT)%.o,$(SRCS)) $(OUTPUT)disasm.o
> > > diff --git a/tools/bpf/bpftool/cgroup.c b/tools/bpf/bpftool/cgroup.c
> > > index 944ebe21a216..90c9aa297806 100644
> > > --- a/tools/bpf/bpftool/cgroup.c
> > > +++ b/tools/bpf/bpftool/cgroup.c
> > > @@ -1,7 +1,10 @@
> > >  // SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> > >  // Copyright (C) 2017 Facebook
> > >  // Author: Roman Gushchin <guro@fb.com>
> > > -
> >
> >
> > Let's keep the blank line
>
> Done
>
> >
> >
> > > +#undef GCC_VERSION
> > > +#ifndef _GNU_SOURCE
> > > +#define _GNU_SOURCE
> > > +#endif
> >
> >
> > What are these for?
>
> kpsingh@kpsingh-genoa:~/projects/linux/tools/bpf/bpftool$ vmk
>
> Auto-detecting system features:
> ...                         clang-bpf-co-re: [ on  ]
> ...                                    llvm: [ on  ]
> ...                                  libcap: [ on  ]
> ...                                  libbfd: [ OFF ]
>
> In file included from cgroup.c:19:
> In file included from ./main.h:16:
> /home/kpsingh/projects/linux/tools/bpf/bpftool/libbpf/include/bpf/skel_internal.h:87:9:
> error: call to undeclared function 'syscall'; ISO C99 and later do not
> support implicit function declarations
> [-Wimplicit-function-declaration]
>    87 |         return syscall(__NR_bpf, cmd, attr, size);
>       |                ^
> 1 error generated.
>
> >
> >
> > >  #define _XOPEN_SOURCE 500
> > >  #include <errno.h>
> > >  #include <fcntl.h>
> >
> > [...]
> >
> > > diff --git a/tools/bpf/bpftool/main.c b/tools/bpf/bpftool/main.c
> > > index 2b7f2bd3a7db..fc25bb390ec7 100644
> > > --- a/tools/bpf/bpftool/main.c
> > > +++ b/tools/bpf/bpftool/main.c
> > > @@ -33,6 +33,9 @@ bool relaxed_maps;
> > >  bool use_loader;
> > >  struct btf *base_btf;
> > >  struct hashmap *refs_table;
> > > +bool sign_progs;
> > > +const char *private_key_path;
> > > +const char *cert_path;
> > >
> > >  static void __noreturn clean_and_exit(int i)
> > >  {
> > > @@ -447,6 +450,7 @@ int main(int argc, char **argv)
> > >               { "nomount",    no_argument,    NULL,   'n' },
> > >               { "debug",      no_argument,    NULL,   'd' },
> > >               { "use-loader", no_argument,    NULL,   'L' },
> > > +             { "sign",       required_argument, NULL, 'S'},
> > >               { "base-btf",   required_argument, NULL, 'B' },
> > >               { 0 }
> > >       };
> > > @@ -473,7 +477,7 @@ int main(int argc, char **argv)
> > >       bin_name = "bpftool";
> > >
> > >       opterr = 0;
> > > -     while ((opt = getopt_long(argc, argv, "VhpjfLmndB:l",
> > > +     while ((opt = getopt_long(argc, argv, "VhpjfLmndSi:k:B:l",
> > >                                 options, NULL)) >= 0) {
> > >               switch (opt) {
> > >               case 'V':
> > > @@ -519,6 +523,16 @@ int main(int argc, char **argv)
> > >               case 'L':
> > >                       use_loader = true;
> > >                       break;
> > > +             case 'S':
> > > +                     sign_progs = true;
> > > +                     use_loader = true;
> > > +                     break;
> > > +             case 'k':
> > > +                     private_key_path = optarg;
> > > +                     break;
> > > +             case 'i':
> > > +                     cert_path = optarg;
> > > +                     break;
> > >               default:
> > >                       p_err("unrecognized option '%s'", argv[optind - 1]);
> > >                       if (json_output)
> > > @@ -533,6 +547,11 @@ int main(int argc, char **argv)
> > >       if (argc < 0)
> > >               usage();
> > >
> > > +     if (sign_progs && (private_key_path == NULL || cert_path == NULL)) {
> > > +             p_err("-i <identity_x509_cert> and -k <private> key must be supplied with -S for signing");
> > > +             return -EINVAL;
> > > +     }
> >
> >
> > What if -i and/or -k are passed without -S?
>
> We can either print a warning or error out
>
> A) User does not want to sign removes --sign and forgets to remove -i
> -k (better with warning)
> B) User wants to sign but forgets to --sign (better with error)
>
> I'd say we print an error so that we don't accidentally not sign, WDYT?
>
> The reason why I think we should keep an explicit --sign is because we
> can also extend this to have e.g. --verify.

if (!sign_progs && (private_key_path != NULL || cert_path != NULL)) {
p_err("-i <identity_x509_cert> and -k <private> also need --sign to be
used for sign programs");
return -EINVAL;
}

I will error out, I was waiting for Quentin's reply, we can fix it
later if needed.

- KP

>
> - KP
>
> >
> >
> > > +
> > >       if (version_requested)
> > >               ret = do_version(argc, argv);
> > >       else
> > > diff --git a/tools/bpf/bpftool/main.h b/tools/bpf/bpftool/main.h
> > > index 6db704fda5c0..f921af3cda87 100644
> > > --- a/tools/bpf/bpftool/main.h
> > > +++ b/tools/bpf/bpftool/main.h
> > > @@ -6,9 +6,14 @@
> > >
> > >  /* BFD and kernel.h both define GCC_VERSION, differently */
> > >  #undef GCC_VERSION
> > > +#ifndef _GNU_SOURCE
> > > +#define _GNU_SOURCE
> > > +#endif
> > >  #include <stdbool.h>
> > >  #include <stdio.h>
> > > +#include <errno.h>
> > >  #include <stdlib.h>
> > > +#include <bpf/skel_internal.h>
> >
> >
> > Wnat do you need these includes (and _GNU_SOURCE) in main.h for?
>
> Explained above, let me know if you have better ideas on where to place these.
>
> >
> >
> > >  #include <linux/bpf.h>
> > >  #include <linux/compiler.h>
> > >  #include <linux/kernel.h>
> >
> > [...]
> >
> > > diff --git a/tools/bpf/bpftool/sign.c b/tools/bpf/bpftool/sign.c
> > > new file mode 100644
> > > index 000000000000..f0b5dd10a46b
> > > --- /dev/null
> > > +++ b/tools/bpf/bpftool/sign.c
> > > @@ -0,0 +1,210 @@
> > > +// SPDX-License-Identifier: GPL-2.0
> >
> >
> > Please consider making this file dual-licensed like the rest of
> > bpftool's source code, "(GPL-2.0-only OR BSD-2-Clause)".
>
> Done.
>
> >
> >
> > > +
> > > +/*
> > > + * Copyright (C) 2022 Google LLC.
> >
> >
> > 2025?
>
> Let's keep it 2022, nah just kidding :) Thanks.
>
> >
> >
> > > + */
> > > +#define _GNU_SOURCE
> >
> >
> > Please guard this:
> >
> >         #ifndef _GNU_SOURCE
> >         #define _GNU_SOURCE
> >         #endif
> >
> > This is because "llvm-config --cflags" passes -D_GNU_SOURCE and we may
> > end up with a duplicate definition, otherwise.
>
> ack, done.
>
> >
> >
> > > +#include <stdio.h>
> > > +#include <stdlib.h>
> > > +#include <stdint.h>
> > > +#include <stdbool.h>
> > > +#include <string.h>
> > > +#include <string.h>
> > > +#include <getopt.h>
> > > +#include <err.h>
> > > +#include <openssl/opensslv.h>
> > > +#include <openssl/bio.h>
> > > +#include <openssl/evp.h>
> > > +#include <openssl/pem.h>
> > > +#include <openssl/err.h>
> > > +#include <openssl/cms.h>
> > > +#include <linux/keyctl.h>
> > > +#include <errno.h>
> > > +
> > > +#include <bpf/skel_internal.h>
> > > +
> > > +#include "main.h"
> > > +
> > > +#define OPEN_SSL_ERR_BUF_LEN 256
> > > +
> > > +static void display_openssl_errors(int l)
> > > +{
> > > +     char buf[OPEN_SSL_ERR_BUF_LEN];
> > > +     const char *file;
> > > +     const char *data;
> > > +     unsigned long e;
> > > +     int flags;
> > > +     int line;
> > > +
> > > +     while ((e = ERR_get_error_all(&file, &line, NULL, &data, &flags))) {
> > > +             ERR_error_string_n(e, buf, sizeof(buf));
> > > +             if (data && (flags & ERR_TXT_STRING)) {
> > > +                     p_err("OpenSSL %s: %s:%d: %s\n", buf, file, line, data);
> >
> >
> > Please remove the trailing '\n', p_err() handles it already.
> >
> >
> > > +             } else {
> > > +                     p_err("OpenSSL %s: %s:%d\n", buf, file, line);
> >
> >
> > Same here.
>
> done.
>
> - KP
>
> >
> > [...]

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

* Re: [PATCH v2 13/13] selftests/bpf: Add test for signed programs
  2025-07-29  2:30   ` Alexei Starovoitov
@ 2025-08-11 14:24     ` KP Singh
  0 siblings, 0 replies; 33+ messages in thread
From: KP Singh @ 2025-08-11 14:24 UTC (permalink / raw)
  To: Alexei Starovoitov
  Cc: bpf, LSM List, Blaise Boscaccy, Paul Moore, K. Y. Srinivasan,
	Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko

On Tue, Jul 29, 2025 at 4:30 AM Alexei Starovoitov
<alexei.starovoitov@gmail.com> wrote:
>
> On Mon, Jul 21, 2025 at 2:20 PM KP Singh <kpsingh@kernel.org> wrote:
> > +
> > +SEC("fexit/bpf_prog_verify_signature")
> > +int BPF_PROG(bpf_sign, struct bpf_prog *prog, union bpf_attr *attr, bool is_kernel, int ret)
>
> I don't understand why it needs to peek into the kernel to
> verify that it goes well. The exposed uapi should be good enough.
> If the signature was specified and it is loaded fine we're good.
> Double checking the kernel decisions goes too far.
> Especially since this function can be inlined by the compiler.

Fair, I will drop this patch

- KP

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

* Re: [PATCH v2 11/13] bpftool: Add support for signing BPF programs
  2025-08-11 14:23       ` KP Singh
@ 2025-08-11 14:39         ` Quentin Monnet
  0 siblings, 0 replies; 33+ messages in thread
From: Quentin Monnet @ 2025-08-11 14:39 UTC (permalink / raw)
  To: KP Singh
  Cc: bpf, linux-security-module, bboscaccy, paul, kys, ast, daniel,
	andrii

2025-08-11 16:23 UTC+0200 ~ KP Singh <kpsingh@kernel.org>
> On Thu, Jul 24, 2025 at 7:07 PM KP Singh <kpsingh@kernel.org> wrote:
>>
>> On Tue, Jul 22, 2025 at 5:51 PM Quentin Monnet <qmo@kernel.org> wrote:
>>>
>>> 2025-07-21 23:19 UTC+0200 ~ KP Singh <kpsingh@kernel.org>

[...]

>>>> @@ -533,6 +547,11 @@ int main(int argc, char **argv)
>>>>       if (argc < 0)
>>>>               usage();
>>>>
>>>> +     if (sign_progs && (private_key_path == NULL || cert_path == NULL)) {
>>>> +             p_err("-i <identity_x509_cert> and -k <private> key must be supplied with -S for signing");
>>>> +             return -EINVAL;
>>>> +     }
>>>
>>>
>>> What if -i and/or -k are passed without -S?
>>
>> We can either print a warning or error out
>>
>> A) User does not want to sign removes --sign and forgets to remove -i
>> -k (better with warning)
>> B) User wants to sign but forgets to --sign (better with error)
>>
>> I'd say we print an error so that we don't accidentally not sign, WDYT?
>>
>> The reason why I think we should keep an explicit --sign is because we
>> can also extend this to have e.g. --verify.
> 
> if (!sign_progs && (private_key_path != NULL || cert_path != NULL)) {
> p_err("-i <identity_x509_cert> and -k <private> also need --sign to be
> used for sign programs");
> return -EINVAL;
> }
> 
> I will error out, I was waiting for Quentin's reply, we can fix it
> later if needed.

Hi KP, I meant to reply to your email but forgot, apologies.

Yes please, it makes sense to me to error out in that case. Let's make
sure that users have the right syntax rather than letting them
accidentally turn off signing.

Thanks for your other comments and clarification too, looks all good to
me :)

Thanks,
Quentin

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

* Re: [PATCH v2 04/13] libbpf: Support exclusive map creation
  2025-07-29  2:25   ` Alexei Starovoitov
@ 2025-08-11 22:18     ` KP Singh
  0 siblings, 0 replies; 33+ messages in thread
From: KP Singh @ 2025-08-11 22:18 UTC (permalink / raw)
  To: Alexei Starovoitov
  Cc: bpf, LSM List, Blaise Boscaccy, Paul Moore, K. Y. Srinivasan,
	Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko

On Tue, Jul 29, 2025 at 4:25 AM Alexei Starovoitov
<alexei.starovoitov@gmail.com> wrote:
>
> On Mon, Jul 21, 2025 at 2:20 PM KP Singh <kpsingh@kernel.org> wrote:
> >
> >
> > +/**
> > + * @brief **bpf_map__get_exclusive_program()** returns the exclusive program
> > + * that is registered with the map (if any).
> > + * @param map BPF map to which the exclusive program is registered.
> > + * @return the registered exclusive program.
> > + */
> > +LIBBPF_API struct bpf_program *bpf_map__get_exclusive_program(struct bpf_map *map);
>
> I couldn't find patches where it's used.
> Do we actually need it?

Andrii asked me to add the getter along with the setter in:

http://lore.kernel.org/bpf/CAEf4BzZghpnHaV+z2GYDNCApzLuxMW6_=4afgpO+D7AG-zTSFQ@mail.gmail.com/

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

* Re: [PATCH v2 12/13] selftests/bpf: Enable signature verification for all lskel tests
  2025-07-29  2:27   ` Alexei Starovoitov
@ 2025-08-11 22:20     ` KP Singh
  0 siblings, 0 replies; 33+ messages in thread
From: KP Singh @ 2025-08-11 22:20 UTC (permalink / raw)
  To: Alexei Starovoitov
  Cc: bpf, LSM List, Blaise Boscaccy, Paul Moore, K. Y. Srinivasan,
	Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko

[...]

> >         $(Q)diff $$(<:.o=.llinked2.o) $$(<:.o=.llinked3.o)
> > -       $(Q)$$(BPFTOOL) gen skeleton -L $$(<:.o=.llinked3.o) name $$(notdir $$(<:.bpf.o=_lskel)) > $$@
> > +       $(Q)$$(BPFTOOL) gen skeleton $(LSKEL_SIGN) $$(<:.o=.llinked3.o) name $$(notdir $$(<:.bpf.o=_lskel)) > $$@
> >         $(Q)rm -f $$(<:.o=.llinked1.o) $$(<:.o=.llinked2.o) $$(<:.o=.llinked3.o)
>
> Does it mean that it makes all lskel tests to be signed tests ?
> It's great that CI green lights it, but imo it's an overkill.
> Let's have a few signed tests instead of making all of them.

Updated:

diff --git a/tools/testing/selftests/bpf/Makefile
b/tools/testing/selftests/bpf/Makefile
index 1295ff8f26ff..e473e2d780fb 100644
--- a/tools/testing/selftests/bpf/Makefile
+++ b/tools/testing/selftests/bpf/Makefile
@@ -496,15 +496,16 @@ LINKED_SKELS := test_static_linked.skel.h
linked_funcs.skel.h             \
                test_subskeleton.skel.h test_subskeleton_lib.skel.h     \
                test_usdt.skel.h

-LSKELS := fentry_test.c fexit_test.c fexit_sleep.c atomics.c           \
-       trace_printk.c trace_vprintk.c map_ptr_kern.c                   \
+LSKELS := fexit_sleep.c trace_printk.c trace_vprintk.c map_ptr_kern.c  \
        core_kern.c core_kern_overflow.c test_ringbuf.c                 \
        test_ringbuf_n.c test_ringbuf_map_key.c test_ringbuf_write.c

+LSKELS_SIGNED := fentry_test.c fexit_test.c atomics.c
+
 # Generate both light skeleton and libbpf skeleton for these
 LSKELS_EXTRA := test_ksyms_module.c test_ksyms_weak.c kfunc_call_test.c \
        kfunc_call_test_subprog.c
-SKEL_BLACKLIST += $$(LSKELS)
+SKEL_BLACKLIST += $$(LSKELS) $$(LSKELS_SIGNED)

 test_static_linked.skel.h-deps := test_static_linked1.bpf.o
test_static_linked2.bpf.o
 linked_funcs.skel.h-deps := linked_funcs1.bpf.o linked_funcs2.bpf.o
@@ -551,6 +552,7 @@ TRUNNER_BPF_SKELS := $$(patsubst
%.c,$$(TRUNNER_OUTPUT)/%.skel.h,   \
                                               $$(TRUNNER_BPF_SRCS)))
 TRUNNER_BPF_LSKELS := $$(patsubst %.c,$$(TRUNNER_OUTPUT)/%.lskel.h,
$$(LSKELS) $$(LSKELS_EXTRA))
 TRUNNER_BPF_SKELS_LINKED := $$(addprefix $$(TRUNNER_OUTPUT)/,$(LINKED_SKELS))
+TRUNNER_BPF_LSKELS_SIGNED := $$(patsubst
%.c,$$(TRUNNER_OUTPUT)/%.lskel.h, $$(LSKELS_SIGNED))
 TEST_GEN_FILES += $$(TRUNNER_BPF_OBJS)

 # Evaluate rules now with extra TRUNNER_XXX variables above already defined
@@ -602,6 +604,15 @@ $(TRUNNER_BPF_LSKELS): %.lskel.h: %.bpf.o
$(BPFTOOL) | $(TRUNNER_OUTPUT)
        $(Q)$$(BPFTOOL) gen object $$(<:.o=.llinked2.o) $$(<:.o=.llinked1.o)
        $(Q)$$(BPFTOOL) gen object $$(<:.o=.llinked3.o) $$(<:.o=.llinked2.o)
        $(Q)diff $$(<:.o=.llinked2.o) $$(<:.o=.llinked3.o)
+       $(Q)$$(BPFTOOL) gen skeleton -L $$(<:.o=.llinked3.o) name
$$(notdir $$(<:.bpf.o=_lskel)) > $$@
+       $(Q)rm -f $$(<:.o=.llinked1.o) $$(<:.o=.llinked2.o) $$(<:.o=.llinked3.o)
+
+$(TRUNNER_BPF_LSKELS_SIGNED): %.lskel.h: %.bpf.o $(BPFTOOL) | $(TRUNNER_OUTPUT)
+       $$(call msg,GEN-SKEL,$(TRUNNER_BINARY) (signed),$$@)
+       $(Q)$$(BPFTOOL) gen object $$(<:.o=.llinked1.o) $$<
+       $(Q)$$(BPFTOOL) gen object $$(<:.o=.llinked2.o) $$(<:.o=.llinked1.o)
+       $(Q)$$(BPFTOOL) gen object $$(<:.o=.llinked3.o) $$(<:.o=.llinked2.o)
+       $(Q)diff $$(<:.o=.llinked2.o) $$(<:.o=.llinked3.o)
        $(Q)$$(BPFTOOL) gen skeleton $(LSKEL_SIGN)
$$(<:.o=.llinked3.o) name $$(notdir $$(<:.bpf.o=_lskel)) > $$@
        $(Q)rm -f $$(<:.o=.llinked1.o) $$(<:.o=.llinked2.o) $$(<:.o=.llinked3.o)

@@ -654,6 +665,7 @@ $(TRUNNER_TEST_OBJS:.o=.d):
$(TRUNNER_OUTPUT)/%.test.d:                     \
                            $(TRUNNER_EXTRA_HDRS)                       \
                            $(TRUNNER_BPF_SKELS)                        \
                            $(TRUNNER_BPF_LSKELS)                       \
+                           $(TRUNNER_BPF_LSKELS_SIGNED)                \
                            $(TRUNNER_BPF_SKELS_LINKED)                 \
                            $$(BPFOBJ) | $(TRUNNER_OUTPUT)

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

* Re: [PATCH v2 08/13] bpf: Implement signature verification for BPF programs
  2025-07-31 15:57   ` Dan Carpenter
@ 2025-08-11 22:22     ` KP Singh
  0 siblings, 0 replies; 33+ messages in thread
From: KP Singh @ 2025-08-11 22:22 UTC (permalink / raw)
  To: Dan Carpenter
  Cc: oe-kbuild, bpf, linux-security-module, lkp, oe-kbuild-all,
	bboscaccy, paul, kys, ast, daniel, andrii

[...]

> vim +/sig +2797 kernel/bpf/syscall.c
>
> c83b0ba795b625 KP Singh           2025-07-21  2782  static noinline int bpf_prog_verify_signature(struct bpf_prog *prog,
> c83b0ba795b625 KP Singh           2025-07-21  2783                                            union bpf_attr *attr,
> c83b0ba795b625 KP Singh           2025-07-21  2784                                            bool is_kernel)
> c83b0ba795b625 KP Singh           2025-07-21  2785  {
> c83b0ba795b625 KP Singh           2025-07-21  2786      bpfptr_t usig = make_bpfptr(attr->signature, is_kernel);
> c83b0ba795b625 KP Singh           2025-07-21  2787      struct bpf_dynptr_kern sig_ptr, insns_ptr;
> c83b0ba795b625 KP Singh           2025-07-21  2788      struct bpf_key *key = NULL;
> c83b0ba795b625 KP Singh           2025-07-21  2789      void *sig;
> c83b0ba795b625 KP Singh           2025-07-21  2790      int err = 0;
> c83b0ba795b625 KP Singh           2025-07-21  2791
> c83b0ba795b625 KP Singh           2025-07-21  2792      key = bpf_lookup_user_key(attr->keyring_id, 0);
> c83b0ba795b625 KP Singh           2025-07-21  2793      if (!key)
> c83b0ba795b625 KP Singh           2025-07-21  2794              return -ENOKEY;
> c83b0ba795b625 KP Singh           2025-07-21  2795
> c83b0ba795b625 KP Singh           2025-07-21  2796      sig = kvmemdup_bpfptr(usig, attr->signature_size);
> c83b0ba795b625 KP Singh           2025-07-21 @2797      if (!sig) {
>
> This should be an if (!IS_ERR(sig)) { check.

Thanks, fixed.

- KP

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

* Re: [PATCH v2 02/13] bpf: Implement exclusive map creation
  2025-07-29 22:59   ` Fan Wu
@ 2025-08-11 22:48     ` KP Singh
  0 siblings, 0 replies; 33+ messages in thread
From: KP Singh @ 2025-08-11 22:48 UTC (permalink / raw)
  To: Fan Wu
  Cc: bpf, linux-security-module, bboscaccy, paul, kys, ast, daniel,
	andrii

On Wed, Jul 30, 2025 at 12:59 AM Fan Wu <wufan@kernel.org> wrote:
>
> On Mon, Jul 21, 2025 at 2:35 PM KP Singh <kpsingh@kernel.org> wrote:
> >
> > Exclusive maps allow maps to only be accessed by program with a
> > program with a matching hash which is specified in the excl_prog_hash
> > attr.
> >
> > For the signing use-case, this allows the trusted loader program
> > to load the map and verify the integrity
> >
> > Signed-off-by: KP Singh <kpsingh@kernel.org>
> > ---
> >  include/linux/bpf.h            |  1 +
> >  include/uapi/linux/bpf.h       |  2 ++
> >  kernel/bpf/syscall.c           | 32 ++++++++++++++++++++++++++++----
> >  kernel/bpf/verifier.c          |  6 ++++++
> >  tools/include/uapi/linux/bpf.h |  2 ++
> >  5 files changed, 39 insertions(+), 4 deletions(-)
> >
>
> ...
>
> > -static int map_create(union bpf_attr *attr, bool kernel)
> > +static int map_create(union bpf_attr *attr, bpfptr_t uattr)
> >  {
> >         const struct bpf_map_ops *ops;
> >         struct bpf_token *token = NULL;
> > @@ -1527,7 +1528,30 @@ static int map_create(union bpf_attr *attr, bool kernel)
> >                         attr->btf_vmlinux_value_type_id;
> >         }
> >
> > -       err = security_bpf_map_create(map, attr, token, kernel);
> > +       if (attr->excl_prog_hash) {
> > +               bpfptr_t uprog_hash = make_bpfptr(attr->excl_prog_hash, uattr.is_kernel);
> > +
> > +               map->excl_prog_sha = kzalloc(SHA256_DIGEST_SIZE, GFP_KERNEL);
> > +               if (!map->excl_prog_sha) {
> > +                       err = -ENOMEM;
> > +                       goto free_map;
> > +               }
> > +
> > +               if (attr->excl_prog_hash_size != SHA256_DIGEST_SIZE) {
> > +                       err = -EINVAL;
> > +                       goto free_map;
> > +               }
>
> Nit: Maybe check the size first to avoid unncessary kzalloc?

Thanks, fixed.

- KP
>
> -Fan
>
> > +
> > +               if (copy_from_bpfptr(map->excl_prog_sha, uprog_hash,
> > +                                    SHA256_DIGEST_SIZE)) {
> > +                       err = -EFAULT;
> > +                       goto free_map;
> > +               }
> > +       } else if (attr->excl_prog_hash_size) {
> > +               return -EINVAL;
> > +       }
> > +
> > +       err = security_bpf_map_create(map, attr, token, uattr.is_kernel);
> >         if (err)
> >                 goto free_map_sec;
> >

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

* Re: [PATCH v2 08/13] bpf: Implement signature verification for BPF programs
  2025-08-05 18:28   ` Blaise Boscaccy
@ 2025-08-13  2:20     ` Paul Moore
  0 siblings, 0 replies; 33+ messages in thread
From: Paul Moore @ 2025-08-13  2:20 UTC (permalink / raw)
  To: Blaise Boscaccy, KP Singh
  Cc: bpf, linux-security-module, kys, ast, daniel, andrii,
	James.Bottomley, wufan

On Tue, Aug 5, 2025 at 2:28 PM Blaise Boscaccy
<bboscaccy@linux.microsoft.com> wrote:
> KP Singh <kpsingh@kernel.org> writes:
> > This patch extends the BPF_PROG_LOAD command by adding three new fields
> > to `union bpf_attr` in the user-space API:
> >
> >   - signature: A pointer to the signature blob.
> >   - signature_size: The size of the signature blob.
> >   - keyring_id: The serial number of a loaded kernel keyring (e.g.,
> >     the user or session keyring) containing the trusted public keys.
> >
> > When a BPF program is loaded with a signature, the kernel:
> >
> > 1.  Retrieves the trusted keyring using the provided `keyring_id`.
> > 2.  Verifies the supplied signature against the BPF program's
> >     instruction buffer.
> > 3.  If the signature is valid and was generated by a key in the trusted
> >     keyring, the program load proceeds.
> > 4.  If no signature is provided, the load proceeds as before, allowing
> >     for backward compatibility. LSMs can chose to restrict unsigned
> >     programs and implement a security policy.
> > 5.  If signature verification fails for any reason,
> >     the program is not loaded.
> [...]
>
> The following is what we propose to build on top of this to implement
> in-kernel hash chain verification. This allows for signature
> verification of arbitrary maps and isn't coupled to light-skeletons or
> any specific implementation.
>
>
> From: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
> Date: Mon, 28 Jul 2025 08:14:57 -0700
> Subject: bpf: Add hash chain signature support for arbitrary maps
>
> This patch introduces hash chain support for signature verification of
> arbitrary bpf map objects which was described here:
> https://lore.kernel.org/linux-security-module/20250721211958.1881379-1-kpsingh@kernel.org/
>
> The UAPI is extended to allow for in-kernel checking of maps passed in
> via the fd_array. A hash chain is constructed from the maps, in order
> specified by the signature_maps field. The hash chain is terminated
> with the hash of the program itself.
>
> Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
> ---
>  include/uapi/linux/bpf.h       |  6 +++
>  kernel/bpf/syscall.c           | 75 ++++++++++++++++++++++++++++++++--
>  tools/include/uapi/linux/bpf.h |  6 +++
>  3 files changed, 83 insertions(+), 4 deletions(-)

Some minor comments below, but in general I think this approach is
good in that it preserves the signature scheme in KP's patchset while
also supporting a scheme that is not reliant on the lskel or
verification, with the user loading the program/lskel choosing which
signature scheme to use.  Unless something has changed since we've
discussed this last, I believe this should provide the basic BPF
infrastructure needed to satisfy all the different requirements
already described.

Thoughts on this KP (as well as any others who have been following along)?

> diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
> index 10fd3ea5d91fd..f7e9bcabd9dcc 100644
> --- a/kernel/bpf/syscall.c
> +++ b/kernel/bpf/syscall.c
> @@ -2780,15 +2780,36 @@ static bool is_perfmon_prog_type(enum bpf_prog_type prog_type)
>         }
>  }
>
> +static inline int bpf_map_get_hash(int map_fd, void *buffer)
> +{
> +       struct bpf_map *map;
> +
> +       CLASS(fd, f)(map_fd);
> +       map = __bpf_map_get(f);
> +       if (IS_ERR(map))
> +               return PTR_ERR(map);
> +
> +       if (!map->ops->map_get_hash)
> +               return -EINVAL;
> +
> +       return map->ops->map_get_hash(map, SHA256_DIGEST_SIZE, buffer);
> +}

It would be nice to see some agility on the hash algorithm, but it's
probably not critical for a first effort.  I can easily see an
algorithm field being added to bpf_attr, using the same digest
algorithm as specified in the PKCS7 signature, or something else
sufficiently clever.

>  static noinline 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;
> +       bpfptr_t umaps;
> +       struct bpf_dynptr_kern sig_ptr, insns_ptr, hash_ptr;
>         struct bpf_key *key = NULL;
>         void *sig;
> +       int *maps;
> +       int map_fd;
>         int err = 0;
> +       u64 buffer[8];
> +       u64 hash[4];

It would be good to replace the magic numbers above with something
that is a bit more self documenting, e.g. 'u64 hash[SHA256_DIGEST_SIZE
/ sizeof(u64)]'.  The same goes for some of the pointer offset math
below, assuming the result isn't too ugly.  If the resulting code is
pretty awful to look at, a quick comment or two might be a good idea.

> +       int n;
>
>         if (system_keyring_id_check(attr->keyring_id) == 0)
>                 key = bpf_lookup_system_key(attr->keyring_id);
> @@ -2808,16 +2829,62 @@ static noinline int bpf_prog_verify_signature(struct bpf_prog *prog,
>         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,
> -                                        (struct bpf_dynptr *)&sig_ptr, key);
> +       if (!attr->signature_maps_size) {
> +               err = bpf_verify_pkcs7_signature((struct bpf_dynptr *)&insns_ptr,
> +                                                (struct bpf_dynptr *)&sig_ptr, key);
> +       } else {
> +               bpf_dynptr_init(&hash_ptr, hash, BPF_DYNPTR_TYPE_LOCAL, 0,
> +                               sizeof(hash));
> +               umaps = make_bpfptr(attr->signature_maps, is_kernel);
> +               maps = kvmemdup_bpfptr(umaps, attr->signature_maps_size * sizeof(*maps));
> +               if (!maps) {
> +                       err = -ENOMEM;
> +                       goto out;
> +               }
> +               n = attr->signature_maps_size - 1;
> +               err = copy_from_bpfptr_offset(&map_fd, make_bpfptr(attr->fd_array, is_kernel),
> +                                             maps[n] * sizeof(map_fd),
> +                                             sizeof(map_fd));
> +               if (err < 0)
> +                       goto free_maps;
> +
> +               err = bpf_map_get_hash(map_fd, hash);
> +               if (err != 0)
> +                       goto free_maps;
> +
> +               n--;
> +               while (n >= 0) {
> +                       memcpy(buffer, hash, sizeof(hash));
> +                       err = copy_from_bpfptr_offset(&map_fd,
> +                                                     make_bpfptr(attr->fd_array, is_kernel),
> +                                                     maps[n] * sizeof(map_fd),
> +                                                     sizeof(map_fd));
> +                       if (err < 0)
> +                               goto free_maps;
> +
> +                       err = bpf_map_get_hash(map_fd, buffer+4);
> +                       if (err != 0)
> +                               goto free_maps;
> +                       sha256((u8 *)buffer, sizeof(buffer), (u8 *)&hash);
> +                       n--;
> +               }
> +               sha256((u8 *)prog->insnsi, prog->len * sizeof(struct bpf_insn), (u8 *)&buffer);
> +               memcpy(buffer+4, hash, sizeof(hash));
> +               sha256((u8 *)buffer, sizeof(buffer), (u8 *)&hash);
> +               err = bpf_verify_pkcs7_signature((struct bpf_dynptr *)&hash_ptr,
> +                                                (struct bpf_dynptr *)&sig_ptr, key);
>
> +free_maps:
> +               kvfree(maps);
> +       }
> +out:
>         bpf_key_put(key);
>         kvfree(sig);
>         return err;
>  }
>
>  /* last field in 'union bpf_attr' used by this command */
> -#define BPF_PROG_LOAD_LAST_FIELD keyring_id
> +#define BPF_PROG_LOAD_LAST_FIELD signature_maps_size
>
>  static int bpf_prog_load(union bpf_attr *attr, bpfptr_t uattr, u32 uattr_size)
>  {

-- 
paul-moore.com

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

end of thread, other threads:[~2025-08-13  2:20 UTC | newest]

Thread overview: 33+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-07-21 21:19 [PATCH v2 00/13] Signed BPF programs KP Singh
2025-07-21 21:19 ` [PATCH v2 01/13] bpf: Update the bpf_prog_calc_tag to use SHA256 KP Singh
2025-07-21 21:19 ` [PATCH v2 02/13] bpf: Implement exclusive map creation KP Singh
2025-07-29 22:59   ` Fan Wu
2025-08-11 22:48     ` KP Singh
2025-07-21 21:19 ` [PATCH v2 03/13] libbpf: Implement SHA256 internal helper KP Singh
2025-07-21 21:19 ` [PATCH v2 04/13] libbpf: Support exclusive map creation KP Singh
2025-07-29  2:25   ` Alexei Starovoitov
2025-08-11 22:18     ` KP Singh
2025-07-21 21:19 ` [PATCH v2 05/13] selftests/bpf: Add tests for exclusive maps KP Singh
2025-07-21 21:19 ` [PATCH v2 06/13] bpf: Return hashes of maps in BPF_OBJ_GET_INFO_BY_FD KP Singh
2025-07-21 21:19 ` [PATCH v2 07/13] bpf: Move the signature kfuncs to helpers.c KP Singh
2025-07-23 16:47   ` James Bottomley
2025-07-21 21:19 ` [PATCH v2 08/13] bpf: Implement signature verification for BPF programs KP Singh
2025-07-23 17:11   ` James Bottomley
2025-07-24 17:22     ` KP Singh
2025-07-31 15:57   ` Dan Carpenter
2025-08-11 22:22     ` KP Singh
2025-08-05 18:28   ` Blaise Boscaccy
2025-08-13  2:20     ` Paul Moore
2025-07-21 21:19 ` [PATCH v2 09/13] libbpf: Update light skeleton for signing KP Singh
2025-07-21 21:19 ` [PATCH v2 10/13] libbpf: Embed and verify the metadata hash in the loader KP Singh
2025-07-21 21:19 ` [PATCH v2 11/13] bpftool: Add support for signing BPF programs KP Singh
2025-07-22 15:51   ` Quentin Monnet
2025-07-24 17:07     ` KP Singh
2025-08-11 14:23       ` KP Singh
2025-08-11 14:39         ` Quentin Monnet
2025-07-21 21:19 ` [PATCH v2 12/13] selftests/bpf: Enable signature verification for all lskel tests KP Singh
2025-07-29  2:27   ` Alexei Starovoitov
2025-08-11 22:20     ` KP Singh
2025-07-21 21:19 ` [PATCH v2 13/13] selftests/bpf: Add test for signed programs KP Singh
2025-07-29  2:30   ` Alexei Starovoitov
2025-08-11 14:24     ` KP Singh

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).