Re: [PATCH RFC 1/4] ipe: add the .ipe policy keyring

From: Fan Wu

Date: Mon Oct 19 2026 - 19:16:21 EST




On 10/19/2026 2:40 PM, Manuel Huber via B4 Relay wrote:
> From: Manuel Huber <manuelh@xxxxxxxxxx>
>
> 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 <manuelh@xxxxxxxxxx>
> ---
> 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 {
>