From: Daniel Borkmann <daniel@iogearbox.net>
To: ast@kernel.org
Cc: kpsingh@kernel.org, James.Bottomley@hansenpartnership.com,
paul@paul-moore.com, bboscaccy@linux.microsoft.com,
memxor@gmail.com, torvalds@linux-foundation.org,
a.s.protopopov@gmail.com, bpf@vger.kernel.org,
linux-security-module@vger.kernel.org
Subject: [PATCH bpf-next v4 9/9] Documentation/bpf: Add BPF signing and enforcement doc
Date: Mon, 6 Jul 2026 15:56:44 +0200 [thread overview]
Message-ID: <20260706135644.326006-10-daniel@iogearbox.net> (raw)
In-Reply-To: <20260706135644.326006-1-daniel@iogearbox.net>
Describe the BPF signing design end to end: why a trusted loader is
needed, the signature(insns || metadata) contract, load-time
verification via fd_array (exclusive + frozen maps), the binary
BPF_SIG_{UNSIGNED,VERIFIED} verdict, and how [BPF] LSMs can enforce
policy on it.
This writes down the contract on the discussion points with the LSM /
integrity folks [0][1]: by the time security_bpf_prog_load() is
called, signature verification has fully completed and covers the
instructions plus the frozen contents of every bound exclusive map;
there is no intermediate "loader verified, payload pending" state
to reason about; and what BPF_SIG_VERIFIED means at each hook is
spelled out explicitly.
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
Link: https://lore.kernel.org/bpf/bc823ddbaf63e0e177eb46d1cc15076e4e2e689d.camel@HansenPartnership.com [0]
Link: https://lore.kernel.org/bpf/CAHC9VhSDkwGgPfrBUh7EgBKEJj_JjnY68c0YAmuuLT_i--GskQ@mail.gmail.com [1]
---
Documentation/bpf/index.rst | 1 +
Documentation/bpf/signing.rst | 496 ++++++++++++++++++++++++++++++++++
2 files changed, 497 insertions(+)
create mode 100644 Documentation/bpf/signing.rst
diff --git a/Documentation/bpf/index.rst b/Documentation/bpf/index.rst
index 0d5c6f659266..638a00d42bc2 100644
--- a/Documentation/bpf/index.rst
+++ b/Documentation/bpf/index.rst
@@ -28,6 +28,7 @@ that goes into great technical depth about the BPF Architecture.
classic_vs_extended.rst
bpf_iterators
bpf_licensing
+ signing
test_debug
clang-notes
linux-notes
diff --git a/Documentation/bpf/signing.rst b/Documentation/bpf/signing.rst
new file mode 100644
index 000000000000..446b4edbae62
--- /dev/null
+++ b/Documentation/bpf/signing.rst
@@ -0,0 +1,496 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+============
+BPF signing
+============
+
+This document describes how BPF programs are cryptographically signed, how the
+kernel verifies them at load time, and how Linux Security Modules (LSMs) -
+including the BPF LSM - use the resulting verdict to enforce policy. It is
+written for developers who want to produce signed BPF objects, understand what
+the signature actually guarantees, or build a policy on top of it.
+
+Motivation
+==========
+
+A signed BPF program lets the kernel establish that the bytecode being loaded
+originates from a trusted producer and was not modified in transit. On its own
+the kernel does not *require* signatures - an unsigned program loads exactly as
+before - but it records a verdict (see `The verdict`_) that an LSM can gate on.
+This is the building block for policies such as "only run BPF that was signed by
+a key in the trusted keyring", as could in the future be enforced by an LSM
+such as IPE.
+
+Signing is orthogonal to the existing permission model: it does not replace the
+capability checks or the verifier. A signed load still requires the usual
+privileges (``CAP_BPF`` and any program-type-specific capability, subject to
+``kernel.unprivileged_bpf_disabled``), and the loader's instructions are still
+checked by the verifier like any other program. A valid signature establishes
+*origin and integrity*, not safety - it lets a policy trust where the bytecode
+came from, it does not let a load skip any check it would otherwise face.
+
+The hard part is *what* gets signed. A naive scheme would sign a program's
+instruction buffer at build time and verify that signature at
+``BPF_PROG_LOAD``. That does not survive contact with real BPF objects, because
+the bytes the kernel finally loads are not the bytes the developer built and
+signed. Between the two, libbpf and the kernel rewrite the program:
+
+- **map file descriptors** are patched into ``ld_imm64`` instructions
+ (``BPF_PSEUDO_MAP_FD``), and a map's fd is assigned at load time, so it
+ differs on every run;
+- **CO-RE relocations** rewrite field offsets, sizes and existence flags against
+ the *running* kernel's BTF, so the result differs from one kernel to the next;
+- **kfunc and ksym references** are resolved to ids/addresses in the running
+ kernel;
+- **global data** (``.rodata``/``.data``/``.bss``) is created and seeded as maps
+ at load.
+
+So a signature over the original instructions cannot match the relocated
+instructions the verifier ends up checking, and the relocated form cannot be
+produced ahead of time because it depends on the target kernel. There is no
+fixed byte string that is both signable at build time and what the kernel
+actually loads - which is why a program cannot simply be signed and loaded
+directly.
+
+The trusted loader
+==================
+
+The solution is to move that setup work *into* a small BPF program - the
+**loader** - and sign the loader instead of the individual programs. libbpf's
+``gen_loader`` machinery (``bpftool gen skeleton -L``, the "light skeleton")
+emits a ``BPF_PROG_TYPE_SYSCALL`` program whose body performs the bpf() syscalls
+that create maps, apply relocations, and load the real programs. The payload it
+installs - the serialized programs, map descriptions, relocation data and
+initial values - lives in a separate array map, the **metadata map**
+(``__loader.map``).
+
+So the unit of trust is the loader, and the signing contract is::
+
+ Sig(I_loader || D_meta)
+
+where ``I_loader`` is the loader's instruction stream and ``D_meta`` is the
+content of the metadata map. Verifying the loader's signature establishes that
+both the loader *and* the payload it is about to install are authentic. The
+loader is reproducible: ``gen_loader`` builds it from primitives so the same
+object yields the same bytes on any build host.
+
+Why the loader is signable when the program is not
+--------------------------------------------------
+
+The loader sidesteps every rewrite listed above, because the bytes that are
+signed are *relocation-invariant*:
+
+- The loader's own instructions are a fixed sequence of bpf() syscalls emitted
+ by ``gen_loader``; they carry no CO-RE relocations and resolve no ksyms, so
+ they are identical on every kernel. The metadata map is referenced by *index*
+ into ``fd_array`` (``BPF_PSEUDO_MAP_IDX_VALUE``), not by a baked-in file
+ descriptor, so even that reference does not change between build and load.
+ The loader instruction bytes the kernel verifies are exactly the bytes that
+ were signed.
+- The metadata map is opaque, frozen data - the serialized target programs,
+ their relocation records, map descriptions and initial values. Its bytes are
+ identical at build time and at load time, so they are simply appended to the
+ instructions and covered by the same signature (there is no separate metadata
+ hash to compute or compare).
+
+All the host-specific rewriting - creating maps, patching their fds into the
+target programs, applying CO-RE, resolving ksyms, seeding global data - still
+happens, but it happens *inside the loader at runtime*, on the verified
+metadata, **after** the kernel has verified the ``insns || metadata`` signature.
+The kernel never has to verify the relocated target programs: it verifies the
+loader and its inputs once, and trust transfers to whatever that now-trusted,
+deterministic loader installs. The relocation step is moved from "before the
+signature can be checked" to "after a trusted program runs" - which is exactly
+what makes it signable.
+
+Because the metadata map is the loader's only untrusted input, two existing map
+properties are reused to keep it trustworthy across the load:
+
+Exclusive maps
+ A map created with ``excl_prog_hash`` (see ``BPF_MAP_CREATE``) may only be
+ accessed by a program whose digest matches that hash. The verifier enforces
+ ``map->excl_prog_sha == prog->digest`` for every map a program uses, so the
+ metadata map is bound to exactly the signed loader and cannot be shared with
+ or mutated by another program.
+
+Frozen maps
+ The metadata map is frozen (``BPF_MAP_FREEZE``) before the loader is loaded.
+ Freezing blocks further userspace writes, so the bytes folded into the
+ signature cannot change before the loader runs. (Freezing does not make the
+ map read-only to the loader program itself, which still writes created file
+ descriptors back into the blob's scratch area.)
+
+Load-time verification
+=======================
+
+Rather than have the loader check its own metadata from within BPF, the kernel
+verifies it directly at ``BPF_PROG_LOAD``, with no new UAPI. The mechanism
+reuses the existing ``fd_array``:
+
+#. Userspace creates the metadata map with ``excl_prog_hash`` set to the
+ loader's digest, populates it, and freezes it.
+#. The loader is loaded with ``signature``/``signature_size``/``keyring_id``
+ set, the metadata map referenced through ``fd_array``, and ``fd_array_cnt``
+ set so the kernel knows the array's length.
+#. Signature verification runs inside the verifier (``bpf_check()``), once it
+ has resolved the ``fd_array`` entries into the program's ``used_maps``. The
+ maps folded into the signature are therefore the very objects the program
+ binds - a single resolution of ``fd_array``, not a separate read, so the
+ verified bytes cannot be swapped for a different map after the check (no
+ time-of-check/time-of-use window). Each folded map must be exclusive (carry
+ ``excl_prog_sha``) and a plain array map (``BPF_MAP_TYPE_ARRAY``); only an
+ array map exposes its value buffer through ``map_direct_value_addr()`` as a
+ kernel address spanning ``value_size`` bytes. A map that is not exclusive, not
+ frozen, or not a plain array is rejected, with a verifier log message naming
+ the offending map. The kernel appends each map's frozen
+ contents to the instruction buffer and verifies the PKCS#7 signature over the
+ concatenation ``insns || metadata_0 || metadata_1 || ...`` in ``used_maps``
+ order, before it rewrites the (signed) instructions.
+
+A signed program therefore takes one of exactly two shapes, both fully
+supported:
+
+- **No bound maps** (``fd_array_cnt == 0``): there is nothing to append, so the
+ kernel verifies the signature over the instructions alone. A valid signature
+ yields ``BPF_SIG_VERIFIED`` and the program loads. This is the ordinary case
+ for a directly-loaded signed program with no separate payload; it is *not*
+ rejected for "missing" metadata, because it has none to cover.
+- **Exclusive bound maps** (``fd_array_cnt > 0``): every entry is exclusive and
+ folded, so the signature covers ``insns || metadata``.
+
+There is no third shape: a non-exclusive map in a signed program's ``fd_array``
+is rejected rather than silently left out of the signature, so a program bound
+to a signed loader never has a map the signature does not cover.
+
+The digest binding (``excl_prog_sha == prog->digest``) is enforced by the
+verifier as usual; because that check runs while ``fd_array`` is resolved -
+before the verifier would otherwise compute the tag - ``prog->digest`` is
+computed up front in the verifier, over the unmodified (signature-covered)
+instructions, for any signed load.
+
+Coverage is then enforced as the verifier resolves instructions, at the point
+each object is bound rather than by a count taken afterwards. Once the signature
+has been verified, binding any further map is refused: a map reached by a
+directly-referenced fd, or a map swapped into an ``fd_array`` slot the loader
+reads, is not among those already folded, so it is rejected the moment the
+verifier tries to bind it. A BTF is refused outright for a signed program - a
+ksym or a BTF fd in ``fd_array``, whether resolved up front or lazily for a
+module kfunc, is rejected when it would be bound. Together with the fold rule
+above this keeps the verdict binary: a signed program cannot use a map its
+signature does not cover, and a different but equally digest-bound map cannot be
+substituted at an ``fd_array`` slot. Non-exclusive maps are never folded, so a
+signed program cannot use one at all.
+
+The verdict
+===========
+
+A program is either unsigned or fully verified - there is no intermediate
+state. The outcome is recorded in ``prog->aux->sig.verdict``:
+
+.. code-block:: c
+
+ enum bpf_sig_verdict {
+ BPF_SIG_UNSIGNED = 0,
+ BPF_SIG_VERIFIED,
+ };
+
+``BPF_SIG_VERIFIED`` means the signature is valid and covers the instructions
+*and* the frozen contents of every exclusive map the program uses:
+
+- For an ordinary, directly-loaded signed program the instructions are the whole
+ artifact and it uses no exclusive maps, so a valid instruction signature is
+ the complete verification.
+- For a signed loader the metadata map is exclusive, so its contents are folded
+ in and the signature covers ``insns || metadata``.
+
+There is deliberately no "instructions verified but metadata not" verdict: a
+signed loader that fails to cover its metadata is *rejected* (see above), not
+recorded with a weaker verdict. ``BPF_SIG_VERIFIED`` therefore always means the
+program and everything the signature is responsible for are authentic, which is
+what a policy can rely on.
+
+Alongside the verdict the kernel records which keyring validated the signature;
+see `Keyrings`_.
+
+Enforcement via LSMs
+====================
+
+Signing only *records* a verdict; an LSM turns it into policy. The verdict and
+keyring fields live in ``struct bpf_prog_aux``, so a BPF LSM program can read
+them directly (see Documentation/bpf/prog_lsm.rst for writing and attaching BPF
+LSM programs); the same fields are equally available to in-tree LSMs. Two hooks
+are useful at different points of the load: the dedicated
+``security_bpf_prog_load()`` gates admission before the main verification work,
+and the existing ``security_bpf_prog()`` observes a program that has fully
+loaded.
+
+Admission: ``security_bpf_prog_load()``
+---------------------------------------
+
+This hook gates admission **for every load**, from a single call site inside the
+verifier (``bpf_check()``), before the main verification work. It runs after the
+optional signature verification, so the verdict and keyring fields are final - the
+hook can see whether, and how strongly, the program was signed, which keyring
+validated it, the load ``attr``, the BPF token and whether the load came from the
+kernel. For a signed load the verdict is ``BPF_SIG_VERIFIED`` here (the signature
+has just been checked); for an unsigned load it is ``BPF_SIG_UNSIGNED``.
+
+This is the place for *coarse admission* that must also see unsigned and
+not-yet-verified loads: require a signature at all, restrict the acceptable
+keyring, restrict which token/credentials may load BPF, apply per-program-type
+rules, or audit every load attempt that makes it past signature verification -
+attempts failing the signature or the metadata binding abort before this hook
+fires. It is the primary deny point.
+
+One subtlety: this hook runs *before* the verifier finishes its work, so
+``BPF_SIG_VERIFIED`` *here* means only "validly signed" - not "loaded". Allowing
+a load at this point lets it *proceed*; it does not guarantee the program will
+load. A validly signed program can still be rejected afterwards on two
+independent grounds: the verifier may reject it like any other program (unsafe
+memory access, bad control flow, resource limits, ...), and the kernel separately
+refuses - as the verifier resolves instructions and binds each object - any map
+the signature does not cover or any BTF at all, regardless of what this hook
+returned. Only after the program has fully loaded, at the next hook
+(``security_bpf_prog()``), does ``BPF_SIG_VERIFIED`` carry its full meaning:
+validly signed *and* fully verified.
+
+A more realistic admission policy than "is it signed at all": accept programs
+signed by a system keyring, accept a user-keyring signature only if the
+key/keyring it was verified against is on an explicit allowlist, and emit a
+tamper-evident record of every decision so that even denied attempts are
+auditable. (Illustrative - error checking elided.)
+
+.. code-block:: c
+
+ /* Serials of user keys/keyrings we additionally trust. */
+ struct {
+ __uint(type, BPF_MAP_TYPE_HASH);
+ __type(key, __s32); /* keyring_serial */
+ __type(value, __u8);
+ __uint(max_entries, 64);
+ } trusted_user_keys SEC(".maps");
+
+ /* Audit stream consumed by a userspace logger. */
+ struct {
+ __uint(type, BPF_MAP_TYPE_RINGBUF);
+ __uint(max_entries, 1 << 16);
+ } audit SEC(".maps");
+
+ struct decision { __u32 prog_type, verdict, ktype; __s32 serial, ret; };
+
+ SEC("lsm/bpf_prog_load")
+ int BPF_PROG(admit, struct bpf_prog *prog, union bpf_attr *attr,
+ struct bpf_token *token, bool kernel)
+ {
+ __u32 verdict = prog->aux->sig.verdict;
+ __u32 ktype = prog->aux->sig.keyring_type;
+ __s32 serial = prog->aux->sig.keyring_serial;
+ struct decision *d;
+ int ret = 0;
+
+ if (kernel)
+ return 0; /* trust in-kernel loads */
+
+ if (verdict != BPF_SIG_VERIFIED)
+ ret = -EPERM; /* must be validly signed */
+ else if (ktype == BPF_SIG_KEYRING_USER &&
+ !bpf_map_lookup_elem(&trusted_user_keys, &serial))
+ ret = -EPERM; /* key/keyring not allowlisted */
+
+ d = bpf_ringbuf_reserve(&audit, sizeof(*d), 0);
+ if (d) {
+ d->prog_type = attr->prog_type;
+ d->verdict = verdict;
+ d->ktype = ktype;
+ d->serial = serial;
+ d->ret = ret;
+ bpf_ringbuf_submit(d, 0); /* record allow *and* deny */
+ }
+ return ret;
+ }
+
+Observing a verified load: ``security_bpf_prog()``
+--------------------------------------------------
+
+There is deliberately no separate "metadata attested" hook. The coverage check
+above is enforced by the kernel unconditionally, so a signed loader that fails
+to cover its metadata never loads and an LSM never has to re-establish that
+fact. To *act on* a program that has successfully and fully loaded, use the
+existing ``security_bpf_prog()`` hook (``lsm/bpf_prog``), which fires from
+``bpf_prog_new_fd()`` - after the verifier, after the coverage check, and after
+``bpf_prog_alloc_id()``. Relative to the admission hook this point is strictly
+later and stronger:
+
+- the program has an id (``prog->aux->id``), so it can be recorded or correlated
+ with later events;
+- ``verdict == BPF_SIG_VERIFIED`` *here* means **fully** verified - a program
+ that used a map the signature does not cover was already rejected, so it cannot
+ reach this point;
+- it observes only programs that actually loaded; a failed load never mints an
+ fd, so it never reaches this hook.
+
+It takes only the ``prog`` and a non-zero return still aborts (the fd is not
+handed out), so it can veto as well as observe. One wrinkle: it also fires on
+other paths that mint a new program fd - notably ``bpf_prog_get_fd_by_id()`` -
+not just on a fresh load. Because the program already has its id here, an LSM
+can tell the two apart with a small hash map: the *first* time an id is seen is
+the load; a later sighting of the same id is just another fd to a program that
+already exists.
+
+To bound the map and let a reused id read as a fresh load, this can be paired
+with ``security_bpf_prog_free()`` (``lsm/bpf_prog_free``), which deletes the
+entry on teardown - keyed by the same ``prog`` pointer, since
+``bpf_prog_free_id()`` has already cleared ``prog->aux->id`` to ``0`` by the time
+that hook runs. (Illustrative - privileged LSM, error checking elided.)
+
+.. code-block:: c
+
+ struct rec { __u32 id, ktype; __s32 serial; };
+
+ struct {
+ __uint(type, BPF_MAP_TYPE_HASH);
+ __type(key, __u64); /* struct bpf_prog * -- stable id */
+ __type(value, struct rec);
+ __uint(max_entries, 4096);
+ } live SEC(".maps");
+
+ SEC("lsm/bpf_prog") /* fires after load and on every later fd */
+ int BPF_PROG(observe, struct bpf_prog *prog)
+ {
+ __u64 key = (__u64)(unsigned long)prog;
+ struct rec r;
+
+ if (prog->aux->sig.verdict != BPF_SIG_VERIFIED)
+ return 0;
+ if (bpf_map_lookup_elem(&live, &key))
+ return 0; /* seen before: a later fd, not a load */
+
+ /* First sighting == this program just loaded; id is valid here. */
+ r.id = prog->aux->id;
+ r.ktype = prog->aux->sig.keyring_type;
+ r.serial = prog->aux->sig.keyring_serial;
+ bpf_map_update_elem(&live, &key, &r, BPF_NOEXIST);
+ /* ... newly-loaded verified-program action, e.g. record r.id ... */
+ return 0;
+ }
+
+Putting them together: to *require* verified BPF, deny at the admission hook
+unless the verdict is ``BPF_SIG_VERIFIED`` (and, if desired, restrict the
+keyring). The kernel then guarantees that any program which actually loads with
+that verdict covered all of its exclusive maps, rejecting any that did not - so
+a deny-by-default admission policy needs no second enforcement point. Use
+``security_bpf_prog()`` to record or finally gate the verified programs once
+they carry an id. The ``verdict``, ``keyring_type`` and ``keyring_serial`` fields
+let a policy distinguish, for example, "verified and signed by a builtin key"
+from "verified by a user key". A policy LSM such as IPE could consume the same
+hooks to enforce system policy without writing any BPF, though none implements
+this today.
+
+Keyrings
+========
+
+``keyring_id`` selects the trusted keyring the PKCS#7 signature is verified
+against. The well-known ids ``0`` (builtin), ``VERIFY_USE_SECONDARY_KEYRING``
+and ``VERIFY_USE_PLATFORM_KEYRING`` select the corresponding system keyrings;
+any other value is treated as the serial of a user/session key or keyring.
+The keyring is looked up first, before the signature bytes are examined, so a
+signature naming a non-existent keyring is rejected up front, and a failed
+verification aborts the load - so a program that loads successfully with a
+signature always has consistent keyring fields recorded.
+
+Two fields are recorded in ``prog->aux->sig`` for an LSM to inspect:
+
+``keyring_type`` (``enum bpf_sig_keyring``)
+ Classified purely from ``keyring_id`` whenever the program is signed:
+ ``BPF_SIG_KEYRING_BUILTIN``, ``_SECONDARY``, ``_PLATFORM`` for the system
+ keyrings, or ``_USER`` for a user/session keyring. It is
+ ``BPF_SIG_KEYRING_NONE`` for an unsigned program.
+
+``keyring_serial`` (``s32``)
+ Set **only** on a successful verification, to the serial of the
+ **user/session key or keyring** that ``keyring_id`` resolved to - the
+ object the signature was verified against, not the individual asymmetric
+ key inside it that matched the signer. Passing
+ ``KEY_SPEC_SESSION_KEYRING``, for example, records the session keyring's
+ serial. The system keyrings are trusted as a whole and expose no serial
+ here, so the serial is ``0`` for builtin, secondary and platform
+ signatures, and ``0`` for unsigned programs. In other words, a non-zero
+ ``keyring_serial`` is exactly "verified against the user key/keyring with
+ this serial".
+
+.. list-table::
+ :header-rows: 1
+
+ * - ``keyring_id``
+ - ``keyring_type``
+ - ``keyring_serial``
+ * - (no signature)
+ - ``BPF_SIG_KEYRING_NONE``
+ - ``0``
+ * - ``0``
+ - ``BPF_SIG_KEYRING_BUILTIN``
+ - ``0``
+ * - ``VERIFY_USE_SECONDARY_KEYRING``
+ - ``BPF_SIG_KEYRING_SECONDARY``
+ - ``0``
+ * - ``VERIFY_USE_PLATFORM_KEYRING``
+ - ``BPF_SIG_KEYRING_PLATFORM``
+ - ``0``
+ * - other (a user/session key serial)
+ - ``BPF_SIG_KEYRING_USER``
+ - serial of the resolved key/keyring
+
+Producing a signed object
+==========================
+
+``bpftool`` generates and signs a light skeleton in one step::
+
+ bpftool gen skeleton -L -S -k <private_key.pem> -i <certificate.x509> \
+ obj.bpf.o > obj.lskel.h
+
+``-L`` selects the light-skeleton (``gen_loader``) backend and ``-S`` enables
+signing; ``-k`` and ``-i`` supply the signing key and its X.509 certificate.
+``bpftool`` signs ``insns || metadata`` - the exact bytes the kernel
+reconstructs - and also computes ``excl_prog_hash`` as the digest of the loader
+instructions so the metadata map can be bound to the loader. The signature and
+hash are embedded in the generated header; the certificate is used only for
+signing and is not included. Loading the skeleton performs the
+create/populate/freeze/load sequence described above.
+
+At runtime the trusted public key must be present in the chosen keyring (for
+example added to the session keyring, or built into the kernel's builtin trusted
+keyring) for verification to succeed.
+
+UAPI reference
+==============
+
+``BPF_PROG_LOAD`` (``union bpf_attr``):
+
+``signature``, ``signature_size``
+ Pointer to and length of the PKCS#7 signature blob.
+
+``keyring_id``
+ Trusted keyring selector (see `Keyrings`_).
+
+``fd_array``, ``fd_array_cnt``
+ Array of map file descriptors bound to the program. ``fd_array_cnt`` must be
+ set for the kernel to scan the array. When a signature is present, every map
+ in the array must be exclusive; its frozen contents are folded into the
+ verified buffer, and a non-exclusive entry is rejected.
+
+``BPF_MAP_CREATE`` (``union bpf_attr``):
+
+``excl_prog_hash``, ``excl_prog_hash_size``
+ SHA-256 digest of the program permitted to access this (exclusive) map. This
+ binds the metadata map to the loader; it is not a hash of the map *content*.
+ The map content is not hashed separately at all - it is covered, as bytes,
+ by the program signature.
+
+Notes and limitations
+======================
+
+- The instructions plus folded metadata are verified as one ``bpf_dynptr``,
+ which bounds the combined size (currently ~16 MiB); very large objects can
+ exceed it.
+- The metadata container is a single-element array map, accessed through
+ ``map_direct_value_addr``.
--
2.43.0
next prev parent reply other threads:[~2026-07-06 13:56 UTC|newest]
Thread overview: 22+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-06 13:56 [PATCH bpf-next v4 0/9] Verify BPF signed loader at load time Daniel Borkmann
2026-07-06 13:56 ` [PATCH bpf-next v4 1/9] bpf: Resolve and cache fd_array objects " Daniel Borkmann
2026-07-06 15:42 ` Anton Protopopov
2026-07-06 13:56 ` [PATCH bpf-next v4 2/9] bpf: Move bigger allocations below fd_array resolution Daniel Borkmann
2026-07-06 14:21 ` sashiko-bot
2026-07-06 13:56 ` [PATCH bpf-next v4 3/9] bpf: Verify signed loader metadata at load time Daniel Borkmann
2026-07-06 14:26 ` sashiko-bot
2026-07-06 14:48 ` Daniel Borkmann
2026-07-06 15:09 ` bot+bpf-ci
2026-07-06 17:16 ` Paul Moore
2026-07-06 13:56 ` [PATCH bpf-next v4 4/9] libbpf: Drop in-loader metadata check for load-time verification Daniel Borkmann
2026-07-06 14:50 ` bot+bpf-ci
2026-07-06 13:56 ` [PATCH bpf-next v4 5/9] bpftool: Check EVP_Digest when computing excl_prog_hash Daniel Borkmann
2026-07-06 13:56 ` [PATCH bpf-next v4 6/9] bpftool: Cover loader metadata with the program signature Daniel Borkmann
2026-07-06 13:56 ` [PATCH bpf-next v4 7/9] selftests/bpf: Adjust bpf_map layout in verifier_map_ptr Daniel Borkmann
2026-07-06 14:27 ` sashiko-bot
2026-07-06 14:30 ` Daniel Borkmann
2026-07-06 13:56 ` [PATCH bpf-next v4 8/9] selftests/bpf: Verify load-time signed loader metadata Daniel Borkmann
2026-07-06 13:56 ` Daniel Borkmann [this message]
2026-07-06 17:13 ` [PATCH bpf-next v4 0/9] Verify BPF signed loader at load time Paul Moore
2026-07-06 17:47 ` Daniel Borkmann
2026-07-06 19:20 ` Paul Moore
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260706135644.326006-10-daniel@iogearbox.net \
--to=daniel@iogearbox.net \
--cc=James.Bottomley@hansenpartnership.com \
--cc=a.s.protopopov@gmail.com \
--cc=ast@kernel.org \
--cc=bboscaccy@linux.microsoft.com \
--cc=bpf@vger.kernel.org \
--cc=kpsingh@kernel.org \
--cc=linux-security-module@vger.kernel.org \
--cc=memxor@gmail.com \
--cc=paul@paul-moore.com \
--cc=torvalds@linux-foundation.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.