On 10/19/2026 2:40 PM, Manuel Huber via B4 Relay wrote:
> From: Manuel Huber <[email protected]>
> 
> Add a dedicated .ipe keyring for IPE policy signature verification,
> modeled after the .dm-verity and .bpf keyring implementations.
> 
> With this, userspace can provision its own keys into the keyring,
> then restrict it and activate it for IPE policy signature verification.
> 
> This enables scenarios in which a trusted userspace component generates
> an IPE policy, and creates an ephemeral policy-signing key pair,
> provisions its public certificate, and restricts the IPE keyring during
> the early lifetime of a system. The trusted component can then sign the
> IPE policy, destroy the private key, and load and activate the policy.
> During that phase, the system is considered in a trusted state whereas
> the system transitions into a possibly untrusted state afterwards,
> running arbitrary user-provided applications. In such a scenario, the
> trusted userspace component could reside on a dm-verity protected
> rootfs disk, and applications would reside on additional dm-verity
> protected disks. The latter disks would be dynamically attached and
> their dm-verity root hashes and/or signatures not known a priori to
> launching the system.
> 

You might need to explain why the existing keyrings, i.e. 
builtin/secondary/platform cannot be used in your scenario. Like the firmware 
is not trusted?

> A concrete usage scenario could be Kata Containers, which enables
> running Kubernetes pods in ephemeral, headless utility VMs, possibly
> without systemd. The rootfs disk is static and known beforehand, but
> containerd's image layers might be acquired and transported into the
> VM using the erofs snapshotter and dynamic block device attachment
> using dm-verity.
> 
> Signed-off-by: Manuel Huber <[email protected]>
> ---
>  Documentation/admin-guide/kernel-parameters.txt |  9 +++
>  security/ipe/Makefile                           |  1 +
>  security/ipe/ipe.c                              |  2 +
>  security/ipe/keyring.c                          | 79 
> +++++++++++++++++++++++++
>  security/ipe/keyring.h                          | 16 +++++
>  security/ipe/policy.c                           | 10 ++++
>  6 files changed, 117 insertions(+)
> 
> diff --git a/Documentation/admin-guide/kernel-parameters.txt 
> b/Documentation/admin-guide/kernel-parameters.txt
> index e75344f4e0cd..fd508dd779ed 100644
> --- a/Documentation/admin-guide/kernel-parameters.txt
> +++ b/Documentation/admin-guide/kernel-parameters.txt
> @@ -2733,6 +2733,15 @@ Kernel parameters
>                       Determine whether IPE starts in permissive (0) or
>                       enforce (1) mode. The default is enforce.
>  
> +     ipe.keyring_unsealed=
> +                     [IPE]
> +                     Format: <bool>
> +                     When set to 1, leave the IPE policy keyring
> +                     unsealed after initialization so userspace can provision
> +                     keys. Once the keyring is nonempty and restricted, it is
> +                     searched during policy-signature verification. The 
> default
> +                     is 0, which creates the keyring restricted and empty.
> +

I would prefer a Kconfig CONFIG_SECURITY_IPE_KEYRING default to N instead, like 
the fsverity keyring. In the kconfig you can also describe in which cases 
should this option be turned on. 

>       ipe.success_audit=
>                       [IPE]
>                       Format: <bool>
> diff --git a/security/ipe/Makefile b/security/ipe/Makefile
> index 2ffabfa63fe9..109e736696a5 100644
> --- a/security/ipe/Makefile
> +++ b/security/ipe/Makefile
> @@ -20,6 +20,7 @@ obj-$(CONFIG_SECURITY_IPE) += \
>       hooks.o \
>       fs.o \
>       ipe.o \
> +     keyring.o \

with Kconfig this can be obj-$(CONFIG_IPE_KEYRING) += keyring.o

>       policy.o \
>       policy_fs.o \
>       policy_parser.o \
> diff --git a/security/ipe/ipe.c b/security/ipe/ipe.c
> index 495bb765de1b..f5232f850dc2 100644
> --- a/security/ipe/ipe.c
> +++ b/security/ipe/ipe.c
> @@ -7,6 +7,7 @@
>  #include "ipe.h"
>  #include "eval.h"
>  #include "hooks.h"
> +#include "keyring.h"
>  
>  extern const char *const ipe_boot_policy;
>  bool ipe_enabled;
> @@ -97,4 +98,5 @@ DEFINE_LSM(ipe) = {
>       .init = ipe_init,
>       .blobs = &ipe_blobs,
>       .initcall_fs = ipe_init_securityfs,
> +     .initcall_late = ipe_keyring_init,
>  };
> diff --git a/security/ipe/keyring.c b/security/ipe/keyring.c
> new file mode 100644
> index 000000000000..ea8d164b62a6
> --- /dev/null
> +++ b/security/ipe/keyring.c
> @@ -0,0 +1,79 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
> + */
> +
> +#include <linux/cred.h>
> +#include <linux/err.h>
> +#include <linux/key.h>
> +#include <linux/moduleparam.h>
> +
> +#include "ipe.h"
> +#include "keyring.h"
> +
> +static struct key *ipe_keyring __ro_after_init;

