mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
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:
commit
776c86a3e7
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
73
kernel/bpf/keys.c
Normal 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);
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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; \
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user