* [PATCH RFC 0/4] IPE: add a dedicated policy keyring
@ 2026-10-19 21:40 Manuel Huber via B4 Relay
2026-10-19 21:40 ` [PATCH RFC 1/4] ipe: add the .ipe " Manuel Huber via B4 Relay
` (4 more replies)
0 siblings, 5 replies; 14+ messages in thread
From: Manuel Huber via B4 Relay @ 2026-10-19 21:40 UTC (permalink / raw)
To: Fan Wu, David Howells, Jarkko Sakkinen
Cc: Christian Brauner, Andrew Halaney, Paul Moore, James Morris,
Serge E. Hallyn, Jonathan Corbet, Shuah Khan, Shuah Khan,
Randy Dunlap, Alexei Starovoitov, Daniel Borkmann,
linux-security-module, keyrings, linux-kselftest, linux-doc, bpf,
linux-kernel, Manuel Huber
Add a dedicated ".ipe" keyring for IPE policy-signature verification,
modeled after the dm-verity keyring, allowing userspace to provision
IPE-specific policy-signing certificates. Systems that do not provision
the keyring retain their existing behavior.
This RFC is based on commit 48f936e302a9 ("ipe: update the project and
test suite links") from ipe/next so review can begin before the
prerequisite keyring work reaches that branch. The series will be rebased
onto a refreshed ipe/next once that work is available there [1].
The keyring readiness check currently follows the dm-verity and BPF
implementations. A parallel keys RFC introduces keyring_is_empty() for
this purpose [2]. A later revision will use the helper if it is available
in the submission base.
Testing:
- Built an x86_64 kernel with GCC 15.2.0 and Clang/LLD 21.1.8.
- Passed all 33 IPE parser KUnit assertions.
- Passed all 4 sealed-default keyring selftests under QEMU TCG.
- Passed all 16 provisioning-mode keyring selftests under QEMU TCG with
ipe.keyring_unsealed=1.
[1] https://lore.kernel.org/r/20261008-ajhalaney-dmverity-key-identifier-v2-0-008456d189cb@amutable.com
[2] https://lore.kernel.org/r/20261016215326.4110064-5-jarkko@kernel.org
Signed-off-by: Manuel Huber <manuelh@nvidia.com>
---
Manuel Huber (4):
ipe: add the .ipe policy keyring
keys: add KEY_SPEC_IPE_KEYRING
Documentation: ipe: document the IPE keyring
selftests/ipe: test the IPE policy keyring
Documentation/admin-guide/LSM/ipe.rst | 41 ++-
Documentation/admin-guide/kernel-parameters.txt | 9 +
Documentation/security/ipe.rst | 16 +-
Documentation/security/keys/core.rst | 9 +
MAINTAINERS | 1 +
include/linux/key.h | 2 +
include/uapi/linux/keyctl.h | 1 +
security/ipe/Makefile | 1 +
security/ipe/ipe.c | 2 +
security/ipe/keyring.c | 80 ++++++
security/ipe/keyring.h | 16 ++
security/ipe/policy.c | 10 +
security/keys/process_keys.c | 23 ++
tools/testing/selftests/Makefile | 1 +
tools/testing/selftests/ipe/Makefile | 5 +
tools/testing/selftests/ipe/config | 7 +
tools/testing/selftests/ipe/test-ipe-keyring.sh | 357 ++++++++++++++++++++++++
17 files changed, 575 insertions(+), 6 deletions(-)
---
base-commit: 48f936e302a9c48cfc032eb5944c3a6246bc9043
change-id: 20261019-b4-ipe-keyring-rfc-b93b9ae129a2
Best regards,
--
Manuel Huber <manuelh@nvidia.com>
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH RFC 1/4] ipe: add the .ipe policy keyring
2026-10-19 21:40 [PATCH RFC 0/4] IPE: add a dedicated policy keyring Manuel Huber via B4 Relay
@ 2026-10-19 21:40 ` Manuel Huber via B4 Relay
2026-10-19 21:52 ` Randy Dunlap
2026-10-19 23:15 ` Fan Wu
2026-10-19 21:40 ` [PATCH RFC 2/4] keys: add KEY_SPEC_IPE_KEYRING Manuel Huber via B4 Relay
` (3 subsequent siblings)
4 siblings, 2 replies; 14+ messages in thread
From: Manuel Huber via B4 Relay @ 2026-10-19 21:40 UTC (permalink / raw)
To: Fan Wu, David Howells, Jarkko Sakkinen
Cc: Christian Brauner, Andrew Halaney, Paul Moore, James Morris,
Serge E. Hallyn, Jonathan Corbet, Shuah Khan, Shuah Khan,
Randy Dunlap, Alexei Starovoitov, Daniel Borkmann,
linux-security-module, keyrings, linux-kselftest, linux-doc, bpf,
linux-kernel, Manuel Huber
From: Manuel Huber <manuelh@nvidia.com>
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.
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@nvidia.com>
---
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.
+
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 \
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;
+
+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_lookup() - Get the IPE keyring if it is ready for use.
+ *
+ * Return: A borrowed pointer to the IPE keyring if it is nonempty and
+ * restricted, or %NULL otherwise. The keyring has system lifetime.
+ */
+struct key *ipe_keyring_lookup(void)
+{
+ if (!ipe_keyring)
+ return NULL;
+
+ if (!READ_ONCE(ipe_keyring->keys.nr_leaves_on_tree) ||
+ !READ_ONCE(ipe_keyring->restrict_link))
+ return NULL;
+
+ return ipe_keyring;
+}
+
+/**
+ * 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;
+ }
+
+ 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;
+ }
+ }
+
+ 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);
+ }
if (rc)
goto err;
} else {
--
2.53.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH RFC 2/4] keys: add KEY_SPEC_IPE_KEYRING
2026-10-19 21:40 [PATCH RFC 0/4] IPE: add a dedicated policy keyring Manuel Huber via B4 Relay
2026-10-19 21:40 ` [PATCH RFC 1/4] ipe: add the .ipe " Manuel Huber via B4 Relay
@ 2026-10-19 21:40 ` Manuel Huber via B4 Relay
2026-10-19 23:24 ` Fan Wu
2026-10-19 21:40 ` [PATCH RFC 3/4] Documentation: ipe: document the IPE keyring Manuel Huber via B4 Relay
` (2 subsequent siblings)
4 siblings, 1 reply; 14+ messages in thread
From: Manuel Huber via B4 Relay @ 2026-10-19 21:40 UTC (permalink / raw)
To: Fan Wu, David Howells, Jarkko Sakkinen
Cc: Christian Brauner, Andrew Halaney, Paul Moore, James Morris,
Serge E. Hallyn, Jonathan Corbet, Shuah Khan, Shuah Khan,
Randy Dunlap, Alexei Starovoitov, Daniel Borkmann,
linux-security-module, keyrings, linux-kselftest, linux-doc, bpf,
linux-kernel, Manuel Huber
From: Manuel Huber <manuelh@nvidia.com>
The IPE policy keyring is allocated unlinked, so userspace would
otherwise have to scrape /proc/keys to name it.
Signed-off-by: Manuel Huber <manuelh@nvidia.com>
---
Documentation/security/keys/core.rst | 9 +++++++++
include/linux/key.h | 2 ++
include/uapi/linux/keyctl.h | 1 +
security/ipe/keyring.c | 1 +
security/keys/process_keys.c | 23 +++++++++++++++++++++++
5 files changed, 36 insertions(+)
diff --git a/Documentation/security/keys/core.rst b/Documentation/security/keys/core.rst
index 326b8a973828..e4ecda5c2525 100644
--- a/Documentation/security/keys/core.rst
+++ b/Documentation/security/keys/core.rst
@@ -373,6 +373,15 @@ process making the call::
KEY_SPEC_REQKEY_AUTH_KEY -7 assumed request_key()
authorisation key
+There are also values that refer to a specific kernel keyring rather than to
+anything belonging to the caller. These keyrings are not linked into any
+keyring, so a search cannot reach them and the ID is the only way to name
+one::
+
+ CONSTANT VALUE KEY REFERENCED
+ ============================== ====== ===========================
+ KEY_SPEC_IPE_KEYRING -12 the ".ipe" keyring
+
The main syscalls are:
diff --git a/include/linux/key.h b/include/linux/key.h
index 81b8f05c6898..9c2ac0c70d56 100644
--- a/include/linux/key.h
+++ b/include/linux/key.h
@@ -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_ipe_keyring(struct key *keyring);
+
extern struct key *key_lookup(key_serial_t id);
static inline key_serial_t key_serial(const struct key *key)
diff --git a/include/uapi/linux/keyctl.h b/include/uapi/linux/keyctl.h
index 4c8884eea808..e42fca286b15 100644
--- a/include/uapi/linux/keyctl.h
+++ b/include/uapi/linux/keyctl.h
@@ -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_IPE_KEYRING -12 /* - key ID for the .ipe keyring */
/* request-key default keyrings */
#define KEY_REQKEY_DEFL_NO_CHANGE -1
diff --git a/security/ipe/keyring.c b/security/ipe/keyring.c
index ea8d164b62a6..5158dc7310a7 100644
--- a/security/ipe/keyring.c
+++ b/security/ipe/keyring.c
@@ -74,6 +74,7 @@ int __init ipe_keyring_init(void)
}
ipe_keyring = keyring;
+ key_register_ipe_keyring(ipe_keyring);
return 0;
}
diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c
index a63c46bb2d14..20d6faefb75e 100644
--- a/security/keys/process_keys.c
+++ b/security/keys/process_keys.c
@@ -22,6 +22,9 @@
/* Session keyring create vs join semaphore */
static DEFINE_MUTEX(key_session_mutex);
+/* IPE keyring reachable through KEY_SPEC_IPE_KEYRING */
+static struct key *ipe_keyring __ro_after_init;
+
/* The root user's tracking struct */
struct key_user root_key_user = {
.usage = REFCOUNT_INIT(3),
@@ -590,6 +593,18 @@ bool lookup_user_key_possessed(const struct key *key,
return key == match_data->raw_data;
}
+/**
+ * key_register_ipe_keyring - Publish the IPE keyring
+ * @keyring: The keyring to publish
+ *
+ * Make @keyring reachable by userspace through the KEY_SPEC_IPE_KEYRING
+ * special key ID. Called once, from an initcall, and never undone.
+ */
+void key_register_ipe_keyring(struct key *keyring)
+{
+ ipe_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 +756,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_IPE_KEYRING:
+ key = ipe_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)
--
2.53.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH RFC 3/4] Documentation: ipe: document the IPE keyring
2026-10-19 21:40 [PATCH RFC 0/4] IPE: add a dedicated policy keyring Manuel Huber via B4 Relay
2026-10-19 21:40 ` [PATCH RFC 1/4] ipe: add the .ipe " Manuel Huber via B4 Relay
2026-10-19 21:40 ` [PATCH RFC 2/4] keys: add KEY_SPEC_IPE_KEYRING Manuel Huber via B4 Relay
@ 2026-10-19 21:40 ` Manuel Huber via B4 Relay
2026-10-19 21:40 ` [PATCH RFC 4/4] selftests/ipe: test the IPE policy keyring Manuel Huber via B4 Relay
2026-10-19 21:54 ` [PATCH RFC 0/4] IPE: add a dedicated " Jarkko Sakkinen
4 siblings, 0 replies; 14+ messages in thread
From: Manuel Huber via B4 Relay @ 2026-10-19 21:40 UTC (permalink / raw)
To: Fan Wu, David Howells, Jarkko Sakkinen
Cc: Christian Brauner, Andrew Halaney, Paul Moore, James Morris,
Serge E. Hallyn, Jonathan Corbet, Shuah Khan, Shuah Khan,
Randy Dunlap, Alexei Starovoitov, Daniel Borkmann,
linux-security-module, keyrings, linux-kselftest, linux-doc, bpf,
linux-kernel, Manuel Huber
From: Manuel Huber <manuelh@nvidia.com>
Document the .ipe policy-verification keyring and its additive use
after the existing trust sources.
Signed-off-by: Manuel Huber <manuelh@nvidia.com>
---
Documentation/admin-guide/LSM/ipe.rst | 41 ++++++++++++++++++++++++++++++++++-
Documentation/security/ipe.rst | 16 +++++++++-----
2 files changed, 51 insertions(+), 6 deletions(-)
diff --git a/Documentation/admin-guide/LSM/ipe.rst b/Documentation/admin-guide/LSM/ipe.rst
index 9e82a2082b94..48ccad203301 100644
--- a/Documentation/admin-guide/LSM/ipe.rst
+++ b/Documentation/admin-guide/LSM/ipe.rst
@@ -238,7 +238,11 @@ unconstrained root, and deploying an "allow all" policy). These
policies must be signed by a certificate that chains to the
``SYSTEM_TRUSTED_KEYRING``, or to the secondary and/or platform keyrings if
``CONFIG_IPE_POLICY_SIG_SECONDARY_KEYRING`` and/or
-``CONFIG_IPE_POLICY_SIG_PLATFORM_KEYRING`` are enabled, respectively.
+``CONFIG_IPE_POLICY_SIG_PLATFORM_KEYRING`` are enabled, respectively. IPE also
+provides a dedicated ``.ipe`` keyring for policy-signing certificates that are
+provisioned by userspace, as described below. The ``.ipe`` keyring is an
+additive final fallback and does not replace the configured system trust
+sources.
With openssl, the policy can be signed by::
openssl smime -sign \
@@ -251,6 +255,41 @@ With openssl, the policy can be signed by::
-outform der \
-out "$MY_POLICY.p7b"
+Provisioning the IPE policy keyring
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The ``.ipe`` keyring allows a system to provision an IPE-specific policy
+signing certificate without embedding it in the kernel or firmware trust
+stores. It is not linked into any process keyring. Userspace addresses it
+directly with the ``KEY_SPEC_IPE_KEYRING`` special key ID (``-12``), which does
+not grant possession of the keyring.
+
+The keyring is restricted while empty by default and cannot then be
+provisioned. To provision it, boot with ``ipe.keyring_unsealed=1``. Userspace
+can add a DER-encoded X.509 certificate and then apply a reject-all
+restriction::
+
+ keyctl padd asymmetric "" -12 < policy-signing-key.der
+ keyctl restrict_keyring -12
+
+Both steps are required. IPE ignores the keyring until it is nonempty and
+restricted, so a partially provisioned, mutable keyring is not a trust source.
+The restriction cannot be removed. More than one certificate can be enrolled
+by repeating the ``keyctl padd`` command before restricting the keyring.
+
+Restriction prevents adding keys, but it does not prevent userspace from
+removing keys or otherwise disabling the keyring. To lock it against further
+userspace modification, drop its write and setattr permissions::
+
+ keyctl setperm -12 0x08030000
+
+This retains the permissions needed for verification and inspection.
+Provisioning, restriction, and permission hardening must be completed before
+relying on ``.ipe`` as a trust source.
+
+Once nonempty and restricted, ``.ipe`` is searched after the configured system
+trust sources. An absent, empty, or unrestricted ``.ipe`` keyring is ignored.
+
Deploying the policies is done through securityfs, through the
``new_policy`` node. To deploy a policy, simply cat the file into the
securityfs node::
diff --git a/Documentation/security/ipe.rst b/Documentation/security/ipe.rst
index 4490aed5918a..80f9893dddb5 100644
--- a/Documentation/security/ipe.rst
+++ b/Documentation/security/ipe.rst
@@ -248,11 +248,17 @@ policy updates have to be done rebootlessly.
To allow an update from an external source, it could be potentially malicious,
so this policy needs to have a way to be identified as trusted. This is
-done via a signature chained to a trust source in the kernel. Arbitrarily,
-this is the ``SYSTEM_TRUSTED_KEYRING``, a keyring that is initially
-populated at kernel compile-time, as this matches the expectation that the
-author of the compiled-in policy described above is the same entity that can
-deploy policy updates.
+done via a signature chained to a trust source in the kernel. IPE can use the
+builtin trusted keyring and, when configured, the secondary and platform
+keyrings. This matches systems where the entity that establishes the kernel or
+firmware trust store is also authorized to deploy policy updates.
+
+IPE also has a dedicated ``.ipe`` keyring for deployments that establish an
+IPE-specific trust anchor from userspace. The keyring can be provisioned and
+irreversibly restricted. IPE uses it only when it is nonempty and restricted,
+and only as a fallback after the configured system keyrings. This keeps the
+existing trust model intact while allowing a policy-signing key to be rotated
+across boots without changing the kernel image or a broader system trust store.
Anti-Rollback / Anti-Replay
~~~~~~~~~~~~~~~~~~~~~~~~~~~
--
2.53.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH RFC 4/4] selftests/ipe: test the IPE policy keyring
2026-10-19 21:40 [PATCH RFC 0/4] IPE: add a dedicated policy keyring Manuel Huber via B4 Relay
` (2 preceding siblings ...)
2026-10-19 21:40 ` [PATCH RFC 3/4] Documentation: ipe: document the IPE keyring Manuel Huber via B4 Relay
@ 2026-10-19 21:40 ` Manuel Huber via B4 Relay
2026-10-19 23:28 ` Fan Wu
2026-10-19 21:54 ` [PATCH RFC 0/4] IPE: add a dedicated " Jarkko Sakkinen
4 siblings, 1 reply; 14+ messages in thread
From: Manuel Huber via B4 Relay @ 2026-10-19 21:40 UTC (permalink / raw)
To: Fan Wu, David Howells, Jarkko Sakkinen
Cc: Christian Brauner, Andrew Halaney, Paul Moore, James Morris,
Serge E. Hallyn, Jonathan Corbet, Shuah Khan, Shuah Khan,
Randy Dunlap, Alexei Starovoitov, Daniel Borkmann,
linux-security-module, keyrings, linux-kselftest, linux-doc, bpf,
linux-kernel, Manuel Huber
From: Manuel Huber <manuelh@nvidia.com>
Add an IPE kselftest that validates policy keyring operations for
both ipe.keyring_unsealed values 0 and 1.
For the default ipe.keyring_unsealed=0, verify that a) the special key
ID resolves, b) certificate enrollment is rejected, and c) the keyring
is not used for policy verification.
For ipe.keyring_unsealed=1, verify that we can a) provision multiple
certificates, b) restrict the keyring, then reject unenrolled and
corrupted signatures, c) authenticate policy loads and updates with
different enrolled certificates.
Since IPE is built in, keyring restriction is irreversible, which is
why we can only exercise the initially sealed and unsealed scenarios
via separate boots. To get full coverage, CI thus needs to spin up
separate environments with respective parameter values.
Signed-off-by: Manuel Huber <manuelh@nvidia.com>
---
MAINTAINERS | 1 +
tools/testing/selftests/Makefile | 1 +
tools/testing/selftests/ipe/Makefile | 5 +
tools/testing/selftests/ipe/config | 7 +
tools/testing/selftests/ipe/test-ipe-keyring.sh | 357 ++++++++++++++++++++++++
5 files changed, 371 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 65e8a4b5c90b..818f9968b315 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -12967,6 +12967,7 @@ F: Documentation/admin-guide/LSM/ipe.rst
F: Documentation/security/ipe.rst
F: scripts/ipe/
F: security/ipe/
+F: tools/testing/selftests/ipe/
INTEL 810/815 FRAMEBUFFER DRIVER
M: Antonino Daplas <adaplas@gmail.com>
diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
index 273853937c25..16ba45a12c5b 100644
--- a/tools/testing/selftests/Makefile
+++ b/tools/testing/selftests/Makefile
@@ -59,6 +59,7 @@ TARGETS += hid
TARGETS += intel_pstate
TARGETS += iommu
TARGETS += ipc
+TARGETS += ipe
TARGETS += ir
TARGETS += kcmp
TARGETS += kexec
diff --git a/tools/testing/selftests/ipe/Makefile b/tools/testing/selftests/ipe/Makefile
new file mode 100644
index 000000000000..f281e68329c1
--- /dev/null
+++ b/tools/testing/selftests/ipe/Makefile
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0
+
+TEST_PROGS := test-ipe-keyring.sh
+
+include ../lib.mk
diff --git a/tools/testing/selftests/ipe/config b/tools/testing/selftests/ipe/config
new file mode 100644
index 000000000000..498c224d2c18
--- /dev/null
+++ b/tools/testing/selftests/ipe/config
@@ -0,0 +1,7 @@
+CONFIG_AUDIT=y
+CONFIG_AUDITSYSCALL=y
+CONFIG_KEYS=y
+CONFIG_PROC_FS=y
+CONFIG_SECURITY=y
+CONFIG_SECURITYFS=y
+CONFIG_SECURITY_IPE=y
diff --git a/tools/testing/selftests/ipe/test-ipe-keyring.sh b/tools/testing/selftests/ipe/test-ipe-keyring.sh
new file mode 100755
index 000000000000..557ccc825fe2
--- /dev/null
+++ b/tools/testing/selftests/ipe/test-ipe-keyring.sh
@@ -0,0 +1,357 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Run from the kernel source tree with:
+# make -C tools/testing/selftests TARGETS=ipe run_tests
+#
+# Full coverage requires one boot with the default command line and another
+# with ipe.keyring_unsealed=1. The test selects the matching mode automatically.
+# The provisioning mode irreversibly restricts and locks the .ipe keyring, so
+# run it once per boot on a disposable test system.
+#
+# Requirements: root with CAP_MAC_ADMIN, keyctl, and openssl.
+
+DIR="$(dirname "$(readlink -f "$0")")"
+# shellcheck source=../kselftest/ktap_helpers.sh
+source "${DIR}/../kselftest/ktap_helpers.sh"
+
+KEY_SPEC_IPE_KEYRING=-12
+IPE_KEYRING_PERM_LOCKED=0x08030000
+IPE_KEYRING_PERM_INITIAL=0x082f0000
+SECURITYFS=/sys/kernel/security
+IPEFS="$SECURITYFS/ipe"
+WORK_DIR=
+MOUNTED_SECURITYFS=0
+POLICY_NAME="ipe_keyring_selftest_$$"
+WRONG_POLICY_NAME="${POLICY_NAME}_wrong"
+POLICY_LOADED=0
+WRONG_POLICY_LOADED=0
+
+skip()
+{
+ ktap_skip_all "$*"
+ exit "$KSFT_SKIP"
+}
+
+fail()
+{
+ ktap_test_fail "$*"
+ ktap_finished
+}
+
+# shellcheck disable=SC2329 # invoked indirectly via trap
+cleanup()
+{
+ if [ "$WRONG_POLICY_LOADED" -eq 1 ] &&
+ [ -w "$IPEFS/policies/$WRONG_POLICY_NAME/delete" ]; then
+ echo 1 > "$IPEFS/policies/$WRONG_POLICY_NAME/delete" 2>/dev/null || true
+ fi
+
+ if [ "$POLICY_LOADED" -eq 1 ] &&
+ [ -w "$IPEFS/policies/$POLICY_NAME/delete" ]; then
+ echo 1 > "$IPEFS/policies/$POLICY_NAME/delete" 2>/dev/null || true
+ fi
+
+ if [ "$MOUNTED_SECURITYFS" -eq 1 ]; then
+ umount "$SECURITYFS" 2>/dev/null || true
+ fi
+
+ if [ -n "$WORK_DIR" ] && [ -d "$WORK_DIR" ]; then
+ rm -rf -- "$WORK_DIR"
+ fi
+}
+
+trap cleanup EXIT
+
+require_command()
+{
+ command -v "$1" >/dev/null 2>&1 || skip "missing command: $1"
+}
+
+generate_keypair()
+{
+ local name=$1
+
+ if ! openssl req -new -x509 -newkey rsa:2048 -sha256 -nodes -days 1 \
+ -subj "/CN=IPE keyring selftest $name/" \
+ -addext "basicConstraints=critical,CA:FALSE" \
+ -addext "keyUsage=critical,digitalSignature" \
+ -keyout "$WORK_DIR/$name-key.pem" \
+ -out "$WORK_DIR/$name-cert.pem" >/dev/null \
+ 2> "$WORK_DIR/$name-openssl.log"; then
+ cat "$WORK_DIR/$name-openssl.log" >&2
+ fail "generate the $name signing key"
+ fi
+
+ if ! openssl x509 -in "$WORK_DIR/$name-cert.pem" -outform DER \
+ -out "$WORK_DIR/$name-cert.der" >/dev/null \
+ 2> "$WORK_DIR/$name-openssl.log"; then
+ cat "$WORK_DIR/$name-openssl.log" >&2
+ fail "convert the $name certificate to DER"
+ fi
+}
+
+make_policy()
+{
+ local name=$1
+ local version=$2
+ local output=$3
+
+ printf 'policy_name=%s policy_version=%s\nDEFAULT action=ALLOW\n' \
+ "$name" "$version" > "$output"
+}
+
+sign_policy()
+{
+ local policy=$1
+ local signer=$2
+ local output=$3
+
+ if ! openssl smime -sign -in "$policy" \
+ -signer "$WORK_DIR/$signer-cert.pem" \
+ -inkey "$WORK_DIR/$signer-key.pem" \
+ -noattr -nodetach -nosmimecap -outform DER \
+ -out "$output" >/dev/null \
+ 2> "$WORK_DIR/$signer-openssl.log"; then
+ cat "$WORK_DIR/$signer-openssl.log" >&2
+ fail "sign policy with the $signer key"
+ fi
+}
+
+corrupt_signed_policy()
+{
+ local input=$1
+ local output=$2
+ local signed_size
+ local corrupt_offset
+
+ cp -- "$input" "$output" || fail "copy the signed policy"
+ signed_size=$(wc -c < "$output") || fail "measure the signed policy"
+ [ "$signed_size" -gt 16 ] || fail "signed policy is unexpectedly short"
+ corrupt_offset=$((signed_size - 16))
+ printf '\377\377\377\377' | dd of="$output" bs=1 \
+ seek="$corrupt_offset" conv=notrunc >/dev/null 2>&1 ||
+ fail "corrupt the signed policy"
+}
+
+keyring_key_count()
+{
+ local output
+
+ output=$(keyctl list "$KEY_SPEC_IPE_KEYRING" 2>/dev/null) || return 1
+ awk 'NR == 1 {
+ if ($0 ~ /empty/)
+ print 0
+ else if ($1 ~ /^[0-9]+$/)
+ print $1
+ else
+ exit 1
+ }' <<< "$output"
+}
+
+check_locked_mutations()
+{
+ local key_serial=$1
+ local keyring_serial=$2
+
+ if keyctl unlink "$key_serial" "$keyring_serial" >/dev/null 2>&1; then
+ fail "locked keyring allowed its certificate to be unlinked"
+ fi
+ if keyctl clear "$keyring_serial" >/dev/null 2>&1; then
+ fail "locked keyring allowed its contents to be cleared"
+ fi
+ if keyctl revoke "$keyring_serial" >/dev/null 2>&1; then
+ fail "locked keyring allowed itself to be revoked"
+ fi
+ if keyctl invalidate "$keyring_serial" >/dev/null 2>&1; then
+ fail "locked keyring allowed itself to be invalidated"
+ fi
+ if keyctl timeout "$keyring_serial" 1 >/dev/null 2>&1; then
+ fail "locked keyring allowed a timeout to be set"
+ fi
+ if keyctl setperm "$keyring_serial" \
+ "$IPE_KEYRING_PERM_INITIAL" >/dev/null 2>&1; then
+ fail "locked keyring allowed its permissions to be restored"
+ fi
+}
+
+test_sealed_keyring()
+{
+ local key_count
+
+ ktap_set_plan 4
+ ktap_test_pass "KEY_SPEC_IPE_KEYRING resolves to the .ipe keyring"
+
+ key_count=$(keyring_key_count) || fail "read the sealed keyring"
+ [ "$key_count" = 0 ] || fail "sealed-default keyring is not empty"
+ ktap_test_pass "the default keyring is empty"
+
+ generate_keypair trusted
+ if keyctl padd asymmetric "ipe-selftest-$$" \
+ "$KEY_SPEC_IPE_KEYRING" < "$WORK_DIR/trusted-cert.der" \
+ >/dev/null 2>&1; then
+ fail "sealed-default keyring accepted a certificate"
+ fi
+ ktap_test_pass "the default keyring rejects new certificates"
+
+ make_policy "$POLICY_NAME" 0.0.1 "$WORK_DIR/policy-v1"
+ sign_policy "$WORK_DIR/policy-v1" trusted "$WORK_DIR/policy-v1.p7b"
+ if cat "$WORK_DIR/policy-v1.p7b" > "$IPEFS/new_policy" 2>/dev/null; then
+ POLICY_LOADED=1
+ fail "empty .ipe keyring authenticated a policy"
+ fi
+ ktap_test_pass "the empty keyring is not used for policy verification"
+}
+
+test_unsealed_keyring()
+{
+ local key_count
+ local key_serial
+
+ ktap_set_plan 16
+ ktap_test_pass "KEY_SPEC_IPE_KEYRING resolves to the .ipe keyring"
+
+ key_count=$(keyring_key_count) || fail "read the unsealed keyring"
+ [ "$key_count" = 0 ] || fail "unsealed keyring is not empty"
+ ktap_test_pass "the unsealed keyring starts empty"
+
+ generate_keypair trusted
+ generate_keypair trusted-alt
+ generate_keypair untrusted
+ key_serial=$(keyctl padd asymmetric "ipe-selftest-$$" \
+ "$KEY_SPEC_IPE_KEYRING" < "$WORK_DIR/trusted-cert.der" \
+ 2>/dev/null) || fail "add a certificate to the unsealed keyring"
+ keyctl padd asymmetric "ipe-selftest-alt-$$" \
+ "$KEY_SPEC_IPE_KEYRING" < "$WORK_DIR/trusted-alt-cert.der" \
+ >/dev/null 2>&1 || fail "add a second certificate to the keyring"
+ key_count=$(keyring_key_count) || fail "count the provisioned certificates"
+ [ "$key_count" = 2 ] || fail "keyring does not contain two certificates"
+ ktap_test_pass "multiple certificates can be provisioned"
+
+ make_policy "$POLICY_NAME" 0.0.1 "$WORK_DIR/policy-v1"
+ sign_policy "$WORK_DIR/policy-v1" trusted "$WORK_DIR/policy-v1.p7b"
+ if cat "$WORK_DIR/policy-v1.p7b" > "$IPEFS/new_policy" 2>/dev/null; then
+ POLICY_LOADED=1
+ fail "unrestricted .ipe keyring authenticated a policy"
+ fi
+ ktap_test_pass "an unrestricted keyring is not used for verification"
+
+ keyctl restrict_keyring "$KEY_SPEC_IPE_KEYRING" >/dev/null 2>&1 ||
+ fail "restrict the provisioned keyring"
+ ktap_test_pass "the provisioned keyring can be restricted"
+
+ if printf x | keyctl padd user "ipe-selftest-probe-$$" \
+ "$KEY_SPEC_IPE_KEYRING" >/dev/null 2>&1; then
+ fail "restricted keyring accepted another key"
+ fi
+ ktap_test_pass "the restricted keyring rejects additional keys"
+
+ make_policy "$WRONG_POLICY_NAME" 0.0.1 "$WORK_DIR/policy-wrong"
+ sign_policy "$WORK_DIR/policy-wrong" untrusted \
+ "$WORK_DIR/policy-wrong.p7b"
+ if cat "$WORK_DIR/policy-wrong.p7b" > "$IPEFS/new_policy" 2>/dev/null; then
+ WRONG_POLICY_LOADED=1
+ fail "an unenrolled certificate authenticated a policy"
+ fi
+ ktap_test_pass "an unenrolled certificate is rejected"
+
+ corrupt_signed_policy "$WORK_DIR/policy-v1.p7b" \
+ "$WORK_DIR/policy-v1-corrupt.p7b"
+ if cat "$WORK_DIR/policy-v1-corrupt.p7b" \
+ > "$IPEFS/new_policy" 2>/dev/null; then
+ POLICY_LOADED=1
+ fail "a corrupted signed policy was authenticated"
+ fi
+ ktap_test_pass "a corrupted signed policy is rejected"
+
+ cat "$WORK_DIR/policy-v1.p7b" > "$IPEFS/new_policy" 2>/dev/null ||
+ fail "load a policy signed by the enrolled certificate"
+ POLICY_LOADED=1
+ ktap_test_pass "the restricted keyring authenticates a policy"
+
+ [ "$(cat "$IPEFS/policies/$POLICY_NAME/name")" = "$POLICY_NAME" ] &&
+ [ "$(cat "$IPEFS/policies/$POLICY_NAME/version")" = 0.0.1 ] ||
+ fail "loaded policy metadata does not match"
+ ktap_test_pass "the authenticated policy metadata matches"
+
+ keyctl setperm "$KEY_SPEC_IPE_KEYRING" \
+ "$IPE_KEYRING_PERM_LOCKED" >/dev/null 2>&1 ||
+ fail "drop mutation permissions from the keyring"
+ ktap_test_pass "mutation permissions can be dropped"
+
+ if keyctl id "$KEY_SPEC_IPE_KEYRING" >/dev/null 2>&1; then
+ fail "the special key ID granted possession rights"
+ fi
+ ktap_test_pass "the special key ID grants no possession rights"
+
+ check_locked_mutations "$key_serial" "$keyring_serial"
+ ktap_test_pass "locked-keyring mutation attempts are rejected"
+
+ make_policy "$POLICY_NAME" 0.0.2 "$WORK_DIR/policy-v2"
+ sign_policy "$WORK_DIR/policy-v2" trusted-alt "$WORK_DIR/policy-v2.p7b"
+ cat "$WORK_DIR/policy-v2.p7b" \
+ > "$IPEFS/policies/$POLICY_NAME/update" 2>/dev/null ||
+ fail "update a policy signed by the second enrolled certificate"
+ ktap_test_pass "the second enrolled certificate authenticates policy updates"
+
+ [ "$(cat "$IPEFS/policies/$POLICY_NAME/version")" = 0.0.2 ] ||
+ fail "policy version was not updated"
+ ktap_test_pass "the authenticated policy update was installed"
+
+ echo 1 > "$IPEFS/policies/$POLICY_NAME/delete" 2>/dev/null ||
+ fail "delete the inactive test policy"
+ POLICY_LOADED=0
+ ktap_test_pass "the inactive test policy can be removed"
+}
+
+ktap_print_header
+
+for command in awk cat cp dd grep id keyctl mktemp mount openssl rm umount wc; do
+ require_command "$command"
+done
+
+[ "$(id -u)" -eq 0 ] || skip "must be run as root"
+
+cap_eff=$(awk '/^CapEff:/ { print $2 }' /proc/self/status)
+[ -n "$cap_eff" ] || skip "cannot read effective capabilities"
+(( (16#$cap_eff & (1 << 33)) != 0 )) || skip "CAP_MAC_ADMIN is required"
+
+WORK_DIR=$(mktemp -d -t ipe-keyring-test.XXXXXX) ||
+ skip "cannot create a temporary directory"
+
+if ! grep -qs " $SECURITYFS securityfs " /proc/mounts; then
+ mount -t securityfs none "$SECURITYFS" >/dev/null 2>&1 ||
+ skip "cannot mount securityfs"
+ MOUNTED_SECURITYFS=1
+fi
+
+[ -w "$IPEFS/new_policy" ] || skip "IPE is not enabled"
+[ -r /sys/module/ipe/parameters/keyring_unsealed ] ||
+ skip "IPE policy keyring is not available"
+
+keyring_serial=$(keyctl id "$KEY_SPEC_IPE_KEYRING" 2>/dev/null) ||
+ skip "KEY_SPEC_IPE_KEYRING does not resolve"
+keyring_description=$(keyctl describe "$keyring_serial" 2>/dev/null) ||
+ skip "cannot describe the IPE keyring"
+case "$keyring_description" in
+ *"keyring: .ipe") ;;
+ *) skip "KEY_SPEC_IPE_KEYRING resolved to an unexpected key" ;;
+esac
+
+keyring_unsealed=$(cat /sys/module/ipe/parameters/keyring_unsealed)
+case "$keyring_unsealed" in
+ Y)
+ [ "$(keyring_key_count)" = 0 ] ||
+ skip "the IPE keyring has already been provisioned"
+ test_unsealed_keyring
+ ;;
+ N)
+ test_sealed_keyring
+ ;;
+ *)
+ ktap_exit_fail_msg \
+ "unexpected ipe.keyring_unsealed value: $keyring_unsealed"
+ ;;
+esac
+
+ktap_finished
--
2.53.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: [PATCH RFC 1/4] ipe: add the .ipe policy keyring
2026-10-19 21:40 ` [PATCH RFC 1/4] ipe: add the .ipe " Manuel Huber via B4 Relay
@ 2026-10-19 21:52 ` Randy Dunlap
2026-10-19 23:15 ` Fan Wu
1 sibling, 0 replies; 14+ messages in thread
From: Randy Dunlap @ 2026-10-19 21:52 UTC (permalink / raw)
To: manuelh, Fan Wu, David Howells, Jarkko Sakkinen
Cc: Christian Brauner, Andrew Halaney, Paul Moore, James Morris,
Serge E. Hallyn, Jonathan Corbet, Shuah Khan, Shuah Khan,
Alexei Starovoitov, Daniel Borkmann, linux-security-module,
keyrings, linux-kselftest, linux-doc, bpf, linux-kernel
On 10/19/26 2:40 PM, Manuel Huber via B4 Relay wrote:
> From: Manuel Huber <manuelh@nvidia.com>
>
> 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.
>
> 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@nvidia.com>
> ---
> 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.
> +
> ipe.success_audit=
> [IPE]
> Format: <bool>
Please also add an entry in kernel-parameters.txt (near the beginning,
in alphabetical order) for what "IPE" means.
--
~Randy
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH RFC 0/4] IPE: add a dedicated policy keyring
2026-10-19 21:40 [PATCH RFC 0/4] IPE: add a dedicated policy keyring Manuel Huber via B4 Relay
` (3 preceding siblings ...)
2026-10-19 21:40 ` [PATCH RFC 4/4] selftests/ipe: test the IPE policy keyring Manuel Huber via B4 Relay
@ 2026-10-19 21:54 ` Jarkko Sakkinen
4 siblings, 0 replies; 14+ messages in thread
From: Jarkko Sakkinen @ 2026-10-19 21:54 UTC (permalink / raw)
To: manuelh
Cc: Fan Wu, David Howells, Christian Brauner, Andrew Halaney,
Paul Moore, James Morris, Serge E. Hallyn, Jonathan Corbet,
Shuah Khan, Shuah Khan, Randy Dunlap, Alexei Starovoitov,
Daniel Borkmann, linux-security-module, keyrings, linux-kselftest,
linux-doc, bpf, linux-kernel
On Mon, Oct 19, 2026 at 09:40:24PM +0000, Manuel Huber via B4 Relay wrote:
> Add a dedicated ".ipe" keyring for IPE policy-signature verification,
> modeled after the dm-verity keyring, allowing userspace to provision
> IPE-specific policy-signing certificates. Systems that do not provision
> the keyring retain their existing behavior.
>
> This RFC is based on commit 48f936e302a9 ("ipe: update the project and
> test suite links") from ipe/next so review can begin before the
> prerequisite keyring work reaches that branch. The series will be rebased
> onto a refreshed ipe/next once that work is available there [1].
>
> The keyring readiness check currently follows the dm-verity and BPF
> implementations. A parallel keys RFC introduces keyring_is_empty() for
> this purpose [2]. A later revision will use the helper if it is available
> in the submission base.
>
> Testing:
> - Built an x86_64 kernel with GCC 15.2.0 and Clang/LLD 21.1.8.
> - Passed all 33 IPE parser KUnit assertions.
> - Passed all 4 sealed-default keyring selftests under QEMU TCG.
> - Passed all 16 provisioning-mode keyring selftests under QEMU TCG with
> ipe.keyring_unsealed=1.
>
> [1] https://lore.kernel.org/r/20261008-ajhalaney-dmverity-key-identifier-v2-0-008456d189cb@amutable.com
> [2] https://lore.kernel.org/r/20261016215326.4110064-5-jarkko@kernel.org
>
> Signed-off-by: Manuel Huber <manuelh@nvidia.com>
> ---
> Manuel Huber (4):
> ipe: add the .ipe policy keyring
> keys: add KEY_SPEC_IPE_KEYRING
> Documentation: ipe: document the IPE keyring
> selftests/ipe: test the IPE policy keyring
>
> Documentation/admin-guide/LSM/ipe.rst | 41 ++-
> Documentation/admin-guide/kernel-parameters.txt | 9 +
> Documentation/security/ipe.rst | 16 +-
> Documentation/security/keys/core.rst | 9 +
> MAINTAINERS | 1 +
> include/linux/key.h | 2 +
> include/uapi/linux/keyctl.h | 1 +
> security/ipe/Makefile | 1 +
> security/ipe/ipe.c | 2 +
> security/ipe/keyring.c | 80 ++++++
> security/ipe/keyring.h | 16 ++
> security/ipe/policy.c | 10 +
> security/keys/process_keys.c | 23 ++
> tools/testing/selftests/Makefile | 1 +
> tools/testing/selftests/ipe/Makefile | 5 +
> tools/testing/selftests/ipe/config | 7 +
> tools/testing/selftests/ipe/test-ipe-keyring.sh | 357 ++++++++++++++++++++++++
> 17 files changed, 575 insertions(+), 6 deletions(-)
> ---
> base-commit: 48f936e302a9c48cfc032eb5944c3a6246bc9043
> change-id: 20261019-b4-ipe-keyring-rfc-b93b9ae129a2
>
> Best regards,
> --
> Manuel Huber <manuelh@nvidia.com>
>
>
We are in the phase of release cycle that it would be hard to find any
time to review this properly so might take a while, or I'll review a
follow-up iteration if other feedback arrives.
Br, Jarkko
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH RFC 1/4] ipe: add the .ipe policy keyring
2026-10-19 21:40 ` [PATCH RFC 1/4] ipe: add the .ipe " Manuel Huber via B4 Relay
2026-10-19 21:52 ` Randy Dunlap
@ 2026-10-19 23:15 ` Fan Wu
2026-10-21 13:54 ` Christian Brauner
1 sibling, 1 reply; 14+ messages in thread
From: Fan Wu @ 2026-10-19 23:15 UTC (permalink / raw)
To: manuelh, David Howells, Jarkko Sakkinen
Cc: Christian Brauner, Andrew Halaney, Paul Moore, James Morris,
Serge E. Hallyn, Jonathan Corbet, Shuah Khan, Shuah Khan,
Randy Dunlap, Alexei Starovoitov, Daniel Borkmann,
linux-security-module, keyrings, linux-kselftest, linux-doc, bpf,
linux-kernel
On 10/19/2026 2:40 PM, Manuel Huber via B4 Relay wrote:
> From: Manuel Huber <manuelh@nvidia.com>
>
> 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@nvidia.com>
> ---
> 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 {
>
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH RFC 2/4] keys: add KEY_SPEC_IPE_KEYRING
2026-10-19 21:40 ` [PATCH RFC 2/4] keys: add KEY_SPEC_IPE_KEYRING Manuel Huber via B4 Relay
@ 2026-10-19 23:24 ` Fan Wu
2026-10-21 13:51 ` Christian Brauner
0 siblings, 1 reply; 14+ messages in thread
From: Fan Wu @ 2026-10-19 23:24 UTC (permalink / raw)
To: manuelh, David Howells, Jarkko Sakkinen
Cc: Christian Brauner, Andrew Halaney, Paul Moore, James Morris,
Serge E. Hallyn, Jonathan Corbet, Shuah Khan, Shuah Khan,
Randy Dunlap, Alexei Starovoitov, Daniel Borkmann,
linux-security-module, keyrings, linux-kselftest, linux-doc, bpf,
linux-kernel
On 10/19/2026 2:40 PM, Manuel Huber via B4 Relay wrote:
> From: Manuel Huber <manuelh@nvidia.com>
>
> The IPE policy keyring is allocated unlinked, so userspace would
> otherwise have to scrape /proc/keys to name it.
>
> Signed-off-by: Manuel Huber <manuelh@nvidia.com>
> ---
I'm ok with this id addition, but why can't Kata use keyctl to find the keyring by name? e.g. keyctl padd asymmetric "" %:.ipe < cert.der.
If the concern is that /proc/keys can be spoofed, that would mean other processes are already running while the keyring is still unsealed, which seems even less appropriate.
Fan
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH RFC 4/4] selftests/ipe: test the IPE policy keyring
2026-10-19 21:40 ` [PATCH RFC 4/4] selftests/ipe: test the IPE policy keyring Manuel Huber via B4 Relay
@ 2026-10-19 23:28 ` Fan Wu
0 siblings, 0 replies; 14+ messages in thread
From: Fan Wu @ 2026-10-19 23:28 UTC (permalink / raw)
To: manuelh, David Howells, Jarkko Sakkinen
Cc: Christian Brauner, Andrew Halaney, Paul Moore, James Morris,
Serge E. Hallyn, Jonathan Corbet, Shuah Khan, Shuah Khan,
Randy Dunlap, Alexei Starovoitov, Daniel Borkmann,
linux-security-module, keyrings, linux-kselftest, linux-doc, bpf,
linux-kernel
On 10/19/2026 2:40 PM, Manuel Huber via B4 Relay wrote:
> From: Manuel Huber <manuelh@nvidia.com>
>
> Add an IPE kselftest that validates policy keyring operations for
> both ipe.keyring_unsealed values 0 and 1.
>
> For the default ipe.keyring_unsealed=0, verify that a) the special key
> ID resolves, b) certificate enrollment is rejected, and c) the keyring
> is not used for policy verification.
> For ipe.keyring_unsealed=1, verify that we can a) provision multiple
> certificates, b) restrict the keyring, then reject unenrolled and
> corrupted signatures, c) authenticate policy loads and updates with
> different enrolled certificates.
>
> Since IPE is built in, keyring restriction is irreversible, which is
> why we can only exercise the initially sealed and unsealed scenarios
> via separate boots. To get full coverage, CI thus needs to spin up
> separate environments with respective parameter values.
>
> Signed-off-by: Manuel Huber <manuelh@nvidia.com>
> ---
IPE doesn't use kselftest. Our tests live in the IPE CI [1], which already has keyring tests for policy signatures [2] and an initrd unit that provisions and seals the .fs-verity keyring [3]. Please add the .ipe cases there instead.
[1] https://github.com/Integrity-Policy-Enforcement/ci
[2] https://github.com/Integrity-Policy-Enforcement/ci/blob/main/suite/cases/policy_signature.py
[3] https://github.com/Integrity-Policy-Enforcement/ci/blob/main/image/initrd/anchor-fsverity.service
Fan
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH RFC 2/4] keys: add KEY_SPEC_IPE_KEYRING
2026-10-19 23:24 ` Fan Wu
@ 2026-10-21 13:51 ` Christian Brauner
0 siblings, 0 replies; 14+ messages in thread
From: Christian Brauner @ 2026-10-21 13:51 UTC (permalink / raw)
To: Fan Wu
Cc: manuelh, David Howells, Jarkko Sakkinen, Andrew Halaney,
Paul Moore, James Morris, Serge E. Hallyn, Jonathan Corbet,
Shuah Khan, Shuah Khan, Randy Dunlap, Alexei Starovoitov,
Daniel Borkmann, linux-security-module, keyrings, linux-kselftest,
linux-doc, bpf, linux-kernel
On Mon, Oct 19, 2026 at 04:24:29PM -0700, Fan Wu wrote:
>
>
> On 10/19/2026 2:40 PM, Manuel Huber via B4 Relay wrote:
> > From: Manuel Huber <manuelh@nvidia.com>
> >
> > The IPE policy keyring is allocated unlinked, so userspace would
> > otherwise have to scrape /proc/keys to name it.
> >
> > Signed-off-by: Manuel Huber <manuelh@nvidia.com>
> > ---
>
> I'm ok with this id addition, but why can't Kata use keyctl to find the keyring by name? e.g. keyctl padd asymmetric "" %:.ipe < cert.der.
>
> If the concern is that /proc/keys can be spoofed, that would mean other processes are already running while the keyring is still unsealed, which seems even less appropriate.
Why do you object to a reliable and trivial way for userspace to
interact with a security sensitive kernel keyring in a dedicated
reserved namespace just for this purpose?
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH RFC 1/4] ipe: add the .ipe policy keyring
2026-10-19 23:15 ` Fan Wu
@ 2026-10-21 13:54 ` Christian Brauner
2026-10-21 16:51 ` Paul Moore
0 siblings, 1 reply; 14+ messages in thread
From: Christian Brauner @ 2026-10-21 13:54 UTC (permalink / raw)
To: Fan Wu
Cc: manuelh, David Howells, Jarkko Sakkinen, Andrew Halaney,
Paul Moore, James Morris, Serge E. Hallyn, Jonathan Corbet,
Shuah Khan, Shuah Khan, Randy Dunlap, Alexei Starovoitov,
Daniel Borkmann, linux-security-module, keyrings, linux-kselftest,
linux-doc, bpf, linux-kernel
On Mon, Oct 19, 2026 at 04:15:57PM -0700, Fan Wu wrote:
>
>
> On 10/19/2026 2:40 PM, Manuel Huber via B4 Relay wrote:
> > From: Manuel Huber <manuelh@nvidia.com>
> >
> > 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@nvidia.com>
> > ---
> > 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.
Please don't start breaking an established pattern that works well.
We're now using this with both bpf and dm-verity with
systemd-keyring-setup. The point is that the keyring is intentionally
inactive unless it is sealed and restricted. That is part of the
security model. fsverity is used as soon as any key is enrolled which
doesn't give userspace a throwaway the key moment.
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH RFC 1/4] ipe: add the .ipe policy keyring
2026-10-21 13:54 ` Christian Brauner
@ 2026-10-21 16:51 ` Paul Moore
2026-10-22 10:18 ` Christian Brauner
0 siblings, 1 reply; 14+ messages in thread
From: Paul Moore @ 2026-10-21 16:51 UTC (permalink / raw)
To: Christian Brauner
Cc: Fan Wu, manuelh, David Howells, Jarkko Sakkinen, Andrew Halaney,
James Morris, Serge E. Hallyn, Jonathan Corbet, Shuah Khan,
Shuah Khan, Randy Dunlap, Alexei Starovoitov, Daniel Borkmann,
linux-security-module, keyrings, linux-kselftest, linux-doc, bpf,
linux-kernel
On Wed, Oct 21, 2026 at 9:54 AM Christian Brauner <brauner@kernel.org> wrote:
> On Mon, Oct 19, 2026 at 04:15:57PM -0700, Fan Wu wrote:
> > On 10/19/2026 2:40 PM, Manuel Huber via B4 Relay wrote:
> > > From: Manuel Huber <manuelh@nvidia.com>
> > >
> > > 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@nvidia.com>
> > > ---
> > > 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.
>
> Please don't start breaking an established pattern that works well.
> We're now using this with both bpf and dm-verity with
> systemd-keyring-setup.
Just a quick point of clarification, you're using the pattern you
described for other keyrings, not necessarily a dedicated IPE keyring
described in this patchset, correct?
I know you/Lennart/systemd made the decision to implement your own
version of IPE instead of working with Fan to augment the existing
implementation to meet your needs, so I'm not sure if you've also done
something with an out-of-tree patch, BPF, etc. to implement your own
keyring.
> The point is that the keyring is intentionally
> inactive unless it is sealed and restricted. That is part of the
> security model. fsverity is used as soon as any key is enrolled which
> doesn't give userspace a throwaway the key moment.
As far as I'm concerned the decision of how IPE uses an IPE keyring is
up to Fan. Of course, fitting in with existing norms is important,
but if it causes IPE or existing IPE uses a lot of pain, that can a
good reason to diverge from those solutions. These days everyone is
going to implement whatever they please, but if you have
needs/requirements about how you want things to behave in LSM land, I
think it would be helpful to engage with us before you make a decision
and then start pleading with the LSM folks to adopt your approach.
--
paul-moore.com
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH RFC 1/4] ipe: add the .ipe policy keyring
2026-10-21 16:51 ` Paul Moore
@ 2026-10-22 10:18 ` Christian Brauner
0 siblings, 0 replies; 14+ messages in thread
From: Christian Brauner @ 2026-10-22 10:18 UTC (permalink / raw)
To: Paul Moore
Cc: Fan Wu, Manuel Huber, David Howells, Jarkko Sakkinen,
Andrew Halaney, James Morris, Serge E. Hallyn, Jonathan Corbet,
Shuah Khan, Shuah Khan, Randy Dunlap, Alexei Starovoitov,
Daniel Borkmann, linux-security-module, keyrings, linux-kselftest,
linux-doc, bpf, linux-kernel
On Wed, Oct 21, 2026 at 12:51:35PM -0400, Paul Moore wrote:
> Just a quick point of clarification, you're using the pattern you
> described for other keyrings, not necessarily a dedicated IPE keyring
> described in this patchset, correct?
Correct. The .dm-verity keyring and the .bpf keyring both work like
this: sealed empty by default, opened for a single boot via a kernel
parameter, and only ever consulted once the keyring is non-empty and
restricted. Manuel modeled the .ipe keyring on them after talking to
Fan.
> I know you/Lennart/systemd made the decision to implement your own
> version of IPE instead of working with Fan to augment the existing
> implementation to meet your needs, so I'm not sure if you've also done
> something with an out-of-tree patch, BPF, etc. to implement your own
> keyring.
There is no out-of-tree anything and there is no keyring of our own.
RestrictFileSystemAccess= is a BPF LSM program that PID 1 loads. It
denies exec, executable mappings and W^X transitions for anything that
isn't on a signed dm-verity device or the initramfs. That's it. It
uses the hooks BPF LSM exposes.
> As far as I'm concerned the decision of how IPE uses an IPE keyring is
> up to Fan.
This is really not an argument. Manuel posted an RFC that follows two
existing in-tree keyrings to the letter. Fan asked to deviate from them.
I explained what that costs.
> Of course, fitting in with existing norms is important,
> but if it causes IPE or existing IPE uses a lot of pain, that can a
> good reason to diverge from those solutions.
Which pain? The keyring is dead unless the kernel command line opens
it, and it is only consulted after the system keyrings have already
rejected the signature. Existing IPE users see no change whatsoever.
Fan's reasons were that it "looks kind of awkward" and that fs-verity
does it differently. I haven't yet heard a single technical reason to
deviate. The properties we get and the other model loses are:
(1) Fail closed by default. Without the boot parameter the keyring is
sealed empty for the whole boot and can never be provisioned. With
the fs-verity model the keyring is open on every kernel that has
it and stays open until someone remembers to restrict it. If
nobody does, root in the "untrusted" phase enrolls its own cert
and loads its own policy.
(2) The opt-in is per boot and lives on the kernel command line, i.e.
in the measured/signed boot chain. A Kconfig knob can't express
"this boot", only "this kernel", and a distro kernel with the
option on is simply open everywhere.
(3) Activation is tied to restriction. The keyring is only consulted
once it can't be extended anymore, so there is never a window
where a half-provisioned keyring is a trust anchor. With the
fs-verity model it is a trust anchor from the first key on and
extensible until sealed.
(4) That's what gives Manuel's Kata case the throwaway key: generate
an ephemeral key pair, enroll the cert, restrict, sign the policy,
load it, destroy the private key. After that nothing can be added
to the keyring and nothing new can be signed. With the fs-verity
model the same sequence leaves the keyring extensible unless the
agent also seals, and the kernel doesn't care whether it did.
Note that IPE's own CI already does padd followed by restrict_keyring
on .fs-verity in the initrd (anchor-fsverity.service). So IPE relies
on exactly this sequence today. The pattern just has the kernel
enforce it instead of hoping that userspace gets it right.
So what does IPE gain from a keyring that is open by default and
consulted before it's sealed?
> These days everyone is
> going to implement whatever they please, but if you have
> needs/requirements about how you want things to behave in LSM land, I
> think it would be helpful to engage with us before you make a decision
> and then start pleading with the LSM folks to adopt your approach.
I'm not pleading with anyone. And the engagement you're asking for
happened. You just didn't engage:
- You read Daniel's bpf keyring series yourself in August, Cc'd the
keyring folks "for obvious reasons", and had nothing to say about
the model. The cover letter spelled out in detail that systemd would
provision that keyring the same way as .dm-verity.
- On 2026-10-11 Manuel announced in the KEY_SPEC thread that he'd
propose a .ipe keyring "modeled after the .dm-verity keyring" after
"initial discussions with Fan Wu". You and linux-security-module
were on Cc.
As for "everyone is going to implement whatever they please"... In May
you told the bpf folks that "we just need to be willing to accept that
we have different needs and show a willingness to accept different
solutions as a result" [1]. I don't see why that shouldn't apply to
systemd using BPF LSM.
Frankly, I couldn't care less whether IPE chooses to model the keyring
the same way. It just means it can't give equivalent security
guarantees. I'm confused why you have to speak for Fu and why you're so
defensive about this.
[1]: https://lore.kernel.org/all/CAHC9VhQ-cq-nD7uq3kv=SFSJyzO_CEgwzFeMcdbznTmPWJoy6g@mail.gmail.com
^ permalink raw reply [flat|nested] 14+ messages in thread
end of thread, other threads:[~2026-10-22 10:19 UTC | newest]
Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-19 21:40 [PATCH RFC 0/4] IPE: add a dedicated policy keyring Manuel Huber via B4 Relay
2026-10-19 21:40 ` [PATCH RFC 1/4] ipe: add the .ipe " Manuel Huber via B4 Relay
2026-10-19 21:52 ` Randy Dunlap
2026-10-19 23:15 ` Fan Wu
2026-10-21 13:54 ` Christian Brauner
2026-10-21 16:51 ` Paul Moore
2026-10-22 10:18 ` Christian Brauner
2026-10-19 21:40 ` [PATCH RFC 2/4] keys: add KEY_SPEC_IPE_KEYRING Manuel Huber via B4 Relay
2026-10-19 23:24 ` Fan Wu
2026-10-21 13:51 ` Christian Brauner
2026-10-19 21:40 ` [PATCH RFC 3/4] Documentation: ipe: document the IPE keyring Manuel Huber via B4 Relay
2026-10-19 21:40 ` [PATCH RFC 4/4] selftests/ipe: test the IPE policy keyring Manuel Huber via B4 Relay
2026-10-19 23:28 ` Fan Wu
2026-10-19 21:54 ` [PATCH RFC 0/4] IPE: add a dedicated " Jarkko Sakkinen
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox