| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/07/08 20:13 | flow | patch-triage |
1mArgs: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"
],
"Reasoning": "The patch moves BPF program signature verification from the userspace/loader program into the kernel's BPF verifier at BPF_PROG_LOAD time. It introduces new logic to verify PKCS#7 signatures over the BPF instructions and the frozen contents of exclusive metadata maps, and to handle continuous vs sparse fd_array resolution. This is a significant functional change in the core BPF verifier and should be fuzzed to ensure the new verification logic, map folding, and fd_array handling are robust against malformed inputs.",
"WorthFuzzing": true
} |
| 1/1 | 2026/07/08 20:13 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 0d9d84baedf30ec3b1ebf92ae86789e61f214489\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Wed Jul 8 20:13:13 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..e73eaaebd8b157\n--- /dev/null\n+++ b/Documentation/bpf/signing.rst\n@@ -0,0 +1,497 @@\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 signed loader\n+never binds a map its 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 (and module BTF) file descriptors bound to the program.\n+ ``fd_array_cnt`` must be set for the kernel to scan the array. When a\n+ signature is present, a BTF entry is rejected outright, and every map must\n+ be exclusive; its frozen contents are folded into the verified buffer, and\n+ 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 adf53f7edf2873..c1a98fa367381f 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 4f42b4e929ad39..001ac53825dabf 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@@ -17568,6 +17633,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@@ -17850,6 +17920,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@@ -17902,6 +17978,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@@ -18119,7 +18237,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@@ -18171,21 +18288,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@@ -19460,7 +19573,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@@ -19472,51 +19585,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@@ -19731,6 +19876,146 @@ 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 ||\n+\t 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 (bpf_map_write_active(map)) {\n+\t\t\tverbose(env, \"signed program metadata map '%s' has active writers\\n\",\n+\t\t\t\tmap-\u003ename);\n+\t\t\terr = -EBUSY;\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@@ -19753,18 +20038,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@@ -19773,22 +20046,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@@ -20012,17 +20314,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..0019492cf07a8e 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,12 +573,31 @@ 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 }\n \n+static void signature_zero_size(void)\n+{\n+\tstatic const __u8 junk[64] = {};\n+\tstruct gen_loader_fixture f;\n+\tint fd;\n+\n+\tif (gen_loader_fixture_init(\u0026f) == 0) {\n+\t\t/*\n+\t\t * A present signature with signature_size == 0 is rejected\n+\t\t * up front, before the keyring is resolved or the signature\n+\t\t * 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 0, KEY_SPEC_SESSION_KEYRING, 0);\n+\t\tASSERT_EQ(fd, -EINVAL, \"zero-size signature rejected\");\n+\t}\n+\tgen_loader_fixture_fini(\u0026f);\n+}\n+\n static void signature_bad_keyring(void)\n {\n \tstatic const __u8 junk[64] = {};\n@@ -515,7 +612,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 +672,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 +758,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 +811,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 +858,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 +1217,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 +1240,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 +1273,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,22 +1330,471 @@ 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_zero_size\"))\n+\t\tsignature_zero_size();\n \tif (test__start_subtest(\"signature_bad_keyring\"))\n \t\tsignature_bad_keyring();\n \tif (test__start_subtest(\"metadata_ctx_max_entries_ignored\"))\n@@ -1114,6 +1803,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 +1823,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/08 20:13 | agent | patch-evaluator |
1mModel: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"
],
"Reasoning": "The patch moves BPF program signature verification from the userspace/loader program into the kernel's BPF verifier at BPF_PROG_LOAD time. It introduces new logic to verify PKCS#7 signatures over the BPF instructions and the frozen contents of exclusive metadata maps, and to handle continuous vs sparse fd_array resolution. This is a significant functional change in the core BPF verifier and should be fuzzed to ensure the new verification logic, map folding, and fd_array handling are robust against malformed inputs.",
"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 0d9d84baedf30ec3b1ebf92ae86789e61f214489
Author: syz-cluster <triage@syzkaller.com>
Date: Wed Jul 8 20:13:13 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..e73eaaebd8b157
--- /dev/null
+++ b/Documentation/bpf/signing.rst
@@ -0,0 +1,497 @@
+.. 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 signed loader
+never binds a map its 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 (and module BTF) file descriptors bound to the program.
+ ``fd_array_cnt`` must be set for the kernel to scan the array. When a
+ signature is present, a BTF entry is rejected outright, and every map 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 adf53f7edf2873..c1a98fa367381f 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 4f42b4e929ad39..001ac53825dabf 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);
@@ -17568,6 +17633,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);
@@ -17850,6 +17920,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);
@@ -17902,6 +17978,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);
@@ -18119,7 +18237,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 ||
@@ -18171,21 +18288,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];
@@ -19460,7 +19573,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;
@@ -19472,51 +19585,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;
}
@@ -19731,6 +19876,146 @@ 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 ||
+ 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 (bpf_map_write_active(map)) {
+ verbose(env, "signed program metadata map '%s' has active writers\n",
+ map->name);
+ err = -EBUSY;
+ 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)
{
@@ -19753,18 +20038,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];
@@ -19773,22 +20046,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);
@@ -20012,17 +20314,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..0019492cf07a8e 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,12 +573,31 @@ 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);
}
+static void signature_zero_size(void)
+{
+ static const __u8 junk[64] = {};
+ struct gen_loader_fixture f;
+ int fd;
+
+ if (gen_loader_fixture_init(&f) == 0) {
+ /*
+ * A present signature with signature_size == 0 is rejected
+ * up front, before the keyring is resolved or the signature
+ * buffer is read.
+ */
+ fd = load_loader(f.gopts.insns, f.gopts.insns_sz, -1, junk,
+ 0, KEY_SPEC_SESSION_KEYRING, 0);
+ ASSERT_EQ(fd, -EINVAL, "zero-size signature rejected");
+ }
+ gen_loader_fixture_fini(&f);
+}
+
static void signature_bad_keyring(void)
{
static const __u8 junk[64] = {};
@@ -515,7 +612,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 +672,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 +758,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 +811,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 +858,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 +1217,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 +1240,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 +1273,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,22 +1330,471 @@ 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_zero_size"))
+ signature_zero_size();
if (test__start_subtest("signature_bad_keyring"))
signature_bad_keyring();
if (test__start_subtest("metadata_ctx_max_entries_ignored"))
@@ -1114,6 +1803,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 +1823,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/08 20:13 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 43579 output: 24 thoughts: 500 Thoughts: **My Assessment of the BPF Program Signature Verification Patch Series** Okay, so I've got this patch series in front of me that fundamentally changes how we handle BPF program signature verification. Previously, the loader program itself, via `emit_signature_match` in `gen_loader.c`, was responsible for this. Now, the kernel itself takes on this responsibility during `BPF_PROG_LOAD` time. That's a pretty big shift. The kernel will now verify the signature against both the instructions and the *frozen* content of any exclusive maps the program uses. That's a significant security enhancement, but it also warrants a very close look. The changes are pretty clear-cut: * In `kernel/bpf/syscall.c`, `bpf_prog_load` now calls `bpf_check`, which is the entry point for verification. The real work is inside that call. * `kernel/bpf/verifier.c` has a new function, `bpf_prog_verify_signature`, which sounds like the heart of the matter. It's handling the PKCS#7 signature over the instructions and the metadata maps. * `tools/bpf/bpftool/sign.c` is being updated to sign the `insns || metadata`. * We're removing `emit_signature_match` from `tools/lib/bpf/gen_loader.c`. Makes sense, since the kernel is doing the work now. * And finally, the tests in `tools/testing/selftests/bpf/prog_tests/signed_loader.c` are being updated. This is a fundamental change to the BPF verifier, which is core kernel functionality. This absolutely *needs* to be thoroughly fuzzed. No question about it. Let's break down the key symbols we need to focus on. Specifically in the new `kernel/bpf/verifier.c`: * `bpf_prog_verify_signature` (obviously the most critical) * And the functions that seem to support it: `process_fd_array_continuous`, `process_fd_array`, `fd_array_get_map_idx`, `fd_array_get_btf`, `fd_slot_map`, `fd_slot_btf`, `fd_slot_set_map`, and `fd_slot_set_btf`. Looks like these are dealing with the map and FD handling for the verification. Okay, next I should check to see if there are any special CONFIG options needed for this to work correctly. The signature verification uses PKCS#7, so that's a key clue. I'll scan through `kernel/bpf/verifier.c` and `kernel/bpf/syscall.c` for any `#ifdef` conditionals. Wait… I see `bpf_verify_pkcs7_signature` is actually being used. Time to go grep for that! Let's see how that's being used. That's probably the piece I want to zero in on for the fuzzing. |
| 4/2 | 2026/07/08 20:13 | 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=19902=static int bpf_prog_verify_signature(struct bpf_verifier_env *env,\n--\nkernel/bpf/verifier.c-20001-\nkernel/bpf/verifier.c:20002:\terr = bpf_verify_pkcs7_signature((struct bpf_dynptr *)\u0026data_ptr,\nkernel/bpf/verifier.c-20003-\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/08 20:13 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 47189 output: 35 thoughts: 139 Thoughts: **Investigating the Availability of `bpf_verify_pkcs7_signature`** Okay, so I'm looking at `bpf_verify_pkcs7_signature`, which is defined as a kfunc in `kernel/bpf/helpers.c`. I see that it's exported, which is good. However, the verifier, specifically `bpf_prog_verify_signature` in `kernel/bpf/verifier.c`, directly calls it. That's a bit odd, and it's making me wonder about dependencies. Is this function *always* available, or does its existence hinge on some configuration options like `CONFIG_SYSTEM_DATA_VERIFICATION` or maybe `CONFIG_BPF_SYSCALL`? That's the question. I need to make sure I'm not missing some conditional compilation. The next logical step is to dive into those two files, `kernel/bpf/helpers.c` and `kernel/bpf/verifier.c`, and meticulously search for any `#ifdef` directives that might control whether or not these functions are compiled or used. I need to know for sure under what conditions this code path is valid. Time to get digging. |
| 6/2 | 2026/07/08 20:13 | 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=19902=static int bpf_prog_verify_signature(struct bpf_verifier_env *env,\n--\nkernel/bpf/verifier.c-20001-\nkernel/bpf/verifier.c:20002:\terr = bpf_verify_pkcs7_signature((struct bpf_dynptr *)\u0026data_ptr,\nkernel/bpf/verifier.c-20003-\t\t\t\t\t (struct bpf_dynptr *)\u0026sig_ptr, key);\n"
} |
| 7/2 | 2026/07/08 20:13 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 48086 output: 33 thoughts: 48 Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience: **Analyzing the BPF Signature Verification Function** Right, so `bpf_verify_pkcs7_signature`... that rings a bell. Let me quickly confirm where that's defined – ah yes, it's nestled away in `kernel/bpf/helpers.c`. Now, the important question, of course, is conditional compilation. I need to figure out if this function is actually *used* and if it's dependent on some feature flag. My primary suspicion is that this is controlled by `#ifdef CONFIG_SYSTEM_DATA_VERIFICATION`. Gotta verify if that's the case. It's almost guaranteed to be part of that guard, right? Let's check. |
| 8/2 | 2026/07/08 20:13 | tool | grepper |
0mArgs:{
"Expression": "CONFIG_SYSTEM_DATA_VERIFICATION",
"PathPrefix": "kernel/bpf/"
}Results: {
"Output": "kernel/bpf/helpers.c=4316=__bpf_kfunc int bpf_verify_pkcs7_signature(const struct bpf_dynptr *data_p,\n--\nkernel/bpf/helpers.c-4319-{\nkernel/bpf/helpers.c:4320:#ifdef CONFIG_SYSTEM_DATA_VERIFICATION\nkernel/bpf/helpers.c-4321-\tconst struct bpf_dynptr_kern *data_ptr = (struct bpf_dynptr_kern *)data_p;\n--\nkernel/bpf/helpers.c-4356-\treturn -EOPNOTSUPP;\nkernel/bpf/helpers.c:4357:#endif /* CONFIG_SYSTEM_DATA_VERIFICATION */\nkernel/bpf/helpers.c-4358-}\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)\n"
} |
| 9/2 | 2026/07/08 20:13 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 48642 output: 33 thoughts: 237 Thoughts: **Investigating BPF Program Signing and Verification** Okay, so `bpf_verify_pkcs7_signature` predictably returns `-EOPNOTSUPP` if `CONFIG_SYSTEM_DATA_VERIFICATION` isn't enabled. That's a given; we're definitely going to need that enabled to properly test this signing feature. Let's see if there are any other configuration dependencies lurking about. The patch touches `kernel/bpf/verifier.c` and `kernel/bpf/syscall.c`, so this is all about BPF program signing and verification, obviously. Is there a more granular config specifically for BPF *program* signing? I should check. Right, time to grep those files – `kernel/bpf/syscall.c` and `kernel/bpf/verifier.c` – for `CONFIG_BPF` or `CONFIG_SECURITY` and see if anything relevant pops up. The patch itself doesn't seem to introduce *new* Kconfig options, which is a bit unusual. It's relying on `bpf_verify_pkcs7_signature`, which, as we know, depends on `SYSTEM_DATA_VERIFICATION`. Wait a minute, I see it's also using `bpf_lookup_system_key` and `bpf_lookup_user_key`. Those sound important. Let me pull up their definitions and make sure I understand how they're used in conjunction with `SYSTEM_DATA_VERIFICATION`. Gotta make sure I'm not missing any subtle interactions here. |
| 10/2 | 2026/07/08 20:13 | tool | grepper |
0mArgs:{
"Expression": "bpf_lookup_system_key",
"PathPrefix": "kernel/bpf/"
}Results: {
"Output": "kernel/bpf/helpers.c=4224=__bpf_kfunc struct bpf_key *bpf_lookup_user_key(s32 serial, u64 flags)\n--\nkernel/bpf/helpers.c-4252-/**\nkernel/bpf/helpers.c:4253: * bpf_lookup_system_key - lookup a key by a system-defined ID\nkernel/bpf/helpers.c-4254- * @id: key ID\n--\nkernel/bpf/helpers.c-4272- */\nkernel/bpf/helpers.c:4273:__bpf_kfunc struct bpf_key *bpf_lookup_system_key(u64 id)\nkernel/bpf/helpers.c-4274-{\n--\nkernel/bpf/helpers.c=4854=BTF_ID_FLAGS(func, bpf_lookup_user_key, KF_ACQUIRE | KF_RET_NULL | KF_SLEEPABLE)\nkernel/bpf/helpers.c:4855:BTF_ID_FLAGS(func, bpf_lookup_system_key, KF_ACQUIRE | KF_RET_NULL)\nkernel/bpf/helpers.c-4856-BTF_ID_FLAGS(func, bpf_key_put, KF_RELEASE)\n--\nkernel/bpf/verifier.c=19902=static int bpf_prog_verify_signature(struct bpf_verifier_env *env,\n--\nkernel/bpf/verifier.c-19923-\tif (system_keyring_id_check(attr-\u003ekeyring_id) == 0)\nkernel/bpf/verifier.c:19924:\t\tkey = bpf_lookup_system_key(attr-\u003ekeyring_id);\nkernel/bpf/verifier.c-19925-\telse\n"
} |
| 11/2 | 2026/07/08 20:13 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 49713 output: 36 thoughts: 21 Thoughts: Here's a summary of my thought process: **Investigating `bpf_lookup_system_key` Configuration Guards** Okay, so I need to figure out if the `bpf_lookup_system_key` function is subject to any conditional compilation or feature flags. I suspect it might be, given the nature of BPF and system-level access, and want to be sure I understand how its presence or behavior is influenced by various kernel configurations. I'll need to dig through the relevant header files, Kconfig options, and potential preprocessor directives to confirm this. Understanding any dependencies on configuration options is critical to properly analyzing its behavior and potential implications. I need to know under what circumstances this function is actually available and what its availability signals. |
| 12/2 | 2026/07/08 20:13 | tool | grepper |
0mArgs:{
"Expression": "bpf_lookup_system_key",
"PathPrefix": "include/linux/bpf.h"
}Results: {
"Output": "include/linux/bpf.h=3820=struct bpf_key *bpf_lookup_user_key(s32 serial, u64 flags);\ninclude/linux/bpf.h:3821:struct bpf_key *bpf_lookup_system_key(u64 id);\ninclude/linux/bpf.h-3822-void bpf_key_put(struct bpf_key *bkey);\n--\ninclude/linux/bpf.h=3832=static inline struct bpf_key *bpf_lookup_user_key(u32 serial, u64 flags)\n--\ninclude/linux/bpf.h-3836-\ninclude/linux/bpf.h:3837:static inline struct bpf_key *bpf_lookup_system_key(u64 id)\ninclude/linux/bpf.h-3838-{\n"
} |
| 13/2 | 2026/07/08 20:13 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 50159 output: 37 thoughts: 23 Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience: **Diving into the BPF Kernel Header** Okay, let's zero in on a specific area within the BPF landscape. I'm going to navigate to `include/linux/bpf.h` and focus around line 3837. This usually indicates a specific struct definition, enum, or function prototype. Given my experience, it's likely related to either core BPF data structures, helper function declarations, or perhaps crucial BPF verifier logic components, which are important, given that line is usually a long file. I'm thinking about what I anticipate seeing there. Are we dealing with a recent feature implementation, or a fundamental structure? I’ll need to understand the surrounding code to grasp the current context. I need to refresh myself, based on what I see on this line, and consider the implications of whatever definition lies there. |
| 14/2 | 2026/07/08 20:13 | tool | read-file |
0mArgs:{
"File": "include/linux/bpf.h",
"FirstLine": 3815,
"LineCount": 30
}Results: {
"Contents": "3815:\t\tbool has_ref;\n3816:\t};\n3817:\t#endif /* CONFIG_KEYS */\n3818:\t\n3819:\t#if defined(CONFIG_KEYS) \u0026\u0026 defined(CONFIG_BPF_SYSCALL)\n3820:\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}\n"
} |
| 15/2 | 2026/07/08 20:13 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 50904 output: 36 thoughts: 562 Thoughts: **Analyzing the BPF Signature Verification Implementation** Okay, so I'm looking at this new BPF signature verification implementation and need to get a clear picture. Initially, I was thinking `CONFIG_KEYS` was the key requirement, but then I realized `SYSTEM_DATA_VERIFICATION` is also essential for `bpf_verify_pkcs7_signature` to actually do anything. It looks like `SYSTEM_DATA_VERIFICATION` might be sufficient, as it seems to select `KEYS` itself. Actually, `SYSTEM_DATA_VERIFICATION` pulls in `PKCS7_MESSAGE_PARSER`, `SYSTEM_TRUSTED_KEYRING`, and the other related configurations. So, `SYSTEM_DATA_VERIFICATION` is the primary configuration needed here. Now, let's look at the functional changes this introduces. The core of the new code is adding `bpf_prog_verify_signature` to `kernel/bpf/verifier.c`. Also, `bpf_check` is being modified to call it. It looks like there are several new additions to handle an array of FDs: `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`, `fd_slot_set_map`, `fd_slot_set_btf`, `fd_slot_map`, and `fd_slot_btf`. These all suggest how a new array structure for File Descriptors is being implemented in the BPF world. So, let's recap the key functions this change introduces, besides the configuration: - `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 was also able to identify a related function, `bpf_classify_keyring`, which seems to have been moved to `kernel/bpf/verifier.c` as well. Wait, it also seems that `bpf_prog_verify_signature` was moved from `kernel/bpf/syscall.c` and has been rewritten. Now, let me dive deeper into the code of `bpf_prog_verify_signature` in `kernel/bpf/verifier.c` and understand its inner workings. |
| 16/2 | 2026/07/08 20:13 | tool | grepper |
0mArgs:{
"Expression": "bpf_prog_verify_signature",
"PathPrefix": "kernel/bpf/verifier.c"
}Results: {
"Output": "kernel/bpf/verifier.c=19879=static enum bpf_sig_keyring bpf_classify_keyring(s32 keyring_id)\n--\nkernel/bpf/verifier.c-19901- */\nkernel/bpf/verifier.c:19902:static int bpf_prog_verify_signature(struct bpf_verifier_env *env,\nkernel/bpf/verifier.c-19903-\t\t\t\t union bpf_attr *attr, bool is_kernel)\n--\nkernel/bpf/verifier.c=20019=int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,\n--\nkernel/bpf/verifier.c-20067-\tif (env-\u003esignature) {\nkernel/bpf/verifier.c:20068:\t\tret = bpf_prog_verify_signature(env, attr, uattr.is_kernel);\nkernel/bpf/verifier.c-20069-\t\tif (ret)\n"
} |
| 17/2 | 2026/07/08 20:13 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 52421 output: 33 thoughts: 22 Thoughts: Here's my summary, as though I'm thinking through this process: **Diving into the BPF Verifier** Alright, time to dig into a potential issue. My first step is to zero in on the `process_fd_array` function, specifically within the `kernel/bpf/verifier.c` file. This function is a critical piece of the BPF verifier puzzle, and I need to fully understand its behaviour in order to grasp a wider issue. I'm focusing on this because I suspect a subtle flaw might reside here, something that could be allowing an exploit or potentially leading to an unexpected state. Let's see what we can find. I'm expecting some interesting logic around file descriptor handling, and I'll be looking carefully at how it interacts with other parts of the verifier. I'm going to start by reviewing the logic and then look for edge cases, particularly those that may lead to out-of-bounds access or other security implications. Let's get to it. |
| 18/2 | 2026/07/08 20:14 | tool | grepper |
0mArgs:{
"Expression": "process_fd_array",
"PathPrefix": "kernel/bpf/verifier.c"
}Results: {
"Output": "kernel/bpf/verifier.c=19576=static int add_fd_from_fd_array(struct bpf_verifier_env *env, u32 idx, int fd)\n--\nkernel/bpf/verifier.c-19614-\nkernel/bpf/verifier.c:19615:static int process_fd_array_continuous(struct bpf_verifier_env *env,\nkernel/bpf/verifier.c-19616-\t\t\t\t bpfptr_t fd_array, u32 cnt)\n--\nkernel/bpf/verifier.c-19642-\nkernel/bpf/verifier.c:19643:static int process_fd_array(struct bpf_verifier_env *env,\nkernel/bpf/verifier.c-19644-\t\t\t union bpf_attr *attr, bpfptr_t uattr)\n--\nkernel/bpf/verifier.c-19661-\tif (attr-\u003efd_array_cnt)\nkernel/bpf/verifier.c:19662:\t\treturn process_fd_array_continuous(env, fd_array,\nkernel/bpf/verifier.c-19663-\t\t\t\t\t\t attr-\u003efd_array_cnt);\n--\nkernel/bpf/verifier.c=20019=int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,\n--\nkernel/bpf/verifier.c-20062-\nkernel/bpf/verifier.c:20063:\tret = process_fd_array(env, attr, uattr);\nkernel/bpf/verifier.c-20064-\tif (ret)\n"
} |
| 19/2 | 2026/07/08 20:14 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 53126 output: 37 thoughts: 26 Thoughts: Here's my take on those thoughts, summarized as if they were my own: **Diving into the BPF Verifier** Alright, time to dig in. The issue I'm looking at likely involves array indexing within the BPF context. My immediate focus? I need to understand how the kernel handles retrieving map indices when working with file descriptors, so I'm going straight to `fd_array_get_map_idx` in `kernel/bpf/verifier.c`. This function is where the magic (or potential problem area) is likely happening, and I need a clear picture of its behavior. Let's see how it's implemented. Hopefully, a close examination will illuminate the cause of the issue. |
| 20/2 | 2026/07/08 20:14 | tool | grepper |
0mArgs:{
"Expression": "fd_array_get_map_idx",
"PathPrefix": "kernel/bpf/verifier.c"
}Results: {
"Output": "kernel/bpf/verifier.c=17967=static int add_used_map(struct bpf_verifier_env *env, int fd)\n--\nkernel/bpf/verifier.c-17980-\nkernel/bpf/verifier.c:17981:static int fd_array_get_map_idx_continuous(struct bpf_verifier_env *env, u32 idx)\nkernel/bpf/verifier.c-17982-{\n--\nkernel/bpf/verifier.c-17997-\nkernel/bpf/verifier.c:17998:static int fd_array_get_map_idx_sparse(struct bpf_verifier_env *env, u32 idx)\nkernel/bpf/verifier.c-17999-{\n--\nkernel/bpf/verifier.c-18007-\nkernel/bpf/verifier.c:18008:static int fd_array_get_map_idx(struct bpf_verifier_env *env, u32 idx)\nkernel/bpf/verifier.c-18009-{\nkernel/bpf/verifier.c-18010-\tif (env-\u003efd_array)\nkernel/bpf/verifier.c:18011:\t\treturn fd_array_get_map_idx_continuous(env, idx);\nkernel/bpf/verifier.c-18012-\tif (env-\u003esignature) {\n--\nkernel/bpf/verifier.c-18016-\tif (!bpfptr_is_null(env-\u003efd_array_raw))\nkernel/bpf/verifier.c:18017:\t\treturn fd_array_get_map_idx_sparse(env, idx);\nkernel/bpf/verifier.c-18018-\n--\nkernel/bpf/verifier.c=18215=static int check_and_resolve_insns(struct bpf_verifier_env *env)\n--\nkernel/bpf/verifier.c-18290-\t\t\tcase BPF_PSEUDO_MAP_IDX:\nkernel/bpf/verifier.c:18291:\t\t\t\tmap_idx = fd_array_get_map_idx(env, insn[0].imm);\nkernel/bpf/verifier.c-18292-\t\t\t\tbreak;\n"
} |
| 21/2 | 2026/07/08 20:14 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 53925 output: 36 thoughts: 25 Thoughts: Here's a first-person summary, tailored for an expert audience, of the thought process described: **Diving into `fd_array_get_btf` for a Deeper Look** Okay, time to dig in. I need to refresh my memory on how the BPF verifier handles file descriptor arrays. Let's start by zeroing in on `fd_array_get_btf` within `kernel/bpf/verifier.c`. My hunch is this is the key function handling the extraction and validation of BTF data associated with file descriptors, specifically within the context of a BPF program. I'm expecting this function plays a critical role in preventing type confusion vulnerabilities. I want to see how the verifier handles the implicit type information derived from the file descriptors. |
| 22/2 | 2026/07/08 20:14 | 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"
} |
| 23/2 | 2026/07/08 20:14 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 54650 output: 32 thoughts: 178 Thoughts: **Analyzing the Function Landscape** Okay, let's see, my mental map of the core functions involved in this area is solidifying. I've got a specific set of symbols I'm keying in on as potential areas of interest: `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`, and `fd_array_get_btf`. They form the core building blocks as I understand it. But hold on, there are a few newcomers in the arena I'm less familiar with - `fd_slot_set_map`, `fd_slot_set_btf`, `fd_slot_map`, and `fd_slot_btf`. These are new enough I need to ascertain their role. Given their names, I'm already anticipating their function, which likely involves direct manipulation of file descriptor slots. To fully understand their integration, I must determine if they are `static inline` or merely `static`. This is important, it could signal they are designed to be optimized away or have a greater scope of use. |
| 24/2 | 2026/07/08 20:14 | tool | grepper |
0mArgs:{
"Expression": "fd_slot_",
"PathPrefix": "kernel/bpf/verifier.c"
}Results: {
"Output": "kernel/bpf/verifier.c=2482=int bpf_get_kfunc_addr(const struct bpf_prog *prog, u32 func_id,\n--\nkernel/bpf/verifier.c-2496-\nkernel/bpf/verifier.c:2497:static void fd_slot_set_map(struct bpf_fd_array *slot, struct bpf_map *map)\nkernel/bpf/verifier.c-2498-{\n--\nkernel/bpf/verifier.c-2501-\nkernel/bpf/verifier.c:2502:static void fd_slot_set_btf(struct bpf_fd_array *slot, struct btf *btf)\nkernel/bpf/verifier.c-2503-{\n--\nkernel/bpf/verifier.c-2506-\nkernel/bpf/verifier.c:2507:static struct bpf_map *fd_slot_map(struct bpf_fd_array slot)\nkernel/bpf/verifier.c-2508-{\n--\nkernel/bpf/verifier.c-2513-\nkernel/bpf/verifier.c:2514:static struct btf *fd_slot_btf(struct bpf_fd_array slot)\nkernel/bpf/verifier.c-2515-{\n--\nkernel/bpf/verifier.c=2522=fd_array_get_btf_continuous(struct bpf_verifier_env *env, u32 idx)\n--\nkernel/bpf/verifier.c-2530-\t}\nkernel/bpf/verifier.c:2531:\tbtf = fd_slot_btf(env-\u003efd_array[idx]);\nkernel/bpf/verifier.c-2532-\tif (!btf) {\n--\nkernel/bpf/verifier.c=17981=static int fd_array_get_map_idx_continuous(struct bpf_verifier_env *env, u32 idx)\n--\nkernel/bpf/verifier.c-17989-\t}\nkernel/bpf/verifier.c:17990:\tmap = fd_slot_map(env-\u003efd_array[idx]);\nkernel/bpf/verifier.c-17991-\tif (!map) {\n--\nkernel/bpf/verifier.c=19576=static int add_fd_from_fd_array(struct bpf_verifier_env *env, u32 idx, int fd)\n--\nkernel/bpf/verifier.c-19587-\t\t\treturn err;\nkernel/bpf/verifier.c:19588:\t\tfd_slot_set_map(\u0026env-\u003efd_array[idx], map);\nkernel/bpf/verifier.c-19589-\t\treturn 0;\n--\nkernel/bpf/verifier.c-19597-\t\t\treturn err;\nkernel/bpf/verifier.c:19598:\t\tfd_slot_set_btf(\u0026env-\u003efd_array[idx], btf);\nkernel/bpf/verifier.c-19599-\t\treturn 0;\n"
} |
| 25/2 | 2026/07/08 20:14 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 55939 output: 21 thoughts: 159 Thoughts: **My Initial Assessment and Investigative Plan** Okay, so we're looking at some static functions that seem ripe for fuzzing – always a good place to start when digging into potential vulnerabilities. They're small helper functions, which could make them easier to analyze but also potentially more critical if they have a flaw. The key functions mentioned are `bpf_prog_verify_signature`, `process_fd_array_continuous`, `process_fd_array`, `fd_array_get_map_idx`, and `fd_array_get_btf`. I'll want to take a closer look at each one, but first, let's prioritize and pick one to start with. The change log points to `add_fd_from_fd_array` being modified to accept `idx`. That's an interesting modification, I need to understand that. So I'll include that function as well. Now, let's see, we also have these configuration options: `SYSTEM_DATA_VERIFICATION` and `KEYS`. I suspect `KEYS` might be dependent on `SYSTEM_DATA_VERIFICATION`, likely enabled by it, but let's confirm that. I'll need to check the Kconfig files, specifically `init/Kconfig` or `certs/Kconfig` to see how those are defined and related. This might give me clues about the intended use case and any security implications related to these configurations. This should give me some direction. |
| 26/2 | 2026/07/08 20:14 | 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"
} |
| 27/2 | 2026/07/08 20:14 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 56497 output: 33 thoughts: 13 Thoughts: Okay, here's my attempt at summarizing the thought process, tailored for an expert audience, and written in the first person: **Analyzing Selection and Dependencies** Alright, so I'm trying to figure out the mechanisms here. My initial thought is to drill down into the 'what'. Specifically, what dictates the selection of this particular element or process? Is it driven by specific criteria, contextual factors, or perhaps a combination? Beyond that, I need to understand the dependencies at play. What other factors or components does this thing rely upon? What are the inputs, and what are the outputs? I need to build a comprehensive map of how this works and find potential critical points. Ultimately, I'm aiming to uncover the core drivers and identify any potential vulnerabilities or levers for manipulation. That means determining the dependent and independent variables. |
| 28/2 | 2026/07/08 20:14 | 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"
} |
| 29/2 | 2026/07/08 20:14 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 56993 output: 21 thoughts: 57 Thoughts: Okay, let's break this down. **Investigating SYSTEM_DATA_VERIFICATION in the Configuration** Alright, so `SYSTEM_DATA_VERIFICATION` is flagged as a boolean option, meaning it's either on or off, and it's being *selected* by something else in the configuration. The question then becomes, can I just directly enable it? Not likely, since it doesn't have a prompt. That's a good indicator that its visibility and activation are controlled by dependencies. So, I need to figure out *what* exactly is triggering it. Let's trace those dependencies and see what's driving this particular option. |
| 30/2 | 2026/07/08 20:14 | 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"
} |
| 31/2 | 2026/07/08 20:14 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 57787 output: 18 thoughts: 87 Thoughts: Okay, let me break this down. **System Data Verification: Enabling the Right Feature** Right, so I'm thinking about enabling some kind of system data verification. I've got a few options here: `FS_VERITY_BUILTIN_SIGNATURES`, `MODULE_SIG_FORMAT`, `CFG80211_REQUIRE_SIGNED_REGDB`, or `SECURITY_IPE`. Each of these seems to offer a path to that goal. But hold on, I need to be careful. It looks like `MODULE_SIG_FORMAT` is a `def_bool n`, meaning it's disabled by default, and I might need to make sure `MODULE_SIG` is enabled first before I touch this one. Let me go double check `MODULE_SIG` to make sure I am enabling the right features in the correct order. |
| 32/2 | 2026/07/08 20:14 | 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"
} |
| 33/2 | 2026/07/08 20:14 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 59099 output: 34 thoughts: 64 Thoughts: **Examining the Kernel Module Signature Verification Process** Okay, so I'm thinking about the most effective way to enable `SYSTEM_DATA_VERIFICATION`. My initial thought is that setting `MODULE_SIG` would be a good place to start. But wait, that rings a bell! Isn't `MODULE_SIG` connected to `MODULE_SIG_FORMAT`, and *doesn't* that selection process also pull in `SYSTEM_DATA_VERIFICATION`? I need to confirm that. Time to delve into `kernel/module/Kconfig` and refresh my memory on the dependency chain for `MODULE_SIG`. I need to ensure this is the correct configuration path for the larger data verification scheme. |
| 34/2 | 2026/07/08 20:14 | tool | read-file |
0mArgs:{
"File": "kernel/module/Kconfig",
"FirstLine": 250,
"LineCount": 10
}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"
} |
| 35/2 | 2026/07/08 20:14 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 59499 output: 264 thoughts: 1253 Thoughts: **Analysis of BPF Program Signature Verification Patch** Okay, let's break this down systematically. My initial thought is that `MODULE_SIG` indirectly enables the necessary configurations, specifically `MODULE_SIG_FORMAT`, which then selects `SYSTEM_DATA_VERIFICATION`. So, enabling `MODULE_SIG` should be sufficient for the required functionality. I'll add `MODULE_SIG` to the `EnableConfigs` list. I see that the patch tests also rely on `KEY_SPEC_SESSION_KEYRING`. Although it's not clear here if it's strictly necessary for *this* functionality, it is a point to note for complete coverage. Also, I confirm that `SYSTEM_DATA_VERIFICATION` does indeed enable `CONFIG_KEYS`, which is likely relevant for key management during signature verification. The core of the change appears to be centered around the `bpf_prog_verify_signature` function. This function is called unconditionally in `bpf_check` if `env->signature` is true, which, in turn, is directly linked to `attr->signature`. If `SYSTEM_DATA_VERIFICATION` is disabled, `bpf_verify_pkcs7_signature` returns `-EOPNOTSUPP`, which confirms my understanding that the new verification logic requires `SYSTEM_DATA_VERIFICATION`. I'll focus on the functions impacted. The focus symbols are: `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`. I double-check that these have the correct static qualifier, or it's implications, and note they do. Looking closer at the code additions/modifications, I see the following in `kernel/bpf/verifier.c`: `bpf_classify_keyring` (static), `fd_slot_set_map`, `fd_slot_set_btf`, `fd_slot_map`, `fd_slot_btf` (all static). Also modified are `__add_used_btf`, `__add_used_map`, and `check_and_resolve_insns`. Since the primary logic for signature verification is contained within the new function, I'll include the new functions in the `FocusSymbols`. Although `__add_used_btf`, `__add_used_map` are modified to account for `env->signature`, they are existing functions, so there isn't too much to include from those, although the changes are notable. Also, I confirm that the patch uses `CONFIG_SECURITY` in `ifdef` statements. I need to keep this in mind as well. I'm also seeing modifications to the `struct bpf_map`, specifically the removal of the `excl` field and I note the removal of `bpf_prog_verify_signature` from `kernel/bpf/syscall.c` and its move to `kernel/bpf/verifier.c`. To confirm the dependency, I will double check that `MODULE_SIG` directly selects `SYSTEM_DATA_VERIFICATION` through the intermediate `MODULE_SIG_FORMAT`. The only other configs that select `SYSTEM_DATA_VERIFICATION` are: `FS_VERITY_BUILTIN_SIGNATURES`, `CFG80211_REQUIRE_SIGNED_REGDB`, `SECURITY_IPE`, `DM_VERITY_VERIFY_ROOTHASH_SIG`, and `UBIFS_FS_AUTHENTICATION`. To summarize the functional change: The patch moves BPF program signature verification into the kernel's BPF verifier at `BPF_PROG_LOAD` time. It adds new verification logic for PKCS#7 signatures over BPF instructions and the frozen contents of exclusive metadata maps, and handles both continuous and sparse `fd_array` resolution. This makes this a significant functional change which requires careful fuzzing to ensure the robustness of the new verification logic and that this applies the signature over any map folding or `fd_array` handling. I confirm that `bpf_prog_verify_signature` does all the signature verification work. And that `process_fd_array_continuous` processes the continuous fd array logic. |
| 36/2 | 2026/07/08 20:14 | 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"
],
"Reasoning": "The patch moves BPF program signature verification from the userspace/loader program into the kernel's BPF verifier at BPF_PROG_LOAD time. It introduces new logic to verify PKCS#7 signatures over the BPF instructions and the frozen contents of exclusive metadata maps, and to handle continuous vs sparse fd_array resolution. This is a significant functional change in the core BPF verifier and should be fuzzed to ensure the new verification logic, map folding, and fd_array handling are robust against malformed inputs.",
"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"
],
"Reasoning": "The patch moves BPF program signature verification from the userspace/loader program into the kernel's BPF verifier at BPF_PROG_LOAD time. It introduces new logic to verify PKCS#7 signatures over the BPF instructions and the frozen contents of exclusive metadata maps, and to handle continuous vs sparse fd_array resolution. This is a significant functional change in the core BPF verifier and should be fuzzed to ensure the new verification logic, map folding, and fd_array handling are robust against malformed inputs.",
"WorthFuzzing": true
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|