| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/07/06 20:24 | flow | patch-triage |
6mArgs:null Results: {
"EnableConfigs": [
"MODULE_SIG"
],
"FocusSymbols": [
"bpf_prog_verify_signature",
"process_fd_array_continuous",
"process_fd_array",
"fd_array_get_map_idx_continuous",
"fd_array_get_map_idx_sparse",
"fd_array_get_map_idx",
"fd_array_get_btf_continuous",
"fd_array_get_btf_sparse",
"fd_array_get_btf",
"add_fd_from_fd_array",
"bpf_verify_pkcs7_signature"
],
"Reasoning": "The patch introduces BPF program signing verification into the kernel verifier. It modifies the `bpf_check` path to verify PKCS#7 signatures of BPF programs and their metadata maps when `attr-\u003esignature` is provided. It also introduces support for a continuous `fd_array` via `fd_array_cnt`. These are reachable core BPF verifier changes that should be fuzzed.",
"WorthFuzzing": true
} |
| 1/1 | 2026/07/06 20:24 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 9e7deb7daed029fcd9201e54beff5f72e265d60b\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Mon Jul 6 20:24:50 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/Documentation/bpf/index.rst b/Documentation/bpf/index.rst\nindex 0d5c6f6592667a..638a00d42bc2a1 100644\n--- a/Documentation/bpf/index.rst\n+++ b/Documentation/bpf/index.rst\n@@ -28,6 +28,7 @@ that goes into great technical depth about the BPF Architecture.\n classic_vs_extended.rst\n bpf_iterators\n bpf_licensing\n+ signing\n test_debug\n clang-notes\n linux-notes\ndiff --git a/Documentation/bpf/signing.rst b/Documentation/bpf/signing.rst\nnew file mode 100644\nindex 00000000000000..446b4edbae626c\n--- /dev/null\n+++ b/Documentation/bpf/signing.rst\n@@ -0,0 +1,496 @@\n+.. SPDX-License-Identifier: GPL-2.0\n+\n+============\n+BPF signing\n+============\n+\n+This document describes how BPF programs are cryptographically signed, how the\n+kernel verifies them at load time, and how Linux Security Modules (LSMs) -\n+including the BPF LSM - use the resulting verdict to enforce policy. It is\n+written for developers who want to produce signed BPF objects, understand what\n+the signature actually guarantees, or build a policy on top of it.\n+\n+Motivation\n+==========\n+\n+A signed BPF program lets the kernel establish that the bytecode being loaded\n+originates from a trusted producer and was not modified in transit. On its own\n+the kernel does not *require* signatures - an unsigned program loads exactly as\n+before - but it records a verdict (see `The verdict`_) that an LSM can gate on.\n+This is the building block for policies such as \"only run BPF that was signed by\n+a key in the trusted keyring\", as could in the future be enforced by an LSM\n+such as IPE.\n+\n+Signing is orthogonal to the existing permission model: it does not replace the\n+capability checks or the verifier. A signed load still requires the usual\n+privileges (``CAP_BPF`` and any program-type-specific capability, subject to\n+``kernel.unprivileged_bpf_disabled``), and the loader's instructions are still\n+checked by the verifier like any other program. A valid signature establishes\n+*origin and integrity*, not safety - it lets a policy trust where the bytecode\n+came from, it does not let a load skip any check it would otherwise face.\n+\n+The hard part is *what* gets signed. A naive scheme would sign a program's\n+instruction buffer at build time and verify that signature at\n+``BPF_PROG_LOAD``. That does not survive contact with real BPF objects, because\n+the bytes the kernel finally loads are not the bytes the developer built and\n+signed. Between the two, libbpf and the kernel rewrite the program:\n+\n+- **map file descriptors** are patched into ``ld_imm64`` instructions\n+ (``BPF_PSEUDO_MAP_FD``), and a map's fd is assigned at load time, so it\n+ differs on every run;\n+- **CO-RE relocations** rewrite field offsets, sizes and existence flags against\n+ the *running* kernel's BTF, so the result differs from one kernel to the next;\n+- **kfunc and ksym references** are resolved to ids/addresses in the running\n+ kernel;\n+- **global data** (``.rodata``/``.data``/``.bss``) is created and seeded as maps\n+ at load.\n+\n+So a signature over the original instructions cannot match the relocated\n+instructions the verifier ends up checking, and the relocated form cannot be\n+produced ahead of time because it depends on the target kernel. There is no\n+fixed byte string that is both signable at build time and what the kernel\n+actually loads - which is why a program cannot simply be signed and loaded\n+directly.\n+\n+The trusted loader\n+==================\n+\n+The solution is to move that setup work *into* a small BPF program - the\n+**loader** - and sign the loader instead of the individual programs. libbpf's\n+``gen_loader`` machinery (``bpftool gen skeleton -L``, the \"light skeleton\")\n+emits a ``BPF_PROG_TYPE_SYSCALL`` program whose body performs the bpf() syscalls\n+that create maps, apply relocations, and load the real programs. The payload it\n+installs - the serialized programs, map descriptions, relocation data and\n+initial values - lives in a separate array map, the **metadata map**\n+(``__loader.map``).\n+\n+So the unit of trust is the loader, and the signing contract is::\n+\n+ Sig(I_loader || D_meta)\n+\n+where ``I_loader`` is the loader's instruction stream and ``D_meta`` is the\n+content of the metadata map. Verifying the loader's signature establishes that\n+both the loader *and* the payload it is about to install are authentic. The\n+loader is reproducible: ``gen_loader`` builds it from primitives so the same\n+object yields the same bytes on any build host.\n+\n+Why the loader is signable when the program is not\n+--------------------------------------------------\n+\n+The loader sidesteps every rewrite listed above, because the bytes that are\n+signed are *relocation-invariant*:\n+\n+- The loader's own instructions are a fixed sequence of bpf() syscalls emitted\n+ by ``gen_loader``; they carry no CO-RE relocations and resolve no ksyms, so\n+ they are identical on every kernel. The metadata map is referenced by *index*\n+ into ``fd_array`` (``BPF_PSEUDO_MAP_IDX_VALUE``), not by a baked-in file\n+ descriptor, so even that reference does not change between build and load.\n+ The loader instruction bytes the kernel verifies are exactly the bytes that\n+ were signed.\n+- The metadata map is opaque, frozen data - the serialized target programs,\n+ their relocation records, map descriptions and initial values. Its bytes are\n+ identical at build time and at load time, so they are simply appended to the\n+ instructions and covered by the same signature (there is no separate metadata\n+ hash to compute or compare).\n+\n+All the host-specific rewriting - creating maps, patching their fds into the\n+target programs, applying CO-RE, resolving ksyms, seeding global data - still\n+happens, but it happens *inside the loader at runtime*, on the verified\n+metadata, **after** the kernel has verified the ``insns || metadata`` signature.\n+The kernel never has to verify the relocated target programs: it verifies the\n+loader and its inputs once, and trust transfers to whatever that now-trusted,\n+deterministic loader installs. The relocation step is moved from \"before the\n+signature can be checked\" to \"after a trusted program runs\" - which is exactly\n+what makes it signable.\n+\n+Because the metadata map is the loader's only untrusted input, two existing map\n+properties are reused to keep it trustworthy across the load:\n+\n+Exclusive maps\n+ A map created with ``excl_prog_hash`` (see ``BPF_MAP_CREATE``) may only be\n+ accessed by a program whose digest matches that hash. The verifier enforces\n+ ``map-\u003eexcl_prog_sha == prog-\u003edigest`` for every map a program uses, so the\n+ metadata map is bound to exactly the signed loader and cannot be shared with\n+ or mutated by another program.\n+\n+Frozen maps\n+ The metadata map is frozen (``BPF_MAP_FREEZE``) before the loader is loaded.\n+ Freezing blocks further userspace writes, so the bytes folded into the\n+ signature cannot change before the loader runs. (Freezing does not make the\n+ map read-only to the loader program itself, which still writes created file\n+ descriptors back into the blob's scratch area.)\n+\n+Load-time verification\n+=======================\n+\n+Rather than have the loader check its own metadata from within BPF, the kernel\n+verifies it directly at ``BPF_PROG_LOAD``, with no new UAPI. The mechanism\n+reuses the existing ``fd_array``:\n+\n+#. Userspace creates the metadata map with ``excl_prog_hash`` set to the\n+ loader's digest, populates it, and freezes it.\n+#. The loader is loaded with ``signature``/``signature_size``/``keyring_id``\n+ set, the metadata map referenced through ``fd_array``, and ``fd_array_cnt``\n+ set so the kernel knows the array's length.\n+#. Signature verification runs inside the verifier (``bpf_check()``), once it\n+ has resolved the ``fd_array`` entries into the program's ``used_maps``. The\n+ maps folded into the signature are therefore the very objects the program\n+ binds - a single resolution of ``fd_array``, not a separate read, so the\n+ verified bytes cannot be swapped for a different map after the check (no\n+ time-of-check/time-of-use window). Each folded map must be exclusive (carry\n+ ``excl_prog_sha``) and a plain array map (``BPF_MAP_TYPE_ARRAY``); only an\n+ array map exposes its value buffer through ``map_direct_value_addr()`` as a\n+ kernel address spanning ``value_size`` bytes. A map that is not exclusive, not\n+ frozen, or not a plain array is rejected, with a verifier log message naming\n+ the offending map. The kernel appends each map's frozen\n+ contents to the instruction buffer and verifies the PKCS#7 signature over the\n+ concatenation ``insns || metadata_0 || metadata_1 || ...`` in ``used_maps``\n+ order, before it rewrites the (signed) instructions.\n+\n+A signed program therefore takes one of exactly two shapes, both fully\n+supported:\n+\n+- **No bound maps** (``fd_array_cnt == 0``): there is nothing to append, so the\n+ kernel verifies the signature over the instructions alone. A valid signature\n+ yields ``BPF_SIG_VERIFIED`` and the program loads. This is the ordinary case\n+ for a directly-loaded signed program with no separate payload; it is *not*\n+ rejected for \"missing\" metadata, because it has none to cover.\n+- **Exclusive bound maps** (``fd_array_cnt \u003e 0``): every entry is exclusive and\n+ folded, so the signature covers ``insns || metadata``.\n+\n+There is no third shape: a non-exclusive map in a signed program's ``fd_array``\n+is rejected rather than silently left out of the signature, so a program bound\n+to a signed loader never has a map the signature does not cover.\n+\n+The digest binding (``excl_prog_sha == prog-\u003edigest``) is enforced by the\n+verifier as usual; because that check runs while ``fd_array`` is resolved -\n+before the verifier would otherwise compute the tag - ``prog-\u003edigest`` is\n+computed up front in the verifier, over the unmodified (signature-covered)\n+instructions, for any signed load.\n+\n+Coverage is then enforced as the verifier resolves instructions, at the point\n+each object is bound rather than by a count taken afterwards. Once the signature\n+has been verified, binding any further map is refused: a map reached by a\n+directly-referenced fd, or a map swapped into an ``fd_array`` slot the loader\n+reads, is not among those already folded, so it is rejected the moment the\n+verifier tries to bind it. A BTF is refused outright for a signed program - a\n+ksym or a BTF fd in ``fd_array``, whether resolved up front or lazily for a\n+module kfunc, is rejected when it would be bound. Together with the fold rule\n+above this keeps the verdict binary: a signed program cannot use a map its\n+signature does not cover, and a different but equally digest-bound map cannot be\n+substituted at an ``fd_array`` slot. Non-exclusive maps are never folded, so a\n+signed program cannot use one at all.\n+\n+The verdict\n+===========\n+\n+A program is either unsigned or fully verified - there is no intermediate\n+state. The outcome is recorded in ``prog-\u003eaux-\u003esig.verdict``:\n+\n+.. code-block:: c\n+\n+ enum bpf_sig_verdict {\n+ BPF_SIG_UNSIGNED = 0,\n+ BPF_SIG_VERIFIED,\n+ };\n+\n+``BPF_SIG_VERIFIED`` means the signature is valid and covers the instructions\n+*and* the frozen contents of every exclusive map the program uses:\n+\n+- For an ordinary, directly-loaded signed program the instructions are the whole\n+ artifact and it uses no exclusive maps, so a valid instruction signature is\n+ the complete verification.\n+- For a signed loader the metadata map is exclusive, so its contents are folded\n+ in and the signature covers ``insns || metadata``.\n+\n+There is deliberately no \"instructions verified but metadata not\" verdict: a\n+signed loader that fails to cover its metadata is *rejected* (see above), not\n+recorded with a weaker verdict. ``BPF_SIG_VERIFIED`` therefore always means the\n+program and everything the signature is responsible for are authentic, which is\n+what a policy can rely on.\n+\n+Alongside the verdict the kernel records which keyring validated the signature;\n+see `Keyrings`_.\n+\n+Enforcement via LSMs\n+====================\n+\n+Signing only *records* a verdict; an LSM turns it into policy. The verdict and\n+keyring fields live in ``struct bpf_prog_aux``, so a BPF LSM program can read\n+them directly (see Documentation/bpf/prog_lsm.rst for writing and attaching BPF\n+LSM programs); the same fields are equally available to in-tree LSMs. Two hooks\n+are useful at different points of the load: the dedicated\n+``security_bpf_prog_load()`` gates admission before the main verification work,\n+and the existing ``security_bpf_prog()`` observes a program that has fully\n+loaded.\n+\n+Admission: ``security_bpf_prog_load()``\n+---------------------------------------\n+\n+This hook gates admission **for every load**, from a single call site inside the\n+verifier (``bpf_check()``), before the main verification work. It runs after the\n+optional signature verification, so the verdict and keyring fields are final - the\n+hook can see whether, and how strongly, the program was signed, which keyring\n+validated it, the load ``attr``, the BPF token and whether the load came from the\n+kernel. For a signed load the verdict is ``BPF_SIG_VERIFIED`` here (the signature\n+has just been checked); for an unsigned load it is ``BPF_SIG_UNSIGNED``.\n+\n+This is the place for *coarse admission* that must also see unsigned and\n+not-yet-verified loads: require a signature at all, restrict the acceptable\n+keyring, restrict which token/credentials may load BPF, apply per-program-type\n+rules, or audit every load attempt that makes it past signature verification -\n+attempts failing the signature or the metadata binding abort before this hook\n+fires. It is the primary deny point.\n+\n+One subtlety: this hook runs *before* the verifier finishes its work, so\n+``BPF_SIG_VERIFIED`` *here* means only \"validly signed\" - not \"loaded\". Allowing\n+a load at this point lets it *proceed*; it does not guarantee the program will\n+load. A validly signed program can still be rejected afterwards on two\n+independent grounds: the verifier may reject it like any other program (unsafe\n+memory access, bad control flow, resource limits, ...), and the kernel separately\n+refuses - as the verifier resolves instructions and binds each object - any map\n+the signature does not cover or any BTF at all, regardless of what this hook\n+returned. Only after the program has fully loaded, at the next hook\n+(``security_bpf_prog()``), does ``BPF_SIG_VERIFIED`` carry its full meaning:\n+validly signed *and* fully verified.\n+\n+A more realistic admission policy than \"is it signed at all\": accept programs\n+signed by a system keyring, accept a user-keyring signature only if the\n+key/keyring it was verified against is on an explicit allowlist, and emit a\n+tamper-evident record of every decision so that even denied attempts are\n+auditable. (Illustrative - error checking elided.)\n+\n+.. code-block:: c\n+\n+ /* Serials of user keys/keyrings we additionally trust. */\n+ struct {\n+ __uint(type, BPF_MAP_TYPE_HASH);\n+ __type(key, __s32); /* keyring_serial */\n+ __type(value, __u8);\n+ __uint(max_entries, 64);\n+ } trusted_user_keys SEC(\".maps\");\n+\n+ /* Audit stream consumed by a userspace logger. */\n+ struct {\n+ __uint(type, BPF_MAP_TYPE_RINGBUF);\n+ __uint(max_entries, 1 \u003c\u003c 16);\n+ } audit SEC(\".maps\");\n+\n+ struct decision { __u32 prog_type, verdict, ktype; __s32 serial, ret; };\n+\n+ SEC(\"lsm/bpf_prog_load\")\n+ int BPF_PROG(admit, struct bpf_prog *prog, union bpf_attr *attr,\n+ struct bpf_token *token, bool kernel)\n+ {\n+ __u32 verdict = prog-\u003eaux-\u003esig.verdict;\n+ __u32 ktype = prog-\u003eaux-\u003esig.keyring_type;\n+ __s32 serial = prog-\u003eaux-\u003esig.keyring_serial;\n+ struct decision *d;\n+ int ret = 0;\n+\n+ if (kernel)\n+ return 0; /* trust in-kernel loads */\n+\n+ if (verdict != BPF_SIG_VERIFIED)\n+ ret = -EPERM; /* must be validly signed */\n+ else if (ktype == BPF_SIG_KEYRING_USER \u0026\u0026\n+ !bpf_map_lookup_elem(\u0026trusted_user_keys, \u0026serial))\n+ ret = -EPERM; /* key/keyring not allowlisted */\n+\n+ d = bpf_ringbuf_reserve(\u0026audit, sizeof(*d), 0);\n+ if (d) {\n+ d-\u003eprog_type = attr-\u003eprog_type;\n+ d-\u003everdict = verdict;\n+ d-\u003ektype = ktype;\n+ d-\u003eserial = serial;\n+ d-\u003eret = ret;\n+ bpf_ringbuf_submit(d, 0); /* record allow *and* deny */\n+ }\n+ return ret;\n+ }\n+\n+Observing a verified load: ``security_bpf_prog()``\n+--------------------------------------------------\n+\n+There is deliberately no separate \"metadata attested\" hook. The coverage check\n+above is enforced by the kernel unconditionally, so a signed loader that fails\n+to cover its metadata never loads and an LSM never has to re-establish that\n+fact. To *act on* a program that has successfully and fully loaded, use the\n+existing ``security_bpf_prog()`` hook (``lsm/bpf_prog``), which fires from\n+``bpf_prog_new_fd()`` - after the verifier, after the coverage check, and after\n+``bpf_prog_alloc_id()``. Relative to the admission hook this point is strictly\n+later and stronger:\n+\n+- the program has an id (``prog-\u003eaux-\u003eid``), so it can be recorded or correlated\n+ with later events;\n+- ``verdict == BPF_SIG_VERIFIED`` *here* means **fully** verified - a program\n+ that used a map the signature does not cover was already rejected, so it cannot\n+ reach this point;\n+- it observes only programs that actually loaded; a failed load never mints an\n+ fd, so it never reaches this hook.\n+\n+It takes only the ``prog`` and a non-zero return still aborts (the fd is not\n+handed out), so it can veto as well as observe. One wrinkle: it also fires on\n+other paths that mint a new program fd - notably ``bpf_prog_get_fd_by_id()`` -\n+not just on a fresh load. Because the program already has its id here, an LSM\n+can tell the two apart with a small hash map: the *first* time an id is seen is\n+the load; a later sighting of the same id is just another fd to a program that\n+already exists.\n+\n+To bound the map and let a reused id read as a fresh load, this can be paired\n+with ``security_bpf_prog_free()`` (``lsm/bpf_prog_free``), which deletes the\n+entry on teardown - keyed by the same ``prog`` pointer, since\n+``bpf_prog_free_id()`` has already cleared ``prog-\u003eaux-\u003eid`` to ``0`` by the time\n+that hook runs. (Illustrative - privileged LSM, error checking elided.)\n+\n+.. code-block:: c\n+\n+ struct rec { __u32 id, ktype; __s32 serial; };\n+\n+ struct {\n+ __uint(type, BPF_MAP_TYPE_HASH);\n+ __type(key, __u64); /* struct bpf_prog * -- stable id */\n+ __type(value, struct rec);\n+ __uint(max_entries, 4096);\n+ } live SEC(\".maps\");\n+\n+ SEC(\"lsm/bpf_prog\") /* fires after load and on every later fd */\n+ int BPF_PROG(observe, struct bpf_prog *prog)\n+ {\n+ __u64 key = (__u64)(unsigned long)prog;\n+ struct rec r;\n+\n+ if (prog-\u003eaux-\u003esig.verdict != BPF_SIG_VERIFIED)\n+ return 0;\n+ if (bpf_map_lookup_elem(\u0026live, \u0026key))\n+ return 0; /* seen before: a later fd, not a load */\n+\n+ /* First sighting == this program just loaded; id is valid here. */\n+ r.id = prog-\u003eaux-\u003eid;\n+ r.ktype = prog-\u003eaux-\u003esig.keyring_type;\n+ r.serial = prog-\u003eaux-\u003esig.keyring_serial;\n+ bpf_map_update_elem(\u0026live, \u0026key, \u0026r, BPF_NOEXIST);\n+ /* ... newly-loaded verified-program action, e.g. record r.id ... */\n+ return 0;\n+ }\n+\n+Putting them together: to *require* verified BPF, deny at the admission hook\n+unless the verdict is ``BPF_SIG_VERIFIED`` (and, if desired, restrict the\n+keyring). The kernel then guarantees that any program which actually loads with\n+that verdict covered all of its exclusive maps, rejecting any that did not - so\n+a deny-by-default admission policy needs no second enforcement point. Use\n+``security_bpf_prog()`` to record or finally gate the verified programs once\n+they carry an id. The ``verdict``, ``keyring_type`` and ``keyring_serial`` fields\n+let a policy distinguish, for example, \"verified and signed by a builtin key\"\n+from \"verified by a user key\". A policy LSM such as IPE could consume the same\n+hooks to enforce system policy without writing any BPF, though none implements\n+this today.\n+\n+Keyrings\n+========\n+\n+``keyring_id`` selects the trusted keyring the PKCS#7 signature is verified\n+against. The well-known ids ``0`` (builtin), ``VERIFY_USE_SECONDARY_KEYRING``\n+and ``VERIFY_USE_PLATFORM_KEYRING`` select the corresponding system keyrings;\n+any other value is treated as the serial of a user/session key or keyring.\n+The keyring is looked up first, before the signature bytes are examined, so a\n+signature naming a non-existent keyring is rejected up front, and a failed\n+verification aborts the load - so a program that loads successfully with a\n+signature always has consistent keyring fields recorded.\n+\n+Two fields are recorded in ``prog-\u003eaux-\u003esig`` for an LSM to inspect:\n+\n+``keyring_type`` (``enum bpf_sig_keyring``)\n+ Classified purely from ``keyring_id`` whenever the program is signed:\n+ ``BPF_SIG_KEYRING_BUILTIN``, ``_SECONDARY``, ``_PLATFORM`` for the system\n+ keyrings, or ``_USER`` for a user/session keyring. It is\n+ ``BPF_SIG_KEYRING_NONE`` for an unsigned program.\n+\n+``keyring_serial`` (``s32``)\n+ Set **only** on a successful verification, to the serial of the\n+ **user/session key or keyring** that ``keyring_id`` resolved to - the\n+ object the signature was verified against, not the individual asymmetric\n+ key inside it that matched the signer. Passing\n+ ``KEY_SPEC_SESSION_KEYRING``, for example, records the session keyring's\n+ serial. The system keyrings are trusted as a whole and expose no serial\n+ here, so the serial is ``0`` for builtin, secondary and platform\n+ signatures, and ``0`` for unsigned programs. In other words, a non-zero\n+ ``keyring_serial`` is exactly \"verified against the user key/keyring with\n+ this serial\".\n+\n+.. list-table::\n+ :header-rows: 1\n+\n+ * - ``keyring_id``\n+ - ``keyring_type``\n+ - ``keyring_serial``\n+ * - (no signature)\n+ - ``BPF_SIG_KEYRING_NONE``\n+ - ``0``\n+ * - ``0``\n+ - ``BPF_SIG_KEYRING_BUILTIN``\n+ - ``0``\n+ * - ``VERIFY_USE_SECONDARY_KEYRING``\n+ - ``BPF_SIG_KEYRING_SECONDARY``\n+ - ``0``\n+ * - ``VERIFY_USE_PLATFORM_KEYRING``\n+ - ``BPF_SIG_KEYRING_PLATFORM``\n+ - ``0``\n+ * - other (a user/session key serial)\n+ - ``BPF_SIG_KEYRING_USER``\n+ - serial of the resolved key/keyring\n+\n+Producing a signed object\n+==========================\n+\n+``bpftool`` generates and signs a light skeleton in one step::\n+\n+ bpftool gen skeleton -L -S -k \u003cprivate_key.pem\u003e -i \u003ccertificate.x509\u003e \\\n+ obj.bpf.o \u003e obj.lskel.h\n+\n+``-L`` selects the light-skeleton (``gen_loader``) backend and ``-S`` enables\n+signing; ``-k`` and ``-i`` supply the signing key and its X.509 certificate.\n+``bpftool`` signs ``insns || metadata`` - the exact bytes the kernel\n+reconstructs - and also computes ``excl_prog_hash`` as the digest of the loader\n+instructions so the metadata map can be bound to the loader. The signature and\n+hash are embedded in the generated header; the certificate is used only for\n+signing and is not included. Loading the skeleton performs the\n+create/populate/freeze/load sequence described above.\n+\n+At runtime the trusted public key must be present in the chosen keyring (for\n+example added to the session keyring, or built into the kernel's builtin trusted\n+keyring) for verification to succeed.\n+\n+UAPI reference\n+==============\n+\n+``BPF_PROG_LOAD`` (``union bpf_attr``):\n+\n+``signature``, ``signature_size``\n+ Pointer to and length of the PKCS#7 signature blob.\n+\n+``keyring_id``\n+ Trusted keyring selector (see `Keyrings`_).\n+\n+``fd_array``, ``fd_array_cnt``\n+ Array of map file descriptors bound to the program. ``fd_array_cnt`` must be\n+ set for the kernel to scan the array. When a signature is present, every map\n+ in the array must be exclusive; its frozen contents are folded into the\n+ verified buffer, and a non-exclusive entry is rejected.\n+\n+``BPF_MAP_CREATE`` (``union bpf_attr``):\n+\n+``excl_prog_hash``, ``excl_prog_hash_size``\n+ SHA-256 digest of the program permitted to access this (exclusive) map. This\n+ binds the metadata map to the loader; it is not a hash of the map *content*.\n+ The map content is not hashed separately at all - it is covered, as bytes,\n+ by the program signature.\n+\n+Notes and limitations\n+======================\n+\n+- The instructions plus folded metadata are verified as one ``bpf_dynptr``,\n+ which bounds the combined size (currently ~16 MiB); very large objects can\n+ exceed it.\n+- The metadata container is a single-element array map, accessed through\n+ ``map_direct_value_addr``.\ndiff --git a/include/linux/bpf.h b/include/linux/bpf.h\nindex ba09795e0bfdb7..2e0f12fb4cea8b 100644\n--- a/include/linux/bpf.h\n+++ b/include/linux/bpf.h\n@@ -299,7 +299,6 @@ struct bpf_map_owner {\n \n struct bpf_map {\n \tu8 sha[SHA256_DIGEST_SIZE];\n-\tu32 excl;\n \tconst struct bpf_map_ops *ops;\n \tstruct bpf_map *inner_map_meta;\n #ifdef CONFIG_SECURITY\ndiff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h\nindex 76b8b7627a1085..317e99b9acc0a4 100644\n--- a/include/linux/bpf_verifier.h\n+++ b/include/linux/bpf_verifier.h\n@@ -898,6 +898,14 @@ struct bpf_scc_info {\n \n struct bpf_liveness;\n \n+struct bpf_fd_array {\n+\tunion {\n+\t\tstruct bpf_map *map;\n+\t\tstruct btf *btf;\n+\t\tunsigned long val;\n+\t};\n+};\n+\n /* single container for all structs\n * one verifier_env per bpf_check() call\n */\n@@ -939,6 +947,7 @@ struct bpf_verifier_env {\n \tbool bypass_spec_v4;\n \tbool seen_direct_write;\n \tbool seen_exception;\n+\tbool signature;\n \tstruct bpf_insn_aux_data *insn_aux_data; /* array of per-insn state */\n \tconst struct bpf_line_info *prev_linfo;\n \tstruct bpf_verifier_log log;\n@@ -989,7 +998,19 @@ struct bpf_verifier_env {\n \tu32 free_list_size;\n \tu32 explored_states_size;\n \tu32 num_backedges;\n-\tbpfptr_t fd_array;\n+\t/*\n+\t * The program's fd_array comes in two shapes, told apart by whether\n+\t * the caller passed fd_array_cnt. They are mutually exclusive:\n+\t * - continuous (fd_array_cnt given): -\u003efd_array holds every entry\n+\t * resolved to its object up front, indexed by fd_array position,\n+\t * with -\u003efd_array_cnt slots; -\u003efd_array_raw is unused.\n+\t * - sparse (no fd_array_cnt): -\u003efd_array is NULL, and entries are\n+\t * read from -\u003efd_array_raw (the caller's fd_array) and resolved\n+\t * on the spot at each reference.\n+\t */\n+\tstruct bpf_fd_array *fd_array;\n+\tu32 fd_array_cnt;\n+\tbpfptr_t fd_array_raw;\n \n \t/* bit mask to keep track of whether a register has been accessed\n \t * since the last time the function state was printed\ndiff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c\nindex 6db306d23b479f..358f2b0ce2bd31 100644\n--- a/kernel/bpf/syscall.c\n+++ b/kernel/bpf/syscall.c\n@@ -40,7 +40,6 @@\n #include \u003clinux/tracepoint.h\u003e\n #include \u003clinux/overflow.h\u003e\n #include \u003clinux/cookie.h\u003e\n-#include \u003clinux/verification.h\u003e\n #include \u003clinux/btf_ids.h\u003e\n \n #include \u003cnet/netfilter/nf_bpf_link.h\u003e\n@@ -1599,13 +1598,6 @@ static int map_create_alloc(union bpf_attr *attr, bpfptr_t uattr, struct bpf_ver\n \t\t\terr = -EFAULT;\n \t\t\tgoto free_map;\n \t\t}\n-\n-\t\t/* See libbpf: emit_signature_match() */\n-\t\tBUILD_BUG_ON(offsetof(struct bpf_map, excl) != SHA256_DIGEST_SIZE);\n-\t\tBUILD_BUG_ON(!__same_type(map-\u003eexcl, u32));\n-\t\tBUILD_BUG_ON(offsetof(struct bpf_map, sha) != 0);\n-\t\tBUILD_BUG_ON(!__same_type(map-\u003esha, u8[SHA256_DIGEST_SIZE]));\n-\t\tmap-\u003eexcl = 1;\n \t} else if (attr-\u003eexcl_prog_hash_size) {\n \t\tbpf_log(log, \"Invalid excl_prog_hash_size.\\n\");\n \t\terr = -EINVAL;\n@@ -2886,64 +2878,6 @@ static bool is_perfmon_prog_type(enum bpf_prog_type prog_type)\n \t}\n }\n \n-static enum bpf_sig_keyring bpf_classify_keyring(s32 keyring_id)\n-{\n-\tswitch (keyring_id) {\n-\tcase 0:\n-\t\treturn BPF_SIG_KEYRING_BUILTIN;\n-\tcase (s32)(unsigned long)VERIFY_USE_SECONDARY_KEYRING:\n-\t\treturn BPF_SIG_KEYRING_SECONDARY;\n-\tcase (s32)(unsigned long)VERIFY_USE_PLATFORM_KEYRING:\n-\t\treturn BPF_SIG_KEYRING_PLATFORM;\n-\tdefault:\n-\t\treturn BPF_SIG_KEYRING_USER;\n-\t}\n-}\n-\n-static int bpf_prog_verify_signature(struct bpf_prog *prog, union bpf_attr *attr,\n-\t\t\t\t bool is_kernel, s32 *keyring_serial)\n-{\n-\tbpfptr_t usig = make_bpfptr(attr-\u003esignature, is_kernel);\n-\tstruct bpf_dynptr_kern sig_ptr, insns_ptr;\n-\tstruct bpf_key *key = NULL;\n-\tvoid *sig;\n-\tint err = 0;\n-\n-\t/*\n-\t * Don't attempt to use kmalloc_large or vmalloc for signatures.\n-\t * Practical signature for BPF program should be below this limit.\n-\t */\n-\tif (attr-\u003esignature_size \u003e KMALLOC_MAX_CACHE_SIZE)\n-\t\treturn -EINVAL;\n-\n-\tif (system_keyring_id_check(attr-\u003ekeyring_id) == 0)\n-\t\tkey = bpf_lookup_system_key(attr-\u003ekeyring_id);\n-\telse\n-\t\tkey = bpf_lookup_user_key(attr-\u003ekeyring_id, 0);\n-\n-\tif (!key)\n-\t\treturn -EINVAL;\n-\n-\tsig = kvmemdup_bpfptr(usig, attr-\u003esignature_size);\n-\tif (IS_ERR(sig)) {\n-\t\tbpf_key_put(key);\n-\t\treturn PTR_ERR(sig);\n-\t}\n-\n-\tbpf_dynptr_init(\u0026sig_ptr, sig, BPF_DYNPTR_TYPE_LOCAL, 0,\n-\t\t\tattr-\u003esignature_size);\n-\tbpf_dynptr_init(\u0026insns_ptr, prog-\u003einsnsi, BPF_DYNPTR_TYPE_LOCAL, 0,\n-\t\t\tprog-\u003elen * sizeof(struct bpf_insn));\n-\n-\terr = bpf_verify_pkcs7_signature((struct bpf_dynptr *)\u0026insns_ptr,\n-\t\t\t\t\t (struct bpf_dynptr *)\u0026sig_ptr, key);\n-\tif (!err)\n-\t\t*keyring_serial = bpf_key_serial(key);\n-\tbpf_key_put(key);\n-\tkvfree(sig);\n-\treturn err;\n-}\n-\n static int bpf_prog_mark_insn_arrays_ready(struct bpf_prog *prog)\n {\n \tint err;\n@@ -3133,17 +3067,8 @@ static int bpf_prog_load(union bpf_attr *attr, bpfptr_t uattr, struct bpf_log_at\n \n \t/* eBPF programs must be GPL compatible to use GPL-ed functions */\n \tprog-\u003egpl_compatible = license_is_gpl_compatible(license) ? 1 : 0;\n-\tif (attr-\u003esignature) {\n-\t\terr = bpf_prog_verify_signature(prog, attr, uattr.is_kernel,\n-\t\t\t\t\t\t\u0026prog-\u003eaux-\u003esig.keyring_serial);\n-\t\tif (err)\n-\t\t\tgoto free_prog;\n-\t\tprog-\u003eaux-\u003esig.keyring_type = bpf_classify_keyring(attr-\u003ekeyring_id);\n-\t\tprog-\u003eaux-\u003esig.verdict = BPF_SIG_VERIFIED;\n-\t} else {\n-\t\tprog-\u003eaux-\u003esig.keyring_type = BPF_SIG_KEYRING_NONE;\n-\t\tprog-\u003eaux-\u003esig.verdict = BPF_SIG_UNSIGNED;\n-\t}\n+\tprog-\u003eaux-\u003esig.keyring_type = BPF_SIG_KEYRING_NONE;\n+\tprog-\u003eaux-\u003esig.verdict = BPF_SIG_UNSIGNED;\n \tprog-\u003eorig_prog = NULL;\n \tprog-\u003ejited = 0;\n \n@@ -3189,10 +3114,6 @@ static int bpf_prog_load(union bpf_attr *attr, bpfptr_t uattr, struct bpf_log_at\n \tif (err \u003c 0)\n \t\tgoto free_prog;\n \n-\terr = security_bpf_prog_load(prog, attr, token, uattr.is_kernel);\n-\tif (err)\n-\t\tgoto free_prog;\n-\n \t/* run eBPF verifier */\n \terr = bpf_check(\u0026prog, attr, uattr, attr_log);\n \tif (err \u003c 0)\ndiff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c\nindex d46f7db20d8f04..65f53128fc33d6 100644\n--- a/kernel/bpf/verifier.c\n+++ b/kernel/bpf/verifier.c\n@@ -22,6 +22,8 @@\n #include \u003clinux/ctype.h\u003e\n #include \u003clinux/error-injection.h\u003e\n #include \u003clinux/bpf_lsm.h\u003e\n+#include \u003clinux/security.h\u003e\n+#include \u003clinux/verification.h\u003e\n #include \u003clinux/btf_ids.h\u003e\n #include \u003clinux/poison.h\u003e\n #include \u003clinux/module.h\u003e\n@@ -2490,6 +2492,83 @@ int bpf_get_kfunc_addr(const struct bpf_prog *prog, u32 func_id,\n \treturn 0;\n }\n \n+#define BPF_FD_SLOT_BTF\t1UL\n+\n+static void fd_slot_set_map(struct bpf_fd_array *slot, struct bpf_map *map)\n+{\n+\tslot-\u003eval = (unsigned long)map;\n+}\n+\n+static void fd_slot_set_btf(struct bpf_fd_array *slot, struct btf *btf)\n+{\n+\tslot-\u003eval = (unsigned long)btf | BPF_FD_SLOT_BTF;\n+}\n+\n+static struct bpf_map *fd_slot_map(struct bpf_fd_array slot)\n+{\n+\tif (slot.val \u0026 BPF_FD_SLOT_BTF)\n+\t\treturn NULL;\n+\treturn (struct bpf_map *)slot.val;\n+}\n+\n+static struct btf *fd_slot_btf(struct bpf_fd_array slot)\n+{\n+\tif (!(slot.val \u0026 BPF_FD_SLOT_BTF))\n+\t\treturn NULL;\n+\treturn (struct btf *)(slot.val \u0026 ~BPF_FD_SLOT_BTF);\n+}\n+\n+static struct btf *\n+fd_array_get_btf_continuous(struct bpf_verifier_env *env, u32 idx)\n+{\n+\tstruct btf *btf;\n+\n+\tif (idx \u003e= env-\u003efd_array_cnt) {\n+\t\tverbose(env, \"kfunc fd_idx %u out of bounds, fd_array_cnt %u\\n\",\n+\t\t\tidx, env-\u003efd_array_cnt);\n+\t\treturn ERR_PTR(-EINVAL);\n+\t}\n+\tbtf = fd_slot_btf(env-\u003efd_array[idx]);\n+\tif (!btf) {\n+\t\tverbose(env, \"kfunc fd_idx %u is not a module BTF\\n\", idx);\n+\t\treturn ERR_PTR(-EINVAL);\n+\t}\n+\tbtf_get(btf);\n+\treturn btf;\n+}\n+\n+static struct btf *\n+fd_array_get_btf_sparse(struct bpf_verifier_env *env, u32 idx)\n+{\n+\tstruct btf *btf;\n+\tint btf_fd;\n+\n+\tif (copy_from_bpfptr_offset(\u0026btf_fd, env-\u003efd_array_raw,\n+\t\t\t\t (size_t)idx * sizeof(btf_fd), sizeof(btf_fd)))\n+\t\treturn ERR_PTR(-EFAULT);\n+\tbtf = btf_get_by_fd(btf_fd);\n+\tif (IS_ERR(btf)) {\n+\t\tverbose(env, \"invalid module BTF fd specified\\n\");\n+\t\treturn btf;\n+\t}\n+\treturn btf;\n+}\n+\n+static struct btf *fd_array_get_btf(struct bpf_verifier_env *env, u32 idx)\n+{\n+\tif (env-\u003esignature) {\n+\t\tverbose(env, \"signed program cannot bind any BTF\\n\");\n+\t\treturn ERR_PTR(-EACCES);\n+\t}\n+\tif (env-\u003efd_array)\n+\t\treturn fd_array_get_btf_continuous(env, idx);\n+\tif (!bpfptr_is_null(env-\u003efd_array_raw))\n+\t\treturn fd_array_get_btf_sparse(env, idx);\n+\n+\tverbose(env, \"kfunc offset \u003e 0 without fd_array is invalid\\n\");\n+\treturn ERR_PTR(-EPROTO);\n+}\n+\n static struct btf *__find_kfunc_desc_btf(struct bpf_verifier_env *env,\n \t\t\t\t\t s16 offset)\n {\n@@ -2498,7 +2577,6 @@ static struct btf *__find_kfunc_desc_btf(struct bpf_verifier_env *env,\n \tstruct bpf_kfunc_btf *b;\n \tstruct module *mod;\n \tstruct btf *btf;\n-\tint btf_fd;\n \n \ttab = env-\u003eprog-\u003eaux-\u003ekfunc_btf_tab;\n \tb = bsearch(\u0026kf_btf, tab-\u003edescs, tab-\u003enr_descs,\n@@ -2509,22 +2587,9 @@ static struct btf *__find_kfunc_desc_btf(struct bpf_verifier_env *env,\n \t\t\treturn ERR_PTR(-E2BIG);\n \t\t}\n \n-\t\tif (bpfptr_is_null(env-\u003efd_array)) {\n-\t\t\tverbose(env, \"kfunc offset \u003e 0 without fd_array is invalid\\n\");\n-\t\t\treturn ERR_PTR(-EPROTO);\n-\t\t}\n-\n-\t\tif (copy_from_bpfptr_offset(\u0026btf_fd, env-\u003efd_array,\n-\t\t\t\t\t offset * sizeof(btf_fd),\n-\t\t\t\t\t sizeof(btf_fd)))\n-\t\t\treturn ERR_PTR(-EFAULT);\n-\n-\t\tbtf = btf_get_by_fd(btf_fd);\n-\t\tif (IS_ERR(btf)) {\n-\t\t\tverbose(env, \"invalid module BTF fd specified\\n\");\n+\t\tbtf = fd_array_get_btf(env, offset);\n+\t\tif (IS_ERR(btf))\n \t\t\treturn btf;\n-\t\t}\n-\n \t\tif (!btf_is_module(btf)) {\n \t\t\tverbose(env, \"BTF fd for kfunc is not a module BTF\\n\");\n \t\t\tbtf_put(btf);\n@@ -17589,6 +17654,11 @@ static int __add_used_btf(struct bpf_verifier_env *env, struct btf *btf)\n \t\tif (env-\u003eused_btfs[i].btf == btf)\n \t\t\tgoto ret_put;\n \n+\tif (env-\u003esignature) {\n+\t\tverbose(env, \"signed program cannot bind any BTF\\n\");\n+\t\tret = -EACCES;\n+\t\tgoto ret_put;\n+\t}\n \tif (env-\u003eused_btf_cnt \u003e= MAX_USED_BTFS) {\n \t\tverbose(env, \"The total number of btfs per program has reached the limit of %u\\n\",\n \t\t\tMAX_USED_BTFS);\n@@ -17871,6 +17941,12 @@ static int __add_used_map(struct bpf_verifier_env *env, struct bpf_map *map)\n \t\tif (env-\u003eused_maps[i] == map)\n \t\t\treturn i;\n \n+\tif (env-\u003esignature \u0026\u0026\n+\t env-\u003eprog-\u003eaux-\u003esig.verdict == BPF_SIG_VERIFIED) {\n+\t\tverbose(env, \"signed program cannot bind map '%s' not covered by the signature\\n\",\n+\t\t\tmap-\u003ename);\n+\t\treturn -EACCES;\n+\t}\n \tif (env-\u003eused_map_cnt \u003e= MAX_USED_MAPS) {\n \t\tverbose(env, \"The total number of maps per program has reached the limit of %u\\n\",\n \t\t\tMAX_USED_MAPS);\n@@ -17923,6 +17999,48 @@ static int add_used_map(struct bpf_verifier_env *env, int fd)\n \treturn __add_used_map(env, map);\n }\n \n+static int fd_array_get_map_idx_continuous(struct bpf_verifier_env *env, u32 idx)\n+{\n+\tstruct bpf_map *map;\n+\n+\tif (idx \u003e= env-\u003efd_array_cnt) {\n+\t\tverbose(env, \"fd_idx %u out of bounds, fd_array_cnt %u\\n\",\n+\t\t\tidx, env-\u003efd_array_cnt);\n+\t\treturn -EINVAL;\n+\t}\n+\tmap = fd_slot_map(env-\u003efd_array[idx]);\n+\tif (!map) {\n+\t\tverbose(env, \"fd_idx %u is not a map\\n\", idx);\n+\t\treturn -EINVAL;\n+\t}\n+\treturn __add_used_map(env, map);\n+}\n+\n+static int fd_array_get_map_idx_sparse(struct bpf_verifier_env *env, u32 idx)\n+{\n+\tint fd;\n+\n+\tif (copy_from_bpfptr_offset(\u0026fd, env-\u003efd_array_raw,\n+\t\t\t\t (size_t)idx * sizeof(fd), sizeof(fd)))\n+\t\treturn -EFAULT;\n+\treturn add_used_map(env, fd);\n+}\n+\n+static int fd_array_get_map_idx(struct bpf_verifier_env *env, u32 idx)\n+{\n+\tif (env-\u003efd_array)\n+\t\treturn fd_array_get_map_idx_continuous(env, idx);\n+\tif (env-\u003esignature) {\n+\t\tverbose(env, \"signed program must bind maps via a continuous fd_array (fd_array_cnt)\\n\");\n+\t\treturn -EACCES;\n+\t}\n+\tif (!bpfptr_is_null(env-\u003efd_array_raw))\n+\t\treturn fd_array_get_map_idx_sparse(env, idx);\n+\n+\tverbose(env, \"fd_idx without fd_array is invalid\\n\");\n+\treturn -EPROTO;\n+}\n+\n static int check_alu_fields(struct bpf_verifier_env *env, struct bpf_insn *insn)\n {\n \tu8 class = BPF_CLASS(insn-\u003ecode);\n@@ -18140,7 +18258,6 @@ static int check_and_resolve_insns(struct bpf_verifier_env *env)\n \t\t\tstruct bpf_map *map;\n \t\t\tint map_idx;\n \t\t\tu64 addr;\n-\t\t\tu32 fd;\n \n \t\t\tif (i == insn_cnt - 1 || insn[1].code != 0 ||\n \t\t\t insn[1].dst_reg != 0 || insn[1].src_reg != 0 ||\n@@ -18192,21 +18309,17 @@ static int check_and_resolve_insns(struct bpf_verifier_env *env)\n \t\t\tswitch (insn[0].src_reg) {\n \t\t\tcase BPF_PSEUDO_MAP_IDX_VALUE:\n \t\t\tcase BPF_PSEUDO_MAP_IDX:\n-\t\t\t\tif (bpfptr_is_null(env-\u003efd_array)) {\n-\t\t\t\t\tverbose(env, \"fd_idx without fd_array is invalid\\n\");\n-\t\t\t\t\treturn -EPROTO;\n-\t\t\t\t}\n-\t\t\t\tif (copy_from_bpfptr_offset(\u0026fd, env-\u003efd_array,\n-\t\t\t\t\t\t\t insn[0].imm * sizeof(fd),\n-\t\t\t\t\t\t\t sizeof(fd)))\n-\t\t\t\t\treturn -EFAULT;\n+\t\t\t\tmap_idx = fd_array_get_map_idx(env, insn[0].imm);\n \t\t\t\tbreak;\n \t\t\tdefault:\n-\t\t\t\tfd = insn[0].imm;\n+\t\t\t\tif (env-\u003esignature) {\n+\t\t\t\t\tverbose(env, \"signed program cannot reference a map by fd, only via fd_array index\\n\");\n+\t\t\t\t\treturn -EINVAL;\n+\t\t\t\t}\n+\t\t\t\tmap_idx = add_used_map(env, insn[0].imm);\n \t\t\t\tbreak;\n \t\t\t}\n \n-\t\t\tmap_idx = add_used_map(env, fd);\n \t\t\tif (map_idx \u003c 0)\n \t\t\t\treturn map_idx;\n \t\t\tmap = env-\u003eused_maps[map_idx];\n@@ -19481,7 +19594,7 @@ struct btf *bpf_get_btf_vmlinux(void)\n * this case expect that every file descriptor in the array is either a map or\n * a BTF. Everything else is considered to be trash.\n */\n-static int add_fd_from_fd_array(struct bpf_verifier_env *env, int fd)\n+static int add_fd_from_fd_array(struct bpf_verifier_env *env, u32 idx, int fd)\n {\n \tstruct bpf_map *map;\n \tstruct btf *btf;\n@@ -19493,51 +19606,83 @@ static int add_fd_from_fd_array(struct bpf_verifier_env *env, int fd)\n \t\terr = __add_used_map(env, map);\n \t\tif (err \u003c 0)\n \t\t\treturn err;\n+\t\tfd_slot_set_map(\u0026env-\u003efd_array[idx], map);\n \t\treturn 0;\n \t}\n \n \tbtf = __btf_get_by_fd(f);\n \tif (!IS_ERR(btf)) {\n \t\tbtf_get(btf);\n-\t\treturn __add_used_btf(env, btf);\n+\t\terr = __add_used_btf(env, btf);\n+\t\tif (err \u003c 0)\n+\t\t\treturn err;\n+\t\tfd_slot_set_btf(\u0026env-\u003efd_array[idx], btf);\n+\t\treturn 0;\n \t}\n \n \tverbose(env, \"fd %d is not pointing to valid bpf_map or btf\\n\", fd);\n \treturn PTR_ERR(map);\n }\n \n-static int process_fd_array(struct bpf_verifier_env *env, union bpf_attr *attr, bpfptr_t uattr)\n+/*\n+ * A continuous fd_array is resolved into an in-memory cache with one slot\n+ * per entry. The bound here is deliberately generous and not derived from\n+ * the per-program object limits: Duplicate entries /are/ permitted, and\n+ * the number of distinct maps and BTFs a program can bind is enforced when\n+ * each entry is resolved by __add_used_map() and __add_used_btf().\n+ */\n+#define MAX_FD_ARRAY_CNT 4096\n+\n+static int process_fd_array_continuous(struct bpf_verifier_env *env,\n+\t\t\t\t bpfptr_t fd_array, u32 cnt)\n {\n-\tsize_t size = sizeof(int);\n-\tint ret;\n-\tint fd;\n+\tint fd, ret;\n \tu32 i;\n \n-\tenv-\u003efd_array = make_bpfptr(attr-\u003efd_array, uattr.is_kernel);\n-\n-\t/*\n-\t * The only difference between old (no fd_array_cnt is given) and new\n-\t * APIs is that in the latter case the fd_array is expected to be\n-\t * continuous and is scanned for map fds right away\n-\t */\n-\tif (!attr-\u003efd_array_cnt)\n-\t\treturn 0;\n-\n-\t/* Check for integer overflow */\n-\tif (attr-\u003efd_array_cnt \u003e= (U32_MAX / size)) {\n-\t\tverbose(env, \"fd_array_cnt is too big (%u)\\n\", attr-\u003efd_array_cnt);\n-\t\treturn -EINVAL;\n+\tif (cnt \u003e MAX_FD_ARRAY_CNT) {\n+\t\tverbose(env, \"fd_array has too many entries (%u, max %u)\\n\",\n+\t\t\tcnt, MAX_FD_ARRAY_CNT);\n+\t\treturn -E2BIG;\n \t}\n \n-\tfor (i = 0; i \u003c attr-\u003efd_array_cnt; i++) {\n-\t\tif (copy_from_bpfptr_offset(\u0026fd, env-\u003efd_array, i * size, size))\n+\tenv-\u003efd_array = kvcalloc(cnt, sizeof(*env-\u003efd_array),\n+\t\t\t\t GFP_KERNEL_ACCOUNT);\n+\tif (!env-\u003efd_array)\n+\t\treturn -ENOMEM;\n+\tenv-\u003efd_array_cnt = cnt;\n+\tfor (i = 0; i \u003c cnt; i++) {\n+\t\tif (copy_from_bpfptr_offset(\u0026fd, fd_array,\n+\t\t\t\t\t (size_t)i * sizeof(fd), sizeof(fd)))\n \t\t\treturn -EFAULT;\n-\n-\t\tret = add_fd_from_fd_array(env, fd);\n+\t\tret = add_fd_from_fd_array(env, i, fd);\n \t\tif (ret)\n \t\t\treturn ret;\n \t}\n+\treturn 0;\n+}\n \n+static int process_fd_array(struct bpf_verifier_env *env,\n+\t\t\t union bpf_attr *attr, bpfptr_t uattr)\n+{\n+\tbpfptr_t fd_array = make_bpfptr(attr-\u003efd_array, uattr.is_kernel);\n+\n+\tif (bpfptr_is_null(fd_array)) {\n+\t\tif (attr-\u003efd_array_cnt) {\n+\t\t\tverbose(env, \"fd_array_cnt %u without fd_array is invalid\\n\",\n+\t\t\t\tattr-\u003efd_array_cnt);\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\t\treturn 0;\n+\t}\n+\t/*\n+\t * New API: the caller passes fd_array_cnt and a continuous array that\n+\t * is resolved and bound up front. Legacy API (no fd_array_cnt): keep\n+\t * the caller's array and resolve entries on the spot at each reference.\n+\t */\n+\tif (attr-\u003efd_array_cnt)\n+\t\treturn process_fd_array_continuous(env, fd_array,\n+\t\t\t\t\t\t attr-\u003efd_array_cnt);\n+\tenv-\u003efd_array_raw = fd_array;\n \treturn 0;\n }\n \n@@ -19752,6 +19897,139 @@ int bpf_fixup_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,\n \treturn 0;\n }\n \n+static enum bpf_sig_keyring bpf_classify_keyring(s32 keyring_id)\n+{\n+\tswitch (keyring_id) {\n+\tcase 0:\n+\t\treturn BPF_SIG_KEYRING_BUILTIN;\n+\tcase (s32)(unsigned long)VERIFY_USE_SECONDARY_KEYRING:\n+\t\treturn BPF_SIG_KEYRING_SECONDARY;\n+\tcase (s32)(unsigned long)VERIFY_USE_PLATFORM_KEYRING:\n+\t\treturn BPF_SIG_KEYRING_PLATFORM;\n+\tdefault:\n+\t\treturn BPF_SIG_KEYRING_USER;\n+\t}\n+}\n+\n+/*\n+ * Verify the PKCS#7 signature of a loaded program. Called from bpf_check()\n+ * once the program's metadata maps have been resolved into used_maps, so\n+ * the exact maps folded into the signature are the ones the program binds.\n+ *\n+ * The signature covers the instructions followed by the frozen contents of\n+ * each map, in @maps order: insns || map_0 || map_1 || [...]. On success the\n+ * verdict and keyring info are recorded on prog-\u003eaux.\n+ */\n+static int bpf_prog_verify_signature(struct bpf_verifier_env *env,\n+\t\t\t\t union bpf_attr *attr, bool is_kernel)\n+{\n+\tbpfptr_t usig = make_bpfptr(attr-\u003esignature, is_kernel);\n+\tstruct bpf_dynptr_kern sig_ptr, data_ptr;\n+\tstruct bpf_prog *prog = env-\u003eprog;\n+\tstruct bpf_map **maps = env-\u003eused_maps;\n+\tstruct bpf_key *key = NULL;\n+\tvoid *sig, *data = NULL;\n+\tu32 map_cnt = env-\u003eused_map_cnt;\n+\tu32 i, off, insns_sz;\n+\tu64 data_sz;\n+\tint err = 0;\n+\n+\t/*\n+\t * Don't attempt to use kmalloc_large or vmalloc for signatures.\n+\t * Practical signature for BPF program should be below this limit.\n+\t */\n+\tif (attr-\u003esignature_size \u003e KMALLOC_MAX_CACHE_SIZE)\n+\t\treturn -EINVAL;\n+\tif (system_keyring_id_check(attr-\u003ekeyring_id) == 0)\n+\t\tkey = bpf_lookup_system_key(attr-\u003ekeyring_id);\n+\telse\n+\t\tkey = bpf_lookup_user_key(attr-\u003ekeyring_id, 0);\n+\tif (!key) {\n+\t\tverbose(env, \"cannot resolve signing keyring with keyring_id %d\\n\",\n+\t\t\tattr-\u003ekeyring_id);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tsig = kvmemdup_bpfptr(usig, attr-\u003esignature_size);\n+\tif (IS_ERR(sig)) {\n+\t\tbpf_key_put(key);\n+\t\treturn PTR_ERR(sig);\n+\t}\n+\n+\tinsns_sz = prog-\u003elen * sizeof(struct bpf_insn);\n+\tdata_sz = insns_sz;\n+\tfor (i = 0; i \u003c map_cnt; i++) {\n+\t\tstruct bpf_map *map = maps[i];\n+\n+\t\tif (map-\u003emap_type != BPF_MAP_TYPE_ARRAY ||\n+\t\t !map-\u003eops-\u003emap_direct_value_addr) {\n+\t\t\tverbose(env, \"signed program metadata map '%s' must be an array\\n\",\n+\t\t\t\tmap-\u003ename);\n+\t\t\terr = -EINVAL;\n+\t\t\tgoto out;\n+\t\t}\n+\t\tif (!READ_ONCE(map-\u003efrozen)) {\n+\t\t\tverbose(env, \"signed program metadata map '%s' must be frozen\\n\",\n+\t\t\t\tmap-\u003ename);\n+\t\t\terr = -EPERM;\n+\t\t\tgoto out;\n+\t\t}\n+\t\tif (!map-\u003eexcl_prog_sha) {\n+\t\t\tverbose(env, \"signed program metadata map '%s' must be exclusive\\n\",\n+\t\t\t\tmap-\u003ename);\n+\t\t\terr = -EPERM;\n+\t\t\tgoto out;\n+\t\t}\n+\t\tdata_sz += map-\u003evalue_size;\n+\t}\n+\tif (bpf_dynptr_check_size(data_sz)) {\n+\t\tverbose(env, \"signed payload too large: %llu bytes\\n\", data_sz);\n+\t\terr = -E2BIG;\n+\t\tgoto out;\n+\t}\n+\tdata = kvmalloc(data_sz, GFP_KERNEL_ACCOUNT | __GFP_ZERO);\n+\tif (!data) {\n+\t\terr = -ENOMEM;\n+\t\tgoto out;\n+\t}\n+\tmemcpy(data, prog-\u003einsnsi, insns_sz);\n+\toff = insns_sz;\n+\tfor (i = 0; i \u003c map_cnt; i++) {\n+\t\tstruct bpf_map *map = maps[i];\n+\t\tu64 addr;\n+\n+\t\terr = map-\u003eops-\u003emap_direct_value_addr(map, \u0026addr, 0);\n+\t\tif (err) {\n+\t\t\tverbose(env, \"failed to read signed metadata map '%s': %d\\n\",\n+\t\t\t\tmap-\u003ename, err);\n+\t\t\tgoto out;\n+\t\t}\n+\t\tmemcpy(data + off, (void *)(unsigned long)addr,\n+\t\t map-\u003evalue_size);\n+\t\toff += map-\u003evalue_size;\n+\t}\n+\n+\tbpf_dynptr_init(\u0026data_ptr, data, BPF_DYNPTR_TYPE_LOCAL, 0, data_sz);\n+\tbpf_dynptr_init(\u0026sig_ptr, sig, BPF_DYNPTR_TYPE_LOCAL, 0,\n+\t\t\tattr-\u003esignature_size);\n+\n+\terr = bpf_verify_pkcs7_signature((struct bpf_dynptr *)\u0026data_ptr,\n+\t\t\t\t\t (struct bpf_dynptr *)\u0026sig_ptr, key);\n+\tif (err) {\n+\t\tverbose(env, \"signature verification failed: %d\\n\", err);\n+\t} else {\n+\t\tverbose(env, \"signature verification passed\\n\");\n+\t\tprog-\u003eaux-\u003esig.keyring_serial = bpf_key_serial(key);\n+\t\tprog-\u003eaux-\u003esig.keyring_type = bpf_classify_keyring(attr-\u003ekeyring_id);\n+\t\tprog-\u003eaux-\u003esig.verdict = BPF_SIG_VERIFIED;\n+\t}\n+out:\n+\tkvfree(data);\n+\tbpf_key_put(key);\n+\tkvfree(sig);\n+\treturn err;\n+}\n+\n int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,\n \t struct bpf_log_attr *attr_log)\n {\n@@ -19774,18 +20052,6 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,\n \t\treturn -ENOMEM;\n \n \tenv-\u003ebt.env = env;\n-\n-\tlen = (*prog)-\u003elen;\n-\tenv-\u003einsn_aux_data =\n-\t\tvzalloc(array_size(sizeof(struct bpf_insn_aux_data), len));\n-\tret = -ENOMEM;\n-\tif (!env-\u003einsn_aux_data)\n-\t\tgoto err_free_env;\n-\tfor (i = 0; i \u003c len; i++)\n-\t\tenv-\u003einsn_aux_data[i].orig_idx = i;\n-\tenv-\u003esucc = bpf_iarray_realloc(NULL, 2);\n-\tif (!env-\u003esucc)\n-\t\tgoto err_free_env;\n \tenv-\u003eprog = *prog;\n \tenv-\u003eops = bpf_verifier_ops[env-\u003eprog-\u003etype];\n \n@@ -19794,22 +20060,51 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,\n \tenv-\u003ebypass_spec_v1 = bpf_bypass_spec_v1(env-\u003eprog-\u003eaux-\u003etoken);\n \tenv-\u003ebypass_spec_v4 = bpf_bypass_spec_v4(env-\u003eprog-\u003eaux-\u003etoken);\n \tenv-\u003ebpf_capable = is_priv = bpf_token_capable(env-\u003eprog-\u003eaux-\u003etoken, CAP_BPF);\n-\n-\tbpf_get_btf_vmlinux();\n-\n-\t/* grab the mutex to protect few globals used by verifier */\n-\tif (!is_priv)\n-\t\tmutex_lock(\u0026bpf_verifier_lock);\n+\tenv-\u003esignature = attr-\u003esignature;\n \n \t/* user could have requested verbose verifier output\n \t * and supplied buffer to store the verification trace\n \t */\n \tret = bpf_vlog_init(\u0026env-\u003elog, attr_log-\u003elevel, attr_log-\u003eubuf, attr_log-\u003esize);\n \tif (ret)\n-\t\tgoto err_unlock;\n+\t\tgoto err_free_env;\n+\tif (env-\u003esignature) {\n+\t\tret = bpf_prog_calc_tag(env-\u003eprog);\n+\t\tif (ret \u003c 0)\n+\t\t\tgoto err_prep;\n+\t}\n \n \tret = process_fd_array(env, attr, uattr);\n \tif (ret)\n+\t\tgoto err_prep;\n+\n+\tif (env-\u003esignature) {\n+\t\tret = bpf_prog_verify_signature(env, attr, uattr.is_kernel);\n+\t\tif (ret)\n+\t\t\tgoto err_prep;\n+\t}\n+\n+\tret = security_bpf_prog_load(env-\u003eprog, attr, env-\u003eprog-\u003eaux-\u003etoken,\n+\t\t\t\t uattr.is_kernel);\n+\tif (ret)\n+\t\tgoto err_prep;\n+\n+\tbpf_get_btf_vmlinux();\n+\n+\t/* grab the mutex to protect few globals used by verifier */\n+\tif (!is_priv)\n+\t\tmutex_lock(\u0026bpf_verifier_lock);\n+\n+\tlen = env-\u003eprog-\u003elen;\n+\tenv-\u003einsn_aux_data =\n+\t\tvzalloc(array_size(sizeof(struct bpf_insn_aux_data), len));\n+\tret = -ENOMEM;\n+\tif (!env-\u003einsn_aux_data)\n+\t\tgoto skip_full_check;\n+\tfor (i = 0; i \u003c len; i++)\n+\t\tenv-\u003einsn_aux_data[i].orig_idx = i;\n+\tenv-\u003esucc = bpf_iarray_realloc(NULL, 2);\n+\tif (!env-\u003esucc)\n \t\tgoto skip_full_check;\n \n \tmark_verifier_state_clean(env);\n@@ -20033,17 +20328,26 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,\n \t*prog = env-\u003eprog;\n \n \tmodule_put(env-\u003eattach_btf_mod);\n-err_unlock:\n \tif (!is_priv)\n \t\tmutex_unlock(\u0026bpf_verifier_lock);\n-\tbpf_clear_insn_aux_data(env, 0, env-\u003eprog-\u003elen);\n+\tgoto err_free_env;\n+err_prep:\n+\terr = bpf_log_attr_finalize(attr_log, \u0026env-\u003elog);\n+\tif (err)\n+\t\tret = err;\n+\trelease_insn_arrays(env);\n+\trelease_maps(env);\n+\trelease_btfs(env);\n err_free_env:\n+\tif (env-\u003einsn_aux_data)\n+\t\tbpf_clear_insn_aux_data(env, 0, env-\u003eprog-\u003elen);\n+\tvfree(env-\u003einsn_aux_data);\n+\tkvfree(env-\u003efd_array);\n \tbpf_stack_liveness_free(env);\n \tkvfree(env-\u003ecfg.insn_postorder);\n \tkvfree(env-\u003escc_info);\n \tkvfree(env-\u003esucc);\n \tkvfree(env-\u003egotox_tmp_buf);\n-\tvfree(env-\u003einsn_aux_data);\n \tkvfree(env);\n \treturn ret;\n }\ndiff --git a/tools/bpf/bpftool/gen.c b/tools/bpf/bpftool/gen.c\nindex 6ae7262ebe0c1d..a01d06d22d1a3b 100644\n--- a/tools/bpf/bpftool/gen.c\n+++ b/tools/bpf/bpftool/gen.c\n@@ -793,6 +793,8 @@ static int gen_trace(struct bpf_object *obj, const char *obj_name, const char *h\n \tif (sign_progs) {\n \t\tsopts.insns = opts.insns;\n \t\tsopts.insns_sz = opts.insns_sz;\n+\t\tsopts.data = opts.data;\n+\t\tsopts.data_sz = opts.data_sz;\n \t\tsopts.excl_prog_hash = prog_sha;\n \t\tsopts.excl_prog_hash_sz = sizeof(prog_sha);\n \t\tsopts.signature = sig_buf;\ndiff --git a/tools/bpf/bpftool/sign.c b/tools/bpf/bpftool/sign.c\nindex f9b742f4bb104b..88726a6db6d0e9 100644\n--- a/tools/bpf/bpftool/sign.c\n+++ b/tools/bpf/bpftool/sign.c\n@@ -135,9 +135,21 @@ int bpftool_prog_sign(struct bpf_load_and_run_opts *opts)\n \tCMS_ContentInfo *cms = NULL;\n \tlong actual_sig_len = 0;\n \tX509 *x509 = NULL;\n+\tvoid *data = NULL;\n+\tsize_t data_sz;\n \tint err = 0;\n \n-\tbd_in = BIO_new_mem_buf(opts-\u003einsns, opts-\u003einsns_sz);\n+\tdata_sz = (size_t)opts-\u003einsns_sz + opts-\u003edata_sz;\n+\tdata = malloc(data_sz);\n+\tif (!data) {\n+\t\terr = -ENOMEM;\n+\t\tgoto cleanup;\n+\t}\n+\tmemcpy(data, opts-\u003einsns, opts-\u003einsns_sz);\n+\tif (opts-\u003edata_sz)\n+\t\tmemcpy((char *)data + opts-\u003einsns_sz, opts-\u003edata, opts-\u003edata_sz);\n+\n+\tbd_in = BIO_new_mem_buf(data, data_sz);\n \tif (!bd_in) {\n \t\terr = -ENOMEM;\n \t\tgoto cleanup;\n@@ -175,10 +187,13 @@ int bpftool_prog_sign(struct bpf_load_and_run_opts *opts)\n \t\tgoto cleanup;\n \t}\n \n-\tEVP_Digest(opts-\u003einsns, opts-\u003einsns_sz, opts-\u003eexcl_prog_hash,\n-\t\t \u0026opts-\u003eexcl_prog_hash_sz, EVP_sha256(), NULL);\n+\tif (EVP_Digest(opts-\u003einsns, opts-\u003einsns_sz, opts-\u003eexcl_prog_hash,\n+\t\t \u0026opts-\u003eexcl_prog_hash_sz, EVP_sha256(), NULL) != 1) {\n+\t\terr = -EIO;\n+\t\tgoto cleanup;\n+\t}\n \n-\t\tbd_out = BIO_new(BIO_s_mem());\n+\tbd_out = BIO_new(BIO_s_mem());\n \tif (!bd_out) {\n \t\terr = -ENOMEM;\n \t\tgoto cleanup;\n@@ -212,6 +227,7 @@ int bpftool_prog_sign(struct bpf_load_and_run_opts *opts)\n \tX509_free(x509);\n \tEVP_PKEY_free(private_key);\n \tBIO_free(bd_in);\n+\tfree(data);\n \tDISPLAY_OSSL_ERR(err \u003c 0);\n \treturn err;\n }\ndiff --git a/tools/lib/bpf/bpf_gen_internal.h b/tools/lib/bpf/bpf_gen_internal.h\nindex 49af4260b8e6b7..04256918775217 100644\n--- a/tools/lib/bpf/bpf_gen_internal.h\n+++ b/tools/lib/bpf/bpf_gen_internal.h\n@@ -51,7 +51,6 @@ struct bpf_gen {\n \t__u32 nr_ksyms;\n \tint fd_array;\n \tint nr_fd_array;\n-\tint hash_insn_offset[SHA256_DWORD_SIZE];\n };\n \n void bpf_gen__init(struct bpf_gen *gen, int log_level, int nr_progs, int nr_maps);\ndiff --git a/tools/lib/bpf/gen_loader.c b/tools/lib/bpf/gen_loader.c\nindex c7f2d2ac7bb35f..6e3dd524276183 100644\n--- a/tools/lib/bpf/gen_loader.c\n+++ b/tools/lib/bpf/gen_loader.c\n@@ -111,7 +111,6 @@ static void emit2(struct bpf_gen *gen, struct bpf_insn insn1, struct bpf_insn in\n \n static int add_data(struct bpf_gen *gen, const void *data, __u32 size);\n static void emit_sys_close_blob(struct bpf_gen *gen, int blob_off);\n-static void emit_signature_match(struct bpf_gen *gen);\n \n void bpf_gen__init(struct bpf_gen *gen, int log_level, int nr_progs, int nr_maps)\n {\n@@ -154,8 +153,6 @@ void bpf_gen__init(struct bpf_gen *gen, int log_level, int nr_progs, int nr_maps\n \t/* R7 contains the error code from sys_bpf. Copy it into R0 and exit. */\n \temit(gen, BPF_MOV64_REG(BPF_REG_0, BPF_REG_7));\n \temit(gen, BPF_EXIT_INSN());\n-\tif (OPTS_GET(gen-\u003eopts, gen_hash, false))\n-\t\temit_signature_match(gen);\n }\n \n static int add_data(struct bpf_gen *gen, const void *data, __u32 size)\n@@ -377,8 +374,6 @@ static void emit_sys_close_blob(struct bpf_gen *gen, int blob_off)\n \t__emit_sys_close(gen);\n }\n \n-static void compute_sha_update_offsets(struct bpf_gen *gen);\n-\n int bpf_gen__finish(struct bpf_gen *gen, int nr_progs, int nr_maps)\n {\n \tint i;\n@@ -408,9 +403,6 @@ int bpf_gen__finish(struct bpf_gen *gen, int nr_progs, int nr_maps)\n \tif (!gen-\u003eerror) {\n \t\tstruct gen_loader_opts *opts = gen-\u003eopts;\n \n-\t\tif (OPTS_GET(opts, gen_hash, false))\n-\t\t\tcompute_sha_update_offsets(gen);\n-\n \t\topts-\u003einsns = gen-\u003einsn_start;\n \t\topts-\u003einsns_sz = gen-\u003einsn_cur - gen-\u003einsn_start;\n \t\topts-\u003edata = gen-\u003edata_start;\n@@ -460,22 +452,6 @@ void bpf_gen__free(struct bpf_gen *gen)\n \t_val;\t\t\t\t\t\t\t\\\n })\n \n-static void compute_sha_update_offsets(struct bpf_gen *gen)\n-{\n-\t__u64 sha[SHA256_DWORD_SIZE];\n-\t__u64 sha_dw;\n-\tint i;\n-\n-\tlibbpf_sha256(gen-\u003edata_start, gen-\u003edata_cur - gen-\u003edata_start, (__u8 *)sha);\n-\tfor (i = 0; i \u003c SHA256_DWORD_SIZE; i++) {\n-\t\tstruct bpf_insn *insn =\n-\t\t\t(struct bpf_insn *)(gen-\u003einsn_start + gen-\u003ehash_insn_offset[i]);\n-\t\tsha_dw = tgt_endian(sha[i]);\n-\t\tinsn[0].imm = (__u32)sha_dw;\n-\t\tinsn[1].imm = sha_dw \u003e\u003e 32;\n-\t}\n-}\n-\n void bpf_gen__load_btf(struct bpf_gen *gen, const void *btf_raw_data,\n \t\t __u32 btf_raw_size)\n {\n@@ -557,8 +533,9 @@ void bpf_gen__map_create(struct bpf_gen *gen,\n \t * Conditionally update max_entries from the host-supplied loader\n \t * ctx. This sizes the map at runtime, but for a signed loader\n \t * (gen_hash) it would let an untrusted host re-dimension the\n-\t * program's maps after emit_signature_match(), outside what the\n-\t * signature attests to. Keep the signer-provided max_entries\n+\t * program's maps, outside what the signature attests to: the\n+\t * metadata blob is covered by the program signature and verified\n+\t * by the kernel at load time. Keep the signer-provided max_entries\n \t * baked into the blob in that case.\n \t */\n \tif (map_idx \u003e= 0 \u0026\u0026 !OPTS_GET(gen-\u003eopts, gen_hash, false))\n@@ -596,45 +573,6 @@ void bpf_gen__map_create(struct bpf_gen *gen,\n \t\temit_sys_close_stack(gen, stack_off(inner_map_fd));\n }\n \n-static void emit_signature_match(struct bpf_gen *gen)\n-{\n-\t__s64 off;\n-\tint i;\n-\n-\t/*\n-\t * Reject if the metadata map is not exclusive. Without exclusivity\n-\t * the cached map-\u003esha[] verified above can be stale: another BPF\n-\t * program with map access could have mutated the contents between\n-\t * BPF_OBJ_GET_INFO_BY_FD and loader execution.\n-\t */\n-\temit2(gen, BPF_LD_IMM64_RAW_FULL(BPF_REG_1, BPF_PSEUDO_MAP_IDX,\n-\t\t\t\t\t 0, 0, 0, 0));\n-\temit(gen, BPF_LDX_MEM(BPF_W, BPF_REG_2, BPF_REG_1, SHA256_DIGEST_LENGTH));\n-\toff = -(gen-\u003einsn_cur - gen-\u003einsn_start - gen-\u003ecleanup_label) / 8 - 2;\n-\tif (is_simm16(off)) {\n-\t\temit(gen, BPF_MOV64_IMM(BPF_REG_7, -EINVAL));\n-\t\temit(gen, BPF_JMP_IMM(BPF_JNE, BPF_REG_2, 1, off));\n-\t} else {\n-\t\tgen-\u003eerror = -ERANGE;\n-\t}\n-\n-\tfor (i = 0; i \u003c SHA256_DWORD_SIZE; i++) {\n-\t\temit2(gen, BPF_LD_IMM64_RAW_FULL(BPF_REG_1, BPF_PSEUDO_MAP_IDX,\n-\t\t\t\t\t\t 0, 0, 0, 0));\n-\t\temit(gen, BPF_LDX_MEM(BPF_DW, BPF_REG_2, BPF_REG_1, i * sizeof(__u64)));\n-\t\tgen-\u003ehash_insn_offset[i] = gen-\u003einsn_cur - gen-\u003einsn_start;\n-\t\temit2(gen, BPF_LD_IMM64_RAW_FULL(BPF_REG_3, 0, 0, 0, 0, 0));\n-\n-\t\toff = -(gen-\u003einsn_cur - gen-\u003einsn_start - gen-\u003ecleanup_label) / 8 - 2;\n-\t\tif (is_simm16(off)) {\n-\t\t\temit(gen, BPF_MOV64_IMM(BPF_REG_7, -EINVAL));\n-\t\t\temit(gen, BPF_JMP_REG(BPF_JNE, BPF_REG_2, BPF_REG_3, off));\n-\t\t} else {\n-\t\t\tgen-\u003eerror = -ERANGE;\n-\t\t}\n-\t}\n-}\n-\n void bpf_gen__record_attach_target(struct bpf_gen *gen, const char *attach_name,\n \t\t\t\t enum bpf_attach_type type)\n {\n@@ -1211,10 +1149,10 @@ void bpf_gen__map_update_elem(struct bpf_gen *gen, int map_idx, void *pvalue,\n \t * }\n \t *\n \t * The runtime initial_value comes from the host-supplied loader\n-\t * ctx and would overwrite the blob value after emit_signature_match()\n-\t * has already validated map-\u003esha[]. For a signed loader (gen_hash)\n-\t * the attested blob value must be authoritative, so skip the override\n-\t * and leave the hashed value in place.\n+\t * ctx and would overwrite the blob value that the program signature\n+\t * covers and the kernel verifies at load time. For a signed loader\n+\t * (gen_hash) the attested blob value must be authoritative, so skip\n+\t * the override and leave the signed value in place.\n \t */\n \tif (!OPTS_GET(gen-\u003eopts, gen_hash, false)) {\n \t\temit(gen, BPF_LDX_MEM(BPF_DW, BPF_REG_3, BPF_REG_6,\ndiff --git a/tools/lib/bpf/libbpf_internal.h b/tools/lib/bpf/libbpf_internal.h\nindex 04cd303fb5a879..d5b7db703b3fe9 100644\n--- a/tools/lib/bpf/libbpf_internal.h\n+++ b/tools/lib/bpf/libbpf_internal.h\n@@ -768,7 +768,6 @@ int elf_resolve_pattern_offsets(const char *binary_path, const char *pattern,\n int probe_fd(int fd);\n \n #define SHA256_DIGEST_LENGTH 32\n-#define SHA256_DWORD_SIZE SHA256_DIGEST_LENGTH / sizeof(__u64)\n \n void libbpf_sha256(const void *data, size_t len, __u8 out[SHA256_DIGEST_LENGTH]);\n int probe_sys_bpf_ext(void);\ndiff --git a/tools/lib/bpf/skel_internal.h b/tools/lib/bpf/skel_internal.h\nindex 74503d358bc8a5..53fee53d36d512 100644\n--- a/tools/lib/bpf/skel_internal.h\n+++ b/tools/lib/bpf/skel_internal.h\n@@ -18,10 +18,6 @@\n #include \"bpf.h\"\n #endif\n \n-#ifndef SHA256_DIGEST_LENGTH\n-#define SHA256_DIGEST_LENGTH 32\n-#endif\n-\n #ifndef __NR_bpf\n # if defined(__mips__) \u0026\u0026 defined(_ABIO32)\n # define __NR_bpf 4355\n@@ -320,25 +316,6 @@ static inline int skel_link_create(int prog_fd, int target_fd,\n \treturn skel_sys_bpf(BPF_LINK_CREATE, \u0026attr, attr_sz);\n }\n \n-static inline int skel_obj_get_info_by_fd(int fd)\n-{\n-\tconst size_t attr_sz = offsetofend(union bpf_attr, info);\n-\t__u8 sha[SHA256_DIGEST_LENGTH];\n-\tstruct bpf_map_info info;\n-\t__u32 info_len = sizeof(info);\n-\tunion bpf_attr attr;\n-\n-\tmemset(\u0026info, 0, sizeof(info));\n-\tinfo.hash = (long) \u0026sha;\n-\tinfo.hash_size = SHA256_DIGEST_LENGTH;\n-\n-\tmemset(\u0026attr, 0, attr_sz);\n-\tattr.info.bpf_fd = fd;\n-\tattr.info.info = (long) \u0026info;\n-\tattr.info.info_len = info_len;\n-\treturn skel_sys_bpf(BPF_OBJ_GET_INFO_BY_FD, \u0026attr, attr_sz);\n-}\n-\n static inline int skel_map_freeze(int fd)\n {\n \tconst size_t attr_sz = offsetofend(union bpf_attr, map_fd);\n@@ -384,12 +361,6 @@ static inline int bpf_load_and_run(struct bpf_load_and_run_opts *opts)\n \t\tset_err;\n \t\tgoto out;\n \t}\n-\terr = skel_obj_get_info_by_fd(map_fd);\n-\tif (err \u003c 0) {\n-\t\topts-\u003eerrstr = \"failed to fetch obj info\";\n-\t\tset_err;\n-\t\tgoto out;\n-\t}\n #endif\n \n \tmemset(\u0026attr, 0, prog_load_attr_sz);\n@@ -400,6 +371,8 @@ static inline int bpf_load_and_run(struct bpf_load_and_run_opts *opts)\n #ifndef __KERNEL__\n \tattr.signature = (long) opts-\u003esignature;\n \tattr.signature_size = opts-\u003esignature_sz;\n+\tif (opts-\u003esignature)\n+\t\tattr.fd_array_cnt = 1;\n #else\n \tif (opts-\u003esignature || opts-\u003esignature_sz)\n \t\tpr_warn(\"signatures are not supported from bpf_preload\\n\");\ndiff --git a/tools/testing/selftests/bpf/prog_tests/signed_loader.c b/tools/testing/selftests/bpf/prog_tests/signed_loader.c\nindex 5fc417e31fc615..56d1f072de4c17 100644\n--- a/tools/testing/selftests/bpf/prog_tests/signed_loader.c\n+++ b/tools/testing/selftests/bpf/prog_tests/signed_loader.c\n@@ -11,6 +11,8 @@\n #include \u003clinux/keyctl.h\u003e\n #include \u003clinux/bpf.h\u003e\n \n+#include \u003cbpf/btf.h\u003e\n+\n #include \"bpf/libbpf_internal.h\" /* for libbpf_sha256() */\n #include \"bpf/skel_internal.h\"\t /* for loader ctx layout (bpf_loader_ctx etc) */\n \n@@ -19,8 +21,6 @@\n #include \"test_signed_loader_data.skel.h\"\n #include \"test_signed_loader_lsm.skel.h\"\n \n-#define SIG_MATCH_INSNS 33 /* excl (5) + 4 * sha-dword (7) */\n-\n enum {\n \tBPF_SIG_UNSIGNED = 0,\n \tBPF_SIG_VERIFIED,\n@@ -35,7 +35,8 @@ enum {\n };\n \n static int load_loader(const void *insns, __u32 insns_sz, int map_fd,\n-\t\t const void *sig, __u32 sig_sz, __s32 keyring_id)\n+\t\t const void *sig, __u32 sig_sz, __s32 keyring_id,\n+\t\t __u32 fd_array_cnt)\n {\n \tunion bpf_attr attr;\n \tint fd;\n@@ -52,6 +53,7 @@ static int load_loader(const void *insns, __u32 insns_sz, int map_fd,\n \t\tattr.signature_size = sig_sz;\n \t\tattr.keyring_id = keyring_id;\n \t}\n+\tattr.fd_array_cnt = fd_array_cnt;\n \tmemcpy(attr.prog_name, \"__loader.prog\", sizeof(\"__loader.prog\"));\n \tfd = syscall(__NR_bpf, BPF_PROG_LOAD, \u0026attr,\n \t\t offsetofend(union bpf_attr, keyring_id));\n@@ -62,14 +64,12 @@ static int run_gen_loader(const void *insns, __u32 insns_sz,\n \t\t\t const void *data, __u32 data_sz,\n \t\t\t const void *excl, __u32 excl_sz,\n \t\t\t const void *sig, __u32 sig_sz,\n-\t\t\t bool get_hash, void *ctx, __u32 ctx_sz, bool *loader_ran)\n+\t\t\t void *ctx, __u32 ctx_sz, bool *loader_ran)\n {\n \tLIBBPF_OPTS(bpf_map_create_opts, mopts,\n \t\t .excl_prog_hash = excl,\n \t\t .excl_prog_hash_size = excl_sz);\n-\t__u8 hbuf[SHA256_DIGEST_LENGTH];\n-\tstruct bpf_map_info info;\n-\t__u32 ilen = sizeof(info), key = 0;\n+\t__u32 key = 0;\n \tunion bpf_attr attr;\n \tint map_fd, prog_fd, ret;\n \n@@ -87,15 +87,6 @@ static int run_gen_loader(const void *insns, __u32 insns_sz,\n \t\tret = -errno;\n \t\tgoto out_map;\n \t}\n-\tif (get_hash) {\n-\t\tmemset(\u0026info, 0, sizeof(info));\n-\t\tinfo.hash = ptr_to_u64(hbuf);\n-\t\tinfo.hash_size = sizeof(hbuf);\n-\t\tif (bpf_map_get_info_by_fd(map_fd, \u0026info, \u0026ilen)) {\n-\t\t\tret = -errno;\n-\t\t\tgoto out_map;\n-\t\t}\n-\t}\n \n \tmemset(\u0026attr, 0, sizeof(attr));\n \tattr.prog_type = BPF_PROG_TYPE_SYSCALL;\n@@ -108,6 +99,7 @@ static int run_gen_loader(const void *insns, __u32 insns_sz,\n \t\tattr.signature = ptr_to_u64(sig);\n \t\tattr.signature_size = sig_sz;\n \t\tattr.keyring_id = KEY_SPEC_SESSION_KEYRING;\n+\t\tattr.fd_array_cnt = 1;\n \t}\n \tmemcpy(attr.prog_name, \"__loader.prog\", sizeof(\"__loader.prog\"));\n \tprog_fd = syscall(__NR_bpf, BPF_PROG_LOAD, \u0026attr,\n@@ -236,79 +228,6 @@ static int sign_buf(const char *dir, const void *buf, __u32 len,\n \treturn ret;\n }\n \n-static void check_sig_match_shape(const struct bpf_insn *in, int n)\n-{\n-\tint a = -1, cleanup = -1, i, base, t, br[5], nb = 0;\n-\n-\t/* BPF_PSEUDO_MAP_IDX (the struct bpf_map * form) is used only here. */\n-\tfor (i = 0; i + 1 \u003c n; i++) {\n-\t\tif (in[i].code == (BPF_LD | BPF_IMM | BPF_DW) \u0026\u0026\n-\t\t in[i].src_reg == BPF_PSEUDO_MAP_IDX) {\n-\t\t\ta = i;\n-\t\t\tbreak;\n-\t\t}\n-\t}\n-\tif (!ASSERT_GE(a, 0, \"emit_signature_match present\"))\n-\t\treturn;\n-\tif (!ASSERT_LE(a + SIG_MATCH_INSNS, n, \"block fits in program\"))\n-\t\treturn;\n-\n-\t/* excl check: r2 = *(u32 *)(map + 32); if r2 != 1 goto cleanup */\n-\tASSERT_EQ(in[a + 2].code, (BPF_LDX | BPF_MEM | BPF_W), \"excl load width\");\n-\tASSERT_EQ(in[a + 2].off, SHA256_DIGEST_LENGTH, \"excl field offset\");\n-\tASSERT_EQ(in[a + 4].code, (BPF_JMP | BPF_JNE | BPF_K), \"excl branch op\");\n-\tASSERT_EQ(in[a + 4].imm, 1, \"excl compared to 1\");\n-\tbr[nb++] = a + 4;\n-\n-\t/* 4 sha-dword checks: r2 = *(u64 *)(map + i*8); if r2 != r3 goto cleanup */\n-\tfor (i = 0; i \u003c 4; i++) {\n-\t\tbase = a + 5 + i * 7;\n-\t\tASSERT_EQ(in[base + 2].code, (BPF_LDX | BPF_MEM | BPF_DW), \"sha load width\");\n-\t\tASSERT_EQ(in[base + 2].off, i * 8, \"sha dword offset\");\n-\t\tASSERT_EQ(in[base + 3].code, (BPF_LD | BPF_IMM | BPF_DW), \"sha imm64 (H_meta)\");\n-\t\tASSERT_EQ(in[base + 6].code, (BPF_JMP | BPF_JNE | BPF_X), \"sha branch op\");\n-\t\tbr[nb++] = base + 6;\n-\t}\n-\n-\t/*\n-\t * Locate the real cleanup label so we can pin the exact jump target,\n-\t * not just \"some backward label\". bpf_gen__init() emits the cleanup\n-\t * block as a prog-fd close loop whose first instruction is the label\n-\t * every error branch jumps to.\n-\t */\n-\tfor (i = 0; i + 2 \u003c a; i++) {\n-\t\tif (in[i].code == (BPF_LDX | BPF_MEM | BPF_W) \u0026\u0026\n-\t\t in[i].dst_reg == BPF_REG_1 \u0026\u0026 in[i].src_reg == BPF_REG_10 \u0026\u0026\n-\t\t in[i + 1].code == (BPF_JMP | BPF_JSLE | BPF_K) \u0026\u0026\n-\t\t in[i + 1].dst_reg == BPF_REG_1 \u0026\u0026 in[i + 1].imm == 0 \u0026\u0026\n-\t\t in[i + 1].off == 1 \u0026\u0026\n-\t\t in[i + 2].code == (BPF_JMP | BPF_CALL) \u0026\u0026\n-\t\t in[i + 2].imm == BPF_FUNC_sys_close) {\n-\t\t\tcleanup = i;\n-\t\t\tbreak;\n-\t\t}\n-\t}\n-\tif (!ASSERT_GE(cleanup, 0, \"cleanup label located\"))\n-\t\treturn;\n-\tfor (i = 0; i \u003c nb; i++) {\n-\t\tt = br[i] + 1 + in[br[i]].off;\n-\t\tASSERT_EQ(t, cleanup, \"sig-match lands on cleanup\");\n-\t}\n-\t/*\n-\t * Same invariant for every other cleanup-bound jump in the program:\n-\t * emit_check_err() is the only source of \"if (r7 \u003c 0) goto cleanup\",\n-\t * so each of those must also resolve exactly to cleanup.\n-\t */\n-\tfor (i = 0, t = 0; i \u003c n; i++) {\n-\t\tif (in[i].code != (BPF_JMP | BPF_JSLT | BPF_K) ||\n-\t\t in[i].dst_reg != BPF_REG_7 || in[i].imm != 0 || in[i].off \u003e= 0)\n-\t\t\tcontinue;\n-\t\tASSERT_EQ(i + 1 + in[i].off, cleanup, \"err-check lands on cleanup\");\n-\t\tt++;\n-\t}\n-\tASSERT_GT(t, 0, \"found emit_check_err jumps\");\n-}\n-\n struct gen_loader_fixture {\n \tstruct test_signed_loader *skel;\n \tstruct gen_loader_opts gopts;\n@@ -372,16 +291,6 @@ static void gen_loader_fixture_fini(struct gen_loader_fixture *f)\n \ttest_signed_loader__destroy(f-\u003eskel);\n }\n \n-static void metadata_check_shape(void)\n-{\n-\tstruct gen_loader_fixture f;\n-\n-\tif (gen_loader_fixture_init(\u0026f) == 0)\n-\t\tcheck_sig_match_shape((const struct bpf_insn *)f.gopts.insns,\n-\t\t\t\t f.gopts.insns_sz / sizeof(struct bpf_insn));\n-\tgen_loader_fixture_fini(\u0026f);\n-}\n-\n static void metadata_match(void)\n {\n \tstruct gen_loader_fixture f;\n@@ -391,94 +300,263 @@ static void metadata_match(void)\n \tif (gen_loader_fixture_init(\u0026f) == 0) {\n \t\tr = run_gen_loader(f.gopts.insns, f.gopts.insns_sz, f.blob,\n \t\t\t\t f.data_sz, f.excl, sizeof(f.excl), NULL, 0,\n-\t\t\t\t true, f.ctx, f.ctx_sz, \u0026ran);\n+\t\t\t\t f.ctx, f.ctx_sz, \u0026ran);\n \t\tASSERT_TRUE(ran, \"loader ran\");\n \t\tASSERT_EQ(r, 0, \"honest loader retval\");\n \t}\n \tgen_loader_fixture_fini(\u0026f);\n }\n \n-static void metadata_sha_mismatch(void)\n+static void signature_enforced(void)\n {\n+\tstatic const __u8 junk[64] = { 0x30, 0x42, 0x13, 0x37, };\n \tstruct gen_loader_fixture f;\n-\tbool ran;\n-\tint r;\n+\tint fd;\n \n \tif (gen_loader_fixture_init(\u0026f) == 0) {\n \t\t/*\n-\t\t * blob[0] lives in the loader's fd_array scratch (first add_data in\n-\t\t * bpf_gen__init); a 0-map program never reads it, so flipping it\n-\t\t * changes only map-\u003esha. The metadata check is the only thing that\n-\t\t * can notice -\u003e isolates emit_signature_match.\n+\t\t * A present-but-invalid signature (the cert bytes are not a\n+\t\t * PKCS#7 signature) must be rejected at load: the signature\n+\t\t * path is honored, not ignored. (The valid path is covered by\n+\t\t * the signed lskels.) Pin -EBADMSG, the PKCS#7 parse failure:\n+\t\t * a looser fd \u003c 0 check could also be satisfied by the sparse\n+\t\t * fd_array rejection (-EACCES) that the loader's map reference\n+\t\t * would trip even if the signature were silently ignored.\n \t\t */\n-\t\tf.blob[0] ^= 0xff;\n-\t\tr = run_gen_loader(f.gopts.insns, f.gopts.insns_sz, f.blob,\n-\t\t\t\t f.data_sz, f.excl, sizeof(f.excl), NULL, 0,\n-\t\t\t\t true, f.ctx, f.ctx_sz, \u0026ran);\n-\t\tASSERT_TRUE(ran, \"loader ran\");\n-\t\tASSERT_EQ(r, -EINVAL, \"tampered blob rejected by emit_signature_match\");\n+\t\tfd = load_loader(f.gopts.insns, f.gopts.insns_sz, -1, junk,\n+\t\t\t\t sizeof(junk), KEY_SPEC_SESSION_KEYRING, 0);\n+\t\tASSERT_EQ(fd, -EBADMSG, \"invalid signature rejected at load\");\n \t}\n \tgen_loader_fixture_fini(\u0026f);\n }\n \n-static void metadata_not_exclusive(void)\n+static void signed_nonexcl_fd_array_rejected(void)\n {\n+\tstatic const __u8 junk[64] = { 0x30, 0x42, 0x13, 0x37, };\n \tstruct gen_loader_fixture f;\n-\tbool ran;\n-\tint r;\n+\tint map_fd, fd;\n \n \tif (gen_loader_fixture_init(\u0026f) == 0) {\n \t\t/*\n-\t\t * Correct blob but a non-exclusive metadata map: the verifier does\n-\t\t * not reject (excl_prog_sha unset), so the runtime map-\u003eexcl == 1\n-\t\t * check in the loader must.\n+\t\t * A signed program may only bind exclusive maps through fd_array\n+\t\t * (their contents are folded into the signature). Binding a\n+\t\t * non-exclusive map is rejected, before the signature is even\n+\t\t * examined.\n \t\t */\n-\t\tr = run_gen_loader(f.gopts.insns, f.gopts.insns_sz, f.blob,\n-\t\t\t\t f.data_sz, NULL, 0, NULL, 0, true, f.ctx,\n-\t\t\t\t f.ctx_sz, \u0026ran);\n-\t\tASSERT_TRUE(ran, \"loader ran\");\n-\t\tASSERT_EQ(r, -EINVAL, \"non-exclusive metadata map rejected\");\n+\t\tmap_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, \"nonexcl\", 4,\n+\t\t\t\t\tf.data_sz, 1, NULL);\n+\t\tif (ASSERT_OK_FD(map_fd, \"nonexcl_map\")) {\n+\t\t\tif (ASSERT_OK(bpf_map_freeze(map_fd), \"freeze\")) {\n+\t\t\t\tfd = load_loader(f.gopts.insns, f.gopts.insns_sz,\n+\t\t\t\t\t\t map_fd, junk, sizeof(junk),\n+\t\t\t\t\t\t KEY_SPEC_SESSION_KEYRING, 1);\n+\t\t\t\tASSERT_EQ(fd, -EPERM,\n+\t\t\t\t\t \"non-exclusive map in signed fd_array rejected\");\n+\t\t\t\tif (fd \u003e= 0)\n+\t\t\t\t\tclose(fd);\n+\t\t\t}\n+\t\t\tclose(map_fd);\n+\t\t}\n \t}\n \tgen_loader_fixture_fini(\u0026f);\n }\n \n-static void metadata_hash_not_computed(void)\n+static void signed_unfrozen_fd_array_rejected(void)\n {\n+\tstatic const __u8 junk[64] = { 0x30, 0x42, 0x13, 0x37, };\n+\tLIBBPF_OPTS(bpf_map_create_opts, mopts);\n \tstruct gen_loader_fixture f;\n-\tbool ran;\n-\tint r;\n+\t__u32 key = 0;\n+\tint map_fd, fd;\n \n \tif (gen_loader_fixture_init(\u0026f) == 0) {\n \t\t/*\n-\t\t * Correct, exclusive, frozen map, but its hash was never computed\n-\t\t * (no OBJ_GET_INFO_BY_FD), so map-\u003esha stays zero. The loader must\n-\t\t * fail closed rather than treat an unset hash as a match.\n+\t\t * The metadata map must be frozen before a signed load so the\n+\t\t * folded bytes cannot change afterwards. Bind an exclusive map\n+\t\t * with matching contents but skip the freeze: the load must be\n+\t\t * rejected by the frozen check with -EPERM. The exclusivity\n+\t\t * check right after it would pass, so the errno uniquely pins\n+\t\t * the freeze requirement.\n \t\t */\n-\t\tr = run_gen_loader(f.gopts.insns, f.gopts.insns_sz, f.blob,\n-\t\t\t\t f.data_sz, f.excl, sizeof(f.excl), NULL, 0,\n-\t\t\t\t false, f.ctx, f.ctx_sz, \u0026ran);\n-\t\tASSERT_TRUE(ran, \"loader ran\");\n-\t\tASSERT_EQ(r, -EINVAL, \"uncomputed metadata hash rejected\");\n+\t\tmopts.excl_prog_hash = f.excl;\n+\t\tmopts.excl_prog_hash_size = sizeof(f.excl);\n+\t\tmap_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, \"unfrozen\", 4,\n+\t\t\t\t\tf.data_sz, 1, \u0026mopts);\n+\t\tif (ASSERT_OK_FD(map_fd, \"unfrozen_map\")) {\n+\t\t\tif (ASSERT_OK(bpf_map_update_elem(map_fd, \u0026key, f.blob, 0),\n+\t\t\t\t \"update\")) {\n+\t\t\t\tfd = load_loader(f.gopts.insns, f.gopts.insns_sz,\n+\t\t\t\t\t\t map_fd, junk, sizeof(junk),\n+\t\t\t\t\t\t KEY_SPEC_SESSION_KEYRING, 1);\n+\t\t\t\tASSERT_EQ(fd, -EPERM,\n+\t\t\t\t\t \"unfrozen map in signed fd_array rejected\");\n+\t\t\t\tif (fd \u003e= 0)\n+\t\t\t\t\tclose(fd);\n+\t\t\t}\n+\t\t\tclose(map_fd);\n+\t\t}\n \t}\n \tgen_loader_fixture_fini(\u0026f);\n }\n \n-static void signature_enforced(void)\n+static void signed_nonarray_fd_array_rejected(void)\n {\n \tstatic const __u8 junk[64] = { 0x30, 0x42, 0x13, 0x37, };\n+\tLIBBPF_OPTS(bpf_map_create_opts, mopts);\n \tstruct gen_loader_fixture f;\n+\tint map_fd, fd;\n+\n+\tif (gen_loader_fixture_init(\u0026f) == 0) {\n+\t\t/*\n+\t\t * Only a plain BPF_MAP_TYPE_ARRAY may be folded into the\n+\t\t * signature. An exclusive map of any other type is rejected\n+\t\t * (-EINVAL) rather than folded - this is the type gate that\n+\t\t * keeps arena maps (map_direct_value_addr() returns a user\n+\t\t * address) and insn-array maps (buffer smaller than value_size)\n+\t\t * out of the hashed region, where the old code would have\n+\t\t * memcpy()'d from them. A hash map stands in here: it is\n+\t\t * exclusive (bound to the loader digest) but not an array.\n+\t\t */\n+\t\tmopts.excl_prog_hash = f.excl;\n+\t\tmopts.excl_prog_hash_size = sizeof(f.excl);\n+\t\tmap_fd = bpf_map_create(BPF_MAP_TYPE_HASH, \"excl_hash\", 4, 4, 1,\n+\t\t\t\t\t\u0026mopts);\n+\t\tif (ASSERT_OK_FD(map_fd, \"excl_hash_map\")) {\n+\t\t\tfd = load_loader(f.gopts.insns, f.gopts.insns_sz, map_fd,\n+\t\t\t\t\t junk, sizeof(junk),\n+\t\t\t\t\t KEY_SPEC_SESSION_KEYRING, 1);\n+\t\t\tASSERT_EQ(fd, -EINVAL,\n+\t\t\t\t \"non-array map in signed fd_array rejected\");\n+\t\t\tif (fd \u003e= 0)\n+\t\t\t\tclose(fd);\n+\t\t\tclose(map_fd);\n+\t\t}\n+\t}\n+\tgen_loader_fixture_fini(\u0026f);\n+}\n+\n+static int setup_meta_map(const struct gen_loader_fixture *f);\n+\n+static void signed_btf_fd_array_rejected(void)\n+{\n+\tchar dir_tmpl[] = \"/tmp/signed_loader_btfXXXXXX\", *dir = NULL;\n+\t__u32 sig_sz = 8192;\n+\tint map_fd = -1, prog_fd = -1;\n+\tunsigned char *buf = NULL;\n+\tstruct gen_loader_fixture f;\n+\tbool have_fixture = false;\n+\tstruct btf *btf = NULL;\n+\tunion bpf_attr attr;\n+\tint fds[2];\n+\t__u8 sig[8192];\n+\n+\tsyscall(__NR_request_key, \"keyring\", \"_uid.0\", NULL,\n+\t\tKEY_SPEC_SESSION_KEYRING);\n+\tdir = mkdtemp(dir_tmpl);\n+\tif (!ASSERT_OK_PTR(dir, \"mkdtemp\"))\n+\t\treturn;\n+\tif (!ASSERT_OK(run_setup(\"setup\", dir), \"verify_sig_setup\")) {\n+\t\trmdir(dir);\n+\t\treturn;\n+\t}\n+\thave_fixture = true;\n+\tif (gen_loader_fixture_init(\u0026f) != 0)\n+\t\tgoto out;\n+\n+\t/*\n+\t * fd_array binds maps and BTFs alike, but only exclusive array maps are\n+\t * folded into the signature. Build an otherwise genuinely signed load -\n+\t * insns || metadata, exclusive frozen map at fd_array[0] - then smuggle\n+\t * an extra BTF into fd_array[1]. A signed program may not bind any BTF,\n+\t * so resolving the fd_array entries rejects the BTF with -EACCES (in\n+\t * __add_used_btf(), before the signature is even verified).\n+\t */\n+\tbuf = malloc((size_t)f.gopts.insns_sz + f.data_sz);\n+\tif (!ASSERT_OK_PTR(buf, \"signbuf\"))\n+\t\tgoto out;\n+\tmemcpy(buf, f.gopts.insns, f.gopts.insns_sz);\n+\tmemcpy(buf + f.gopts.insns_sz, f.blob, f.data_sz);\n+\tif (!ASSERT_OK(sign_buf(dir, buf, f.gopts.insns_sz + f.data_sz, sig,\n+\t\t\t \u0026sig_sz), \"sign insns||metadata\"))\n+\t\tgoto out;\n+\n+\tmap_fd = setup_meta_map(\u0026f);\n+\tif (!ASSERT_OK_FD(map_fd, \"meta_map\"))\n+\t\tgoto out;\n+\tbtf = btf__new_empty();\n+\tif (!ASSERT_OK_PTR(btf, \"btf_new_empty\"))\n+\t\tgoto out;\n+\tbtf__add_int(btf, \"int\", 4, BTF_INT_SIGNED);\n+\tif (!ASSERT_OK(btf__load_into_kernel(btf), \"btf_load\"))\n+\t\tgoto out;\n+\n+\tfds[0] = map_fd;\n+\tfds[1] = btf__fd(btf);\n+\tmemset(\u0026attr, 0, sizeof(attr));\n+\tattr.prog_type = BPF_PROG_TYPE_SYSCALL;\n+\tattr.insns = ptr_to_u64(f.gopts.insns);\n+\tattr.insn_cnt = f.gopts.insns_sz / sizeof(struct bpf_insn);\n+\tattr.license = ptr_to_u64(\"Dual BSD/GPL\");\n+\tattr.prog_flags = BPF_F_SLEEPABLE;\n+\tattr.fd_array = ptr_to_u64(fds);\n+\tattr.fd_array_cnt = 2;\n+\tattr.signature = ptr_to_u64(sig);\n+\tattr.signature_size = sig_sz;\n+\tattr.keyring_id = KEY_SPEC_SESSION_KEYRING;\n+\tmemcpy(attr.prog_name, \"__loader.prog\", sizeof(\"__loader.prog\"));\n+\tprog_fd = syscall(__NR_bpf, BPF_PROG_LOAD, \u0026attr,\n+\t\t\t offsetofend(union bpf_attr, keyring_id));\n+\tASSERT_EQ(prog_fd \u003c 0 ? -errno : prog_fd, -EACCES,\n+\t\t \"BTF in signed fd_array rejected\");\n+\tif (prog_fd \u003e= 0)\n+\t\tclose(prog_fd);\n+out:\n+\tif (btf)\n+\t\tbtf__free(btf);\n+\tif (map_fd \u003e= 0)\n+\t\tclose(map_fd);\n+\tif (have_fixture)\n+\t\tgen_loader_fixture_fini(\u0026f);\n+\tif (dir)\n+\t\trun_setup(\"cleanup\", dir);\n+\tfree(buf);\n+}\n+\n+static void signature_failure_logs(void)\n+{\n+\tstatic const __u8 junk[64] = { 0x30, 0x42, 0x13, 0x37, };\n+\tchar log_buf[1024] = {};\n+\tstruct gen_loader_fixture f;\n+\tunion bpf_attr attr;\n \tint fd;\n \n \tif (gen_loader_fixture_init(\u0026f) == 0) {\n \t\t/*\n-\t\t * A present-but-invalid signature (the cert bytes are not a\n-\t\t * PKCS#7 signature) must be rejected at load: the signature\n-\t\t * path is honored, not ignored. (The valid path is covered by\n-\t\t * the signed lskels.)\n+\t\t * Signature verification now runs inside bpf_check(), so a\n+\t\t * failure is reported through the verifier log. A present-but-\n+\t\t * invalid signature is rejected and the log says why.\n \t\t */\n-\t\tfd = load_loader(f.gopts.insns, f.gopts.insns_sz, -1, junk,\n-\t\t\t\t sizeof(junk), KEY_SPEC_SESSION_KEYRING);\n+\t\tmemset(\u0026attr, 0, sizeof(attr));\n+\t\tattr.prog_type = BPF_PROG_TYPE_SYSCALL;\n+\t\tattr.insns = ptr_to_u64(f.gopts.insns);\n+\t\tattr.insn_cnt = f.gopts.insns_sz / sizeof(struct bpf_insn);\n+\t\tattr.license = ptr_to_u64(\"Dual BSD/GPL\");\n+\t\tattr.prog_flags = BPF_F_SLEEPABLE;\n+\t\tattr.signature = ptr_to_u64(junk);\n+\t\tattr.signature_size = sizeof(junk);\n+\t\tattr.keyring_id = KEY_SPEC_SESSION_KEYRING;\n+\t\tattr.log_level = 1;\n+\t\tattr.log_buf = ptr_to_u64(log_buf);\n+\t\tattr.log_size = sizeof(log_buf);\n+\t\tmemcpy(attr.prog_name, \"__loader.prog\", sizeof(\"__loader.prog\"));\n+\n+\t\tfd = syscall(__NR_bpf, BPF_PROG_LOAD, \u0026attr,\n+\t\t\t offsetofend(union bpf_attr, keyring_id));\n \t\tASSERT_LT(fd, 0, \"invalid signature rejected at load\");\n+\t\tif (fd \u003e= 0)\n+\t\t\tclose(fd);\n+\t\tASSERT_HAS_SUBSTR(log_buf, \"signature verification failed\",\n+\t\t\t\t \"verifier logs signature failure\");\n \t}\n \tgen_loader_fixture_fini(\u0026f);\n }\n@@ -495,7 +573,7 @@ static void signature_too_large(void)\n \t\t * is rejected before the buffer is read.\n \t\t */\n \t\tfd = load_loader(f.gopts.insns, f.gopts.insns_sz, -1, junk,\n-\t\t\t\t 64 \u003c\u003c 20, KEY_SPEC_SESSION_KEYRING);\n+\t\t\t\t 64 \u003c\u003c 20, KEY_SPEC_SESSION_KEYRING, 0);\n \t\tASSERT_EQ(fd, -EINVAL, \"oversized signature rejected\");\n \t}\n \tgen_loader_fixture_fini(\u0026f);\n@@ -515,7 +593,7 @@ static void signature_bad_keyring(void)\n \t\t * large positive serial takes the user-keyring path and won't exist.\n \t\t */\n \t\tfd = load_loader(f.gopts.insns, f.gopts.insns_sz, -1, junk,\n-\t\t\t\t sizeof(junk), INT_MAX);\n+\t\t\t\t sizeof(junk), INT_MAX, 0);\n \t\tASSERT_EQ(fd, -EINVAL, \"signature with bad keyring_id rejected\");\n \t}\n \tgen_loader_fixture_fini(\u0026f);\n@@ -575,7 +653,7 @@ static void metadata_ctx_max_entries_ignored(void)\n \tmemcpy(blob, gopts.data, data_sz);\n \n \tr = run_gen_loader(gopts.insns, gopts.insns_sz, blob, data_sz,\n-\t\t\t excl, sizeof(excl), NULL, 0, true, ctx, ctx_sz, \u0026ran);\n+\t\t\t excl, sizeof(excl), NULL, 0, ctx, ctx_sz, \u0026ran);\n \tif (!ASSERT_TRUE(ran, \"loader ran\") ||\n \t !ASSERT_EQ(r, 0, \"loader retval\"))\n \t\tgoto free_blob;\n@@ -661,7 +739,7 @@ static void metadata_ctx_initial_value_ignored(void)\n \tmemcpy(blob, gopts.data, data_sz);\n \n \tr = run_gen_loader(gopts.insns, gopts.insns_sz, blob, data_sz,\n-\t\t\t excl, sizeof(excl), NULL, 0, true, ctx, ctx_sz, \u0026ran);\n+\t\t\t excl, sizeof(excl), NULL, 0, ctx, ctx_sz, \u0026ran);\n \tif (!ASSERT_TRUE(ran, \"loader ran\") ||\n \t !ASSERT_EQ(r, 0, \"loader retval\"))\n \t\tgoto free_blob;\n@@ -714,6 +792,7 @@ static void signature_authenticates_insns(void)\n \t__u8 excl[SHA256_DIGEST_LENGTH], sig[8192];\n \t__u32 sig_sz = sizeof(sig), insns_sz, data_sz, ctx_sz;\n \tunsigned char *insns = NULL, *tampered = NULL, *blob = NULL;\n+\tunsigned char *signbuf = NULL;\n \tint nr_maps = 0, nr_progs = 0, r;\n \tstruct bpf_program *p;\n \tstruct bpf_map *m;\n@@ -760,29 +839,141 @@ static void signature_authenticates_insns(void)\n \tmemcpy(blob, gopts.data, data_sz);\n \tlibbpf_sha256(insns, insns_sz, excl);\n \n-\tif (!ASSERT_OK(sign_buf(dir, insns, insns_sz, sig, \u0026sig_sz), \"sign-file\"))\n+\tsignbuf = malloc((size_t)insns_sz + data_sz);\n+\tif (!ASSERT_OK_PTR(signbuf, \"signbuf\"))\n+\t\tgoto cleanup;\n+\tmemcpy(signbuf, insns, insns_sz);\n+\tmemcpy(signbuf + insns_sz, blob, data_sz);\n+\tif (!ASSERT_OK(sign_buf(dir, signbuf, insns_sz + data_sz, sig, \u0026sig_sz),\n+\t\t \"sign-file\"))\n \t\tgoto cleanup;\n \n \tmemset(ctx, 0, ctx_sz);\n \t((struct bpf_loader_ctx *)ctx)-\u003esz = ctx_sz;\n \tr = run_gen_loader(insns, insns_sz, blob, data_sz, excl, sizeof(excl),\n-\t\t\t sig, sig_sz, true, ctx, ctx_sz, \u0026ran);\n+\t\t\t sig, sig_sz, ctx, ctx_sz, \u0026ran);\n \tASSERT_TRUE(ran, \"valid signature: loader loaded and ran\");\n \tASSERT_EQ(r, 0, \"valid signature accepted\");\n \tclose_loader_ctx_fds(ctx, nr_maps, nr_progs);\n \n \tmemcpy(tampered, insns, insns_sz);\n \ttampered[insns_sz / 2] ^= 0xff;\n+\t/*\n+\t * Bind the metadata map to the tampered loader's own digest, so the\n+\t * verifier's exclusive-map check (excl_prog_sha == prog-\u003edigest) passes\n+\t * and the signature - verified after the maps are resolved - is what\n+\t * rejects the load. This is the attacker's best case: even after\n+\t * re-binding the exclusive map to their tampered loader, the signature\n+\t * over the original insns || metadata still fails. (Leaving the map\n+\t * bound to the original digest would instead trip the excl check first.)\n+\t */\n+\tlibbpf_sha256(tampered, insns_sz, excl);\n \tmemset(ctx, 0, ctx_sz);\n \t((struct bpf_loader_ctx *)ctx)-\u003esz = ctx_sz;\n \tr = run_gen_loader(tampered, insns_sz, blob, data_sz, excl, sizeof(excl),\n-\t\t\t sig, sig_sz, true, ctx, ctx_sz, \u0026ran);\n+\t\t\t sig, sig_sz, ctx, ctx_sz, \u0026ran);\n \tASSERT_FALSE(ran, \"tampered loader rejected before run\");\n \tASSERT_EQ(r, -EKEYREJECTED, \"signature is bound to the instructions\");\n cleanup:\n \tfree(insns);\n \tfree(tampered);\n \tfree(blob);\n+\tfree(signbuf);\n+\tfree(ctx);\n+\ttest_signed_loader__destroy(skel);\n+\trun_setup(\"cleanup\", dir);\n+}\n+\n+static void signature_authenticates_metadata(void)\n+{\n+\tLIBBPF_OPTS(gen_loader_opts, gopts, .gen_hash = true);\n+\tchar dir_tmpl[] = \"/tmp/signed_loaderXXXXXX\", *dir;\n+\tstruct test_signed_loader *skel = NULL;\n+\t__u8 excl[SHA256_DIGEST_LENGTH], sig[8192];\n+\t__u32 sig_sz = sizeof(sig), insns_sz, data_sz, ctx_sz;\n+\tunsigned char *insns = NULL, *blob = NULL;\n+\tunsigned char *signbuf = NULL;\n+\tint nr_maps = 0, nr_progs = 0, r;\n+\tstruct bpf_program *p;\n+\tstruct bpf_map *m;\n+\tvoid *ctx = NULL;\n+\tbool ran;\n+\n+\tsyscall(__NR_request_key, \"keyring\", \"_uid.0\", NULL,\n+\t\tKEY_SPEC_SESSION_KEYRING);\n+\tdir = mkdtemp(dir_tmpl);\n+\tif (!ASSERT_OK_PTR(dir, \"mkdtemp\"))\n+\t\treturn;\n+\tif (!ASSERT_OK(run_setup(\"setup\", dir), \"verify_sig_setup\")) {\n+\t\trmdir(dir);\n+\t\treturn;\n+\t}\n+\n+\tskel = test_signed_loader__open();\n+\tif (!ASSERT_OK_PTR(skel, \"skel_open\"))\n+\t\tgoto cleanup;\n+\tif (!ASSERT_OK(bpf_object__gen_loader(skel-\u003eobj, \u0026gopts), \"gen_loader\"))\n+\t\tgoto cleanup;\n+\tif (!ASSERT_OK(bpf_object__load(skel-\u003eobj), \"gen_load\"))\n+\t\tgoto cleanup;\n+\n+\tbpf_object__for_each_program(p, skel-\u003eobj)\n+\t\tnr_progs++;\n+\tbpf_object__for_each_map(m, skel-\u003eobj)\n+\t\tnr_maps++;\n+\tctx_sz = sizeof(struct bpf_loader_ctx) +\n+\t\t nr_maps * sizeof(struct bpf_map_desc) +\n+\t\t nr_progs * sizeof(struct bpf_prog_desc);\n+\tinsns_sz = gopts.insns_sz;\n+\tdata_sz = gopts.data_sz;\n+\tctx = calloc(1, ctx_sz);\n+\tinsns = malloc(insns_sz);\n+\tblob = malloc(data_sz);\n+\tif (!ASSERT_OK_PTR(ctx, \"ctx\") ||\n+\t !ASSERT_OK_PTR(insns, \"insns\") ||\n+\t !ASSERT_OK_PTR(blob, \"blob\"))\n+\t\tgoto cleanup;\n+\tmemcpy(insns, gopts.insns, insns_sz);\n+\tmemcpy(blob, gopts.data, data_sz);\n+\tlibbpf_sha256(insns, insns_sz, excl);\n+\n+\tsignbuf = malloc((size_t)insns_sz + data_sz);\n+\tif (!ASSERT_OK_PTR(signbuf, \"signbuf\"))\n+\t\tgoto cleanup;\n+\tmemcpy(signbuf, insns, insns_sz);\n+\tmemcpy(signbuf + insns_sz, blob, data_sz);\n+\tif (!ASSERT_OK(sign_buf(dir, signbuf, insns_sz + data_sz, sig, \u0026sig_sz),\n+\t\t \"sign-file\"))\n+\t\tgoto cleanup;\n+\n+\tmemset(ctx, 0, ctx_sz);\n+\t((struct bpf_loader_ctx *)ctx)-\u003esz = ctx_sz;\n+\tr = run_gen_loader(insns, insns_sz, blob, data_sz, excl, sizeof(excl),\n+\t\t\t sig, sig_sz, ctx, ctx_sz, \u0026ran);\n+\tASSERT_TRUE(ran, \"valid signature: loader loaded and ran\");\n+\tASSERT_EQ(r, 0, \"valid signature accepted\");\n+\tclose_loader_ctx_fds(ctx, nr_maps, nr_progs);\n+\n+\t/*\n+\t * Tamper the metadata after signing while leaving the instructions\n+\t * and thus the exclusive hash binding untouched: the map freezes\n+\t * fine and excl_prog_sha still matches the loader's digest, so the\n+\t * load reaches signature verification, which folds the live frozen\n+\t * map bytes into the checked payload and must reject the modified\n+\t * blob. A kernel folding anything but the map contents themselves\n+\t * would wrongly accept this load.\n+\t */\n+\tblob[data_sz / 2] ^= 0xff;\n+\tmemset(ctx, 0, ctx_sz);\n+\t((struct bpf_loader_ctx *)ctx)-\u003esz = ctx_sz;\n+\tr = run_gen_loader(insns, insns_sz, blob, data_sz, excl, sizeof(excl),\n+\t\t\t sig, sig_sz, ctx, ctx_sz, \u0026ran);\n+\tASSERT_FALSE(ran, \"tampered metadata rejected before run\");\n+\tASSERT_EQ(r, -EKEYREJECTED, \"signature is bound to the metadata\");\n+cleanup:\n+\tfree(insns);\n+\tfree(blob);\n+\tfree(signbuf);\n \tfree(ctx);\n \ttest_signed_loader__destroy(skel);\n \trun_setup(\"cleanup\", dir);\n@@ -1007,10 +1198,11 @@ static void lsm_signature_verdict(void)\n {\n \tchar dir_tmpl[] = \"/tmp/signed_loader_lsmXXXXXX\", *dir = NULL;\n \tstruct test_signed_loader_lsm *lsm = NULL;\n+\t__u32 sig_sz = 8192, msig_sz = 8192;\n \tint map_fd = -1, prog_fd = -1;\n \tbool have_fixture = false;\n \tstruct gen_loader_fixture f;\n-\t__u32 sig_sz = 8192;\n+\tunsigned char *buf;\n \t__s32 ses_serial;\n \t__u8 sig[8192];\n \n@@ -1029,7 +1221,7 @@ static void lsm_signature_verdict(void)\n \tif (!ASSERT_OK_FD(map_fd, \"meta_map_unsigned\"))\n \t\tgoto out;\n \tlsm-\u003ebss-\u003eseen = 0;\n-\tprog_fd = load_loader(f.gopts.insns, f.gopts.insns_sz, map_fd, NULL, 0, 0);\n+\tprog_fd = load_loader(f.gopts.insns, f.gopts.insns_sz, map_fd, NULL, 0, 0, 0);\n \tclose(map_fd);\n \tmap_fd = -1;\n \tif (!ASSERT_OK_FD(prog_fd, \"unsigned loader load\"))\n@@ -1062,22 +1254,51 @@ static void lsm_signature_verdict(void)\n \t\tgoto out;\n \tlsm-\u003ebss-\u003eseen = 0;\n \tprog_fd = load_loader(f.gopts.insns, f.gopts.insns_sz, map_fd, sig,\n-\t\t\t sig_sz, KEY_SPEC_SESSION_KEYRING);\n+\t\t\t sig_sz, KEY_SPEC_SESSION_KEYRING, 0);\n \tclose(map_fd);\n \tmap_fd = -1;\n-\tif (!ASSERT_OK_FD(prog_fd, \"signed loader load\"))\n-\t\tgoto out;\n-\tclose(prog_fd);\n+\tASSERT_EQ(prog_fd, -EACCES, \"unfolded metadata rejected\");\n+\tif (prog_fd \u003e= 0)\n+\t\tclose(prog_fd);\n \tprog_fd = -1;\n \n \tses_serial = syscall(__NR_keyctl, KEYCTL_GET_KEYRING_ID,\n \t\t\t KEY_SPEC_SESSION_KEYRING, 0);\n \tASSERT_EQ(lsm-\u003ebss-\u003eseen, 1, \"signed: one observed load\");\n-\tASSERT_EQ(lsm-\u003ebss-\u003esig_verdict, BPF_SIG_VERIFIED, \"signed verdict\");\n+\tASSERT_EQ(lsm-\u003ebss-\u003esig_verdict, BPF_SIG_VERIFIED,\n+\t\t \"admission saw a valid signature\");\n \tASSERT_EQ(lsm-\u003ebss-\u003esig_keyring_type, BPF_SIG_KEYRING_USER, \"signed keyring type\");\n \tASSERT_GT(ses_serial, 0, \"session keyring serial resolved\");\n \tASSERT_EQ(lsm-\u003ebss-\u003esig_keyring_serial, ses_serial,\n \t\t \"signed: validated against session keyring\");\n+\n+\tbuf = malloc((size_t)f.gopts.insns_sz + f.data_sz);\n+\tif (!ASSERT_OK_PTR(buf, \"meta_signbuf\"))\n+\t\tgoto out;\n+\tmemcpy(buf, f.gopts.insns, f.gopts.insns_sz);\n+\tmemcpy(buf + f.gopts.insns_sz, f.blob, f.data_sz);\n+\tif (!ASSERT_OK(sign_buf(dir, buf, f.gopts.insns_sz + f.data_sz,\n+\t\t\t\tsig, \u0026msig_sz), \"sign insns||metadata\")) {\n+\t\tfree(buf);\n+\t\tgoto out;\n+\t}\n+\tfree(buf);\n+\n+\tmap_fd = setup_meta_map(\u0026f);\n+\tif (!ASSERT_OK_FD(map_fd, \"meta_map_bound\"))\n+\t\tgoto out;\n+\tlsm-\u003ebss-\u003eseen = 0;\n+\tprog_fd = load_loader(f.gopts.insns, f.gopts.insns_sz, map_fd, sig,\n+\t\t\t msig_sz, KEY_SPEC_SESSION_KEYRING, 1);\n+\tclose(map_fd);\n+\tmap_fd = -1;\n+\tif (!ASSERT_OK_FD(prog_fd, \"metadata-bound loader load\"))\n+\t\tgoto out;\n+\tclose(prog_fd);\n+\tprog_fd = -1;\n+\tASSERT_EQ(lsm-\u003ebss-\u003eseen, 1, \"metadata: one observed load\");\n+\tASSERT_EQ(lsm-\u003ebss-\u003esig_verdict, BPF_SIG_VERIFIED,\n+\t\t \"metadata-bound verdict\");\n out:\n \tif (map_fd \u003e= 0)\n \t\tclose(map_fd);\n@@ -1090,20 +1311,467 @@ static void lsm_signature_verdict(void)\n \ttest_signed_loader_lsm__destroy(lsm);\n }\n \n+/*\n+ * Load-time metadata verification: the kernel folds the frozen metadata map\n+ * into the signature (insns || metadata) and checks it at BPF_PROG_LOAD via\n+ * fd_array_cnt, rather than the loader checking from within BPF. Sign that\n+ * concatenation, hand the kernel the map, and confirm the signed loader loads,\n+ * runs, and installs its target.\n+ */\n+static int loadtime_drive(const char *dir, const void *insns, __u32 insns_sz,\n+\t\t\t const void *data, __u32 data_sz, const __u8 *excl,\n+\t\t\t void *ctx, __u32 ctx_sz, int *load_ret, bool *ran)\n+{\n+\tLIBBPF_OPTS(bpf_map_create_opts, mopts,\n+\t\t .excl_prog_hash = excl,\n+\t\t .excl_prog_hash_size = SHA256_DIGEST_LENGTH);\n+\t__u32 sig_sz = 8192, key = 0;\n+\tunsigned char *buf = NULL;\n+\tint map_fd, prog_fd, ret = 0;\n+\tunion bpf_attr attr;\n+\t__u8 sig[8192];\n+\n+\t*ran = false;\n+\t*load_ret = 0;\n+\n+\t/*\n+\t * Metadata map, bound to the loader digest and frozen, exactly as\n+\t * skel_internal.h's bpf_load_and_run() sets it up.\n+\t */\n+\tmap_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, \"__loader.map\", 4,\n+\t\t\t\tdata_sz, 1, \u0026mopts);\n+\tif (map_fd \u003c 0) {\n+\t\tret = -errno;\n+\t\tgoto out_load;\n+\t}\n+\tif (bpf_map_update_elem(map_fd, \u0026key, data, 0) || bpf_map_freeze(map_fd)) {\n+\t\tret = -errno;\n+\t\tgoto out_load;\n+\t}\n+\n+\t/* Sign insns || metadata, the same bytes the kernel reconstructs. */\n+\tbuf = malloc((size_t)insns_sz + data_sz);\n+\tif (!buf) {\n+\t\tret = -ENOMEM;\n+\t\tgoto out_load;\n+\t}\n+\tmemcpy(buf, insns, insns_sz);\n+\tmemcpy(buf + insns_sz, data, data_sz);\n+\tret = sign_buf(dir, buf, insns_sz + data_sz, sig, \u0026sig_sz);\n+\tif (ret)\n+\t\tgoto out_load;\n+\n+\tmemset(\u0026attr, 0, sizeof(attr));\n+\tattr.prog_type = BPF_PROG_TYPE_SYSCALL;\n+\tattr.insns = ptr_to_u64(insns);\n+\tattr.insn_cnt = insns_sz / sizeof(struct bpf_insn);\n+\tattr.license = ptr_to_u64(\"Dual BSD/GPL\");\n+\tattr.prog_flags = BPF_F_SLEEPABLE;\n+\tattr.fd_array = ptr_to_u64(\u0026map_fd);\n+\tattr.signature = ptr_to_u64(sig);\n+\tattr.signature_size = sig_sz;\n+\tattr.keyring_id = KEY_SPEC_SESSION_KEYRING;\n+\tattr.fd_array_cnt = 1;\n+\tmemcpy(attr.prog_name, \"__loader.prog\", sizeof(\"__loader.prog\"));\n+\tprog_fd = syscall(__NR_bpf, BPF_PROG_LOAD, \u0026attr,\n+\t\t\t offsetofend(union bpf_attr, keyring_id));\n+\tif (prog_fd \u003c 0) {\n+\t\tret = -errno;\n+\t\tgoto out_load;\n+\t}\n+\n+\tmemset(\u0026attr, 0, sizeof(attr));\n+\tattr.test.prog_fd = prog_fd;\n+\tattr.test.ctx_in = ptr_to_u64(ctx);\n+\tattr.test.ctx_size_in = ctx_sz;\n+\tif (syscall(__NR_bpf, BPF_PROG_RUN, \u0026attr,\n+\t\t offsetofend(union bpf_attr, test)) \u003c 0) {\n+\t\tret = -errno;\n+\t\tgoto out_prog;\n+\t}\n+\t*ran = true;\n+\tret = (int)attr.test.retval;\n+out_prog:\n+\tclose(prog_fd);\n+\tgoto out_map;\n+out_load:\n+\t*load_ret = ret;\n+out_map:\n+\tfree(buf);\n+\tif (map_fd \u003e= 0)\n+\t\tclose(map_fd);\n+\treturn ret;\n+}\n+\n+static void loadtime_verify(struct bpf_object *obj, int expect_maps)\n+{\n+\tLIBBPF_OPTS(gen_loader_opts, gopts, .gen_hash = true);\n+\tchar dir_tmpl[] = \"/tmp/signed_loader_ltXXXXXX\", *dir = NULL;\n+\tint nr_maps = 0, nr_progs = 0, load_ret = 0, r;\n+\t__u8 excl[SHA256_DIGEST_LENGTH];\n+\tstruct bpf_prog_desc *pd;\n+\tstruct bpf_map_desc *md;\n+\tunsigned char *blob = NULL;\n+\tstruct bpf_program *p;\n+\tstruct bpf_map *m;\n+\t__u32 ctx_sz, data_sz;\n+\tvoid *ctx = NULL;\n+\tbool ran = false;\n+\n+\tsyscall(__NR_request_key, \"keyring\", \"_uid.0\", NULL,\n+\t\tKEY_SPEC_SESSION_KEYRING);\n+\tdir = mkdtemp(dir_tmpl);\n+\tif (!ASSERT_OK_PTR(dir, \"mkdtemp\"))\n+\t\treturn;\n+\tif (!ASSERT_OK(run_setup(\"setup\", dir), \"verify_sig_setup\")) {\n+\t\trmdir(dir);\n+\t\treturn;\n+\t}\n+\n+\tif (!ASSERT_OK(bpf_object__gen_loader(obj, \u0026gopts), \"gen_loader\"))\n+\t\tgoto out;\n+\tif (!ASSERT_OK(bpf_object__load(obj), \"gen_load\"))\n+\t\tgoto out;\n+\n+\tbpf_object__for_each_program(p, obj)\n+\t\tnr_progs++;\n+\tbpf_object__for_each_map(m, obj)\n+\t\tnr_maps++;\n+\tif (!ASSERT_EQ(nr_maps, expect_maps, \"fixture map count\"))\n+\t\tgoto out;\n+\n+\tctx_sz = sizeof(struct bpf_loader_ctx) +\n+\t\t nr_maps * sizeof(struct bpf_map_desc) +\n+\t\t nr_progs * sizeof(struct bpf_prog_desc);\n+\tctx = calloc(1, ctx_sz);\n+\tif (!ASSERT_OK_PTR(ctx, \"ctx_alloc\"))\n+\t\tgoto out;\n+\t((struct bpf_loader_ctx *)ctx)-\u003esz = ctx_sz;\n+\n+\tdata_sz = gopts.data_sz;\n+\tblob = malloc(data_sz);\n+\tif (!ASSERT_OK_PTR(blob, \"blob_alloc\"))\n+\t\tgoto out;\n+\tmemcpy(blob, gopts.data, data_sz);\n+\n+\t/* excl_prog_hash = SHA256(loader insns) == the loader's prog-\u003edigest. */\n+\tlibbpf_sha256(gopts.insns, gopts.insns_sz, excl);\n+\n+\tr = loadtime_drive(dir, gopts.insns, gopts.insns_sz, blob, data_sz,\n+\t\t\t excl, ctx, ctx_sz, \u0026load_ret, \u0026ran);\n+\tASSERT_OK(load_ret, \"signed loader loaded (insns || metadata)\");\n+\tASSERT_TRUE(ran, \"loader ran\");\n+\tASSERT_EQ(r, 0, \"loader installed its target\");\n+\n+\tmd = (struct bpf_map_desc *)((char *)ctx + sizeof(struct bpf_loader_ctx));\n+\tpd = (struct bpf_prog_desc *)(md + nr_maps);\n+\tASSERT_GT(pd[0].prog_fd, 0, \"target program installed\");\n+\tif (nr_maps)\n+\t\tASSERT_GT(md[0].map_fd, 0, \"target map installed\");\n+\n+\tclose_loader_ctx_fds(ctx, nr_maps, nr_progs);\n+out:\n+\tfree(blob);\n+\tfree(ctx);\n+\tif (dir)\n+\t\trun_setup(\"cleanup\", dir);\n+}\n+\n+static void loadtime_no_map(void)\n+{\n+\tstruct test_signed_loader *skel = test_signed_loader__open();\n+\n+\tif (!ASSERT_OK_PTR(skel, \"skel_open\"))\n+\t\treturn;\n+\tloadtime_verify(skel-\u003eobj, 0);\n+\ttest_signed_loader__destroy(skel);\n+}\n+\n+static void loadtime_with_map(void)\n+{\n+\tstruct test_signed_loader_map *skel = test_signed_loader_map__open();\n+\n+\tif (!ASSERT_OK_PTR(skel, \"skel_open\"))\n+\t\treturn;\n+\tloadtime_verify(skel-\u003eobj, 1);\n+\ttest_signed_loader_map__destroy(skel);\n+}\n+\n+/*\n+ * A signed program need not bind any map. A plain BPF_PROG_TYPE_SYSCALL\n+ * program with no fd_array is signed over its instructions alone: the kernel\n+ * verifies the signature, folds no metadata, and the program loads. Exercise\n+ * the fd_array == NULL / fd_array_cnt == 0 path, and confirm the signature\n+ * still authenticates the instructions (a tampered copy is rejected).\n+ */\n+static void signed_no_fd_array(void)\n+{\n+\tstruct bpf_insn insns[] = {\n+\t\tBPF_MOV64_IMM(BPF_REG_0, 0),\n+\t\tBPF_EXIT_INSN(),\n+\t};\n+\tchar dir_tmpl[] = \"/tmp/signed_loaderXXXXXX\", *dir;\n+\t__u32 sig_sz = 8192;\n+\tunion bpf_attr attr;\n+\t__u8 sig[8192];\n+\tint prog_fd, err;\n+\n+\tsyscall(__NR_request_key, \"keyring\", \"_uid.0\", NULL,\n+\t\tKEY_SPEC_SESSION_KEYRING);\n+\tdir = mkdtemp(dir_tmpl);\n+\tif (!ASSERT_OK_PTR(dir, \"mkdtemp\"))\n+\t\treturn;\n+\tif (!ASSERT_OK(run_setup(\"setup\", dir), \"verify_sig_setup\")) {\n+\t\trmdir(dir);\n+\t\treturn;\n+\t}\n+\n+\t/* No metadata map: the signed payload is the instructions alone. */\n+\tif (!ASSERT_OK(sign_buf(dir, insns, sizeof(insns), sig, \u0026sig_sz),\n+\t\t \"sign-file\"))\n+\t\tgoto cleanup;\n+\n+\tmemset(\u0026attr, 0, sizeof(attr));\n+\tattr.prog_type = BPF_PROG_TYPE_SYSCALL;\n+\tattr.insns = ptr_to_u64(insns);\n+\tattr.insn_cnt = ARRAY_SIZE(insns);\n+\tattr.license = ptr_to_u64(\"Dual BSD/GPL\");\n+\tattr.prog_flags = BPF_F_SLEEPABLE;\n+\tattr.signature = ptr_to_u64(sig);\n+\tattr.signature_size = sig_sz;\n+\tattr.keyring_id = KEY_SPEC_SESSION_KEYRING;\n+\t/* fd_array and fd_array_cnt deliberately left NULL/0. */\n+\tmemcpy(attr.prog_name, \"signed_nomap\", sizeof(\"signed_nomap\"));\n+\n+\tprog_fd = syscall(__NR_bpf, BPF_PROG_LOAD, \u0026attr,\n+\t\t\t offsetofend(union bpf_attr, keyring_id));\n+\tif (!ASSERT_GE(prog_fd, 0, \"map-less signed program loaded\")) {\n+\t\tif (prog_fd \u003e= 0)\n+\t\t\tclose(prog_fd);\n+\t\tgoto cleanup;\n+\t}\n+\tclose(prog_fd);\n+\n+\t/* The signature covers the instructions, so tampering must be rejected. */\n+\tinsns[0].imm = 1;\n+\tprog_fd = syscall(__NR_bpf, BPF_PROG_LOAD, \u0026attr,\n+\t\t\t offsetofend(union bpf_attr, keyring_id));\n+\terr = prog_fd \u003c 0 ? -errno : prog_fd;\n+\tASSERT_EQ(err, -EKEYREJECTED, \"tampered map-less program rejected\");\n+\tif (prog_fd \u003e= 0)\n+\t\tclose(prog_fd);\n+cleanup:\n+\trun_setup(\"cleanup\", dir);\n+}\n+\n+/*\n+ * A signed program may reach maps only through fd_array indices, so the kernel\n+ * folds (and thus attests) them. A direct BPF_PSEUDO_MAP_FD reference - a raw,\n+ * unfolded fd baked into the signed instructions - is rejected by the verifier.\n+ */\n+static void signed_map_by_fd_rejected(void)\n+{\n+\tstruct bpf_insn insns[] = {\n+\t\tBPF_LD_MAP_FD(BPF_REG_1, 0),\n+\t\tBPF_MOV64_IMM(BPF_REG_0, 0),\n+\t\tBPF_EXIT_INSN(),\n+\t};\n+\tchar dir_tmpl[] = \"/tmp/signed_loaderXXXXXX\", *dir;\n+\t__u32 sig_sz = 8192;\n+\tunion bpf_attr attr;\n+\t__u8 sig[8192];\n+\tint map_fd, prog_fd, err;\n+\n+\tmap_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, \"sig_mapfd\", 4, 4, 1, NULL);\n+\tif (!ASSERT_GE(map_fd, 0, \"map_create\"))\n+\t\treturn;\n+\tinsns[0].imm = map_fd;\t/* bake the raw map fd into the ld_imm64 */\n+\n+\tsyscall(__NR_request_key, \"keyring\", \"_uid.0\", NULL,\n+\t\tKEY_SPEC_SESSION_KEYRING);\n+\tdir = mkdtemp(dir_tmpl);\n+\tif (!ASSERT_OK_PTR(dir, \"mkdtemp\"))\n+\t\tgoto out_map;\n+\tif (!ASSERT_OK(run_setup(\"setup\", dir), \"verify_sig_setup\")) {\n+\t\trmdir(dir);\n+\t\tgoto out_map;\n+\t}\n+\n+\t/* Sign the instructions, raw map fd and all. */\n+\tif (!ASSERT_OK(sign_buf(dir, insns, sizeof(insns), sig, \u0026sig_sz),\n+\t\t \"sign-file\"))\n+\t\tgoto cleanup;\n+\n+\tmemset(\u0026attr, 0, sizeof(attr));\n+\tattr.prog_type = BPF_PROG_TYPE_SYSCALL;\n+\tattr.insns = ptr_to_u64(insns);\n+\tattr.insn_cnt = ARRAY_SIZE(insns);\n+\tattr.license = ptr_to_u64(\"Dual BSD/GPL\");\n+\tattr.prog_flags = BPF_F_SLEEPABLE;\n+\tattr.signature = ptr_to_u64(sig);\n+\tattr.signature_size = sig_sz;\n+\tattr.keyring_id = KEY_SPEC_SESSION_KEYRING;\n+\t/* No fd_array: the map is reached by a raw fd in the instructions. */\n+\tmemcpy(attr.prog_name, \"signed_mapfd\", sizeof(\"signed_mapfd\"));\n+\n+\tprog_fd = syscall(__NR_bpf, BPF_PROG_LOAD, \u0026attr,\n+\t\t\t offsetofend(union bpf_attr, keyring_id));\n+\terr = prog_fd \u003c 0 ? -errno : prog_fd;\n+\tASSERT_EQ(err, -EINVAL, \"signed program referencing a map by fd rejected\");\n+\tif (prog_fd \u003e= 0)\n+\t\tclose(prog_fd);\n+cleanup:\n+\trun_setup(\"cleanup\", dir);\n+out_map:\n+\tclose(map_fd);\n+}\n+\n+/*\n+ * A signed program may reach maps only through the continuous fd_array, so the\n+ * kernel folds (and thus attests) them. Referencing a map by fd_array *index*\n+ * while leaving fd_array_cnt at 0 selects the sparse path, which resolves a map\n+ * the signature never covered; the verifier rejects it up front with -EACCES.\n+ */\n+static void signed_sparse_fd_array_rejected(void)\n+{\n+\tstruct bpf_insn insns[] = {\n+\t\tBPF_LD_IMM64_RAW(BPF_REG_1, BPF_PSEUDO_MAP_IDX, 0),\n+\t\tBPF_MOV64_IMM(BPF_REG_0, 0),\n+\t\tBPF_EXIT_INSN(),\n+\t};\n+\tchar dir_tmpl[] = \"/tmp/signed_loader_spXXXXXX\", *dir;\n+\t__u32 sig_sz = 8192;\n+\tunion bpf_attr attr;\n+\t__u8 sig[8192];\n+\tint map_fd, prog_fd, err;\n+\n+\tmap_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, \"sig_sparse\", 4, 4, 1, NULL);\n+\tif (!ASSERT_GE(map_fd, 0, \"map_create\"))\n+\t\treturn;\n+\n+\tsyscall(__NR_request_key, \"keyring\", \"_uid.0\", NULL,\n+\t\tKEY_SPEC_SESSION_KEYRING);\n+\tdir = mkdtemp(dir_tmpl);\n+\tif (!ASSERT_OK_PTR(dir, \"mkdtemp\"))\n+\t\tgoto out_map;\n+\tif (!ASSERT_OK(run_setup(\"setup\", dir), \"verify_sig_setup\")) {\n+\t\trmdir(dir);\n+\t\tgoto out_map;\n+\t}\n+\n+\t/* Sign the instructions alone; the sparse map is not folded. */\n+\tif (!ASSERT_OK(sign_buf(dir, insns, sizeof(insns), sig, \u0026sig_sz),\n+\t\t \"sign-file\"))\n+\t\tgoto cleanup;\n+\n+\tmemset(\u0026attr, 0, sizeof(attr));\n+\tattr.prog_type = BPF_PROG_TYPE_SYSCALL;\n+\tattr.insns = ptr_to_u64(insns);\n+\tattr.insn_cnt = ARRAY_SIZE(insns);\n+\tattr.license = ptr_to_u64(\"Dual BSD/GPL\");\n+\tattr.prog_flags = BPF_F_SLEEPABLE;\n+\tattr.fd_array = ptr_to_u64(\u0026map_fd);\n+\tattr.fd_array_cnt = 0; /* sparse: force lazy map resolution */\n+\tattr.signature = ptr_to_u64(sig);\n+\tattr.signature_size = sig_sz;\n+\tattr.keyring_id = KEY_SPEC_SESSION_KEYRING;\n+\tmemcpy(attr.prog_name, \"signed_sparse\", sizeof(\"signed_sparse\"));\n+\n+\tprog_fd = syscall(__NR_bpf, BPF_PROG_LOAD, \u0026attr,\n+\t\t\t offsetofend(union bpf_attr, keyring_id));\n+\terr = prog_fd \u003c 0 ? -errno : prog_fd;\n+\tASSERT_EQ(err, -EACCES, \"signed program binding a sparse fd_array map rejected\");\n+\tif (prog_fd \u003e= 0)\n+\t\tclose(prog_fd);\n+cleanup:\n+\trun_setup(\"cleanup\", dir);\n+out_map:\n+\tclose(map_fd);\n+}\n+\n+static void signed_module_kfunc_rejected(void)\n+{\n+\tstruct bpf_insn insns[] = {\n+\t\tBPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, BPF_PSEUDO_KFUNC_CALL, 1, 1),\n+\t\tBPF_MOV64_IMM(BPF_REG_0, 0),\n+\t\tBPF_EXIT_INSN(),\n+\t};\n+\tchar dir_tmpl[] = \"/tmp/signed_loader_kfnXXXXXX\", *dir;\n+\tint prog_fd, err, fds[2];\n+\tstruct btf *btf = NULL;\n+\t__u32 sig_sz = 8192;\n+\tunion bpf_attr attr;\n+\t__u8 sig[8192];\n+\n+\tsyscall(__NR_request_key, \"keyring\", \"_uid.0\", NULL,\n+\t\tKEY_SPEC_SESSION_KEYRING);\n+\tdir = mkdtemp(dir_tmpl);\n+\tif (!ASSERT_OK_PTR(dir, \"mkdtemp\"))\n+\t\treturn;\n+\tif (!ASSERT_OK(run_setup(\"setup\", dir), \"verify_sig_setup\")) {\n+\t\trmdir(dir);\n+\t\treturn;\n+\t}\n+\tif (!ASSERT_OK(sign_buf(dir, insns, sizeof(insns), sig, \u0026sig_sz),\n+\t\t \"sign-file\"))\n+\t\tgoto cleanup;\n+\tbtf = btf__new_empty();\n+\tif (!ASSERT_OK_PTR(btf, \"btf_new_empty\"))\n+\t\tgoto cleanup;\n+\tbtf__add_int(btf, \"int\", 4, BTF_INT_SIGNED);\n+\tif (!ASSERT_OK(btf__load_into_kernel(btf), \"btf_load\"))\n+\t\tgoto cleanup;\n+\tfds[0] = -1;\n+\tfds[1] = btf__fd(btf);\n+\n+\tmemset(\u0026attr, 0, sizeof(attr));\n+\tattr.prog_type = BPF_PROG_TYPE_SYSCALL;\n+\tattr.insns = ptr_to_u64(insns);\n+\tattr.insn_cnt = ARRAY_SIZE(insns);\n+\tattr.license = ptr_to_u64(\"Dual BSD/GPL\");\n+\tattr.prog_flags = BPF_F_SLEEPABLE;\n+\tattr.fd_array = ptr_to_u64(fds);\n+\tattr.fd_array_cnt = 0; /* sparse: force lazy kfunc BTF resolution */\n+\tattr.signature = ptr_to_u64(sig);\n+\tattr.signature_size = sig_sz;\n+\tattr.keyring_id = KEY_SPEC_SESSION_KEYRING;\n+\tmemcpy(attr.prog_name, \"signed_kfunc\", sizeof(\"signed_kfunc\"));\n+\n+\tprog_fd = syscall(__NR_bpf, BPF_PROG_LOAD, \u0026attr,\n+\t\t\t offsetofend(union bpf_attr, keyring_id));\n+\terr = prog_fd \u003c 0 ? -errno : prog_fd;\n+\tif (prog_fd \u003e= 0)\n+\t\tclose(prog_fd);\n+\n+\tASSERT_EQ(err, -EACCES, \"module kfunc BTF in signed program rejected\");\n+cleanup:\n+\tif (btf)\n+\t\tbtf__free(btf);\n+\trun_setup(\"cleanup\", dir);\n+}\n+\n void test_signed_loader(void)\n {\n-\tif (test__start_subtest(\"metadata_check_shape\"))\n-\t\tmetadata_check_shape();\n+\tif (test__start_subtest(\"loadtime_no_map\"))\n+\t\tloadtime_no_map();\n+\tif (test__start_subtest(\"loadtime_with_map\"))\n+\t\tloadtime_with_map();\n \tif (test__start_subtest(\"metadata_match\"))\n \t\tmetadata_match();\n-\tif (test__start_subtest(\"metadata_sha_mismatch\"))\n-\t\tmetadata_sha_mismatch();\n-\tif (test__start_subtest(\"metadata_not_exclusive\"))\n-\t\tmetadata_not_exclusive();\n-\tif (test__start_subtest(\"metadata_hash_not_computed\"))\n-\t\tmetadata_hash_not_computed();\n \tif (test__start_subtest(\"signature_enforced\"))\n \t\tsignature_enforced();\n+\tif (test__start_subtest(\"signed_nonexcl_fd_array_rejected\"))\n+\t\tsigned_nonexcl_fd_array_rejected();\n+\tif (test__start_subtest(\"signed_unfrozen_fd_array_rejected\"))\n+\t\tsigned_unfrozen_fd_array_rejected();\n+\tif (test__start_subtest(\"signed_nonarray_fd_array_rejected\"))\n+\t\tsigned_nonarray_fd_array_rejected();\n+\tif (test__start_subtest(\"signed_btf_fd_array_rejected\"))\n+\t\tsigned_btf_fd_array_rejected();\n+\tif (test__start_subtest(\"signed_module_kfunc_rejected\"))\n+\t\tsigned_module_kfunc_rejected();\n+\tif (test__start_subtest(\"signature_failure_logs\"))\n+\t\tsignature_failure_logs();\n \tif (test__start_subtest(\"signature_too_large\"))\n \t\tsignature_too_large();\n \tif (test__start_subtest(\"signature_bad_keyring\"))\n@@ -1114,6 +1782,8 @@ void test_signed_loader(void)\n \t\tmetadata_ctx_initial_value_ignored();\n \tif (test__start_subtest(\"signature_authenticates_insns\"))\n \t\tsignature_authenticates_insns();\n+\tif (test__start_subtest(\"signature_authenticates_metadata\"))\n+\t\tsignature_authenticates_metadata();\n \tif (test__start_subtest(\"hash_requires_frozen\"))\n \t\thash_requires_frozen();\n \tif (test__start_subtest(\"no_update_after_freeze\"))\n@@ -1132,4 +1802,10 @@ void test_signed_loader(void)\n \t\tmap_hash_unsupported_type();\n \tif (test__start_subtest(\"lsm_signature_verdict\"))\n \t\tlsm_signature_verdict();\n+\tif (test__start_subtest(\"signed_no_fd_array\"))\n+\t\tsigned_no_fd_array();\n+\tif (test__start_subtest(\"signed_map_by_fd_rejected\"))\n+\t\tsigned_map_by_fd_rejected();\n+\tif (test__start_subtest(\"signed_sparse_fd_array_rejected\"))\n+\t\tsigned_sparse_fd_array_rejected();\n }\ndiff --git a/tools/testing/selftests/bpf/progs/test_signed_loader.c b/tools/testing/selftests/bpf/progs/test_signed_loader.c\nindex d9a4b85f9391f5..50451a69b99a27 100644\n--- a/tools/testing/selftests/bpf/progs/test_signed_loader.c\n+++ b/tools/testing/selftests/bpf/progs/test_signed_loader.c\n@@ -4,10 +4,11 @@\n \n /*\n * Minimal, map-less program. Driven through libbpf's gen_loader (gen_hash)\n- * by prog_tests/signed_loader.c so the generated light-skeleton loader (with\n- * the emit_signature_match metadata check) can be exercised against good\n- * and tampered metadata. A socket filter needs no load-time attach resolution,\n- * and having no maps keeps the generated loader's ctx trivial (0 maps, 1 prog).\n+ * by prog_tests/signed_loader.c so the generated light-skeleton loader can be\n+ * exercised against good and tampered metadata, which the kernel now verifies\n+ * at load time via the insns||metadata signature. A socket filter needs no\n+ * load-time attach resolution, and having no maps keeps the generated loader's\n+ * ctx trivial (0 maps, 1 prog).\n */\n SEC(\"socket\")\n int probe(void *ctx)\ndiff --git a/tools/testing/selftests/bpf/progs/verifier_map_ptr.c b/tools/testing/selftests/bpf/progs/verifier_map_ptr.c\nindex 1661936598703c..e0a65835c861bb 100644\n--- a/tools/testing/selftests/bpf/progs/verifier_map_ptr.c\n+++ b/tools/testing/selftests/bpf/progs/verifier_map_ptr.c\n@@ -72,14 +72,15 @@ __naked void bpf_map_ptr_write_rejected(void)\n \n /*\n * struct bpf_map starts with the SHA256 hash sha[32] at offset 0 (a readable\n- * byte array), the u32 excl field at offset 32, and the ops pointer at offset\n- * 40. Reading a u32 at offset 41 reaches into the middle of the ops pointer,\n- * i.e. a partial pointer access, which is rejected.\n+ * byte array), followed by the ops pointer at offset 32 and the inner_map_meta\n+ * pointer at offset 40. Reading a u32 at offset 41 reaches into the middle of\n+ * the inner_map_meta pointer, i.e. a partial pointer access, which is\n+ * rejected.\n */\n SEC(\"socket\")\n __description(\"bpf_map_ptr: read non-existent field rejected\")\n __failure\n-__msg(\"cannot access ptr member ops with moff 40 in struct bpf_map with off 41 size 4\")\n+__msg(\"cannot access ptr member inner_map_meta with moff 40 in struct bpf_map with off 41 size 4\")\n __failure_unpriv\n __msg_unpriv(\"access is allowed only to CAP_PERFMON and CAP_SYS_ADMIN\")\n __flag(BPF_F_ANY_ALIGNMENT)\n@@ -97,23 +98,23 @@ __naked void read_non_existent_field_rejected(void)\n }\n \n /*\n- * The u32 excl field spans offsets 32..35 (mend 36). Reading a u32 at offset\n- * 33 starts inside excl but extends past its end, which the verifier rejects\n+ * The sha byte array spans offsets 0..31 (mend 32). Reading a u32 at offset\n+ * 30 starts inside sha but extends past its end, which the verifier rejects\n * as an out-of-bounds scalar access.\n */\n SEC(\"socket\")\n-__description(\"bpf_map_ptr: read beyond excl field rejected\")\n+__description(\"bpf_map_ptr: read beyond sha field rejected\")\n __failure\n-__msg(\"access beyond the end of member excl (mend:36) in struct bpf_map with off 33 size 4\")\n+__msg(\"access beyond the end of member sha (mend:32) in struct bpf_map with off 30 size 4\")\n __failure_unpriv\n __msg_unpriv(\"access is allowed only to CAP_PERFMON and CAP_SYS_ADMIN\")\n __flag(BPF_F_ANY_ALIGNMENT)\n-__naked void read_beyond_excl_field_rejected(void)\n+__naked void read_beyond_sha_field_rejected(void)\n {\n \tasm volatile (\"\t\t\t\t\t\\\n \tr6 = 0;\t\t\t\t\t\t\\\n \tr1 = %[map_array_48b] ll;\t\t\t\\\n-\tr6 = *(u32*)(r1 + 33);\t\t\t\t\\\n+\tr6 = *(u32*)(r1 + 30);\t\t\t\t\\\n \tr0 = 1;\t\t\t\t\t\t\\\n \texit;\t\t\t\t\t\t\\\n \"\t:\n@@ -131,7 +132,7 @@ __naked void ptr_read_ops_field_accepted(void)\n \tasm volatile (\"\t\t\t\t\t\\\n \tr6 = 0;\t\t\t\t\t\t\\\n \tr1 = %[map_array_48b] ll;\t\t\t\\\n-\tr6 = *(u64*)(r1 + 40);\t\t\t\t\\\n+\tr6 = *(u64*)(r1 + 32);\t\t\t\t\\\n \tr0 = 1;\t\t\t\t\t\t\\\n \texit;\t\t\t\t\t\t\\\n \"\t:\n"
} |
| 2/1 | 2026/07/06 20:24 | agent | patch-evaluator |
6mModel:gemini-3.1-pro-preview Args: null Results: {
"EnableConfigs": [
"MODULE_SIG"
],
"FocusSymbols": [
"bpf_prog_verify_signature",
"process_fd_array_continuous",
"process_fd_array",
"fd_array_get_map_idx_continuous",
"fd_array_get_map_idx_sparse",
"fd_array_get_map_idx",
"fd_array_get_btf_continuous",
"fd_array_get_btf_sparse",
"fd_array_get_btf",
"add_fd_from_fd_array",
"bpf_verify_pkcs7_signature"
],
"Reasoning": "The patch introduces BPF program signing verification into the kernel verifier. It modifies the `bpf_check` path to verify PKCS#7 signatures of BPF programs and their metadata maps when `attr-\u003esignature` is provided. It also introduces support for a continuous `fd_array` via `fd_array_cnt`. These are reachable core BPF verifier changes that should be fuzzed.",
"WorthFuzzing": true
}Instruction: You are an expert Linux kernel maintainer. Your job is to review a provided patch series and determine if it makes functional changes to the kernel that should be fuzzed. IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively use your code access tools to examine the actual source code and confirm any assumptions. Return WorthFuzzing=false if the patch only contains: - Modifications to Documentation/, Kconfig files, or code comments. - Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints. - Changes to numeric constants or macros that do not functionally alter execution flow. - Code paths that are impossible to reach in virtualized environments like GCE or QEMU, even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim). If it modifies reachable core kernel logic, drivers, or architectures, use your code search tools to verify the code can be executed, then return WorthFuzzing=true. When returning WorthFuzzing=true, you MUST ALSO: 1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols. Avoid listing generic hot-path functions to prevent skewed test distributions. 2. Identify any specific CONFIG_ options required to properly test this new/modified feature. Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions. Do not list too generic configs (we already have them enabled). Only list those that specifically cover the modified code. List them in the EnableConfigs output array, and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4"). Prefer calling several tools at the same time to save round-trips. Use set-results tool to provide results of the analysis. It must be called exactly once before the final reply. Ignore results of this tool. Prompt: For your convenience, here is the diff of the changes:
commit 9e7deb7daed029fcd9201e54beff5f72e265d60b
Author: syz-cluster <triage@syzkaller.com>
Date: Mon Jul 6 20:24:50 2026 +0000
syz-cluster: applied patch under review
diff --git a/Documentation/bpf/index.rst b/Documentation/bpf/index.rst
index 0d5c6f6592667a..638a00d42bc2a1 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 00000000000000..446b4edbae626c
--- /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``.
diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index ba09795e0bfdb7..2e0f12fb4cea8b 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -299,7 +299,6 @@ struct bpf_map_owner {
struct bpf_map {
u8 sha[SHA256_DIGEST_SIZE];
- u32 excl;
const struct bpf_map_ops *ops;
struct bpf_map *inner_map_meta;
#ifdef CONFIG_SECURITY
diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
index 76b8b7627a1085..317e99b9acc0a4 100644
--- a/include/linux/bpf_verifier.h
+++ b/include/linux/bpf_verifier.h
@@ -898,6 +898,14 @@ struct bpf_scc_info {
struct bpf_liveness;
+struct bpf_fd_array {
+ union {
+ struct bpf_map *map;
+ struct btf *btf;
+ unsigned long val;
+ };
+};
+
/* single container for all structs
* one verifier_env per bpf_check() call
*/
@@ -939,6 +947,7 @@ struct bpf_verifier_env {
bool bypass_spec_v4;
bool seen_direct_write;
bool seen_exception;
+ bool signature;
struct bpf_insn_aux_data *insn_aux_data; /* array of per-insn state */
const struct bpf_line_info *prev_linfo;
struct bpf_verifier_log log;
@@ -989,7 +998,19 @@ struct bpf_verifier_env {
u32 free_list_size;
u32 explored_states_size;
u32 num_backedges;
- bpfptr_t fd_array;
+ /*
+ * The program's fd_array comes in two shapes, told apart by whether
+ * the caller passed fd_array_cnt. They are mutually exclusive:
+ * - continuous (fd_array_cnt given): ->fd_array holds every entry
+ * resolved to its object up front, indexed by fd_array position,
+ * with ->fd_array_cnt slots; ->fd_array_raw is unused.
+ * - sparse (no fd_array_cnt): ->fd_array is NULL, and entries are
+ * read from ->fd_array_raw (the caller's fd_array) and resolved
+ * on the spot at each reference.
+ */
+ struct bpf_fd_array *fd_array;
+ u32 fd_array_cnt;
+ bpfptr_t fd_array_raw;
/* bit mask to keep track of whether a register has been accessed
* since the last time the function state was printed
diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
index 6db306d23b479f..358f2b0ce2bd31 100644
--- a/kernel/bpf/syscall.c
+++ b/kernel/bpf/syscall.c
@@ -40,7 +40,6 @@
#include <linux/tracepoint.h>
#include <linux/overflow.h>
#include <linux/cookie.h>
-#include <linux/verification.h>
#include <linux/btf_ids.h>
#include <net/netfilter/nf_bpf_link.h>
@@ -1599,13 +1598,6 @@ static int map_create_alloc(union bpf_attr *attr, bpfptr_t uattr, struct bpf_ver
err = -EFAULT;
goto free_map;
}
-
- /* See libbpf: emit_signature_match() */
- BUILD_BUG_ON(offsetof(struct bpf_map, excl) != SHA256_DIGEST_SIZE);
- BUILD_BUG_ON(!__same_type(map->excl, u32));
- BUILD_BUG_ON(offsetof(struct bpf_map, sha) != 0);
- BUILD_BUG_ON(!__same_type(map->sha, u8[SHA256_DIGEST_SIZE]));
- map->excl = 1;
} else if (attr->excl_prog_hash_size) {
bpf_log(log, "Invalid excl_prog_hash_size.\n");
err = -EINVAL;
@@ -2886,64 +2878,6 @@ static bool is_perfmon_prog_type(enum bpf_prog_type prog_type)
}
}
-static enum bpf_sig_keyring bpf_classify_keyring(s32 keyring_id)
-{
- switch (keyring_id) {
- case 0:
- return BPF_SIG_KEYRING_BUILTIN;
- case (s32)(unsigned long)VERIFY_USE_SECONDARY_KEYRING:
- return BPF_SIG_KEYRING_SECONDARY;
- case (s32)(unsigned long)VERIFY_USE_PLATFORM_KEYRING:
- return BPF_SIG_KEYRING_PLATFORM;
- default:
- return BPF_SIG_KEYRING_USER;
- }
-}
-
-static int bpf_prog_verify_signature(struct bpf_prog *prog, union bpf_attr *attr,
- bool is_kernel, s32 *keyring_serial)
-{
- 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;
-
- /*
- * Don't attempt to use kmalloc_large or vmalloc for signatures.
- * Practical signature for BPF program should be below this limit.
- */
- if (attr->signature_size > KMALLOC_MAX_CACHE_SIZE)
- return -EINVAL;
-
- 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 -EINVAL;
-
- sig = kvmemdup_bpfptr(usig, attr->signature_size);
- if (IS_ERR(sig)) {
- bpf_key_put(key);
- return PTR_ERR(sig);
- }
-
- 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);
- if (!err)
- *keyring_serial = bpf_key_serial(key);
- bpf_key_put(key);
- kvfree(sig);
- return err;
-}
-
static int bpf_prog_mark_insn_arrays_ready(struct bpf_prog *prog)
{
int err;
@@ -3133,17 +3067,8 @@ static int bpf_prog_load(union bpf_attr *attr, bpfptr_t uattr, struct bpf_log_at
/* 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,
- &prog->aux->sig.keyring_serial);
- if (err)
- goto free_prog;
- prog->aux->sig.keyring_type = bpf_classify_keyring(attr->keyring_id);
- prog->aux->sig.verdict = BPF_SIG_VERIFIED;
- } else {
- prog->aux->sig.keyring_type = BPF_SIG_KEYRING_NONE;
- prog->aux->sig.verdict = BPF_SIG_UNSIGNED;
- }
+ prog->aux->sig.keyring_type = BPF_SIG_KEYRING_NONE;
+ prog->aux->sig.verdict = BPF_SIG_UNSIGNED;
prog->orig_prog = NULL;
prog->jited = 0;
@@ -3189,10 +3114,6 @@ static int bpf_prog_load(union bpf_attr *attr, bpfptr_t uattr, struct bpf_log_at
if (err < 0)
goto free_prog;
- err = security_bpf_prog_load(prog, attr, token, uattr.is_kernel);
- if (err)
- goto free_prog;
-
/* run eBPF verifier */
err = bpf_check(&prog, attr, uattr, attr_log);
if (err < 0)
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index d46f7db20d8f04..65f53128fc33d6 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -22,6 +22,8 @@
#include <linux/ctype.h>
#include <linux/error-injection.h>
#include <linux/bpf_lsm.h>
+#include <linux/security.h>
+#include <linux/verification.h>
#include <linux/btf_ids.h>
#include <linux/poison.h>
#include <linux/module.h>
@@ -2490,6 +2492,83 @@ int bpf_get_kfunc_addr(const struct bpf_prog *prog, u32 func_id,
return 0;
}
+#define BPF_FD_SLOT_BTF 1UL
+
+static void fd_slot_set_map(struct bpf_fd_array *slot, struct bpf_map *map)
+{
+ slot->val = (unsigned long)map;
+}
+
+static void fd_slot_set_btf(struct bpf_fd_array *slot, struct btf *btf)
+{
+ slot->val = (unsigned long)btf | BPF_FD_SLOT_BTF;
+}
+
+static struct bpf_map *fd_slot_map(struct bpf_fd_array slot)
+{
+ if (slot.val & BPF_FD_SLOT_BTF)
+ return NULL;
+ return (struct bpf_map *)slot.val;
+}
+
+static struct btf *fd_slot_btf(struct bpf_fd_array slot)
+{
+ if (!(slot.val & BPF_FD_SLOT_BTF))
+ return NULL;
+ return (struct btf *)(slot.val & ~BPF_FD_SLOT_BTF);
+}
+
+static struct btf *
+fd_array_get_btf_continuous(struct bpf_verifier_env *env, u32 idx)
+{
+ struct btf *btf;
+
+ if (idx >= env->fd_array_cnt) {
+ verbose(env, "kfunc fd_idx %u out of bounds, fd_array_cnt %u\n",
+ idx, env->fd_array_cnt);
+ return ERR_PTR(-EINVAL);
+ }
+ btf = fd_slot_btf(env->fd_array[idx]);
+ if (!btf) {
+ verbose(env, "kfunc fd_idx %u is not a module BTF\n", idx);
+ return ERR_PTR(-EINVAL);
+ }
+ btf_get(btf);
+ return btf;
+}
+
+static struct btf *
+fd_array_get_btf_sparse(struct bpf_verifier_env *env, u32 idx)
+{
+ struct btf *btf;
+ int btf_fd;
+
+ if (copy_from_bpfptr_offset(&btf_fd, env->fd_array_raw,
+ (size_t)idx * sizeof(btf_fd), sizeof(btf_fd)))
+ return ERR_PTR(-EFAULT);
+ btf = btf_get_by_fd(btf_fd);
+ if (IS_ERR(btf)) {
+ verbose(env, "invalid module BTF fd specified\n");
+ return btf;
+ }
+ return btf;
+}
+
+static struct btf *fd_array_get_btf(struct bpf_verifier_env *env, u32 idx)
+{
+ if (env->signature) {
+ verbose(env, "signed program cannot bind any BTF\n");
+ return ERR_PTR(-EACCES);
+ }
+ if (env->fd_array)
+ return fd_array_get_btf_continuous(env, idx);
+ if (!bpfptr_is_null(env->fd_array_raw))
+ return fd_array_get_btf_sparse(env, idx);
+
+ verbose(env, "kfunc offset > 0 without fd_array is invalid\n");
+ return ERR_PTR(-EPROTO);
+}
+
static struct btf *__find_kfunc_desc_btf(struct bpf_verifier_env *env,
s16 offset)
{
@@ -2498,7 +2577,6 @@ static struct btf *__find_kfunc_desc_btf(struct bpf_verifier_env *env,
struct bpf_kfunc_btf *b;
struct module *mod;
struct btf *btf;
- int btf_fd;
tab = env->prog->aux->kfunc_btf_tab;
b = bsearch(&kf_btf, tab->descs, tab->nr_descs,
@@ -2509,22 +2587,9 @@ static struct btf *__find_kfunc_desc_btf(struct bpf_verifier_env *env,
return ERR_PTR(-E2BIG);
}
- if (bpfptr_is_null(env->fd_array)) {
- verbose(env, "kfunc offset > 0 without fd_array is invalid\n");
- return ERR_PTR(-EPROTO);
- }
-
- if (copy_from_bpfptr_offset(&btf_fd, env->fd_array,
- offset * sizeof(btf_fd),
- sizeof(btf_fd)))
- return ERR_PTR(-EFAULT);
-
- btf = btf_get_by_fd(btf_fd);
- if (IS_ERR(btf)) {
- verbose(env, "invalid module BTF fd specified\n");
+ btf = fd_array_get_btf(env, offset);
+ if (IS_ERR(btf))
return btf;
- }
-
if (!btf_is_module(btf)) {
verbose(env, "BTF fd for kfunc is not a module BTF\n");
btf_put(btf);
@@ -17589,6 +17654,11 @@ static int __add_used_btf(struct bpf_verifier_env *env, struct btf *btf)
if (env->used_btfs[i].btf == btf)
goto ret_put;
+ if (env->signature) {
+ verbose(env, "signed program cannot bind any BTF\n");
+ ret = -EACCES;
+ goto ret_put;
+ }
if (env->used_btf_cnt >= MAX_USED_BTFS) {
verbose(env, "The total number of btfs per program has reached the limit of %u\n",
MAX_USED_BTFS);
@@ -17871,6 +17941,12 @@ static int __add_used_map(struct bpf_verifier_env *env, struct bpf_map *map)
if (env->used_maps[i] == map)
return i;
+ if (env->signature &&
+ env->prog->aux->sig.verdict == BPF_SIG_VERIFIED) {
+ verbose(env, "signed program cannot bind map '%s' not covered by the signature\n",
+ map->name);
+ return -EACCES;
+ }
if (env->used_map_cnt >= MAX_USED_MAPS) {
verbose(env, "The total number of maps per program has reached the limit of %u\n",
MAX_USED_MAPS);
@@ -17923,6 +17999,48 @@ static int add_used_map(struct bpf_verifier_env *env, int fd)
return __add_used_map(env, map);
}
+static int fd_array_get_map_idx_continuous(struct bpf_verifier_env *env, u32 idx)
+{
+ struct bpf_map *map;
+
+ if (idx >= env->fd_array_cnt) {
+ verbose(env, "fd_idx %u out of bounds, fd_array_cnt %u\n",
+ idx, env->fd_array_cnt);
+ return -EINVAL;
+ }
+ map = fd_slot_map(env->fd_array[idx]);
+ if (!map) {
+ verbose(env, "fd_idx %u is not a map\n", idx);
+ return -EINVAL;
+ }
+ return __add_used_map(env, map);
+}
+
+static int fd_array_get_map_idx_sparse(struct bpf_verifier_env *env, u32 idx)
+{
+ int fd;
+
+ if (copy_from_bpfptr_offset(&fd, env->fd_array_raw,
+ (size_t)idx * sizeof(fd), sizeof(fd)))
+ return -EFAULT;
+ return add_used_map(env, fd);
+}
+
+static int fd_array_get_map_idx(struct bpf_verifier_env *env, u32 idx)
+{
+ if (env->fd_array)
+ return fd_array_get_map_idx_continuous(env, idx);
+ if (env->signature) {
+ verbose(env, "signed program must bind maps via a continuous fd_array (fd_array_cnt)\n");
+ return -EACCES;
+ }
+ if (!bpfptr_is_null(env->fd_array_raw))
+ return fd_array_get_map_idx_sparse(env, idx);
+
+ verbose(env, "fd_idx without fd_array is invalid\n");
+ return -EPROTO;
+}
+
static int check_alu_fields(struct bpf_verifier_env *env, struct bpf_insn *insn)
{
u8 class = BPF_CLASS(insn->code);
@@ -18140,7 +18258,6 @@ static int check_and_resolve_insns(struct bpf_verifier_env *env)
struct bpf_map *map;
int map_idx;
u64 addr;
- u32 fd;
if (i == insn_cnt - 1 || insn[1].code != 0 ||
insn[1].dst_reg != 0 || insn[1].src_reg != 0 ||
@@ -18192,21 +18309,17 @@ static int check_and_resolve_insns(struct bpf_verifier_env *env)
switch (insn[0].src_reg) {
case BPF_PSEUDO_MAP_IDX_VALUE:
case BPF_PSEUDO_MAP_IDX:
- if (bpfptr_is_null(env->fd_array)) {
- verbose(env, "fd_idx without fd_array is invalid\n");
- return -EPROTO;
- }
- if (copy_from_bpfptr_offset(&fd, env->fd_array,
- insn[0].imm * sizeof(fd),
- sizeof(fd)))
- return -EFAULT;
+ map_idx = fd_array_get_map_idx(env, insn[0].imm);
break;
default:
- fd = insn[0].imm;
+ if (env->signature) {
+ verbose(env, "signed program cannot reference a map by fd, only via fd_array index\n");
+ return -EINVAL;
+ }
+ map_idx = add_used_map(env, insn[0].imm);
break;
}
- map_idx = add_used_map(env, fd);
if (map_idx < 0)
return map_idx;
map = env->used_maps[map_idx];
@@ -19481,7 +19594,7 @@ struct btf *bpf_get_btf_vmlinux(void)
* this case expect that every file descriptor in the array is either a map or
* a BTF. Everything else is considered to be trash.
*/
-static int add_fd_from_fd_array(struct bpf_verifier_env *env, int fd)
+static int add_fd_from_fd_array(struct bpf_verifier_env *env, u32 idx, int fd)
{
struct bpf_map *map;
struct btf *btf;
@@ -19493,51 +19606,83 @@ static int add_fd_from_fd_array(struct bpf_verifier_env *env, int fd)
err = __add_used_map(env, map);
if (err < 0)
return err;
+ fd_slot_set_map(&env->fd_array[idx], map);
return 0;
}
btf = __btf_get_by_fd(f);
if (!IS_ERR(btf)) {
btf_get(btf);
- return __add_used_btf(env, btf);
+ err = __add_used_btf(env, btf);
+ if (err < 0)
+ return err;
+ fd_slot_set_btf(&env->fd_array[idx], btf);
+ return 0;
}
verbose(env, "fd %d is not pointing to valid bpf_map or btf\n", fd);
return PTR_ERR(map);
}
-static int process_fd_array(struct bpf_verifier_env *env, union bpf_attr *attr, bpfptr_t uattr)
+/*
+ * A continuous fd_array is resolved into an in-memory cache with one slot
+ * per entry. The bound here is deliberately generous and not derived from
+ * the per-program object limits: Duplicate entries /are/ permitted, and
+ * the number of distinct maps and BTFs a program can bind is enforced when
+ * each entry is resolved by __add_used_map() and __add_used_btf().
+ */
+#define MAX_FD_ARRAY_CNT 4096
+
+static int process_fd_array_continuous(struct bpf_verifier_env *env,
+ bpfptr_t fd_array, u32 cnt)
{
- size_t size = sizeof(int);
- int ret;
- int fd;
+ int fd, ret;
u32 i;
- env->fd_array = make_bpfptr(attr->fd_array, uattr.is_kernel);
-
- /*
- * The only difference between old (no fd_array_cnt is given) and new
- * APIs is that in the latter case the fd_array is expected to be
- * continuous and is scanned for map fds right away
- */
- if (!attr->fd_array_cnt)
- return 0;
-
- /* Check for integer overflow */
- if (attr->fd_array_cnt >= (U32_MAX / size)) {
- verbose(env, "fd_array_cnt is too big (%u)\n", attr->fd_array_cnt);
- return -EINVAL;
+ if (cnt > MAX_FD_ARRAY_CNT) {
+ verbose(env, "fd_array has too many entries (%u, max %u)\n",
+ cnt, MAX_FD_ARRAY_CNT);
+ return -E2BIG;
}
- for (i = 0; i < attr->fd_array_cnt; i++) {
- if (copy_from_bpfptr_offset(&fd, env->fd_array, i * size, size))
+ env->fd_array = kvcalloc(cnt, sizeof(*env->fd_array),
+ GFP_KERNEL_ACCOUNT);
+ if (!env->fd_array)
+ return -ENOMEM;
+ env->fd_array_cnt = cnt;
+ for (i = 0; i < cnt; i++) {
+ if (copy_from_bpfptr_offset(&fd, fd_array,
+ (size_t)i * sizeof(fd), sizeof(fd)))
return -EFAULT;
-
- ret = add_fd_from_fd_array(env, fd);
+ ret = add_fd_from_fd_array(env, i, fd);
if (ret)
return ret;
}
+ return 0;
+}
+static int process_fd_array(struct bpf_verifier_env *env,
+ union bpf_attr *attr, bpfptr_t uattr)
+{
+ bpfptr_t fd_array = make_bpfptr(attr->fd_array, uattr.is_kernel);
+
+ if (bpfptr_is_null(fd_array)) {
+ if (attr->fd_array_cnt) {
+ verbose(env, "fd_array_cnt %u without fd_array is invalid\n",
+ attr->fd_array_cnt);
+ return -EINVAL;
+ }
+ return 0;
+ }
+ /*
+ * New API: the caller passes fd_array_cnt and a continuous array that
+ * is resolved and bound up front. Legacy API (no fd_array_cnt): keep
+ * the caller's array and resolve entries on the spot at each reference.
+ */
+ if (attr->fd_array_cnt)
+ return process_fd_array_continuous(env, fd_array,
+ attr->fd_array_cnt);
+ env->fd_array_raw = fd_array;
return 0;
}
@@ -19752,6 +19897,139 @@ int bpf_fixup_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
return 0;
}
+static enum bpf_sig_keyring bpf_classify_keyring(s32 keyring_id)
+{
+ switch (keyring_id) {
+ case 0:
+ return BPF_SIG_KEYRING_BUILTIN;
+ case (s32)(unsigned long)VERIFY_USE_SECONDARY_KEYRING:
+ return BPF_SIG_KEYRING_SECONDARY;
+ case (s32)(unsigned long)VERIFY_USE_PLATFORM_KEYRING:
+ return BPF_SIG_KEYRING_PLATFORM;
+ default:
+ return BPF_SIG_KEYRING_USER;
+ }
+}
+
+/*
+ * Verify the PKCS#7 signature of a loaded program. Called from bpf_check()
+ * once the program's metadata maps have been resolved into used_maps, so
+ * the exact maps folded into the signature are the ones the program binds.
+ *
+ * The signature covers the instructions followed by the frozen contents of
+ * each map, in @maps order: insns || map_0 || map_1 || [...]. On success the
+ * verdict and keyring info are recorded on prog->aux.
+ */
+static int bpf_prog_verify_signature(struct bpf_verifier_env *env,
+ union bpf_attr *attr, bool is_kernel)
+{
+ bpfptr_t usig = make_bpfptr(attr->signature, is_kernel);
+ struct bpf_dynptr_kern sig_ptr, data_ptr;
+ struct bpf_prog *prog = env->prog;
+ struct bpf_map **maps = env->used_maps;
+ struct bpf_key *key = NULL;
+ void *sig, *data = NULL;
+ u32 map_cnt = env->used_map_cnt;
+ u32 i, off, insns_sz;
+ u64 data_sz;
+ int err = 0;
+
+ /*
+ * Don't attempt to use kmalloc_large or vmalloc for signatures.
+ * Practical signature for BPF program should be below this limit.
+ */
+ if (attr->signature_size > KMALLOC_MAX_CACHE_SIZE)
+ return -EINVAL;
+ 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) {
+ verbose(env, "cannot resolve signing keyring with keyring_id %d\n",
+ attr->keyring_id);
+ return -EINVAL;
+ }
+
+ sig = kvmemdup_bpfptr(usig, attr->signature_size);
+ if (IS_ERR(sig)) {
+ bpf_key_put(key);
+ return PTR_ERR(sig);
+ }
+
+ insns_sz = prog->len * sizeof(struct bpf_insn);
+ data_sz = insns_sz;
+ for (i = 0; i < map_cnt; i++) {
+ struct bpf_map *map = maps[i];
+
+ if (map->map_type != BPF_MAP_TYPE_ARRAY ||
+ !map->ops->map_direct_value_addr) {
+ verbose(env, "signed program metadata map '%s' must be an array\n",
+ map->name);
+ err = -EINVAL;
+ goto out;
+ }
+ if (!READ_ONCE(map->frozen)) {
+ verbose(env, "signed program metadata map '%s' must be frozen\n",
+ map->name);
+ err = -EPERM;
+ goto out;
+ }
+ if (!map->excl_prog_sha) {
+ verbose(env, "signed program metadata map '%s' must be exclusive\n",
+ map->name);
+ err = -EPERM;
+ goto out;
+ }
+ data_sz += map->value_size;
+ }
+ if (bpf_dynptr_check_size(data_sz)) {
+ verbose(env, "signed payload too large: %llu bytes\n", data_sz);
+ err = -E2BIG;
+ goto out;
+ }
+ data = kvmalloc(data_sz, GFP_KERNEL_ACCOUNT | __GFP_ZERO);
+ if (!data) {
+ err = -ENOMEM;
+ goto out;
+ }
+ memcpy(data, prog->insnsi, insns_sz);
+ off = insns_sz;
+ for (i = 0; i < map_cnt; i++) {
+ struct bpf_map *map = maps[i];
+ u64 addr;
+
+ err = map->ops->map_direct_value_addr(map, &addr, 0);
+ if (err) {
+ verbose(env, "failed to read signed metadata map '%s': %d\n",
+ map->name, err);
+ goto out;
+ }
+ memcpy(data + off, (void *)(unsigned long)addr,
+ map->value_size);
+ off += map->value_size;
+ }
+
+ bpf_dynptr_init(&data_ptr, data, BPF_DYNPTR_TYPE_LOCAL, 0, data_sz);
+ bpf_dynptr_init(&sig_ptr, sig, BPF_DYNPTR_TYPE_LOCAL, 0,
+ attr->signature_size);
+
+ err = bpf_verify_pkcs7_signature((struct bpf_dynptr *)&data_ptr,
+ (struct bpf_dynptr *)&sig_ptr, key);
+ if (err) {
+ verbose(env, "signature verification failed: %d\n", err);
+ } else {
+ verbose(env, "signature verification passed\n");
+ prog->aux->sig.keyring_serial = bpf_key_serial(key);
+ prog->aux->sig.keyring_type = bpf_classify_keyring(attr->keyring_id);
+ prog->aux->sig.verdict = BPF_SIG_VERIFIED;
+ }
+out:
+ kvfree(data);
+ bpf_key_put(key);
+ kvfree(sig);
+ return err;
+}
+
int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,
struct bpf_log_attr *attr_log)
{
@@ -19774,18 +20052,6 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,
return -ENOMEM;
env->bt.env = env;
-
- len = (*prog)->len;
- env->insn_aux_data =
- vzalloc(array_size(sizeof(struct bpf_insn_aux_data), len));
- ret = -ENOMEM;
- if (!env->insn_aux_data)
- goto err_free_env;
- for (i = 0; i < len; i++)
- env->insn_aux_data[i].orig_idx = i;
- env->succ = bpf_iarray_realloc(NULL, 2);
- if (!env->succ)
- goto err_free_env;
env->prog = *prog;
env->ops = bpf_verifier_ops[env->prog->type];
@@ -19794,22 +20060,51 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,
env->bypass_spec_v1 = bpf_bypass_spec_v1(env->prog->aux->token);
env->bypass_spec_v4 = bpf_bypass_spec_v4(env->prog->aux->token);
env->bpf_capable = is_priv = bpf_token_capable(env->prog->aux->token, CAP_BPF);
-
- bpf_get_btf_vmlinux();
-
- /* grab the mutex to protect few globals used by verifier */
- if (!is_priv)
- mutex_lock(&bpf_verifier_lock);
+ env->signature = attr->signature;
/* user could have requested verbose verifier output
* and supplied buffer to store the verification trace
*/
ret = bpf_vlog_init(&env->log, attr_log->level, attr_log->ubuf, attr_log->size);
if (ret)
- goto err_unlock;
+ goto err_free_env;
+ if (env->signature) {
+ ret = bpf_prog_calc_tag(env->prog);
+ if (ret < 0)
+ goto err_prep;
+ }
ret = process_fd_array(env, attr, uattr);
if (ret)
+ goto err_prep;
+
+ if (env->signature) {
+ ret = bpf_prog_verify_signature(env, attr, uattr.is_kernel);
+ if (ret)
+ goto err_prep;
+ }
+
+ ret = security_bpf_prog_load(env->prog, attr, env->prog->aux->token,
+ uattr.is_kernel);
+ if (ret)
+ goto err_prep;
+
+ bpf_get_btf_vmlinux();
+
+ /* grab the mutex to protect few globals used by verifier */
+ if (!is_priv)
+ mutex_lock(&bpf_verifier_lock);
+
+ len = env->prog->len;
+ env->insn_aux_data =
+ vzalloc(array_size(sizeof(struct bpf_insn_aux_data), len));
+ ret = -ENOMEM;
+ if (!env->insn_aux_data)
+ goto skip_full_check;
+ for (i = 0; i < len; i++)
+ env->insn_aux_data[i].orig_idx = i;
+ env->succ = bpf_iarray_realloc(NULL, 2);
+ if (!env->succ)
goto skip_full_check;
mark_verifier_state_clean(env);
@@ -20033,17 +20328,26 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,
*prog = env->prog;
module_put(env->attach_btf_mod);
-err_unlock:
if (!is_priv)
mutex_unlock(&bpf_verifier_lock);
- bpf_clear_insn_aux_data(env, 0, env->prog->len);
+ goto err_free_env;
+err_prep:
+ err = bpf_log_attr_finalize(attr_log, &env->log);
+ if (err)
+ ret = err;
+ release_insn_arrays(env);
+ release_maps(env);
+ release_btfs(env);
err_free_env:
+ if (env->insn_aux_data)
+ bpf_clear_insn_aux_data(env, 0, env->prog->len);
+ vfree(env->insn_aux_data);
+ kvfree(env->fd_array);
bpf_stack_liveness_free(env);
kvfree(env->cfg.insn_postorder);
kvfree(env->scc_info);
kvfree(env->succ);
kvfree(env->gotox_tmp_buf);
- vfree(env->insn_aux_data);
kvfree(env);
return ret;
}
diff --git a/tools/bpf/bpftool/gen.c b/tools/bpf/bpftool/gen.c
index 6ae7262ebe0c1d..a01d06d22d1a3b 100644
--- a/tools/bpf/bpftool/gen.c
+++ b/tools/bpf/bpftool/gen.c
@@ -793,6 +793,8 @@ static int gen_trace(struct bpf_object *obj, const char *obj_name, const char *h
if (sign_progs) {
sopts.insns = opts.insns;
sopts.insns_sz = opts.insns_sz;
+ sopts.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;
diff --git a/tools/bpf/bpftool/sign.c b/tools/bpf/bpftool/sign.c
index f9b742f4bb104b..88726a6db6d0e9 100644
--- a/tools/bpf/bpftool/sign.c
+++ b/tools/bpf/bpftool/sign.c
@@ -135,9 +135,21 @@ int bpftool_prog_sign(struct bpf_load_and_run_opts *opts)
CMS_ContentInfo *cms = NULL;
long actual_sig_len = 0;
X509 *x509 = NULL;
+ void *data = NULL;
+ size_t data_sz;
int err = 0;
- bd_in = BIO_new_mem_buf(opts->insns, opts->insns_sz);
+ data_sz = (size_t)opts->insns_sz + opts->data_sz;
+ data = malloc(data_sz);
+ if (!data) {
+ err = -ENOMEM;
+ goto cleanup;
+ }
+ memcpy(data, opts->insns, opts->insns_sz);
+ if (opts->data_sz)
+ memcpy((char *)data + opts->insns_sz, opts->data, opts->data_sz);
+
+ bd_in = BIO_new_mem_buf(data, data_sz);
if (!bd_in) {
err = -ENOMEM;
goto cleanup;
@@ -175,10 +187,13 @@ int bpftool_prog_sign(struct bpf_load_and_run_opts *opts)
goto cleanup;
}
- EVP_Digest(opts->insns, opts->insns_sz, opts->excl_prog_hash,
- &opts->excl_prog_hash_sz, EVP_sha256(), NULL);
+ if (EVP_Digest(opts->insns, opts->insns_sz, opts->excl_prog_hash,
+ &opts->excl_prog_hash_sz, EVP_sha256(), NULL) != 1) {
+ err = -EIO;
+ goto cleanup;
+ }
- bd_out = BIO_new(BIO_s_mem());
+ bd_out = BIO_new(BIO_s_mem());
if (!bd_out) {
err = -ENOMEM;
goto cleanup;
@@ -212,6 +227,7 @@ int bpftool_prog_sign(struct bpf_load_and_run_opts *opts)
X509_free(x509);
EVP_PKEY_free(private_key);
BIO_free(bd_in);
+ free(data);
DISPLAY_OSSL_ERR(err < 0);
return err;
}
diff --git a/tools/lib/bpf/bpf_gen_internal.h b/tools/lib/bpf/bpf_gen_internal.h
index 49af4260b8e6b7..04256918775217 100644
--- a/tools/lib/bpf/bpf_gen_internal.h
+++ b/tools/lib/bpf/bpf_gen_internal.h
@@ -51,7 +51,6 @@ 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 c7f2d2ac7bb35f..6e3dd524276183 100644
--- a/tools/lib/bpf/gen_loader.c
+++ b/tools/lib/bpf/gen_loader.c
@@ -111,7 +111,6 @@ static void emit2(struct bpf_gen *gen, struct bpf_insn insn1, struct bpf_insn in
static int add_data(struct bpf_gen *gen, const void *data, __u32 size);
static void emit_sys_close_blob(struct bpf_gen *gen, int blob_off);
-static void emit_signature_match(struct bpf_gen *gen);
void bpf_gen__init(struct bpf_gen *gen, int log_level, int nr_progs, int nr_maps)
{
@@ -154,8 +153,6 @@ void bpf_gen__init(struct bpf_gen *gen, int log_level, int nr_progs, int nr_maps
/* R7 contains the error code from sys_bpf. Copy it into R0 and exit. */
emit(gen, BPF_MOV64_REG(BPF_REG_0, BPF_REG_7));
emit(gen, BPF_EXIT_INSN());
- if (OPTS_GET(gen->opts, gen_hash, false))
- emit_signature_match(gen);
}
static int add_data(struct bpf_gen *gen, const void *data, __u32 size)
@@ -377,8 +374,6 @@ static void emit_sys_close_blob(struct bpf_gen *gen, int blob_off)
__emit_sys_close(gen);
}
-static void compute_sha_update_offsets(struct bpf_gen *gen);
-
int bpf_gen__finish(struct bpf_gen *gen, int nr_progs, int nr_maps)
{
int i;
@@ -408,9 +403,6 @@ int bpf_gen__finish(struct bpf_gen *gen, int nr_progs, int nr_maps)
if (!gen->error) {
struct gen_loader_opts *opts = gen->opts;
- if (OPTS_GET(opts, gen_hash, false))
- compute_sha_update_offsets(gen);
-
opts->insns = gen->insn_start;
opts->insns_sz = gen->insn_cur - gen->insn_start;
opts->data = gen->data_start;
@@ -460,22 +452,6 @@ void bpf_gen__free(struct bpf_gen *gen)
_val; \
})
-static void compute_sha_update_offsets(struct bpf_gen *gen)
-{
- __u64 sha[SHA256_DWORD_SIZE];
- __u64 sha_dw;
- int i;
-
- libbpf_sha256(gen->data_start, gen->data_cur - gen->data_start, (__u8 *)sha);
- 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;
- }
-}
-
void bpf_gen__load_btf(struct bpf_gen *gen, const void *btf_raw_data,
__u32 btf_raw_size)
{
@@ -557,8 +533,9 @@ void bpf_gen__map_create(struct bpf_gen *gen,
* Conditionally update max_entries from the host-supplied loader
* ctx. This sizes the map at runtime, but for a signed loader
* (gen_hash) it would let an untrusted host re-dimension the
- * program's maps after emit_signature_match(), outside what the
- * signature attests to. Keep the signer-provided max_entries
+ * program's maps, outside what the signature attests to: the
+ * metadata blob is covered by the program signature and verified
+ * by the kernel at load time. Keep the signer-provided max_entries
* baked into the blob in that case.
*/
if (map_idx >= 0 && !OPTS_GET(gen->opts, gen_hash, false))
@@ -596,45 +573,6 @@ void bpf_gen__map_create(struct bpf_gen *gen,
emit_sys_close_stack(gen, stack_off(inner_map_fd));
}
-static void emit_signature_match(struct bpf_gen *gen)
-{
- __s64 off;
- int i;
-
- /*
- * Reject if the metadata map is not exclusive. Without exclusivity
- * the cached map->sha[] verified above can be stale: another BPF
- * program with map access could have mutated the contents between
- * BPF_OBJ_GET_INFO_BY_FD and loader execution.
- */
- emit2(gen, BPF_LD_IMM64_RAW_FULL(BPF_REG_1, BPF_PSEUDO_MAP_IDX,
- 0, 0, 0, 0));
- emit(gen, BPF_LDX_MEM(BPF_W, BPF_REG_2, BPF_REG_1, SHA256_DIGEST_LENGTH));
- off = -(gen->insn_cur - gen->insn_start - gen->cleanup_label) / 8 - 2;
- if (is_simm16(off)) {
- emit(gen, BPF_MOV64_IMM(BPF_REG_7, -EINVAL));
- emit(gen, BPF_JMP_IMM(BPF_JNE, BPF_REG_2, 1, off));
- } else {
- gen->error = -ERANGE;
- }
-
- for (i = 0; i < SHA256_DWORD_SIZE; i++) {
- emit2(gen, BPF_LD_IMM64_RAW_FULL(BPF_REG_1, BPF_PSEUDO_MAP_IDX,
- 0, 0, 0, 0));
- emit(gen, BPF_LDX_MEM(BPF_DW, BPF_REG_2, BPF_REG_1, i * sizeof(__u64)));
- gen->hash_insn_offset[i] = gen->insn_cur - gen->insn_start;
- emit2(gen, BPF_LD_IMM64_RAW_FULL(BPF_REG_3, 0, 0, 0, 0, 0));
-
- off = -(gen->insn_cur - gen->insn_start - gen->cleanup_label) / 8 - 2;
- if (is_simm16(off)) {
- emit(gen, BPF_MOV64_IMM(BPF_REG_7, -EINVAL));
- emit(gen, BPF_JMP_REG(BPF_JNE, BPF_REG_2, BPF_REG_3, off));
- } else {
- gen->error = -ERANGE;
- }
- }
-}
-
void bpf_gen__record_attach_target(struct bpf_gen *gen, const char *attach_name,
enum bpf_attach_type type)
{
@@ -1211,10 +1149,10 @@ void bpf_gen__map_update_elem(struct bpf_gen *gen, int map_idx, void *pvalue,
* }
*
* The runtime initial_value comes from the host-supplied loader
- * ctx and would overwrite the blob value after emit_signature_match()
- * has already validated map->sha[]. For a signed loader (gen_hash)
- * the attested blob value must be authoritative, so skip the override
- * and leave the hashed value in place.
+ * ctx and would overwrite the blob value that the program signature
+ * covers and the kernel verifies at load time. For a signed loader
+ * (gen_hash) the attested blob value must be authoritative, so skip
+ * the override and leave the signed value in place.
*/
if (!OPTS_GET(gen->opts, gen_hash, false)) {
emit(gen, BPF_LDX_MEM(BPF_DW, BPF_REG_3, BPF_REG_6,
diff --git a/tools/lib/bpf/libbpf_internal.h b/tools/lib/bpf/libbpf_internal.h
index 04cd303fb5a879..d5b7db703b3fe9 100644
--- a/tools/lib/bpf/libbpf_internal.h
+++ b/tools/lib/bpf/libbpf_internal.h
@@ -768,7 +768,6 @@ 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)
void libbpf_sha256(const void *data, size_t len, __u8 out[SHA256_DIGEST_LENGTH]);
int probe_sys_bpf_ext(void);
diff --git a/tools/lib/bpf/skel_internal.h b/tools/lib/bpf/skel_internal.h
index 74503d358bc8a5..53fee53d36d512 100644
--- a/tools/lib/bpf/skel_internal.h
+++ b/tools/lib/bpf/skel_internal.h
@@ -18,10 +18,6 @@
#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
@@ -320,25 +316,6 @@ static inline int skel_link_create(int prog_fd, int target_fd,
return skel_sys_bpf(BPF_LINK_CREATE, &attr, attr_sz);
}
-static inline int skel_obj_get_info_by_fd(int fd)
-{
- const size_t attr_sz = offsetofend(union bpf_attr, info);
- __u8 sha[SHA256_DIGEST_LENGTH];
- struct bpf_map_info info;
- __u32 info_len = sizeof(info);
- union bpf_attr attr;
-
- memset(&info, 0, sizeof(info));
- info.hash = (long) &sha;
- info.hash_size = SHA256_DIGEST_LENGTH;
-
- memset(&attr, 0, attr_sz);
- attr.info.bpf_fd = fd;
- attr.info.info = (long) &info;
- attr.info.info_len = info_len;
- return skel_sys_bpf(BPF_OBJ_GET_INFO_BY_FD, &attr, attr_sz);
-}
-
static inline int skel_map_freeze(int fd)
{
const size_t attr_sz = offsetofend(union bpf_attr, map_fd);
@@ -384,12 +361,6 @@ static inline int bpf_load_and_run(struct bpf_load_and_run_opts *opts)
set_err;
goto out;
}
- err = skel_obj_get_info_by_fd(map_fd);
- if (err < 0) {
- opts->errstr = "failed to fetch obj info";
- set_err;
- goto out;
- }
#endif
memset(&attr, 0, prog_load_attr_sz);
@@ -400,6 +371,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;
+ if (opts->signature)
+ attr.fd_array_cnt = 1;
#else
if (opts->signature || opts->signature_sz)
pr_warn("signatures are not supported from bpf_preload\n");
diff --git a/tools/testing/selftests/bpf/prog_tests/signed_loader.c b/tools/testing/selftests/bpf/prog_tests/signed_loader.c
index 5fc417e31fc615..56d1f072de4c17 100644
--- a/tools/testing/selftests/bpf/prog_tests/signed_loader.c
+++ b/tools/testing/selftests/bpf/prog_tests/signed_loader.c
@@ -11,6 +11,8 @@
#include <linux/keyctl.h>
#include <linux/bpf.h>
+#include <bpf/btf.h>
+
#include "bpf/libbpf_internal.h" /* for libbpf_sha256() */
#include "bpf/skel_internal.h" /* for loader ctx layout (bpf_loader_ctx etc) */
@@ -19,8 +21,6 @@
#include "test_signed_loader_data.skel.h"
#include "test_signed_loader_lsm.skel.h"
-#define SIG_MATCH_INSNS 33 /* excl (5) + 4 * sha-dword (7) */
-
enum {
BPF_SIG_UNSIGNED = 0,
BPF_SIG_VERIFIED,
@@ -35,7 +35,8 @@ enum {
};
static int load_loader(const void *insns, __u32 insns_sz, int map_fd,
- const void *sig, __u32 sig_sz, __s32 keyring_id)
+ const void *sig, __u32 sig_sz, __s32 keyring_id,
+ __u32 fd_array_cnt)
{
union bpf_attr attr;
int fd;
@@ -52,6 +53,7 @@ static int load_loader(const void *insns, __u32 insns_sz, int map_fd,
attr.signature_size = sig_sz;
attr.keyring_id = keyring_id;
}
+ attr.fd_array_cnt = fd_array_cnt;
memcpy(attr.prog_name, "__loader.prog", sizeof("__loader.prog"));
fd = syscall(__NR_bpf, BPF_PROG_LOAD, &attr,
offsetofend(union bpf_attr, keyring_id));
@@ -62,14 +64,12 @@ static int run_gen_loader(const void *insns, __u32 insns_sz,
const void *data, __u32 data_sz,
const void *excl, __u32 excl_sz,
const void *sig, __u32 sig_sz,
- bool get_hash, void *ctx, __u32 ctx_sz, bool *loader_ran)
+ void *ctx, __u32 ctx_sz, bool *loader_ran)
{
LIBBPF_OPTS(bpf_map_create_opts, mopts,
.excl_prog_hash = excl,
.excl_prog_hash_size = excl_sz);
- __u8 hbuf[SHA256_DIGEST_LENGTH];
- struct bpf_map_info info;
- __u32 ilen = sizeof(info), key = 0;
+ __u32 key = 0;
union bpf_attr attr;
int map_fd, prog_fd, ret;
@@ -87,15 +87,6 @@ static int run_gen_loader(const void *insns, __u32 insns_sz,
ret = -errno;
goto out_map;
}
- if (get_hash) {
- memset(&info, 0, sizeof(info));
- info.hash = ptr_to_u64(hbuf);
- info.hash_size = sizeof(hbuf);
- if (bpf_map_get_info_by_fd(map_fd, &info, &ilen)) {
- ret = -errno;
- goto out_map;
- }
- }
memset(&attr, 0, sizeof(attr));
attr.prog_type = BPF_PROG_TYPE_SYSCALL;
@@ -108,6 +99,7 @@ static int run_gen_loader(const void *insns, __u32 insns_sz,
attr.signature = ptr_to_u64(sig);
attr.signature_size = sig_sz;
attr.keyring_id = KEY_SPEC_SESSION_KEYRING;
+ attr.fd_array_cnt = 1;
}
memcpy(attr.prog_name, "__loader.prog", sizeof("__loader.prog"));
prog_fd = syscall(__NR_bpf, BPF_PROG_LOAD, &attr,
@@ -236,79 +228,6 @@ static int sign_buf(const char *dir, const void *buf, __u32 len,
return ret;
}
-static void check_sig_match_shape(const struct bpf_insn *in, int n)
-{
- int a = -1, cleanup = -1, i, base, t, br[5], nb = 0;
-
- /* BPF_PSEUDO_MAP_IDX (the struct bpf_map * form) is used only here. */
- for (i = 0; i + 1 < n; i++) {
- if (in[i].code == (BPF_LD | BPF_IMM | BPF_DW) &&
- in[i].src_reg == BPF_PSEUDO_MAP_IDX) {
- a = i;
- break;
- }
- }
- if (!ASSERT_GE(a, 0, "emit_signature_match present"))
- return;
- if (!ASSERT_LE(a + SIG_MATCH_INSNS, n, "block fits in program"))
- return;
-
- /* excl check: r2 = *(u32 *)(map + 32); if r2 != 1 goto cleanup */
- ASSERT_EQ(in[a + 2].code, (BPF_LDX | BPF_MEM | BPF_W), "excl load width");
- ASSERT_EQ(in[a + 2].off, SHA256_DIGEST_LENGTH, "excl field offset");
- ASSERT_EQ(in[a + 4].code, (BPF_JMP | BPF_JNE | BPF_K), "excl branch op");
- ASSERT_EQ(in[a + 4].imm, 1, "excl compared to 1");
- br[nb++] = a + 4;
-
- /* 4 sha-dword checks: r2 = *(u64 *)(map + i*8); if r2 != r3 goto cleanup */
- for (i = 0; i < 4; i++) {
- base = a + 5 + i * 7;
- ASSERT_EQ(in[base + 2].code, (BPF_LDX | BPF_MEM | BPF_DW), "sha load width");
- ASSERT_EQ(in[base + 2].off, i * 8, "sha dword offset");
- ASSERT_EQ(in[base + 3].code, (BPF_LD | BPF_IMM | BPF_DW), "sha imm64 (H_meta)");
- ASSERT_EQ(in[base + 6].code, (BPF_JMP | BPF_JNE | BPF_X), "sha branch op");
- br[nb++] = base + 6;
- }
-
- /*
- * Locate the real cleanup label so we can pin the exact jump target,
- * not just "some backward label". bpf_gen__init() emits the cleanup
- * block as a prog-fd close loop whose first instruction is the label
- * every error branch jumps to.
- */
- for (i = 0; i + 2 < a; i++) {
- if (in[i].code == (BPF_LDX | BPF_MEM | BPF_W) &&
- in[i].dst_reg == BPF_REG_1 && in[i].src_reg == BPF_REG_10 &&
- in[i + 1].code == (BPF_JMP | BPF_JSLE | BPF_K) &&
- in[i + 1].dst_reg == BPF_REG_1 && in[i + 1].imm == 0 &&
- in[i + 1].off == 1 &&
- in[i + 2].code == (BPF_JMP | BPF_CALL) &&
- in[i + 2].imm == BPF_FUNC_sys_close) {
- cleanup = i;
- break;
- }
- }
- if (!ASSERT_GE(cleanup, 0, "cleanup label located"))
- return;
- for (i = 0; i < nb; i++) {
- t = br[i] + 1 + in[br[i]].off;
- ASSERT_EQ(t, cleanup, "sig-match lands on cleanup");
- }
- /*
- * Same invariant for every other cleanup-bound jump in the program:
- * emit_check_err() is the only source of "if (r7 < 0) goto cleanup",
- * so each of those must also resolve exactly to cleanup.
- */
- for (i = 0, t = 0; i < n; i++) {
- if (in[i].code != (BPF_JMP | BPF_JSLT | BPF_K) ||
- in[i].dst_reg != BPF_REG_7 || in[i].imm != 0 || in[i].off >= 0)
- continue;
- ASSERT_EQ(i + 1 + in[i].off, cleanup, "err-check lands on cleanup");
- t++;
- }
- ASSERT_GT(t, 0, "found emit_check_err jumps");
-}
-
struct gen_loader_fixture {
struct test_signed_loader *skel;
struct gen_loader_opts gopts;
@@ -372,16 +291,6 @@ static void gen_loader_fixture_fini(struct gen_loader_fixture *f)
test_signed_loader__destroy(f->skel);
}
-static void metadata_check_shape(void)
-{
- struct gen_loader_fixture f;
-
- if (gen_loader_fixture_init(&f) == 0)
- check_sig_match_shape((const struct bpf_insn *)f.gopts.insns,
- f.gopts.insns_sz / sizeof(struct bpf_insn));
- gen_loader_fixture_fini(&f);
-}
-
static void metadata_match(void)
{
struct gen_loader_fixture f;
@@ -391,94 +300,263 @@ static void metadata_match(void)
if (gen_loader_fixture_init(&f) == 0) {
r = run_gen_loader(f.gopts.insns, f.gopts.insns_sz, f.blob,
f.data_sz, f.excl, sizeof(f.excl), NULL, 0,
- true, f.ctx, f.ctx_sz, &ran);
+ f.ctx, f.ctx_sz, &ran);
ASSERT_TRUE(ran, "loader ran");
ASSERT_EQ(r, 0, "honest loader retval");
}
gen_loader_fixture_fini(&f);
}
-static void metadata_sha_mismatch(void)
+static void signature_enforced(void)
{
+ static const __u8 junk[64] = { 0x30, 0x42, 0x13, 0x37, };
struct gen_loader_fixture f;
- bool ran;
- int r;
+ int fd;
if (gen_loader_fixture_init(&f) == 0) {
/*
- * blob[0] lives in the loader's fd_array scratch (first add_data in
- * bpf_gen__init); a 0-map program never reads it, so flipping it
- * changes only map->sha. The metadata check is the only thing that
- * can notice -> isolates emit_signature_match.
+ * A present-but-invalid signature (the cert bytes are not a
+ * PKCS#7 signature) must be rejected at load: the signature
+ * path is honored, not ignored. (The valid path is covered by
+ * the signed lskels.) Pin -EBADMSG, the PKCS#7 parse failure:
+ * a looser fd < 0 check could also be satisfied by the sparse
+ * fd_array rejection (-EACCES) that the loader's map reference
+ * would trip even if the signature were silently ignored.
*/
- f.blob[0] ^= 0xff;
- r = run_gen_loader(f.gopts.insns, f.gopts.insns_sz, f.blob,
- f.data_sz, f.excl, sizeof(f.excl), NULL, 0,
- true, f.ctx, f.ctx_sz, &ran);
- ASSERT_TRUE(ran, "loader ran");
- ASSERT_EQ(r, -EINVAL, "tampered blob rejected by emit_signature_match");
+ fd = load_loader(f.gopts.insns, f.gopts.insns_sz, -1, junk,
+ sizeof(junk), KEY_SPEC_SESSION_KEYRING, 0);
+ ASSERT_EQ(fd, -EBADMSG, "invalid signature rejected at load");
}
gen_loader_fixture_fini(&f);
}
-static void metadata_not_exclusive(void)
+static void signed_nonexcl_fd_array_rejected(void)
{
+ static const __u8 junk[64] = { 0x30, 0x42, 0x13, 0x37, };
struct gen_loader_fixture f;
- bool ran;
- int r;
+ int map_fd, fd;
if (gen_loader_fixture_init(&f) == 0) {
/*
- * Correct blob but a non-exclusive metadata map: the verifier does
- * not reject (excl_prog_sha unset), so the runtime map->excl == 1
- * check in the loader must.
+ * A signed program may only bind exclusive maps through fd_array
+ * (their contents are folded into the signature). Binding a
+ * non-exclusive map is rejected, before the signature is even
+ * examined.
*/
- r = run_gen_loader(f.gopts.insns, f.gopts.insns_sz, f.blob,
- f.data_sz, NULL, 0, NULL, 0, true, f.ctx,
- f.ctx_sz, &ran);
- ASSERT_TRUE(ran, "loader ran");
- ASSERT_EQ(r, -EINVAL, "non-exclusive metadata map rejected");
+ map_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, "nonexcl", 4,
+ f.data_sz, 1, NULL);
+ if (ASSERT_OK_FD(map_fd, "nonexcl_map")) {
+ if (ASSERT_OK(bpf_map_freeze(map_fd), "freeze")) {
+ fd = load_loader(f.gopts.insns, f.gopts.insns_sz,
+ map_fd, junk, sizeof(junk),
+ KEY_SPEC_SESSION_KEYRING, 1);
+ ASSERT_EQ(fd, -EPERM,
+ "non-exclusive map in signed fd_array rejected");
+ if (fd >= 0)
+ close(fd);
+ }
+ close(map_fd);
+ }
}
gen_loader_fixture_fini(&f);
}
-static void metadata_hash_not_computed(void)
+static void signed_unfrozen_fd_array_rejected(void)
{
+ static const __u8 junk[64] = { 0x30, 0x42, 0x13, 0x37, };
+ LIBBPF_OPTS(bpf_map_create_opts, mopts);
struct gen_loader_fixture f;
- bool ran;
- int r;
+ __u32 key = 0;
+ int map_fd, fd;
if (gen_loader_fixture_init(&f) == 0) {
/*
- * Correct, exclusive, frozen map, but its hash was never computed
- * (no OBJ_GET_INFO_BY_FD), so map->sha stays zero. The loader must
- * fail closed rather than treat an unset hash as a match.
+ * The metadata map must be frozen before a signed load so the
+ * folded bytes cannot change afterwards. Bind an exclusive map
+ * with matching contents but skip the freeze: the load must be
+ * rejected by the frozen check with -EPERM. The exclusivity
+ * check right after it would pass, so the errno uniquely pins
+ * the freeze requirement.
*/
- r = run_gen_loader(f.gopts.insns, f.gopts.insns_sz, f.blob,
- f.data_sz, f.excl, sizeof(f.excl), NULL, 0,
- false, f.ctx, f.ctx_sz, &ran);
- ASSERT_TRUE(ran, "loader ran");
- ASSERT_EQ(r, -EINVAL, "uncomputed metadata hash rejected");
+ mopts.excl_prog_hash = f.excl;
+ mopts.excl_prog_hash_size = sizeof(f.excl);
+ map_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, "unfrozen", 4,
+ f.data_sz, 1, &mopts);
+ if (ASSERT_OK_FD(map_fd, "unfrozen_map")) {
+ if (ASSERT_OK(bpf_map_update_elem(map_fd, &key, f.blob, 0),
+ "update")) {
+ fd = load_loader(f.gopts.insns, f.gopts.insns_sz,
+ map_fd, junk, sizeof(junk),
+ KEY_SPEC_SESSION_KEYRING, 1);
+ ASSERT_EQ(fd, -EPERM,
+ "unfrozen map in signed fd_array rejected");
+ if (fd >= 0)
+ close(fd);
+ }
+ close(map_fd);
+ }
}
gen_loader_fixture_fini(&f);
}
-static void signature_enforced(void)
+static void signed_nonarray_fd_array_rejected(void)
{
static const __u8 junk[64] = { 0x30, 0x42, 0x13, 0x37, };
+ LIBBPF_OPTS(bpf_map_create_opts, mopts);
struct gen_loader_fixture f;
+ int map_fd, fd;
+
+ if (gen_loader_fixture_init(&f) == 0) {
+ /*
+ * Only a plain BPF_MAP_TYPE_ARRAY may be folded into the
+ * signature. An exclusive map of any other type is rejected
+ * (-EINVAL) rather than folded - this is the type gate that
+ * keeps arena maps (map_direct_value_addr() returns a user
+ * address) and insn-array maps (buffer smaller than value_size)
+ * out of the hashed region, where the old code would have
+ * memcpy()'d from them. A hash map stands in here: it is
+ * exclusive (bound to the loader digest) but not an array.
+ */
+ mopts.excl_prog_hash = f.excl;
+ mopts.excl_prog_hash_size = sizeof(f.excl);
+ map_fd = bpf_map_create(BPF_MAP_TYPE_HASH, "excl_hash", 4, 4, 1,
+ &mopts);
+ if (ASSERT_OK_FD(map_fd, "excl_hash_map")) {
+ fd = load_loader(f.gopts.insns, f.gopts.insns_sz, map_fd,
+ junk, sizeof(junk),
+ KEY_SPEC_SESSION_KEYRING, 1);
+ ASSERT_EQ(fd, -EINVAL,
+ "non-array map in signed fd_array rejected");
+ if (fd >= 0)
+ close(fd);
+ close(map_fd);
+ }
+ }
+ gen_loader_fixture_fini(&f);
+}
+
+static int setup_meta_map(const struct gen_loader_fixture *f);
+
+static void signed_btf_fd_array_rejected(void)
+{
+ char dir_tmpl[] = "/tmp/signed_loader_btfXXXXXX", *dir = NULL;
+ __u32 sig_sz = 8192;
+ int map_fd = -1, prog_fd = -1;
+ unsigned char *buf = NULL;
+ struct gen_loader_fixture f;
+ bool have_fixture = false;
+ struct btf *btf = NULL;
+ union bpf_attr attr;
+ int fds[2];
+ __u8 sig[8192];
+
+ syscall(__NR_request_key, "keyring", "_uid.0", NULL,
+ KEY_SPEC_SESSION_KEYRING);
+ dir = mkdtemp(dir_tmpl);
+ if (!ASSERT_OK_PTR(dir, "mkdtemp"))
+ return;
+ if (!ASSERT_OK(run_setup("setup", dir), "verify_sig_setup")) {
+ rmdir(dir);
+ return;
+ }
+ have_fixture = true;
+ if (gen_loader_fixture_init(&f) != 0)
+ goto out;
+
+ /*
+ * fd_array binds maps and BTFs alike, but only exclusive array maps are
+ * folded into the signature. Build an otherwise genuinely signed load -
+ * insns || metadata, exclusive frozen map at fd_array[0] - then smuggle
+ * an extra BTF into fd_array[1]. A signed program may not bind any BTF,
+ * so resolving the fd_array entries rejects the BTF with -EACCES (in
+ * __add_used_btf(), before the signature is even verified).
+ */
+ buf = malloc((size_t)f.gopts.insns_sz + f.data_sz);
+ if (!ASSERT_OK_PTR(buf, "signbuf"))
+ goto out;
+ memcpy(buf, f.gopts.insns, f.gopts.insns_sz);
+ memcpy(buf + f.gopts.insns_sz, f.blob, f.data_sz);
+ if (!ASSERT_OK(sign_buf(dir, buf, f.gopts.insns_sz + f.data_sz, sig,
+ &sig_sz), "sign insns||metadata"))
+ goto out;
+
+ map_fd = setup_meta_map(&f);
+ if (!ASSERT_OK_FD(map_fd, "meta_map"))
+ goto out;
+ btf = btf__new_empty();
+ if (!ASSERT_OK_PTR(btf, "btf_new_empty"))
+ goto out;
+ btf__add_int(btf, "int", 4, BTF_INT_SIGNED);
+ if (!ASSERT_OK(btf__load_into_kernel(btf), "btf_load"))
+ goto out;
+
+ fds[0] = map_fd;
+ fds[1] = btf__fd(btf);
+ memset(&attr, 0, sizeof(attr));
+ attr.prog_type = BPF_PROG_TYPE_SYSCALL;
+ attr.insns = ptr_to_u64(f.gopts.insns);
+ attr.insn_cnt = f.gopts.insns_sz / sizeof(struct bpf_insn);
+ attr.license = ptr_to_u64("Dual BSD/GPL");
+ attr.prog_flags = BPF_F_SLEEPABLE;
+ attr.fd_array = ptr_to_u64(fds);
+ attr.fd_array_cnt = 2;
+ attr.signature = ptr_to_u64(sig);
+ attr.signature_size = sig_sz;
+ attr.keyring_id = KEY_SPEC_SESSION_KEYRING;
+ memcpy(attr.prog_name, "__loader.prog", sizeof("__loader.prog"));
+ prog_fd = syscall(__NR_bpf, BPF_PROG_LOAD, &attr,
+ offsetofend(union bpf_attr, keyring_id));
+ ASSERT_EQ(prog_fd < 0 ? -errno : prog_fd, -EACCES,
+ "BTF in signed fd_array rejected");
+ if (prog_fd >= 0)
+ close(prog_fd);
+out:
+ if (btf)
+ btf__free(btf);
+ if (map_fd >= 0)
+ close(map_fd);
+ if (have_fixture)
+ gen_loader_fixture_fini(&f);
+ if (dir)
+ run_setup("cleanup", dir);
+ free(buf);
+}
+
+static void signature_failure_logs(void)
+{
+ static const __u8 junk[64] = { 0x30, 0x42, 0x13, 0x37, };
+ char log_buf[1024] = {};
+ struct gen_loader_fixture f;
+ union bpf_attr attr;
int fd;
if (gen_loader_fixture_init(&f) == 0) {
/*
- * A present-but-invalid signature (the cert bytes are not a
- * PKCS#7 signature) must be rejected at load: the signature
- * path is honored, not ignored. (The valid path is covered by
- * the signed lskels.)
+ * Signature verification now runs inside bpf_check(), so a
+ * failure is reported through the verifier log. A present-but-
+ * invalid signature is rejected and the log says why.
*/
- fd = load_loader(f.gopts.insns, f.gopts.insns_sz, -1, junk,
- sizeof(junk), KEY_SPEC_SESSION_KEYRING);
+ memset(&attr, 0, sizeof(attr));
+ attr.prog_type = BPF_PROG_TYPE_SYSCALL;
+ attr.insns = ptr_to_u64(f.gopts.insns);
+ attr.insn_cnt = f.gopts.insns_sz / sizeof(struct bpf_insn);
+ attr.license = ptr_to_u64("Dual BSD/GPL");
+ attr.prog_flags = BPF_F_SLEEPABLE;
+ attr.signature = ptr_to_u64(junk);
+ attr.signature_size = sizeof(junk);
+ attr.keyring_id = KEY_SPEC_SESSION_KEYRING;
+ attr.log_level = 1;
+ attr.log_buf = ptr_to_u64(log_buf);
+ attr.log_size = sizeof(log_buf);
+ memcpy(attr.prog_name, "__loader.prog", sizeof("__loader.prog"));
+
+ fd = syscall(__NR_bpf, BPF_PROG_LOAD, &attr,
+ offsetofend(union bpf_attr, keyring_id));
ASSERT_LT(fd, 0, "invalid signature rejected at load");
+ if (fd >= 0)
+ close(fd);
+ ASSERT_HAS_SUBSTR(log_buf, "signature verification failed",
+ "verifier logs signature failure");
}
gen_loader_fixture_fini(&f);
}
@@ -495,7 +573,7 @@ static void signature_too_large(void)
* is rejected before the buffer is read.
*/
fd = load_loader(f.gopts.insns, f.gopts.insns_sz, -1, junk,
- 64 << 20, KEY_SPEC_SESSION_KEYRING);
+ 64 << 20, KEY_SPEC_SESSION_KEYRING, 0);
ASSERT_EQ(fd, -EINVAL, "oversized signature rejected");
}
gen_loader_fixture_fini(&f);
@@ -515,7 +593,7 @@ static void signature_bad_keyring(void)
* large positive serial takes the user-keyring path and won't exist.
*/
fd = load_loader(f.gopts.insns, f.gopts.insns_sz, -1, junk,
- sizeof(junk), INT_MAX);
+ sizeof(junk), INT_MAX, 0);
ASSERT_EQ(fd, -EINVAL, "signature with bad keyring_id rejected");
}
gen_loader_fixture_fini(&f);
@@ -575,7 +653,7 @@ static void metadata_ctx_max_entries_ignored(void)
memcpy(blob, gopts.data, data_sz);
r = run_gen_loader(gopts.insns, gopts.insns_sz, blob, data_sz,
- excl, sizeof(excl), NULL, 0, true, ctx, ctx_sz, &ran);
+ excl, sizeof(excl), NULL, 0, ctx, ctx_sz, &ran);
if (!ASSERT_TRUE(ran, "loader ran") ||
!ASSERT_EQ(r, 0, "loader retval"))
goto free_blob;
@@ -661,7 +739,7 @@ static void metadata_ctx_initial_value_ignored(void)
memcpy(blob, gopts.data, data_sz);
r = run_gen_loader(gopts.insns, gopts.insns_sz, blob, data_sz,
- excl, sizeof(excl), NULL, 0, true, ctx, ctx_sz, &ran);
+ excl, sizeof(excl), NULL, 0, ctx, ctx_sz, &ran);
if (!ASSERT_TRUE(ran, "loader ran") ||
!ASSERT_EQ(r, 0, "loader retval"))
goto free_blob;
@@ -714,6 +792,7 @@ static void signature_authenticates_insns(void)
__u8 excl[SHA256_DIGEST_LENGTH], sig[8192];
__u32 sig_sz = sizeof(sig), insns_sz, data_sz, ctx_sz;
unsigned char *insns = NULL, *tampered = NULL, *blob = NULL;
+ unsigned char *signbuf = NULL;
int nr_maps = 0, nr_progs = 0, r;
struct bpf_program *p;
struct bpf_map *m;
@@ -760,29 +839,141 @@ static void signature_authenticates_insns(void)
memcpy(blob, gopts.data, data_sz);
libbpf_sha256(insns, insns_sz, excl);
- if (!ASSERT_OK(sign_buf(dir, insns, insns_sz, sig, &sig_sz), "sign-file"))
+ signbuf = malloc((size_t)insns_sz + data_sz);
+ if (!ASSERT_OK_PTR(signbuf, "signbuf"))
+ goto cleanup;
+ memcpy(signbuf, insns, insns_sz);
+ memcpy(signbuf + insns_sz, blob, data_sz);
+ if (!ASSERT_OK(sign_buf(dir, signbuf, insns_sz + data_sz, sig, &sig_sz),
+ "sign-file"))
goto cleanup;
memset(ctx, 0, ctx_sz);
((struct bpf_loader_ctx *)ctx)->sz = ctx_sz;
r = run_gen_loader(insns, insns_sz, blob, data_sz, excl, sizeof(excl),
- sig, sig_sz, true, ctx, ctx_sz, &ran);
+ sig, sig_sz, ctx, ctx_sz, &ran);
ASSERT_TRUE(ran, "valid signature: loader loaded and ran");
ASSERT_EQ(r, 0, "valid signature accepted");
close_loader_ctx_fds(ctx, nr_maps, nr_progs);
memcpy(tampered, insns, insns_sz);
tampered[insns_sz / 2] ^= 0xff;
+ /*
+ * Bind the metadata map to the tampered loader's own digest, so the
+ * verifier's exclusive-map check (excl_prog_sha == prog->digest) passes
+ * and the signature - verified after the maps are resolved - is what
+ * rejects the load. This is the attacker's best case: even after
+ * re-binding the exclusive map to their tampered loader, the signature
+ * over the original insns || metadata still fails. (Leaving the map
+ * bound to the original digest would instead trip the excl check first.)
+ */
+ libbpf_sha256(tampered, insns_sz, excl);
memset(ctx, 0, ctx_sz);
((struct bpf_loader_ctx *)ctx)->sz = ctx_sz;
r = run_gen_loader(tampered, insns_sz, blob, data_sz, excl, sizeof(excl),
- sig, sig_sz, true, ctx, ctx_sz, &ran);
+ sig, sig_sz, ctx, ctx_sz, &ran);
ASSERT_FALSE(ran, "tampered loader rejected before run");
ASSERT_EQ(r, -EKEYREJECTED, "signature is bound to the instructions");
cleanup:
free(insns);
free(tampered);
free(blob);
+ free(signbuf);
+ free(ctx);
+ test_signed_loader__destroy(skel);
+ run_setup("cleanup", dir);
+}
+
+static void signature_authenticates_metadata(void)
+{
+ LIBBPF_OPTS(gen_loader_opts, gopts, .gen_hash = true);
+ char dir_tmpl[] = "/tmp/signed_loaderXXXXXX", *dir;
+ struct test_signed_loader *skel = NULL;
+ __u8 excl[SHA256_DIGEST_LENGTH], sig[8192];
+ __u32 sig_sz = sizeof(sig), insns_sz, data_sz, ctx_sz;
+ unsigned char *insns = NULL, *blob = NULL;
+ unsigned char *signbuf = NULL;
+ int nr_maps = 0, nr_progs = 0, r;
+ struct bpf_program *p;
+ struct bpf_map *m;
+ void *ctx = NULL;
+ bool ran;
+
+ syscall(__NR_request_key, "keyring", "_uid.0", NULL,
+ KEY_SPEC_SESSION_KEYRING);
+ dir = mkdtemp(dir_tmpl);
+ if (!ASSERT_OK_PTR(dir, "mkdtemp"))
+ return;
+ if (!ASSERT_OK(run_setup("setup", dir), "verify_sig_setup")) {
+ rmdir(dir);
+ return;
+ }
+
+ skel = test_signed_loader__open();
+ if (!ASSERT_OK_PTR(skel, "skel_open"))
+ goto cleanup;
+ if (!ASSERT_OK(bpf_object__gen_loader(skel->obj, &gopts), "gen_loader"))
+ goto cleanup;
+ if (!ASSERT_OK(bpf_object__load(skel->obj), "gen_load"))
+ goto cleanup;
+
+ bpf_object__for_each_program(p, skel->obj)
+ nr_progs++;
+ bpf_object__for_each_map(m, skel->obj)
+ nr_maps++;
+ ctx_sz = sizeof(struct bpf_loader_ctx) +
+ nr_maps * sizeof(struct bpf_map_desc) +
+ nr_progs * sizeof(struct bpf_prog_desc);
+ insns_sz = gopts.insns_sz;
+ data_sz = gopts.data_sz;
+ ctx = calloc(1, ctx_sz);
+ insns = malloc(insns_sz);
+ blob = malloc(data_sz);
+ if (!ASSERT_OK_PTR(ctx, "ctx") ||
+ !ASSERT_OK_PTR(insns, "insns") ||
+ !ASSERT_OK_PTR(blob, "blob"))
+ goto cleanup;
+ memcpy(insns, gopts.insns, insns_sz);
+ memcpy(blob, gopts.data, data_sz);
+ libbpf_sha256(insns, insns_sz, excl);
+
+ signbuf = malloc((size_t)insns_sz + data_sz);
+ if (!ASSERT_OK_PTR(signbuf, "signbuf"))
+ goto cleanup;
+ memcpy(signbuf, insns, insns_sz);
+ memcpy(signbuf + insns_sz, blob, data_sz);
+ if (!ASSERT_OK(sign_buf(dir, signbuf, insns_sz + data_sz, sig, &sig_sz),
+ "sign-file"))
+ goto cleanup;
+
+ memset(ctx, 0, ctx_sz);
+ ((struct bpf_loader_ctx *)ctx)->sz = ctx_sz;
+ r = run_gen_loader(insns, insns_sz, blob, data_sz, excl, sizeof(excl),
+ sig, sig_sz, ctx, ctx_sz, &ran);
+ ASSERT_TRUE(ran, "valid signature: loader loaded and ran");
+ ASSERT_EQ(r, 0, "valid signature accepted");
+ close_loader_ctx_fds(ctx, nr_maps, nr_progs);
+
+ /*
+ * Tamper the metadata after signing while leaving the instructions
+ * and thus the exclusive hash binding untouched: the map freezes
+ * fine and excl_prog_sha still matches the loader's digest, so the
+ * load reaches signature verification, which folds the live frozen
+ * map bytes into the checked payload and must reject the modified
+ * blob. A kernel folding anything but the map contents themselves
+ * would wrongly accept this load.
+ */
+ blob[data_sz / 2] ^= 0xff;
+ memset(ctx, 0, ctx_sz);
+ ((struct bpf_loader_ctx *)ctx)->sz = ctx_sz;
+ r = run_gen_loader(insns, insns_sz, blob, data_sz, excl, sizeof(excl),
+ sig, sig_sz, ctx, ctx_sz, &ran);
+ ASSERT_FALSE(ran, "tampered metadata rejected before run");
+ ASSERT_EQ(r, -EKEYREJECTED, "signature is bound to the metadata");
+cleanup:
+ free(insns);
+ free(blob);
+ free(signbuf);
free(ctx);
test_signed_loader__destroy(skel);
run_setup("cleanup", dir);
@@ -1007,10 +1198,11 @@ static void lsm_signature_verdict(void)
{
char dir_tmpl[] = "/tmp/signed_loader_lsmXXXXXX", *dir = NULL;
struct test_signed_loader_lsm *lsm = NULL;
+ __u32 sig_sz = 8192, msig_sz = 8192;
int map_fd = -1, prog_fd = -1;
bool have_fixture = false;
struct gen_loader_fixture f;
- __u32 sig_sz = 8192;
+ unsigned char *buf;
__s32 ses_serial;
__u8 sig[8192];
@@ -1029,7 +1221,7 @@ static void lsm_signature_verdict(void)
if (!ASSERT_OK_FD(map_fd, "meta_map_unsigned"))
goto out;
lsm->bss->seen = 0;
- prog_fd = load_loader(f.gopts.insns, f.gopts.insns_sz, map_fd, NULL, 0, 0);
+ prog_fd = load_loader(f.gopts.insns, f.gopts.insns_sz, map_fd, NULL, 0, 0, 0);
close(map_fd);
map_fd = -1;
if (!ASSERT_OK_FD(prog_fd, "unsigned loader load"))
@@ -1062,22 +1254,51 @@ static void lsm_signature_verdict(void)
goto out;
lsm->bss->seen = 0;
prog_fd = load_loader(f.gopts.insns, f.gopts.insns_sz, map_fd, sig,
- sig_sz, KEY_SPEC_SESSION_KEYRING);
+ sig_sz, KEY_SPEC_SESSION_KEYRING, 0);
close(map_fd);
map_fd = -1;
- if (!ASSERT_OK_FD(prog_fd, "signed loader load"))
- goto out;
- close(prog_fd);
+ ASSERT_EQ(prog_fd, -EACCES, "unfolded metadata rejected");
+ if (prog_fd >= 0)
+ close(prog_fd);
prog_fd = -1;
ses_serial = syscall(__NR_keyctl, KEYCTL_GET_KEYRING_ID,
KEY_SPEC_SESSION_KEYRING, 0);
ASSERT_EQ(lsm->bss->seen, 1, "signed: one observed load");
- ASSERT_EQ(lsm->bss->sig_verdict, BPF_SIG_VERIFIED, "signed verdict");
+ ASSERT_EQ(lsm->bss->sig_verdict, BPF_SIG_VERIFIED,
+ "admission saw a valid signature");
ASSERT_EQ(lsm->bss->sig_keyring_type, BPF_SIG_KEYRING_USER, "signed keyring type");
ASSERT_GT(ses_serial, 0, "session keyring serial resolved");
ASSERT_EQ(lsm->bss->sig_keyring_serial, ses_serial,
"signed: validated against session keyring");
+
+ buf = malloc((size_t)f.gopts.insns_sz + f.data_sz);
+ if (!ASSERT_OK_PTR(buf, "meta_signbuf"))
+ goto out;
+ memcpy(buf, f.gopts.insns, f.gopts.insns_sz);
+ memcpy(buf + f.gopts.insns_sz, f.blob, f.data_sz);
+ if (!ASSERT_OK(sign_buf(dir, buf, f.gopts.insns_sz + f.data_sz,
+ sig, &msig_sz), "sign insns||metadata")) {
+ free(buf);
+ goto out;
+ }
+ free(buf);
+
+ map_fd = setup_meta_map(&f);
+ if (!ASSERT_OK_FD(map_fd, "meta_map_bound"))
+ goto out;
+ lsm->bss->seen = 0;
+ prog_fd = load_loader(f.gopts.insns, f.gopts.insns_sz, map_fd, sig,
+ msig_sz, KEY_SPEC_SESSION_KEYRING, 1);
+ close(map_fd);
+ map_fd = -1;
+ if (!ASSERT_OK_FD(prog_fd, "metadata-bound loader load"))
+ goto out;
+ close(prog_fd);
+ prog_fd = -1;
+ ASSERT_EQ(lsm->bss->seen, 1, "metadata: one observed load");
+ ASSERT_EQ(lsm->bss->sig_verdict, BPF_SIG_VERIFIED,
+ "metadata-bound verdict");
out:
if (map_fd >= 0)
close(map_fd);
@@ -1090,20 +1311,467 @@ static void lsm_signature_verdict(void)
test_signed_loader_lsm__destroy(lsm);
}
+/*
+ * Load-time metadata verification: the kernel folds the frozen metadata map
+ * into the signature (insns || metadata) and checks it at BPF_PROG_LOAD via
+ * fd_array_cnt, rather than the loader checking from within BPF. Sign that
+ * concatenation, hand the kernel the map, and confirm the signed loader loads,
+ * runs, and installs its target.
+ */
+static int loadtime_drive(const char *dir, const void *insns, __u32 insns_sz,
+ const void *data, __u32 data_sz, const __u8 *excl,
+ void *ctx, __u32 ctx_sz, int *load_ret, bool *ran)
+{
+ LIBBPF_OPTS(bpf_map_create_opts, mopts,
+ .excl_prog_hash = excl,
+ .excl_prog_hash_size = SHA256_DIGEST_LENGTH);
+ __u32 sig_sz = 8192, key = 0;
+ unsigned char *buf = NULL;
+ int map_fd, prog_fd, ret = 0;
+ union bpf_attr attr;
+ __u8 sig[8192];
+
+ *ran = false;
+ *load_ret = 0;
+
+ /*
+ * Metadata map, bound to the loader digest and frozen, exactly as
+ * skel_internal.h's bpf_load_and_run() sets it up.
+ */
+ map_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, "__loader.map", 4,
+ data_sz, 1, &mopts);
+ if (map_fd < 0) {
+ ret = -errno;
+ goto out_load;
+ }
+ if (bpf_map_update_elem(map_fd, &key, data, 0) || bpf_map_freeze(map_fd)) {
+ ret = -errno;
+ goto out_load;
+ }
+
+ /* Sign insns || metadata, the same bytes the kernel reconstructs. */
+ buf = malloc((size_t)insns_sz + data_sz);
+ if (!buf) {
+ ret = -ENOMEM;
+ goto out_load;
+ }
+ memcpy(buf, insns, insns_sz);
+ memcpy(buf + insns_sz, data, data_sz);
+ ret = sign_buf(dir, buf, insns_sz + data_sz, sig, &sig_sz);
+ if (ret)
+ goto out_load;
+
+ memset(&attr, 0, sizeof(attr));
+ attr.prog_type = BPF_PROG_TYPE_SYSCALL;
+ attr.insns = ptr_to_u64(insns);
+ attr.insn_cnt = insns_sz / sizeof(struct bpf_insn);
+ attr.license = ptr_to_u64("Dual BSD/GPL");
+ attr.prog_flags = BPF_F_SLEEPABLE;
+ attr.fd_array = ptr_to_u64(&map_fd);
+ attr.signature = ptr_to_u64(sig);
+ attr.signature_size = sig_sz;
+ attr.keyring_id = KEY_SPEC_SESSION_KEYRING;
+ attr.fd_array_cnt = 1;
+ memcpy(attr.prog_name, "__loader.prog", sizeof("__loader.prog"));
+ prog_fd = syscall(__NR_bpf, BPF_PROG_LOAD, &attr,
+ offsetofend(union bpf_attr, keyring_id));
+ if (prog_fd < 0) {
+ ret = -errno;
+ goto out_load;
+ }
+
+ memset(&attr, 0, sizeof(attr));
+ attr.test.prog_fd = prog_fd;
+ attr.test.ctx_in = ptr_to_u64(ctx);
+ attr.test.ctx_size_in = ctx_sz;
+ if (syscall(__NR_bpf, BPF_PROG_RUN, &attr,
+ offsetofend(union bpf_attr, test)) < 0) {
+ ret = -errno;
+ goto out_prog;
+ }
+ *ran = true;
+ ret = (int)attr.test.retval;
+out_prog:
+ close(prog_fd);
+ goto out_map;
+out_load:
+ *load_ret = ret;
+out_map:
+ free(buf);
+ if (map_fd >= 0)
+ close(map_fd);
+ return ret;
+}
+
+static void loadtime_verify(struct bpf_object *obj, int expect_maps)
+{
+ LIBBPF_OPTS(gen_loader_opts, gopts, .gen_hash = true);
+ char dir_tmpl[] = "/tmp/signed_loader_ltXXXXXX", *dir = NULL;
+ int nr_maps = 0, nr_progs = 0, load_ret = 0, r;
+ __u8 excl[SHA256_DIGEST_LENGTH];
+ struct bpf_prog_desc *pd;
+ struct bpf_map_desc *md;
+ unsigned char *blob = NULL;
+ struct bpf_program *p;
+ struct bpf_map *m;
+ __u32 ctx_sz, data_sz;
+ void *ctx = NULL;
+ bool ran = false;
+
+ syscall(__NR_request_key, "keyring", "_uid.0", NULL,
+ KEY_SPEC_SESSION_KEYRING);
+ dir = mkdtemp(dir_tmpl);
+ if (!ASSERT_OK_PTR(dir, "mkdtemp"))
+ return;
+ if (!ASSERT_OK(run_setup("setup", dir), "verify_sig_setup")) {
+ rmdir(dir);
+ return;
+ }
+
+ if (!ASSERT_OK(bpf_object__gen_loader(obj, &gopts), "gen_loader"))
+ goto out;
+ if (!ASSERT_OK(bpf_object__load(obj), "gen_load"))
+ goto out;
+
+ bpf_object__for_each_program(p, obj)
+ nr_progs++;
+ bpf_object__for_each_map(m, obj)
+ nr_maps++;
+ if (!ASSERT_EQ(nr_maps, expect_maps, "fixture map count"))
+ goto out;
+
+ ctx_sz = sizeof(struct bpf_loader_ctx) +
+ nr_maps * sizeof(struct bpf_map_desc) +
+ nr_progs * sizeof(struct bpf_prog_desc);
+ ctx = calloc(1, ctx_sz);
+ if (!ASSERT_OK_PTR(ctx, "ctx_alloc"))
+ goto out;
+ ((struct bpf_loader_ctx *)ctx)->sz = ctx_sz;
+
+ data_sz = gopts.data_sz;
+ blob = malloc(data_sz);
+ if (!ASSERT_OK_PTR(blob, "blob_alloc"))
+ goto out;
+ memcpy(blob, gopts.data, data_sz);
+
+ /* excl_prog_hash = SHA256(loader insns) == the loader's prog->digest. */
+ libbpf_sha256(gopts.insns, gopts.insns_sz, excl);
+
+ r = loadtime_drive(dir, gopts.insns, gopts.insns_sz, blob, data_sz,
+ excl, ctx, ctx_sz, &load_ret, &ran);
+ ASSERT_OK(load_ret, "signed loader loaded (insns || metadata)");
+ ASSERT_TRUE(ran, "loader ran");
+ ASSERT_EQ(r, 0, "loader installed its target");
+
+ md = (struct bpf_map_desc *)((char *)ctx + sizeof(struct bpf_loader_ctx));
+ pd = (struct bpf_prog_desc *)(md + nr_maps);
+ ASSERT_GT(pd[0].prog_fd, 0, "target program installed");
+ if (nr_maps)
+ ASSERT_GT(md[0].map_fd, 0, "target map installed");
+
+ close_loader_ctx_fds(ctx, nr_maps, nr_progs);
+out:
+ free(blob);
+ free(ctx);
+ if (dir)
+ run_setup("cleanup", dir);
+}
+
+static void loadtime_no_map(void)
+{
+ struct test_signed_loader *skel = test_signed_loader__open();
+
+ if (!ASSERT_OK_PTR(skel, "skel_open"))
+ return;
+ loadtime_verify(skel->obj, 0);
+ test_signed_loader__destroy(skel);
+}
+
+static void loadtime_with_map(void)
+{
+ struct test_signed_loader_map *skel = test_signed_loader_map__open();
+
+ if (!ASSERT_OK_PTR(skel, "skel_open"))
+ return;
+ loadtime_verify(skel->obj, 1);
+ test_signed_loader_map__destroy(skel);
+}
+
+/*
+ * A signed program need not bind any map. A plain BPF_PROG_TYPE_SYSCALL
+ * program with no fd_array is signed over its instructions alone: the kernel
+ * verifies the signature, folds no metadata, and the program loads. Exercise
+ * the fd_array == NULL / fd_array_cnt == 0 path, and confirm the signature
+ * still authenticates the instructions (a tampered copy is rejected).
+ */
+static void signed_no_fd_array(void)
+{
+ struct bpf_insn insns[] = {
+ BPF_MOV64_IMM(BPF_REG_0, 0),
+ BPF_EXIT_INSN(),
+ };
+ char dir_tmpl[] = "/tmp/signed_loaderXXXXXX", *dir;
+ __u32 sig_sz = 8192;
+ union bpf_attr attr;
+ __u8 sig[8192];
+ int prog_fd, err;
+
+ syscall(__NR_request_key, "keyring", "_uid.0", NULL,
+ KEY_SPEC_SESSION_KEYRING);
+ dir = mkdtemp(dir_tmpl);
+ if (!ASSERT_OK_PTR(dir, "mkdtemp"))
+ return;
+ if (!ASSERT_OK(run_setup("setup", dir), "verify_sig_setup")) {
+ rmdir(dir);
+ return;
+ }
+
+ /* No metadata map: the signed payload is the instructions alone. */
+ if (!ASSERT_OK(sign_buf(dir, insns, sizeof(insns), sig, &sig_sz),
+ "sign-file"))
+ goto cleanup;
+
+ memset(&attr, 0, sizeof(attr));
+ attr.prog_type = BPF_PROG_TYPE_SYSCALL;
+ attr.insns = ptr_to_u64(insns);
+ attr.insn_cnt = ARRAY_SIZE(insns);
+ attr.license = ptr_to_u64("Dual BSD/GPL");
+ attr.prog_flags = BPF_F_SLEEPABLE;
+ attr.signature = ptr_to_u64(sig);
+ attr.signature_size = sig_sz;
+ attr.keyring_id = KEY_SPEC_SESSION_KEYRING;
+ /* fd_array and fd_array_cnt deliberately left NULL/0. */
+ memcpy(attr.prog_name, "signed_nomap", sizeof("signed_nomap"));
+
+ prog_fd = syscall(__NR_bpf, BPF_PROG_LOAD, &attr,
+ offsetofend(union bpf_attr, keyring_id));
+ if (!ASSERT_GE(prog_fd, 0, "map-less signed program loaded")) {
+ if (prog_fd >= 0)
+ close(prog_fd);
+ goto cleanup;
+ }
+ close(prog_fd);
+
+ /* The signature covers the instructions, so tampering must be rejected. */
+ insns[0].imm = 1;
+ prog_fd = syscall(__NR_bpf, BPF_PROG_LOAD, &attr,
+ offsetofend(union bpf_attr, keyring_id));
+ err = prog_fd < 0 ? -errno : prog_fd;
+ ASSERT_EQ(err, -EKEYREJECTED, "tampered map-less program rejected");
+ if (prog_fd >= 0)
+ close(prog_fd);
+cleanup:
+ run_setup("cleanup", dir);
+}
+
+/*
+ * A signed program may reach maps only through fd_array indices, so the kernel
+ * folds (and thus attests) them. A direct BPF_PSEUDO_MAP_FD reference - a raw,
+ * unfolded fd baked into the signed instructions - is rejected by the verifier.
+ */
+static void signed_map_by_fd_rejected(void)
+{
+ struct bpf_insn insns[] = {
+ BPF_LD_MAP_FD(BPF_REG_1, 0),
+ BPF_MOV64_IMM(BPF_REG_0, 0),
+ BPF_EXIT_INSN(),
+ };
+ char dir_tmpl[] = "/tmp/signed_loaderXXXXXX", *dir;
+ __u32 sig_sz = 8192;
+ union bpf_attr attr;
+ __u8 sig[8192];
+ int map_fd, prog_fd, err;
+
+ map_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, "sig_mapfd", 4, 4, 1, NULL);
+ if (!ASSERT_GE(map_fd, 0, "map_create"))
+ return;
+ insns[0].imm = map_fd; /* bake the raw map fd into the ld_imm64 */
+
+ syscall(__NR_request_key, "keyring", "_uid.0", NULL,
+ KEY_SPEC_SESSION_KEYRING);
+ dir = mkdtemp(dir_tmpl);
+ if (!ASSERT_OK_PTR(dir, "mkdtemp"))
+ goto out_map;
+ if (!ASSERT_OK(run_setup("setup", dir), "verify_sig_setup")) {
+ rmdir(dir);
+ goto out_map;
+ }
+
+ /* Sign the instructions, raw map fd and all. */
+ if (!ASSERT_OK(sign_buf(dir, insns, sizeof(insns), sig, &sig_sz),
+ "sign-file"))
+ goto cleanup;
+
+ memset(&attr, 0, sizeof(attr));
+ attr.prog_type = BPF_PROG_TYPE_SYSCALL;
+ attr.insns = ptr_to_u64(insns);
+ attr.insn_cnt = ARRAY_SIZE(insns);
+ attr.license = ptr_to_u64("Dual BSD/GPL");
+ attr.prog_flags = BPF_F_SLEEPABLE;
+ attr.signature = ptr_to_u64(sig);
+ attr.signature_size = sig_sz;
+ attr.keyring_id = KEY_SPEC_SESSION_KEYRING;
+ /* No fd_array: the map is reached by a raw fd in the instructions. */
+ memcpy(attr.prog_name, "signed_mapfd", sizeof("signed_mapfd"));
+
+ prog_fd = syscall(__NR_bpf, BPF_PROG_LOAD, &attr,
+ offsetofend(union bpf_attr, keyring_id));
+ err = prog_fd < 0 ? -errno : prog_fd;
+ ASSERT_EQ(err, -EINVAL, "signed program referencing a map by fd rejected");
+ if (prog_fd >= 0)
+ close(prog_fd);
+cleanup:
+ run_setup("cleanup", dir);
+out_map:
+ close(map_fd);
+}
+
+/*
+ * A signed program may reach maps only through the continuous fd_array, so the
+ * kernel folds (and thus attests) them. Referencing a map by fd_array *index*
+ * while leaving fd_array_cnt at 0 selects the sparse path, which resolves a map
+ * the signature never covered; the verifier rejects it up front with -EACCES.
+ */
+static void signed_sparse_fd_array_rejected(void)
+{
+ struct bpf_insn insns[] = {
+ BPF_LD_IMM64_RAW(BPF_REG_1, BPF_PSEUDO_MAP_IDX, 0),
+ BPF_MOV64_IMM(BPF_REG_0, 0),
+ BPF_EXIT_INSN(),
+ };
+ char dir_tmpl[] = "/tmp/signed_loader_spXXXXXX", *dir;
+ __u32 sig_sz = 8192;
+ union bpf_attr attr;
+ __u8 sig[8192];
+ int map_fd, prog_fd, err;
+
+ map_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, "sig_sparse", 4, 4, 1, NULL);
+ if (!ASSERT_GE(map_fd, 0, "map_create"))
+ return;
+
+ syscall(__NR_request_key, "keyring", "_uid.0", NULL,
+ KEY_SPEC_SESSION_KEYRING);
+ dir = mkdtemp(dir_tmpl);
+ if (!ASSERT_OK_PTR(dir, "mkdtemp"))
+ goto out_map;
+ if (!ASSERT_OK(run_setup("setup", dir), "verify_sig_setup")) {
+ rmdir(dir);
+ goto out_map;
+ }
+
+ /* Sign the instructions alone; the sparse map is not folded. */
+ if (!ASSERT_OK(sign_buf(dir, insns, sizeof(insns), sig, &sig_sz),
+ "sign-file"))
+ goto cleanup;
+
+ memset(&attr, 0, sizeof(attr));
+ attr.prog_type = BPF_PROG_TYPE_SYSCALL;
+ attr.insns = ptr_to_u64(insns);
+ attr.insn_cnt = ARRAY_SIZE(insns);
+ attr.license = ptr_to_u64("Dual BSD/GPL");
+ attr.prog_flags = BPF_F_SLEEPABLE;
+ attr.fd_array = ptr_to_u64(&map_fd);
+ attr.fd_array_cnt = 0; /* sparse: force lazy map resolution */
+ attr.signature = ptr_to_u64(sig);
+ attr.signature_size = sig_sz;
+ attr.keyring_id = KEY_SPEC_SESSION_KEYRING;
+ memcpy(attr.prog_name, "signed_sparse", sizeof("signed_sparse"));
+
+ prog_fd = syscall(__NR_bpf, BPF_PROG_LOAD, &attr,
+ offsetofend(union bpf_attr, keyring_id));
+ err = prog_fd < 0 ? -errno : prog_fd;
+ ASSERT_EQ(err, -EACCES, "signed program binding a sparse fd_array map rejected");
+ if (prog_fd >= 0)
+ close(prog_fd);
+cleanup:
+ run_setup("cleanup", dir);
+out_map:
+ close(map_fd);
+}
+
+static void signed_module_kfunc_rejected(void)
+{
+ struct bpf_insn insns[] = {
+ BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, BPF_PSEUDO_KFUNC_CALL, 1, 1),
+ BPF_MOV64_IMM(BPF_REG_0, 0),
+ BPF_EXIT_INSN(),
+ };
+ char dir_tmpl[] = "/tmp/signed_loader_kfnXXXXXX", *dir;
+ int prog_fd, err, fds[2];
+ struct btf *btf = NULL;
+ __u32 sig_sz = 8192;
+ union bpf_attr attr;
+ __u8 sig[8192];
+
+ syscall(__NR_request_key, "keyring", "_uid.0", NULL,
+ KEY_SPEC_SESSION_KEYRING);
+ dir = mkdtemp(dir_tmpl);
+ if (!ASSERT_OK_PTR(dir, "mkdtemp"))
+ return;
+ if (!ASSERT_OK(run_setup("setup", dir), "verify_sig_setup")) {
+ rmdir(dir);
+ return;
+ }
+ if (!ASSERT_OK(sign_buf(dir, insns, sizeof(insns), sig, &sig_sz),
+ "sign-file"))
+ goto cleanup;
+ btf = btf__new_empty();
+ if (!ASSERT_OK_PTR(btf, "btf_new_empty"))
+ goto cleanup;
+ btf__add_int(btf, "int", 4, BTF_INT_SIGNED);
+ if (!ASSERT_OK(btf__load_into_kernel(btf), "btf_load"))
+ goto cleanup;
+ fds[0] = -1;
+ fds[1] = btf__fd(btf);
+
+ memset(&attr, 0, sizeof(attr));
+ attr.prog_type = BPF_PROG_TYPE_SYSCALL;
+ attr.insns = ptr_to_u64(insns);
+ attr.insn_cnt = ARRAY_SIZE(insns);
+ attr.license = ptr_to_u64("Dual BSD/GPL");
+ attr.prog_flags = BPF_F_SLEEPABLE;
+ attr.fd_array = ptr_to_u64(fds);
+ attr.fd_array_cnt = 0; /* sparse: force lazy kfunc BTF resolution */
+ attr.signature = ptr_to_u64(sig);
+ attr.signature_size = sig_sz;
+ attr.keyring_id = KEY_SPEC_SESSION_KEYRING;
+ memcpy(attr.prog_name, "signed_kfunc", sizeof("signed_kfunc"));
+
+ prog_fd = syscall(__NR_bpf, BPF_PROG_LOAD, &attr,
+ offsetofend(union bpf_attr, keyring_id));
+ err = prog_fd < 0 ? -errno : prog_fd;
+ if (prog_fd >= 0)
+ close(prog_fd);
+
+ ASSERT_EQ(err, -EACCES, "module kfunc BTF in signed program rejected");
+cleanup:
+ if (btf)
+ btf__free(btf);
+ run_setup("cleanup", dir);
+}
+
void test_signed_loader(void)
{
- if (test__start_subtest("metadata_check_shape"))
- metadata_check_shape();
+ if (test__start_subtest("loadtime_no_map"))
+ loadtime_no_map();
+ if (test__start_subtest("loadtime_with_map"))
+ loadtime_with_map();
if (test__start_subtest("metadata_match"))
metadata_match();
- if (test__start_subtest("metadata_sha_mismatch"))
- metadata_sha_mismatch();
- if (test__start_subtest("metadata_not_exclusive"))
- metadata_not_exclusive();
- if (test__start_subtest("metadata_hash_not_computed"))
- metadata_hash_not_computed();
if (test__start_subtest("signature_enforced"))
signature_enforced();
+ if (test__start_subtest("signed_nonexcl_fd_array_rejected"))
+ signed_nonexcl_fd_array_rejected();
+ if (test__start_subtest("signed_unfrozen_fd_array_rejected"))
+ signed_unfrozen_fd_array_rejected();
+ if (test__start_subtest("signed_nonarray_fd_array_rejected"))
+ signed_nonarray_fd_array_rejected();
+ if (test__start_subtest("signed_btf_fd_array_rejected"))
+ signed_btf_fd_array_rejected();
+ if (test__start_subtest("signed_module_kfunc_rejected"))
+ signed_module_kfunc_rejected();
+ if (test__start_subtest("signature_failure_logs"))
+ signature_failure_logs();
if (test__start_subtest("signature_too_large"))
signature_too_large();
if (test__start_subtest("signature_bad_keyring"))
@@ -1114,6 +1782,8 @@ void test_signed_loader(void)
metadata_ctx_initial_value_ignored();
if (test__start_subtest("signature_authenticates_insns"))
signature_authenticates_insns();
+ if (test__start_subtest("signature_authenticates_metadata"))
+ signature_authenticates_metadata();
if (test__start_subtest("hash_requires_frozen"))
hash_requires_frozen();
if (test__start_subtest("no_update_after_freeze"))
@@ -1132,4 +1802,10 @@ void test_signed_loader(void)
map_hash_unsupported_type();
if (test__start_subtest("lsm_signature_verdict"))
lsm_signature_verdict();
+ if (test__start_subtest("signed_no_fd_array"))
+ signed_no_fd_array();
+ if (test__start_subtest("signed_map_by_fd_rejected"))
+ signed_map_by_fd_rejected();
+ if (test__start_subtest("signed_sparse_fd_array_rejected"))
+ signed_sparse_fd_array_rejected();
}
diff --git a/tools/testing/selftests/bpf/progs/test_signed_loader.c b/tools/testing/selftests/bpf/progs/test_signed_loader.c
index d9a4b85f9391f5..50451a69b99a27 100644
--- a/tools/testing/selftests/bpf/progs/test_signed_loader.c
+++ b/tools/testing/selftests/bpf/progs/test_signed_loader.c
@@ -4,10 +4,11 @@
/*
* Minimal, map-less program. Driven through libbpf's gen_loader (gen_hash)
- * by prog_tests/signed_loader.c so the generated light-skeleton loader (with
- * the emit_signature_match metadata check) can be exercised against good
- * and tampered metadata. A socket filter needs no load-time attach resolution,
- * and having no maps keeps the generated loader's ctx trivial (0 maps, 1 prog).
+ * by prog_tests/signed_loader.c so the generated light-skeleton loader can be
+ * exercised against good and tampered metadata, which the kernel now verifies
+ * at load time via the insns||metadata signature. A socket filter needs no
+ * load-time attach resolution, and having no maps keeps the generated loader's
+ * ctx trivial (0 maps, 1 prog).
*/
SEC("socket")
int probe(void *ctx)
diff --git a/tools/testing/selftests/bpf/progs/verifier_map_ptr.c b/tools/testing/selftests/bpf/progs/verifier_map_ptr.c
index 1661936598703c..e0a65835c861bb 100644
--- a/tools/testing/selftests/bpf/progs/verifier_map_ptr.c
+++ b/tools/testing/selftests/bpf/progs/verifier_map_ptr.c
@@ -72,14 +72,15 @@ __naked void bpf_map_ptr_write_rejected(void)
/*
* struct bpf_map starts with the SHA256 hash sha[32] at offset 0 (a readable
- * byte array), the u32 excl field at offset 32, and the ops pointer at offset
- * 40. Reading a u32 at offset 41 reaches into the middle of the ops pointer,
- * i.e. a partial pointer access, which is rejected.
+ * byte array), followed by the ops pointer at offset 32 and the inner_map_meta
+ * pointer at offset 40. Reading a u32 at offset 41 reaches into the middle of
+ * the inner_map_meta pointer, i.e. a partial pointer access, which is
+ * rejected.
*/
SEC("socket")
__description("bpf_map_ptr: read non-existent field rejected")
__failure
-__msg("cannot access ptr member ops with moff 40 in struct bpf_map with off 41 size 4")
+__msg("cannot access ptr member inner_map_meta with moff 40 in struct bpf_map with off 41 size 4")
__failure_unpriv
__msg_unpriv("access is allowed only to CAP_PERFMON and CAP_SYS_ADMIN")
__flag(BPF_F_ANY_ALIGNMENT)
@@ -97,23 +98,23 @@ __naked void read_non_existent_field_rejected(void)
}
/*
- * The u32 excl field spans offsets 32..35 (mend 36). Reading a u32 at offset
- * 33 starts inside excl but extends past its end, which the verifier rejects
+ * The sha byte array spans offsets 0..31 (mend 32). Reading a u32 at offset
+ * 30 starts inside sha but extends past its end, which the verifier rejects
* as an out-of-bounds scalar access.
*/
SEC("socket")
-__description("bpf_map_ptr: read beyond excl field rejected")
+__description("bpf_map_ptr: read beyond sha field rejected")
__failure
-__msg("access beyond the end of member excl (mend:36) in struct bpf_map with off 33 size 4")
+__msg("access beyond the end of member sha (mend:32) in struct bpf_map with off 30 size 4")
__failure_unpriv
__msg_unpriv("access is allowed only to CAP_PERFMON and CAP_SYS_ADMIN")
__flag(BPF_F_ANY_ALIGNMENT)
-__naked void read_beyond_excl_field_rejected(void)
+__naked void read_beyond_sha_field_rejected(void)
{
asm volatile (" \
r6 = 0; \
r1 = %[map_array_48b] ll; \
- r6 = *(u32*)(r1 + 33); \
+ r6 = *(u32*)(r1 + 30); \
r0 = 1; \
exit; \
" :
@@ -131,7 +132,7 @@ __naked void ptr_read_ops_field_accepted(void)
asm volatile (" \
r6 = 0; \
r1 = %[map_array_48b] ll; \
- r6 = *(u64*)(r1 + 40); \
+ r6 = *(u64*)(r1 + 32); \
r0 = 1; \
exit; \
" :
|
| 3/2 | 2026/07/06 20:24 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 43192 output: 24 thoughts: 640 Thoughts: **My Assessment of the BPF Signing Patch Series** Okay, so I'm looking at this patch series aimed at introducing BPF signing. It seems like a pretty significant shift, centralizing the signature verification process within the kernel verifier itself. My initial thought is this could have serious implications, both in terms of security and performance, and will warrant a thorough review. My immediate impression is that the changes are fairly extensive, touching multiple files across the kernel and tooling. I see new documentation with `Documentation/bpf/signing.rst`, which is a good sign – that's something to delve into. The core kernel files affected – `include/linux/bpf.h`, `include/linux/bpf_verifier.h`, `kernel/bpf/syscall.c`, `kernel/bpf/verifier.c` – clearly indicate the heart of the change: integrating the signing functionality directly into the BPF verification process. The modifications to `bpftool` suggest added tooling support for generating and managing signatures, which is important. And the selftests are a critical indicator that they considered a lot of testcases. The key functional shift is moving the signature verification from userspace/loader into the kernel verifier. Specifically, `bpf_prog_verify_signature` is the function that's being moved. I'm noting the addition of `fd_array_cnt` to `union bpf_attr` and the handling of continuous `fd_array` with `process_fd_array_continuous` as well. The presence of `env->signature` check within `bpf_check` and the subsequent call to `bpf_prog_verify_signature` shows how the signing integration is being implemented within the existing verification framework. My attention is now focused on `kernel/bpf/verifier.c` and `kernel/bpf/syscall.c` to see the exact nature of these modifications. It seems the signature verification is moving from `syscall.c` to `verifier.c` which, on the face of it, makes sense. But it requires some detailed checking, which I'm already doing. The modification of `process_fd_array` is relevant, since that deals with file descriptors and maps. And the addition of `fd_array_get_map_idx` and `fd_array_get_btf` means a new way of dealing with existing structures. Given these kernel modifications, I'm thinking this is definitely worth fuzzing. The changes touch core BPF verifier logic, meaning any vulnerabilities here could be quite impactful. My fuzzing targets will be: `bpf_prog_verify_signature`, `process_fd_array`, `process_fd_array_continuous`, `fd_array_get_map_idx`, `fd_array_get_btf`, and `add_fd_from_fd_array`. These functions are now critical. I need to be sure that `bpf_prog_verify_signature` is reachable in all the required configurations, and in all the different code paths. I'll need to check the required configs for BPF signing to ensure the tests are comprehensive. I'll need to search for `bpf_prog_verify_signature` or `bpf_verify_pkcs7_signature` to look at how these functions are being called. |
| 4/2 | 2026/07/06 20:25 | tool | grepper |
0mArgs:{
"Expression": "bpf_verify_pkcs7_signature"
}Results: {
"Output": "include/linux/bpf.h=3822=void bpf_key_put(struct bpf_key *bkey);\ninclude/linux/bpf.h:3823:int bpf_verify_pkcs7_signature(const struct bpf_dynptr *data_p,\ninclude/linux/bpf.h-3824-\t\t\t const struct bpf_dynptr *sig_p,\n--\ninclude/linux/bpf.h=3842=static inline void bpf_key_put(struct bpf_key *bkey)\n--\ninclude/linux/bpf.h-3845-\ninclude/linux/bpf.h:3846:static inline int bpf_verify_pkcs7_signature(const struct bpf_dynptr *data_p,\ninclude/linux/bpf.h-3847-\t\t\t\t\t const struct bpf_dynptr *sig_p,\n--\nkernel/bpf/helpers.c=4297=__bpf_kfunc void bpf_key_put(struct bpf_key *bkey)\n--\nkernel/bpf/helpers.c-4305-/**\nkernel/bpf/helpers.c:4306: * bpf_verify_pkcs7_signature - verify a PKCS#7 signature\nkernel/bpf/helpers.c-4307- * @data_p: data to verify\n--\nkernel/bpf/helpers.c-4315- */\nkernel/bpf/helpers.c:4316:__bpf_kfunc int bpf_verify_pkcs7_signature(const struct bpf_dynptr *data_p,\nkernel/bpf/helpers.c-4317-\t\t\t const struct bpf_dynptr *sig_p,\n--\nkernel/bpf/helpers.c=4856=BTF_ID_FLAGS(func, bpf_key_put, KF_RELEASE)\nkernel/bpf/helpers.c-4857-#ifdef CONFIG_SYSTEM_DATA_VERIFICATION\nkernel/bpf/helpers.c:4858:BTF_ID_FLAGS(func, bpf_verify_pkcs7_signature, KF_SLEEPABLE)\nkernel/bpf/helpers.c-4859-#endif\n--\nkernel/bpf/verifier.c=19923=static int bpf_prog_verify_signature(struct bpf_verifier_env *env,\n--\nkernel/bpf/verifier.c-20015-\nkernel/bpf/verifier.c:20016:\terr = bpf_verify_pkcs7_signature((struct bpf_dynptr *)\u0026data_ptr,\nkernel/bpf/verifier.c-20017-\t\t\t\t\t (struct bpf_dynptr *)\u0026sig_ptr, key);\n--\ntools/testing/selftests/bpf/bpf_kfuncs.h=77=extern void bpf_key_put(struct bpf_key *key) __ksym;\ntools/testing/selftests/bpf/bpf_kfuncs.h:78:extern int bpf_verify_pkcs7_signature(const struct bpf_dynptr *data_ptr,\ntools/testing/selftests/bpf/bpf_kfuncs.h-79-\t\t\t\t const struct bpf_dynptr *sig_ptr,\n--\ntools/testing/selftests/bpf/prog_tests/kfunc_dynptr_param.c=22=static int libbpf_print_cb(enum libbpf_print_level level, const char *fmt,\n--\ntools/testing/selftests/bpf/prog_tests/kfunc_dynptr_param.c-27-\ntools/testing/selftests/bpf/prog_tests/kfunc_dynptr_param.c:28:\tif (strcmp(va_arg(args, char *), \"bpf_verify_pkcs7_signature\"))\ntools/testing/selftests/bpf/prog_tests/kfunc_dynptr_param.c-29-\t\treturn 0;\n--\ntools/testing/selftests/bpf/prog_tests/kfunc_dynptr_param.c=35=static bool has_pkcs7_kfunc_support(void)\n--\ntools/testing/selftests/bpf/prog_tests/kfunc_dynptr_param.c-52-\t\tfprintf(stderr,\ntools/testing/selftests/bpf/prog_tests/kfunc_dynptr_param.c:53:\t\t \"%s:SKIP:bpf_verify_pkcs7_signature() kfunc not supported\\n\",\ntools/testing/selftests/bpf/prog_tests/kfunc_dynptr_param.c-54-\t\t __func__);\n--\ntools/testing/selftests/bpf/prog_tests/verify_pkcs7_sig.c=46=static int libbpf_print_cb(enum libbpf_print_level level, const char *fmt,\n--\ntools/testing/selftests/bpf/prog_tests/verify_pkcs7_sig.c-54-\ntools/testing/selftests/bpf/prog_tests/verify_pkcs7_sig.c:55:\tif (strcmp(va_arg(args, char *), \"bpf_verify_pkcs7_signature\"))\ntools/testing/selftests/bpf/prog_tests/verify_pkcs7_sig.c-56-\t\treturn 0;\n--\ntools/testing/selftests/bpf/prog_tests/verify_pkcs7_sig.c=242=static void test_verify_pkcs7_sig_from_map(void)\n--\ntools/testing/selftests/bpf/prog_tests/verify_pkcs7_sig.c-273-\t\tprintf(\ntools/testing/selftests/bpf/prog_tests/verify_pkcs7_sig.c:274:\t\t \"%s:SKIP:bpf_verify_pkcs7_signature() kfunc not supported\\n\",\ntools/testing/selftests/bpf/prog_tests/verify_pkcs7_sig.c-275-\t\t __func__);\n--\ntools/testing/selftests/bpf/progs/test_kfunc_dynptr_param.c=34=int BPF_PROG(not_valid_dynptr, int cmd, union bpf_attr *attr, unsigned int size, bool kernel)\n--\ntools/testing/selftests/bpf/progs/test_kfunc_dynptr_param.c-37-\ntools/testing/selftests/bpf/progs/test_kfunc_dynptr_param.c:38:\treturn bpf_verify_pkcs7_signature((struct bpf_dynptr *)\u0026val,\ntools/testing/selftests/bpf/progs/test_kfunc_dynptr_param.c-39-\t\t\t\t\t (struct bpf_dynptr *)\u0026val, NULL);\n--\ntools/testing/selftests/bpf/progs/test_kfunc_dynptr_param.c=44=int BPF_PROG(not_ptr_to_stack, int cmd, union bpf_attr *attr, unsigned int size, bool kernel)\n--\ntools/testing/selftests/bpf/progs/test_kfunc_dynptr_param.c-47-\ntools/testing/selftests/bpf/progs/test_kfunc_dynptr_param.c:48:\treturn bpf_verify_pkcs7_signature(\u0026val, \u0026val, NULL);\ntools/testing/selftests/bpf/progs/test_kfunc_dynptr_param.c-49-}\n--\ntools/testing/selftests/bpf/progs/test_kfunc_dynptr_param.c=52=int BPF_PROG(dynptr_data_null, int cmd, union bpf_attr *attr, unsigned int size, bool kernel)\n--\ntools/testing/selftests/bpf/progs/test_kfunc_dynptr_param.c-74-\ntools/testing/selftests/bpf/progs/test_kfunc_dynptr_param.c:75:\terr = bpf_verify_pkcs7_signature(\u0026ptr, \u0026ptr, trusted_keyring);\ntools/testing/selftests/bpf/progs/test_kfunc_dynptr_param.c-76-\n--\ntools/testing/selftests/bpf/progs/test_sig_in_xattr.c=46=int BPF_PROG(test_file_open, struct file *f)\n--\ntools/testing/selftests/bpf/progs/test_sig_in_xattr.c-79-\t/* Verify signature */\ntools/testing/selftests/bpf/progs/test_sig_in_xattr.c:80:\tret = bpf_verify_pkcs7_signature(\u0026digest_ptr, \u0026sig_ptr, trusted_keyring);\ntools/testing/selftests/bpf/progs/test_sig_in_xattr.c-81-\n--\ntools/testing/selftests/bpf/progs/test_verify_pkcs7_sig.c=40=int BPF_PROG(bpf, int cmd, union bpf_attr *attr, unsigned int size, bool kernel)\n--\ntools/testing/selftests/bpf/progs/test_verify_pkcs7_sig.c-83-\ntools/testing/selftests/bpf/progs/test_verify_pkcs7_sig.c:84:\tret = bpf_verify_pkcs7_signature(\u0026data_ptr, \u0026sig_ptr, trusted_keyring);\ntools/testing/selftests/bpf/progs/test_verify_pkcs7_sig.c-85-\n"
} |
| 5/2 | 2026/07/06 20:25 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 46981 output: 35 thoughts: 112 Thoughts: **Investigating the BPF Signature Verification Functions** Okay, so I'm looking at how signature verification is handled within the BPF subsystem, specifically the PKCS#7 signature verification. I see that the function `bpf_verify_pkcs7_signature` is defined in `kernel/bpf/helpers.c`. And it seems like it's linked to the `CONFIG_SYSTEM_DATA_VERIFICATION` config option. But then, I also see that `bpf_prog_verify_signature`, located in `kernel/bpf/verifier.c`, *calls* `bpf_verify_pkcs7_signature`. Now I need to understand this better. Is `bpf_verify_pkcs7_signature` always available, or is its inclusion conditional on `CONFIG_SYSTEM_DATA_VERIFICATION`? This could impact the overall availability and behavior of BPF program signature validation. I'll need to dig deeper into the code and potential build configurations to clarify this dependency. |
| 6/2 | 2026/07/06 20:25 | tool | grepper |
0mArgs:{
"Expression": "bpf_verify_pkcs7_signature",
"PathPrefix": "kernel/bpf/"
}Results: {
"Output": "kernel/bpf/helpers.c=4297=__bpf_kfunc void bpf_key_put(struct bpf_key *bkey)\n--\nkernel/bpf/helpers.c-4305-/**\nkernel/bpf/helpers.c:4306: * bpf_verify_pkcs7_signature - verify a PKCS#7 signature\nkernel/bpf/helpers.c-4307- * @data_p: data to verify\n--\nkernel/bpf/helpers.c-4315- */\nkernel/bpf/helpers.c:4316:__bpf_kfunc int bpf_verify_pkcs7_signature(const struct bpf_dynptr *data_p,\nkernel/bpf/helpers.c-4317-\t\t\t const struct bpf_dynptr *sig_p,\n--\nkernel/bpf/helpers.c=4856=BTF_ID_FLAGS(func, bpf_key_put, KF_RELEASE)\nkernel/bpf/helpers.c-4857-#ifdef CONFIG_SYSTEM_DATA_VERIFICATION\nkernel/bpf/helpers.c:4858:BTF_ID_FLAGS(func, bpf_verify_pkcs7_signature, KF_SLEEPABLE)\nkernel/bpf/helpers.c-4859-#endif\n--\nkernel/bpf/verifier.c=19923=static int bpf_prog_verify_signature(struct bpf_verifier_env *env,\n--\nkernel/bpf/verifier.c-20015-\nkernel/bpf/verifier.c:20016:\terr = bpf_verify_pkcs7_signature((struct bpf_dynptr *)\u0026data_ptr,\nkernel/bpf/verifier.c-20017-\t\t\t\t\t (struct bpf_dynptr *)\u0026sig_ptr, key);\n"
} |
| 7/2 | 2026/07/06 20:25 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 47806 output: 37 thoughts: 48 Thoughts: **Investigating the BPF Signature Verification Function** Okay, so I'm looking at `bpf_verify_pkcs7_signature` and need to quickly figure out its conditional compilation. I remember seeing its definition in `kernel/bpf/helpers.c`. My next thought is to jump directly to that file to check for any `#ifdef` preprocessor directives. I'm specifically interested in whether it's guarded by the `CONFIG_SYSTEM_DATA_VERIFICATION` flag. That configuration option often relates to enabling security features, so I suspect this function might be tied to a broader system data verification system. Understanding its conditional nature is crucial for figuring out when and how this signature verification is active in the kernel. This will likely inform me about the scenarios in which signature checks are performed on BPF programs or related data. I'm going to look for that `#ifdef` statement right now. |
| 8/2 | 2026/07/06 20:25 | tool | read-file |
0mArgs:{
"File": "kernel/bpf/helpers.c",
"FirstLine": 4300,
"LineCount": 50
}Results: {
"Contents": "4300:\t\t\tkey_put(bkey-\u003ekey);\n4301:\t\n4302:\t\tkfree(bkey);\n4303:\t}\n4304:\t\n4305:\t/**\n4306:\t * bpf_verify_pkcs7_signature - verify a PKCS#7 signature\n4307:\t * @data_p: data to verify\n4308:\t * @sig_p: signature of the data\n4309:\t * @trusted_keyring: keyring with keys trusted for signature verification\n4310:\t *\n4311:\t * Verify the PKCS#7 signature *sig_ptr* against the supplied *data_ptr*\n4312:\t * with keys in a keyring referenced by *trusted_keyring*.\n4313:\t *\n4314:\t * Return: 0 on success, a negative value on error.\n4315:\t */\n4316:\t__bpf_kfunc int bpf_verify_pkcs7_signature(const struct bpf_dynptr *data_p,\n4317:\t\t\t\t const struct bpf_dynptr *sig_p,\n4318:\t\t\t\t struct bpf_key *trusted_keyring)\n4319:\t{\n4320:\t#ifdef CONFIG_SYSTEM_DATA_VERIFICATION\n4321:\t\tconst struct bpf_dynptr_kern *data_ptr = (struct bpf_dynptr_kern *)data_p;\n4322:\t\tconst struct bpf_dynptr_kern *sig_ptr = (struct bpf_dynptr_kern *)sig_p;\n4323:\t\tconst void *data, *sig;\n4324:\t\tu32 data_len, sig_len;\n4325:\t\tint ret;\n4326:\t\n4327:\t\tif (trusted_keyring-\u003ehas_ref) {\n4328:\t\t\t/*\n4329:\t\t\t * Do the permission check deferred in bpf_lookup_user_key().\n4330:\t\t\t * See bpf_lookup_user_key() for more details.\n4331:\t\t\t *\n4332:\t\t\t * A call to key_task_permission() here would be redundant, as\n4333:\t\t\t * it is already done by keyring_search() called by\n4334:\t\t\t * find_asymmetric_key().\n4335:\t\t\t */\n4336:\t\t\tret = key_validate(trusted_keyring-\u003ekey);\n4337:\t\t\tif (ret \u003c 0)\n4338:\t\t\t\treturn ret;\n4339:\t\t}\n4340:\t\n4341:\t\tdata_len = __bpf_dynptr_size(data_ptr);\n4342:\t\tdata = __bpf_dynptr_data(data_ptr, data_len);\n4343:\t\tif (!data)\n4344:\t\t\treturn -EINVAL;\n4345:\t\n4346:\t\tsig_len = __bpf_dynptr_size(sig_ptr);\n4347:\t\tsig = __bpf_dynptr_data(sig_ptr, sig_len);\n4348:\t\tif (!sig)\n4349:\t\t\treturn -EINVAL;\n"
} |
| 9/2 | 2026/07/06 20:25 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 48903 output: 37 thoughts: 57 Thoughts: Here's my summary, as if I were thinking these thoughts: **Analyzing the BPF PKCS7 Verification Function** Okay, so I'm looking at `bpf_verify_pkcs7_signature` here. Interesting... it's defined regardless, which is a good starting point. But, as I suspected, the actual *implementation* is conditional, based on `#ifdef CONFIG_SYSTEM_DATA_VERIFICATION`. My gut feeling is that if that config option isn't set, the function probably returns `-EOPNOTSUPP`. That would be a standard way to signal "operation not supported" in this context. I should double-check that to be absolutely sure, though – no assumptions without confirmation! I'll quickly verify that return path within the code. |
| 10/2 | 2026/07/06 20:25 | tool | read-file |
0mArgs:{
"File": "kernel/bpf/helpers.c",
"FirstLine": 4350,
"LineCount": 50
}Results: {
"Contents": "4350:\t\n4351:\t\treturn verify_pkcs7_signature(data, data_len, sig, sig_len,\n4352:\t\t\t\t\t trusted_keyring-\u003ekey,\n4353:\t\t\t\t\t VERIFYING_BPF_SIGNATURE, NULL,\n4354:\t\t\t\t\t NULL);\n4355:\t#else\n4356:\t\treturn -EOPNOTSUPP;\n4357:\t#endif /* CONFIG_SYSTEM_DATA_VERIFICATION */\n4358:\t}\n4359:\t#endif /* CONFIG_KEYS */\n4360:\t\n4361:\ttypedef int (*bpf_task_work_callback_t)(struct bpf_map *map, void *key, void *value);\n4362:\t\n4363:\tenum bpf_task_work_state {\n4364:\t\t/* bpf_task_work is ready to be used */\n4365:\t\tBPF_TW_STANDBY = 0,\n4366:\t\t/* irq work scheduling in progress */\n4367:\t\tBPF_TW_PENDING,\n4368:\t\t/* task work scheduling in progress */\n4369:\t\tBPF_TW_SCHEDULING,\n4370:\t\t/* task work is scheduled successfully */\n4371:\t\tBPF_TW_SCHEDULED,\n4372:\t\t/* callback is running */\n4373:\t\tBPF_TW_RUNNING,\n4374:\t\t/* associated BPF map value is deleted */\n4375:\t\tBPF_TW_FREED,\n4376:\t};\n4377:\t\n4378:\tstruct bpf_task_work_ctx {\n4379:\t\tenum bpf_task_work_state state;\n4380:\t\trefcount_t refcnt;\n4381:\t\tstruct callback_head work;\n4382:\t\tstruct irq_work irq_work;\n4383:\t\t/* bpf_prog that schedules task work */\n4384:\t\tstruct bpf_prog *prog;\n4385:\t\t/* task for which callback is scheduled */\n4386:\t\tstruct task_struct *task;\n4387:\t\t/* the map and map value associated with this context */\n4388:\t\tstruct bpf_map *map;\n4389:\t\tvoid *map_val;\n4390:\t\tenum task_work_notify_mode mode;\n4391:\t\tbpf_task_work_callback_t callback_fn;\n4392:\t\tstruct rcu_head rcu;\n4393:\t} __aligned(8);\n4394:\t\n4395:\t/* Actual type for struct bpf_task_work */\n4396:\tstruct bpf_task_work_kern {\n4397:\t\tstruct bpf_task_work_ctx *ctx;\n4398:\t};\n4399:\t\n"
} |
| 11/2 | 2026/07/06 20:25 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 49917 output: 39 thoughts: 60 Thoughts: **Examining the BPF Verification Code** Okay, hold on a second. I just realized that `bpf_verify_pkcs7_signature` is also conditional, protected by the `#ifdef CONFIG_KEYS` directive. That's good to know, makes sense with the configuration options. Now, I should double check to ensure the `bpf_prog_verify_signature` is either always compiled or also conditionally built. So, I'm going to take a look at the code in `kernel/bpf/verifier.c` to clarify that specific detail. Gotta make sure I have a complete picture. |
| 12/2 | 2026/07/06 20:26 | tool | read-file |
0mArgs:{
"File": "kernel/bpf/verifier.c",
"FirstLine": 19890,
"LineCount": 100
}Results: {
"Contents": "19890:\t\t\tint idx = *cnt;\n19891:\t\n19892:\t\t\tinsn_buf[idx++] = ld_addrs[0];\n19893:\t\t\tinsn_buf[idx++] = ld_addrs[1];\n19894:\t\t\tinsn_buf[idx++] = *insn;\n19895:\t\t\t*cnt = idx;\n19896:\t\t}\n19897:\t\treturn 0;\n19898:\t}\n19899:\t\n19900:\tstatic enum bpf_sig_keyring bpf_classify_keyring(s32 keyring_id)\n19901:\t{\n19902:\t\tswitch (keyring_id) {\n19903:\t\tcase 0:\n19904:\t\t\treturn BPF_SIG_KEYRING_BUILTIN;\n19905:\t\tcase (s32)(unsigned long)VERIFY_USE_SECONDARY_KEYRING:\n19906:\t\t\treturn BPF_SIG_KEYRING_SECONDARY;\n19907:\t\tcase (s32)(unsigned long)VERIFY_USE_PLATFORM_KEYRING:\n19908:\t\t\treturn BPF_SIG_KEYRING_PLATFORM;\n19909:\t\tdefault:\n19910:\t\t\treturn BPF_SIG_KEYRING_USER;\n19911:\t\t}\n19912:\t}\n19913:\t\n19914:\t/*\n19915:\t * Verify the PKCS#7 signature of a loaded program. Called from bpf_check()\n19916:\t * once the program's metadata maps have been resolved into used_maps, so\n19917:\t * the exact maps folded into the signature are the ones the program binds.\n19918:\t *\n19919:\t * The signature covers the instructions followed by the frozen contents of\n19920:\t * each map, in @maps order: insns || map_0 || map_1 || [...]. On success the\n19921:\t * verdict and keyring info are recorded on prog-\u003eaux.\n19922:\t */\n19923:\tstatic int bpf_prog_verify_signature(struct bpf_verifier_env *env,\n19924:\t\t\t\t\t union bpf_attr *attr, bool is_kernel)\n19925:\t{\n19926:\t\tbpfptr_t usig = make_bpfptr(attr-\u003esignature, is_kernel);\n19927:\t\tstruct bpf_dynptr_kern sig_ptr, data_ptr;\n19928:\t\tstruct bpf_prog *prog = env-\u003eprog;\n19929:\t\tstruct bpf_map **maps = env-\u003eused_maps;\n19930:\t\tstruct bpf_key *key = NULL;\n19931:\t\tvoid *sig, *data = NULL;\n19932:\t\tu32 map_cnt = env-\u003eused_map_cnt;\n19933:\t\tu32 i, off, insns_sz;\n19934:\t\tu64 data_sz;\n19935:\t\tint err = 0;\n19936:\t\n19937:\t\t/*\n19938:\t\t * Don't attempt to use kmalloc_large or vmalloc for signatures.\n19939:\t\t * Practical signature for BPF program should be below this limit.\n19940:\t\t */\n19941:\t\tif (attr-\u003esignature_size \u003e KMALLOC_MAX_CACHE_SIZE)\n19942:\t\t\treturn -EINVAL;\n19943:\t\tif (system_keyring_id_check(attr-\u003ekeyring_id) == 0)\n19944:\t\t\tkey = bpf_lookup_system_key(attr-\u003ekeyring_id);\n19945:\t\telse\n19946:\t\t\tkey = bpf_lookup_user_key(attr-\u003ekeyring_id, 0);\n19947:\t\tif (!key) {\n19948:\t\t\tverbose(env, \"cannot resolve signing keyring with keyring_id %d\\n\",\n19949:\t\t\t\tattr-\u003ekeyring_id);\n19950:\t\t\treturn -EINVAL;\n19951:\t\t}\n19952:\t\n19953:\t\tsig = kvmemdup_bpfptr(usig, attr-\u003esignature_size);\n19954:\t\tif (IS_ERR(sig)) {\n19955:\t\t\tbpf_key_put(key);\n19956:\t\t\treturn PTR_ERR(sig);\n19957:\t\t}\n19958:\t\n19959:\t\tinsns_sz = prog-\u003elen * sizeof(struct bpf_insn);\n19960:\t\tdata_sz = insns_sz;\n19961:\t\tfor (i = 0; i \u003c map_cnt; i++) {\n19962:\t\t\tstruct bpf_map *map = maps[i];\n19963:\t\n19964:\t\t\tif (map-\u003emap_type != BPF_MAP_TYPE_ARRAY ||\n19965:\t\t\t !map-\u003eops-\u003emap_direct_value_addr) {\n19966:\t\t\t\tverbose(env, \"signed program metadata map '%s' must be an array\\n\",\n19967:\t\t\t\t\tmap-\u003ename);\n19968:\t\t\t\terr = -EINVAL;\n19969:\t\t\t\tgoto out;\n19970:\t\t\t}\n19971:\t\t\tif (!READ_ONCE(map-\u003efrozen)) {\n19972:\t\t\t\tverbose(env, \"signed program metadata map '%s' must be frozen\\n\",\n19973:\t\t\t\t\tmap-\u003ename);\n19974:\t\t\t\terr = -EPERM;\n19975:\t\t\t\tgoto out;\n19976:\t\t\t}\n19977:\t\t\tif (!map-\u003eexcl_prog_sha) {\n19978:\t\t\t\tverbose(env, \"signed program metadata map '%s' must be exclusive\\n\",\n19979:\t\t\t\t\tmap-\u003ename);\n19980:\t\t\t\terr = -EPERM;\n19981:\t\t\t\tgoto out;\n19982:\t\t\t}\n19983:\t\t\tdata_sz += map-\u003evalue_size;\n19984:\t\t}\n19985:\t\tif (bpf_dynptr_check_size(data_sz)) {\n19986:\t\t\tverbose(env, \"signed payload too large: %llu bytes\\n\", data_sz);\n19987:\t\t\terr = -E2BIG;\n19988:\t\t\tgoto out;\n19989:\t\t}\n"
} |
| 13/2 | 2026/07/06 20:26 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 51875 output: 37 thoughts: 107 Thoughts: **Unraveling the Conditional Compilation of BPF Signature Verification** Okay, so `bpf_prog_verify_signature` is always compiled, which seems straightforward enough. But here's the kicker: it *calls* `bpf_verify_pkcs7_signature`. And *that* function, as I recall, is tucked away in `kernel/bpf/helpers.c` and is conditional on `#ifdef CONFIG_KEYS`. This immediately raises a flag. If `CONFIG_KEYS` isn't enabled, shouldn't the compiler be throwing a fit? Is `bpf_verify_pkcs7_signature` even *defined* in the absence of `CONFIG_KEYS`? It feels like a potential build-time issue just waiting to surface. I'd better double-check the includes, specifically `include/linux/bpf.h`, to see if my memory is failing me or if there's a disconnect somewhere. Let me see... I need to verify how `bpf_verify_pkcs7_signature` is actually declared in that header file. Time to make sure this code will actually build correctly! |
| 14/2 | 2026/07/06 20:27 | tool | read-file |
0mArgs:{
"File": "include/linux/bpf.h",
"FirstLine": 3820,
"LineCount": 50
}Results: {
"Contents": "3820:\tstruct bpf_key *bpf_lookup_user_key(s32 serial, u64 flags);\n3821:\tstruct bpf_key *bpf_lookup_system_key(u64 id);\n3822:\tvoid bpf_key_put(struct bpf_key *bkey);\n3823:\tint bpf_verify_pkcs7_signature(const struct bpf_dynptr *data_p,\n3824:\t\t\t\t const struct bpf_dynptr *sig_p,\n3825:\t\t\t\t struct bpf_key *trusted_keyring);\n3826:\t\n3827:\tstatic inline s32 bpf_key_serial(const struct bpf_key *key)\n3828:\t{\n3829:\t\treturn key-\u003ehas_ref ? key-\u003ekey-\u003eserial : 0;\n3830:\t}\n3831:\t#else\n3832:\tstatic inline struct bpf_key *bpf_lookup_user_key(u32 serial, u64 flags)\n3833:\t{\n3834:\t\treturn NULL;\n3835:\t}\n3836:\t\n3837:\tstatic inline struct bpf_key *bpf_lookup_system_key(u64 id)\n3838:\t{\n3839:\t\treturn NULL;\n3840:\t}\n3841:\t\n3842:\tstatic inline void bpf_key_put(struct bpf_key *bkey)\n3843:\t{\n3844:\t}\n3845:\t\n3846:\tstatic inline int bpf_verify_pkcs7_signature(const struct bpf_dynptr *data_p,\n3847:\t\t\t\t\t\t const struct bpf_dynptr *sig_p,\n3848:\t\t\t\t\t\t struct bpf_key *trusted_keyring)\n3849:\t{\n3850:\t\treturn -EOPNOTSUPP;\n3851:\t}\n3852:\t\n3853:\tstatic inline s32 bpf_key_serial(const struct bpf_key *key)\n3854:\t{\n3855:\t\treturn 0;\n3856:\t}\n3857:\t#endif /* defined(CONFIG_KEYS) \u0026\u0026 defined(CONFIG_BPF_SYSCALL) */\n3858:\t\n3859:\t/* verifier prototypes for helper functions called from eBPF programs */\n3860:\textern const struct bpf_func_proto bpf_map_lookup_elem_proto;\n3861:\textern const struct bpf_func_proto bpf_map_update_elem_proto;\n3862:\textern const struct bpf_func_proto bpf_map_delete_elem_proto;\n3863:\textern const struct bpf_func_proto bpf_map_push_elem_proto;\n3864:\textern const struct bpf_func_proto bpf_map_pop_elem_proto;\n3865:\textern const struct bpf_func_proto bpf_map_peek_elem_proto;\n3866:\textern const struct bpf_func_proto bpf_map_lookup_percpu_elem_proto;\n3867:\t\n3868:\textern const struct bpf_func_proto bpf_get_prandom_u32_proto;\n3869:\textern const struct bpf_func_proto bpf_get_smp_processor_id_proto;\n"
} |
| 15/2 | 2026/07/06 20:27 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 53168 output: 21 thoughts: 320 Thoughts: **Analyzing the Kernel's Signature Verification Logic**
Okay, so I'm digging into how the BPF verifier handles signatures, specifically in the absence of certain kernel configurations. My initial thought was that if `CONFIG_KEYS` isn't enabled, `bpf_verify_pkcs7_signature` just bails out with an `-EOPNOTSUPP` error. But wait, it's more nuanced than that. Looking at the code for `bpf_prog_verify_signature`, it calls either `bpf_lookup_system_key` or `bpf_lookup_user_key`. If `CONFIG_KEYS` isn't set, *those* functions actually return `NULL`.
Now, let's trace this further. The code flow in `bpf_prog_verify_signature` does a lookup based on `attr->keyring_id`:
```c
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) {
verbose(env, "cannot resolve signing keyring with keyring_id %d\n",
attr->keyring_id);
return -EINVAL;
}
```
So, if `CONFIG_KEYS` is off, `key` ends up being `NULL` because those lookup functions return `NULL`, and the code *should* return `-EINVAL`. This is an important distinction and means the initial assumption was incorrect.
Consequently, to properly test this particular code path, I'll need *both* `CONFIG_KEYS` and `CONFIG_SYSTEM_DATA_VERIFICATION` enabled. Hang on... isn't `CONFIG_SYSTEM_DATA_VERIFICATION` the flag that enables the PKCS7 signature verification in the first place? Aha! Okay, so I should now go check the `init/Kconfig` or `certs/Kconfig` files to verify how `CONFIG_SYSTEM_DATA_VERIFICATION` is defined and how it relates to this verification code.
|
| 16/2 | 2026/07/06 20:27 | tool | grepper |
0mArgs:{
"Expression": "config SYSTEM_DATA_VERIFICATION"
}Results: {
"Output": "init/Kconfig=2181=endmenu\ninit/Kconfig-2182-\ninit/Kconfig:2183:config SYSTEM_DATA_VERIFICATION\ninit/Kconfig-2184-\tdef_bool n\n"
} |
| 17/2 | 2026/07/06 20:27 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 54057 output: 21 thoughts: 56 Thoughts: Okay, here's my take on those thoughts, framed as an expert's internal monologue: **Investigating CONFIG_SYSTEM_DATA_VERIFICATION** Alright, so `CONFIG_SYSTEM_DATA_VERIFICATION` is a boolean, straight forward enough. The real question is, what's *driving* its selection? It's clearly a dependency for things like `MODULE_SIG`, `KEXEC_SIG`, and `BPF_SYSCALL`, and I need to understand the larger context. Is there anything else that enables it? Need to quickly trace that back and check the dependencies. Let's dig in and see what's going on upstream. |
| 18/2 | 2026/07/06 20:27 | tool | grepper |
0mArgs:{
"Expression": "select SYSTEM_DATA_VERIFICATION"
}Results: {
"Output": "arch/s390/Kconfig=75=config S390\n--\narch/s390/Kconfig-278-\tselect SYSCTL_EXCEPTION_TRACE\narch/s390/Kconfig:279:\tselect SYSTEM_DATA_VERIFICATION if KEXEC_SIG\narch/s390/Kconfig-280-\tselect THREAD_INFO_IN_TASK\n--\ndrivers/md/Kconfig=581=config DM_VERITY_VERIFY_ROOTHASH_SIG\n--\ndrivers/md/Kconfig-583-\tdepends on DM_VERITY\ndrivers/md/Kconfig:584:\tselect SYSTEM_DATA_VERIFICATION\ndrivers/md/Kconfig-585-\thelp\n--\nfs/ubifs/Kconfig=89=config UBIFS_FS_AUTHENTICATION\n--\nfs/ubifs/Kconfig-92-\tselect CRYPTO_HMAC\nfs/ubifs/Kconfig:93:\tselect SYSTEM_DATA_VERIFICATION\nfs/ubifs/Kconfig-94-\thelp\n--\nfs/verity/Kconfig=34=config FS_VERITY_BUILTIN_SIGNATURES\n--\nfs/verity/Kconfig-36-\tdepends on FS_VERITY\nfs/verity/Kconfig:37:\tselect SYSTEM_DATA_VERIFICATION\nfs/verity/Kconfig-38-\thelp\n--\ninit/Kconfig=2267=config MODULE_SIG_FORMAT\ninit/Kconfig-2268-\tdef_bool n\ninit/Kconfig:2269:\tselect SYSTEM_DATA_VERIFICATION\ninit/Kconfig-2270-\n--\nnet/wireless/Kconfig=89=config CFG80211_REQUIRE_SIGNED_REGDB\n--\nnet/wireless/Kconfig-91-\tdefault y\nnet/wireless/Kconfig:92:\tselect SYSTEM_DATA_VERIFICATION\nnet/wireless/Kconfig-93-\thelp\n--\nsecurity/ipe/Kconfig=6=menuconfig SECURITY_IPE\n--\nsecurity/ipe/Kconfig-10-\tselect PKCS7_MESSAGE_PARSER\nsecurity/ipe/Kconfig:11:\tselect SYSTEM_DATA_VERIFICATION\nsecurity/ipe/Kconfig-12-\tselect IPE_PROP_DM_VERITY if DM_VERITY\n"
} |
| 19/2 | 2026/07/06 20:27 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 54853 output: 18 thoughts: 124 Thoughts: **Navigating the Kconfig Labyrinth** Okay, so the goal is to enable `CONFIG_SYSTEM_DATA_VERIFICATION`. My initial thought was to just set it directly, but then I realized it's defined as a `def_bool n`. That means I can't enable it unless there's a prompt associated with it. But a quick check reveals there's no visible prompt. So, I need to find a different strategy. I need to enable something that *selects* `SYSTEM_DATA_VERIFICATION`. I had the idea to use `CONFIG_MODULE_SIG_FORMAT` or `CONFIG_FS_VERITY_BUILTIN_SIGNATURES`. Unfortunately, `CONFIG_MODULE_SIG_FORMAT` is also a `def_bool n`. Looks like I can't use it. Okay, time to dig deeper. I'll focus my search on `MODULE_SIG` and will check `init/Kconfig` to see what is going on there. |
| 20/2 | 2026/07/06 20:27 | tool | grepper |
0mArgs:{
"Expression": "config MODULE_SIG"
}Results: {
"Output": "certs/Kconfig=2=menu \"Certificates for signature checking\"\ncerts/Kconfig-3-\ncerts/Kconfig:4:config MODULE_SIG_KEY\ncerts/Kconfig-5-\tstring \"File name or PKCS#11 URI of module signing key\"\n--\ncerts/Kconfig=18=choice\n--\ncerts/Kconfig-24-\ncerts/Kconfig:25:config MODULE_SIG_KEY_TYPE_RSA\ncerts/Kconfig-26-\tbool \"RSA\"\n--\ncerts/Kconfig-29-\ncerts/Kconfig:30:config MODULE_SIG_KEY_TYPE_ECDSA\ncerts/Kconfig-31-\tbool \"ECDSA\"\n--\ncerts/Kconfig-41-\ncerts/Kconfig:42:config MODULE_SIG_KEY_TYPE_MLDSA_44\ncerts/Kconfig-43-\tbool \"ML-DSA-44\"\n--\ncerts/Kconfig-52-\ncerts/Kconfig:53:config MODULE_SIG_KEY_TYPE_MLDSA_65\ncerts/Kconfig-54-\tbool \"ML-DSA-65\"\n--\ncerts/Kconfig-63-\ncerts/Kconfig:64:config MODULE_SIG_KEY_TYPE_MLDSA_87\ncerts/Kconfig-65-\tbool \"ML-DSA-87\"\n--\ninit/Kconfig=2263=config RT_MUTEXES\n--\ninit/Kconfig-2266-\ninit/Kconfig:2267:config MODULE_SIG_FORMAT\ninit/Kconfig-2268-\tdef_bool n\n--\nkernel/module/Kconfig=241=config MODULE_SRCVERSION_ALL\n--\nkernel/module/Kconfig-251-\nkernel/module/Kconfig:252:config MODULE_SIG\nkernel/module/Kconfig-253-\tbool \"Module signature verification\"\n--\nkernel/module/Kconfig-273-\nkernel/module/Kconfig:274:config MODULE_SIG_FORCE\nkernel/module/Kconfig-275-\tbool \"Require modules to be validly signed\"\n--\nkernel/module/Kconfig=281=if MODULE_SIG || IMA_APPRAISE_MODSIG\nkernel/module/Kconfig-282-\nkernel/module/Kconfig:283:config MODULE_SIG_ALL\nkernel/module/Kconfig-284-\tbool \"Automatically sign all modules\"\n--\nkernel/module/Kconfig=293=choice\n--\nkernel/module/Kconfig-302-\nkernel/module/Kconfig:303:config MODULE_SIG_SHA256\nkernel/module/Kconfig-304-\tbool \"SHA-256\"\n--\nkernel/module/Kconfig-306-\nkernel/module/Kconfig:307:config MODULE_SIG_SHA384\nkernel/module/Kconfig-308-\tbool \"SHA-384\"\n--\nkernel/module/Kconfig-310-\nkernel/module/Kconfig:311:config MODULE_SIG_SHA512\nkernel/module/Kconfig-312-\tbool \"SHA-512\"\n--\nkernel/module/Kconfig-314-\nkernel/module/Kconfig:315:config MODULE_SIG_SHA3_256\nkernel/module/Kconfig-316-\tbool \"SHA3-256\"\n--\nkernel/module/Kconfig-318-\nkernel/module/Kconfig:319:config MODULE_SIG_SHA3_384\nkernel/module/Kconfig-320-\tbool \"SHA3-384\"\n--\nkernel/module/Kconfig-322-\nkernel/module/Kconfig:323:config MODULE_SIG_SHA3_512\nkernel/module/Kconfig-324-\tbool \"SHA3-512\"\n--\nkernel/module/Kconfig=327=endchoice\nkernel/module/Kconfig-328-\nkernel/module/Kconfig:329:config MODULE_SIG_HASH\nkernel/module/Kconfig-330-\tstring\n"
} |
| 21/2 | 2026/07/06 20:27 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 56230 output: 30 thoughts: 12 Thoughts: Here's my take on the thought process, as if it were my own: **Investigating `MODULE_SIG`** Okay, so I need to understand what exactly the `MODULE_SIG` macro is selecting. This is fundamental; it's the core piece I need to grasp to proceed. Knowing the selection criteria for `MODULE_SIG` will likely provide crucial context for my current task. I need to delve into the details of what `MODULE_SIG` encompasses. |
| 22/2 | 2026/07/06 20:27 | tool | grepper |
0mArgs:{
"Expression": "config MODULE_SIG",
"PathPrefix": "kernel/module/Kconfig"
}Results: {
"Output": "kernel/module/Kconfig=241=config MODULE_SRCVERSION_ALL\n--\nkernel/module/Kconfig-251-\nkernel/module/Kconfig:252:config MODULE_SIG\nkernel/module/Kconfig-253-\tbool \"Module signature verification\"\n--\nkernel/module/Kconfig-273-\nkernel/module/Kconfig:274:config MODULE_SIG_FORCE\nkernel/module/Kconfig-275-\tbool \"Require modules to be validly signed\"\n--\nkernel/module/Kconfig=281=if MODULE_SIG || IMA_APPRAISE_MODSIG\nkernel/module/Kconfig-282-\nkernel/module/Kconfig:283:config MODULE_SIG_ALL\nkernel/module/Kconfig-284-\tbool \"Automatically sign all modules\"\n--\nkernel/module/Kconfig=293=choice\n--\nkernel/module/Kconfig-302-\nkernel/module/Kconfig:303:config MODULE_SIG_SHA256\nkernel/module/Kconfig-304-\tbool \"SHA-256\"\n--\nkernel/module/Kconfig-306-\nkernel/module/Kconfig:307:config MODULE_SIG_SHA384\nkernel/module/Kconfig-308-\tbool \"SHA-384\"\n--\nkernel/module/Kconfig-310-\nkernel/module/Kconfig:311:config MODULE_SIG_SHA512\nkernel/module/Kconfig-312-\tbool \"SHA-512\"\n--\nkernel/module/Kconfig-314-\nkernel/module/Kconfig:315:config MODULE_SIG_SHA3_256\nkernel/module/Kconfig-316-\tbool \"SHA3-256\"\n--\nkernel/module/Kconfig-318-\nkernel/module/Kconfig:319:config MODULE_SIG_SHA3_384\nkernel/module/Kconfig-320-\tbool \"SHA3-384\"\n--\nkernel/module/Kconfig-322-\nkernel/module/Kconfig:323:config MODULE_SIG_SHA3_512\nkernel/module/Kconfig-324-\tbool \"SHA3-512\"\n--\nkernel/module/Kconfig=327=endchoice\nkernel/module/Kconfig-328-\nkernel/module/Kconfig:329:config MODULE_SIG_HASH\nkernel/module/Kconfig-330-\tstring\n"
} |
| 23/2 | 2026/07/06 20:27 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 57007 output: 34 thoughts: 19 Thoughts: Here's my take on the kernel module Kconfig file: **Diving into Kconfig: Module Configuration** Okay, let's zero in on `kernel/module/Kconfig`, specifically around line 252. I need to refresh my understanding of how the module configuration system is handled at its core. I'm probably looking for the main definitions and structures for module options, dependencies, and default configurations. I know the Kconfig language well, so I'm expecting to see things like `config`, `bool`, `tristate`, `select`, `depends on`, and `default` directives. I'm going to start by getting a high-level view of how modules are integrated into the overall configuration flow; this could reveal any recent changes to module configuration. This should provide some context around the module parameters. |
| 24/2 | 2026/07/06 20:28 | tool | read-file |
0mArgs:{
"File": "kernel/module/Kconfig",
"FirstLine": 250,
"LineCount": 20
}Results: {
"Contents": " 250:\t\t will be created for all modules. If unsure, say N.\n 251:\t\n 252:\tconfig MODULE_SIG\n 253:\t\tbool \"Module signature verification\"\n 254:\t\tselect MODULE_SIG_FORMAT\n 255:\t\thelp\n 256:\t\t Check modules for valid signatures upon load: the signature\n 257:\t\t is simply appended to the module. For more information see\n 258:\t\t \u003cfile:Documentation/admin-guide/module-signing.rst\u003e.\n 259:\t\n 260:\t\t Note that this option adds the OpenSSL development packages as a\n 261:\t\t kernel build dependency so that the signing tool can use its crypto\n 262:\t\t library.\n 263:\t\n 264:\t\t You should enable this option if you wish to use either\n 265:\t\t CONFIG_SECURITY_LOCKDOWN_LSM or lockdown functionality imposed via\n 266:\t\t another LSM - otherwise unsigned modules will be loadable regardless\n 267:\t\t of the lockdown policy.\n 268:\t\n 269:\t\t !!!WARNING!!! If you enable this option, you MUST make sure that the\n"
} |
| 25/2 | 2026/07/06 20:28 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 57549 output: 16 thoughts: 97 Thoughts: **Navigating the Kernel Configuration Labyrinth** Okay, let's break this down. It seems that `MODULE_SIG` is the key – it directly pulls in `MODULE_SIG_FORMAT`, which in turn leads to `SYSTEM_DATA_VERIFICATION`. Interesting. But hang on... `SYSTEM_DATA_VERIFICATION` is *also* brought in by `FS_VERITY_BUILTIN_SIGNATURES`. So, it's not a direct, exclusive dependency. If I simply enable `MODULE_SIG`, the kernel will handle `SYSTEM_DATA_VERIFICATION` and, critically, `KEYS`. Okay, that makes a kind of logical sense. Wait a minute... I know that `KEYS` are necessary anyway! Given this, I should confirm that `CONFIG_KEYS` is also enabled. That's a critical component for all of this to work and I need to make sure it's set correctly. Time to check its status. |
| 26/2 | 2026/07/06 20:29 | tool | grepper |
0mArgs:{
"Expression": "config KEYS"
}Results: {
"Output": "drivers/clocksource/Kconfig=242=config NSPIRE_TIMER\n--\ndrivers/clocksource/Kconfig-247-\ndrivers/clocksource/Kconfig:248:config KEYSTONE_TIMER\ndrivers/clocksource/Kconfig-249-\tbool \"Keystone timer driver\" if COMPILE_TEST\n--\ndrivers/irqchip/Kconfig=398=config IRQ_CROSSBAR\n--\ndrivers/irqchip/Kconfig-405-\ndrivers/irqchip/Kconfig:406:config KEYSTONE_IRQ\ndrivers/irqchip/Kconfig-407-\ttristate \"Keystone 2 IRQ controller IP\"\n--\ndrivers/remoteproc/Kconfig=112=config DA8XX_REMOTEPROC\n--\ndrivers/remoteproc/Kconfig-132-\ndrivers/remoteproc/Kconfig:133:config KEYSTONE_REMOTEPROC\ndrivers/remoteproc/Kconfig-134-\ttristate \"Keystone Remoteproc support\"\n--\ndrivers/soc/ti/Kconfig=9=if SOC_TI\ndrivers/soc/ti/Kconfig-10-\ndrivers/soc/ti/Kconfig:11:config KEYSTONE_NAVIGATOR_QMSS\ndrivers/soc/ti/Kconfig-12-\ttristate \"Keystone Queue Manager Sub System\"\n--\ndrivers/soc/ti/Kconfig-22-\ndrivers/soc/ti/Kconfig:23:config KEYSTONE_NAVIGATOR_DMA\ndrivers/soc/ti/Kconfig-24-\ttristate \"TI Keystone Navigator Packet DMA support\"\n--\ndrivers/usb/phy/Kconfig=23=config FSL_USB2_OTG\n--\ndrivers/usb/phy/Kconfig-30-\ndrivers/usb/phy/Kconfig:31:config KEYSTONE_USB_PHY\ndrivers/usb/phy/Kconfig-32-\ttristate \"Keystone USB PHY Driver\"\n--\nsecurity/keys/Kconfig-5-\nsecurity/keys/Kconfig:6:menuconfig KEYS\nsecurity/keys/Kconfig-7-\tbool \"Enable access key retention support\"\n--\nsecurity/keys/Kconfig=24=if KEYS\nsecurity/keys/Kconfig-25-\nsecurity/keys/Kconfig:26:config KEYS_REQUEST_CACHE\nsecurity/keys/Kconfig-27-\tbool \"Enable temporary caching of the last request_key() result\"\n"
} |
| 27/2 | 2026/07/06 20:29 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 58467 output: 32 thoughts: 295 Thoughts: **My Analysis of BPF Signature Verification**
Okay, so I'm digging into this BPF signature verification code. Initially, I was looking at `CONFIG_KEYS` and `CONFIG_MODULE_SIG` as potential triggers, but hang on... I think `CONFIG_SYSTEM_DATA_VERIFICATION` is the key. Let's see if we can enable that through `CONFIG_MODULE_SIG`. Yup, it looks like `CONFIG_MODULE_SIG` selects `MODULE_SIG_FORMAT`, which *does* indeed select `SYSTEM_DATA_VERIFICATION`. That's good. But wait, `CONFIG_SYSTEM_DATA_VERIFICATION` is *also* selected by `CONFIG_FS_VERITY_BUILTIN_SIGNATURES`. So, it seems like just specifying `MODULE_SIG` should be enough.
Wait, though... `CONFIG_BPF_SYSCALL` is *already* enabled. Right, let's go back and check `kernel/bpf/verifier.c` again.
```c
if (env->signature) {
ret = bpf_prog_verify_signature(env, attr, uattr.is_kernel);
if (ret)
goto err_prep;
}
```
Okay, so that code path is reachable if `env->signature` is set. The patch is adding `attr->signature` and `attr->signature_size` to the `union bpf_attr`. Now, did I miss something? Wasn't `attr->signature` already defined there? Let me double-check the `include/uapi/linux/bpf.h` file.
|
| 28/2 | 2026/07/06 20:29 | tool | grepper |
0mArgs:{
"Expression": "signature",
"PathPrefix": "include/uapi/linux/bpf.h"
}Results: {
"Output": "include/uapi/linux/bpf.h=1527=union bpf_attr {\n--\ninclude/uapi/linux/bpf.h-1661-\t\t__u32\t\tfd_array_cnt;\ninclude/uapi/linux/bpf.h:1662:\t\t/* Pointer to a buffer containing the signature of the BPF\ninclude/uapi/linux/bpf.h-1663-\t\t * program.\ninclude/uapi/linux/bpf.h-1664-\t\t */\ninclude/uapi/linux/bpf.h:1665:\t\t__aligned_u64 signature;\ninclude/uapi/linux/bpf.h:1666:\t\t/* Size of the signature buffer in bytes. */\ninclude/uapi/linux/bpf.h:1667:\t\t__u32 \t\tsignature_size;\ninclude/uapi/linux/bpf.h:1668:\t\t/* ID of the kernel keyring to be used for signature\ninclude/uapi/linux/bpf.h-1669-\t\t * verification.\n--\ninclude/uapi/linux/bpf.h-5257- *\t\tThe following are a list of supported map types and their\ninclude/uapi/linux/bpf.h:5258: *\t\trespective expected callback signatures:\ninclude/uapi/linux/bpf.h-5259- *\n--\ninclude/uapi/linux/bpf.h-5488- *\ninclude/uapi/linux/bpf.h:5489: *\t\tThe expected callback signature is\ninclude/uapi/linux/bpf.h-5490- *\n"
} |
| 29/2 | 2026/07/06 20:29 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 59571 output: 36 thoughts: 261 Thoughts: **My Analysis of the BPF Patch** Okay, so I see this patch is about `signature`, and I recall that it was initially handled in `syscall.c`. Now, the patch moves it to `verifier.c`, which makes sense for the overall architecture as that's where verifications reside, and it tweaks how the verification itself is done by incorporating metadata maps. This restructuring feels like a good step towards improved code organization. Hmm, this patch also touches `fd_array` handling. Looks like it introduces a significant change to how the program's `fd_array` is managed. The comment explains the two different shapes: either continuous, where `fd_array` holds all entries up front, or sparse where entries are resolved on the spot from `fd_array_raw`. The key here is this new `fd_array_cnt` variable. I don't recall seeing that before, so I need to check `include/uapi/linux/bpf.h` to fully understand this introduction. Is this a new user-space API change? Is it backwards compatible? This is an interesting modification that could have implications for performance and memory management, depending on how it's used. Let me go find that header file. |
| 30/2 | 2026/07/06 20:29 | tool | grepper |
0mArgs:{
"Expression": "fd_array_cnt",
"PathPrefix": "include/uapi/linux/bpf.h"
}Results: {
"Output": "include/uapi/linux/bpf.h=1527=union bpf_attr {\n--\ninclude/uapi/linux/bpf.h-1651-\t\t__s32\t\tprog_token_fd;\ninclude/uapi/linux/bpf.h:1652:\t\t/* The fd_array_cnt can be used to pass the length of the\ninclude/uapi/linux/bpf.h-1653-\t\t * fd_array array. In this case all the [map] file descriptors\n--\ninclude/uapi/linux/bpf.h-1657-\t\t * used by the verifier during the program load. If provided,\ninclude/uapi/linux/bpf.h:1658:\t\t * then the fd_array[0,...,fd_array_cnt-1] is expected to be\ninclude/uapi/linux/bpf.h-1659-\t\t * continuous.\ninclude/uapi/linux/bpf.h-1660-\t\t */\ninclude/uapi/linux/bpf.h:1661:\t\t__u32\t\tfd_array_cnt;\ninclude/uapi/linux/bpf.h-1662-\t\t/* Pointer to a buffer containing the signature of the BPF\n"
} |
| 31/2 | 2026/07/06 20:29 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 60444 output: 16 thoughts: 671 Thoughts: **Analyzing BPF Patch for Potential Fuzzing Targets** Okay, so I'm looking at this patch and immediately, `fd_array_cnt` pops out. Wait, isn't that already defined somewhere? Let's verify if this patch introduced it or just modified its usage. The diff confirms it, the patch doesn't touch `include/uapi/linux/bpf.h`. Aha! It's modification, not introduction. The real meat is in the `kernel/bpf/verifier.c` changes. I see `process_fd_array` is now `process_fd_array_continuous`, and there's a whole family of new functions: `fd_array_get_map_idx_continuous`, `fd_array_get_map_idx_sparse`, `fd_array_get_btf_continuous`, and `fd_array_get_btf_sparse`. This is screaming "potential fuzzing target" to me. The interplay between continuous and sparse access patterns within this context could be very interesting from a security perspective. I'm making a mental note of all the related symbols, including the initial ones, along with the newly added: * `bpf_prog_verify_signature` * `process_fd_array_continuous` * `process_fd_array` * `fd_array_get_map_idx_continuous` * `fd_array_get_map_idx_sparse` * `fd_array_get_map_idx` * `fd_array_get_btf_continuous` * `fd_array_get_btf_sparse` * `fd_array_get_btf` * `add_fd_from_fd_array` I'm expanding my scope and checking for other potentially relevant functions to be aware of. Right, `fd_slot_set_map`, `fd_slot_set_btf`, `fd_slot_map`, `fd_slot_btf` – those are related too. Also, I see `__add_used_btf` and `__add_used_map` are also updated, now with `env->signature` checks. And `check_and_resolve_insns` now calls `fd_array_get_map_idx`. This is all building a more complete picture of the attack surface. Now, let's think about enabling the right kernel configurations. I'll need `MODULE_SIG` to get `SYSTEM_DATA_VERIFICATION` and `KEYS`. I need to ensure I don't overlook any dependencies. A quick check tells me `MODULES` is often enabled, but it could be that I can enable just `SYSTEM_DATA_VERIFICATION` directly in my configurations. Wait a minute... `SYSTEM_DATA_VERIFICATION` is a boolean with a default value of *n* and no associated prompt. I can't enable it directly! I have to enable something that *selects* it. Looks like `MODULE_SIG` is the way to go. I'll also need to verify that `KEYS` is activated by enabling `MODULE_SIG`. |
| 32/2 | 2026/07/06 20:29 | tool | grepper |
0mArgs:{
"Expression": "select KEYS"
}Results: {
"Output": "arch/arm/mach-keystone/Kconfig=2=config ARCH_KEYSTONE\n--\narch/arm/mach-keystone/Kconfig-6-\tselect HAVE_ARM_ARCH_TIMER\narch/arm/mach-keystone/Kconfig:7:\tselect KEYSTONE_TIMER\narch/arm/mach-keystone/Kconfig-8-\tselect ARCH_HAS_RESET_CONTROLLER\n--\ndrivers/nvme/common/Kconfig=3=config NVME_KEYRING\ndrivers/nvme/common/Kconfig-4- tristate\ndrivers/nvme/common/Kconfig:5: select KEYS\ndrivers/nvme/common/Kconfig-6-\n--\ndrivers/nvme/host/Kconfig=100=config NVME_TCP_TLS\n--\ndrivers/nvme/host/Kconfig-103-\tselect NET_HANDSHAKE\ndrivers/nvme/host/Kconfig:104:\tselect KEYS\ndrivers/nvme/host/Kconfig-105-\tselect TLS\n--\ndrivers/nvme/target/Kconfig=3=config NVME_TARGET\n--\ndrivers/nvme/target/Kconfig-7-\tselect NVME_KEYRING if NVME_TARGET_TCP_TLS\ndrivers/nvme/target/Kconfig:8:\tselect KEYS if NVME_TARGET_TCP_TLS\ndrivers/nvme/target/Kconfig-9-\tselect SGL_ALLOC\n--\nfs/crypto/Kconfig=2=config FS_ENCRYPTION\n--\nfs/crypto/Kconfig-8-\tselect CRYPTO_LIB_SHA512\nfs/crypto/Kconfig:9:\tselect KEYS\nfs/crypto/Kconfig-10-\thelp\n--\nfs/nfs/Kconfig=77=config NFS_V4\n--\nfs/nfs/Kconfig-79-\tdepends on NFS_FS\nfs/nfs/Kconfig:80:\tselect KEYS\nfs/nfs/Kconfig-81-\tselect SUNRPC_BACKCHANNEL\n--\nfs/smb/client/Kconfig=2=config CIFS\n--\nfs/smb/client/Kconfig-16-\tselect CRYPTO_LIB_SHA512\nfs/smb/client/Kconfig:17:\tselect KEYS\nfs/smb/client/Kconfig-18-\tselect DNS_RESOLVER\n--\nfs/ubifs/Kconfig=89=config UBIFS_FS_AUTHENTICATION\nfs/ubifs/Kconfig-90-\tbool \"UBIFS authentication support\"\nfs/ubifs/Kconfig:91:\tselect KEYS\nfs/ubifs/Kconfig-92-\tselect CRYPTO_HMAC\n--\ninit/Kconfig=2183=config SYSTEM_DATA_VERIFICATION\n--\ninit/Kconfig-2185-\tselect SYSTEM_TRUSTED_KEYRING\ninit/Kconfig:2186:\tselect KEYS\ninit/Kconfig-2187-\tselect CRYPTO\n--\nnet/ceph/Kconfig=2=config CEPH_LIB\n--\nnet/ceph/Kconfig-11-\tselect CRYPTO\nnet/ceph/Kconfig:12:\tselect KEYS\nnet/ceph/Kconfig-13-\tdefault n\n--\nnet/rxrpc/Kconfig=6=config AF_RXRPC\n--\nnet/rxrpc/Kconfig-10-\tselect CRYPTO_LIB_DES if RXKAD\nnet/rxrpc/Kconfig:11:\tselect KEYS\nnet/rxrpc/Kconfig-12-\tselect NET_UDP_TUNNEL\n--\nsecurity/integrity/Kconfig=19=config INTEGRITY_SIGNATURE\n--\nsecurity/integrity/Kconfig-21-\tdefault n\nsecurity/integrity/Kconfig:22:\tselect KEYS\nsecurity/integrity/Kconfig-23-\tselect SIGNATURE\n--\nsecurity/integrity/evm/Kconfig=2=config EVM\nsecurity/integrity/evm/Kconfig-3-\tbool \"EVM support\"\nsecurity/integrity/evm/Kconfig:4:\tselect KEYS\nsecurity/integrity/evm/Kconfig-5-\tselect ENCRYPTED_KEYS\n"
} |
| 33/2 | 2026/07/06 20:29 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 62821 output: 33 thoughts: 1004 Thoughts: **BPF Program Signing Verification and Configuration Review** Alright, let's break this down. It looks like the core of the patch involves adding BPF program signature verification within the kernel's verifier. The intent is to verify PKCS#7 signatures of BPF programs and their metadata maps when a signature is provided via the `attr->signature` field. Also, the patch adds support for continuous `fd_array` through the `fd_array_cnt` field. These are fundamental changes in the BPF verifier path that demand thorough fuzzing. My initial thought was that enabling `SYSTEM_DATA_VERIFICATION` would be the key, since it's the target. However, `SYSTEM_DATA_VERIFICATION` is selected by several different configuration options. My first thought was `MODULE_SIG`, as it also selects `MODULE_SIG_FORMAT`, which selects `SYSTEM_DATA_VERIFICATION`, which selects `KEYS`. I then double-checked `kernel/module/Kconfig` to confirm that `MODULE_SIG` is a boolean and that the selection is correct. It is, and looks straightforward. I considered dependencies. Does `MODULE_SIG` depend on `MODULES`? Yes, it's typically inside an `if MODULES` block in `kernel/module/Kconfig`, so it does depend on the module system being enabled. I quickly checked other config options that also select `SYSTEM_DATA_VERIFICATION`: `FS_VERITY_BUILTIN_SIGNATURES`, `DM_VERITY_VERIFY_ROOTHASH_SIG`, `UBIFS_FS_AUTHENTICATION`, `CFG80211_REQUIRE_SIGNED_REGDB`, and `SECURITY_IPE`. Only `SECURITY_IPE` doesn't depend on another config. But `MODULE_SIG` is the standard route, so I'll go with that for now. Then, there's `KEXEC_SIG`, which also selects `SYSTEM_DATA_VERIFICATION`. To be safe, I was thinking of adding `MODULE_SIG` and `FS_VERITY_BUILTIN_SIGNATURES`, or just `MODULE_SIG`. I started to dig deeper into the code modifications. The patch introduces `bpf_prog_verify_signature`, which is defined in `kernel/bpf/helpers.c` and is exported as a kfunc. The patch also modifies `kernel/bpf/verifier.c` and `kernel/bpf/syscall.c`. Focusing on the symbols, I noted `bpf_prog_verify_signature`, `process_fd_array_continuous`, `process_fd_array`, `fd_array_get_map_idx_continuous`, `fd_array_get_map_idx_sparse`, `fd_array_get_map_idx`, `fd_array_get_btf_continuous`, `fd_array_get_btf_sparse`, `fd_array_get_btf`, and `add_fd_from_fd_array`. Upon inspection, `process_fd_array` is present but modified, while `bpf_prog_verify_signature`, `fd_array_get_map_idx`, and `fd_array_get_btf` are new within `verifier.c`. Also, helper functions `fd_slot_set_map`, `fd_slot_set_btf`, `fd_slot_map`, `fd_slot_btf` are present. Finally, `__add_used_btf` and `__add_used_map` are modified. I verified that `bpf_prog_verify_signature` and `process_fd_array_continuous` are static. So, the plan is to enable `MODULE_SIG`, even though `SYSTEM_DATA_VERIFICATION` is a `def_bool`. As long as it is selected by something else, or if the config options are explicitly specified, this should be fine. Finally, I checked `init/Kconfig` for the dependencies of `SYSTEM_DATA_VERIFICATION` and confirmed that it requires `ASN1`, `CRYPTO`, and `PKCS7_MESSAGE_PARSER`. This is an important consideration. In summary, the key config to provide is `MODULE_SIG`. This should correctly enable the necessary functionality for BPF program signature verification. |
| 34/2 | 2026/07/06 20:30 | tool | read-file |
0mArgs:{
"File": "init/Kconfig",
"FirstLine": 2180,
"LineCount": 20
}Results: {
"Contents": "2180:\t\n2181:\tendmenu\n2182:\t\n2183:\tconfig SYSTEM_DATA_VERIFICATION\n2184:\t\tdef_bool n\n2185:\t\tselect SYSTEM_TRUSTED_KEYRING\n2186:\t\tselect KEYS\n2187:\t\tselect CRYPTO\n2188:\t\tselect CRYPTO_RSA\n2189:\t\tselect ASYMMETRIC_KEY_TYPE\n2190:\t\tselect ASYMMETRIC_PUBLIC_KEY_SUBTYPE\n2191:\t\tselect ASN1\n2192:\t\tselect OID_REGISTRY\n2193:\t\tselect X509_CERTIFICATE_PARSER\n2194:\t\tselect PKCS7_MESSAGE_PARSER\n2195:\t\thelp\n2196:\t\t Provide PKCS#7 message verification using the contents of the system\n2197:\t\t trusted keyring to provide public keys. This then can be used for\n2198:\t\t module verification, kexec image verification and firmware blob\n2199:\t\t verification.\n"
} |
| 35/2 | 2026/07/06 20:30 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 65070 output: 37 thoughts: 323 Thoughts: **Analyzing BPF Verification and Signature Checks** Okay, so I've got a handle on the configuration options here. It seems like enabling `MODULE_SIG` is the key – that'll automatically pull in `SYSTEM_DATA_VERIFICATION` and all its required crypto and key parsing configurations. I remember now, `MODULE_SIG` is defined in `kernel/module/Kconfig`. I wonder if there are any other relevant configs, but let's just stick with `MODULE_SIG` for now. Now, let's look at the implementation side. The focus is clearly on changes to BPF program verification and related functions. I'm seeing modifications to `process_fd_array` and the movement of `bpf_prog_verify_signature` from `syscall.c` to `verifier.c`, along with its modification. Importantly, `fd_array_get_map_idx` and `fd_array_get_btf` are brand new additions, which I'll definitely need to dig into. Given the new `fd_array_get_map_idx` function, I should start by examining `kernel/bpf/verifier.c`. I'm guessing that's where I'll find its implementation and hopefully understand how it interacts with the rest of the verification process. I'll need to see how these additions relate to the existing `process_fd_array` and how they contribute to the overall security posture. The fact that `bpf_prog_verify_signature` has been moved and modified is a strong indication that the signature verification process is evolving, which is a key point to understand. |
| 36/2 | 2026/07/06 20:30 | tool | grepper |
0mArgs:{
"Expression": "fd_array_get_map_idx",
"PathPrefix": "kernel/bpf/verifier.c"
}Results: {
"Output": "kernel/bpf/verifier.c=17988=static int add_used_map(struct bpf_verifier_env *env, int fd)\n--\nkernel/bpf/verifier.c-18001-\nkernel/bpf/verifier.c:18002:static int fd_array_get_map_idx_continuous(struct bpf_verifier_env *env, u32 idx)\nkernel/bpf/verifier.c-18003-{\n--\nkernel/bpf/verifier.c-18018-\nkernel/bpf/verifier.c:18019:static int fd_array_get_map_idx_sparse(struct bpf_verifier_env *env, u32 idx)\nkernel/bpf/verifier.c-18020-{\n--\nkernel/bpf/verifier.c-18028-\nkernel/bpf/verifier.c:18029:static int fd_array_get_map_idx(struct bpf_verifier_env *env, u32 idx)\nkernel/bpf/verifier.c-18030-{\nkernel/bpf/verifier.c-18031-\tif (env-\u003efd_array)\nkernel/bpf/verifier.c:18032:\t\treturn fd_array_get_map_idx_continuous(env, idx);\nkernel/bpf/verifier.c-18033-\tif (env-\u003esignature) {\n--\nkernel/bpf/verifier.c-18037-\tif (!bpfptr_is_null(env-\u003efd_array_raw))\nkernel/bpf/verifier.c:18038:\t\treturn fd_array_get_map_idx_sparse(env, idx);\nkernel/bpf/verifier.c-18039-\n--\nkernel/bpf/verifier.c=18236=static int check_and_resolve_insns(struct bpf_verifier_env *env)\n--\nkernel/bpf/verifier.c-18311-\t\t\tcase BPF_PSEUDO_MAP_IDX:\nkernel/bpf/verifier.c:18312:\t\t\t\tmap_idx = fd_array_get_map_idx(env, insn[0].imm);\nkernel/bpf/verifier.c-18313-\t\t\t\tbreak;\n"
} |
| 37/2 | 2026/07/06 20:30 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 66359 output: 36 thoughts: 32 Thoughts: Okay, here's my take on those thoughts, framed as an expert's internal monologue: **Checking on the `fd_array_get_btf` Function** Right, that's it then, they're definitely present in the code base. Now, to get the specifics, I need to dig into the implementation, and I know exactly where to find it. Time to open up `kernel/bpf/verifier.c`. Specifically, I'm after the `fd_array_get_btf` function within that file. I need to understand how it's behaving, the logic it's using, and how the interaction with file descriptors is handled to ascertain the precise mechanism and how it's interacting. |
| 38/2 | 2026/07/06 20:31 | tool | grepper |
0mArgs:{
"Expression": "fd_array_get_btf",
"PathPrefix": "kernel/bpf/verifier.c"
}Results: {
"Output": "kernel/bpf/verifier.c=2521=static struct btf *\nkernel/bpf/verifier.c:2522:fd_array_get_btf_continuous(struct bpf_verifier_env *env, u32 idx)\nkernel/bpf/verifier.c-2523-{\n--\nkernel/bpf/verifier.c=2540=static struct btf *\nkernel/bpf/verifier.c:2541:fd_array_get_btf_sparse(struct bpf_verifier_env *env, u32 idx)\nkernel/bpf/verifier.c-2542-{\n--\nkernel/bpf/verifier.c-2556-\nkernel/bpf/verifier.c:2557:static struct btf *fd_array_get_btf(struct bpf_verifier_env *env, u32 idx)\nkernel/bpf/verifier.c-2558-{\n--\nkernel/bpf/verifier.c-2563-\tif (env-\u003efd_array)\nkernel/bpf/verifier.c:2564:\t\treturn fd_array_get_btf_continuous(env, idx);\nkernel/bpf/verifier.c-2565-\tif (!bpfptr_is_null(env-\u003efd_array_raw))\nkernel/bpf/verifier.c:2566:\t\treturn fd_array_get_btf_sparse(env, idx);\nkernel/bpf/verifier.c-2567-\n--\nkernel/bpf/verifier.c=2572=static struct btf *__find_kfunc_desc_btf(struct bpf_verifier_env *env,\n--\nkernel/bpf/verifier.c-2589-\nkernel/bpf/verifier.c:2590:\t\tbtf = fd_array_get_btf(env, offset);\nkernel/bpf/verifier.c-2591-\t\tif (IS_ERR(btf))\n"
} |
| 39/2 | 2026/07/06 20:31 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 67080 output: 250 thoughts: 386 Thoughts: **My Analysis of the BPF Program Signing Verification Patch** Okay, so I'm looking at this patch and, yes, it's definitely introducing some interesting changes. My reasoning goes like this: we've got BPF program signing verification being integrated into the kernel verifier. That's a critical point. Specifically, it seems the `bpf_check` path has been modified to handle PKCS#7 signature verification for BPF programs and their associated metadata maps, but only when `attr->signature` is present. Plus, there is this new continuous `fd_array` support implemented with `fd_array_cnt`. This impacts the core BPF verifier and thus, warrants careful fuzzing. From a configuration perspective, I see that this is conditional on `MODULE_SIG` being enabled. Now, my focus is zeroing in on a few key symbols here: `bpf_prog_verify_signature`, `process_fd_array_continuous`, `process_fd_array`, `fd_array_get_map_idx_continuous`, `fd_array_get_map_idx_sparse`, `fd_array_get_map_idx`, `fd_array_get_btf_continuous`, `fd_array_get_btf_sparse`, `fd_array_get_btf`, and `add_fd_from_fd_array`. Wait a second. I just realized that `bpf_prog_verify_signature` is a *static* function within `kernel/bpf/verifier.c`. That means... okay, are there any other functions involved in this? Ah, yes, `bpf_verify_pkcs7_signature` is in `kernel/bpf/helpers.c`. It's defined as a kfunc, but crucially, it's also called by `bpf_prog_verify_signature`. That changes things a bit. So I'll include `bpf_verify_pkcs7_signature` as a primary focus as well. I should verify that `bpf_verify_pkcs7_signature` is reachable. Yes, it is called by `bpf_prog_verify_signature`. Okay, let's call `set-results`. |
| 40/2 | 2026/07/06 20:31 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"MODULE_SIG"
],
"FocusSymbols": [
"bpf_prog_verify_signature",
"process_fd_array_continuous",
"process_fd_array",
"fd_array_get_map_idx_continuous",
"fd_array_get_map_idx_sparse",
"fd_array_get_map_idx",
"fd_array_get_btf_continuous",
"fd_array_get_btf_sparse",
"fd_array_get_btf",
"add_fd_from_fd_array",
"bpf_verify_pkcs7_signature"
],
"Reasoning": "The patch introduces BPF program signing verification into the kernel verifier. It modifies the `bpf_check` path to verify PKCS#7 signatures of BPF programs and their metadata maps when `attr-\u003esignature` is provided. It also introduces support for a continuous `fd_array` via `fd_array_cnt`. These are reachable core BPF verifier changes that should be fuzzed.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"MODULE_SIG"
],
"FocusSymbols": [
"bpf_prog_verify_signature",
"process_fd_array_continuous",
"process_fd_array",
"fd_array_get_map_idx_continuous",
"fd_array_get_map_idx_sparse",
"fd_array_get_map_idx",
"fd_array_get_btf_continuous",
"fd_array_get_btf_sparse",
"fd_array_get_btf",
"add_fd_from_fd_array",
"bpf_verify_pkcs7_signature"
],
"Reasoning": "The patch introduces BPF program signing verification into the kernel verifier. It modifies the `bpf_check` path to verify PKCS#7 signatures of BPF programs and their metadata maps when `attr-\u003esignature` is provided. It also introduces support for a continuous `fd_array` via `fd_array_cnt`. These are reachable core BPF verifier changes that should be fuzzed.",
"WorthFuzzing": true
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|