You can remove static here then the other ipe code can directly use the 
variable instead of a function call. 

> +
> +static bool ipe_keyring_unsealed __ro_after_init;
> +
> +/* Set the right module name for built-in parameters. */
> +#ifdef KBUILD_MODNAME
> +#undef KBUILD_MODNAME
> +#define KBUILD_MODNAME "ipe"
> +#endif
> +
> +module_param_named(keyring_unsealed, ipe_keyring_unsealed, bool, 0444);
> +MODULE_PARM_DESC(keyring_unsealed, "Leave the IPE keyring unsealed");
> +

...

> +
> +/**
> + * ipe_keyring_init() - Allocate and configure the IPE keyring.
> + *
> + * Return: Always %0. A failure leaves IPE without its dedicated keyring.
> + */
> +int __init ipe_keyring_init(void)
> +{
> +     struct key *keyring;
> +     int rc;
> +
> +     keyring = keyring_alloc(".ipe", 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("failed to allocate keyring: %ld\n", PTR_ERR(keyring));
> +             return 0;

You might need to panic here like the fsverity kerying.

> +     }
> +
> +     if (!ipe_keyring_unsealed) {
> +             rc = keyring_restrict(make_key_ref(keyring, true), NULL, NULL);
> +             if (rc) {
> +                     pr_err("failed to restrict keyring: %d\n", rc);
> +                     key_revoke(keyring);
> +                     key_put(keyring);
> +                     return 0;
> +             }
> +     }

This looks kind of awkward, like if unseal is not set, it can create an empty 
keyring then immediately seal it. I would switch to the fsverity's approach to 
let userspace seal it.

> +
> +     ipe_keyring = keyring;
> +
> +     return 0;
> +}
> diff --git a/security/ipe/keyring.h b/security/ipe/keyring.h
> new file mode 100644
> index 000000000000..3d450a465511
> --- /dev/null
> +++ b/security/ipe/keyring.h
> @@ -0,0 +1,16 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (C) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
> + */
> +
> +#ifndef _IPE_KEYRING_H
> +#define _IPE_KEYRING_H
> +
> +#include <linux/init.h>
> +
> +struct key;
> +
> +int __init ipe_keyring_init(void);
> +struct key *ipe_keyring_lookup(void);
> +
> +#endif /* _IPE_KEYRING_H */
> diff --git a/security/ipe/policy.c b/security/ipe/policy.c
> index e542329726bc..abc1ff9ac230 100644
> --- a/security/ipe/policy.c
> +++ b/security/ipe/policy.c
> @@ -9,6 +9,7 @@
>  #include "ipe.h"
>  #include "eval.h"
>  #include "fs.h"
> +#include "keyring.h"
>  #include "policy.h"
>  #include "policy_parser.h"
>  #include "audit.h"
> @@ -160,6 +161,7 @@ struct ipe_policy *ipe_new_policy(const char *text, 
> size_t textlen,
>                                 const char *pkcs7, size_t pkcs7len)
>  {
>       struct ipe_policy *new = NULL;
> +     struct key *keyring;
>       int rc = 0;
>  
>       new = kzalloc_obj(*new);
> @@ -189,6 +191,14 @@ struct ipe_policy *ipe_new_policy(const char *text, 
> size_t textlen,
>                                                   
> VERIFYING_UNSPECIFIED_SIGNATURE,
>                                                   set_pkcs7_data, new);
>  #endif
> +             if (rc == -ENOKEY || rc == -EKEYREJECTED) {
> +                     keyring = ipe_keyring_lookup();
> +                     if (keyring)
> +                             rc = verify_pkcs7_signature(NULL, 0, new->pkcs7,
> +                                                         pkcs7len, keyring,
> +                                                         
> VERIFYING_UNSPECIFIED_SIGNATURE,
> +                                                         set_pkcs7_data, 
> new);
> +             }

With Kconfig this part can be just like the above cases. 

Fan

>               if (rc)
>                       goto err;
>       } else {
> 


Reply via email to