Merge branch 'bpf-keyring-and-signed-loader-ml-dsa-support'

Daniel Borkmann says:

====================
BPF keyring and signed loader ML-DSA support

Add a dedicated BPF keyring which is integrated for BPF signing. It
is modelled after the dm-verity keyring which was added in commit
033724b ("dm-verity: add dm-verity keyring") and which can eventually
be used also via systemd through the same enrollment method as in
dm-verity's case. It is selected with a new well-known keyring_id
VERIFY_USE_BPF_KEYRING and gives an operator a place to enroll a
BPF-only signing key at boot, specifically scoped to signed BPF
program loading. Next, to demonstrate that BPF signing is algorithm
agnostic, we add a few small tooling changes to support ML-DSA and
integrate everything into BPF selftest for BPF CI to ensure nothing
breaks with future changes. For more details, see individual commits.

v3 -> v4:
  - Refactor the keyring dispatch a bit better wrt if/else chaining (BPF CI)
  - Improve selftest for bpf keyring in signed loader wrt skips (BPF CI)
  - selftests/bpf/config sorting (BPF CI)
  - Misc documentation improvement (BPF CI)
v2 -> v3:
  - Added KEY_SPEC_BPF_KEYRING rather than VERIFY_USE_BPF_KEYRING
    then it can also be looked up via lookup_user_key (Christian Brauner)
  - Added CMS_NO_SIGNING_TIME to bpftool patch (Sashiko)
v1 -> v2:
  - Mainly addressing LLM feedback in docs, commit message and
    selftests (BPF CI, sashiko)
    - Fix reasoning on BPF_PROG_MAX_SIGNATURE_SIZE
    - Add reasoning about bpftool MAX_SIG_SIZE change and pinning
      the CMS digest to SHA-512
    - Fix misc signing.rst phrasing
    - Fix a potential NULL deref in bpf_keyring_lookup selftest code
    - Add CONFIG_CRYPTO_SHA512=y to BPF selftest config
    - Clean up key material on ML-DSA signed loader test failure
    - Refactor duplicated bpf_attr setup for bpf keyring tests
    - Fix wording in kernel-parameters.txt
====================

Link: https://patch.msgid.link/20260828175227.1537793-1-daniel@iogearbox.net
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
Alexei Starovoitov 2026-08-29 18:17:51 -07:00
commit 776c86a3e7
17 changed files with 1003 additions and 158 deletions

View File

@ -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.

View File

@ -254,21 +254,24 @@ 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 or an explicit allowlist of a caller-supplied staging key/
keyring, and emit a record of every decision including denied attempts.
(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 +294,18 @@ 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_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 +319,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 +396,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 +406,158 @@ 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 resolved as a caller-
supplied key or keyring, named either by its serial or by one of the other
``KEY_SPEC_*`` ids:
.. 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
* - ``KEY_SPEC_BPF_KEYRING`` (``-9``)
- the bpf keyring
* - anything else
- 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
``KEY_SPEC_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
``KEY_SPEC_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, but the
``KEY_SPEC_BPF_KEYRING`` special key id addresses it directly, so its serial
does not have to be looked up first. Steps would be as follows::
keyctl padd asymmetric "" -9 < signing_key.der
keyctl restrict_keyring -9
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 "" -9 < $key
done
keyctl restrict_keyring -9
keyctl show -9
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 -9 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``. ``KEY_SPEC_BPF_KEYRING`` names the keyring but conveys no rights
of its own - the keyring is not possessed by anyone, so the ``KEY_POS_*`` bits
are only ever reached by the in-kernel search, and userspace is left with what
the ``KEY_USR_*`` bits grant. Once they are dropped, addressing it by the serial
``/proc/keys`` reports gets no further than the special id does.
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 +577,51 @@ 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)
* - ``KEY_SPEC_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 both
``CONFIG_CRYPTO_MLDSA`` and ``CONFIG_CRYPTO_SHA512`` in the kernel - the latter
for the signedAttrs digest described below, which ``CONFIG_CRYPTO_MLDSA`` does
not select. Note the absence of a digest option: ML-DSA hashes the message
itself, so openssl ignores 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 +630,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.
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.
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::
struct obj *skel = obj__open();
skel->keyring_id = KEY_SPEC_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 +687,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 +702,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 ``KEY_SPEC_BPF_KEYRING`` never resolves.

View File

@ -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)
{
}

View File

