| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/08/25 14:55 | flow | patch-triage |
18mArgs:null Results: {
"EnableConfigs": [
"KEYS",
"BPF_SYSCALL"
],
"FocusSymbols": [
"bpf_lookup_keyring",
"bpf_keyring_enforced"
],
"KMSANReasoning": "The patch series introduces a new BPF keyring (`.bpf`) for BPF program signature verification, along with a kernel parameter `bpf.keyring_unsealed` to manage its state. The changes primarily involve adding a new keyring initialization function, adding a lookup function `bpf_lookup_keyring` which allocates a `struct bpf_key` and initializes its fields, and modifying `bpf_prog_verify_signature` to handle the new `VERIFY_USE_BPF_KEYRING` ID.\n\nNone of these changes introduce new kernel structures that are copied to user space, nor do they introduce complex data structures with potentially uninitialized fields. The only new kernel allocation is for `struct bpf_key` via `kmalloc_obj`, and all its fields (`key` and `has_ref`) are explicitly initialized immediately after allocation. The signature and data buffers used during verification are allocated with `kvmemdup_bpfptr` (which copies from user space) and `kvmalloc(..., __GFP_ZERO)` (which zero-initializes), respectively.\n\nAny potential bugs introduced by these changes, such as memory leaks, out-of-bounds accesses, or use-after-free issues, would be effectively caught by standard KASAN and other default bug detectors. There is no risk of uninitialized memory usage or info-leaks that would necessitate a dedicated KMSAN fuzzing session.",
"NeedsKMSAN": false,
"Reasoning": "The patch introduces a new `.bpf` keyring for BPF program signature verification and a `bpf.keyring_unsealed` boot parameter. It modifies the BPF program loading and verification paths in `bpf_prog_verify_signature` to handle the new keyring and enforce its use when configured. These changes affect the `bpf()` syscall path and introduce new reachable state and logic that should be fuzzed.",
"WorthFuzzing": true
} |
| 1/1 | 2026/08/25 14:55 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 4b232f890ce6f15bc78b56751fb69c404b179db0\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Tue Aug 25 14:55:07 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt\nindex e4643634a9b1b..e70fb15757d40 100644\n--- a/Documentation/admin-guide/kernel-parameters.txt\n+++ b/Documentation/admin-guide/kernel-parameters.txt\n@@ -667,6 +667,22 @@ Kernel parameters\n \n \t\t\tSee Documentation/admin-guide/bootconfig.rst\n \n+\tbpf.keyring_unsealed=\n+\t\t\t[BPF] When set to 1, leave the bpf keyring unsealed\n+\t\t\tafter initialization so that userspace can provision\n+\t\t\tkeys. Once the keyring is restricted it becomes active\n+\t\t\tand can be used for BPF program signature verification.\n+\n+\t\t\tSetting this also means that the bpf keyring is the\n+\t\t\tonly non-system keyring a loader may select for the\n+\t\t\trest of the boot: caller-supplied user/session\n+\t\t\tkeyrings are refused with -EPERM, whether or not\n+\t\t\tprovisioning actually completed. The system keyrings\n+\t\t\tstay selectable. Leaving it unset keeps the prior\n+\t\t\tbehaviour, where a caller-supplied keyring is allowed.\n+\n+\t\t\tSee Documentation/bpf/signing.rst\n+\n \tbttv.card=\t[HW,V4L] bttv (bt848 + bt878 based grabber cards)\n \tbttv.radio=\tMost important insmod options are available as\n \t\t\tkernel args too.\ndiff --git a/Documentation/bpf/signing.rst b/Documentation/bpf/signing.rst\nindex e73eaaebd8b15..6c03fb58b1464 100644\n--- a/Documentation/bpf/signing.rst\n+++ b/Documentation/bpf/signing.rst\n@@ -254,21 +254,25 @@ 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+A more realistic admission policy than \"is it signed at all\": base trust in\n+the bpf keyring, accept a staging signature only while the key/keyring the\n+program was verified against is on an explicit allowlist, and emit a tamper-\n+evident record of every decision so that even denied attempts are auditable.\n+(illustrative - error checking elided.)\n \n .. code-block:: c\n \n- /* Serials of user keys/keyrings we additionally trust. */\n+ /*\n+ * Serials of caller-supplied keyrings we are willing to stage. Empty\n+ * on a system that has committed to the bpf keyring, where the kernel\n+ * refuses them anyway.\n+ */\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+ } staging_keys SEC(\".maps\");\n \n /* Audit stream consumed by a userspace logger. */\n struct {\n@@ -291,11 +295,19 @@ auditable. (Illustrative - error checking elided.)\n if (kernel)\n return 0; /* trust in-kernel loads */\n \n- if (verdict != BPF_SIG_VERIFIED)\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+ } else switch (ktype) {\n+ case BPF_SIG_KEYRING_BUILTIN:\n+ case BPF_SIG_KEYRING_BPF:\n+ break;\n+ case BPF_SIG_KEYRING_USER:\n+ if (!bpf_map_lookup_elem(\u0026staging_keys, \u0026serial))\n+ ret = -EPERM;\n+ break;\n+ default:\n+ ret = -EPERM; /* keyring not in policy */\n+ }\n \n d = bpf_ringbuf_reserve(\u0026audit, sizeof(*d), 0);\n if (d) {\n@@ -309,6 +321,11 @@ auditable. (Illustrative - error checking elided.)\n return ret;\n }\n \n+Such a policy is what makes a caller-supplied keyring usable at all on a system\n+that does not boot with ``bpf.keyring_unsealed=1``: the allowlist bounds which\n+staged keys count, and the LSM itself has to protect them from being tampered\n+with.\n+\n Observing a verified load: ``security_bpf_prog()``\n --------------------------------------------------\n \n@@ -381,8 +398,9 @@ 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+let a policy distinguish \"verified against the operator's bpf keyring\" from\n+\"verified against a keyring the loader supplied itself\", which is the\n+distinction that matters most. 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@@ -390,33 +408,154 @@ 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+against. Four values are well-known; anything else is taken as the serial of a\n+caller-supplied user or session key or keyring:\n+\n+.. list-table::\n+ :header-rows: 1\n+\n+ * - ``keyring_id``\n+ - Keyring\n+ * - ``0``\n+ - builtin trusted keyring\n+ * - ``VERIFY_USE_SECONDARY_KEYRING`` (``1``)\n+ - secondary trusted keyring\n+ * - ``VERIFY_USE_PLATFORM_KEYRING`` (``2``)\n+ - platform keyring\n+ * - ``VERIFY_USE_BPF_KEYRING`` (``3``)\n+ - the bpf keyring\n+ * - anything else\n+ - serial of a caller-supplied user/session key or keyring\n+\n+The keyring is resolved first, before the signature bytes are examined, so a\n+signature naming a keyring that cannot be used is rejected up front, and a\n+failed verification aborts the load - a program that loads successfully with\n+a signature therefore always has consistent keyring fields recorded.\n+\n+The bpf keyring\n+---------------\n+\n+A system keyring needs a kernel rebuild or a vouched-for enrollment to rotate a\n+key, and grants BPF-signing trust to keys trusted for everything else in the\n+kernel too. A caller-supplied keyring, at the other extreme, is filled by the\n+very process that loads the program and so carries no trust of its own.\n+\n+The bpf keyring fills that gap and is the trust anchor which a signed BPF\n+deployment should be built on top of: a keyring named ``.bpf``, selected with\n+``VERIFY_USE_BPF_KEYRING``, that an operator provisions at boot with a key\n+scoped to BPF program loading and nothing else in the kernel's trust hierarchy.\n+It is owned by the operator rather than by the loader, and rotatable across a\n+reboot without touching the kernel image. It is modelled after the dm-verity\n+keyring (see ``dm_verity.keyring_unsealed=``) and provisioned the same way: an\n+initrd runs the ``keyctl`` steps below before handing off to the rootfs.\n+\n+Provisioning\n+~~~~~~~~~~~~\n+\n+The keyring is created during ``late_initcall`` and is **sealed empty** by\n+default: it carries a reject-all restriction, so no key can ever be added and\n+``VERIFY_USE_BPF_KEYRING`` fails with ``-ENOKEY`` for the whole boot.\n+\n+``bpf.keyring_unsealed=1`` leaves it unrestricted at init so the initrd can\n+provision it. The keyring is not linked into any process keyring, so it is\n+addressed by the serial ``/proc/keys`` reports. Steps would be as follows::\n+\n+ serial=0x$(awk '$8 == \"keyring\" \u0026\u0026 $9 == \".bpf:\" { print $1 }' /proc/keys)\n+\n+ keyctl padd asymmetric \"\" $serial \u003c signing_key.der\n+ keyctl restrict_keyring $serial\n+\n+Both steps are required: the keyring is consulted only once it is **non-empty\n+and restricted**. An unrestricted keyring is ignored even when it holds keys,\n+so a half-provisioned keyring is inert rather than a weaker trust anchor, and a\n+load naming it fails with ``-ENOKEY`` and a verifier log. Restricting cannot\n+be undone.\n+\n+More than one key is enrolled by repeating the ``keyctl padd`` step; the\n+restriction is applied once, after the last of them::\n+\n+ for key in /etc/bpf/keys/*.der; do\n+ keyctl padd asymmetric \"\" $serial \u003c $key\n+ done\n+\n+ keyctl restrict_keyring $serial\n+ keyctl show $serial\n+\n+The restriction bounds what can be added, never what can be taken away. A key\n+that is already enrolled can still be unlinked, and the keyring cleared or\n+revoked, by anything running as root. That does not weaken the anchor, since\n+a keyring left empty is no longer consulted and a load naming it fails with\n+``-ENOKEY``, but it does take signed loading out until the next boot. Dropping\n+the user permissions the keyring no longer needs would close that; as a third\n+step in the initrd::\n+\n+ keyctl setperm $serial 0x08030000\n+\n+What remains is ``KEY_POS_SEARCH`` for the in-kernel search during verification,\n+plus ``KEY_USR_VIEW`` and ``KEY_USR_READ`` so the keyring stays visible in\n+``/proc/keys`` and ``keyctl show``.\n+\n+Provisioning has to complete before control passes to the rootfs. The keyring\n+takes any number of keys for as long as it is unrestricted, so it is the\n+restriction that bounds the enrolled set, not the first enrollment. Before\n+the initrd hands off control, it must therefore restrict the keyring after\n+the last enrollment.\n+\n+Enforcement\n+~~~~~~~~~~~\n+\n+``bpf.keyring_unsealed=1`` states that the bpf keyring is *the* trust anchor for\n+this boot, so it does more than unseal. From the first program load onwards a\n+caller-supplied user/session keyring is refused with ``-EPERM`` and a verifier\n+log message, whether or not provisioning ever completed. The system keyrings\n+stay selectable.\n+\n+Enforcement is readable at ``/sys/module/bpf/parameters/keyring_unsealed``, and\n+read-only there: the flag is ``__ro_after_init`` behind a 0444 parameter. It is\n+also derived from the boot flag rather than from the keyring's runtime state,\n+so there is no window early in boot during which a caller-supplied keyring is\n+still accepted.\n+\n+Caller-supplied keyrings are for staging\n+----------------------------------------\n+\n+A ``keyring_id`` naming a user or session key or keyring is a *staging*\n+mechanism, not a trust anchor: it is filled by the same userspace that loads the\n+program, so verifying against it establishes only that the loader signed what it\n+loaded. Its purpose is to let software installed onto a running system - whose\n+signing key is not enrolled anywhere yet - run signed until that key reaches the\n+bpf keyring on the next boot.\n+\n+A system that has committed to the bpf keyring refuses this path outright (see\n+`Enforcement`_). A system that has not can still allow it, but a policy must\n+never treat ``BPF_SIG_KEYRING_USER`` as equivalent to the bpf or system\n+keyrings; it should allowlist the specific serials it is willing to stage and\n+pair that with a BPF LSM policy protecting those keys from tampering, as in\n+`Enforcement via LSMs`_.\n+\n+Recorded fields\n+---------------\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+ keyrings, ``_BPF`` for the bpf keyring, or ``_USER`` for a caller-supplied\n+ user/session keyring. It is ``BPF_SIG_KEYRING_NONE`` for an unsigned\n+ 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+ **caller-supplied 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+ serial. The system keyrings and the bpf keyring are trusted as a whole and\n+ expose no serial here, so the serial is ``0`` for them, and ``0`` for\n+ unsigned programs. A non-zero ``keyring_serial`` is therefore exactly\n+ \"verified against the caller-supplied key/keyring with this serial\", which\n+ is exactly the case a policy has to scrutinise.\n \n .. list-table::\n :header-rows: 1\n@@ -436,16 +575,49 @@ Two fields are recorded in ``prog-\u003eaux-\u003esig`` for an LSM to inspect:\n * - ``VERIFY_USE_PLATFORM_KEYRING``\n - ``BPF_SIG_KEYRING_PLATFORM``\n - ``0``\n- * - other (a user/session key serial)\n+ * - ``VERIFY_USE_BPF_KEYRING``\n+ - ``BPF_SIG_KEYRING_BPF``\n+ - ``0``\n+ * - other (a caller-supplied key serial)\n - ``BPF_SIG_KEYRING_USER``\n - serial of the resolved key/keyring\n \n-Producing a signed object\n-==========================\n+Producing and loading a signed object\n+=====================================\n+\n+Generating a signing key\n+------------------------\n+\n+Signing is algorithm agnostic: the algorithm comes from the X.509 certificate\n+and the PKCS#7 ``SignerInfo``. Anything the X.509 and PKCS#7 parsers understand\n+works with no BPF-side change. Both examples below read the certificate request\n+config from ``x509.genkey``, for which ``certs/x509.genkey`` serves as a\n+template (see Documentation/admin-guide/module-signing.rst). RSA::\n+\n+ openssl req -new -nodes -utf8 -sha256 -days 36500 -batch -x509 \\\n+ -config x509.genkey -outform PEM \\\n+ -out signing_key.pem -keyout signing_key.pem\n+ openssl x509 -in signing_key.pem -outform der -out signing_key.der\n+\n+ML-DSA-87 (FIPS-204), which needs openssl 3.5 or later and ``CONFIG_CRYPTO_MLDSA``\n+in the kernel. Note the absence of a digest option: ML-DSA hashes the message\n+itself and openssl rejects an explicit digest for it::\n+\n+ openssl req -new -nodes -utf8 -days 36500 -batch -x509 \\\n+ -newkey ML-DSA-87 -config x509.genkey -outform PEM \\\n+ -out signing_key.pem -keyout signing_key.pem\n+ openssl x509 -in signing_key.pem -outform der -out signing_key.der\n+\n+``bpftool`` handles the following internally: openssl before 4.0 cannot combine\n+ML-DSA with ``CMS_NOATTR``, so it falls back to signedAttrs, where only SHA-512\n+is permitted. This mirrors what module signing does as well.\n+\n+Signing\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+ bpftool gen skeleton -L -S -k signing_key.pem -i signing_key.der \\\n obj.bpf.o \u003e obj.lskel.h\n \n ``-L`` selects the light-skeleton (``gen_loader``) backend and ``-S`` enables\n@@ -454,12 +626,36 @@ signing; ``-k`` and ``-i`` supply the signing key and its X.509 certificate.\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+signing and is not included.\n+\n+Loading\n+-------\n+\n+The generated skeleton exposes ``keyring_id``, which selects the keyring the\n+kernel verifies against. Set it between open and load; loading then performs\n+the 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+ struct obj *skel = obj__open();\n+\n+ skel-\u003ekeyring_id = 3; /* VERIFY_USE_BPF_KEYRING */\n+ err = obj__load(skel);\n+\n+For the staging case the same object is loaded against a keyring the caller\n+populated itself, which only works on a system that has not set\n+``bpf.keyring_unsealed=1``::\n+\n+ /*\n+ * Staging only: this keyring is under the loader's own control and\n+ * carries no trust of its own. See \"Caller-supplied keyrings are for\n+ * staging\".\n+ */\n+ key_id = add_key(\"asymmetric\", \"\", der, der_sz, KEY_SPEC_SESSION_KEYRING);\n+ skel-\u003ekeyring_id = KEY_SPEC_SESSION_KEYRING;\n+ err = obj__load(skel);\n+\n+Either way the trusted public key must already be in the chosen keyring for\n+verification to succeed. For the bpf keyring that enrollment happens once at\n+boot, see `Provisioning`_.\n \n UAPI reference\n ==============\n@@ -487,6 +683,13 @@ UAPI reference\n The map content is not hashed separately at all - it is covered, as bytes,\n by the program signature.\n \n+Kernel command line:\n+\n+``bpf.keyring_unsealed=``\n+ Set to ``1`` to leave the bpf keyring unsealed for provisioning, and to make\n+ it the only non-system keyring a loader may select for the rest of the boot\n+ (see `The bpf keyring`_).\n+\n Notes and limitations\n ======================\n \n@@ -495,3 +698,5 @@ Notes and limitations\n exceed it.\n - The metadata container is a single-element array map, accessed through\n ``map_direct_value_addr``.\n+- The bpf keyring needs ``CONFIG_KEYS``; without it there is no bpf keyring\n+ and ``VERIFY_USE_BPF_KEYRING`` never resolves.\ndiff --git a/include/linux/bpf.h b/include/linux/bpf.h\nindex b3cd28d9e3b55..571f7792a5c41 100644\n--- a/include/linux/bpf.h\n+++ b/include/linux/bpf.h\n@@ -1735,6 +1735,7 @@ enum bpf_sig_keyring {\n \tBPF_SIG_KEYRING_SECONDARY,\n \tBPF_SIG_KEYRING_PLATFORM,\n \tBPF_SIG_KEYRING_USER,\n+\tBPF_SIG_KEYRING_BPF,\n };\n \n struct bpf_prog_aux {\n@@ -3819,6 +3820,8 @@ struct bpf_key {\n #if defined(CONFIG_KEYS) \u0026\u0026 defined(CONFIG_BPF_SYSCALL)\n struct bpf_key *bpf_lookup_user_key(s32 serial, u64 flags);\n struct bpf_key *bpf_lookup_system_key(u64 id);\n+struct bpf_key *bpf_lookup_keyring(void);\n+bool bpf_keyring_enforced(void);\n void bpf_key_put(struct bpf_key *bkey);\n int bpf_verify_pkcs7_signature(const struct bpf_dynptr *data_p,\n \t\t\t const struct bpf_dynptr *sig_p,\n@@ -3839,6 +3842,16 @@ static inline struct bpf_key *bpf_lookup_system_key(u64 id)\n \treturn NULL;\n }\n \n+static inline struct bpf_key *bpf_lookup_keyring(void)\n+{\n+\treturn NULL;\n+}\n+\n+static inline bool bpf_keyring_enforced(void)\n+{\n+\treturn false;\n+}\n+\n static inline void bpf_key_put(struct bpf_key *bkey)\n {\n }\ndiff --git a/include/linux/verification.h b/include/linux/verification.h\nindex dec7f2beabfd4..1cb59ddda2509 100644\n--- a/include/linux/verification.h\n+++ b/include/linux/verification.h\n@@ -18,6 +18,16 @@\n #define VERIFY_USE_SECONDARY_KEYRING ((struct key *)1UL)\n #define VERIFY_USE_PLATFORM_KEYRING ((struct key *)2UL)\n \n+/*\n+ * The id of BPF's \".bpf\" keyring, reserved from the same space. It is\n+ * explicitly not a sentinel like the two above as BPF resolves it to\n+ * the keyring itself and passes that, so verify_pkcs7_signature() never\n+ * sees this value, and system_keyring_id_check() must keep rejecting it.\n+ * Left as a plain integer so that handing it over as @trusted_keys does\n+ * not compile.\n+ */\n+#define VERIFY_USE_BPF_KEYRING\t3\n+\n static inline int system_keyring_id_check(u64 id)\n {\n \tif (id \u003e (unsigned long)VERIFY_USE_PLATFORM_KEYRING)\ndiff --git a/kernel/bpf/Makefile b/kernel/bpf/Makefile\nindex 90255d80e5be6..9a92c348bbda6 100644\n--- a/kernel/bpf/Makefile\n+++ b/kernel/bpf/Makefile\n@@ -27,6 +27,9 @@ obj-$(CONFIG_BPF_SYSCALL) += offload.o\n obj-$(CONFIG_BPF_SYSCALL) += net_namespace.o\n obj-$(CONFIG_BPF_SYSCALL) += tcx.o\n endif\n+ifeq ($(CONFIG_KEYS),y)\n+obj-$(CONFIG_BPF_SYSCALL) += keys.o\n+endif\n ifeq ($(CONFIG_PERF_EVENTS),y)\n obj-$(CONFIG_BPF_SYSCALL) += stackmap.o\n endif\ndiff --git a/kernel/bpf/keys.c b/kernel/bpf/keys.c\nnew file mode 100644\nindex 0000000000000..60cb85295c89a\n--- /dev/null\n+++ b/kernel/bpf/keys.c\n@@ -0,0 +1,72 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+/* Copyright (c) 2026 Isovalent */\n+\n+#include \u003clinux/bpf.h\u003e\n+#include \u003clinux/cred.h\u003e\n+#include \u003clinux/err.h\u003e\n+#include \u003clinux/init.h\u003e\n+#include \u003clinux/key.h\u003e\n+#include \u003clinux/moduleparam.h\u003e\n+#include \u003clinux/slab.h\u003e\n+\n+#undef MODULE_PARAM_PREFIX\n+#define MODULE_PARAM_PREFIX \"bpf.\"\n+\n+static struct key *bpf_keyring;\n+\n+static bool bpf_keyring_unsealed __ro_after_init;\n+module_param_named(keyring_unsealed, bpf_keyring_unsealed, bool, 0444);\n+MODULE_PARM_DESC(keyring_unsealed, \"Leave the bpf keyring unsealed\");\n+\n+bool bpf_keyring_enforced(void)\n+{\n+\treturn bpf_keyring_unsealed;\n+}\n+\n+struct bpf_key *bpf_lookup_keyring(void)\n+{\n+\tstruct bpf_key *bkey;\n+\n+\tif (!bpf_keyring)\n+\t\treturn NULL;\n+\tif (!READ_ONCE(bpf_keyring-\u003ekeys.nr_leaves_on_tree) ||\n+\t !READ_ONCE(bpf_keyring-\u003erestrict_link))\n+\t\treturn NULL;\n+\n+\tbkey = kmalloc_obj(*bkey);\n+\tif (!bkey)\n+\t\treturn NULL;\n+\n+\tbkey-\u003ekey = bpf_keyring;\n+\tbkey-\u003ehas_ref = false;\n+\treturn bkey;\n+}\n+\n+static int __init bpf_keyring_init(void)\n+{\n+\tstruct key *keyring;\n+\n+\tkeyring = keyring_alloc(\".bpf\",\n+\t\t\t\tGLOBAL_ROOT_UID, GLOBAL_ROOT_GID,\n+\t\t\t\tcurrent_cred(), KEY_POS_SEARCH |\n+\t\t\t\tKEY_USR_VIEW | KEY_USR_READ |\n+\t\t\t\tKEY_USR_WRITE | KEY_USR_SEARCH |\n+\t\t\t\tKEY_USR_SETATTR, KEY_ALLOC_NOT_IN_QUOTA,\n+\t\t\t\tNULL, NULL);\n+\tif (IS_ERR(keyring)) {\n+\t\tpr_err(\"bpf: cannot allocate bpf keyring: %ld\\n\",\n+\t\t PTR_ERR(keyring));\n+\t\treturn 0;\n+\t}\n+\tif (!bpf_keyring_unsealed \u0026\u0026\n+\t keyring_restrict(make_key_ref(keyring, true), NULL, NULL)) {\n+\t\tpr_err(\"bpf: cannot seal bpf keyring\\n\");\n+\t\tkey_revoke(keyring);\n+\t\tkey_put(keyring);\n+\t\treturn 0;\n+\t}\n+\n+\tbpf_keyring = keyring;\n+\treturn 0;\n+}\n+late_initcall(bpf_keyring_init);\ndiff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c\nindex e036ae20bf6b9..558e518ad3109 100644\n--- a/kernel/bpf/verifier.c\n+++ b/kernel/bpf/verifier.c\n@@ -20972,6 +20972,14 @@ int bpf_fixup_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,\n \treturn 0;\n }\n \n+/*\n+ * Upper bound on the PKCS#7 signature blob passed with a program. Comfortably\n+ * above the largest signature the kernel can verify, and far below anything\n+ * that would make rejecting a load expensive. Deliberately a fixed number so\n+ * that what the syscall accepts does not depend on PAGE_SIZE.\n+ */\n+#define BPF_PROG_MAX_SIGNATURE_SIZE\t(64 * 1024)\n+\n static enum bpf_sig_keyring bpf_classify_keyring(s32 keyring_id)\n {\n \tswitch (keyring_id) {\n@@ -20981,6 +20989,8 @@ static enum bpf_sig_keyring bpf_classify_keyring(s32 keyring_id)\n \t\treturn BPF_SIG_KEYRING_SECONDARY;\n \tcase (s32)(unsigned long)VERIFY_USE_PLATFORM_KEYRING:\n \t\treturn BPF_SIG_KEYRING_PLATFORM;\n+\tcase VERIFY_USE_BPF_KEYRING:\n+\t\treturn BPF_SIG_KEYRING_BPF;\n \tdefault:\n \t\treturn BPF_SIG_KEYRING_USER;\n \t}\n@@ -21009,21 +21019,32 @@ static int bpf_prog_verify_signature(struct bpf_verifier_env *env,\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 attr-\u003esignature_size \u003e BPF_PROG_MAX_SIGNATURE_SIZE)\n \t\treturn -EINVAL;\n-\tif (system_keyring_id_check(attr-\u003ekeyring_id) == 0)\n+\n+\tif (!system_keyring_id_check(attr-\u003ekeyring_id)) {\n \t\tkey = bpf_lookup_system_key(attr-\u003ekeyring_id);\n-\telse\n-\t\tkey = bpf_lookup_user_key(attr-\u003ekeyring_id, 0);\n+\t} else {\n+\t\tif (attr-\u003ekeyring_id != VERIFY_USE_BPF_KEYRING) {\n+\t\t\tif (bpf_keyring_enforced()) {\n+\t\t\t\tverbose(env, \"caller-supplied keyring refused, use bpf keyring\\n\");\n+\t\t\t\treturn -EPERM;\n+\t\t\t}\n+\t\t\tkey = bpf_lookup_user_key(attr-\u003ekeyring_id, 0);\n+\t\t} else {\n+\t\t\tkey = bpf_lookup_keyring();\n+\t\t}\n+\t}\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\tif (attr-\u003ekeyring_id == VERIFY_USE_BPF_KEYRING) {\n+\t\t\tverbose(env, \"the bpf keyring is empty or has not been restricted\\n\");\n+\t\t\treturn -ENOKEY;\n+\t\t} else {\n+\t\t\tverbose(env, \"cannot resolve signing keyring with keyring_id %d\\n\",\n+\t\t\t\tattr-\u003ekeyring_id);\n+\t\t\treturn -EINVAL;\n+\t\t}\n \t}\n \n \tsig = kvmemdup_bpfptr(usig, attr-\u003esignature_size);\ndiff --git a/tools/bpf/bpftool/main.h b/tools/bpf/bpftool/main.h\nindex 78b6e0ebb85d8..9315a1db1f7c2 100644\n--- a/tools/bpf/bpftool/main.h\n+++ b/tools/bpf/bpftool/main.h\n@@ -57,7 +57,7 @@ static inline void *u64_to_ptr(__u64 ptr)\n \t})\n \n #define ERR_MAX_LEN\t1024\n-#define MAX_SIG_SIZE\t4096\n+#define MAX_SIG_SIZE\t16384\n \n #define BPF_TAG_FMT\t\"%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx\"\n \ndiff --git a/tools/bpf/bpftool/sign.c b/tools/bpf/bpftool/sign.c\nindex 88726a6db6d0e..1b5502f48add0 100644\n--- a/tools/bpf/bpftool/sign.c\n+++ b/tools/bpf/bpftool/sign.c\n@@ -130,6 +130,9 @@ __u32 register_session_key(const char *key_der_path)\n \n int bpftool_prog_sign(struct bpf_load_and_run_opts *opts)\n {\n+\tunsigned int signer_flags = CMS_NOCERTS | CMS_BINARY | CMS_NOSMIMECAP |\n+\t\t\t\t CMS_USE_KEYID | CMS_NOATTR;\n+\tconst EVP_MD *cms_digest = EVP_sha256();\n \tBIO *bd_in = NULL, *bd_out = NULL;\n \tEVP_PKEY *private_key = NULL;\n \tCMS_ContentInfo *cms = NULL;\n@@ -167,6 +170,21 @@ int bpftool_prog_sign(struct bpf_load_and_run_opts *opts)\n \t\tgoto cleanup;\n \t}\n \n+#if OPENSSL_VERSION_NUMBER \u003e= 0x30000000L \u0026\u0026 OPENSSL_VERSION_NUMBER \u003c 0x40000000L\n+\tif (EVP_PKEY_is_a(private_key, \"ML-DSA-44\") ||\n+\t EVP_PKEY_is_a(private_key, \"ML-DSA-65\") ||\n+\t EVP_PKEY_is_a(private_key, \"ML-DSA-87\")) {\n+\t\t/*\n+\t\t * ML-DSA + CMS_NOATTR is not supported before openssl-4.0, so\n+\t\t * fall back to signedAttrs. ML-DSA does its own hashing and\n+\t\t * ignores the CMS digest, except when signedAttrs are used,\n+\t\t * where only SHA-512 is permitted.\n+\t\t */\n+\t\tsigner_flags \u0026= ~CMS_NOATTR;\n+\t\tcms_digest = EVP_sha512();\n+\t}\n+#endif\n+\n \tcms = CMS_sign(NULL, NULL, NULL, NULL,\n \t\t CMS_NOCERTS | CMS_PARTIAL | CMS_BINARY | CMS_DETACHED |\n \t\t\t CMS_STREAM);\n@@ -175,9 +193,7 @@ int bpftool_prog_sign(struct bpf_load_and_run_opts *opts)\n \t\tgoto cleanup;\n \t}\n \n-\tif (!CMS_add1_signer(cms, x509, private_key, EVP_sha256(),\n-\t\t\t CMS_NOCERTS | CMS_BINARY | CMS_NOSMIMECAP |\n-\t\t\t CMS_USE_KEYID | CMS_NOATTR)) {\n+\tif (!CMS_add1_signer(cms, x509, private_key, cms_digest, signer_flags)) {\n \t\terr = -EINVAL;\n \t\tgoto cleanup;\n \t}\ndiff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile\nindex 5f1a3bfc0569f..05b3ee64290fa 100644\n--- a/tools/testing/selftests/bpf/Makefile\n+++ b/tools/testing/selftests/bpf/Makefile\n@@ -663,7 +663,7 @@ $(TRUNNER_BPF_LSKELS): %.lskel.h: %.bpf.o $(BPFTOOL) | $(TRUNNER_OUTPUT)\n \t}) \u0026\u0026 \\\n \trm -f $$(\u003c:.o=.llinked1.o) $$(\u003c:.o=.llinked2.o) $$(\u003c:.o=.llinked3.o)\n \n-$(TRUNNER_BPF_LSKELS_SIGNED): %.lskel.h: %.bpf.o $(BPFTOOL) | $(TRUNNER_OUTPUT)\n+$(TRUNNER_BPF_LSKELS_SIGNED): %.lskel.h: %.bpf.o $(BPFTOOL) $(VERIFICATION_CERT) | $(TRUNNER_OUTPUT)\n \t$(Q)$(if $(PERMISSIVE),if [ ! -f $$\u003c ]; then\t\t\t\\\n \t\t$$(RM) $$@;\t\t\t\t\t\t\\\n \t\tprintf ' %-12s %s\\n' 'SKIP-SKEL' '$$(notdir $$@)' 1\u003e\u00262; \\\ndiff --git a/tools/testing/selftests/bpf/config b/tools/testing/selftests/bpf/config\nindex ea7044f30adc3..2ec15117abb50 100644\n--- a/tools/testing/selftests/bpf/config\n+++ b/tools/testing/selftests/bpf/config\n@@ -51,6 +51,8 @@ CONFIG_IPV6_SEG6_LWTUNNEL=y\n CONFIG_IPV6_SIT=y\n CONFIG_IPV6_TUNNEL=y\n CONFIG_KEYS=y\n+CONFIG_CRYPTO_MLDSA=y\n+CONFIG_CRYPTO_SHA512=y\n CONFIG_LIRC=y\n CONFIG_LIVEPATCH=y\n CONFIG_LWTUNNEL=y\ndiff --git a/tools/testing/selftests/bpf/prog_tests/signed_loader.c b/tools/testing/selftests/bpf/prog_tests/signed_loader.c\nindex 77381d345435c..620a6d22a759c 100644\n--- a/tools/testing/selftests/bpf/prog_tests/signed_loader.c\n+++ b/tools/testing/selftests/bpf/prog_tests/signed_loader.c\n@@ -32,11 +32,20 @@ enum {\n \tBPF_SIG_KEYRING_SECONDARY,\n \tBPF_SIG_KEYRING_PLATFORM,\n \tBPF_SIG_KEYRING_USER,\n+\tBPF_SIG_KEYRING_BPF,\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 __u32 fd_array_cnt)\n+#define BPF_KEYRING_BPF\t\t3\n+\n+/* verify_sig_setup.sh exits with this when openssl cannot do ML-DSA. */\n+#define SETUP_SKIP\t\t(-77)\n+\n+/* FIPS-204 ML-DSA-87 signature size, see include/crypto/mldsa.h. */\n+#define MLDSA87_SIGNATURE_SIZE\t4627\n+\n+static int load_loader_log(const void *insns, __u32 insns_sz, int map_fd,\n+\t\t\t const void *sig, __u32 sig_sz, __s32 keyring_id,\n+\t\t\t __u32 fd_array_cnt, char *log_buf, __u32 log_sz)\n {\n \tunion bpf_attr attr;\n \tint fd;\n@@ -48,18 +57,31 @@ static int load_loader(const void *insns, __u32 insns_sz, int map_fd,\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 = fd_array_cnt;\n \tif (sig) {\n \t\tattr.signature = ptr_to_u64(sig);\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+\tif (log_buf) {\n+\t\tattr.log_level = 1;\n+\t\tattr.log_buf = ptr_to_u64(log_buf);\n+\t\tattr.log_size = log_sz;\n+\t}\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 \treturn fd \u003c 0 ? -errno : fd;\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 __u32 fd_array_cnt)\n+{\n+\treturn load_loader_log(insns, insns_sz, map_fd, sig, sig_sz, keyring_id,\n+\t\t\t fd_array_cnt, NULL, 0);\n+}\n+\n 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@@ -156,12 +178,30 @@ static int run_setup(const char *cmd, const char *dir)\n \t}\n \tif (waitpid(pid, \u0026status, 0) \u003c 0)\n \t\treturn -errno;\n-\treturn (WIFEXITED(status) \u0026\u0026\n-\t\tWEXITSTATUS(status) == 0) ? 0 : -EINVAL;\n+\tif (!WIFEXITED(status))\n+\t\treturn -EINVAL;\n+\treturn -WEXITSTATUS(status);\n }\n \n-static int sign_buf(const char *dir, const void *buf, __u32 len,\n-\t\t void *sig, __u32 *sig_sz)\n+static void genkey_dir_fini(const char *dir)\n+{\n+\tstatic const char * const files[] = {\n+\t\t\"signing_key.der\", \"signing_key.pem\", \"x509.genkey\",\n+\t};\n+\tchar path[PATH_MAX];\n+\tsize_t i;\n+\n+\tif (!dir)\n+\t\treturn;\n+\tfor (i = 0; i \u003c ARRAY_SIZE(files); i++) {\n+\t\tsnprintf(path, sizeof(path), \"%s/%s\", dir, files[i]);\n+\t\tunlink(path);\n+\t}\n+\trmdir(dir);\n+}\n+\n+static int sign_buf_digest(const char *dir, const void *buf, __u32 len,\n+\t\t\t void *sig, __u32 *sig_sz, const char *digest)\n {\n \tchar data_tmpl[PATH_MAX], key[PATH_MAX];\n \tchar sigpath[PATH_MAX + sizeof(\".p7s\")];\n@@ -176,6 +216,7 @@ static int sign_buf(const char *dir, const void *buf, __u32 len,\n \tfd = mkstemp(data_tmpl);\n \tif (fd \u003c 0)\n \t\treturn -errno;\n+\tsnprintf(sigpath, sizeof(sigpath), \"%s.p7s\", data_tmpl);\n \tif (write(fd, buf, len) != (ssize_t)len) {\n \t\tclose(fd);\n \t\tret = -EIO;\n@@ -190,7 +231,7 @@ static int sign_buf(const char *dir, const void *buf, __u32 len,\n \t}\n \tif (pid == 0) {\n \t\tsnprintf(key, sizeof(key), \"%s/signing_key.pem\", dir);\n-\t\texeclp(\"./sign-file\", \"./sign-file\", \"-d\", \"sha256\",\n+\t\texeclp(\"./sign-file\", \"./sign-file\", \"-d\", digest,\n \t\t key, key, data_tmpl, NULL);\n \t\texit(1);\n \t}\n@@ -200,34 +241,38 @@ static int sign_buf(const char *dir, const void *buf, __u32 len,\n \t\tgoto out;\n \t}\n \n-\tsnprintf(sigpath, sizeof(sigpath), \"%s.p7s\", data_tmpl);\n \tif (stat(sigpath, \u0026st) \u003c 0) {\n \t\tret = -errno;\n \t\tgoto out;\n \t}\n \tif (st.st_size \u003e (off_t)*sig_sz) {\n \t\tret = -E2BIG;\n-\t\tgoto out_sig;\n+\t\tgoto out;\n \t}\n \tfd = open(sigpath, O_RDONLY);\n \tif (fd \u003c 0) {\n \t\tret = -errno;\n-\t\tgoto out_sig;\n+\t\tgoto out;\n \t}\n \tif (read(fd, sig, st.st_size) != st.st_size) {\n \t\tclose(fd);\n \t\tret = -EIO;\n-\t\tgoto out_sig;\n+\t\tgoto out;\n \t}\n \tclose(fd);\n \t*sig_sz = st.st_size;\n-out_sig:\n-\tunlink(sigpath);\n out:\n+\tunlink(sigpath);\n \tunlink(data_tmpl);\n \treturn ret;\n }\n \n+static int sign_buf(const char *dir, const void *buf, __u32 len,\n+\t\t void *sig, __u32 *sig_sz)\n+{\n+\treturn sign_buf_digest(dir, buf, len, sig, sig_sz, \"sha256\");\n+}\n+\n struct gen_loader_fixture {\n \tstruct test_signed_loader *skel;\n \tstruct gen_loader_opts gopts;\n@@ -457,7 +502,7 @@ static void signed_btf_fd_array_rejected(void)\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+\tif (!ASSERT_OK(run_setup(\"setup-rsa\", dir), \"verify_sig_setup\")) {\n \t\trmdir(dir);\n \t\treturn;\n \t}\n@@ -529,7 +574,6 @@ static void signature_failure_logs(void)\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@@ -538,22 +582,9 @@ static void signature_failure_logs(void)\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\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\tfd = load_loader_log(f.gopts.insns, f.gopts.insns_sz, -1, junk,\n+\t\t\t\t sizeof(junk), KEY_SPEC_SESSION_KEYRING, 0,\n+\t\t\t\t log_buf, sizeof(log_buf));\n \t\tASSERT_LT(fd, 0, \"invalid signature rejected at load\");\n \t\tif (fd \u003e= 0)\n \t\t\tclose(fd);\n@@ -571,8 +602,9 @@ static void signature_too_large(void)\n \n \tif (gen_loader_fixture_init(\u0026f) == 0) {\n \t\t/*\n-\t\t * signature_size beyond the kernel's bound (KMALLOC_MAX_CACHE_SIZE)\n-\t\t * is rejected before the buffer is read.\n+\t\t * signature_size beyond the kernel's bound\n+\t\t * (BPF_PROG_MAX_SIGNATURE_SIZE) is rejected before the buffer\n+\t\t * 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, 0);\n@@ -626,6 +658,268 @@ static void signature_bad_keyring(void)\n \tgen_loader_fixture_fini(\u0026f);\n }\n \n+static bool keyring_unsealed_boot(void)\n+{\n+\tchar val = 0;\n+\tint fd;\n+\n+\tfd = open(\"/sys/module/bpf/parameters/keyring_unsealed\", O_RDONLY);\n+\tif (fd \u003c 0)\n+\t\treturn false;\n+\tif (read(fd, \u0026val, 1) != 1)\n+\t\tval = 0;\n+\tclose(fd);\n+\treturn val == 'Y' || val == '1';\n+}\n+\n+static int bpf_keyring_lookup(int *nr_keys)\n+{\n+\tchar line[512], type[32], desc[64];\n+\tint serial = -ENOENT;\n+\tFILE *f;\n+\n+\tf = fopen(\"/proc/keys\", \"r\");\n+\tif (!f)\n+\t\treturn -errno;\n+\n+\twhile (fgets(line, sizeof(line), f)) {\n+\t\tunsigned int hex;\n+\t\tchar *sum;\n+\n+\t\tif (sscanf(line, \"%x %*s %*s %*s %*s %*s %*s %31s %63s\",\n+\t\t\t \u0026hex, type, desc) != 3)\n+\t\t\tcontinue;\n+\t\tif (strcmp(type, \"keyring\") || strcmp(desc, \".bpf:\"))\n+\t\t\tcontinue;\n+\n+\t\tserial = (int)hex;\n+\t\tif (nr_keys) {\n+\t\t\tsum = strstr(line, \".bpf: \");\n+\t\t\t*nr_keys = !sum || !strncmp(sum + 6, \"empty\", 5) ?\n+\t\t\t\t 0 : atoi(sum + 6);\n+\t\t}\n+\t\tbreak;\n+\t}\n+\tfclose(f);\n+\treturn serial;\n+}\n+\n+static long keyctl_ret(int cmd, unsigned long arg2, unsigned long arg3)\n+{\n+\tlong ret = syscall(__NR_keyctl, cmd, arg2, arg3);\n+\n+\treturn ret \u003c 0 ? -errno : ret;\n+}\n+\n+/*\n+ * What the bpf keyring still needs once it got provisioned: KEY_POS_SEARCH\n+ * for the in-kernel search during verification, and the user view/read bits\n+ * so it stays visible in /proc/keys, rest is dropped so the enrolled is\n+ * therefore final.\n+ */\n+#define BPF_KEYRING_PERM_LOCKED\t\t0x08030000\n+/* What bpf_keyring_init() grants at boot. */\n+#define BPF_KEYRING_PERM_INITIAL\t0x082f0000\n+\n+static void bpf_keyring_sealed(void)\n+{\n+\tstatic const __u8 junk[64] = {};\n+\tstruct gen_loader_fixture f;\n+\tint serial, key, fd;\n+\n+\tif (keyring_unsealed_boot()) {\n+\t\tprintf(\"%s:SKIP:the bpf keyring was unsealed at boot\\n\", __func__);\n+\t\ttest__skip();\n+\t\treturn;\n+\t}\n+\tserial = bpf_keyring_lookup(NULL);\n+\tif (serial \u003e= 0) {\n+\t\tkey = syscall(__NR_add_key, \"user\", \"sealprobe\", \"x\", 1, serial);\n+\t\tif (key \u003e= 0)\n+\t\t\tsyscall(__NR_keyctl, KEYCTL_UNLINK, key, serial);\n+\t\tASSERT_EQ(key \u003c 0 ? -errno : 0, -EPERM,\n+\t\t\t \"nothing links into a sealed keyring\");\n+\t}\n+\tif (gen_loader_fixture_init(\u0026f) == 0) {\n+\t\tfd = load_loader(f.gopts.insns, f.gopts.insns_sz, -1, junk,\n+\t\t\t\t sizeof(junk), BPF_KEYRING_BPF, 0);\n+\t\tASSERT_EQ(fd, -ENOKEY, \"sealed bpf keyring rejected\");\n+\t\tif (fd \u003e= 0)\n+\t\t\tclose(fd);\n+\t}\n+\tgen_loader_fixture_fini(\u0026f);\n+}\n+\n+static int try_load(const struct gen_loader_fixture *f, const void *sig,\n+\t\t __u32 sig_sz, __s32 keyring_id, char *log_buf, __u32 log_sz)\n+{\n+\tint map_fd, prog_fd;\n+\n+\tmap_fd = setup_meta_map(f);\n+\tif (!ASSERT_OK_FD(map_fd, \"meta_map\"))\n+\t\treturn map_fd;\n+\tprog_fd = load_loader_log(f-\u003egopts.insns, f-\u003egopts.insns_sz, map_fd,\n+\t\t\t\t sig, sig_sz, keyring_id, 1, log_buf, log_sz);\n+\tclose(map_fd);\n+\tif (prog_fd \u003e= 0)\n+\t\tclose(prog_fd);\n+\treturn prog_fd;\n+}\n+\n+/*\n+ * This needs bpf.keyring_unsealed=1 on the guest kernel command line, which\n+ * vmtest.sh can pass via KERNEL_CMDLINE_EXTRA. There is no way to unseal the\n+ * keyring from here, so without it the test skips. It also only works once\n+ * per boot, as restricting a keyring cannot be undone.\n+ */\n+static void bpf_keyring_provisioned(void)\n+{\n+\tchar dir_tmpl[] = \"/tmp/bpfkeyringXXXXXX\";\n+\tchar bad_tmpl[] = \"/tmp/bpfkeyringbadXXXXXX\";\n+\t__u8 *sig = NULL, *bad = NULL, *buf = NULL;\n+\tint serial, err;\n+\tint nr_keys = 0, der_fd = -1;\n+\tstruct gen_loader_fixture f;\n+\t__u32 sig_sz = 8192, bad_sz;\n+\tbool have_fixture = false;\n+\tchar *dir, *bad_dir = NULL;\n+\tchar log_buf[1024] = {};\n+\tchar path[PATH_MAX];\n+\t__u8 der[4096];\n+\tssize_t der_sz;\n+\n+\tserial = bpf_keyring_lookup(\u0026nr_keys);\n+\tif (serial \u003c 0) {\n+\t\tprintf(\"%s:SKIP:no bpf keyring (needs CONFIG_KEYS)\\n\", __func__);\n+\t\ttest__skip();\n+\t\treturn;\n+\t}\n+\tif (nr_keys != 0) {\n+\t\tprintf(\"%s:SKIP:the bpf keyring has already been provisioned\\n\",\n+\t\t __func__);\n+\t\ttest__skip();\n+\t\treturn;\n+\t}\n+\n+\tdir = mkdtemp(dir_tmpl);\n+\tif (!ASSERT_OK_PTR(dir, \"mkdtemp\"))\n+\t\treturn;\n+\tif (!ASSERT_OK(run_setup(\"genkey\", dir), \"verify_sig_setup genkey\"))\n+\t\tgoto rmdir;\n+\n+\tsnprintf(path, sizeof(path), \"%s/signing_key.der\", dir);\n+\tder_fd = open(path, O_RDONLY);\n+\tif (!ASSERT_OK_FD(der_fd, \"open signing_key.der\"))\n+\t\tgoto rmdir;\n+\tder_sz = read(der_fd, der, sizeof(der));\n+\tclose(der_fd);\n+\tif (!ASSERT_GT(der_sz, 0, \"read signing_key.der\"))\n+\t\tgoto rmdir;\n+\n+\terr = syscall(__NR_add_key, \"asymmetric\", \"\", der, (size_t)der_sz,\n+\t\t serial);\n+\tif (err \u003c 0 \u0026\u0026 errno == EPERM) {\n+\t\tprintf(\"%s:SKIP:the bpf keyring is sealed, need bpf.keyring_unsealed=1\\n\",\n+\t\t __func__);\n+\t\ttest__skip();\n+\t\tgoto rmdir;\n+\t}\n+\tif (!ASSERT_GE(err, 0, \"add the signing key to the bpf keyring\"))\n+\t\tgoto rmdir;\n+\n+\tsig = malloc(sig_sz);\n+\tif (!ASSERT_OK_PTR(sig, \"sig buf\"))\n+\t\tgoto out;\n+\thave_fixture = true;\n+\tif (gen_loader_fixture_init(\u0026f) != 0)\n+\t\tgoto out;\n+\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\t\u0026sig_sz), \"sign insns||metadata\"))\n+\t\tgoto out;\n+\n+\tASSERT_EQ(try_load(\u0026f, sig, sig_sz, BPF_KEYRING_BPF, NULL, 0), -ENOKEY,\n+\t\t \"unrestricted keyring still not consulted\");\n+\n+\tASSERT_EQ(try_load(\u0026f, sig, sig_sz, KEY_SPEC_SESSION_KEYRING, NULL, 0),\n+\t\t -EPERM, \"caller-supplied keyring refused before provisioning\");\n+\n+\tif (!ASSERT_OK(syscall(__NR_keyctl, KEYCTL_RESTRICT_KEYRING, serial,\n+\t\t\t NULL, NULL), \"restrict bpf keyring\"))\n+\t\tgoto out;\n+\n+\tif (!ASSERT_OK_FD(try_load(\u0026f, sig, sig_sz, BPF_KEYRING_BPF, NULL, 0),\n+\t\t\t \"load signed by a key in the .bpf keyring\"))\n+\t\tgoto out;\n+\n+\tbad_dir = mkdtemp(bad_tmpl);\n+\tif (!ASSERT_OK_PTR(bad_dir, \"mkdtemp unenrolled\"))\n+\t\tgoto out;\n+\tif (!ASSERT_OK(run_setup(\"genkey\", bad_dir), \"verify_sig_setup genkey unenrolled\"))\n+\t\tgoto out;\n+\tbad_sz = 8192;\n+\tbad = malloc(bad_sz);\n+\tif (!ASSERT_OK_PTR(bad, \"bad sig buf\"))\n+\t\tgoto out;\n+\tif (!ASSERT_OK(sign_buf(bad_dir, buf, f.gopts.insns_sz + f.data_sz, bad,\n+\t\t\t\t\u0026bad_sz), \"sign with an unenrolled key\"))\n+\t\tgoto out;\n+\n+\tASSERT_EQ(try_load(\u0026f, bad, bad_sz, BPF_KEYRING_BPF, log_buf,\n+\t\t\t sizeof(log_buf)), -ENOKEY,\n+\t\t \"key outside the bpf keyring refused\");\n+\tASSERT_HAS_SUBSTR(log_buf, \"signature verification failed\",\n+\t\t\t \"the bpf keyring was consulted\");\n+\n+\tf.blob[0] ^= 0xff;\n+\terr = try_load(\u0026f, sig, sig_sz, BPF_KEYRING_BPF, NULL, 0);\n+\tf.blob[0] ^= 0xff;\n+\tASSERT_EQ(err, -EKEYREJECTED, \"tampered metadata refused\");\n+\n+\tASSERT_EQ(try_load(\u0026f, sig, sig_sz, KEY_SPEC_SESSION_KEYRING, NULL, 0),\n+\t\t -EPERM, \"caller-supplied keyring refused once .bpf is in use\");\n+\n+\terr = keyctl_ret(KEYCTL_UNLINK, KEY_SPEC_SESSION_KEYRING, serial);\n+\tASSERT_EQ(err, -ENOENT, \"keyring writable while the user bits are there\");\n+\n+\terr = keyctl_ret(KEYCTL_SETPERM, serial, BPF_KEYRING_PERM_LOCKED);\n+\tif (!ASSERT_OK(err, \"drop the user bits on the bpf keyring\"))\n+\t\tgoto out;\n+\n+\tASSERT_OK_FD(try_load(\u0026f, sig, sig_sz, BPF_KEYRING_BPF, NULL, 0),\n+\t\t \"load still verified against the locked keyring\");\n+\n+\terr = keyctl_ret(KEYCTL_UNLINK, KEY_SPEC_SESSION_KEYRING, serial);\n+\tASSERT_EQ(err, -EACCES, \"unlink refused\");\n+\terr = keyctl_ret(KEYCTL_CLEAR, serial, 0);\n+\tASSERT_EQ(err, -EACCES, \"clear refused\");\n+\terr = keyctl_ret(KEYCTL_REVOKE, serial, 0);\n+\tASSERT_EQ(err, -EACCES, \"revoke refused\");\n+\terr = keyctl_ret(KEYCTL_INVALIDATE, serial, 0);\n+\tASSERT_EQ(err, -EACCES, \"invalidate refused\");\n+\terr = keyctl_ret(KEYCTL_SET_TIMEOUT, serial, 1);\n+\tASSERT_EQ(err, -EACCES, \"timeout refused\");\n+\terr = keyctl_ret(KEYCTL_SETPERM, serial, BPF_KEYRING_PERM_INITIAL);\n+\tASSERT_EQ(err, -EACCES, \"the bits cannot be granted back\");\n+\n+\tASSERT_EQ(bpf_keyring_lookup(\u0026nr_keys), serial, \"keyring still there\");\n+\tASSERT_EQ(nr_keys, 1, \"the enrolled key survived\");\n+out:\n+\tif (have_fixture)\n+\t\tgen_loader_fixture_fini(\u0026f);\n+\tgenkey_dir_fini(bad_dir);\n+\tfree(buf);\n+\tfree(bad);\n+\tfree(sig);\n+rmdir:\n+\tgenkey_dir_fini(dir);\n+}\n+\n /*\n * A signed loader must ignore ctx-supplied map dimensions: the host cannot\n * resize a signed program's maps via the loader ctx. Drive a one-map program\n@@ -831,7 +1125,7 @@ static void signature_authenticates_insns(void)\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+\tif (!ASSERT_OK(run_setup(\"setup-rsa\", dir), \"verify_sig_setup\")) {\n \t\trmdir(dir);\n \t\treturn;\n \t}\n@@ -931,7 +1225,7 @@ static void signature_authenticates_metadata(void)\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+\tif (!ASSERT_OK(run_setup(\"setup-rsa\", dir), \"verify_sig_setup\")) {\n \t\trmdir(dir);\n \t\treturn;\n \t}\n@@ -1267,7 +1561,7 @@ static void lsm_signature_verdict(void)\n \tdir = mkdtemp(dir_tmpl);\n \tif (!ASSERT_OK_PTR(dir, \"mkdtemp\"))\n \t\tgoto out;\n-\tif (!ASSERT_OK(run_setup(\"setup\", dir), \"verify_sig_setup\")) {\n+\tif (!ASSERT_OK(run_setup(\"setup-rsa\", dir), \"verify_sig_setup\")) {\n \t\trmdir(dir);\n \t\tdir = NULL;\n \t\tgoto out;\n@@ -1450,7 +1744,7 @@ static void loadtime_verify(struct bpf_object *obj, int expect_maps)\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+\tif (!ASSERT_OK(run_setup(\"setup-rsa\", dir), \"verify_sig_setup\")) {\n \t\trmdir(dir);\n \t\treturn;\n \t}\n@@ -1524,6 +1818,89 @@ static void loadtime_with_map(void)\n \ttest_signed_loader_map__destroy(skel);\n }\n \n+/*\n+ * End-to-end signed load with a post-quantum key. ML-DSA (FIPS-204) is wired\n+ * through the X.509 and PKCS#7 parsers, and BPF reaches them via\n+ * verify_pkcs7_signature() without knowing the algorithm, so an ML-DSA key in\n+ * the keyring should verify an ML-DSA signed program with no BPF-side work.\n+ */\n+static void mldsa_signed_load(void)\n+{\n+\tchar dir_tmpl[] = \"/tmp/bpfmldsaXXXXXX\";\n+\tint map_fd = -1, prog_fd = -1, err;\n+\t__u8 *sig = NULL, *buf = NULL;\n+\tstruct gen_loader_fixture f;\n+\tbool have_fixture = false;\n+\t__u32 sig_sz = 16384;\n+\tchar *dir;\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+\n+\terr = run_setup(\"setup-mldsa\", dir);\n+\tif (err == SETUP_SKIP) {\n+\t\tprintf(\"%s:SKIP:openssl has no ML-DSA support (needs 3.5+)\\n\",\n+\t\t __func__);\n+\t\ttest__skip();\n+\t\tgenkey_dir_fini(dir);\n+\t\treturn;\n+\t}\n+\tif (!ASSERT_OK(err, \"verify_sig_setup setup-mldsa\")) {\n+\t\tgenkey_dir_fini(dir);\n+\t\treturn;\n+\t}\n+\n+\tsig = malloc(sig_sz);\n+\tif (!ASSERT_OK_PTR(sig, \"sig buf\"))\n+\t\tgoto out;\n+\thave_fixture = true;\n+\tif (gen_loader_fixture_init(\u0026f) != 0)\n+\t\tgoto out;\n+\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+\n+\t/*\n+\t * ML-DSA hashes the message itself, but openssl before 4.0 cannot\n+\t * produce a CMS message without signedAttrs for it, and with those in\n+\t * play only SHA-512 is permitted for the messageDigest attribute.\n+\t */\n+\tif (!ASSERT_OK(sign_buf_digest(dir, buf, f.gopts.insns_sz + f.data_sz,\n+\t\t\t\t sig, \u0026sig_sz, \"sha512\"),\n+\t\t \"sign insns||metadata with ML-DSA\"))\n+\t\tgoto out;\n+\n+\t/*\n+\t * Guard against the setup silently handing back some other key type:\n+\t * an RSA or ECDSA signature is a few hundred bytes, where an ML-DSA-87\n+\t * one cannot be smaller than the raw signature it carries.\n+\t */\n+\tASSERT_GT(sig_sz, MLDSA87_SIGNATURE_SIZE, \"ML-DSA-87 signature size\");\n+\n+\tmap_fd = setup_meta_map(\u0026f);\n+\tif (!ASSERT_OK_FD(map_fd, \"meta_map\"))\n+\t\tgoto out;\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, 1);\n+\tASSERT_OK_FD(prog_fd, \"ML-DSA signed loader load\");\n+out:\n+\tif (prog_fd \u003e= 0)\n+\t\tclose(prog_fd);\n+\tif (map_fd \u003e= 0)\n+\t\tclose(map_fd);\n+\tif (have_fixture)\n+\t\tgen_loader_fixture_fini(\u0026f);\n+\tfree(buf);\n+\tfree(sig);\n+\trun_setup(\"cleanup\", dir);\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@@ -1548,7 +1925,7 @@ static void signed_no_fd_array(void)\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+\tif (!ASSERT_OK(run_setup(\"setup-rsa\", dir), \"verify_sig_setup\")) {\n \t\trmdir(dir);\n \t\treturn;\n \t}\n@@ -1619,7 +1996,7 @@ static void signed_map_by_fd_rejected(void)\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+\tif (!ASSERT_OK(run_setup(\"setup-rsa\", dir), \"verify_sig_setup\")) {\n \t\trmdir(dir);\n \t\tgoto out_map;\n \t}\n@@ -1681,7 +2058,7 @@ static void signed_sparse_fd_array_rejected(void)\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+\tif (!ASSERT_OK(run_setup(\"setup-rsa\", dir), \"verify_sig_setup\")) {\n \t\trmdir(dir);\n \t\tgoto out_map;\n \t}\n@@ -1735,7 +2112,7 @@ static void signed_module_kfunc_rejected(void)\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+\tif (!ASSERT_OK(run_setup(\"setup-rsa\", dir), \"verify_sig_setup\")) {\n \t\trmdir(dir);\n \t\treturn;\n \t}\n@@ -1779,6 +2156,11 @@ static void signed_module_kfunc_rejected(void)\n \n void test_signed_loader(void)\n {\n+\tif (keyring_unsealed_boot()) {\n+\t\tif (test__start_subtest(\"bpf_keyring_provisioned\"))\n+\t\t\tbpf_keyring_provisioned();\n+\t\treturn;\n+\t}\n \tif (test__start_subtest(\"loadtime_no_map\"))\n \t\tloadtime_no_map();\n \tif (test__start_subtest(\"loadtime_with_map\"))\n@@ -1805,6 +2187,10 @@ void test_signed_loader(void)\n \t\tsignature_zero_size();\n \tif (test__start_subtest(\"signature_bad_keyring\"))\n \t\tsignature_bad_keyring();\n+\tif (test__start_subtest(\"bpf_keyring_sealed\"))\n+\t\tbpf_keyring_sealed();\n+\tif (test__start_subtest(\"mldsa_signed_load\"))\n+\t\tmldsa_signed_load();\n \tif (test__start_subtest(\"metadata_ctx_max_entries_ignored\"))\n \t\tmetadata_ctx_max_entries_ignored();\n \tif (test__start_subtest(\"metadata_ctx_initial_value_ignored\"))\ndiff --git a/tools/testing/selftests/bpf/prog_tests/verify_pkcs7_sig.c b/tools/testing/selftests/bpf/prog_tests/verify_pkcs7_sig.c\nindex f327feb8e38c3..12b146d205d75 100644\n--- a/tools/testing/selftests/bpf/prog_tests/verify_pkcs7_sig.c\n+++ b/tools/testing/selftests/bpf/prog_tests/verify_pkcs7_sig.c\n@@ -257,7 +257,7 @@ static void test_verify_pkcs7_sig_from_map(void)\n \tif (!ASSERT_OK_PTR(tmp_dir, \"mkdtemp\"))\n \t\treturn;\n \n-\tret = _run_setup_process(tmp_dir, \"setup\");\n+\tret = _run_setup_process(tmp_dir, \"setup-rsa\");\n \tif (!ASSERT_OK(ret, \"_run_setup_process\"))\n \t\tgoto close_prog;\n \n@@ -458,7 +458,7 @@ static void test_pkcs7_sig_fsverity(void)\n \tsnprintf(data_path, PATH_MAX, \"%s/data-file\", tmp_dir);\n \tsnprintf(sig_path, PATH_MAX, \"%s/sig-file\", tmp_dir);\n \n-\tret = _run_setup_process(tmp_dir, \"setup\");\n+\tret = _run_setup_process(tmp_dir, \"setup-rsa\");\n \tif (!ASSERT_OK(ret, \"_run_setup_process\"))\n \t\tgoto out;\n \ndiff --git a/tools/testing/selftests/bpf/verify_sig_setup.sh b/tools/testing/selftests/bpf/verify_sig_setup.sh\nindex 09179fb551f09..2737c1a2bcfd6 100755\n--- a/tools/testing/selftests/bpf/verify_sig_setup.sh\n+++ b/tools/testing/selftests/bpf/verify_sig_setup.sh\n@@ -28,7 +28,7 @@ authorityKeyIdentifier=keyid\n \n usage()\n {\n-\techo \"Usage: $0 \u003csetup|cleanup \u003cexisting_tmp_dir\u003e\"\n+\techo \"Usage: $0 \u003csetup-rsa|setup-mldsa|cleanup \u003cexisting_tmp_dir\u003e\"\n \texit 1\n }\n \n@@ -47,7 +47,7 @@ genkey()\n \t\t${tmp_dir}/signing_key.der -outform der\n }\n \n-setup()\n+setup_rsa()\n {\n \tlocal tmp_dir=\"$1\"\n \n@@ -57,6 +57,57 @@ setup()\n \tkeyctl link $key_id $keyring_id\n }\n \n+mldsa_supported()\n+{\n+\tlocal tmp_dir=\"$1\"\n+\n+\tgenkey_mldsa \"${tmp_dir}\" || return 1\n+\t: \u003e ${tmp_dir}/probe\n+\t# Same digest as the caller signs with, see sign_buf_digest().\n+\t./sign-file -d sha512 ${tmp_dir}/signing_key.pem \\\n+\t\t${tmp_dir}/signing_key.pem ${tmp_dir}/probe || return 1\n+\trm -f ${tmp_dir}/probe ${tmp_dir}/probe.p7s\n+}\n+\n+genkey_mldsa()\n+{\n+\tlocal tmp_dir=\"$1\"\n+\n+\techo \"${x509_genkey_content}\" \u003e ${tmp_dir}/x509.genkey\n+\n+\t# No -\u003cdigest\u003e here: ML-DSA hashes the message itself, and openssl\n+\t# rejects an explicit digest for it.\n+\topenssl req -new -nodes -utf8 -days 36500 \\\n+\t\t\t-batch -x509 -newkey ML-DSA-87 \\\n+\t\t\t-config ${tmp_dir}/x509.genkey \\\n+\t\t\t-outform PEM -out ${tmp_dir}/signing_key.pem \\\n+\t\t\t-keyout ${tmp_dir}/signing_key.pem 2\u003e\u00261\n+\n+\topenssl x509 -in ${tmp_dir}/signing_key.pem -out \\\n+\t\t${tmp_dir}/signing_key.der -outform der\n+}\n+\n+mldsa_skip()\n+{\n+\tlocal tmp_dir=\"$1\"\n+\n+\trm -f ${tmp_dir}/x509.genkey ${tmp_dir}/signing_key.pem \\\n+\t\t${tmp_dir}/signing_key.der ${tmp_dir}/probe \\\n+\t\t${tmp_dir}/probe.p7s\n+\texit 77\n+}\n+\n+setup_mldsa()\n+{\n+\tlocal tmp_dir=\"$1\"\n+\n+\tmldsa_supported \"${tmp_dir}\" || mldsa_skip \"${tmp_dir}\"\n+\tkey_id=$(cat ${tmp_dir}/signing_key.der |\n+\t\t keyctl padd asymmetric ebpf_testing_key @s)\n+\tkeyring_id=$(keyctl newring ebpf_testing_keyring @s)\n+\tkeyctl link $key_id $keyring_id\n+}\n+\n cleanup() {\n \tlocal tmp_dir=\"$1\"\n \n@@ -91,7 +142,7 @@ catch()\n \tlocal exit_code=\"$1\"\n \tlocal log_file=\"$2\"\n \n-\tif [[ \"${exit_code}\" -ne 0 ]]; then\n+\tif [[ \"${exit_code}\" -ne 0 \u0026\u0026 \"${exit_code}\" -ne 77 ]]; then\n \t\tcat \"${log_file}\" \u003e\u00263\n \tfi\n \n@@ -108,8 +159,10 @@ main()\n \n \t[[ ! -d \"${tmp_dir}\" ]] \u0026\u0026 echo \"Directory ${tmp_dir} doesn't exist\" \u0026\u0026 exit 1\n \n-\tif [[ \"${action}\" == \"setup\" ]]; then\n-\t\tsetup \"${tmp_dir}\"\n+\tif [[ \"${action}\" == \"setup-rsa\" ]]; then\n+\t\tsetup_rsa \"${tmp_dir}\"\n+\telif [[ \"${action}\" == \"setup-mldsa\" ]]; then\n+\t\tsetup_mldsa \"${tmp_dir}\"\n \telif [[ \"${action}\" == \"genkey\" ]]; then\n \t\tgenkey \"${tmp_dir}\"\n \telif [[ \"${action}\" == \"cleanup\" ]]; then\ndiff --git a/tools/testing/selftests/bpf/vmtest.sh b/tools/testing/selftests/bpf/vmtest.sh\nindex 6a3d026d76bd6..e7e0b419a0b87 100755\n--- a/tools/testing/selftests/bpf/vmtest.sh\n+++ b/tools/testing/selftests/bpf/vmtest.sh\n@@ -107,6 +107,14 @@ Options:\n \t-s)\t\tInstead of powering off the VM, start an interactive\n \t\t\tshell. If \u003ccommand\u003e is specified, the shell runs after\n \t\t\tthe command finishes executing\n+\n+Environment variables:\n+\n+\tKERNEL_CMDLINE_EXTRA\n+\t\t\tExtra arguments to append to the guest kernel command\n+\t\t\tline, for tests that need a boot-time setting. e.g:\n+\n+\t KERNEL_CMDLINE_EXTRA=\"bpf.keyring_unsealed=1\" $0 -- ./test_progs -t signed_loader\n EOF\n }\n \n@@ -286,6 +294,12 @@ EOF\n \t\tQEMU_FLAGS=(\"${HOST_FLAGS[@]}\")\n \tfi\n \n+\tlocal kernel_cmdline=\"root=/dev/vda rw console=${QEMU_CONSOLE}\"\n+\n+\tif [[ -n \"${KERNEL_CMDLINE_EXTRA:-}\" ]]; then\n+\t\tkernel_cmdline+=\" ${KERNEL_CMDLINE_EXTRA}\"\n+\tfi\n+\n \t${QEMU_BINARY} \\\n \t\t-nodefaults \\\n \t\t-display none \\\n@@ -294,7 +308,7 @@ EOF\n \t\t-m 4G \\\n \t\t-drive file=\"${rootfs_img}\",format=raw,index=1,media=disk,if=virtio,cache=none \\\n \t\t-kernel \"${kernel_bzimage}\" \\\n-\t\t-append \"root=/dev/vda rw console=${QEMU_CONSOLE}\"\n+\t\t-append \"${kernel_cmdline}\"\n }\n \n copy_logs()\n"
} |
| 2/1 | 2026/08/25 14:55 | agent | patch-evaluator |
1mModel:best-expensive Args: null Results: {
"EnableConfigs": [
"KEYS",
"BPF_SYSCALL"
],
"FocusSymbols": [
"bpf_lookup_keyring",
"bpf_keyring_enforced"
],
"Reasoning": "The patch introduces a new `.bpf` keyring for BPF program signature verification and a `bpf.keyring_unsealed` boot parameter. It modifies the BPF program loading and verification paths in `bpf_prog_verify_signature` to handle the new keyring and enforce its use when configured. These changes affect the `bpf()` syscall path and introduce new reachable state and logic that should be fuzzed.",
"WorthFuzzing": true
}Instruction: You are an expert Linux kernel maintainer. Your job is to review a provided patch series and determine if it makes functional changes to the kernel that should be fuzzed. IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively use your code access tools to examine the actual source code and confirm any assumptions. Return WorthFuzzing=false if the patch only contains: - Modifications to Documentation/, Kconfig files, or code comments. - Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints. - Changes to numeric constants or macros that do not functionally alter execution flow. - Code paths that are impossible to reach in virtualized environments like GCE or QEMU, even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim). - Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed, ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU. - Driver .remove, .shutdown, or pci_unregister_driver teardown callbacks (e.g., igb_remove) that are executed only during PCI hot-unplug or sysfs driver unbind operations. 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. Prefer non-static, non-inlined API entrypoint functions over internal static helper functions (which are inlined by the compiler and do not have distinct symbol addresses). 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. Also check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed for Kbuild to compile the code statically into vmlinux. 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 4b232f890ce6f15bc78b56751fb69c404b179db0
Author: syz-cluster <triage@syzkaller.com>
Date: Tue Aug 25 14:55:07 2026 +0000
syz-cluster: applied patch under review
diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index e4643634a9b1b..e70fb15757d40 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -667,6 +667,22 @@ Kernel parameters
See Documentation/admin-guide/bootconfig.rst
+ bpf.keyring_unsealed=
+ [BPF] When set to 1, leave the bpf keyring unsealed
+ after initialization so that userspace can provision
+ keys. Once the keyring is restricted it becomes active
+ and can be used for BPF program signature verification.
+
+ Setting this also means that the bpf keyring is the
+ only non-system keyring a loader may select for the
+ rest of the boot: caller-supplied user/session
+ keyrings are refused with -EPERM, whether or not
+ provisioning actually completed. The system keyrings
+ stay selectable. Leaving it unset keeps the prior
+ behaviour, where a caller-supplied keyring is allowed.
+
+ See Documentation/bpf/signing.rst
+
bttv.card= [HW,V4L] bttv (bt848 + bt878 based grabber cards)
bttv.radio= Most important insmod options are available as
kernel args too.
diff --git a/Documentation/bpf/signing.rst b/Documentation/bpf/signing.rst
index e73eaaebd8b15..6c03fb58b1464 100644
--- a/Documentation/bpf/signing.rst
+++ b/Documentation/bpf/signing.rst
@@ -254,21 +254,25 @@ 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.)
+A more realistic admission policy than "is it signed at all": base trust in
+the bpf keyring, accept a staging signature only while the key/keyring the
+program 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. */
+ /*
+ * Serials of caller-supplied keyrings we are willing to stage. Empty
+ * on a system that has committed to the bpf keyring, where the kernel
+ * refuses them anyway.
+ */
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__type(key, __s32); /* keyring_serial */
__type(value, __u8);
__uint(max_entries, 64);
- } trusted_user_keys SEC(".maps");
+ } staging_keys SEC(".maps");
/* Audit stream consumed by a userspace logger. */
struct {
@@ -291,11 +295,19 @@ auditable. (Illustrative - error checking elided.)
if (kernel)
return 0; /* trust in-kernel loads */
- if (verdict != BPF_SIG_VERIFIED)
+ 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 */
+ } else switch (ktype) {
+ case BPF_SIG_KEYRING_BUILTIN:
+ case BPF_SIG_KEYRING_BPF:
+ break;
+ case BPF_SIG_KEYRING_USER:
+ if (!bpf_map_lookup_elem(&staging_keys, &serial))
+ ret = -EPERM;
+ break;
+ default:
+ ret = -EPERM; /* keyring not in policy */
+ }
d = bpf_ringbuf_reserve(&audit, sizeof(*d), 0);
if (d) {
@@ -309,6 +321,11 @@ auditable. (Illustrative - error checking elided.)
return ret;
}
+Such a policy is what makes a caller-supplied keyring usable at all on a system
+that does not boot with ``bpf.keyring_unsealed=1``: the allowlist bounds which
+staged keys count, and the LSM itself has to protect them from being tampered
+with.
+
Observing a verified load: ``security_bpf_prog()``
--------------------------------------------------
@@ -381,8 +398,9 @@ 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
+let a policy distinguish "verified against the operator's bpf keyring" from
+"verified against a keyring the loader supplied itself", which is the
+distinction that matters most. A policy LSM such as IPE could consume the same
hooks to enforce system policy without writing any BPF, though none implements
this today.
@@ -390,33 +408,154 @@ 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.
+against. Four values are well-known; anything else is taken as the serial of a
+caller-supplied user or session key or keyring:
+
+.. list-table::
+ :header-rows: 1
+
+ * - ``keyring_id``
+ - Keyring
+ * - ``0``
+ - builtin trusted keyring
+ * - ``VERIFY_USE_SECONDARY_KEYRING`` (``1``)
+ - secondary trusted keyring
+ * - ``VERIFY_USE_PLATFORM_KEYRING`` (``2``)
+ - platform keyring
+ * - ``VERIFY_USE_BPF_KEYRING`` (``3``)
+ - the bpf keyring
+ * - anything else
+ - serial of a caller-supplied user/session key or keyring
+
+The keyring is resolved first, before the signature bytes are examined, so a
+signature naming a keyring that cannot be used is rejected up front, and a
+failed verification aborts the load - a program that loads successfully with
+a signature therefore always has consistent keyring fields recorded.
+
+The bpf keyring
+---------------
+
+A system keyring needs a kernel rebuild or a vouched-for enrollment to rotate a
+key, and grants BPF-signing trust to keys trusted for everything else in the
+kernel too. A caller-supplied keyring, at the other extreme, is filled by the
+very process that loads the program and so carries no trust of its own.
+
+The bpf keyring fills that gap and is the trust anchor which a signed BPF
+deployment should be built on top of: a keyring named ``.bpf``, selected with
+``VERIFY_USE_BPF_KEYRING``, that an operator provisions at boot with a key
+scoped to BPF program loading and nothing else in the kernel's trust hierarchy.
+It is owned by the operator rather than by the loader, and rotatable across a
+reboot without touching the kernel image. It is modelled after the dm-verity
+keyring (see ``dm_verity.keyring_unsealed=``) and provisioned the same way: an
+initrd runs the ``keyctl`` steps below before handing off to the rootfs.
+
+Provisioning
+~~~~~~~~~~~~
+
+The keyring is created during ``late_initcall`` and is **sealed empty** by
+default: it carries a reject-all restriction, so no key can ever be added and
+``VERIFY_USE_BPF_KEYRING`` fails with ``-ENOKEY`` for the whole boot.
+
+``bpf.keyring_unsealed=1`` leaves it unrestricted at init so the initrd can
+provision it. The keyring is not linked into any process keyring, so it is
+addressed by the serial ``/proc/keys`` reports. Steps would be as follows::
+
+ serial=0x$(awk '$8 == "keyring" && $9 == ".bpf:" { print $1 }' /proc/keys)
+
+ keyctl padd asymmetric "" $serial < signing_key.der
+ keyctl restrict_keyring $serial
+
+Both steps are required: the keyring is consulted only once it is **non-empty
+and restricted**. An unrestricted keyring is ignored even when it holds keys,
+so a half-provisioned keyring is inert rather than a weaker trust anchor, and a
+load naming it fails with ``-ENOKEY`` and a verifier log. Restricting cannot
+be undone.
+
+More than one key is enrolled by repeating the ``keyctl padd`` step; the
+restriction is applied once, after the last of them::
+
+ for key in /etc/bpf/keys/*.der; do
+ keyctl padd asymmetric "" $serial < $key
+ done
+
+ keyctl restrict_keyring $serial
+ keyctl show $serial
+
+The restriction bounds what can be added, never what can be taken away. A key
+that is already enrolled can still be unlinked, and the keyring cleared or
+revoked, by anything running as root. That does not weaken the anchor, since
+a keyring left empty is no longer consulted and a load naming it fails with
+``-ENOKEY``, but it does take signed loading out until the next boot. Dropping
+the user permissions the keyring no longer needs would close that; as a third
+step in the initrd::
+
+ keyctl setperm $serial 0x08030000
+
+What remains is ``KEY_POS_SEARCH`` for the in-kernel search during verification,
+plus ``KEY_USR_VIEW`` and ``KEY_USR_READ`` so the keyring stays visible in
+``/proc/keys`` and ``keyctl show``.
+
+Provisioning has to complete before control passes to the rootfs. The keyring
+takes any number of keys for as long as it is unrestricted, so it is the
+restriction that bounds the enrolled set, not the first enrollment. Before
+the initrd hands off control, it must therefore restrict the keyring after
+the last enrollment.
+
+Enforcement
+~~~~~~~~~~~
+
+``bpf.keyring_unsealed=1`` states that the bpf keyring is *the* trust anchor for
+this boot, so it does more than unseal. From the first program load onwards a
+caller-supplied user/session keyring is refused with ``-EPERM`` and a verifier
+log message, whether or not provisioning ever completed. The system keyrings
+stay selectable.
+
+Enforcement is readable at ``/sys/module/bpf/parameters/keyring_unsealed``, and
+read-only there: the flag is ``__ro_after_init`` behind a 0444 parameter. It is
+also derived from the boot flag rather than from the keyring's runtime state,
+so there is no window early in boot during which a caller-supplied keyring is
+still accepted.
+
+Caller-supplied keyrings are for staging
+----------------------------------------
+
+A ``keyring_id`` naming a user or session key or keyring is a *staging*
+mechanism, not a trust anchor: it is filled by the same userspace that loads the
+program, so verifying against it establishes only that the loader signed what it
+loaded. Its purpose is to let software installed onto a running system - whose
+signing key is not enrolled anywhere yet - run signed until that key reaches the
+bpf keyring on the next boot.
+
+A system that has committed to the bpf keyring refuses this path outright (see
+`Enforcement`_). A system that has not can still allow it, but a policy must
+never treat ``BPF_SIG_KEYRING_USER`` as equivalent to the bpf or system
+keyrings; it should allowlist the specific serials it is willing to stage and
+pair that with a BPF LSM policy protecting those keys from tampering, as in
+`Enforcement via LSMs`_.
+
+Recorded fields
+---------------
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.
+ keyrings, ``_BPF`` for the bpf keyring, or ``_USER`` for a caller-supplied
+ 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
+ **caller-supplied 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".
+ serial. The system keyrings and the bpf keyring are trusted as a whole and
+ expose no serial here, so the serial is ``0`` for them, and ``0`` for
+ unsigned programs. A non-zero ``keyring_serial`` is therefore exactly
+ "verified against the caller-supplied key/keyring with this serial", which
+ is exactly the case a policy has to scrutinise.
.. list-table::
:header-rows: 1
@@ -436,16 +575,49 @@ Two fields are recorded in ``prog->aux->sig`` for an LSM to inspect:
* - ``VERIFY_USE_PLATFORM_KEYRING``
- ``BPF_SIG_KEYRING_PLATFORM``
- ``0``
- * - other (a user/session key serial)
+ * - ``VERIFY_USE_BPF_KEYRING``
+ - ``BPF_SIG_KEYRING_BPF``
+ - ``0``
+ * - other (a caller-supplied key serial)
- ``BPF_SIG_KEYRING_USER``
- serial of the resolved key/keyring
-Producing a signed object
-==========================
+Producing and loading a signed object
+=====================================
+
+Generating a signing key
+------------------------
+
+Signing is algorithm agnostic: the algorithm comes from the X.509 certificate
+and the PKCS#7 ``SignerInfo``. Anything the X.509 and PKCS#7 parsers understand
+works with no BPF-side change. Both examples below read the certificate request
+config from ``x509.genkey``, for which ``certs/x509.genkey`` serves as a
+template (see Documentation/admin-guide/module-signing.rst). RSA::
+
+ openssl req -new -nodes -utf8 -sha256 -days 36500 -batch -x509 \
+ -config x509.genkey -outform PEM \
+ -out signing_key.pem -keyout signing_key.pem
+ openssl x509 -in signing_key.pem -outform der -out signing_key.der
+
+ML-DSA-87 (FIPS-204), which needs openssl 3.5 or later and ``CONFIG_CRYPTO_MLDSA``
+in the kernel. Note the absence of a digest option: ML-DSA hashes the message
+itself and openssl rejects an explicit digest for it::
+
+ openssl req -new -nodes -utf8 -days 36500 -batch -x509 \
+ -newkey ML-DSA-87 -config x509.genkey -outform PEM \
+ -out signing_key.pem -keyout signing_key.pem
+ openssl x509 -in signing_key.pem -outform der -out signing_key.der
+
+``bpftool`` handles the following internally: openssl before 4.0 cannot combine
+ML-DSA with ``CMS_NOATTR``, so it falls back to signedAttrs, where only SHA-512
+is permitted. This mirrors what module signing does as well.
+
+Signing
+-------
``bpftool`` generates and signs a light skeleton in one step::
- bpftool gen skeleton -L -S -k <private_key.pem> -i <certificate.x509> \
+ bpftool gen skeleton -L -S -k signing_key.pem -i signing_key.der \
obj.bpf.o > obj.lskel.h
``-L`` selects the light-skeleton (``gen_loader``) backend and ``-S`` enables
@@ -454,12 +626,36 @@ signing; ``-k`` and ``-i`` supply the signing key and its X.509 certificate.
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.
+signing and is not included.
+
+Loading
+-------
+
+The generated skeleton exposes ``keyring_id``, which selects the keyring the
+kernel verifies against. Set it between open and load; loading then 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.
+ struct obj *skel = obj__open();
+
+ skel->keyring_id = 3; /* VERIFY_USE_BPF_KEYRING */
+ err = obj__load(skel);
+
+For the staging case the same object is loaded against a keyring the caller
+populated itself, which only works on a system that has not set
+``bpf.keyring_unsealed=1``::
+
+ /*
+ * Staging only: this keyring is under the loader's own control and
+ * carries no trust of its own. See "Caller-supplied keyrings are for
+ * staging".
+ */
+ key_id = add_key("asymmetric", "", der, der_sz, KEY_SPEC_SESSION_KEYRING);
+ skel->keyring_id = KEY_SPEC_SESSION_KEYRING;
+ err = obj__load(skel);
+
+Either way the trusted public key must already be in the chosen keyring for
+verification to succeed. For the bpf keyring that enrollment happens once at
+boot, see `Provisioning`_.
UAPI reference
==============
@@ -487,6 +683,13 @@ UAPI reference
The map content is not hashed separately at all - it is covered, as bytes,
by the program signature.
+Kernel command line:
+
+``bpf.keyring_unsealed=``
+ Set to ``1`` to leave the bpf keyring unsealed for provisioning, and to make
+ it the only non-system keyring a loader may select for the rest of the boot
+ (see `The bpf keyring`_).
+
Notes and limitations
======================
@@ -495,3 +698,5 @@ Notes and limitations
exceed it.
- The metadata container is a single-element array map, accessed through
``map_direct_value_addr``.
+- The bpf keyring needs ``CONFIG_KEYS``; without it there is no bpf keyring
+ and ``VERIFY_USE_BPF_KEYRING`` never resolves.
diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index b3cd28d9e3b55..571f7792a5c41 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -1735,6 +1735,7 @@ enum bpf_sig_keyring {
BPF_SIG_KEYRING_SECONDARY,
BPF_SIG_KEYRING_PLATFORM,
BPF_SIG_KEYRING_USER,
+ BPF_SIG_KEYRING_BPF,
};
struct bpf_prog_aux {
@@ -3819,6 +3820,8 @@ struct bpf_key {
#if defined(CONFIG_KEYS) && defined(CONFIG_BPF_SYSCALL)
struct bpf_key *bpf_lookup_user_key(s32 serial, u64 flags);
struct bpf_key *bpf_lookup_system_key(u64 id);
+struct bpf_key *bpf_lookup_keyring(void);
+bool bpf_keyring_enforced(void);
void bpf_key_put(struct bpf_key *bkey);
int bpf_verify_pkcs7_signature(const struct bpf_dynptr *data_p,
const struct bpf_dynptr *sig_p,
@@ -3839,6 +3842,16 @@ static inline struct bpf_key *bpf_lookup_system_key(u64 id)
return NULL;
}
+static inline struct bpf_key *bpf_lookup_keyring(void)
+{
+ return NULL;
+}
+
+static inline bool bpf_keyring_enforced(void)
+{
+ return false;
+}
+
static inline void bpf_key_put(struct bpf_key *bkey)
{
}
diff --git a/include/linux/verification.h b/include/linux/verification.h
index dec7f2beabfd4..1cb59ddda2509 100644
--- a/include/linux/verification.h
+++ b/include/linux/verification.h
@@ -18,6 +18,16 @@
#define VERIFY_USE_SECONDARY_KEYRING ((struct key *)1UL)
#define VERIFY_USE_PLATFORM_KEYRING ((struct key *)2UL)
+/*
+ * The id of BPF's ".bpf" keyring, reserved from the same space. It is
+ * explicitly not a sentinel like the two above as BPF resolves it to
+ * the keyring itself and passes that, so verify_pkcs7_signature() never
+ * sees this value, and system_keyring_id_check() must keep rejecting it.
+ * Left as a plain integer so that handing it over as @trusted_keys does
+ * not compile.
+ */
+#define VERIFY_USE_BPF_KEYRING 3
+
static inline int system_keyring_id_check(u64 id)
{
if (id > (unsigned long)VERIFY_USE_PLATFORM_KEYRING)
diff --git a/kernel/bpf/Makefile b/kernel/bpf/Makefile
index 90255d80e5be6..9a92c348bbda6 100644
--- a/kernel/bpf/Makefile
+++ b/kernel/bpf/Makefile
@@ -27,6 +27,9 @@ obj-$(CONFIG_BPF_SYSCALL) += offload.o
obj-$(CONFIG_BPF_SYSCALL) += net_namespace.o
obj-$(CONFIG_BPF_SYSCALL) += tcx.o
endif
+ifeq ($(CONFIG_KEYS),y)
+obj-$(CONFIG_BPF_SYSCALL) += keys.o
+endif
ifeq ($(CONFIG_PERF_EVENTS),y)
obj-$(CONFIG_BPF_SYSCALL) += stackmap.o
endif
diff --git a/kernel/bpf/keys.c b/kernel/bpf/keys.c
new file mode 100644
index 0000000000000..60cb85295c89a
--- /dev/null
+++ b/kernel/bpf/keys.c
@@ -0,0 +1,72 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2026 Isovalent */
+
+#include <linux/bpf.h>
+#include <linux/cred.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/key.h>
+#include <linux/moduleparam.h>
+#include <linux/slab.h>
+
+#undef MODULE_PARAM_PREFIX
+#define MODULE_PARAM_PREFIX "bpf."
+
+static struct key *bpf_keyring;
+
+static bool bpf_keyring_unsealed __ro_after_init;
+module_param_named(keyring_unsealed, bpf_keyring_unsealed, bool, 0444);
+MODULE_PARM_DESC(keyring_unsealed, "Leave the bpf keyring unsealed");
+
+bool bpf_keyring_enforced(void)
+{
+ return bpf_keyring_unsealed;
+}
+
+struct bpf_key *bpf_lookup_keyring(void)
+{
+ struct bpf_key *bkey;
+
+ if (!bpf_keyring)
+ return NULL;
+ if (!READ_ONCE(bpf_keyring->keys.nr_leaves_on_tree) ||
+ !READ_ONCE(bpf_keyring->restrict_link))
+ return NULL;
+
+ bkey = kmalloc_obj(*bkey);
+ if (!bkey)
+ return NULL;
+
+ bkey->key = bpf_keyring;
+ bkey->has_ref = false;
+ return bkey;
+}
+
+static int __init bpf_keyring_init(void)
+{
+ struct key *keyring;
+
+ keyring = keyring_alloc(".bpf",
+ GLOBAL_ROOT_UID, GLOBAL_ROOT_GID,
+ current_cred(), KEY_POS_SEARCH |
+ KEY_USR_VIEW | KEY_USR_READ |
+ KEY_USR_WRITE | KEY_USR_SEARCH |
+ KEY_USR_SETATTR, KEY_ALLOC_NOT_IN_QUOTA,
+ NULL, NULL);
+ if (IS_ERR(keyring)) {
+ pr_err("bpf: cannot allocate bpf keyring: %ld\n",
+ PTR_ERR(keyring));
+ return 0;
+ }
+ if (!bpf_keyring_unsealed &&
+ keyring_restrict(make_key_ref(keyring, true), NULL, NULL)) {
+ pr_err("bpf: cannot seal bpf keyring\n");
+ key_revoke(keyring);
+ key_put(keyring);
+ return 0;
+ }
+
+ bpf_keyring = keyring;
+ return 0;
+}
+late_initcall(bpf_keyring_init);
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index e036ae20bf6b9..558e518ad3109 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -20972,6 +20972,14 @@ int bpf_fixup_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
return 0;
}
+/*
+ * Upper bound on the PKCS#7 signature blob passed with a program. Comfortably
+ * above the largest signature the kernel can verify, and far below anything
+ * that would make rejecting a load expensive. Deliberately a fixed number so
+ * that what the syscall accepts does not depend on PAGE_SIZE.
+ */
+#define BPF_PROG_MAX_SIGNATURE_SIZE (64 * 1024)
+
static enum bpf_sig_keyring bpf_classify_keyring(s32 keyring_id)
{
switch (keyring_id) {
@@ -20981,6 +20989,8 @@ static enum bpf_sig_keyring bpf_classify_keyring(s32 keyring_id)
return BPF_SIG_KEYRING_SECONDARY;
case (s32)(unsigned long)VERIFY_USE_PLATFORM_KEYRING:
return BPF_SIG_KEYRING_PLATFORM;
+ case VERIFY_USE_BPF_KEYRING:
+ return BPF_SIG_KEYRING_BPF;
default:
return BPF_SIG_KEYRING_USER;
}
@@ -21009,21 +21019,32 @@ static int bpf_prog_verify_signature(struct bpf_verifier_env *env,
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)
+ attr->signature_size > BPF_PROG_MAX_SIGNATURE_SIZE)
return -EINVAL;
- if (system_keyring_id_check(attr->keyring_id) == 0)
+
+ if (!system_keyring_id_check(attr->keyring_id)) {
key = bpf_lookup_system_key(attr->keyring_id);
- else
- key = bpf_lookup_user_key(attr->keyring_id, 0);
+ } else {
+ if (attr->keyring_id != VERIFY_USE_BPF_KEYRING) {
+ if (bpf_keyring_enforced()) {
+ verbose(env, "caller-supplied keyring refused, use bpf keyring\n");
+ return -EPERM;
+ }
+ key = bpf_lookup_user_key(attr->keyring_id, 0);
+ } else {
+ key = bpf_lookup_keyring();
+ }
+ }
if (!key) {
- verbose(env, "cannot resolve signing keyring with keyring_id %d\n",
- attr->keyring_id);
- return -EINVAL;
+ if (attr->keyring_id == VERIFY_USE_BPF_KEYRING) {
+ verbose(env, "the bpf keyring is empty or has not been restricted\n");
+ return -ENOKEY;
+ } else {
+ verbose(env, "cannot resolve signing keyring with keyring_id %d\n",
+ attr->keyring_id);
+ return -EINVAL;
+ }
}
sig = kvmemdup_bpfptr(usig, attr->signature_size);
diff --git a/tools/bpf/bpftool/main.h b/tools/bpf/bpftool/main.h
index 78b6e0ebb85d8..9315a1db1f7c2 100644
--- a/tools/bpf/bpftool/main.h
+++ b/tools/bpf/bpftool/main.h
@@ -57,7 +57,7 @@ static inline void *u64_to_ptr(__u64 ptr)
})
#define ERR_MAX_LEN 1024
-#define MAX_SIG_SIZE 4096
+#define MAX_SIG_SIZE 16384
#define BPF_TAG_FMT "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
diff --git a/tools/bpf/bpftool/sign.c b/tools/bpf/bpftool/sign.c
index 88726a6db6d0e..1b5502f48add0 100644
--- a/tools/bpf/bpftool/sign.c
+++ b/tools/bpf/bpftool/sign.c
@@ -130,6 +130,9 @@ __u32 register_session_key(const char *key_der_path)
int bpftool_prog_sign(struct bpf_load_and_run_opts *opts)
{
+ unsigned int signer_flags = CMS_NOCERTS | CMS_BINARY | CMS_NOSMIMECAP |
+ CMS_USE_KEYID | CMS_NOATTR;
+ const EVP_MD *cms_digest = EVP_sha256();
BIO *bd_in = NULL, *bd_out = NULL;
EVP_PKEY *private_key = NULL;
CMS_ContentInfo *cms = NULL;
@@ -167,6 +170,21 @@ int bpftool_prog_sign(struct bpf_load_and_run_opts *opts)
goto cleanup;
}
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L && OPENSSL_VERSION_NUMBER < 0x40000000L
+ if (EVP_PKEY_is_a(private_key, "ML-DSA-44") ||
+ EVP_PKEY_is_a(private_key, "ML-DSA-65") ||
+ EVP_PKEY_is_a(private_key, "ML-DSA-87")) {
+ /*
+ * ML-DSA + CMS_NOATTR is not supported before openssl-4.0, so
+ * fall back to signedAttrs. ML-DSA does its own hashing and
+ * ignores the CMS digest, except when signedAttrs are used,
+ * where only SHA-512 is permitted.
+ */
+ signer_flags &= ~CMS_NOATTR;
+ cms_digest = EVP_sha512();
+ }
+#endif
+
cms = CMS_sign(NULL, NULL, NULL, NULL,
CMS_NOCERTS | CMS_PARTIAL | CMS_BINARY | CMS_DETACHED |
CMS_STREAM);
@@ -175,9 +193,7 @@ int bpftool_prog_sign(struct bpf_load_and_run_opts *opts)
goto cleanup;
}
- if (!CMS_add1_signer(cms, x509, private_key, EVP_sha256(),
- CMS_NOCERTS | CMS_BINARY | CMS_NOSMIMECAP |
- CMS_USE_KEYID | CMS_NOATTR)) {
+ if (!CMS_add1_signer(cms, x509, private_key, cms_digest, signer_flags)) {
err = -EINVAL;
goto cleanup;
}
diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile
index 5f1a3bfc0569f..05b3ee64290fa 100644
--- a/tools/testing/selftests/bpf/Makefile
+++ b/tools/testing/selftests/bpf/Makefile
@@ -663,7 +663,7 @@ $(TRUNNER_BPF_LSKELS): %.lskel.h: %.bpf.o $(BPFTOOL) | $(TRUNNER_OUTPUT)
}) && \
rm -f $$(<:.o=.llinked1.o) $$(<:.o=.llinked2.o) $$(<:.o=.llinked3.o)
-$(TRUNNER_BPF_LSKELS_SIGNED): %.lskel.h: %.bpf.o $(BPFTOOL) | $(TRUNNER_OUTPUT)
+$(TRUNNER_BPF_LSKELS_SIGNED): %.lskel.h: %.bpf.o $(BPFTOOL) $(VERIFICATION_CERT) | $(TRUNNER_OUTPUT)
$(Q)$(if $(PERMISSIVE),if [ ! -f $$< ]; then \
$$(RM) $$@; \
printf ' %-12s %s\n' 'SKIP-SKEL' '$$(notdir $$@)' 1>&2; \
diff --git a/tools/testing/selftests/bpf/config b/tools/testing/selftests/bpf/config
index ea7044f30adc3..2ec15117abb50 100644
--- a/tools/testing/selftests/bpf/config
+++ b/tools/testing/selftests/bpf/config
@@ -51,6 +51,8 @@ CONFIG_IPV6_SEG6_LWTUNNEL=y
CONFIG_IPV6_SIT=y
CONFIG_IPV6_TUNNEL=y
CONFIG_KEYS=y
+CONFIG_CRYPTO_MLDSA=y
+CONFIG_CRYPTO_SHA512=y
CONFIG_LIRC=y
CONFIG_LIVEPATCH=y
CONFIG_LWTUNNEL=y
diff --git a/tools/testing/selftests/bpf/prog_tests/signed_loader.c b/tools/testing/selftests/bpf/prog_tests/signed_loader.c
index 77381d345435c..620a6d22a759c 100644
--- a/tools/testing/selftests/bpf/prog_tests/signed_loader.c
+++ b/tools/testing/selftests/bpf/prog_tests/signed_loader.c
@@ -32,11 +32,20 @@ enum {
BPF_SIG_KEYRING_SECONDARY,
BPF_SIG_KEYRING_PLATFORM,
BPF_SIG_KEYRING_USER,
+ BPF_SIG_KEYRING_BPF,
};
-static int load_loader(const void *insns, __u32 insns_sz, int map_fd,
- const void *sig, __u32 sig_sz, __s32 keyring_id,
- __u32 fd_array_cnt)
+#define BPF_KEYRING_BPF 3
+
+/* verify_sig_setup.sh exits with this when openssl cannot do ML-DSA. */
+#define SETUP_SKIP (-77)
+
+/* FIPS-204 ML-DSA-87 signature size, see include/crypto/mldsa.h. */
+#define MLDSA87_SIGNATURE_SIZE 4627
+
+static int load_loader_log(const void *insns, __u32 insns_sz, int map_fd,
+ const void *sig, __u32 sig_sz, __s32 keyring_id,
+ __u32 fd_array_cnt, char *log_buf, __u32 log_sz)
{
union bpf_attr attr;
int fd;
@@ -48,18 +57,31 @@ static int load_loader(const void *insns, __u32 insns_sz, int map_fd,
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 = fd_array_cnt;
if (sig) {
attr.signature = ptr_to_u64(sig);
attr.signature_size = sig_sz;
attr.keyring_id = keyring_id;
}
- attr.fd_array_cnt = fd_array_cnt;
+ if (log_buf) {
+ attr.log_level = 1;
+ attr.log_buf = ptr_to_u64(log_buf);
+ attr.log_size = log_sz;
+ }
memcpy(attr.prog_name, "__loader.prog", sizeof("__loader.prog"));
fd = syscall(__NR_bpf, BPF_PROG_LOAD, &attr,
offsetofend(union bpf_attr, keyring_id));
return fd < 0 ? -errno : fd;
}
+static int load_loader(const void *insns, __u32 insns_sz, int map_fd,
+ const void *sig, __u32 sig_sz, __s32 keyring_id,
+ __u32 fd_array_cnt)
+{
+ return load_loader_log(insns, insns_sz, map_fd, sig, sig_sz, keyring_id,
+ fd_array_cnt, NULL, 0);
+}
+
static int run_gen_loader(const void *insns, __u32 insns_sz,
const void *data, __u32 data_sz,
const void *excl, __u32 excl_sz,
@@ -156,12 +178,30 @@ static int run_setup(const char *cmd, const char *dir)
}
if (waitpid(pid, &status, 0) < 0)
return -errno;
- return (WIFEXITED(status) &&
- WEXITSTATUS(status) == 0) ? 0 : -EINVAL;
+ if (!WIFEXITED(status))
+ return -EINVAL;
+ return -WEXITSTATUS(status);
}
-static int sign_buf(const char *dir, const void *buf, __u32 len,
- void *sig, __u32 *sig_sz)
+static void genkey_dir_fini(const char *dir)
+{
+ static const char * const files[] = {
+ "signing_key.der", "signing_key.pem", "x509.genkey",
+ };
+ char path[PATH_MAX];
+ size_t i;
+
+ if (!dir)
+ return;
+ for (i = 0; i < ARRAY_SIZE(files); i++) {
+ snprintf(path, sizeof(path), "%s/%s", dir, files[i]);
+ unlink(path);
+ }
+ rmdir(dir);
+}
+
+static int sign_buf_digest(const char *dir, const void *buf, __u32 len,
+ void *sig, __u32 *sig_sz, const char *digest)
{
char data_tmpl[PATH_MAX], key[PATH_MAX];
char sigpath[PATH_MAX + sizeof(".p7s")];
@@ -176,6 +216,7 @@ static int sign_buf(const char *dir, const void *buf, __u32 len,
fd = mkstemp(data_tmpl);
if (fd < 0)
return -errno;
+ snprintf(sigpath, sizeof(sigpath), "%s.p7s", data_tmpl);
if (write(fd, buf, len) != (ssize_t)len) {
close(fd);
ret = -EIO;
@@ -190,7 +231,7 @@ static int sign_buf(const char *dir, const void *buf, __u32 len,
}
if (pid == 0) {
snprintf(key, sizeof(key), "%s/signing_key.pem", dir);
- execlp("./sign-file", "./sign-file", "-d", "sha256",
+ execlp("./sign-file", "./sign-file", "-d", digest,
key, key, data_tmpl, NULL);
exit(1);
}
@@ -200,34 +241,38 @@ static int sign_buf(const char *dir, const void *buf, __u32 len,
goto out;
}
- snprintf(sigpath, sizeof(sigpath), "%s.p7s", data_tmpl);
if (stat(sigpath, &st) < 0) {
ret = -errno;
goto out;
}
if (st.st_size > (off_t)*sig_sz) {
ret = -E2BIG;
- goto out_sig;
+ goto out;
}
fd = open(sigpath, O_RDONLY);
if (fd < 0) {
ret = -errno;
- goto out_sig;
+ goto out;
}
if (read(fd, sig, st.st_size) != st.st_size) {
close(fd);
ret = -EIO;
- goto out_sig;
+ goto out;
}
close(fd);
*sig_sz = st.st_size;
-out_sig:
- unlink(sigpath);
out:
+ unlink(sigpath);
unlink(data_tmpl);
return ret;
}
+static int sign_buf(const char *dir, const void *buf, __u32 len,
+ void *sig, __u32 *sig_sz)
+{
+ return sign_buf_digest(dir, buf, len, sig, sig_sz, "sha256");
+}
+
struct gen_loader_fixture {
struct test_signed_loader *skel;
struct gen_loader_opts gopts;
@@ -457,7 +502,7 @@ static void signed_btf_fd_array_rejected(void)
dir = mkdtemp(dir_tmpl);
if (!ASSERT_OK_PTR(dir, "mkdtemp"))
return;
- if (!ASSERT_OK(run_setup("setup", dir), "verify_sig_setup")) {
+ if (!ASSERT_OK(run_setup("setup-rsa", dir), "verify_sig_setup")) {
rmdir(dir);
return;
}
@@ -529,7 +574,6 @@ 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) {
@@ -538,22 +582,9 @@ static void signature_failure_logs(void)
* failure is reported through the verifier log. A present-but-
* invalid signature is rejected and the log says why.
*/
- 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));
+ fd = load_loader_log(f.gopts.insns, f.gopts.insns_sz, -1, junk,
+ sizeof(junk), KEY_SPEC_SESSION_KEYRING, 0,
+ log_buf, sizeof(log_buf));
ASSERT_LT(fd, 0, "invalid signature rejected at load");
if (fd >= 0)
close(fd);
@@ -571,8 +602,9 @@ static void signature_too_large(void)
if (gen_loader_fixture_init(&f) == 0) {
/*
- * signature_size beyond the kernel's bound (KMALLOC_MAX_CACHE_SIZE)
- * is rejected before the buffer is read.
+ * signature_size beyond the kernel's bound
+ * (BPF_PROG_MAX_SIGNATURE_SIZE) 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, 0);
@@ -626,6 +658,268 @@ static void signature_bad_keyring(void)
gen_loader_fixture_fini(&f);
}
+static bool keyring_unsealed_boot(void)
+{
+ char val = 0;
+ int fd;
+
+ fd = open("/sys/module/bpf/parameters/keyring_unsealed", O_RDONLY);
+ if (fd < 0)
+ return false;
+ if (read(fd, &val, 1) != 1)
+ val = 0;
+ close(fd);
+ return val == 'Y' || val == '1';
+}
+
+static int bpf_keyring_lookup(int *nr_keys)
+{
+ char line[512], type[32], desc[64];
+ int serial = -ENOENT;
+ FILE *f;
+
+ f = fopen("/proc/keys", "r");
+ if (!f)
+ return -errno;
+
+ while (fgets(line, sizeof(line), f)) {
+ unsigned int hex;
+ char *sum;
+
+ if (sscanf(line, "%x %*s %*s %*s %*s %*s %*s %31s %63s",
+ &hex, type, desc) != 3)
+ continue;
+ if (strcmp(type, "keyring") || strcmp(desc, ".bpf:"))
+ continue;
+
+ serial = (int)hex;
+ if (nr_keys) {
+ sum = strstr(line, ".bpf: ");
+ *nr_keys = !sum || !strncmp(sum + 6, "empty", 5) ?
+ 0 : atoi(sum + 6);
+ }
+ break;
+ }
+ fclose(f);
+ return serial;
+}
+
+static long keyctl_ret(int cmd, unsigned long arg2, unsigned long arg3)
+{
+ long ret = syscall(__NR_keyctl, cmd, arg2, arg3);
+
+ return ret < 0 ? -errno : ret;
+}
+
+/*
+ * What the bpf keyring still needs once it got provisioned: KEY_POS_SEARCH
+ * for the in-kernel search during verification, and the user view/read bits
+ * so it stays visible in /proc/keys, rest is dropped so the enrolled is
+ * therefore final.
+ */
+#define BPF_KEYRING_PERM_LOCKED 0x08030000
+/* What bpf_keyring_init() grants at boot. */
+#define BPF_KEYRING_PERM_INITIAL 0x082f0000
+
+static void bpf_keyring_sealed(void)
+{
+ static const __u8 junk[64] = {};
+ struct gen_loader_fixture f;
+ int serial, key, fd;
+
+ if (keyring_unsealed_boot()) {
+ printf("%s:SKIP:the bpf keyring was unsealed at boot\n", __func__);
+ test__skip();
+ return;
+ }
+ serial = bpf_keyring_lookup(NULL);
+ if (serial >= 0) {
+ key = syscall(__NR_add_key, "user", "sealprobe", "x", 1, serial);
+ if (key >= 0)
+ syscall(__NR_keyctl, KEYCTL_UNLINK, key, serial);
+ ASSERT_EQ(key < 0 ? -errno : 0, -EPERM,
+ "nothing links into a sealed keyring");
+ }
+ if (gen_loader_fixture_init(&f) == 0) {
+ fd = load_loader(f.gopts.insns, f.gopts.insns_sz, -1, junk,
+ sizeof(junk), BPF_KEYRING_BPF, 0);
+ ASSERT_EQ(fd, -ENOKEY, "sealed bpf keyring rejected");
+ if (fd >= 0)
+ close(fd);
+ }
+ gen_loader_fixture_fini(&f);
+}
+
+static int try_load(const struct gen_loader_fixture *f, const void *sig,
+ __u32 sig_sz, __s32 keyring_id, char *log_buf, __u32 log_sz)
+{
+ int map_fd, prog_fd;
+
+ map_fd = setup_meta_map(f);
+ if (!ASSERT_OK_FD(map_fd, "meta_map"))
+ return map_fd;
+ prog_fd = load_loader_log(f->gopts.insns, f->gopts.insns_sz, map_fd,
+ sig, sig_sz, keyring_id, 1, log_buf, log_sz);
+ close(map_fd);
+ if (prog_fd >= 0)
+ close(prog_fd);
+ return prog_fd;
+}
+
+/*
+ * This needs bpf.keyring_unsealed=1 on the guest kernel command line, which
+ * vmtest.sh can pass via KERNEL_CMDLINE_EXTRA. There is no way to unseal the
+ * keyring from here, so without it the test skips. It also only works once
+ * per boot, as restricting a keyring cannot be undone.
+ */
+static void bpf_keyring_provisioned(void)
+{
+ char dir_tmpl[] = "/tmp/bpfkeyringXXXXXX";
+ char bad_tmpl[] = "/tmp/bpfkeyringbadXXXXXX";
+ __u8 *sig = NULL, *bad = NULL, *buf = NULL;
+ int serial, err;
+ int nr_keys = 0, der_fd = -1;
+ struct gen_loader_fixture f;
+ __u32 sig_sz = 8192, bad_sz;
+ bool have_fixture = false;
+ char *dir, *bad_dir = NULL;
+ char log_buf[1024] = {};
+ char path[PATH_MAX];
+ __u8 der[4096];
+ ssize_t der_sz;
+
+ serial = bpf_keyring_lookup(&nr_keys);
+ if (serial < 0) {
+ printf("%s:SKIP:no bpf keyring (needs CONFIG_KEYS)\n", __func__);
+ test__skip();
+ return;
+ }
+ if (nr_keys != 0) {
+ printf("%s:SKIP:the bpf keyring has already been provisioned\n",
+ __func__);
+ test__skip();
+ return;
+ }
+
+ dir = mkdtemp(dir_tmpl);
+ if (!ASSERT_OK_PTR(dir, "mkdtemp"))
+ return;
+ if (!ASSERT_OK(run_setup("genkey", dir), "verify_sig_setup genkey"))
+ goto rmdir;
+
+ snprintf(path, sizeof(path), "%s/signing_key.der", dir);
+ der_fd = open(path, O_RDONLY);
+ if (!ASSERT_OK_FD(der_fd, "open signing_key.der"))
+ goto rmdir;
+ der_sz = read(der_fd, der, sizeof(der));
+ close(der_fd);
+ if (!ASSERT_GT(der_sz, 0, "read signing_key.der"))
+ goto rmdir;
+
+ err = syscall(__NR_add_key, "asymmetric", "", der, (size_t)der_sz,
+ serial);
+ if (err < 0 && errno == EPERM) {
+ printf("%s:SKIP:the bpf keyring is sealed, need bpf.keyring_unsealed=1\n",
+ __func__);
+ test__skip();
+ goto rmdir;
+ }
+ if (!ASSERT_GE(err, 0, "add the signing key to the bpf keyring"))
+ goto rmdir;
+
+ sig = malloc(sig_sz);
+ if (!ASSERT_OK_PTR(sig, "sig buf"))
+ goto out;
+ have_fixture = true;
+ if (gen_loader_fixture_init(&f) != 0)
+ goto out;
+
+ 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;
+
+ ASSERT_EQ(try_load(&f, sig, sig_sz, BPF_KEYRING_BPF, NULL, 0), -ENOKEY,
+ "unrestricted keyring still not consulted");
+
+ ASSERT_EQ(try_load(&f, sig, sig_sz, KEY_SPEC_SESSION_KEYRING, NULL, 0),
+ -EPERM, "caller-supplied keyring refused before provisioning");
+
+ if (!ASSERT_OK(syscall(__NR_keyctl, KEYCTL_RESTRICT_KEYRING, serial,
+ NULL, NULL), "restrict bpf keyring"))
+ goto out;
+
+ if (!ASSERT_OK_FD(try_load(&f, sig, sig_sz, BPF_KEYRING_BPF, NULL, 0),
+ "load signed by a key in the .bpf keyring"))
+ goto out;
+
+ bad_dir = mkdtemp(bad_tmpl);
+ if (!ASSERT_OK_PTR(bad_dir, "mkdtemp unenrolled"))
+ goto out;
+ if (!ASSERT_OK(run_setup("genkey", bad_dir), "verify_sig_setup genkey unenrolled"))
+ goto out;
+ bad_sz = 8192;
+ bad = malloc(bad_sz);
+ if (!ASSERT_OK_PTR(bad, "bad sig buf"))
+ goto out;
+ if (!ASSERT_OK(sign_buf(bad_dir, buf, f.gopts.insns_sz + f.data_sz, bad,
+ &bad_sz), "sign with an unenrolled key"))
+ goto out;
+
+ ASSERT_EQ(try_load(&f, bad, bad_sz, BPF_KEYRING_BPF, log_buf,
+ sizeof(log_buf)), -ENOKEY,
+ "key outside the bpf keyring refused");
+ ASSERT_HAS_SUBSTR(log_buf, "signature verification failed",
+ "the bpf keyring was consulted");
+
+ f.blob[0] ^= 0xff;
+ err = try_load(&f, sig, sig_sz, BPF_KEYRING_BPF, NULL, 0);
+ f.blob[0] ^= 0xff;
+ ASSERT_EQ(err, -EKEYREJECTED, "tampered metadata refused");
+
+ ASSERT_EQ(try_load(&f, sig, sig_sz, KEY_SPEC_SESSION_KEYRING, NULL, 0),
+ -EPERM, "caller-supplied keyring refused once .bpf is in use");
+
+ err = keyctl_ret(KEYCTL_UNLINK, KEY_SPEC_SESSION_KEYRING, serial);
+ ASSERT_EQ(err, -ENOENT, "keyring writable while the user bits are there");
+
+ err = keyctl_ret(KEYCTL_SETPERM, serial, BPF_KEYRING_PERM_LOCKED);
+ if (!ASSERT_OK(err, "drop the user bits on the bpf keyring"))
+ goto out;
+
+ ASSERT_OK_FD(try_load(&f, sig, sig_sz, BPF_KEYRING_BPF, NULL, 0),
+ "load still verified against the locked keyring");
+
+ err = keyctl_ret(KEYCTL_UNLINK, KEY_SPEC_SESSION_KEYRING, serial);
+ ASSERT_EQ(err, -EACCES, "unlink refused");
+ err = keyctl_ret(KEYCTL_CLEAR, serial, 0);
+ ASSERT_EQ(err, -EACCES, "clear refused");
+ err = keyctl_ret(KEYCTL_REVOKE, serial, 0);
+ ASSERT_EQ(err, -EACCES, "revoke refused");
+ err = keyctl_ret(KEYCTL_INVALIDATE, serial, 0);
+ ASSERT_EQ(err, -EACCES, "invalidate refused");
+ err = keyctl_ret(KEYCTL_SET_TIMEOUT, serial, 1);
+ ASSERT_EQ(err, -EACCES, "timeout refused");
+ err = keyctl_ret(KEYCTL_SETPERM, serial, BPF_KEYRING_PERM_INITIAL);
+ ASSERT_EQ(err, -EACCES, "the bits cannot be granted back");
+
+ ASSERT_EQ(bpf_keyring_lookup(&nr_keys), serial, "keyring still there");
+ ASSERT_EQ(nr_keys, 1, "the enrolled key survived");
+out:
+ if (have_fixture)
+ gen_loader_fixture_fini(&f);
+ genkey_dir_fini(bad_dir);
+ free(buf);
+ free(bad);
+ free(sig);
+rmdir:
+ genkey_dir_fini(dir);
+}
+
/*
* A signed loader must ignore ctx-supplied map dimensions: the host cannot
* resize a signed program's maps via the loader ctx. Drive a one-map program
@@ -831,7 +1125,7 @@ static void signature_authenticates_insns(void)
dir = mkdtemp(dir_tmpl);
if (!ASSERT_OK_PTR(dir, "mkdtemp"))
return;
- if (!ASSERT_OK(run_setup("setup", dir), "verify_sig_setup")) {
+ if (!ASSERT_OK(run_setup("setup-rsa", dir), "verify_sig_setup")) {
rmdir(dir);
return;
}
@@ -931,7 +1225,7 @@ static void signature_authenticates_metadata(void)
dir = mkdtemp(dir_tmpl);
if (!ASSERT_OK_PTR(dir, "mkdtemp"))
return;
- if (!ASSERT_OK(run_setup("setup", dir), "verify_sig_setup")) {
+ if (!ASSERT_OK(run_setup("setup-rsa", dir), "verify_sig_setup")) {
rmdir(dir);
return;
}
@@ -1267,7 +1561,7 @@ static void lsm_signature_verdict(void)
dir = mkdtemp(dir_tmpl);
if (!ASSERT_OK_PTR(dir, "mkdtemp"))
goto out;
- if (!ASSERT_OK(run_setup("setup", dir), "verify_sig_setup")) {
+ if (!ASSERT_OK(run_setup("setup-rsa", dir), "verify_sig_setup")) {
rmdir(dir);
dir = NULL;
goto out;
@@ -1450,7 +1744,7 @@ static void loadtime_verify(struct bpf_object *obj, int expect_maps)
dir = mkdtemp(dir_tmpl);
if (!ASSERT_OK_PTR(dir, "mkdtemp"))
return;
- if (!ASSERT_OK(run_setup("setup", dir), "verify_sig_setup")) {
+ if (!ASSERT_OK(run_setup("setup-rsa", dir), "verify_sig_setup")) {
rmdir(dir);
return;
}
@@ -1524,6 +1818,89 @@ static void loadtime_with_map(void)
test_signed_loader_map__destroy(skel);
}
+/*
+ * End-to-end signed load with a post-quantum key. ML-DSA (FIPS-204) is wired
+ * through the X.509 and PKCS#7 parsers, and BPF reaches them via
+ * verify_pkcs7_signature() without knowing the algorithm, so an ML-DSA key in
+ * the keyring should verify an ML-DSA signed program with no BPF-side work.
+ */
+static void mldsa_signed_load(void)
+{
+ char dir_tmpl[] = "/tmp/bpfmldsaXXXXXX";
+ int map_fd = -1, prog_fd = -1, err;
+ __u8 *sig = NULL, *buf = NULL;
+ struct gen_loader_fixture f;
+ bool have_fixture = false;
+ __u32 sig_sz = 16384;
+ char *dir;
+
+ syscall(__NR_request_key, "keyring", "_uid.0", NULL,
+ KEY_SPEC_SESSION_KEYRING);
+ dir = mkdtemp(dir_tmpl);
+ if (!ASSERT_OK_PTR(dir, "mkdtemp"))
+ return;
+
+ err = run_setup("setup-mldsa", dir);
+ if (err == SETUP_SKIP) {
+ printf("%s:SKIP:openssl has no ML-DSA support (needs 3.5+)\n",
+ __func__);
+ test__skip();
+ genkey_dir_fini(dir);
+ return;
+ }
+ if (!ASSERT_OK(err, "verify_sig_setup setup-mldsa")) {
+ genkey_dir_fini(dir);
+ return;
+ }
+
+ sig = malloc(sig_sz);
+ if (!ASSERT_OK_PTR(sig, "sig buf"))
+ goto out;
+ have_fixture = true;
+ if (gen_loader_fixture_init(&f) != 0)
+ goto out;
+
+ 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);
+
+ /*
+ * ML-DSA hashes the message itself, but openssl before 4.0 cannot
+ * produce a CMS message without signedAttrs for it, and with those in
+ * play only SHA-512 is permitted for the messageDigest attribute.
+ */
+ if (!ASSERT_OK(sign_buf_digest(dir, buf, f.gopts.insns_sz + f.data_sz,
+ sig, &sig_sz, "sha512"),
+ "sign insns||metadata with ML-DSA"))
+ goto out;
+
+ /*
+ * Guard against the setup silently handing back some other key type:
+ * an RSA or ECDSA signature is a few hundred bytes, where an ML-DSA-87
+ * one cannot be smaller than the raw signature it carries.
+ */
+ ASSERT_GT(sig_sz, MLDSA87_SIGNATURE_SIZE, "ML-DSA-87 signature size");
+
+ map_fd = setup_meta_map(&f);
+ if (!ASSERT_OK_FD(map_fd, "meta_map"))
+ goto out;
+ prog_fd = load_loader(f.gopts.insns, f.gopts.insns_sz, map_fd, sig,
+ sig_sz, KEY_SPEC_SESSION_KEYRING, 1);
+ ASSERT_OK_FD(prog_fd, "ML-DSA signed loader load");
+out:
+ if (prog_fd >= 0)
+ close(prog_fd);
+ if (map_fd >= 0)
+ close(map_fd);
+ if (have_fixture)
+ gen_loader_fixture_fini(&f);
+ free(buf);
+ free(sig);
+ run_setup("cleanup", dir);
+}
+
/*
* 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
@@ -1548,7 +1925,7 @@ static void signed_no_fd_array(void)
dir = mkdtemp(dir_tmpl);
if (!ASSERT_OK_PTR(dir, "mkdtemp"))
return;
- if (!ASSERT_OK(run_setup("setup", dir), "verify_sig_setup")) {
+ if (!ASSERT_OK(run_setup("setup-rsa", dir), "verify_sig_setup")) {
rmdir(dir);
return;
}
@@ -1619,7 +1996,7 @@ static void signed_map_by_fd_rejected(void)
dir = mkdtemp(dir_tmpl);
if (!ASSERT_OK_PTR(dir, "mkdtemp"))
goto out_map;
- if (!ASSERT_OK(run_setup("setup", dir), "verify_sig_setup")) {
+ if (!ASSERT_OK(run_setup("setup-rsa", dir), "verify_sig_setup")) {
rmdir(dir);
goto out_map;
}
@@ -1681,7 +2058,7 @@ static void signed_sparse_fd_array_rejected(void)
dir = mkdtemp(dir_tmpl);
if (!ASSERT_OK_PTR(dir, "mkdtemp"))
goto out_map;
- if (!ASSERT_OK(run_setup("setup", dir), "verify_sig_setup")) {
+ if (!ASSERT_OK(run_setup("setup-rsa", dir), "verify_sig_setup")) {
rmdir(dir);
goto out_map;
}
@@ -1735,7 +2112,7 @@ static void signed_module_kfunc_rejected(void)
dir = mkdtemp(dir_tmpl);
if (!ASSERT_OK_PTR(dir, "mkdtemp"))
return;
- if (!ASSERT_OK(run_setup("setup", dir), "verify_sig_setup")) {
+ if (!ASSERT_OK(run_setup("setup-rsa", dir), "verify_sig_setup")) {
rmdir(dir);
return;
}
@@ -1779,6 +2156,11 @@ static void signed_module_kfunc_rejected(void)
void test_signed_loader(void)
{
+ if (keyring_unsealed_boot()) {
+ if (test__start_subtest("bpf_keyring_provisioned"))
+ bpf_keyring_provisioned();
+ return;
+ }
if (test__start_subtest("loadtime_no_map"))
loadtime_no_map();
if (test__start_subtest("loadtime_with_map"))
@@ -1805,6 +2187,10 @@ void test_signed_loader(void)
signature_zero_size();
if (test__start_subtest("signature_bad_keyring"))
signature_bad_keyring();
+ if (test__start_subtest("bpf_keyring_sealed"))
+ bpf_keyring_sealed();
+ if (test__start_subtest("mldsa_signed_load"))
+ mldsa_signed_load();
if (test__start_subtest("metadata_ctx_max_entries_ignored"))
metadata_ctx_max_entries_ignored();
if (test__start_subtest("metadata_ctx_initial_value_ignored"))
diff --git a/tools/testing/selftests/bpf/prog_tests/verify_pkcs7_sig.c b/tools/testing/selftests/bpf/prog_tests/verify_pkcs7_sig.c
index f327feb8e38c3..12b146d205d75 100644
--- a/tools/testing/selftests/bpf/prog_tests/verify_pkcs7_sig.c
+++ b/tools/testing/selftests/bpf/prog_tests/verify_pkcs7_sig.c
@@ -257,7 +257,7 @@ static void test_verify_pkcs7_sig_from_map(void)
if (!ASSERT_OK_PTR(tmp_dir, "mkdtemp"))
return;
- ret = _run_setup_process(tmp_dir, "setup");
+ ret = _run_setup_process(tmp_dir, "setup-rsa");
if (!ASSERT_OK(ret, "_run_setup_process"))
goto close_prog;
@@ -458,7 +458,7 @@ static void test_pkcs7_sig_fsverity(void)
snprintf(data_path, PATH_MAX, "%s/data-file", tmp_dir);
snprintf(sig_path, PATH_MAX, "%s/sig-file", tmp_dir);
- ret = _run_setup_process(tmp_dir, "setup");
+ ret = _run_setup_process(tmp_dir, "setup-rsa");
if (!ASSERT_OK(ret, "_run_setup_process"))
goto out;
diff --git a/tools/testing/selftests/bpf/verify_sig_setup.sh b/tools/testing/selftests/bpf/verify_sig_setup.sh
index 09179fb551f09..2737c1a2bcfd6 100755
--- a/tools/testing/selftests/bpf/verify_sig_setup.sh
+++ b/tools/testing/selftests/bpf/verify_sig_setup.sh
@@ -28,7 +28,7 @@ authorityKeyIdentifier=keyid
usage()
{
- echo "Usage: $0 <setup|cleanup <existing_tmp_dir>"
+ echo "Usage: $0 <setup-rsa|setup-mldsa|cleanup <existing_tmp_dir>"
exit 1
}
@@ -47,7 +47,7 @@ genkey()
${tmp_dir}/signing_key.der -outform der
}
-setup()
+setup_rsa()
{
local tmp_dir="$1"
@@ -57,6 +57,57 @@ setup()
keyctl link $key_id $keyring_id
}
+mldsa_supported()
+{
+ local tmp_dir="$1"
+
+ genkey_mldsa "${tmp_dir}" || return 1
+ : > ${tmp_dir}/probe
+ # Same digest as the caller signs with, see sign_buf_digest().
+ ./sign-file -d sha512 ${tmp_dir}/signing_key.pem \
+ ${tmp_dir}/signing_key.pem ${tmp_dir}/probe || return 1
+ rm -f ${tmp_dir}/probe ${tmp_dir}/probe.p7s
+}
+
+genkey_mldsa()
+{
+ local tmp_dir="$1"
+
+ echo "${x509_genkey_content}" > ${tmp_dir}/x509.genkey
+
+ # No -<digest> here: ML-DSA hashes the message itself, and openssl
+ # rejects an explicit digest for it.
+ openssl req -new -nodes -utf8 -days 36500 \
+ -batch -x509 -newkey ML-DSA-87 \
+ -config ${tmp_dir}/x509.genkey \
+ -outform PEM -out ${tmp_dir}/signing_key.pem \
+ -keyout ${tmp_dir}/signing_key.pem 2>&1
+
+ openssl x509 -in ${tmp_dir}/signing_key.pem -out \
+ ${tmp_dir}/signing_key.der -outform der
+}
+
+mldsa_skip()
+{
+ local tmp_dir="$1"
+
+ rm -f ${tmp_dir}/x509.genkey ${tmp_dir}/signing_key.pem \
+ ${tmp_dir}/signing_key.der ${tmp_dir}/probe \
+ ${tmp_dir}/probe.p7s
+ exit 77
+}
+
+setup_mldsa()
+{
+ local tmp_dir="$1"
+
+ mldsa_supported "${tmp_dir}" || mldsa_skip "${tmp_dir}"
+ key_id=$(cat ${tmp_dir}/signing_key.der |
+ keyctl padd asymmetric ebpf_testing_key @s)
+ keyring_id=$(keyctl newring ebpf_testing_keyring @s)
+ keyctl link $key_id $keyring_id
+}
+
cleanup() {
local tmp_dir="$1"
@@ -91,7 +142,7 @@ catch()
local exit_code="$1"
local log_file="$2"
- if [[ "${exit_code}" -ne 0 ]]; then
+ if [[ "${exit_code}" -ne 0 && "${exit_code}" -ne 77 ]]; then
cat "${log_file}" >&3
fi
@@ -108,8 +159,10 @@ main()
[[ ! -d "${tmp_dir}" ]] && echo "Directory ${tmp_dir} doesn't exist" && exit 1
- if [[ "${action}" == "setup" ]]; then
- setup "${tmp_dir}"
+ if [[ "${action}" == "setup-rsa" ]]; then
+ setup_rsa "${tmp_dir}"
+ elif [[ "${action}" == "setup-mldsa" ]]; then
+ setup_mldsa "${tmp_dir}"
elif [[ "${action}" == "genkey" ]]; then
genkey "${tmp_dir}"
elif [[ "${action}" == "cleanup" ]]; then
diff --git a/tools/testing/selftests/bpf/vmtest.sh b/tools/testing/selftests/bpf/vmtest.sh
index 6a3d026d76bd6..e7e0b419a0b87 100755
--- a/tools/testing/selftests/bpf/vmtest.sh
+++ b/tools/testing/selftests/bpf/vmtest.sh
@@ -107,6 +107,14 @@ Options:
-s) Instead of powering off the VM, start an interactive
shell. If <command> is specified, the shell runs after
the command finishes executing
+
+Environment variables:
+
+ KERNEL_CMDLINE_EXTRA
+ Extra arguments to append to the guest kernel command
+ line, for tests that need a boot-time setting. e.g:
+
+ KERNEL_CMDLINE_EXTRA="bpf.keyring_unsealed=1" $0 -- ./test_progs -t signed_loader
EOF
}
@@ -286,6 +294,12 @@ EOF
QEMU_FLAGS=("${HOST_FLAGS[@]}")
fi
+ local kernel_cmdline="root=/dev/vda rw console=${QEMU_CONSOLE}"
+
+ if [[ -n "${KERNEL_CMDLINE_EXTRA:-}" ]]; then
+ kernel_cmdline+=" ${KERNEL_CMDLINE_EXTRA}"
+ fi
+
${QEMU_BINARY} \
-nodefaults \
-display none \
@@ -294,7 +308,7 @@ EOF
-m 4G \
-drive file="${rootfs_img}",format=raw,index=1,media=disk,if=virtio,cache=none \
-kernel "${kernel_bzimage}" \
- -append "root=/dev/vda rw console=${QEMU_CONSOLE}"
+ -append "${kernel_cmdline}"
}
copy_logs()
|
| 3/2 | 2026/08/25 14:55 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 22749 output: 22 thoughts: 192 Thoughts: **Analyzing the BPF Keyring Implementation** Okay, so I'm looking at this patch series, and it's introducing a new `bpf.keyring_unsealed=1` boot parameter, which is intriguing. And, it seems to be setting up a new `.bpf` keyring specifically for verifying BPF program signatures. Smart move, that kind of dedicated infrastructure keeps things clean and manageable. The patch touches `kernel/bpf/keys.c` and `kernel/bpf/verifier.c`, so those are the areas I need to focus on. Let me break down the core changes: a few new functions are being introduced, `bpf_keyring_enforced()`, `bpf_lookup_keyring()`, and `bpf_keyring_init()`, which give me the initial sense that a keyring is indeed being created and managed. Interesting as well is that the already defined `bpf_prog_verify_signature()` function is being modified to handle `VERIFY_USE_BPF_KEYRING` which makes perfect sense in terms of leveraging existing code for new functionality. Time to dig into the details. I'll fire up `grepper` and see what the code really looks like and examine the contexts in which `bpf_prog_verify_signature` and `bpf_keyring_init` are being called, and how those new functions fit into the existing BPF verification process. Need to understand the logic flow here. |
| 4/2 | 2026/08/25 14:55 | tool | grepper |
0mArgs:{
"Expression": "bpf_prog_verify_signature"
}Results: {
"Output": "kernel/bpf/verifier.c=20983=static enum bpf_sig_keyring bpf_classify_keyring(s32 keyring_id)\n--\nkernel/bpf/verifier.c-21007- */\nkernel/bpf/verifier.c:21008:static int bpf_prog_verify_signature(struct bpf_verifier_env *env,\nkernel/bpf/verifier.c-21009-\t\t\t\t union bpf_attr *attr, bool is_kernel)\n--\nkernel/bpf/verifier.c=21136=int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,\n--\nkernel/bpf/verifier.c-21187-\tif (env-\u003esignature) {\nkernel/bpf/verifier.c:21188:\t\tret = bpf_prog_verify_signature(env, attr, uattr.is_kernel);\nkernel/bpf/verifier.c-21189-\t\tif (ret)\n--\ntools/testing/selftests/bpf/prog_tests/signed_loader.c=639=static void signature_bad_keyring(void)\n--\ntools/testing/selftests/bpf/prog_tests/signed_loader.c-647-\t\t * A present signature with a keyring_id that resolves to no key is\ntools/testing/selftests/bpf/prog_tests/signed_loader.c:648:\t\t * rejected up front: bpf_prog_verify_signature() fails the keyring\ntools/testing/selftests/bpf/prog_tests/signed_loader.c-649-\t\t * lookup (-EINVAL) before it ever looks at the signature bytes. A\n"
} |
| 5/2 | 2026/08/25 14:55 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 23719 output: 21 thoughts: 190 Thoughts: **Analyzing the Dependencies of This BPF Patch** Okay, so this patch isn't introducing a *new* config option, which is good. Instead, it's leveraging existing ones: `CONFIG_KEYS` and `CONFIG_BPF_SYSCALL`. I need to figure out the interplay here. Let's see... looking at `kernel/bpf/Makefile`... Aha! The dependency is explicit: ```makefile ifeq ($(CONFIG_KEYS),y) obj-$(CONFIG_BPF_SYSCALL) += keys.o endif ``` So, `CONFIG_KEYS` *must* be enabled for `keys.o` to even be considered for inclusion. And then, only if `CONFIG_BPF_SYSCALL` is also enabled, will the object file be built. Got it. So both are required. Now, let me check the selftests config, just to see what else might be brought along for the ride. Looking at `tools/testing/selftests/bpf/config`: ``` CONFIG_KEYS=y CONFIG_CRYPTO_MLDSA=y CONFIG_CRYPTO_SHA512=y ``` Alright, `KEYS` is explicitly set to `y` there. And, interestingly, the selftests also pull in `CRYPTO_MLDSA` and `CRYPTO_SHA512`. Those crypto options *might* be relevant, or they may simply be inherited dependencies from somewhere else in the stack. I should keep an eye on those. Now I need to understand what `keys.c` does... Let's see what we can find in the code itself, `kernel/bpf/keys.c`. |
| 6/2 | 2026/08/25 14:55 | tool | grepper |
0mArgs:{
"Expression": "bpf_keyring_init"
}Results: {
"Output": "kernel/bpf/keys.c=26=struct bpf_key *bpf_lookup_keyring(void)\n--\nkernel/bpf/keys.c-44-\nkernel/bpf/keys.c:45:static int __init bpf_keyring_init(void)\nkernel/bpf/keys.c-46-{\n--\nkernel/bpf/keys.c-71-}\nkernel/bpf/keys.c:72:late_initcall(bpf_keyring_init);\n--\ntools/testing/selftests/bpf/prog_tests/signed_loader.c=707=static long keyctl_ret(int cmd, unsigned long arg2, unsigned long arg3)\n--\ntools/testing/selftests/bpf/prog_tests/signed_loader.c-720-#define BPF_KEYRING_PERM_LOCKED\t\t0x08030000\ntools/testing/selftests/bpf/prog_tests/signed_loader.c:721:/* What bpf_keyring_init() grants at boot. */\ntools/testing/selftests/bpf/prog_tests/signed_loader.c-722-#define BPF_KEYRING_PERM_INITIAL\t0x082f0000\n"
} |
| 7/2 | 2026/08/25 14:55 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 24624 output: 39 thoughts: 88 Thoughts: Here's my summary, as I understand it: **Examining BPF Keyring Functionality and Verification Changes** Okay, so I see some new BPF keyring functions have been added. Specifically, `bpf_keyring_enforced`, `bpf_lookup_keyring`, and `bpf_keyring_init`. My immediate thought is, "Interesting... what's the rationale behind these?" This likely indicates an effort to extend BPF's capabilities within the kernel, specifically around key management. The naming convention is pretty straightforward, I can infer that `enforced` might be related to access controls, `lookup` for retrieving keys, and `init` for initialization of the keyring. Now, the mention of `bpf_prog_verify_signature` being modified in `kernel/bpf/verifier.c` really grabs my attention. That suggests that these new keyring functions have necessitated changes to the BPF program verification process. My next step is clearly to dive into `bpf_prog_verify_signature` in `kernel/bpf/verifier.c`. I need to understand *how* the verification process has been altered to accommodate the new keyring features. I'm keen to see if this affects the security model in any notable ways. Understanding the signature verification is crucial in assessing the integrity and safety of BPF programs, especially now that keyrings are involved. I'll focus on how the verification process handles these additions, to ensure that the security properties remain. |
| 8/2 | 2026/08/25 14:55 | tool | read-file |
0mArgs:{
"File": "kernel/bpf/verifier.c",
"FirstLine": 21000,
"LineCount": 100
}Results: {
"Contents": "21000:\t * Verify the PKCS#7 signature of a loaded program. Called from bpf_check()\n21001:\t * once the program's metadata maps have been resolved into used_maps, so\n21002:\t * the exact maps folded into the signature are the ones the program binds.\n21003:\t *\n21004:\t * The signature covers the instructions followed by the frozen contents of\n21005:\t * each map, in @maps order: insns || map_0 || map_1 || [...]. On success the\n21006:\t * verdict and keyring info are recorded on prog-\u003eaux.\n21007:\t */\n21008:\tstatic int bpf_prog_verify_signature(struct bpf_verifier_env *env,\n21009:\t\t\t\t\t union bpf_attr *attr, bool is_kernel)\n21010:\t{\n21011:\t\tbpfptr_t usig = make_bpfptr(attr-\u003esignature, is_kernel);\n21012:\t\tstruct bpf_dynptr_kern sig_ptr, data_ptr;\n21013:\t\tstruct bpf_prog *prog = env-\u003eprog;\n21014:\t\tstruct bpf_map **maps = env-\u003eused_maps;\n21015:\t\tstruct bpf_key *key = NULL;\n21016:\t\tvoid *sig, *data = NULL;\n21017:\t\tu32 map_cnt = env-\u003eused_map_cnt;\n21018:\t\tu32 i, off, insns_sz;\n21019:\t\tu64 data_sz;\n21020:\t\tint err = 0;\n21021:\t\n21022:\t\tif (!attr-\u003esignature_size ||\n21023:\t\t attr-\u003esignature_size \u003e BPF_PROG_MAX_SIGNATURE_SIZE)\n21024:\t\t\treturn -EINVAL;\n21025:\t\n21026:\t\tif (!system_keyring_id_check(attr-\u003ekeyring_id)) {\n21027:\t\t\tkey = bpf_lookup_system_key(attr-\u003ekeyring_id);\n21028:\t\t} else {\n21029:\t\t\tif (attr-\u003ekeyring_id != VERIFY_USE_BPF_KEYRING) {\n21030:\t\t\t\tif (bpf_keyring_enforced()) {\n21031:\t\t\t\t\tverbose(env, \"caller-supplied keyring refused, use bpf keyring\\n\");\n21032:\t\t\t\t\treturn -EPERM;\n21033:\t\t\t\t}\n21034:\t\t\t\tkey = bpf_lookup_user_key(attr-\u003ekeyring_id, 0);\n21035:\t\t\t} else {\n21036:\t\t\t\tkey = bpf_lookup_keyring();\n21037:\t\t\t}\n21038:\t\t}\n21039:\t\tif (!key) {\n21040:\t\t\tif (attr-\u003ekeyring_id == VERIFY_USE_BPF_KEYRING) {\n21041:\t\t\t\tverbose(env, \"the bpf keyring is empty or has not been restricted\\n\");\n21042:\t\t\t\treturn -ENOKEY;\n21043:\t\t\t} else {\n21044:\t\t\t\tverbose(env, \"cannot resolve signing keyring with keyring_id %d\\n\",\n21045:\t\t\t\t\tattr-\u003ekeyring_id);\n21046:\t\t\t\treturn -EINVAL;\n21047:\t\t\t}\n21048:\t\t}\n21049:\t\n21050:\t\tsig = kvmemdup_bpfptr(usig, attr-\u003esignature_size);\n21051:\t\tif (IS_ERR(sig)) {\n21052:\t\t\tbpf_key_put(key);\n21053:\t\t\treturn PTR_ERR(sig);\n21054:\t\t}\n21055:\t\n21056:\t\tinsns_sz = prog-\u003elen * sizeof(struct bpf_insn);\n21057:\t\tdata_sz = insns_sz;\n21058:\t\tfor (i = 0; i \u003c map_cnt; i++) {\n21059:\t\t\tstruct bpf_map *map = maps[i];\n21060:\t\n21061:\t\t\tif (map-\u003emap_type != BPF_MAP_TYPE_ARRAY ||\n21062:\t\t\t !map-\u003eops-\u003emap_direct_value_addr) {\n21063:\t\t\t\tverbose(env, \"signed program metadata map '%s' must be an array\\n\",\n21064:\t\t\t\t\tmap-\u003ename);\n21065:\t\t\t\terr = -EINVAL;\n21066:\t\t\t\tgoto out;\n21067:\t\t\t}\n21068:\t\t\tif (!READ_ONCE(map-\u003efrozen)) {\n21069:\t\t\t\tverbose(env, \"signed program metadata map '%s' must be frozen\\n\",\n21070:\t\t\t\t\tmap-\u003ename);\n21071:\t\t\t\terr = -EPERM;\n21072:\t\t\t\tgoto out;\n21073:\t\t\t}\n21074:\t\t\tif (bpf_map_write_active(map)) {\n21075:\t\t\t\tverbose(env, \"signed program metadata map '%s' has active writers\\n\",\n21076:\t\t\t\t\tmap-\u003ename);\n21077:\t\t\t\terr = -EBUSY;\n21078:\t\t\t\tgoto out;\n21079:\t\t\t}\n21080:\t\t\tif (!map-\u003eexcl_prog_sha) {\n21081:\t\t\t\tverbose(env, \"signed program metadata map '%s' must be exclusive\\n\",\n21082:\t\t\t\t\tmap-\u003ename);\n21083:\t\t\t\terr = -EPERM;\n21084:\t\t\t\tgoto out;\n21085:\t\t\t}\n21086:\t\t\tdata_sz += map-\u003evalue_size;\n21087:\t\t}\n21088:\t\tif (bpf_dynptr_check_size(data_sz)) {\n21089:\t\t\tverbose(env, \"signed payload too large: %llu bytes\\n\", data_sz);\n21090:\t\t\terr = -E2BIG;\n21091:\t\t\tgoto out;\n21092:\t\t}\n21093:\t\tdata = kvmalloc(data_sz, GFP_KERNEL_ACCOUNT | __GFP_ZERO);\n21094:\t\tif (!data) {\n21095:\t\t\terr = -ENOMEM;\n21096:\t\t\tgoto out;\n21097:\t\t}\n21098:\t\tmemcpy(data, prog-\u003einsnsi, insns_sz);\n21099:\t\toff = insns_sz;\n"
} |
| 9/2 | 2026/08/25 14:55 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 26812 output: 38 thoughts: 153 Thoughts: **My Assessment of the BPF Keyring Patch** Okay, so this patch is introducing something pretty significant: a dedicated keyring specifically for verifying the signatures of BPF programs. That's a solid move, it isolates that concern. I see the new functions are `bpf_lookup_keyring`, `bpf_keyring_enforced`, and `bpf_keyring_init`. Those names are straightforward; exactly what you'd expect. The core of it all seems to be in `bpf_prog_verify_signature`, which is the function that actually does the validation. Now, the question is *where* this is being called from. Aha, it's reachable through `bpf_check`, specifically during the BPF program load process when a signature is present. That makes sense. Let's see... `bpf_prog_verify_signature` is called if `env->signature` is set. Right, right... and `env->signature` gets set if `attr->signature` is provided, which is how the program loader passes the signature. Okay, that's clean. Let me confirm this connection by taking a quick look at `bpf_check` in `kernel/bpf/verifier.c`. Just want to be sure I'm not missing anything. |
| 10/2 | 2026/08/25 14:56 | tool | read-file |
0mArgs:{
"File": "kernel/bpf/verifier.c",
"FirstLine": 21180,
"LineCount": 20
}Results: {
"Contents": "21180:\t\t\t\tgoto err_prep;\n21181:\t\t}\n21182:\t\n21183:\t\tret = process_fd_array(env, attr, uattr);\n21184:\t\tif (ret)\n21185:\t\t\tgoto err_prep;\n21186:\t\n21187:\t\tif (env-\u003esignature) {\n21188:\t\t\tret = bpf_prog_verify_signature(env, attr, uattr.is_kernel);\n21189:\t\t\tif (ret)\n21190:\t\t\t\tgoto err_prep;\n21191:\t\t}\n21192:\t\n21193:\t\tret = security_bpf_prog_load(env-\u003eprog, attr, env-\u003eprog-\u003eaux-\u003etoken,\n21194:\t\t\t\t\t uattr.is_kernel);\n21195:\t\tif (ret)\n21196:\t\t\tgoto err_prep;\n21197:\t\n21198:\t\tbpf_get_btf_vmlinux();\n21199:\t\n"
} |
| 11/2 | 2026/08/25 14:56 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 27574 output: 34 thoughts: 465 Thoughts: **My Analysis of BPF Signature Verification** Okay, so I'm thinking, `bpf_prog_verify_signature` seems like a solid symbol to start with, along with `bpf_lookup_keyring` and `bpf_keyring_enforced`. I need to remember that `bpf_keyring_init` is called during boot as it is an initcall. This requires `KEYS` and `BPF_SYSCALL` configurations. Wait, hang on, I see `CONFIG_CRYPTO_MLDSA` and `CONFIG_CRYPTO_SHA512` are in the selftests config, but those aren't *strictly* needed for the core feature, they are just there for selftests to use ML-DSA signatures. The feature itself just uses whatever crypto the kernel's PKCS#7 parser supports. Okay, back to the configs...`CONFIG_KEYS` is definitely required for `bpf_lookup_keyring` and `bpf_keyring_enforced` because those functions live in `kernel/bpf/keys.c`, which only gets compiled if `CONFIG_KEYS=y`. Let me double-check the `kernel/bpf/Makefile` to confirm: ```makefile ifeq ($(CONFIG_KEYS),y) obj-$(CONFIG_BPF_SYSCALL) += keys.o endif ``` Yep, confirmed, `CONFIG_KEYS` is a hard requirement. Now, about `bpf_prog_verify_signature`, I should probably check if it's static. Yes, it is. The instructions say to prefer non-static, non-inlined API entrypoints over internal static helpers, because static helpers get inlined and don't have distinct symbol addresses. `bpf_prog_verify_signature` is static. Is it inlined? It's pretty large, so it's probably *not* inlined, but `bpf_check` is the main entry point, and that seems problematic. Wait, `bpf_check` is the main verifier entry point. Fuzzing that is probably too generic, so let's see, are there any non-static functions available? `bpf_lookup_keyring` and `bpf_keyring_enforced` are non-static. Let's dig into `kernel/bpf/keys.c` a bit further... |
| 12/2 | 2026/08/25 14:56 | tool | read-file |
0mArgs:{
"File": "kernel/bpf/keys.c",
"FirstLine": 1,
"LineCount": 50
}Results: {
"Contents": " 1:\t// SPDX-License-Identifier: GPL-2.0-only\n 2:\t/* Copyright (c) 2026 Isovalent */\n 3:\t\n 4:\t#include \u003clinux/bpf.h\u003e\n 5:\t#include \u003clinux/cred.h\u003e\n 6:\t#include \u003clinux/err.h\u003e\n 7:\t#include \u003clinux/init.h\u003e\n 8:\t#include \u003clinux/key.h\u003e\n 9:\t#include \u003clinux/moduleparam.h\u003e\n 10:\t#include \u003clinux/slab.h\u003e\n 11:\t\n 12:\t#undef MODULE_PARAM_PREFIX\n 13:\t#define MODULE_PARAM_PREFIX \"bpf.\"\n 14:\t\n 15:\tstatic struct key *bpf_keyring;\n 16:\t\n 17:\tstatic bool bpf_keyring_unsealed __ro_after_init;\n 18:\tmodule_param_named(keyring_unsealed, bpf_keyring_unsealed, bool, 0444);\n 19:\tMODULE_PARM_DESC(keyring_unsealed, \"Leave the bpf keyring unsealed\");\n 20:\t\n 21:\tbool bpf_keyring_enforced(void)\n 22:\t{\n 23:\t\treturn bpf_keyring_unsealed;\n 24:\t}\n 25:\t\n 26:\tstruct bpf_key *bpf_lookup_keyring(void)\n 27:\t{\n 28:\t\tstruct bpf_key *bkey;\n 29:\t\n 30:\t\tif (!bpf_keyring)\n 31:\t\t\treturn NULL;\n 32:\t\tif (!READ_ONCE(bpf_keyring-\u003ekeys.nr_leaves_on_tree) ||\n 33:\t\t !READ_ONCE(bpf_keyring-\u003erestrict_link))\n 34:\t\t\treturn NULL;\n 35:\t\n 36:\t\tbkey = kmalloc_obj(*bkey);\n 37:\t\tif (!bkey)\n 38:\t\t\treturn NULL;\n 39:\t\n 40:\t\tbkey-\u003ekey = bpf_keyring;\n 41:\t\tbkey-\u003ehas_ref = false;\n 42:\t\treturn bkey;\n 43:\t}\n 44:\t\n 45:\tstatic int __init bpf_keyring_init(void)\n 46:\t{\n 47:\t\tstruct key *keyring;\n 48:\t\n 49:\t\tkeyring = keyring_alloc(\".bpf\",\n 50:\t\t\t\t\tGLOBAL_ROOT_UID, GLOBAL_ROOT_GID,\n"
} |
| 13/2 | 2026/08/25 14:56 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 29274 output: 151 thoughts: 1181 Thoughts: **Fuzzing Plan: BPF Keyring Signature Verification** Okay, so I'm looking at this patch and figuring out how to approach fuzzing it. I see that `bpf_lookup_keyring` and `bpf_keyring_enforced` are non-static functions, and they seem central to this new BPF keyring functionality, so those are definitely prime candidates for fuzzing symbols. Although `bpf_prog_verify_signature` is static and potentially large, it already exists, and this patch just *modifies* it to use the new keyring, which means its main logic is critical and should also be considered. Since the patch heavily changes this function, including it makes sense. Essentially, this patch introduces a new BPF keyring for verifying BPF program signatures, and it enforces its use when unsealed. This impacts the BPF program loading and verification paths, accessed from user-space through the `bpf()` syscall. This modified path is ripe for fuzzing. My primary focus will be on the added/modified functionality related to the keyring. I'll flag `bpf_lookup_keyring` and `bpf_keyring_enforced` as "FocusSymbols". I'll also include `bpf_prog_verify_signature`, as its role is essential and it's not inlined. As for configurations, it's pretty clear that `CONFIG_KEYS` and `BPF_SYSCALL` are relevant. I need the keyring support (KEYS) and the BPF system call (BPF_SYSCALL), as the BPF syscall is how the BPF logic is reached. I reviewed the changes: besides the BPF code in `kernel/bpf/keys.c` and `kernel/bpf/verifier.c`, many selftests and tools are updated to support the changes. The functional changes are indeed within the kernel modules. This is worth fuzzing. So, the plan is: fuzz the BPF system call, with the KEY config enabled, and target the verification logic that now incorporates keyring checks. I'm ready to `set-results`. |
| 14/2 | 2026/08/25 14:56 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"KEYS",
"BPF_SYSCALL"
],
"FocusSymbols": [
"bpf_lookup_keyring",
"bpf_keyring_enforced"
],
"Reasoning": "The patch introduces a new `.bpf` keyring for BPF program signature verification and a `bpf.keyring_unsealed` boot parameter. It modifies the BPF program loading and verification paths in `bpf_prog_verify_signature` to handle the new keyring and enforce its use when configured. These changes affect the `bpf()` syscall path and introduce new reachable state and logic that should be fuzzed.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"KEYS",
"BPF_SYSCALL"
],
"FocusSymbols": [
"bpf_lookup_keyring",
"bpf_keyring_enforced"
],
"Reasoning": "The patch introduces a new `.bpf` keyring for BPF program signature verification and a `bpf.keyring_unsealed` boot parameter. It modifies the BPF program loading and verification paths in `bpf_prog_verify_signature` to handle the new keyring and enforce its use when configured. These changes affect the `bpf()` syscall path and introduce new reachable state and logic that should be fuzzed.",
"WorthFuzzing": true
} |
| 15/1 | 2026/08/25 14:56 | action | If |
17mArgs:{
"WorthFuzzing": true
}Results: null |
| 16/2 | 2026/08/25 14:56 | agent | kmsan-evaluator |
17mModel:best-expensive Args: null Results: {
"KMSANReasoning": "The patch series introduces a new BPF keyring (`.bpf`) for BPF program signature verification, along with a kernel parameter `bpf.keyring_unsealed` to manage its state. The changes primarily involve adding a new keyring initialization function, adding a lookup function `bpf_lookup_keyring` which allocates a `struct bpf_key` and initializes its fields, and modifying `bpf_prog_verify_signature` to handle the new `VERIFY_USE_BPF_KEYRING` ID.\n\nNone of these changes introduce new kernel structures that are copied to user space, nor do they introduce complex data structures with potentially uninitialized fields. The only new kernel allocation is for `struct bpf_key` via `kmalloc_obj`, and all its fields (`key` and `has_ref`) are explicitly initialized immediately after allocation. The signature and data buffers used during verification are allocated with `kvmemdup_bpfptr` (which copies from user space) and `kvmalloc(..., __GFP_ZERO)` (which zero-initializes), respectively.\n\nAny potential bugs introduced by these changes, such as memory leaks, out-of-bounds accesses, or use-after-free issues, would be effectively caught by standard KASAN and other default bug detectors. There is no risk of uninitialized memory usage or info-leaks that would necessitate a dedicated KMSAN fuzzing session.",
"NeedsKMSAN": false
}Instruction: You are an expert Linux kernel security engineer specializing in kernel memory
error detectors (KASAN and KMSAN). Your job is to review the provided patch series and
determine if the code changes justify spawning a dedicated KMSAN (KernelMemorySanitizer)
fuzzing session in addition to standard KASAN fuzzing.
CRITICAL DISTINCTION BETWEEN KASAN AND KMSAN:
- Standard KASAN kernel builds (upstream-apparmor-kasan.config) already enable
a comprehensive suite of debugging tools and sanitizers, including KASAN
(out-of-bounds accesses, use-after-free, double free, invalid free), LOCKDEP
(locking bugs and deadlocks), UB-sanitizers, and memory corruption checks.
- KMSAN (KernelMemorySanitizer) detects reads of UNINITIALIZED memory (stack, heap,
or page allocations) and kernel-to-user memory info-leaks.
Rule: THERE IS NO SENSE IN RUNNING A KMSAN SESSION IF A BUG CAN BE CAUGHT BY KASAN,
LOCKDEP, OR OTHER STANDARD BUG DETECTORS.
A dedicated KMSAN fuzzing session incurs significant resource costs. You must ONLY
set NeedsKMSAN=true if the code changes introduce or expose UNINITIALIZED MEMORY risks
that are detected ONLY by KMSAN.
Look holistically at the patch series and surrounding code. Even if no direct
uninitialized field accesses or new buffer allocations are added in the diff itself,
a patch may alter control flow, bounds checking, or data length calculations in ways
that change how the rest of the code operates on existing buffers (e.g. allowing
uninitialized stack/heap memory to be read, copied to user space, or used in control
flow). Do not hesitate to use your code access tools to inspect the surrounding code,
called functions, and callers.
Set NeedsKMSAN=true ONLY IF the patch introduces or modifies:
1. Kernel structures sent to user space (via copy_to_user, put_user, netlink skb
attributes, ioctl output arguments, socket options, or BPF buffers) where fields
or structure padding might not be fully initialized/zeroed.
2. Conditional logic or branching that depends on potentially uninitialized variables
or struct fields.
3. Allocation or initialization of complex data structures where uninitialized fields
could be read later in reachable code paths.
4. Bounds checks, lengths, or logic in a way that allows surrounding code to access
uninitialized bytes of existing buffers.
Set NeedsKMSAN=false IF:
- The code changes primarily risk out-of-bounds access, array overflows, NULL pointer
dereferences, locking deadlocks, or use-after-free bugs (these are already caught
by KASAN, LOCKDEP, or standard bug detectors).
- All stack/heap structures touched or introduced by the patch are fully zeroed
or initialized (e.g. using = {0}, memset, kzalloc) before being read or copied.
- The patch does not introduce any risk of uninitialized memory usage or info-leaks.
Use your code access tools to inspect the surrounding code if necessary, then provide
detailed KMSANReasoning contrasting KASAN vs KMSAN applicability for this patch.
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 4b232f890ce6f15bc78b56751fb69c404b179db0
Author: syz-cluster <triage@syzkaller.com>
Date: Tue Aug 25 14:55:07 2026 +0000
syz-cluster: applied patch under review
diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index e4643634a9b1b..e70fb15757d40 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -667,6 +667,22 @@ Kernel parameters
See Documentation/admin-guide/bootconfig.rst
+ bpf.keyring_unsealed=
+ [BPF] When set to 1, leave the bpf keyring unsealed
+ after initialization so that userspace can provision
+ keys. Once the keyring is restricted it becomes active
+ and can be used for BPF program signature verification.
+
+ Setting this also means that the bpf keyring is the
+ only non-system keyring a loader may select for the
+ rest of the boot: caller-supplied user/session
+ keyrings are refused with -EPERM, whether or not
+ provisioning actually completed. The system keyrings
+ stay selectable. Leaving it unset keeps the prior
+ behaviour, where a caller-supplied keyring is allowed.
+
+ See Documentation/bpf/signing.rst
+
bttv.card= [HW,V4L] bttv (bt848 + bt878 based grabber cards)
bttv.radio= Most important insmod options are available as
kernel args too.
diff --git a/Documentation/bpf/signing.rst b/Documentation/bpf/signing.rst
index e73eaaebd8b15..6c03fb58b1464 100644
--- a/Documentation/bpf/signing.rst
+++ b/Documentation/bpf/signing.rst
@@ -254,21 +254,25 @@ 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.)
+A more realistic admission policy than "is it signed at all": base trust in
+the bpf keyring, accept a staging signature only while the key/keyring the
+program 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. */
+ /*
+ * Serials of caller-supplied keyrings we are willing to stage. Empty
+ * on a system that has committed to the bpf keyring, where the kernel
+ * refuses them anyway.
+ */
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__type(key, __s32); /* keyring_serial */
__type(value, __u8);
__uint(max_entries, 64);
- } trusted_user_keys SEC(".maps");
+ } staging_keys SEC(".maps");
/* Audit stream consumed by a userspace logger. */
struct {
@@ -291,11 +295,19 @@ auditable. (Illustrative - error checking elided.)
if (kernel)
return 0; /* trust in-kernel loads */
- if (verdict != BPF_SIG_VERIFIED)
+ 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 */
+ } else switch (ktype) {
+ case BPF_SIG_KEYRING_BUILTIN:
+ case BPF_SIG_KEYRING_BPF:
+ break;
+ case BPF_SIG_KEYRING_USER:
+ if (!bpf_map_lookup_elem(&staging_keys, &serial))
+ ret = -EPERM;
+ break;
+ default:
+ ret = -EPERM; /* keyring not in policy */
+ }
d = bpf_ringbuf_reserve(&audit, sizeof(*d), 0);
if (d) {
@@ -309,6 +321,11 @@ auditable. (Illustrative - error checking elided.)
return ret;
}
+Such a policy is what makes a caller-supplied keyring usable at all on a system
+that does not boot with ``bpf.keyring_unsealed=1``: the allowlist bounds which
+staged keys count, and the LSM itself has to protect them from being tampered
+with.
+
Observing a verified load: ``security_bpf_prog()``
--------------------------------------------------
@@ -381,8 +398,9 @@ 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
+let a policy distinguish "verified against the operator's bpf keyring" from
+"verified against a keyring the loader supplied itself", which is the
+distinction that matters most. A policy LSM such as IPE could consume the same
hooks to enforce system policy without writing any BPF, though none implements
this today.
@@ -390,33 +408,154 @@ 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.
+against. Four values are well-known; anything else is taken as the serial of a
+caller-supplied user or session key or keyring:
+
+.. list-table::
+ :header-rows: 1
+
+ * - ``keyring_id``
+ - Keyring
+ * - ``0``
+ - builtin trusted keyring
+ * - ``VERIFY_USE_SECONDARY_KEYRING`` (``1``)
+ - secondary trusted keyring
+ * - ``VERIFY_USE_PLATFORM_KEYRING`` (``2``)
+ - platform keyring
+ * - ``VERIFY_USE_BPF_KEYRING`` (``3``)
+ - the bpf keyring
+ * - anything else
+ - serial of a caller-supplied user/session key or keyring
+
+The keyring is resolved first, before the signature bytes are examined, so a
+signature naming a keyring that cannot be used is rejected up front, and a
+failed verification aborts the load - a program that loads successfully with
+a signature therefore always has consistent keyring fields recorded.
+
+The bpf keyring
+---------------
+
+A system keyring needs a kernel rebuild or a vouched-for enrollment to rotate a
+key, and grants BPF-signing trust to keys trusted for everything else in the
+kernel too. A caller-supplied keyring, at the other extreme, is filled by the
+very process that loads the program and so carries no trust of its own.
+
+The bpf keyring fills that gap and is the trust anchor which a signed BPF
+deployment should be built on top of: a keyring named ``.bpf``, selected with
+``VERIFY_USE_BPF_KEYRING``, that an operator provisions at boot with a key
+scoped to BPF program loading and nothing else in the kernel's trust hierarchy.
+It is owned by the operator rather than by the loader, and rotatable across a
+reboot without touching the kernel image. It is modelled after the dm-verity
+keyring (see ``dm_verity.keyring_unsealed=``) and provisioned the same way: an
+initrd runs the ``keyctl`` steps below before handing off to the rootfs.
+
+Provisioning
+~~~~~~~~~~~~
+
+The keyring is created during ``late_initcall`` and is **sealed empty** by
+default: it carries a reject-all restriction, so no key can ever be added and
+``VERIFY_USE_BPF_KEYRING`` fails with ``-ENOKEY`` for the whole boot.
+
+``bpf.keyring_unsealed=1`` leaves it unrestricted at init so the initrd can
+provision it. The keyring is not linked into any process keyring, so it is
+addressed by the serial ``/proc/keys`` reports. Steps would be as follows::
+
+ serial=0x$(awk '$8 == "keyring" && $9 == ".bpf:" { print $1 }' /proc/keys)
+
+ keyctl padd asymmetric "" $serial < signing_key.der
+ keyctl restrict_keyring $serial
+
+Both steps are required: the keyring is consulted only once it is **non-empty
+and restricted**. An unrestricted keyring is ignored even when it holds keys,
+so a half-provisioned keyring is inert rather than a weaker trust anchor, and a
+load naming it fails with ``-ENOKEY`` and a verifier log. Restricting cannot
+be undone.
+
+More than one key is enrolled by repeating the ``keyctl padd`` step; the
+restriction is applied once, after the last of them::
+
+ for key in /etc/bpf/keys/*.der; do
+ keyctl padd asymmetric "" $serial < $key
+ done
+
+ keyctl restrict_keyring $serial
+ keyctl show $serial
+
+The restriction bounds what can be added, never what can be taken away. A key
+that is already enrolled can still be unlinked, and the keyring cleared or
+revoked, by anything running as root. That does not weaken the anchor, since
+a keyring left empty is no longer consulted and a load naming it fails with
+``-ENOKEY``, but it does take signed loading out until the next boot. Dropping
+the user permissions the keyring no longer needs would close that; as a third
+step in the initrd::
+
+ keyctl setperm $serial 0x08030000
+
+What remains is ``KEY_POS_SEARCH`` for the in-kernel search during verification,
+plus ``KEY_USR_VIEW`` and ``KEY_USR_READ`` so the keyring stays visible in
+``/proc/keys`` and ``keyctl show``.
+
+Provisioning has to complete before control passes to the rootfs. The keyring
+takes any number of keys for as long as it is unrestricted, so it is the
+restriction that bounds the enrolled set, not the first enrollment. Before
+the initrd hands off control, it must therefore restrict the keyring after
+the last enrollment.
+
+Enforcement
+~~~~~~~~~~~
+
+``bpf.keyring_unsealed=1`` states that the bpf keyring is *the* trust anchor for
+this boot, so it does more than unseal. From the first program load onwards a
+caller-supplied user/session keyring is refused with ``-EPERM`` and a verifier
+log message, whether or not provisioning ever completed. The system keyrings
+stay selectable.
+
+Enforcement is readable at ``/sys/module/bpf/parameters/keyring_unsealed``, and
+read-only there: the flag is ``__ro_after_init`` behind a 0444 parameter. It is
+also derived from the boot flag rather than from the keyring's runtime state,
+so there is no window early in boot during which a caller-supplied keyring is
+still accepted.
+
+Caller-supplied keyrings are for staging
+----------------------------------------
+
+A ``keyring_id`` naming a user or session key or keyring is a *staging*
+mechanism, not a trust anchor: it is filled by the same userspace that loads the
+program, so verifying against it establishes only that the loader signed what it
+loaded. Its purpose is to let software installed onto a running system - whose
+signing key is not enrolled anywhere yet - run signed until that key reaches the
+bpf keyring on the next boot.
+
+A system that has committed to the bpf keyring refuses this path outright (see
+`Enforcement`_). A system that has not can still allow it, but a policy must
+never treat ``BPF_SIG_KEYRING_USER`` as equivalent to the bpf or system
+keyrings; it should allowlist the specific serials it is willing to stage and
+pair that with a BPF LSM policy protecting those keys from tampering, as in
+`Enforcement via LSMs`_.
+
+Recorded fields
+---------------
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.
+ keyrings, ``_BPF`` for the bpf keyring, or ``_USER`` for a caller-supplied
+ 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
+ **caller-supplied 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".
+ serial. The system keyrings and the bpf keyring are trusted as a whole and
+ expose no serial here, so the serial is ``0`` for them, and ``0`` for
+ unsigned programs. A non-zero ``keyring_serial`` is therefore exactly
+ "verified against the caller-supplied key/keyring with this serial", which
+ is exactly the case a policy has to scrutinise.
.. list-table::
:header-rows: 1
@@ -436,16 +575,49 @@ Two fields are recorded in ``prog->aux->sig`` for an LSM to inspect:
* - ``VERIFY_USE_PLATFORM_KEYRING``
- ``BPF_SIG_KEYRING_PLATFORM``
- ``0``
- * - other (a user/session key serial)
+ * - ``VERIFY_USE_BPF_KEYRING``
+ - ``BPF_SIG_KEYRING_BPF``
+ - ``0``
+ * - other (a caller-supplied key serial)
- ``BPF_SIG_KEYRING_USER``
- serial of the resolved key/keyring
-Producing a signed object
-==========================
+Producing and loading a signed object
+=====================================
+
+Generating a signing key
+------------------------
+
+Signing is algorithm agnostic: the algorithm comes from the X.509 certificate
+and the PKCS#7 ``SignerInfo``. Anything the X.509 and PKCS#7 parsers understand
+works with no BPF-side change. Both examples below read the certificate request
+config from ``x509.genkey``, for which ``certs/x509.genkey`` serves as a
+template (see Documentation/admin-guide/module-signing.rst). RSA::
+
+ openssl req -new -nodes -utf8 -sha256 -days 36500 -batch -x509 \
+ -config x509.genkey -outform PEM \
+ -out signing_key.pem -keyout signing_key.pem
+ openssl x509 -in signing_key.pem -outform der -out signing_key.der
+
+ML-DSA-87 (FIPS-204), which needs openssl 3.5 or later and ``CONFIG_CRYPTO_MLDSA``
+in the kernel. Note the absence of a digest option: ML-DSA hashes the message
+itself and openssl rejects an explicit digest for it::
+
+ openssl req -new -nodes -utf8 -days 36500 -batch -x509 \
+ -newkey ML-DSA-87 -config x509.genkey -outform PEM \
+ -out signing_key.pem -keyout signing_key.pem
+ openssl x509 -in signing_key.pem -outform der -out signing_key.der
+
+``bpftool`` handles the following internally: openssl before 4.0 cannot combine
+ML-DSA with ``CMS_NOATTR``, so it falls back to signedAttrs, where only SHA-512
+is permitted. This mirrors what module signing does as well.
+
+Signing
+-------
``bpftool`` generates and signs a light skeleton in one step::
- bpftool gen skeleton -L -S -k <private_key.pem> -i <certificate.x509> \
+ bpftool gen skeleton -L -S -k signing_key.pem -i signing_key.der \
obj.bpf.o > obj.lskel.h
``-L`` selects the light-skeleton (``gen_loader``) backend and ``-S`` enables
@@ -454,12 +626,36 @@ signing; ``-k`` and ``-i`` supply the signing key and its X.509 certificate.
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.
+signing and is not included.
+
+Loading
+-------
+
+The generated skeleton exposes ``keyring_id``, which selects the keyring the
+kernel verifies against. Set it between open and load; loading then 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.
+ struct obj *skel = obj__open();
+
+ skel->keyring_id = 3; /* VERIFY_USE_BPF_KEYRING */
+ err = obj__load(skel);
+
+For the staging case the same object is loaded against a keyring the caller
+populated itself, which only works on a system that has not set
+``bpf.keyring_unsealed=1``::
+
+ /*
+ * Staging only: this keyring is under the loader's own control and
+ * carries no trust of its own. See "Caller-supplied keyrings are for
+ * staging".
+ */
+ key_id = add_key("asymmetric", "", der, der_sz, KEY_SPEC_SESSION_KEYRING);
+ skel->keyring_id = KEY_SPEC_SESSION_KEYRING;
+ err = obj__load(skel);
+
+Either way the trusted public key must already be in the chosen keyring for
+verification to succeed. For the bpf keyring that enrollment happens once at
+boot, see `Provisioning`_.
UAPI reference
==============
@@ -487,6 +683,13 @@ UAPI reference
The map content is not hashed separately at all - it is covered, as bytes,
by the program signature.
+Kernel command line:
+
+``bpf.keyring_unsealed=``
+ Set to ``1`` to leave the bpf keyring unsealed for provisioning, and to make
+ it the only non-system keyring a loader may select for the rest of the boot
+ (see `The bpf keyring`_).
+
Notes and limitations
======================
@@ -495,3 +698,5 @@ Notes and limitations
exceed it.
- The metadata container is a single-element array map, accessed through
``map_direct_value_addr``.
+- The bpf keyring needs ``CONFIG_KEYS``; without it there is no bpf keyring
+ and ``VERIFY_USE_BPF_KEYRING`` never resolves.
diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index b3cd28d9e3b55..571f7792a5c41 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -1735,6 +1735,7 @@ enum bpf_sig_keyring {
BPF_SIG_KEYRING_SECONDARY,
BPF_SIG_KEYRING_PLATFORM,
BPF_SIG_KEYRING_USER,
+ BPF_SIG_KEYRING_BPF,
};
struct bpf_prog_aux {
@@ -3819,6 +3820,8 @@ struct bpf_key {
#if defined(CONFIG_KEYS) && defined(CONFIG_BPF_SYSCALL)
struct bpf_key *bpf_lookup_user_key(s32 serial, u64 flags);
struct bpf_key *bpf_lookup_system_key(u64 id);
+struct bpf_key *bpf_lookup_keyring(void);
+bool bpf_keyring_enforced(void);
void bpf_key_put(struct bpf_key *bkey);
int bpf_verify_pkcs7_signature(const struct bpf_dynptr *data_p,
const struct bpf_dynptr *sig_p,
@@ -3839,6 +3842,16 @@ static inline struct bpf_key *bpf_lookup_system_key(u64 id)
return NULL;
}
+static inline struct bpf_key *bpf_lookup_keyring(void)
+{
+ return NULL;
+}
+
+static inline bool bpf_keyring_enforced(void)
+{
+ return false;
+}
+
static inline void bpf_key_put(struct bpf_key *bkey)
{
}
diff --git a/include/linux/verification.h b/include/linux/verification.h
index dec7f2beabfd4..1cb59ddda2509 100644
--- a/include/linux/verification.h
+++ b/include/linux/verification.h
@@ -18,6 +18,16 @@
#define VERIFY_USE_SECONDARY_KEYRING ((struct key *)1UL)
#define VERIFY_USE_PLATFORM_KEYRING ((struct key *)2UL)
+/*
+ * The id of BPF's ".bpf" keyring, reserved from the same space. It is
+ * explicitly not a sentinel like the two above as BPF resolves it to
+ * the keyring itself and passes that, so verify_pkcs7_signature() never
+ * sees this value, and system_keyring_id_check() must keep rejecting it.
+ * Left as a plain integer so that handing it over as @trusted_keys does
+ * not compile.
+ */
+#define VERIFY_USE_BPF_KEYRING 3
+
static inline int system_keyring_id_check(u64 id)
{
if (id > (unsigned long)VERIFY_USE_PLATFORM_KEYRING)
diff --git a/kernel/bpf/Makefile b/kernel/bpf/Makefile
index 90255d80e5be6..9a92c348bbda6 100644
--- a/kernel/bpf/Makefile
+++ b/kernel/bpf/Makefile
@@ -27,6 +27,9 @@ obj-$(CONFIG_BPF_SYSCALL) += offload.o
obj-$(CONFIG_BPF_SYSCALL) += net_namespace.o
obj-$(CONFIG_BPF_SYSCALL) += tcx.o
endif
+ifeq ($(CONFIG_KEYS),y)
+obj-$(CONFIG_BPF_SYSCALL) += keys.o
+endif
ifeq ($(CONFIG_PERF_EVENTS),y)
obj-$(CONFIG_BPF_SYSCALL) += stackmap.o
endif
diff --git a/kernel/bpf/keys.c b/kernel/bpf/keys.c
new file mode 100644
index 0000000000000..60cb85295c89a
--- /dev/null
+++ b/kernel/bpf/keys.c
@@ -0,0 +1,72 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2026 Isovalent */
+
+#include <linux/bpf.h>
+#include <linux/cred.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/key.h>
+#include <linux/moduleparam.h>
+#include <linux/slab.h>
+
+#undef MODULE_PARAM_PREFIX
+#define MODULE_PARAM_PREFIX "bpf."
+
+static struct key *bpf_keyring;
+
+static bool bpf_keyring_unsealed __ro_after_init;
+module_param_named(keyring_unsealed, bpf_keyring_unsealed, bool, 0444);
+MODULE_PARM_DESC(keyring_unsealed, "Leave the bpf keyring unsealed");
+
+bool bpf_keyring_enforced(void)
+{
+ return bpf_keyring_unsealed;
+}
+
+struct bpf_key *bpf_lookup_keyring(void)
+{
+ struct bpf_key *bkey;
+
+ if (!bpf_keyring)
+ return NULL;
+ if (!READ_ONCE(bpf_keyring->keys.nr_leaves_on_tree) ||
+ !READ_ONCE(bpf_keyring->restrict_link))
+ return NULL;
+
+ bkey = kmalloc_obj(*bkey);
+ if (!bkey)
+ return NULL;
+
+ bkey->key = bpf_keyring;
+ bkey->has_ref = false;
+ return bkey;
+}
+
+static int __init bpf_keyring_init(void)
+{
+ struct key *keyring;
+
+ keyring = keyring_alloc(".bpf",
+ GLOBAL_ROOT_UID, GLOBAL_ROOT_GID,
+ current_cred(), KEY_POS_SEARCH |
+ KEY_USR_VIEW | KEY_USR_READ |
+ KEY_USR_WRITE | KEY_USR_SEARCH |
+ KEY_USR_SETATTR, KEY_ALLOC_NOT_IN_QUOTA,
+ NULL, NULL);
+ if (IS_ERR(keyring)) {
+ pr_err("bpf: cannot allocate bpf keyring: %ld\n",
+ PTR_ERR(keyring));
+ return 0;
+ }
+ if (!bpf_keyring_unsealed &&
+ keyring_restrict(make_key_ref(keyring, true), NULL, NULL)) {
+ pr_err("bpf: cannot seal bpf keyring\n");
+ key_revoke(keyring);
+ key_put(keyring);
+ return 0;
+ }
+
+ bpf_keyring = keyring;
+ return 0;
+}
+late_initcall(bpf_keyring_init);
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index e036ae20bf6b9..558e518ad3109 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -20972,6 +20972,14 @@ int bpf_fixup_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
return 0;
}
+/*
+ * Upper bound on the PKCS#7 signature blob passed with a program. Comfortably
+ * above the largest signature the kernel can verify, and far below anything
+ * that would make rejecting a load expensive. Deliberately a fixed number so
+ * that what the syscall accepts does not depend on PAGE_SIZE.
+ */
+#define BPF_PROG_MAX_SIGNATURE_SIZE (64 * 1024)
+
static enum bpf_sig_keyring bpf_classify_keyring(s32 keyring_id)
{
switch (keyring_id) {
@@ -20981,6 +20989,8 @@ static enum bpf_sig_keyring bpf_classify_keyring(s32 keyring_id)
return BPF_SIG_KEYRING_SECONDARY;
case (s32)(unsigned long)VERIFY_USE_PLATFORM_KEYRING:
return BPF_SIG_KEYRING_PLATFORM;
+ case VERIFY_USE_BPF_KEYRING:
+ return BPF_SIG_KEYRING_BPF;
default:
return BPF_SIG_KEYRING_USER;
}
@@ -21009,21 +21019,32 @@ static int bpf_prog_verify_signature(struct bpf_verifier_env *env,
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)
+ attr->signature_size > BPF_PROG_MAX_SIGNATURE_SIZE)
return -EINVAL;
- if (system_keyring_id_check(attr->keyring_id) == 0)
+
+ if (!system_keyring_id_check(attr->keyring_id)) {
key = bpf_lookup_system_key(attr->keyring_id);
- else
- key = bpf_lookup_user_key(attr->keyring_id, 0);
+ } else {
+ if (attr->keyring_id != VERIFY_USE_BPF_KEYRING) {
+ if (bpf_keyring_enforced()) {
+ verbose(env, "caller-supplied keyring refused, use bpf keyring\n");
+ return -EPERM;
+ }
+ key = bpf_lookup_user_key(attr->keyring_id, 0);
+ } else {
+ key = bpf_lookup_keyring();
+ }
+ }
if (!key) {
- verbose(env, "cannot resolve signing keyring with keyring_id %d\n",
- attr->keyring_id);
- return -EINVAL;
+ if (attr->keyring_id == VERIFY_USE_BPF_KEYRING) {
+ verbose(env, "the bpf keyring is empty or has not been restricted\n");
+ return -ENOKEY;
+ } else {
+ verbose(env, "cannot resolve signing keyring with keyring_id %d\n",
+ attr->keyring_id);
+ return -EINVAL;
+ }
}
sig = kvmemdup_bpfptr(usig, attr->signature_size);
diff --git a/tools/bpf/bpftool/main.h b/tools/bpf/bpftool/main.h
index 78b6e0ebb85d8..9315a1db1f7c2 100644
--- a/tools/bpf/bpftool/main.h
+++ b/tools/bpf/bpftool/main.h
@@ -57,7 +57,7 @@ static inline void *u64_to_ptr(__u64 ptr)
})
#define ERR_MAX_LEN 1024
-#define MAX_SIG_SIZE 4096
+#define MAX_SIG_SIZE 16384
#define BPF_TAG_FMT "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
diff --git a/tools/bpf/bpftool/sign.c b/tools/bpf/bpftool/sign.c
index 88726a6db6d0e..1b5502f48add0 100644
--- a/tools/bpf/bpftool/sign.c
+++ b/tools/bpf/bpftool/sign.c
@@ -130,6 +130,9 @@ __u32 register_session_key(const char *key_der_path)
int bpftool_prog_sign(struct bpf_load_and_run_opts *opts)
{
+ unsigned int signer_flags = CMS_NOCERTS | CMS_BINARY | CMS_NOSMIMECAP |
+ CMS_USE_KEYID | CMS_NOATTR;
+ const EVP_MD *cms_digest = EVP_sha256();
BIO *bd_in = NULL, *bd_out = NULL;
EVP_PKEY *private_key = NULL;
CMS_ContentInfo *cms = NULL;
@@ -167,6 +170,21 @@ int bpftool_prog_sign(struct bpf_load_and_run_opts *opts)
goto cleanup;
}
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L && OPENSSL_VERSION_NUMBER < 0x40000000L
+ if (EVP_PKEY_is_a(private_key, "ML-DSA-44") ||
+ EVP_PKEY_is_a(private_key, "ML-DSA-65") ||
+ EVP_PKEY_is_a(private_key, "ML-DSA-87")) {
+ /*
+ * ML-DSA + CMS_NOATTR is not supported before openssl-4.0, so
+ * fall back to signedAttrs. ML-DSA does its own hashing and
+ * ignores the CMS digest, except when signedAttrs are used,
+ * where only SHA-512 is permitted.
+ */
+ signer_flags &= ~CMS_NOATTR;
+ cms_digest = EVP_sha512();
+ }
+#endif
+
cms = CMS_sign(NULL, NULL, NULL, NULL,
CMS_NOCERTS | CMS_PARTIAL | CMS_BINARY | CMS_DETACHED |
CMS_STREAM);
@@ -175,9 +193,7 @@ int bpftool_prog_sign(struct bpf_load_and_run_opts *opts)
goto cleanup;
}
- if (!CMS_add1_signer(cms, x509, private_key, EVP_sha256(),
- CMS_NOCERTS | CMS_BINARY | CMS_NOSMIMECAP |
- CMS_USE_KEYID | CMS_NOATTR)) {
+ if (!CMS_add1_signer(cms, x509, private_key, cms_digest, signer_flags)) {
err = -EINVAL;
goto cleanup;
}
diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile
index 5f1a3bfc0569f..05b3ee64290fa 100644
--- a/tools/testing/selftests/bpf/Makefile
+++ b/tools/testing/selftests/bpf/Makefile
@@ -663,7 +663,7 @@ $(TRUNNER_BPF_LSKELS): %.lskel.h: %.bpf.o $(BPFTOOL) | $(TRUNNER_OUTPUT)
}) && \
rm -f $$(<:.o=.llinked1.o) $$(<:.o=.llinked2.o) $$(<:.o=.llinked3.o)
-$(TRUNNER_BPF_LSKELS_SIGNED): %.lskel.h: %.bpf.o $(BPFTOOL) | $(TRUNNER_OUTPUT)
+$(TRUNNER_BPF_LSKELS_SIGNED): %.lskel.h: %.bpf.o $(BPFTOOL) $(VERIFICATION_CERT) | $(TRUNNER_OUTPUT)
$(Q)$(if $(PERMISSIVE),if [ ! -f $$< ]; then \
$$(RM) $$@; \
printf ' %-12s %s\n' 'SKIP-SKEL' '$$(notdir $$@)' 1>&2; \
diff --git a/tools/testing/selftests/bpf/config b/tools/testing/selftests/bpf/config
index ea7044f30adc3..2ec15117abb50 100644
--- a/tools/testing/selftests/bpf/config
+++ b/tools/testing/selftests/bpf/config
@@ -51,6 +51,8 @@ CONFIG_IPV6_SEG6_LWTUNNEL=y
CONFIG_IPV6_SIT=y
CONFIG_IPV6_TUNNEL=y
CONFIG_KEYS=y
+CONFIG_CRYPTO_MLDSA=y
+CONFIG_CRYPTO_SHA512=y
CONFIG_LIRC=y
CONFIG_LIVEPATCH=y
CONFIG_LWTUNNEL=y
diff --git a/tools/testing/selftests/bpf/prog_tests/signed_loader.c b/tools/testing/selftests/bpf/prog_tests/signed_loader.c
index 77381d345435c..620a6d22a759c 100644
--- a/tools/testing/selftests/bpf/prog_tests/signed_loader.c
+++ b/tools/testing/selftests/bpf/prog_tests/signed_loader.c
@@ -32,11 +32,20 @@ enum {
BPF_SIG_KEYRING_SECONDARY,
BPF_SIG_KEYRING_PLATFORM,
BPF_SIG_KEYRING_USER,
+ BPF_SIG_KEYRING_BPF,
};
-static int load_loader(const void *insns, __u32 insns_sz, int map_fd,
- const void *sig, __u32 sig_sz, __s32 keyring_id,
- __u32 fd_array_cnt)
+#define BPF_KEYRING_BPF 3
+
+/* verify_sig_setup.sh exits with this when openssl cannot do ML-DSA. */
+#define SETUP_SKIP (-77)
+
+/* FIPS-204 ML-DSA-87 signature size, see include/crypto/mldsa.h. */
+#define MLDSA87_SIGNATURE_SIZE 4627
+
+static int load_loader_log(const void *insns, __u32 insns_sz, int map_fd,
+ const void *sig, __u32 sig_sz, __s32 keyring_id,
+ __u32 fd_array_cnt, char *log_buf, __u32 log_sz)
{
union bpf_attr attr;
int fd;
@@ -48,18 +57,31 @@ static int load_loader(const void *insns, __u32 insns_sz, int map_fd,
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 = fd_array_cnt;
if (sig) {
attr.signature = ptr_to_u64(sig);
attr.signature_size = sig_sz;
attr.keyring_id = keyring_id;
}
- attr.fd_array_cnt = fd_array_cnt;
+ if (log_buf) {
+ attr.log_level = 1;
+ attr.log_buf = ptr_to_u64(log_buf);
+ attr.log_size = log_sz;
+ }
memcpy(attr.prog_name, "__loader.prog", sizeof("__loader.prog"));
fd = syscall(__NR_bpf, BPF_PROG_LOAD, &attr,
offsetofend(union bpf_attr, keyring_id));
return fd < 0 ? -errno : fd;
}
+static int load_loader(const void *insns, __u32 insns_sz, int map_fd,
+ const void *sig, __u32 sig_sz, __s32 keyring_id,
+ __u32 fd_array_cnt)
+{
+ return load_loader_log(insns, insns_sz, map_fd, sig, sig_sz, keyring_id,
+ fd_array_cnt, NULL, 0);
+}
+
static int run_gen_loader(const void *insns, __u32 insns_sz,
const void *data, __u32 data_sz,
const void *excl, __u32 excl_sz,
@@ -156,12 +178,30 @@ static int run_setup(const char *cmd, const char *dir)
}
if (waitpid(pid, &status, 0) < 0)
return -errno;
- return (WIFEXITED(status) &&
- WEXITSTATUS(status) == 0) ? 0 : -EINVAL;
+ if (!WIFEXITED(status))
+ return -EINVAL;
+ return -WEXITSTATUS(status);
}
-static int sign_buf(const char *dir, const void *buf, __u32 len,
- void *sig, __u32 *sig_sz)
+static void genkey_dir_fini(const char *dir)
+{
+ static const char * const files[] = {
+ "signing_key.der", "signing_key.pem", "x509.genkey",
+ };
+ char path[PATH_MAX];
+ size_t i;
+
+ if (!dir)
+ return;
+ for (i = 0; i < ARRAY_SIZE(files); i++) {
+ snprintf(path, sizeof(path), "%s/%s", dir, files[i]);
+ unlink(path);
+ }
+ rmdir(dir);
+}
+
+static int sign_buf_digest(const char *dir, const void *buf, __u32 len,
+ void *sig, __u32 *sig_sz, const char *digest)
{
char data_tmpl[PATH_MAX], key[PATH_MAX];
char sigpath[PATH_MAX + sizeof(".p7s")];
@@ -176,6 +216,7 @@ static int sign_buf(const char *dir, const void *buf, __u32 len,
fd = mkstemp(data_tmpl);
if (fd < 0)
return -errno;
+ snprintf(sigpath, sizeof(sigpath), "%s.p7s", data_tmpl);
if (write(fd, buf, len) != (ssize_t)len) {
close(fd);
ret = -EIO;
@@ -190,7 +231,7 @@ static int sign_buf(const char *dir, const void *buf, __u32 len,
}
if (pid == 0) {
snprintf(key, sizeof(key), "%s/signing_key.pem", dir);
- execlp("./sign-file", "./sign-file", "-d", "sha256",
+ execlp("./sign-file", "./sign-file", "-d", digest,
key, key, data_tmpl, NULL);
exit(1);
}
@@ -200,34 +241,38 @@ static int sign_buf(const char *dir, const void *buf, __u32 len,
goto out;
}
- snprintf(sigpath, sizeof(sigpath), "%s.p7s", data_tmpl);
if (stat(sigpath, &st) < 0) {
ret = -errno;
goto out;
}
if (st.st_size > (off_t)*sig_sz) {
ret = -E2BIG;
- goto out_sig;
+ goto out;
}
fd = open(sigpath, O_RDONLY);
if (fd < 0) {
ret = -errno;
- goto out_sig;
+ goto out;
}
if (read(fd, sig, st.st_size) != st.st_size) {
close(fd);
ret = -EIO;
- goto out_sig;
+ goto out;
}
close(fd);
*sig_sz = st.st_size;
-out_sig:
- unlink(sigpath);
out:
+ unlink(sigpath);
unlink(data_tmpl);
return ret;
}
+static int sign_buf(const char *dir, const void *buf, __u32 len,
+ void *sig, __u32 *sig_sz)
+{
+ return sign_buf_digest(dir, buf, len, sig, sig_sz, "sha256");
+}
+
struct gen_loader_fixture {
struct test_signed_loader *skel;
struct gen_loader_opts gopts;
@@ -457,7 +502,7 @@ static void signed_btf_fd_array_rejected(void)
dir = mkdtemp(dir_tmpl);
if (!ASSERT_OK_PTR(dir, "mkdtemp"))
return;
- if (!ASSERT_OK(run_setup("setup", dir), "verify_sig_setup")) {
+ if (!ASSERT_OK(run_setup("setup-rsa", dir), "verify_sig_setup")) {
rmdir(dir);
return;
}
@@ -529,7 +574,6 @@ 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) {
@@ -538,22 +582,9 @@ static void signature_failure_logs(void)
* failure is reported through the verifier log. A present-but-
* invalid signature is rejected and the log says why.
*/
- 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));
+ fd = load_loader_log(f.gopts.insns, f.gopts.insns_sz, -1, junk,
+ sizeof(junk), KEY_SPEC_SESSION_KEYRING, 0,
+ log_buf, sizeof(log_buf));
ASSERT_LT(fd, 0, "invalid signature rejected at load");
if (fd >= 0)
close(fd);
@@ -571,8 +602,9 @@ static void signature_too_large(void)
if (gen_loader_fixture_init(&f) == 0) {
/*
- * signature_size beyond the kernel's bound (KMALLOC_MAX_CACHE_SIZE)
- * is rejected before the buffer is read.
+ * signature_size beyond the kernel's bound
+ * (BPF_PROG_MAX_SIGNATURE_SIZE) 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, 0);
@@ -626,6 +658,268 @@ static void signature_bad_keyring(void)
gen_loader_fixture_fini(&f);
}
+static bool keyring_unsealed_boot(void)
+{
+ char val = 0;
+ int fd;
+
+ fd = open("/sys/module/bpf/parameters/keyring_unsealed", O_RDONLY);
+ if (fd < 0)
+ return false;
+ if (read(fd, &val, 1) != 1)
+ val = 0;
+ close(fd);
+ return val == 'Y' || val == '1';
+}
+
+static int bpf_keyring_lookup(int *nr_keys)
+{
+ char line[512], type[32], desc[64];
+ int serial = -ENOENT;
+ FILE *f;
+
+ f = fopen("/proc/keys", "r");
+ if (!f)
+ return -errno;
+
+ while (fgets(line, sizeof(line), f)) {
+ unsigned int hex;
+ char *sum;
+
+ if (sscanf(line, "%x %*s %*s %*s %*s %*s %*s %31s %63s",
+ &hex, type, desc) != 3)
+ continue;
+ if (strcmp(type, "keyring") || strcmp(desc, ".bpf:"))
+ continue;
+
+ serial = (int)hex;
+ if (nr_keys) {
+ sum = strstr(line, ".bpf: ");
+ *nr_keys = !sum || !strncmp(sum + 6, "empty", 5) ?
+ 0 : atoi(sum + 6);
+ }
+ break;
+ }
+ fclose(f);
+ return serial;
+}
+
+static long keyctl_ret(int cmd, unsigned long arg2, unsigned long arg3)
+{
+ long ret = syscall(__NR_keyctl, cmd, arg2, arg3);
+
+ return ret < 0 ? -errno : ret;
+}
+
+/*
+ * What the bpf keyring still needs once it got provisioned: KEY_POS_SEARCH
+ * for the in-kernel search during verification, and the user view/read bits
+ * so it stays visible in /proc/keys, rest is dropped so the enrolled is
+ * therefore final.
+ */
+#define BPF_KEYRING_PERM_LOCKED 0x08030000
+/* What bpf_keyring_init() grants at boot. */
+#define BPF_KEYRING_PERM_INITIAL 0x082f0000
+
+static void bpf_keyring_sealed(void)
+{
+ static const __u8 junk[64] = {};
+ struct gen_loader_fixture f;
+ int serial, key, fd;
+
+ if (keyring_unsealed_boot()) {
+ printf("%s:SKIP:the bpf keyring was unsealed at boot\n", __func__);
+ test__skip();
+ return;
+ }
+ serial = bpf_keyring_lookup(NULL);
+ if (serial >= 0) {
+ key = syscall(__NR_add_key, "user", "sealprobe", "x", 1, serial);
+ if (key >= 0)
+ syscall(__NR_keyctl, KEYCTL_UNLINK, key, serial);
+ ASSERT_EQ(key < 0 ? -errno : 0, -EPERM,
+ "nothing links into a sealed keyring");
+ }
+ if (gen_loader_fixture_init(&f) == 0) {
+ fd = load_loader(f.gopts.insns, f.gopts.insns_sz, -1, junk,
+ sizeof(junk), BPF_KEYRING_BPF, 0);
+ ASSERT_EQ(fd, -ENOKEY, "sealed bpf keyring rejected");
+ if (fd >= 0)
+ close(fd);
+ }
+ gen_loader_fixture_fini(&f);
+}
+
+static int try_load(const struct gen_loader_fixture *f, const void *sig,
+ __u32 sig_sz, __s32 keyring_id, char *log_buf, __u32 log_sz)
+{
+ int map_fd, prog_fd;
+
+ map_fd = setup_meta_map(f);
+ if (!ASSERT_OK_FD(map_fd, "meta_map"))
+ return map_fd;
+ prog_fd = load_loader_log(f->gopts.insns, f->gopts.insns_sz, map_fd,
+ sig, sig_sz, keyring_id, 1, log_buf, log_sz);
+ close(map_fd);
+ if (prog_fd >= 0)
+ close(prog_fd);
+ return prog_fd;
+}
+
+/*
+ * This needs bpf.keyring_unsealed=1 on the guest kernel command line, which
+ * vmtest.sh can pass via KERNEL_CMDLINE_EXTRA. There is no way to unseal the
+ * keyring from here, so without it the test skips. It also only works once
+ * per boot, as restricting a keyring cannot be undone.
+ */
+static void bpf_keyring_provisioned(void)
+{
+ char dir_tmpl[] = "/tmp/bpfkeyringXXXXXX";
+ char bad_tmpl[] = "/tmp/bpfkeyringbadXXXXXX";
+ __u8 *sig = NULL, *bad = NULL, *buf = NULL;
+ int serial, err;
+ int nr_keys = 0, der_fd = -1;
+ struct gen_loader_fixture f;
+ __u32 sig_sz = 8192, bad_sz;
+ bool have_fixture = false;
+ char *dir, *bad_dir = NULL;
+ char log_buf[1024] = {};
+ char path[PATH_MAX];
+ __u8 der[4096];
+ ssize_t der_sz;
+
+ serial = bpf_keyring_lookup(&nr_keys);
+ if (serial < 0) {
+ printf("%s:SKIP:no bpf keyring (needs CONFIG_KEYS)\n", __func__);
+ test__skip();
+ return;
+ }
+ if (nr_keys != 0) {
+ printf("%s:SKIP:the bpf keyring has already been provisioned\n",
+ __func__);
+ test__skip();
+ return;
+ }
+
+ dir = mkdtemp(dir_tmpl);
+ if (!ASSERT_OK_PTR(dir, "mkdtemp"))
+ return;
+ if (!ASSERT_OK(run_setup("genkey", dir), "verify_sig_setup genkey"))
+ goto rmdir;
+
+ snprintf(path, sizeof(path), "%s/signing_key.der", dir);
+ der_fd = open(path, O_RDONLY);
+ if (!ASSERT_OK_FD(der_fd, "open signing_key.der"))
+ goto rmdir;
+ der_sz = read(der_fd, der, sizeof(der));
+ close(der_fd);
+ if (!ASSERT_GT(der_sz, 0, "read signing_key.der"))
+ goto rmdir;
+
+ err = syscall(__NR_add_key, "asymmetric", "", der, (size_t)der_sz,
+ serial);
+ if (err < 0 && errno == EPERM) {
+ printf("%s:SKIP:the bpf keyring is sealed, need bpf.keyring_unsealed=1\n",
+ __func__);
+ test__skip();
+ goto rmdir;
+ }
+ if (!ASSERT_GE(err, 0, "add the signing key to the bpf keyring"))
+ goto rmdir;
+
+ sig = malloc(sig_sz);
+ if (!ASSERT_OK_PTR(sig, "sig buf"))
+ goto out;
+ have_fixture = true;
+ if (gen_loader_fixture_init(&f) != 0)
+ goto out;
+
+ 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;
+
+ ASSERT_EQ(try_load(&f, sig, sig_sz, BPF_KEYRING_BPF, NULL, 0), -ENOKEY,
+ "unrestricted keyring still not consulted");
+
+ ASSERT_EQ(try_load(&f, sig, sig_sz, KEY_SPEC_SESSION_KEYRING, NULL, 0),
+ -EPERM, "caller-supplied keyring refused before provisioning");
+
+ if (!ASSERT_OK(syscall(__NR_keyctl, KEYCTL_RESTRICT_KEYRING, serial,
+ NULL, NULL), "restrict bpf keyring"))
+ goto out;
+
+ if (!ASSERT_OK_FD(try_load(&f, sig, sig_sz, BPF_KEYRING_BPF, NULL, 0),
+ "load signed by a key in the .bpf keyring"))
+ goto out;
+
+ bad_dir = mkdtemp(bad_tmpl);
+ if (!ASSERT_OK_PTR(bad_dir, "mkdtemp unenrolled"))
+ goto out;
+ if (!ASSERT_OK(run_setup("genkey", bad_dir), "verify_sig_setup genkey unenrolled"))
+ goto out;
+ bad_sz = 8192;
+ bad = malloc(bad_sz);
+ if (!ASSERT_OK_PTR(bad, "bad sig buf"))
+ goto out;
+ if (!ASSERT_OK(sign_buf(bad_dir, buf, f.gopts.insns_sz + f.data_sz, bad,
+ &bad_sz), "sign with an unenrolled key"))
+ goto out;
+
+ ASSERT_EQ(try_load(&f, bad, bad_sz, BPF_KEYRING_BPF, log_buf,
+ sizeof(log_buf)), -ENOKEY,
+ "key outside the bpf keyring refused");
+ ASSERT_HAS_SUBSTR(log_buf, "signature verification failed",
+ "the bpf keyring was consulted");
+
+ f.blob[0] ^= 0xff;
+ err = try_load(&f, sig, sig_sz, BPF_KEYRING_BPF, NULL, 0);
+ f.blob[0] ^= 0xff;
+ ASSERT_EQ(err, -EKEYREJECTED, "tampered metadata refused");
+
+ ASSERT_EQ(try_load(&f, sig, sig_sz, KEY_SPEC_SESSION_KEYRING, NULL, 0),
+ -EPERM, "caller-supplied keyring refused once .bpf is in use");
+
+ err = keyctl_ret(KEYCTL_UNLINK, KEY_SPEC_SESSION_KEYRING, serial);
+ ASSERT_EQ(err, -ENOENT, "keyring writable while the user bits are there");
+
+ err = keyctl_ret(KEYCTL_SETPERM, serial, BPF_KEYRING_PERM_LOCKED);
+ if (!ASSERT_OK(err, "drop the user bits on the bpf keyring"))
+ goto out;
+
+ ASSERT_OK_FD(try_load(&f, sig, sig_sz, BPF_KEYRING_BPF, NULL, 0),
+ "load still verified against the locked keyring");
+
+ err = keyctl_ret(KEYCTL_UNLINK, KEY_SPEC_SESSION_KEYRING, serial);
+ ASSERT_EQ(err, -EACCES, "unlink refused");
+ err = keyctl_ret(KEYCTL_CLEAR, serial, 0);
+ ASSERT_EQ(err, -EACCES, "clear refused");
+ err = keyctl_ret(KEYCTL_REVOKE, serial, 0);
+ ASSERT_EQ(err, -EACCES, "revoke refused");
+ err = keyctl_ret(KEYCTL_INVALIDATE, serial, 0);
+ ASSERT_EQ(err, -EACCES, "invalidate refused");
+ err = keyctl_ret(KEYCTL_SET_TIMEOUT, serial, 1);
+ ASSERT_EQ(err, -EACCES, "timeout refused");
+ err = keyctl_ret(KEYCTL_SETPERM, serial, BPF_KEYRING_PERM_INITIAL);
+ ASSERT_EQ(err, -EACCES, "the bits cannot be granted back");
+
+ ASSERT_EQ(bpf_keyring_lookup(&nr_keys), serial, "keyring still there");
+ ASSERT_EQ(nr_keys, 1, "the enrolled key survived");
+out:
+ if (have_fixture)
+ gen_loader_fixture_fini(&f);
+ genkey_dir_fini(bad_dir);
+ free(buf);
+ free(bad);
+ free(sig);
+rmdir:
+ genkey_dir_fini(dir);
+}
+
/*
* A signed loader must ignore ctx-supplied map dimensions: the host cannot
* resize a signed program's maps via the loader ctx. Drive a one-map program
@@ -831,7 +1125,7 @@ static void signature_authenticates_insns(void)
dir = mkdtemp(dir_tmpl);
if (!ASSERT_OK_PTR(dir, "mkdtemp"))
return;
- if (!ASSERT_OK(run_setup("setup", dir), "verify_sig_setup")) {
+ if (!ASSERT_OK(run_setup("setup-rsa", dir), "verify_sig_setup")) {
rmdir(dir);
return;
}
@@ -931,7 +1225,7 @@ static void signature_authenticates_metadata(void)
dir = mkdtemp(dir_tmpl);
if (!ASSERT_OK_PTR(dir, "mkdtemp"))
return;
- if (!ASSERT_OK(run_setup("setup", dir), "verify_sig_setup")) {
+ if (!ASSERT_OK(run_setup("setup-rsa", dir), "verify_sig_setup")) {
rmdir(dir);
return;
}
@@ -1267,7 +1561,7 @@ static void lsm_signature_verdict(void)
dir = mkdtemp(dir_tmpl);
if (!ASSERT_OK_PTR(dir, "mkdtemp"))
goto out;
- if (!ASSERT_OK(run_setup("setup", dir), "verify_sig_setup")) {
+ if (!ASSERT_OK(run_setup("setup-rsa", dir), "verify_sig_setup")) {
rmdir(dir);
dir = NULL;
goto out;
@@ -1450,7 +1744,7 @@ static void loadtime_verify(struct bpf_object *obj, int expect_maps)
dir = mkdtemp(dir_tmpl);
if (!ASSERT_OK_PTR(dir, "mkdtemp"))
return;
- if (!ASSERT_OK(run_setup("setup", dir), "verify_sig_setup")) {
+ if (!ASSERT_OK(run_setup("setup-rsa", dir), "verify_sig_setup")) {
rmdir(dir);
return;
}
@@ -1524,6 +1818,89 @@ static void loadtime_with_map(void)
test_signed_loader_map__destroy(skel);
}
+/*
+ * End-to-end signed load with a post-quantum key. ML-DSA (FIPS-204) is wired
+ * through the X.509 and PKCS#7 parsers, and BPF reaches them via
+ * verify_pkcs7_signature() without knowing the algorithm, so an ML-DSA key in
+ * the keyring should verify an ML-DSA signed program with no BPF-side work.
+ */
+static void mldsa_signed_load(void)
+{
+ char dir_tmpl[] = "/tmp/bpfmldsaXXXXXX";
+ int map_fd = -1, prog_fd = -1, err;
+ __u8 *sig = NULL, *buf = NULL;
+ struct gen_loader_fixture f;
+ bool have_fixture = false;
+ __u32 sig_sz = 16384;
+ char *dir;
+
+ syscall(__NR_request_key, "keyring", "_uid.0", NULL,
+ KEY_SPEC_SESSION_KEYRING);
+ dir = mkdtemp(dir_tmpl);
+ if (!ASSERT_OK_PTR(dir, "mkdtemp"))
+ return;
+
+ err = run_setup("setup-mldsa", dir);
+ if (err == SETUP_SKIP) {
+ printf("%s:SKIP:openssl has no ML-DSA support (needs 3.5+)\n",
+ __func__);
+ test__skip();
+ genkey_dir_fini(dir);
+ return;
+ }
+ if (!ASSERT_OK(err, "verify_sig_setup setup-mldsa")) {
+ genkey_dir_fini(dir);
+ return;
+ }
+
+ sig = malloc(sig_sz);
+ if (!ASSERT_OK_PTR(sig, "sig buf"))
+ goto out;
+ have_fixture = true;
+ if (gen_loader_fixture_init(&f) != 0)
+ goto out;
+
+ 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);
+
+ /*
+ * ML-DSA hashes the message itself, but openssl before 4.0 cannot
+ * produce a CMS message without signedAttrs for it, and with those in
+ * play only SHA-512 is permitted for the messageDigest attribute.
+ */
+ if (!ASSERT_OK(sign_buf_digest(dir, buf, f.gopts.insns_sz + f.data_sz,
+ sig, &sig_sz, "sha512"),
+ "sign insns||metadata with ML-DSA"))
+ goto out;
+
+ /*
+ * Guard against the setup silently handing back some other key type:
+ * an RSA or ECDSA signature is a few hundred bytes, where an ML-DSA-87
+ * one cannot be smaller than the raw signature it carries.
+ */
+ ASSERT_GT(sig_sz, MLDSA87_SIGNATURE_SIZE, "ML-DSA-87 signature size");
+
+ map_fd = setup_meta_map(&f);
+ if (!ASSERT_OK_FD(map_fd, "meta_map"))
+ goto out;
+ prog_fd = load_loader(f.gopts.insns, f.gopts.insns_sz, map_fd, sig,
+ sig_sz, KEY_SPEC_SESSION_KEYRING, 1);
+ ASSERT_OK_FD(prog_fd, "ML-DSA signed loader load");
+out:
+ if (prog_fd >= 0)
+ close(prog_fd);
+ if (map_fd >= 0)
+ close(map_fd);
+ if (have_fixture)
+ gen_loader_fixture_fini(&f);
+ free(buf);
+ free(sig);
+ run_setup("cleanup", dir);
+}
+
/*
* 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
@@ -1548,7 +1925,7 @@ static void signed_no_fd_array(void)
dir = mkdtemp(dir_tmpl);
if (!ASSERT_OK_PTR(dir, "mkdtemp"))
return;
- if (!ASSERT_OK(run_setup("setup", dir), "verify_sig_setup")) {
+ if (!ASSERT_OK(run_setup("setup-rsa", dir), "verify_sig_setup")) {
rmdir(dir);
return;
}
@@ -1619,7 +1996,7 @@ static void signed_map_by_fd_rejected(void)
dir = mkdtemp(dir_tmpl);
if (!ASSERT_OK_PTR(dir, "mkdtemp"))
goto out_map;
- if (!ASSERT_OK(run_setup("setup", dir), "verify_sig_setup")) {
+ if (!ASSERT_OK(run_setup("setup-rsa", dir), "verify_sig_setup")) {
rmdir(dir);
goto out_map;
}
@@ -1681,7 +2058,7 @@ static void signed_sparse_fd_array_rejected(void)
dir = mkdtemp(dir_tmpl);
if (!ASSERT_OK_PTR(dir, "mkdtemp"))
goto out_map;
- if (!ASSERT_OK(run_setup("setup", dir), "verify_sig_setup")) {
+ if (!ASSERT_OK(run_setup("setup-rsa", dir), "verify_sig_setup")) {
rmdir(dir);
goto out_map;
}
@@ -1735,7 +2112,7 @@ static void signed_module_kfunc_rejected(void)
dir = mkdtemp(dir_tmpl);
if (!ASSERT_OK_PTR(dir, "mkdtemp"))
return;
- if (!ASSERT_OK(run_setup("setup", dir), "verify_sig_setup")) {
+ if (!ASSERT_OK(run_setup("setup-rsa", dir), "verify_sig_setup")) {
rmdir(dir);
return;
}
@@ -1779,6 +2156,11 @@ static void signed_module_kfunc_rejected(void)
void test_signed_loader(void)
{
+ if (keyring_unsealed_boot()) {
+ if (test__start_subtest("bpf_keyring_provisioned"))
+ bpf_keyring_provisioned();
+ return;
+ }
if (test__start_subtest("loadtime_no_map"))
loadtime_no_map();
if (test__start_subtest("loadtime_with_map"))
@@ -1805,6 +2187,10 @@ void test_signed_loader(void)
signature_zero_size();
if (test__start_subtest("signature_bad_keyring"))
signature_bad_keyring();
+ if (test__start_subtest("bpf_keyring_sealed"))
+ bpf_keyring_sealed();
+ if (test__start_subtest("mldsa_signed_load"))
+ mldsa_signed_load();
if (test__start_subtest("metadata_ctx_max_entries_ignored"))
metadata_ctx_max_entries_ignored();
if (test__start_subtest("metadata_ctx_initial_value_ignored"))
diff --git a/tools/testing/selftests/bpf/prog_tests/verify_pkcs7_sig.c b/tools/testing/selftests/bpf/prog_tests/verify_pkcs7_sig.c
index f327feb8e38c3..12b146d205d75 100644
--- a/tools/testing/selftests/bpf/prog_tests/verify_pkcs7_sig.c
+++ b/tools/testing/selftests/bpf/prog_tests/verify_pkcs7_sig.c
@@ -257,7 +257,7 @@ static void test_verify_pkcs7_sig_from_map(void)
if (!ASSERT_OK_PTR(tmp_dir, "mkdtemp"))
return;
- ret = _run_setup_process(tmp_dir, "setup");
+ ret = _run_setup_process(tmp_dir, "setup-rsa");
if (!ASSERT_OK(ret, "_run_setup_process"))
goto close_prog;
@@ -458,7 +458,7 @@ static void test_pkcs7_sig_fsverity(void)
snprintf(data_path, PATH_MAX, "%s/data-file", tmp_dir);
snprintf(sig_path, PATH_MAX, "%s/sig-file", tmp_dir);
- ret = _run_setup_process(tmp_dir, "setup");
+ ret = _run_setup_process(tmp_dir, "setup-rsa");
if (!ASSERT_OK(ret, "_run_setup_process"))
goto out;
diff --git a/tools/testing/selftests/bpf/verify_sig_setup.sh b/tools/testing/selftests/bpf/verify_sig_setup.sh
index 09179fb551f09..2737c1a2bcfd6 100755
--- a/tools/testing/selftests/bpf/verify_sig_setup.sh
+++ b/tools/testing/selftests/bpf/verify_sig_setup.sh
@@ -28,7 +28,7 @@ authorityKeyIdentifier=keyid
usage()
{
- echo "Usage: $0 <setup|cleanup <existing_tmp_dir>"
+ echo "Usage: $0 <setup-rsa|setup-mldsa|cleanup <existing_tmp_dir>"
exit 1
}
@@ -47,7 +47,7 @@ genkey()
${tmp_dir}/signing_key.der -outform der
}
-setup()
+setup_rsa()
{
local tmp_dir="$1"
@@ -57,6 +57,57 @@ setup()
keyctl link $key_id $keyring_id
}
+mldsa_supported()
+{
+ local tmp_dir="$1"
+
+ genkey_mldsa "${tmp_dir}" || return 1
+ : > ${tmp_dir}/probe
+ # Same digest as the caller signs with, see sign_buf_digest().
+ ./sign-file -d sha512 ${tmp_dir}/signing_key.pem \
+ ${tmp_dir}/signing_key.pem ${tmp_dir}/probe || return 1
+ rm -f ${tmp_dir}/probe ${tmp_dir}/probe.p7s
+}
+
+genkey_mldsa()
+{
+ local tmp_dir="$1"
+
+ echo "${x509_genkey_content}" > ${tmp_dir}/x509.genkey
+
+ # No -<digest> here: ML-DSA hashes the message itself, and openssl
+ # rejects an explicit digest for it.
+ openssl req -new -nodes -utf8 -days 36500 \
+ -batch -x509 -newkey ML-DSA-87 \
+ -config ${tmp_dir}/x509.genkey \
+ -outform PEM -out ${tmp_dir}/signing_key.pem \
+ -keyout ${tmp_dir}/signing_key.pem 2>&1
+
+ openssl x509 -in ${tmp_dir}/signing_key.pem -out \
+ ${tmp_dir}/signing_key.der -outform der
+}
+
+mldsa_skip()
+{
+ local tmp_dir="$1"
+
+ rm -f ${tmp_dir}/x509.genkey ${tmp_dir}/signing_key.pem \
+ ${tmp_dir}/signing_key.der ${tmp_dir}/probe \
+ ${tmp_dir}/probe.p7s
+ exit 77
+}
+
+setup_mldsa()
+{
+ local tmp_dir="$1"
+
+ mldsa_supported "${tmp_dir}" || mldsa_skip "${tmp_dir}"
+ key_id=$(cat ${tmp_dir}/signing_key.der |
+ keyctl padd asymmetric ebpf_testing_key @s)
+ keyring_id=$(keyctl newring ebpf_testing_keyring @s)
+ keyctl link $key_id $keyring_id
+}
+
cleanup() {
local tmp_dir="$1"
@@ -91,7 +142,7 @@ catch()
local exit_code="$1"
local log_file="$2"
- if [[ "${exit_code}" -ne 0 ]]; then
+ if [[ "${exit_code}" -ne 0 && "${exit_code}" -ne 77 ]]; then
cat "${log_file}" >&3
fi
@@ -108,8 +159,10 @@ main()
[[ ! -d "${tmp_dir}" ]] && echo "Directory ${tmp_dir} doesn't exist" && exit 1
- if [[ "${action}" == "setup" ]]; then
- setup "${tmp_dir}"
+ if [[ "${action}" == "setup-rsa" ]]; then
+ setup_rsa "${tmp_dir}"
+ elif [[ "${action}" == "setup-mldsa" ]]; then
+ setup_mldsa "${tmp_dir}"
elif [[ "${action}" == "genkey" ]]; then
genkey "${tmp_dir}"
elif [[ "${action}" == "cleanup" ]]; then
diff --git a/tools/testing/selftests/bpf/vmtest.sh b/tools/testing/selftests/bpf/vmtest.sh
index 6a3d026d76bd6..e7e0b419a0b87 100755
--- a/tools/testing/selftests/bpf/vmtest.sh
+++ b/tools/testing/selftests/bpf/vmtest.sh
@@ -107,6 +107,14 @@ Options:
-s) Instead of powering off the VM, start an interactive
shell. If <command> is specified, the shell runs after
the command finishes executing
+
+Environment variables:
+
+ KERNEL_CMDLINE_EXTRA
+ Extra arguments to append to the guest kernel command
+ line, for tests that need a boot-time setting. e.g:
+
+ KERNEL_CMDLINE_EXTRA="bpf.keyring_unsealed=1" $0 -- ./test_progs -t signed_loader
EOF
}
@@ -286,6 +294,12 @@ EOF
QEMU_FLAGS=("${HOST_FLAGS[@]}")
fi
+ local kernel_cmdline="root=/dev/vda rw console=${QEMU_CONSOLE}"
+
+ if [[ -n "${KERNEL_CMDLINE_EXTRA:-}" ]]; then
+ kernel_cmdline+=" ${KERNEL_CMDLINE_EXTRA}"
+ fi
+
${QEMU_BINARY} \
-nodefaults \
-display none \
@@ -294,7 +308,7 @@ EOF
-m 4G \
-drive file="${rootfs_img}",format=raw,index=1,media=disk,if=virtio,cache=none \
-kernel "${kernel_bzimage}" \
- -append "root=/dev/vda rw console=${QEMU_CONSOLE}"
+ -append "${kernel_cmdline}"
}
copy_logs()
|
| 17/3 | 2026/08/25 14:56 | llm | kmsan-evaluator |
7mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 22746 output: 18 thoughts: 957 Thoughts: **KMSAN Fuzzing Session Assessment for BPF Keyring Patch** Okay, let's break this down. My initial assessment is that we need to determine if this patch series warrants a dedicated KMSAN fuzzing session. The patch introduces a new BPF keyring (`.bpf`) and a kernel parameter, `bpf.keyring_unsealed=1`. My primary concern is if this patch opens up any new memory safety vulnerabilities. Specifically, I need to understand if this introduces new user-space interactions or complex data structures where uninitialized fields could lead to memory corruption. Looking at the changes, especially in `kernel/bpf/verifier.c`, the core modification seems to revolve around BPF program signature verification against this new keyring. The code checks `attr->signature_size` and looks up keys using `bpf_lookup_system_key` or `bpf_lookup_user_key`, depending on the `keyring_id`. There's a new check for `VERIFY_USE_BPF_KEYRING` which calls `bpf_lookup_keyring`. Now, the line `bkey = kmalloc_obj(*bkey);` in `bpf_lookup_keyring` caught my eye. The `kmalloc_obj` seems unusual, not a standard kernel function. My first thought was this seems like something custom. I need to understand what this is. Let's see. This looks like `kmalloc_obj(*bkey)` which doesn't seem correct. Let me check the include files or other kernel files. Wait, let me just check. Oh wait, `kmalloc_obj` is not in the patch itself. The diff looks like it's saying `kmalloc_obj`. I'm going to search for `kmalloc_obj`. Let's confirm it's not a typo. Oh wait, I see it's right in the code. Let's make sure it's not a typo. Let's see... I'll check `include/linux/slab.h` or `kernel/bpf/keys.c`. Alright, let me use the grepper tool to search for `kmalloc_obj`. |
| 18/3 | 2026/08/25 15:04 | tool | grepper |
0mArgs:{
"Expression": "kmalloc_obj"
}Results: {
"Output": "\nFull output is too long, showing 500 out of 15562 lines.\nUse more precise expression if possible.\n\nDocumentation/RCU/Design/Requirements/Requirements.rst=203=non-\\ ``NULL``, locklessly accessing the ``-\u003ea`` and ``-\u003eb`` fields.\n--\nDocumentation/RCU/Design/Requirements/Requirements.rst-208- 2 {\nDocumentation/RCU/Design/Requirements/Requirements.rst:209: 3 p = kmalloc_obj(*p);\nDocumentation/RCU/Design/Requirements/Requirements.rst-210- 4 if (!p)\n--\nDocumentation/RCU/Design/Requirements/Requirements.rst=225=their rights to reorder this code as follows:\n--\nDocumentation/RCU/Design/Requirements/Requirements.rst-230- 2 {\nDocumentation/RCU/Design/Requirements/Requirements.rst:231: 3 p = kmalloc_obj(*p);\nDocumentation/RCU/Design/Requirements/Requirements.rst-232- 4 if (!p)\n--\nDocumentation/RCU/Design/Requirements/Requirements.rst=261=shows an example of insertion:\n--\nDocumentation/RCU/Design/Requirements/Requirements.rst-266- 2 {\nDocumentation/RCU/Design/Requirements/Requirements.rst:267: 3 p = kmalloc_obj(*p);\nDocumentation/RCU/Design/Requirements/Requirements.rst-268- 4 if (!p)\n--\nDocumentation/RCU/listRCU.rst=267=The RCU version of audit_upd_rule() is as follows::\n--\nDocumentation/RCU/listRCU.rst-278-\t\t\tif (!audit_compare_rule(rule, \u0026e-\u003erule)) {\nDocumentation/RCU/listRCU.rst:279:\t\t\t\tne = kmalloc_obj(*entry, GFP_ATOMIC);\nDocumentation/RCU/listRCU.rst-280-\t\t\t\tif (ne == NULL)\n--\nDocumentation/RCU/whatisRCU.rst=441=uses of RCU may be found in listRCU.rst and NMI-RCU.rst.\n--\nDocumentation/RCU/whatisRCU.rst-470-\nDocumentation/RCU/whatisRCU.rst:471:\t\tnew_fp = kmalloc_obj(*new_fp);\nDocumentation/RCU/whatisRCU.rst-472-\t\tspin_lock(\u0026foo_mutex);\n--\nDocumentation/RCU/whatisRCU.rst=553=The foo_update_a() function might then be written as follows::\n--\nDocumentation/RCU/whatisRCU.rst-572-\nDocumentation/RCU/whatisRCU.rst:573:\t\tnew_fp = kmalloc_obj(*new_fp);\nDocumentation/RCU/whatisRCU.rst-574-\t\tspin_lock(\u0026foo_mutex);\n--\nDocumentation/core-api/kref.rst=39=kref_init as so::\n--\nDocumentation/core-api/kref.rst-42-\nDocumentation/core-api/kref.rst:43: data = kmalloc_obj(*data);\nDocumentation/core-api/kref.rst-44- if (!data)\n--\nDocumentation/core-api/kref.rst=81=thread to process::\n--\nDocumentation/core-api/kref.rst-102-\tstruct task_struct *task;\nDocumentation/core-api/kref.rst:103:\tdata = kmalloc_obj(*data);\nDocumentation/core-api/kref.rst-104-\tif (!data)\n--\nDocumentation/kernel-hacking/locking.rst=383=to protect the cache and all the objects within it. Here's the code::\n--\nDocumentation/kernel-hacking/locking.rst-444-\nDocumentation/kernel-hacking/locking.rst:445: if ((obj = kmalloc_obj(*obj)) == NULL)\nDocumentation/kernel-hacking/locking.rst-446- return -ENOMEM;\n--\nDocumentation/kernel-hacking/locking.rst=499=which are taken away, and the ``+`` are lines which are added.\n--\nDocumentation/kernel-hacking/locking.rst-519-\nDocumentation/kernel-hacking/locking.rst:520: if ((obj = kmalloc_obj(*obj)) == NULL)\nDocumentation/kernel-hacking/locking.rst-521- return -ENOMEM;\n--\nDocumentation/locking/locktypes.rst=498=works perfectly::\n--\nDocumentation/locking/locktypes.rst-500- raw_spin_lock(\u0026lock);\nDocumentation/locking/locktypes.rst:501: p = kmalloc_obj(*p, GFP_ATOMIC);\nDocumentation/locking/locktypes.rst-502-\n--\nDocumentation/locking/locktypes.rst=507=preemption on PREEMPT_RT kernels::\n--\nDocumentation/locking/locktypes.rst-509- spin_lock(\u0026lock);\nDocumentation/locking/locktypes.rst:510: p = kmalloc_obj(*p, GFP_ATOMIC);\nDocumentation/locking/locktypes.rst-511-\n--\nDocumentation/process/coding-style.rst=938=The kernel provides the following general purpose memory allocators:\nDocumentation/process/coding-style.rst:939:kmalloc(), kzalloc(), kmalloc_objs(), kzalloc_objs(), vmalloc(), and\nDocumentation/process/coding-style.rst-940-vzalloc(). Please refer to the API documentation for further information\n--\nDocumentation/process/coding-style.rst=944=The preferred form for passing a size of a struct is the following:\n--\nDocumentation/process/coding-style.rst-947-\nDocumentation/process/coding-style.rst:948:\tp = kmalloc_obj(*p, ...);\nDocumentation/process/coding-style.rst-949-\n--\nDocumentation/process/coding-style.rst=958=The preferred form for allocating an array is the following:\n--\nDocumentation/process/coding-style.rst-961-\nDocumentation/process/coding-style.rst:962:\tp = kmalloc_objs(*p, n, ...);\nDocumentation/process/coding-style.rst-963-\n--\nDocumentation/process/deprecated.rst=386=may help with alignment, wrap-around, or additional hardening. The\nDocumentation/process/deprecated.rst:387:kmalloc_obj()-family of macros provide this introspection, which can be\nDocumentation/process/deprecated.rst-388-used for the common code patterns for single, array, and flexible object\n--\nDocumentation/process/deprecated.rst=398=become, respectively::\nDocumentation/process/deprecated.rst-399-\nDocumentation/process/deprecated.rst:400:\tptr = kmalloc_obj(*ptr [, gfp] );\nDocumentation/process/deprecated.rst-401-\tptr = kzalloc_obj(*ptr [, gfp] );\nDocumentation/process/deprecated.rst:402:\tptr = kmalloc_objs(*ptr, count [, gfp] );\nDocumentation/process/deprecated.rst-403-\tptr = kzalloc_objs(*ptr, count [, gfp] );\nDocumentation/process/deprecated.rst-404-\tptr = kmalloc_flex(*ptr, flex_member, count [, gfp] );\nDocumentation/process/deprecated.rst:405:\t__auto_type ptr = kmalloc_obj(struct foo [, gfp] );\nDocumentation/process/deprecated.rst-406-\n--\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst=1734=callback::\n--\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst-1739- ....\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst:1740: data = kmalloc_obj(*data);\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst-1741- substream-\u003eruntime-\u003eprivate_data = data;\n--\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst=3302=destructor function is set in the ``private_free`` field::\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst-3303-\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst:3304: struct mydata *p = kmalloc_obj(*p);\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst-3305- hw-\u003eprivate_data = p;\n--\nDocumentation/spi/spi-summary.rst=242=And SOC-specific utility code might look something like::\n--\nDocumentation/spi/spi-summary.rst-251-\nDocumentation/spi/spi-summary.rst:252:\t\tpdata2 = kmalloc_obj(*pdata2);\nDocumentation/spi/spi-summary.rst-253-\t\t*pdata2 = pdata;\n--\nDocumentation/translations/it_IT/kernel-hacking/locking.rst=403=e tutti gli oggetti che contiene. Ecco il codice::\n--\nDocumentation/translations/it_IT/kernel-hacking/locking.rst-464-\nDocumentation/translations/it_IT/kernel-hacking/locking.rst:465: if ((obj = kmalloc_obj(*obj)) == NULL)\nDocumentation/translations/it_IT/kernel-hacking/locking.rst-466- return -ENOMEM;\n--\nDocumentation/translations/it_IT/kernel-hacking/locking.rst=519=sono quelle rimosse, mentre quelle ``+`` sono quelle aggiunte.\n--\nDocumentation/translations/it_IT/kernel-hacking/locking.rst-539-\nDocumentation/translations/it_IT/kernel-hacking/locking.rst:540: if ((obj = kmalloc_obj(*obj)) == NULL)\nDocumentation/translations/it_IT/kernel-hacking/locking.rst-541- return -ENOMEM;\n--\nDocumentation/translations/it_IT/locking/locktypes.rst=488=memoria. Su un kernel non-PREEMPT_RT il seguente codice funziona perfettamente::\n--\nDocumentation/translations/it_IT/locking/locktypes.rst-490- raw_spin_lock(\u0026lock);\nDocumentation/translations/it_IT/locking/locktypes.rst:491: p = kmalloc_obj(*p, GFP_ATOMIC);\nDocumentation/translations/it_IT/locking/locktypes.rst-492-\n--\nDocumentation/translations/it_IT/locking/locktypes.rst=497=PREEMPT_RT::\n--\nDocumentation/translations/it_IT/locking/locktypes.rst-499- spin_lock(\u0026lock);\nDocumentation/translations/it_IT/locking/locktypes.rst:500: p = kmalloc_obj(*p, GFP_ATOMIC);\nDocumentation/translations/it_IT/locking/locktypes.rst-501-\n--\nDocumentation/translations/it_IT/process/coding-style.rst=942=Il modo preferito per passare la dimensione di una struttura è il seguente:\n--\nDocumentation/translations/it_IT/process/coding-style.rst-945-\nDocumentation/translations/it_IT/process/coding-style.rst:946:\tp = kmalloc_obj(*p, ...);\nDocumentation/translations/it_IT/process/coding-style.rst-947-\n--\nDocumentation/translations/sp_SP/process/coding-style.rst=954=La forma preferida para pasar el tamaño de una estructura es la siguiente:\n--\nDocumentation/translations/sp_SP/process/coding-style.rst-957-\nDocumentation/translations/sp_SP/process/coding-style.rst:958:\tp = kmalloc_obj(*p, ...);\nDocumentation/translations/sp_SP/process/coding-style.rst-959-\n--\nDocumentation/translations/zh_CN/core-api/kref.rst=46=kref可以出现在数据结构体中的任何地方。\n--\nDocumentation/translations/zh_CN/core-api/kref.rst-54-\nDocumentation/translations/zh_CN/core-api/kref.rst:55: data = kmalloc_obj(*data);\nDocumentation/translations/zh_CN/core-api/kref.rst-56- if (!data)\n--\nDocumentation/translations/zh_CN/core-api/kref.rst=62=Kref规则\n--\nDocumentation/translations/zh_CN/core-api/kref.rst-108-\tstruct task_struct *task;\nDocumentation/translations/zh_CN/core-api/kref.rst:109:\tdata = kmalloc_obj(*data);\nDocumentation/translations/zh_CN/core-api/kref.rst-110-\tif (!data)\n--\nDocumentation/translations/zh_CN/process/coding-style.rst=810=Documentation/translations/zh_CN/core-api/memory-allocation.rst 。\n--\nDocumentation/translations/zh_CN/process/coding-style.rst-815-\nDocumentation/translations/zh_CN/process/coding-style.rst:816:\tp = kmalloc_obj(*p, ...);\nDocumentation/translations/zh_CN/process/coding-style.rst-817-\n--\nDocumentation/translations/zh_TW/process/coding-style.rst=824=Documentation/translations/zh_CN/core-api/memory-allocation.rst 。\n--\nDocumentation/translations/zh_TW/process/coding-style.rst-829-\nDocumentation/translations/zh_TW/process/coding-style.rst:830:\tp = kmalloc_obj(*p, ...);\nDocumentation/translations/zh_TW/process/coding-style.rst-831-\n--\narch/alpha/kernel/core_marvel.c=857=marvel_agp_setup(alpha_agp_info *agp)\n--\narch/alpha/kernel/core_marvel.c-863-\narch/alpha/kernel/core_marvel.c:864:\taper = kmalloc_obj(*aper);\narch/alpha/kernel/core_marvel.c-865-\tif (aper == NULL) return -ENOMEM;\n--\narch/alpha/kernel/core_marvel.c=1019=marvel_agp_info(void)\n--\narch/alpha/kernel/core_marvel.c-1061-\t */\narch/alpha/kernel/core_marvel.c:1062:\tagp = kmalloc_obj(*agp);\narch/alpha/kernel/core_marvel.c-1063-\tif (!agp)\n--\narch/alpha/kernel/core_titan.c=590=titan_agp_setup(alpha_agp_info *agp)\n--\narch/alpha/kernel/core_titan.c-596-\narch/alpha/kernel/core_titan.c:597:\taper = kmalloc_obj(struct titan_agp_aperture);\narch/alpha/kernel/core_titan.c-598-\tif (aper == NULL)\n--\narch/alpha/kernel/core_titan.c=731=titan_agp_info(void)\n--\narch/alpha/kernel/core_titan.c-762-\t */\narch/alpha/kernel/core_titan.c:763:\tagp = kmalloc_obj(*agp);\narch/alpha/kernel/core_titan.c-764-\tif (!agp)\n--\narch/alpha/kernel/module.c=29=process_reloc_for_got(Elf64_Rela *rela,\n--\narch/alpha/kernel/module.c-48-\narch/alpha/kernel/module.c:49:\tg = kmalloc_obj(*g);\narch/alpha/kernel/module.c-50-\tg-\u003enext = chains[r_sym].next;\n--\narch/alpha/kernel/pci.c=211=static void pdev_save_srm_config(struct pci_dev *dev)\n--\narch/alpha/kernel/pci.c-223-\narch/alpha/kernel/pci.c:224:\ttmp = kmalloc_obj(*tmp);\narch/alpha/kernel/pci.c-225-\tif (!tmp) {\n--\narch/arc/kernel/unwind.c=359=void *unwind_add_table(struct module *module, const void *table_start,\n--\narch/arc/kernel/unwind.c-368-\narch/arc/kernel/unwind.c:369:\ttable = kmalloc_obj(*table);\narch/arc/kernel/unwind.c-370-\tif (!table)\n--\narch/arm/common/locomo.c=274=static int locomo_suspend(struct platform_device *dev, pm_message_t state)\n--\narch/arm/common/locomo.c-279-\narch/arm/common/locomo.c:280:\tsave = kmalloc_obj(struct locomo_save_data);\narch/arm/common/locomo.c-281-\tif (!save)\n--\narch/arm/common/sa1111.c=964=static int sa1111_suspend_noirq(struct device *dev)\n--\narch/arm/common/sa1111.c-971-\narch/arm/common/sa1111.c:972:\tsave = kmalloc_obj(struct sa1111_save_data);\narch/arm/common/sa1111.c-973-\tif (!save)\n--\narch/arm/kernel/unwind.c=572=struct unwind_table *unwind_table_add(unsigned long start, unsigned long size,\n--\narch/arm/kernel/unwind.c-576-\tunsigned long flags;\narch/arm/kernel/unwind.c:577:\tstruct unwind_table *tab = kmalloc_obj(*tab);\narch/arm/kernel/unwind.c-578-\n--\narch/arm/mach-omap2/omap-iommu.c=53=static struct powerdomain *_get_pwrdm(struct device *dev)\n--\narch/arm/mach-omap2/omap-iommu.c-101-\narch/arm/mach-omap2/omap-iommu.c:102:\tentry = kmalloc_obj(*entry);\narch/arm/mach-omap2/omap-iommu.c-103-\tif (entry) {\n--\narch/arm/mach-omap2/pm34xx.c=406=static int __init pwrdms_setup(struct powerdomain *pwrdm, void *unused)\n--\narch/arm/mach-omap2/pm34xx.c-412-\narch/arm/mach-omap2/pm34xx.c:413:\tpwrst = kmalloc_obj(struct power_state, GFP_ATOMIC);\narch/arm/mach-omap2/pm34xx.c-414-\tif (!pwrst)\n--\narch/arm/mach-omap2/pm44xx.c=113=static int __init pwrdms_setup(struct powerdomain *pwrdm, void *unused)\n--\narch/arm/mach-omap2/pm44xx.c-134-\narch/arm/mach-omap2/pm44xx.c:135:\tpwrst = kmalloc_obj(struct power_state, GFP_ATOMIC);\narch/arm/mach-omap2/pm44xx.c-136-\tif (!pwrst)\n--\narch/arm/mm/pgd.c-19-#ifdef CONFIG_ARM_LPAE\narch/arm/mm/pgd.c:20:#define _pgd_alloc(mm)\t\tkmalloc_objs(pgd_t, PTRS_PER_PGD, GFP_KERNEL | __GFP_ZERO)\narch/arm/mm/pgd.c-21-#define _pgd_free(mm, pgd)\tkfree(pgd)\n--\narch/arm/probes/kprobes/test-core.c=764=static int coverage_start(const union decode_item *table)\narch/arm/probes/kprobes/test-core.c-765-{\narch/arm/probes/kprobes/test-core.c:766:\tcoverage.base = kmalloc_objs(struct coverage_entry,\narch/arm/probes/kprobes/test-core.c-767-\t\t\t\t MAX_COVERAGE_ENTRIES);\n--\narch/arm64/kvm/pmu-emul.c=774=void kvm_host_pmu_init(struct arm_pmu *pmu)\n--\narch/arm64/kvm/pmu-emul.c-786-\narch/arm64/kvm/pmu-emul.c:787:\tentry = kmalloc_obj(*entry);\narch/arm64/kvm/pmu-emul.c-788-\tif (!entry)\n--\narch/arm64/kvm/vgic/vgic-debug.c=102=static void *vgic_debug_start(struct seq_file *s, loff_t *pos)\n--\narch/arm64/kvm/vgic/vgic-debug.c-106-\narch/arm64/kvm/vgic/vgic-debug.c:107:\titer = kmalloc_obj(*iter);\narch/arm64/kvm/vgic/vgic-debug.c-108-\tif (!iter)\n--\narch/arm64/kvm/vgic/vgic-debug.c=364=static void *vgic_its_debug_start(struct seq_file *s, loff_t *pos)\n--\narch/arm64/kvm/vgic/vgic-debug.c-377-\narch/arm64/kvm/vgic/vgic-debug.c:378:\titer = kmalloc_obj(*iter);\narch/arm64/kvm/vgic/vgic-debug.c-379-\tif (!iter)\n--\narch/arm64/kvm/vgic/vgic-init.c=743=void __init vgic_set_kvm_info(const struct gic_kvm_info *info)\n--\narch/arm64/kvm/vgic/vgic-init.c-745-\tBUG_ON(gic_kvm_info != NULL);\narch/arm64/kvm/vgic/vgic-init.c:746:\tgic_kvm_info = kmalloc_obj(*gic_kvm_info);\narch/arm64/kvm/vgic/vgic-init.c-747-\tif (gic_kvm_info)\n--\narch/m68k/emu/nfblock.c=97=static int __init nfhd_init_one(int id, u32 blocks, u32 bsize)\n--\narch/m68k/emu/nfblock.c-114-\narch/m68k/emu/nfblock.c:115:\tdev = kmalloc_obj(struct nfhd_device);\narch/m68k/emu/nfblock.c-116-\tif (!dev)\n--\narch/m68k/mm/kmap.c=108=static struct vm_struct *get_io_area(unsigned long size)\n--\narch/m68k/mm/kmap.c-112-\narch/m68k/mm/kmap.c:113:\tarea = kmalloc_obj(*area);\narch/m68k/mm/kmap.c-114-\tif (!area)\n--\narch/mips/alchemy/common/dbdma.c=253=u32 au1xxx_dbdma_chan_alloc(u32 srcid, u32 destid,\n--\narch/mips/alchemy/common/dbdma.c-312-\t\t\t */\narch/mips/alchemy/common/dbdma.c:313:\t\t\tctp = kmalloc_obj(chan_tab_t, GFP_ATOMIC);\narch/mips/alchemy/common/dbdma.c-314-\t\t\tchan_tab_ptr[i] = ctp;\n--\narch/mips/alchemy/common/dbdma.c=391=u32 au1xxx_dbdma_ring_alloc(u32 chanid, int entries)\n--\narch/mips/alchemy/common/dbdma.c-414-\t */\narch/mips/alchemy/common/dbdma.c:415:\tdesc_base = (u32) kmalloc_objs(au1x_ddma_desc_t, entries,\narch/mips/alchemy/common/dbdma.c-416-\t\t\t\t GFP_KERNEL | GFP_DMA);\n--\narch/mips/kernel/module.c=59=static int apply_r_mips_hi16(struct module *me, u32 *location, Elf_Addr v,\n--\narch/mips/kernel/module.c-74-\t */\narch/mips/kernel/module.c:75:\tn = kmalloc_obj(*n);\narch/mips/kernel/module.c-76-\tif (!n)\n--\narch/mips/kernel/vpe.c=311=static int apply_r_mips_hi16(struct module *me, uint32_t *location,\n--\narch/mips/kernel/vpe.c-320-\t */\narch/mips/kernel/vpe.c:321:\tn = kmalloc_obj(*n);\narch/mips/kernel/vpe.c-322-\tif (!n)\n--\narch/parisc/kernel/inventory.c=188=pat_query_module(ulong pcell_loc, ulong mod_index)\n--\narch/parisc/kernel/inventory.c-195-\narch/parisc/kernel/inventory.c:196:\tpa_pdc_cell = kmalloc_obj(*pa_pdc_cell);\narch/parisc/kernel/inventory.c-197-\tif (!pa_pdc_cell)\n--\narch/parisc/kernel/inventory.c=532=add_system_map_addresses(struct parisc_device *dev, int num_addrs, \n--\narch/parisc/kernel/inventory.c-538-\narch/parisc/kernel/inventory.c:539:\tdev-\u003eaddr = kmalloc_objs(*dev-\u003eaddr, num_addrs);\narch/parisc/kernel/inventory.c-540-\tif(!dev-\u003eaddr) {\n--\narch/parisc/kernel/processor.c=81=static int __init processor_probe(struct parisc_device *dev)\n--\narch/parisc/kernel/processor.c-112-\narch/parisc/kernel/processor.c:113:\t\tpa_pdc_cell = kmalloc_obj(*pa_pdc_cell);\narch/parisc/kernel/processor.c-114-\t\tif (!pa_pdc_cell)\n--\narch/parisc/kernel/unwind.c=149=unwind_table_add(const char *name, unsigned long base_addr, \n--\narch/parisc/kernel/unwind.c-159-\narch/parisc/kernel/unwind.c:160:\ttable = kmalloc_obj(struct unwind_table, GFP_USER);\narch/parisc/kernel/unwind.c-161-\tif (table == NULL)\n--\narch/parisc/kernel/unwind.c=406=void unwind_frame_init_from_blocked_task(struct unwind_frame_info *info, struct task_struct *t)\n--\narch/parisc/kernel/unwind.c-410-\narch/parisc/kernel/unwind.c:411:\tr2 = kmalloc_obj(struct pt_regs, GFP_ATOMIC);\narch/parisc/kernel/unwind.c-412-\tif (!r2)\n--\narch/powerpc/kernel/nvram_64.c=984=int __init nvram_scan_partitions(void)\n--\narch/powerpc/kernel/nvram_64.c-1032-\t\t}\narch/powerpc/kernel/nvram_64.c:1033:\t\ttmp_part = kmalloc_obj(*tmp_part);\narch/powerpc/kernel/nvram_64.c-1034-\t\terr = -ENOMEM;\n--\narch/powerpc/kvm/e500_mmu.c=731=int kvm_vcpu_ioctl_config_tlb(struct kvm_vcpu *vcpu,\n--\narch/powerpc/kvm/e500_mmu.c-774-\t\t cfg-\u003earray / PAGE_SIZE;\narch/powerpc/kvm/e500_mmu.c:775:\tpages = kmalloc_objs(*pages, num_pages);\narch/powerpc/kvm/e500_mmu.c-776-\tif (!pages)\n--\narch/powerpc/kvm/e500_mmu.c=898=int kvmppc_e500_tlb_init(struct kvmppc_vcpu_e500 *vcpu_e500)\n--\narch/powerpc/kvm/e500_mmu.c-914-\narch/powerpc/kvm/e500_mmu.c:915:\tvcpu_e500-\u003egtlb_arch = kmalloc_objs(*vcpu_e500-\u003egtlb_arch,\narch/powerpc/kvm/e500_mmu.c-916-\t\t\t\t\t KVM_E500_TLB0_SIZE + KVM_E500_TLB1_SIZE);\n--\narch/powerpc/lib/rheap.c=45=static int grow(rh_info_t * info, int max_blocks)\n--\narch/powerpc/lib/rheap.c-56-\narch/powerpc/lib/rheap.c:57:\tblock = kmalloc_objs(rh_block_t, max_blocks, GFP_ATOMIC);\narch/powerpc/lib/rheap.c-58-\tif (block == NULL)\n--\narch/powerpc/lib/rheap.c=253=rh_info_t *rh_create(unsigned int alignment)\n--\narch/powerpc/lib/rheap.c-260-\narch/powerpc/lib/rheap.c:261:\tinfo = kmalloc_obj(*info, GFP_ATOMIC);\narch/powerpc/lib/rheap.c-262-\tif (info == NULL)\n--\narch/powerpc/mm/book3s64/mmu_context.c=95=static int hash__init_new_context(struct mm_struct *mm)\n--\narch/powerpc/mm/book3s64/mmu_context.c-98-\narch/powerpc/mm/book3s64/mmu_context.c:99:\tmm-\u003econtext.hash_context = kmalloc_obj(struct hash_mm_context);\narch/powerpc/mm/book3s64/mmu_context.c-100-\tif (!mm-\u003econtext.hash_context)\n--\narch/powerpc/mm/book3s64/mmu_context.c-125-\t\tif (current-\u003emm-\u003econtext.hash_context-\u003espt) {\narch/powerpc/mm/book3s64/mmu_context.c:126:\t\t\tmm-\u003econtext.hash_context-\u003espt = kmalloc_obj(struct subpage_prot_table);\narch/powerpc/mm/book3s64/mmu_context.c-127-\t\t\tif (!mm-\u003econtext.hash_context-\u003espt) {\n--\narch/powerpc/perf/hv-24x7.c=623=static int event_uniq_add(struct rb_root *root, const char *name, int nl,\n--\narch/powerpc/perf/hv-24x7.c-650-\narch/powerpc/perf/hv-24x7.c:651:\tdata = kmalloc_obj(*data);\narch/powerpc/perf/hv-24x7.c-652-\tif (!data)\n--\narch/powerpc/perf/hv-24x7.c=755=static int create_events_from_catalog(struct attribute ***events_,\n--\narch/powerpc/perf/hv-24x7.c-908-\narch/powerpc/perf/hv-24x7.c:909:\tevents = kmalloc_objs(*events, attr_max + 1);\narch/powerpc/perf/hv-24x7.c-910-\tif (!events) {\n--\narch/powerpc/perf/hv-24x7.c-914-\narch/powerpc/perf/hv-24x7.c:915:\tevent_descs = kmalloc_objs(*event_descs, event_idx + 1);\narch/powerpc/perf/hv-24x7.c-916-\tif (!event_descs) {\n--\narch/powerpc/perf/hv-24x7.c-920-\narch/powerpc/perf/hv-24x7.c:921:\tevent_long_descs = kmalloc_objs(*event_long_descs, event_idx + 1);\narch/powerpc/perf/hv-24x7.c-922-\tif (!event_long_descs) {\n--\narch/powerpc/platforms/44x/hsta_msi.c=122=static int hsta_msi_probe(struct platform_device *pdev)\n--\narch/powerpc/platforms/44x/hsta_msi.c-153-\narch/powerpc/platforms/44x/hsta_msi.c:154:\tppc4xx_hsta_msi.irq_map = kmalloc_objs(int, irq_count);\narch/powerpc/platforms/44x/hsta_msi.c-155-\tif (!ppc4xx_hsta_msi.irq_map) {\n--\narch/powerpc/platforms/cell/spufs/file.c=44=static int spufs_attr_open(struct inode *inode, struct file *file,\n--\narch/powerpc/platforms/cell/spufs/file.c-49-\narch/powerpc/platforms/cell/spufs/file.c:50:\tattr = kmalloc_obj(*attr);\narch/powerpc/platforms/cell/spufs/file.c-51-\tif (!attr)\n--\narch/powerpc/platforms/pseries/dlpar.c=628=void queue_hotplug_event(struct pseries_hp_errorlog *hp_errlog)\n--\narch/powerpc/platforms/pseries/dlpar.c-636-\narch/powerpc/platforms/pseries/dlpar.c:637:\twork = kmalloc_obj(struct pseries_hp_work, GFP_ATOMIC);\narch/powerpc/platforms/pseries/dlpar.c-638-\tif (work) {\n--\narch/powerpc/platforms/pseries/hvcserver.c=119=int hvcs_get_partner_info(uint32_t unit_address, struct list_head *head,\n--\narch/powerpc/platforms/pseries/hvcserver.c-162-\t\t * hvcs_free_partner_info(). */\narch/powerpc/platforms/pseries/hvcserver.c:163:\t\tnext_partner_info = kmalloc_obj(struct hvcs_partner_info,\narch/powerpc/platforms/pseries/hvcserver.c-164-\t\t\t\t\t\tGFP_ATOMIC);\n--\narch/powerpc/platforms/pseries/lparcfg.c=144=static void show_gpci_data(struct seq_file *m)\n--\narch/powerpc/platforms/pseries/lparcfg.c-149-\narch/powerpc/platforms/pseries/lparcfg.c:150:\tbuf = kmalloc_obj(*buf);\narch/powerpc/platforms/pseries/lparcfg.c-151-\tif (buf == NULL)\n--\narch/powerpc/platforms/pseries/msi.c=435=static int pseries_msi_ops_prepare(struct irq_domain *domain, struct device *dev,\n--\narch/powerpc/platforms/pseries/msi.c-443-\tstruct pseries_msi_device *pseries_dev __free(kfree)\narch/powerpc/platforms/pseries/msi.c:444:\t\t= kmalloc_obj(*pseries_dev);\narch/powerpc/platforms/pseries/msi.c-445-\tif (!pseries_dev)\n--\narch/powerpc/platforms/pseries/pci.c=120=static int pseries_pci_sriov_enable(struct pci_dev *pdev, u16 num_vfs)\n--\narch/powerpc/platforms/pseries/pci.c-143-\tpdn = pci_get_pdn(pdev);\narch/powerpc/platforms/pseries/pci.c:144:\tpdn-\u003epe_num_map = kmalloc_objs(*pdn-\u003epe_num_map, num_vfs);\narch/powerpc/platforms/pseries/pci.c-145-\tif (!pdn-\u003epe_num_map)\n--\narch/powerpc/platforms/pseries/vas.c=1076=static int __init pseries_vas_init(void)\n--\narch/powerpc/platforms/pseries/vas.c-1089-\narch/powerpc/platforms/pseries/vas.c:1090:\thv_caps = kmalloc_obj(*hv_caps);\narch/powerpc/platforms/pseries/vas.c-1091-\tif (!hv_caps)\n--\narch/powerpc/platforms/pseries/vio.c=705=static int vio_cmo_bus_probe(struct vio_dev *viodev)\n--\narch/powerpc/platforms/pseries/vio.c-747-\narch/powerpc/platforms/pseries/vio.c:748:\t\tdev_ent = kmalloc_obj(struct vio_cmo_dev_entry);\narch/powerpc/platforms/pseries/vio.c-749-\t\tif (!dev_ent)\n--\narch/powerpc/sysdev/fsl_lbc.c=352=static int fsl_lbc_syscore_suspend(void *data)\n--\narch/powerpc/sysdev/fsl_lbc.c-364-\narch/powerpc/sysdev/fsl_lbc.c:365:\tctrl-\u003esaved_regs = kmalloc_obj(struct fsl_lbc_regs);\narch/powerpc/sysdev/fsl_lbc.c-366-\tif (!ctrl-\u003esaved_regs)\n--\narch/powerpc/sysdev/mpic.c=1549=void __init mpic_init(struct mpic *mpic)\n--\narch/powerpc/sysdev/mpic.c-1641-\t/* allocate memory to save mpic state */\narch/powerpc/sysdev/mpic.c:1642:\tmpic-\u003esave_data = kmalloc_objs(*mpic-\u003esave_data, mpic-\u003enum_sources);\narch/powerpc/sysdev/mpic.c-1643-\tBUG_ON(mpic-\u003esave_data == NULL);\n--\narch/powerpc/sysdev/of_rtc.c=23=void __init of_instantiate_rtc(void)\n--\narch/powerpc/sysdev/of_rtc.c-35-\narch/powerpc/sysdev/of_rtc.c:36:\t\t\tres = kmalloc_obj(*res);\narch/powerpc/sysdev/of_rtc.c-37-\t\t\tif (!res) {\n--\narch/riscv/kernel/module.c=654=static int add_relocation_to_accumulate(struct module *me, int type,\n--\narch/riscv/kernel/module.c-665-\narch/riscv/kernel/module.c:666:\tentry = kmalloc_obj(*entry);\narch/riscv/kernel/module.c-667-\n--\narch/riscv/kernel/module.c-699-\tif (!found) {\narch/riscv/kernel/module.c:700:\t\trel_head = kmalloc_obj(*rel_head);\narch/riscv/kernel/module.c-701-\n--\narch/riscv/kernel/module.c-711-\t\t\tbucket =\narch/riscv/kernel/module.c:712:\t\t\t\tkmalloc_obj(struct used_bucket);\n\n"
} |
| 19/3 | 2026/08/25 15:04 | llm | kmsan-evaluator |
7mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 34527 output: 20 thoughts: 369 Thoughts: **Analyzing the bpf_lookup_keyring Patch**
Okay, so `kmalloc_obj`. My initial thought is this is probably a macro, most likely defined somewhere in the kernel's `include/linux/slab.h` directory, or a similar header. It's almost certainly a wrapper around `kmalloc(sizeof(*bkey), GFP_KERNEL)`. Just a standard memory allocation.
Right, the patch uses `kmalloc_obj(*bkey)`, which is purely allocation. Nothing fancy. My immediate concern, though, is whether the patch inadvertently introduces uninitialized memory being exposed to user space. I don't see any obvious direct path here, but let's be thorough.
Let's dive into `bpf_lookup_keyring`:
```c
+struct bpf_key *bpf_lookup_keyring(void)
+{
+ struct bpf_key *bkey;
+
+ if (!bpf_keyring)
+ return NULL;
+ if (!READ_ONCE(bpf_keyring->keys.nr_leaves_on_tree) ||
+ !READ_ONCE(bpf_keyring->restrict_link))
+ return NULL;
+
+ bkey = kmalloc_obj(*bkey);
+ if (!bkey)
+ return NULL;
+
+ bkey->key = bpf_keyring;
+ bkey->has_ref = false;
+ return bkey;
+}
```
Aha! Now this is where it gets interesting. `kmalloc_obj` allocates, but does it zero the memory? If it behaves like standard `kmalloc`, the allocated `bkey` *isn't* fully initialized at this point.
Okay, let me check the definition of `struct bpf_key` to understand the implications of potentially uninitialized memory. Let's look at `include/linux/bpf.h`.
|
| 20/3 | 2026/08/25 15:11 | tool | grepper |
0mArgs:{
"Expression": "struct bpf_key \\{"
}Results: {
"Output": "include/linux/bpf.h=3804=static inline int bpf_fd_reuseport_array_update_elem(struct bpf_map *map,\n--\ninclude/linux/bpf.h-3813-#ifdef CONFIG_KEYS\ninclude/linux/bpf.h:3814:struct bpf_key {\ninclude/linux/bpf.h-3815-\tstruct key *key;\n--\ntools/testing/selftests/bpf/progs/verifier_ref_tracking.c-25-\ntools/testing/selftests/bpf/progs/verifier_ref_tracking.c:26:struct bpf_key {} __attribute__((preserve_access_index));\ntools/testing/selftests/bpf/progs/verifier_ref_tracking.c-27-\n"
} |
| 21/3 | 2026/08/25 15:11 | llm | kmsan-evaluator |
1mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 35563 output: 37 thoughts: 26 Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience: **Diving into the `bpf_key` Structure** Okay, so I need to understand the specifics of `struct bpf_key`, and the best place to start, as always, is the authoritative source: `include/linux/bpf.h`. I need to get a clear picture of its members, their data types, and any relevant comments within the header file itself. This is crucial for whatever I'm working on – whether it's understanding existing BPF code, extending the kernel's BPF functionality, or maybe even debugging something related to key management within a BPF program. I'm looking for anything that might shed light on how keys are structured, how they're used, and any potential gotchas or optimizations related to them. Gotta make sure I'm not missing anything critical for my current task. |
| 22/3 | 2026/08/25 15:12 | tool | read-file |
0mArgs:{
"File": "include/linux/bpf.h",
"FirstLine": 3814,
"LineCount": 10
}Results: {
"Contents": "3814:\tstruct bpf_key {\n3815:\t\tstruct key *key;\n3816:\t\tbool has_ref;\n3817:\t};\n3818:\t#endif /* CONFIG_KEYS */\n3819:\t\n3820:\t#if defined(CONFIG_KEYS) \u0026\u0026 defined(CONFIG_BPF_SYSCALL)\n3821:\tstruct bpf_key *bpf_lookup_user_key(s32 serial, u64 flags);\n3822:\tstruct bpf_key *bpf_lookup_system_key(u64 id);\n3823:\tstruct bpf_key *bpf_lookup_keyring(void);\n"
} |
| 23/3 | 2026/08/25 15:12 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 36007 output: 38 thoughts: 165 Thoughts: **Analyzing the `bpf_key` Structure and its Usage** Okay, so I'm looking at this `struct bpf_key` and it's pretty straightforward. Just two fields: a pointer to a `struct key` called `key`, and a boolean `has_ref`. Right away, I notice the initialization in `bpf_lookup_keyring`: `bkey->key = bpf_keyring;` and `bkey->has_ref = false;`. Nothing unusual there. I'm thinking about potential alignment. There *might* be some padding between `key` which is 8 bytes, and `has_ref`, which is 1 byte, but since this `struct bpf_key` isn't exposed to user space, that's not a huge concern. It's used internally in `bpf_prog_verify_signature` and eventually cleaned up with `bpf_key_put`. That means there are no concerns about structure layout being interpreted in an environment that is less trustworthy than within the kernel itself. Now, let's dive into that `bpf_prog_verify_signature` function. I should check it out in `kernel/bpf/verifier.c`. Let's see how it's actually using this `bpf_key` structure and if there are any subtle interactions to be aware of. |
| 24/3 | 2026/08/25 15:12 | tool | read-file |
0mArgs:{
"File": "kernel/bpf/verifier.c",
"FirstLine": 21015,
"LineCount": 50
}Results: {
"Contents": "21015:\t\tstruct bpf_key *key = NULL;\n21016:\t\tvoid *sig, *data = NULL;\n21017:\t\tu32 map_cnt = env-\u003eused_map_cnt;\n21018:\t\tu32 i, off, insns_sz;\n21019:\t\tu64 data_sz;\n21020:\t\tint err = 0;\n21021:\t\n21022:\t\tif (!attr-\u003esignature_size ||\n21023:\t\t attr-\u003esignature_size \u003e BPF_PROG_MAX_SIGNATURE_SIZE)\n21024:\t\t\treturn -EINVAL;\n21025:\t\n21026:\t\tif (!system_keyring_id_check(attr-\u003ekeyring_id)) {\n21027:\t\t\tkey = bpf_lookup_system_key(attr-\u003ekeyring_id);\n21028:\t\t} else {\n21029:\t\t\tif (attr-\u003ekeyring_id != VERIFY_USE_BPF_KEYRING) {\n21030:\t\t\t\tif (bpf_keyring_enforced()) {\n21031:\t\t\t\t\tverbose(env, \"caller-supplied keyring refused, use bpf keyring\\n\");\n21032:\t\t\t\t\treturn -EPERM;\n21033:\t\t\t\t}\n21034:\t\t\t\tkey = bpf_lookup_user_key(attr-\u003ekeyring_id, 0);\n21035:\t\t\t} else {\n21036:\t\t\t\tkey = bpf_lookup_keyring();\n21037:\t\t\t}\n21038:\t\t}\n21039:\t\tif (!key) {\n21040:\t\t\tif (attr-\u003ekeyring_id == VERIFY_USE_BPF_KEYRING) {\n21041:\t\t\t\tverbose(env, \"the bpf keyring is empty or has not been restricted\\n\");\n21042:\t\t\t\treturn -ENOKEY;\n21043:\t\t\t} else {\n21044:\t\t\t\tverbose(env, \"cannot resolve signing keyring with keyring_id %d\\n\",\n21045:\t\t\t\t\tattr-\u003ekeyring_id);\n21046:\t\t\t\treturn -EINVAL;\n21047:\t\t\t}\n21048:\t\t}\n21049:\t\n21050:\t\tsig = kvmemdup_bpfptr(usig, attr-\u003esignature_size);\n21051:\t\tif (IS_ERR(sig)) {\n21052:\t\t\tbpf_key_put(key);\n21053:\t\t\treturn PTR_ERR(sig);\n21054:\t\t}\n21055:\t\n21056:\t\tinsns_sz = prog-\u003elen * sizeof(struct bpf_insn);\n21057:\t\tdata_sz = insns_sz;\n21058:\t\tfor (i = 0; i \u003c map_cnt; i++) {\n21059:\t\t\tstruct bpf_map *map = maps[i];\n21060:\t\n21061:\t\t\tif (map-\u003emap_type != BPF_MAP_TYPE_ARRAY ||\n21062:\t\t\t !map-\u003eops-\u003emap_direct_value_addr) {\n21063:\t\t\t\tverbose(env, \"signed program metadata map '%s' must be an array\\n\",\n21064:\t\t\t\t\tmap-\u003ename);\n"
} |
| 25/3 | 2026/08/25 15:12 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 37383 output: 38 thoughts: 324 Thoughts: **My Assessment of the Proposed Patch** Okay, let me break this down. From what I see, this patch is fairly contained – it’s not introducing any novel user-space data structures, which is a good starting point. The core seems to be centered around handling `VERIFY_USE_BPF_KEYRING` within `bpf_prog_verify_signature`. That's the key. I can immediately infer that the goal is to leverage a BPF keyring for signature verification, a reasonable architectural choice. So, it looks like it's building the groundwork for key management with `bpf_keyring_init`, and the `bpf_keyring_unsealed` module parameter is interesting – it provides control over keyring sealing, which likely has security implications. I need to keep that in mind. The addition of `bpf_lookup_keyring` raises a red flag. I want to check for potential uninitialized memory exposures, so I'll drill down on the memory allocation. The use of `kmalloc_obj`, which is functionally the same as `kmalloc`, to allocate the `struct bpf_key` is standard. It seems safe as the fields `key` and `has_ref` are initialized, but I'll make sure they stay initialized. Furthermore, since the `bpf_key` structure itself isn't directly exposed to user space, the risk seems minimal, but that's something to explicitly verify. The signature handling is where the core of the security lies. The signature is copied from user space via `kvmemdup_bpfptr`, which is good practice. I'm pleased to see that the `attr->signature_size` is validated against `BPF_PROG_MAX_SIGNATURE_SIZE` (64KB), a stricter bound than the old `KMALLOC_MAX_CACHE_SIZE`. This prevents excessive memory allocation. It then gets verified using `bpf_verify_pkcs7_signature`. The fact that it's using a PKCS#7 signature and it's being copied and validated suggests a solid, well-defined approach to signing BPF programs. But that function usage, I need to investigate that further. I should check how that function is being used to make sure there are no other potential flaws. Finally, the updates to `bpftool` for ML-DSA and CMS_NOATTR support are welcome, and the added selftests are a positive sign of robust development. Overall, it's a solid start. I want to do a code review of the `bpf_verify_pkcs7_signature` function itself and make sure there are no unexpected side effects. |
| 26/3 | 2026/08/25 15:13 | tool | read-file |
0mArgs:{
"File": "kernel/bpf/verifier.c",
"FirstLine": 21065,
"LineCount": 50
}Results: {
"Contents": "21065:\t\t\t\terr = -EINVAL;\n21066:\t\t\t\tgoto out;\n21067:\t\t\t}\n21068:\t\t\tif (!READ_ONCE(map-\u003efrozen)) {\n21069:\t\t\t\tverbose(env, \"signed program metadata map '%s' must be frozen\\n\",\n21070:\t\t\t\t\tmap-\u003ename);\n21071:\t\t\t\terr = -EPERM;\n21072:\t\t\t\tgoto out;\n21073:\t\t\t}\n21074:\t\t\tif (bpf_map_write_active(map)) {\n21075:\t\t\t\tverbose(env, \"signed program metadata map '%s' has active writers\\n\",\n21076:\t\t\t\t\tmap-\u003ename);\n21077:\t\t\t\terr = -EBUSY;\n21078:\t\t\t\tgoto out;\n21079:\t\t\t}\n21080:\t\t\tif (!map-\u003eexcl_prog_sha) {\n21081:\t\t\t\tverbose(env, \"signed program metadata map '%s' must be exclusive\\n\",\n21082:\t\t\t\t\tmap-\u003ename);\n21083:\t\t\t\terr = -EPERM;\n21084:\t\t\t\tgoto out;\n21085:\t\t\t}\n21086:\t\t\tdata_sz += map-\u003evalue_size;\n21087:\t\t}\n21088:\t\tif (bpf_dynptr_check_size(data_sz)) {\n21089:\t\t\tverbose(env, \"signed payload too large: %llu bytes\\n\", data_sz);\n21090:\t\t\terr = -E2BIG;\n21091:\t\t\tgoto out;\n21092:\t\t}\n21093:\t\tdata = kvmalloc(data_sz, GFP_KERNEL_ACCOUNT | __GFP_ZERO);\n21094:\t\tif (!data) {\n21095:\t\t\terr = -ENOMEM;\n21096:\t\t\tgoto out;\n21097:\t\t}\n21098:\t\tmemcpy(data, prog-\u003einsnsi, insns_sz);\n21099:\t\toff = insns_sz;\n21100:\t\tfor (i = 0; i \u003c map_cnt; i++) {\n21101:\t\t\tstruct bpf_map *map = maps[i];\n21102:\t\t\tu64 addr;\n21103:\t\n21104:\t\t\terr = map-\u003eops-\u003emap_direct_value_addr(map, \u0026addr, 0);\n21105:\t\t\tif (err) {\n21106:\t\t\t\tverbose(env, \"failed to read signed metadata map '%s': %d\\n\",\n21107:\t\t\t\t\tmap-\u003ename, err);\n21108:\t\t\t\tgoto out;\n21109:\t\t\t}\n21110:\t\t\tmemcpy(data + off, (void *)(unsigned long)addr,\n21111:\t\t\t map-\u003evalue_size);\n21112:\t\t\toff += map-\u003evalue_size;\n21113:\t\t}\n21114:\t\n"
} |
| 27/3 | 2026/08/25 15:13 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 39106 output: 21 thoughts: 1586 Thoughts: **My Assessment of the Patch Series: No KMSAN Fuzzing Needed** Okay, let's break this down. First, I allocated a `data` buffer using `kvmalloc` with `__GFP_ZERO`. This means it's fully zeroed right off the bat, and then I populate it with `memcpy`. Subsequently, I pass this memory to `bpf_verify_pkcs7_signature`. So, there's no way uninitialized memory is leaking there. Now, I checked the changes to `include/linux/bpf.h`, specifically the addition of `BPF_SIG_KEYRING_BPF` to the `enum bpf_sig_keyring`. That's a simple enum addition; no memory concerns there. Next, I looked at `bpf_classify_keyring` in `kernel/bpf/verifier.c`. This involves adding a case in a `switch` statement that classifies the new keyring. Again, no memory initialization issues are evident. Then I examined the `bpf_keyring_init` function in `kernel/bpf/keys.c`. This creates a new keyring and, if it's not sealed, it attempts to restrict it. This is where I have to be particularly careful. I checked, and `bpf_keyring_init` isn't leaking any uninitialized data. I continued with `bpf_keyring_enforced` and `bpf_lookup_keyring`, scrutinizing them for any potential uninitialized memory reads. But from what I can see, these functions only return pre-allocated structures, no uninitialized data is going to be leaking. I also checked the `bpftool` changes related to the CMS signature handling and the selftest changes. Those didn't present any immediate issues. Given that this patch series introduces a new keyring for BPF program signature verification but doesn't introduce any new structures sent to user space, or any complex data structures where uninitialized fields could be read later. I also made sure that the allocation of `bkey = kmalloc_obj(*bkey);` is initialized. Therefore, I'm confident that a dedicated KMSAN fuzzing session isn't needed. The standard bug detectors like KASAN and others, should be sufficient to catch any potential memory errors like out-of-bounds access or use-after-free issues. Let's just double check `kvmemdup_bpfptr` to make sure! |
| 28/3 | 2026/08/25 15:13 | tool | grepper |
0mArgs:{
"Expression": "kvmemdup_bpfptr"
}Results: {
"Output": "include/linux/bpfptr.h=62=static inline int copy_to_bpfptr_offset(bpfptr_t dst, size_t offset,\n--\ninclude/linux/bpfptr.h-67-\ninclude/linux/bpfptr.h:68:static inline void *kvmemdup_bpfptr_noprof(bpfptr_t src, size_t len)\ninclude/linux/bpfptr.h-69-{\n--\ninclude/linux/bpfptr.h-79-}\ninclude/linux/bpfptr.h:80:#define kvmemdup_bpfptr(...)\talloc_hooks(kvmemdup_bpfptr_noprof(__VA_ARGS__))\ninclude/linux/bpfptr.h-81-\n--\nkernel/bpf/syscall.c=1772=static void *___bpf_copy_key(bpfptr_t ukey, u64 key_size)\n--\nkernel/bpf/syscall.c-1774-\tif (key_size)\nkernel/bpf/syscall.c:1775:\t\treturn kvmemdup_bpfptr(ukey, key_size);\nkernel/bpf/syscall.c-1776-\n--\nkernel/bpf/syscall.c=1847=static int map_update_elem(union bpf_attr *attr, bpfptr_t uattr)\n--\nkernel/bpf/syscall.c-1879-\tvalue_size = bpf_map_value_size(map, attr-\u003eflags);\nkernel/bpf/syscall.c:1880:\tvalue = kvmemdup_bpfptr(uvalue, value_size);\nkernel/bpf/syscall.c-1881-\tif (IS_ERR(value)) {\n--\nkernel/bpf/verifier.c=21008=static int bpf_prog_verify_signature(struct bpf_verifier_env *env,\n--\nkernel/bpf/verifier.c-21049-\nkernel/bpf/verifier.c:21050:\tsig = kvmemdup_bpfptr(usig, attr-\u003esignature_size);\nkernel/bpf/verifier.c-21051-\tif (IS_ERR(sig)) {\n"
} |
| 29/3 | 2026/08/25 15:13 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 41806 output: 322 thoughts: 1173 Thoughts: **Analysis of the BPF Keyring Patch for Uninitialized Memory** Okay, let's break this down. The core concern is whether this patch introduces any new avenues for uninitialized memory to leak information or cause vulnerabilities. I'm going through the code changes methodically. First, `kvmemdup_bpfptr`: that one's solid. It's designed to copy data *from* userspace, so whatever memory it returns is guaranteed to be initialized with user-provided data. And if the copy fails, it frees the allocated memory, preventing any potential leak. Good. Now, `bpf_keyring_init`. This is called at `late_initcall`, creating the keyring. It allocates and initializes a keyring struct, and specifically restricts it if `bpf_keyring_unsealed` is false. No issues here; everything's properly initialized. Moving on to `bpf_lookup_keyring`. This is where a `struct bpf_key` is allocated using `kmalloc_obj`. Crucially, after the allocation, it explicitly initializes both `bkey->key` and `bkey->has_ref`. This is key: all the fields are initialized immediately after allocation, preventing any potential uninitialized memory reads. Then, the `bpf_prog_verify_signature` logic: This is just about selecting the right keyring. No memory allocation happens here, just branching logic to determine which keyring to use based on the `attr->keyring_id`. So, this section is also clean. And finally, `bpf_verify_pkcs7_signature` itself. This is *not* modified in the patch, so the patch doesn't impact this function directly, it just calls it. Therefore, after a careful review, I'm confident that this patch does *not* introduce any uninitialized memory risks. There are no allocations where fields are left uninitialized, and no new kernel structures are being copied to user space. It is a new BPF keyring (`.bpf`) for BPF program signature verification, including a kernel parameter to manage its state. The changes are straightforward and focus on keyring management and verification logic, without introducing any obvious sources of uninitialized memory. KMSAN isn't necessary here, as this patch does not pose a threat to the security and integrity of the system in terms of uninitialized memory. Standard bug detectors like KASAN should be more than sufficient to catch any issues related to this patch. The key allocations and copies are either from a known source, or fully zeroed. |
| 30/3 | 2026/08/25 15:13 | tool | set-results |
0mArgs:{
"KMSANReasoning": "The patch series introduces a new BPF keyring (`.bpf`) for BPF program signature verification, along with a kernel parameter `bpf.keyring_unsealed` to manage its state. The changes primarily involve adding a new keyring initialization function, adding a lookup function `bpf_lookup_keyring` which allocates a `struct bpf_key` and initializes its fields, and modifying `bpf_prog_verify_signature` to handle the new `VERIFY_USE_BPF_KEYRING` ID.\n\nNone of these changes introduce new kernel structures that are copied to user space, nor do they introduce complex data structures with potentially uninitialized fields. The only new kernel allocation is for `struct bpf_key` via `kmalloc_obj`, and all its fields (`key` and `has_ref`) are explicitly initialized immediately after allocation. The signature and data buffers used during verification are allocated with `kvmemdup_bpfptr` (which copies from user space) and `kvmalloc(..., __GFP_ZERO)` (which zero-initializes), respectively.\n\nAny potential bugs introduced by these changes, such as memory leaks, out-of-bounds accesses, or use-after-free issues, would be effectively caught by standard KASAN and other default bug detectors. There is no risk of uninitialized memory usage or info-leaks that would necessitate a dedicated KMSAN fuzzing session.",
"NeedsKMSAN": false
}Results: {
"KMSANReasoning": "The patch series introduces a new BPF keyring (`.bpf`) for BPF program signature verification, along with a kernel parameter `bpf.keyring_unsealed` to manage its state. The changes primarily involve adding a new keyring initialization function, adding a lookup function `bpf_lookup_keyring` which allocates a `struct bpf_key` and initializes its fields, and modifying `bpf_prog_verify_signature` to handle the new `VERIFY_USE_BPF_KEYRING` ID.\n\nNone of these changes introduce new kernel structures that are copied to user space, nor do they introduce complex data structures with potentially uninitialized fields. The only new kernel allocation is for `struct bpf_key` via `kmalloc_obj`, and all its fields (`key` and `has_ref`) are explicitly initialized immediately after allocation. The signature and data buffers used during verification are allocated with `kvmemdup_bpfptr` (which copies from user space) and `kvmalloc(..., __GFP_ZERO)` (which zero-initializes), respectively.\n\nAny potential bugs introduced by these changes, such as memory leaks, out-of-bounds accesses, or use-after-free issues, would be effectively caught by standard KASAN and other default bug detectors. There is no risk of uninitialized memory usage or info-leaks that would necessitate a dedicated KMSAN fuzzing session.",
"NeedsKMSAN": false
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|