@ -440,6 +440,8 @@ extern key_ref_t keyring_search(key_ref_t keyring,
extern int keyring_restrict(key_ref_t keyring, const char *type,
const char *restriction);
extern void key_register_bpf_keyring(struct key *keyring);
extern struct key *key_lookup(key_serial_t id);
static inline key_serial_t key_serial(const struct key *key)

View File

@ -24,6 +24,7 @@
#define KEY_SPEC_GROUP_KEYRING -6 /* - key ID for GID-specific keyring */
#define KEY_SPEC_REQKEY_AUTH_KEY -7 /* - key ID for assumed request_key auth key */
#define KEY_SPEC_REQUESTOR_KEYRING -8 /* - key ID for request_key() dest keyring */
#define KEY_SPEC_BPF_KEYRING -9 /* - key ID for the BPF-specific keyring */
/* request-key default keyrings */
#define KEY_REQKEY_DEFL_NO_CHANGE -1

View File

@ -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

73
kernel/bpf/keys.c Normal file
View File

@ -0,0 +1,73 @@
// 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;
key_register_bpf_keyring(keyring);
return 0;
}
late_initcall(bpf_keyring_init);

View File

@ -24,6 +24,7 @@
#include <linux/bpf_lsm.h>
#include <linux/security.h>
#include <linux/verification.h>
#include <linux/keyctl.h>
#include <linux/btf_ids.h>
#include <linux/poison.h>
#include <linux/module.h>
@ -21118,6 +21119,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) {
@ -21127,6 +21136,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 KEY_SPEC_BPF_KEYRING:
return BPF_SIG_KEYRING_BPF;
default:
return BPF_SIG_KEYRING_USER;
}
@ -21155,18 +21166,25 @@ 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) == 0) {
key = bpf_lookup_system_key(attr->keyring_id);
else
} else if (attr->keyring_id == KEY_SPEC_BPF_KEYRING) {
key = bpf_lookup_keyring();
} else if (bpf_keyring_enforced()) {
verbose(env, "caller-supplied keyring refused, use bpf keyring\n");
return -EPERM;
} else {
key = bpf_lookup_user_key(attr->keyring_id, 0);
}
if (!key) {
if (attr->keyring_id == KEY_SPEC_BPF_KEYRING) {
verbose(env, "the bpf keyring is empty or has not been restricted\n");
return -ENOKEY;
}
verbose(env, "cannot resolve signing keyring with keyring_id %d\n",
attr->keyring_id);
return -EINVAL;

View File

@ -22,6 +22,9 @@
/* Session keyring create vs join semaphore */
static DEFINE_MUTEX(key_session_mutex);
/* BPF keyring reachable through KEY_SPEC_BPF_KEYRING */
static struct key *bpf_keyring __ro_after_init;
/* The root user's tracking struct */
struct key_user root_key_user = {
.usage = REFCOUNT_INIT(3),
@ -590,6 +593,20 @@ bool lookup_user_key_possessed(const struct key *key,
return key == match_data->raw_data;
}
/**
* key_register_bpf_keyring - Publish the BPF keyring for KEY_SPEC_BPF_KEYRING
* @keyring: The keyring to publish
*
* Make @keyring reachable by userspace through the KEY_SPEC_BPF_KEYRING
* special key ID, so that provisioning it does not require scraping its
* serial out of /proc/keys first. Called once, from an initcall, and never
* undone.
*/
void key_register_bpf_keyring(struct key *keyring)
{
bpf_keyring = keyring;
}
/*
* Look up a key ID given us by userspace with a given permissions mask to get
* the key it refers to.
@ -741,6 +758,14 @@ key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags,
key_ref = make_key_ref(key, 1);
break;
case KEY_SPEC_BPF_KEYRING:
key = bpf_keyring;
if (!key)
goto error;
__key_get(key);
key_ref = make_key_ref(key, 0);
break;
default:
key_ref = ERR_PTR(-EINVAL);
if (id < 1)

View File

@ -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"

View File

@ -130,6 +130,12 @@ __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 |
#ifdef CMS_NO_SIGNING_TIME
CMS_NO_SIGNING_TIME |
#endif
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 +173,20 @@ 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")) {
/*
* See workaround in 0ad9a71933e7 ("modsign: Enable ML-DSA
* module signing"). Kernel only accepts sha512, see also
* 8bbdeb7a25b4 ("pkcs7, x509: Add ML-DSA support").
*/
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 +195,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;
}

View File

@ -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; \

View File

@ -12,7 +12,9 @@ CONFIG_BPF_SYSCALL=y
# CONFIG_BPF_UNPRIV_DEFAULT_OFF is not set
CONFIG_CGROUP_BPF=y
CONFIG_CRYPTO_HMAC=y
CONFIG_CRYPTO_MLDSA=y
CONFIG_CRYPTO_SHA256=y
CONFIG_CRYPTO_SHA512=y
CONFIG_CRYPTO_USER_API=y
CONFIG_CRYPTO_USER_API_HASH=y
CONFIG_CRYPTO_USER_API_SKCIPHER=y

View File

@ -32,11 +32,22 @@ 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)
#ifndef KEY_SPEC_BPF_KEYRING
#define KEY_SPEC_BPF_KEYRING -9
#endif
/* 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 +59,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 +180,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 +218,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 +233,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 +243,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 +504,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 +576,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 +584,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 +604,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 +660,280 @@ 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) {
ASSERT_EQ(keyctl_ret(KEYCTL_GET_KEYRING_ID,
KEY_SPEC_BPF_KEYRING, 0), serial,
"KEY_SPEC_BPF_KEYRING resolves to the bpf keyring");
key = syscall(__NR_add_key, "user", "sealprobe", "x", 1,
KEY_SPEC_BPF_KEYRING);
if (key >= 0)
syscall(__NR_keyctl, KEYCTL_UNLINK, key,
KEY_SPEC_BPF_KEYRING);
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), KEY_SPEC_BPF_KEYRING, 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;
ASSERT_EQ(keyctl_ret(KEYCTL_GET_KEYRING_ID, KEY_SPEC_BPF_KEYRING, 0),
serial, "KEY_SPEC_BPF_KEYRING resolves to the bpf keyring");
err = syscall(__NR_add_key, "asymmetric", "", der, (size_t)der_sz,
KEY_SPEC_BPF_KEYRING);
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, KEY_SPEC_BPF_KEYRING, 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,
KEY_SPEC_BPF_KEYRING, NULL, NULL),
"restrict bpf keyring"))
goto out;
if (!ASSERT_OK_FD(try_load(&f, sig, sig_sz, KEY_SPEC_BPF_KEYRING,
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, KEY_SPEC_BPF_KEYRING, 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, KEY_SPEC_BPF_KEYRING, 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, KEY_SPEC_BPF_KEYRING, NULL, 0),
"load still verified against the locked keyring");
ASSERT_EQ(keyctl_ret(KEYCTL_GET_KEYRING_ID, KEY_SPEC_BPF_KEYRING, 0),
-EACCES, "the special id grants no rights of its own");
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 +1139,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 +1239,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 +1575,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 +1758,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 +1832,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:no working ML-DSA signing, set SELFTESTS_VERBOSE=1\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 +1939,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 +2010,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 +2072,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 +2126,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;
}
@ -1777,64 +2168,71 @@ static void signed_module_kfunc_rejected(void)
run_setup("cleanup", dir);
}
enum subtest_boot {
BOOT_ANY,
BOOT_SEALED,
BOOT_UNSEALED,
};
static const struct {
const char *name;
void (*fn)(void);
enum subtest_boot boot;
} subtests[] = {
{ "loadtime_no_map", loadtime_no_map, BOOT_SEALED },
{ "loadtime_with_map", loadtime_with_map, BOOT_SEALED },
{ "metadata_match", metadata_match, BOOT_ANY },
{ "signature_enforced", signature_enforced, BOOT_SEALED },
{ "signed_nonexcl_fd_array_rejected", signed_nonexcl_fd_array_rejected, BOOT_SEALED },
{ "signed_unfrozen_fd_array_rejected", signed_unfrozen_fd_array_rejected, BOOT_SEALED },
{ "signed_nonarray_fd_array_rejected", signed_nonarray_fd_array_rejected, BOOT_SEALED },
{ "signed_btf_fd_array_rejected", signed_btf_fd_array_rejected, BOOT_ANY },
{ "signed_module_kfunc_rejected", signed_module_kfunc_rejected, BOOT_SEALED },
{ "signature_failure_logs", signature_failure_logs, BOOT_SEALED },
{ "signature_too_large", signature_too_large, BOOT_ANY },
{ "signature_zero_size", signature_zero_size, BOOT_ANY },
{ "signature_bad_keyring", signature_bad_keyring, BOOT_SEALED },
{ "bpf_keyring_sealed", bpf_keyring_sealed, BOOT_ANY },
{ "mldsa_signed_load", mldsa_signed_load, BOOT_SEALED },
{ "metadata_ctx_max_entries_ignored", metadata_ctx_max_entries_ignored, BOOT_ANY },
{ "metadata_ctx_initial_value_ignored", metadata_ctx_initial_value_ignored, BOOT_ANY },
{ "signature_authenticates_insns", signature_authenticates_insns, BOOT_SEALED },
{ "signature_authenticates_metadata", signature_authenticates_metadata, BOOT_SEALED },
{ "hash_requires_frozen", hash_requires_frozen, BOOT_ANY },
{ "no_update_after_freeze", no_update_after_freeze, BOOT_ANY },
{ "freeze_writable_mmap", freeze_writable_mmap, BOOT_ANY },
{ "no_writable_mmap_frozen", no_writable_mmap_frozen, BOOT_ANY },
{ "map_hash_matches_libbpf", map_hash_matches_libbpf, BOOT_ANY },
{ "map_hash_multi_element", map_hash_multi_element, BOOT_ANY },
{ "map_hash_bad_size", map_hash_bad_size, BOOT_ANY },
{ "map_hash_unsupported_type", map_hash_unsupported_type, BOOT_ANY },
{ "lsm_signature_verdict", lsm_signature_verdict, BOOT_SEALED },
{ "signed_no_fd_array", signed_no_fd_array, BOOT_SEALED },
{ "signed_map_by_fd_rejected", signed_map_by_fd_rejected, BOOT_SEALED },
{ "signed_sparse_fd_array_rejected", signed_sparse_fd_array_rejected, BOOT_SEALED },
{ "bpf_keyring_provisioned", bpf_keyring_provisioned, BOOT_UNSEALED },
};
void test_signed_loader(void)
{
if (test__start_subtest("loadtime_no_map"))
loadtime_no_map();
if (test__start_subtest("loadtime_with_map"))
loadtime_with_map();
if (test__start_subtest("metadata_match"))
metadata_match();
if (test__start_subtest("signature_enforced"))
signature_enforced();
if (test__start_subtest("signed_nonexcl_fd_array_rejected"))
signed_nonexcl_fd_array_rejected();
if (test__start_subtest("signed_unfrozen_fd_array_rejected"))
signed_unfrozen_fd_array_rejected();
if (test__start_subtest("signed_nonarray_fd_array_rejected"))
signed_nonarray_fd_array_rejected();
if (test__start_subtest("signed_btf_fd_array_rejected"))
signed_btf_fd_array_rejected();
if (test__start_subtest("signed_module_kfunc_rejected"))
signed_module_kfunc_rejected();
if (test__start_subtest("signature_failure_logs"))
signature_failure_logs();
if (test__start_subtest("signature_too_large"))
signature_too_large();
if (test__start_subtest("signature_zero_size"))
signature_zero_size();
if (test__start_subtest("signature_bad_keyring"))
signature_bad_keyring();
if (test__start_subtest("metadata_ctx_max_entries_ignored"))
metadata_ctx_max_entries_ignored();
if (test__start_subtest("metadata_ctx_initial_value_ignored"))
metadata_ctx_initial_value_ignored();
if (test__start_subtest("signature_authenticates_insns"))
signature_authenticates_insns();
if (test__start_subtest("signature_authenticates_metadata"))
signature_authenticates_metadata();
if (test__start_subtest("hash_requires_frozen"))
hash_requires_frozen();
if (test__start_subtest("no_update_after_freeze"))
no_update_after_freeze();
if (test__start_subtest("freeze_writable_mmap"))
freeze_writable_mmap();
if (test__start_subtest("no_writable_mmap_frozen"))
no_writable_mmap_frozen();
if (test__start_subtest("map_hash_matches_libbpf"))
map_hash_matches_libbpf();
if (test__start_subtest("map_hash_multi_element"))
map_hash_multi_element();
if (test__start_subtest("map_hash_bad_size"))
map_hash_bad_size();
if (test__start_subtest("map_hash_unsupported_type"))
map_hash_unsupported_type();
if (test__start_subtest("lsm_signature_verdict"))
lsm_signature_verdict();
if (test__start_subtest("signed_no_fd_array"))
signed_no_fd_array();
if (test__start_subtest("signed_map_by_fd_rejected"))
signed_map_by_fd_rejected();
if (test__start_subtest("signed_sparse_fd_array_rejected"))
signed_sparse_fd_array_rejected();
bool unsealed = keyring_unsealed_boot();
unsigned int i;
for (i = 0; i < ARRAY_SIZE(subtests); i++) {
if (!test__start_subtest(subtests[i].name))
continue;
if (subtests[i].boot == BOOT_SEALED && unsealed) {
printf("%s:SKIP:needs a boot without bpf.keyring_unsealed=1\n",
subtests[i].name);
test__skip();
continue;
}
if (subtests[i].boot == BOOT_UNSEALED && !unsealed) {
printf("%s:SKIP:needs bpf.keyring_unsealed=1\n",
subtests[i].name);
test__skip();
continue;
}
subtests[i].fn();
}
}

View File

@ -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;

View File

@ -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, so openssl
# ignores 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

View File

@ -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()