* [PATCH v2 0/2] keys: Enforce keep guard when moving keys
@ 2026-05-31 20:51 Liu Mingyu
2026-05-31 20:51 ` [PATCH v2 1/2] " Liu Mingyu
2026-05-31 20:51 ` [PATCH v2 2/2] keys: Add KUnit coverage for KEYCTL_MOVE keep guard Liu Mingyu
0 siblings, 2 replies; 4+ messages in thread
From: Liu Mingyu @ 2026-05-31 20:51 UTC (permalink / raw)
To: David Howells, Jarkko Sakkinen
Cc: keyrings@vger.kernel.org, linux-kernel@vger.kernel.org,
Eric Biggers, Kees Cook, Mimi Zohar, Randy Dunlap
Split the KEYCTL_MOVE fix from its KUnit coverage so the fix can be
backported without pulling in the test module.
Patch 1 enforces the KEEP removal guard in key_move() and documents the
new -EPERM return path. Patch 2 adds KUnit coverage for the normal move,
protected rejection and same-keyring no-op paths.
Changes in v2:
- Split KUnit coverage into a separate patch as requested by Jarkko.
- Keep the fix patch limited to code and documentation for backporting.
- Add a stable Cc trailer to the fix patch.
Mingyu Liu (2):
keys: Enforce keep guard when moving keys
keys: Add KUnit coverage for KEYCTL_MOVE keep guard
Documentation/security/keys/core.rst | 1 +
security/keys/Kconfig | 13 +++
security/keys/Makefile | 1 +
security/keys/keyctl.c | 2 +
security/keys/keyring.c | 7 +-
security/keys/keyring_test.c | 121 +++++++++++++++++++++++++++
6 files changed, 144 insertions(+), 1 deletion(-)
create mode 100644 security/keys/keyring_test.c
base-commit: 8fde5d1d47f69db6082dfa34500c27f8485389a5
--
2.51.2.windows.1
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH v2 1/2] keys: Enforce keep guard when moving keys
2026-05-31 20:51 [PATCH v2 0/2] keys: Enforce keep guard when moving keys Liu Mingyu
@ 2026-05-31 20:51 ` Liu Mingyu
2026-06-08 3:45 ` Jarkko Sakkinen
2026-05-31 20:51 ` [PATCH v2 2/2] keys: Add KUnit coverage for KEYCTL_MOVE keep guard Liu Mingyu
1 sibling, 1 reply; 4+ messages in thread
From: Liu Mingyu @ 2026-05-31 20:51 UTC (permalink / raw)
To: David Howells, Jarkko Sakkinen
Cc: keyrings@vger.kernel.org, linux-kernel@vger.kernel.org,
Eric Biggers, Kees Cook, Mimi Zohar, Randy Dunlap
KEYCTL_MOVE removes the source link as part of moving a key
between keyrings. key_unlink() rejects removal of a
KEY_FLAG_KEEP-protected key from a KEY_FLAG_KEEP-protected keyring,
but key_move() did not enforce the same rule.
Reject such moves with -EPERM when both the source keyring and key
are protected. Leave same-keyring moves as a no-op so callers can
continue to use KEYCTL_MOVE idempotently. Document the errno so
callers can distinguish the protected removal case.
Fixes: ed0ac5c7ec37 ("keys: Add a keyctl to move a key between keyrings")
Cc: stable@vger.kernel.org
Signed-off-by: Mingyu Liu <edwardliu0214@outlook.com>
---
Documentation/security/keys/core.rst | 1 +
security/keys/keyctl.c | 2 ++
security/keys/keyring.c | 7 ++++++-
3 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/Documentation/security/keys/core.rst b/Documentation/security/keys/core.rst
index 326b8a973828..6096ce6c63da 100644
--- a/Documentation/security/keys/core.rst
+++ b/Documentation/security/keys/core.rst
@@ -600,6 +600,7 @@ The keyctl syscall functions are:
A process must have link permission on the key for this function to be
successful and write permission on both keyrings. Any errors that can
occur from KEYCTL_LINK also apply on the destination keyring here.
+ If the key and source keyring are protected, EPERM will be returned.
* Unlink a key or keyring from another keyring::
diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c
index ef855d69c97a..b37bf1505ec5 100644
--- a/security/keys/keyctl.c
+++ b/security/keys/keyctl.c
@@ -590,6 +590,8 @@ long keyctl_keyring_unlink(key_serial_t id, key_serial_t ringid)
* the caller Write permission. There must also be a link in the from keyring
* to the key. If both keyrings are the same, nothing is done.
*
+ * If the key and source keyring are protected, -EPERM will be returned.
+ *
* If successful, 0 will be returned.
*/
long keyctl_keyring_move(key_serial_t id, key_serial_t from_ringid,
diff --git a/security/keys/keyring.c b/security/keys/keyring.c
index 5a9887d6b7be..60c184bd9a8d 100644
--- a/security/keys/keyring.c
+++ b/security/keys/keyring.c
@@ -1582,7 +1582,8 @@ EXPORT_SYMBOL(key_unlink);
*
* Returns 0 if successful, -ENOTDIR if either keyring isn't a keyring,
* -EKEYREVOKED if either keyring has been revoked, -ENFILE if the second
- * keyring is full, -EDQUOT if there is insufficient key data quota remaining
+ * keyring is full, -EPERM if this would remove a protected key from a
+ * protected keyring, -EDQUOT if there is insufficient key data quota remaining
* to add another link or -ENOMEM if there's insufficient memory. If
* KEYCTL_MOVE_EXCL is set, then -EEXIST will be returned if there's already a
* matching key in @to_keyring.
@@ -1608,6 +1609,10 @@ int key_move(struct key *key,
key_check(from_keyring);
key_check(to_keyring);
+ if (test_bit(KEY_FLAG_KEEP, &from_keyring->flags) &&
+ test_bit(KEY_FLAG_KEEP, &key->flags))
+ return -EPERM;
+
ret = __key_move_lock(from_keyring, to_keyring, &key->index_key);
if (ret < 0)
goto out;
--
2.51.2.windows.1
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH v2 2/2] keys: Add KUnit coverage for KEYCTL_MOVE keep guard
2026-05-31 20:51 [PATCH v2 0/2] keys: Enforce keep guard when moving keys Liu Mingyu
2026-05-31 20:51 ` [PATCH v2 1/2] " Liu Mingyu
@ 2026-05-31 20:51 ` Liu Mingyu
1 sibling, 0 replies; 4+ messages in thread
From: Liu Mingyu @ 2026-05-31 20:51 UTC (permalink / raw)
To: David Howells, Jarkko Sakkinen
Cc: keyrings@vger.kernel.org, linux-kernel@vger.kernel.org,
Eric Biggers, Kees Cook, Mimi Zohar, Randy Dunlap
Add keyring KUnit tests that exercise successful key_move(), rejection
of moving a protected key out of a protected source keyring, and the
same-keyring no-op path.
Keep the test-only infrastructure separate from the KEYCTL_MOVE fix so
the fix can be backported without pulling in the KUnit module.
Signed-off-by: Mingyu Liu <edwardliu0214@outlook.com>
---
security/keys/Kconfig | 13 ++++
security/keys/Makefile | 1 +
security/keys/keyring_test.c | 121 +++++++++++++++++++++++++++++++++++
3 files changed, 135 insertions(+)
create mode 100644 security/keys/keyring_test.c
diff --git a/security/keys/Kconfig b/security/keys/Kconfig
index 84f39e50ca36..acffb5f7385c 100644
--- a/security/keys/Kconfig
+++ b/security/keys/Kconfig
@@ -129,4 +129,17 @@ config KEY_NOTIFICATIONS
This makes use of pipes to handle the notification buffer and
provides KEYCTL_WATCH_KEY to enable/disable watches.
+config KEYS_KUNIT_TEST
+ tristate "KUnit tests for keyrings" if !KUNIT_ALL_TESTS
+ depends on KUNIT
+ default KUNIT_ALL_TESTS
+ help
+ Build KUnit tests for keyring operations.
+ These tests exercise keyring link and move behavior, including
+ protection of KEY_FLAG_KEEP entries.
+ They are intended for KUnit runs on developer kernels and are not
+ needed for normal systems.
+
+ If you are unsure how to answer this question, answer N.
+
endif # KEYS
diff --git a/security/keys/Makefile b/security/keys/Makefile
index 5f40807f05b3..fa583a4ea945 100644
--- a/security/keys/Makefile
+++ b/security/keys/Makefile
@@ -23,6 +23,7 @@ obj-$(CONFIG_SYSCTL) += sysctl.o
obj-$(CONFIG_PERSISTENT_KEYRINGS) += persistent.o
obj-$(CONFIG_KEY_DH_OPERATIONS) += dh.o
obj-$(CONFIG_ASYMMETRIC_KEY_TYPE) += keyctl_pkey.o
+obj-$(CONFIG_KEYS_KUNIT_TEST) += keyring_test.o
#
# Key types
diff --git a/security/keys/keyring_test.c b/security/keys/keyring_test.c
new file mode 100644
index 000000000000..0055b50224e9
--- /dev/null
+++ b/security/keys/keyring_test.c
@@ -0,0 +1,121 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * KUnit tests for keyring operations.
+ */
+
+#include <keys/user-type.h>
+#include <kunit/test.h>
+#include <linux/cred.h>
+#include <linux/err.h>
+#include <linux/key.h>
+#include <linux/key-type.h>
+#include <linux/keyctl.h>
+#include <linux/module.h>
+#include <linux/uidgid.h>
+
+static void keyring_test_key_put(void *data)
+{
+ key_put(data);
+}
+
+static struct key *test_keyring_alloc(struct kunit *test, const char *desc,
+ unsigned long flags)
+{
+ struct key *keyring;
+
+ keyring = keyring_alloc(desc, GLOBAL_ROOT_UID, GLOBAL_ROOT_GID,
+ current_cred(), KEY_POS_ALL | KEY_USR_ALL,
+ KEY_ALLOC_NOT_IN_QUOTA | flags, NULL, NULL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, keyring);
+ KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test,
+ keyring_test_key_put,
+ keyring), 0);
+
+ return keyring;
+}
+
+static struct key *test_user_key_alloc(struct kunit *test, const char *desc,
+ struct key *keyring,
+ unsigned long flags)
+{
+ static const char payload[] = "payload";
+ struct key *key;
+ int ret;
+
+ key = key_alloc(&key_type_user, desc, GLOBAL_ROOT_UID, GLOBAL_ROOT_GID,
+ current_cred(), KEY_POS_ALL | KEY_USR_ALL,
+ KEY_ALLOC_NOT_IN_QUOTA | flags, NULL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, key);
+ KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test,
+ keyring_test_key_put,
+ key), 0);
+
+ ret = key_instantiate_and_link(key, payload, sizeof(payload),
+ keyring, NULL);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ return key;
+}
+
+static void keyring_move_user_key(struct kunit *test)
+{
+ struct key *from, *to, *key;
+ int ret;
+
+ from = test_keyring_alloc(test, "move-from", 0);
+ to = test_keyring_alloc(test, "move-to", 0);
+ key = test_user_key_alloc(test, "move-key", from, 0);
+
+ ret = key_move(key, from, to, 0);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+
+ ret = key_move(key, to, from, 0);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+}
+
+static void keyring_move_keep_key_fails(struct kunit *test)
+{
+ struct key *from, *to, *key;
+ int ret;
+
+ from = test_keyring_alloc(test, "keep-from", KEY_ALLOC_SET_KEEP);
+ to = test_keyring_alloc(test, "keep-to", 0);
+ key = test_user_key_alloc(test, "keep-key", from, 0);
+
+ KUNIT_ASSERT_TRUE(test, test_bit(KEY_FLAG_KEEP, &from->flags));
+ KUNIT_ASSERT_TRUE(test, test_bit(KEY_FLAG_KEEP, &key->flags));
+
+ ret = key_move(key, from, to, 0);
+ KUNIT_EXPECT_EQ(test, ret, -EPERM);
+
+ ret = key_move(key, to, from, 0);
+ KUNIT_EXPECT_EQ(test, ret, -ENOENT);
+}
+
+static void keyring_move_keep_same_keyring(struct kunit *test)
+{
+ struct key *keyring, *key;
+ int ret;
+
+ keyring = test_keyring_alloc(test, "keep-same", KEY_ALLOC_SET_KEEP);
+ key = test_user_key_alloc(test, "keep-same-key", keyring, 0);
+
+ ret = key_move(key, keyring, keyring, 0);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+}
+
+static struct kunit_case keyring_test_cases[] = {
+ KUNIT_CASE(keyring_move_user_key),
+ KUNIT_CASE(keyring_move_keep_key_fails),
+ KUNIT_CASE(keyring_move_keep_same_keyring),
+ {}
+};
+
+static struct kunit_suite keyring_test_suite = {
+ .name = "keyring",
+ .test_cases = keyring_test_cases,
+};
+
+kunit_test_suite(keyring_test_suite);
+
+MODULE_LICENSE("GPL");
--
2.51.2.windows.1
^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH v2 1/2] keys: Enforce keep guard when moving keys
2026-05-31 20:51 ` [PATCH v2 1/2] " Liu Mingyu
@ 2026-06-08 3:45 ` Jarkko Sakkinen
0 siblings, 0 replies; 4+ messages in thread
From: Jarkko Sakkinen @ 2026-06-08 3:45 UTC (permalink / raw)
To: Liu Mingyu
Cc: David Howells, keyrings@vger.kernel.org,
linux-kernel@vger.kernel.org, Eric Biggers, Kees Cook, Mimi Zohar,
Randy Dunlap
On Sun, May 31, 2026 at 08:51:49PM +0000, Liu Mingyu wrote:
> KEYCTL_MOVE removes the source link as part of moving a key
> between keyrings. key_unlink() rejects removal of a
> KEY_FLAG_KEEP-protected key from a KEY_FLAG_KEEP-protected keyring,
> but key_move() did not enforce the same rule.
>
> Reject such moves with -EPERM when both the source keyring and key
"Enforce the same resriction in key_move() with -EPERM ..."?
> are protected. Leave same-keyring moves as a no-op so callers can
> continue to use KEYCTL_MOVE idempotently. Document the errno so
> callers can distinguish the protected removal case.
>
> Fixes: ed0ac5c7ec37 ("keys: Add a keyctl to move a key between keyrings")
> Cc: stable@vger.kernel.org
> Signed-off-by: Mingyu Liu <edwardliu0214@outlook.com>
> ---
> Documentation/security/keys/core.rst | 1 +
> security/keys/keyctl.c | 2 ++
> security/keys/keyring.c | 7 ++++++-
> 3 files changed, 9 insertions(+), 1 deletion(-)
>
> diff --git a/Documentation/security/keys/core.rst b/Documentation/security/keys/core.rst
> index 326b8a973828..6096ce6c63da 100644
> --- a/Documentation/security/keys/core.rst
> +++ b/Documentation/security/keys/core.rst
> @@ -600,6 +600,7 @@ The keyctl syscall functions are:
> A process must have link permission on the key for this function to be
> successful and write permission on both keyrings. Any errors that can
> occur from KEYCTL_LINK also apply on the destination keyring here.
> + If the key and source keyring are protected, EPERM will be returned.
>
>
> * Unlink a key or keyring from another keyring::
> diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c
> index ef855d69c97a..b37bf1505ec5 100644
> --- a/security/keys/keyctl.c
> +++ b/security/keys/keyctl.c
> @@ -590,6 +590,8 @@ long keyctl_keyring_unlink(key_serial_t id, key_serial_t ringid)
> * the caller Write permission. There must also be a link in the from keyring
> * to the key. If both keyrings are the same, nothing is done.
> *
> + * If the key and source keyring are protected, -EPERM will be returned.
> + *
> * If successful, 0 will be returned.
> */
> long keyctl_keyring_move(key_serial_t id, key_serial_t from_ringid,
> diff --git a/security/keys/keyring.c b/security/keys/keyring.c
> index 5a9887d6b7be..60c184bd9a8d 100644
> --- a/security/keys/keyring.c
> +++ b/security/keys/keyring.c
> @@ -1582,7 +1582,8 @@ EXPORT_SYMBOL(key_unlink);
> *
> * Returns 0 if successful, -ENOTDIR if either keyring isn't a keyring,
> * -EKEYREVOKED if either keyring has been revoked, -ENFILE if the second
> - * keyring is full, -EDQUOT if there is insufficient key data quota remaining
> + * keyring is full, -EPERM if this would remove a protected key from a
> + * protected keyring, -EDQUOT if there is insufficient key data quota remaining
> * to add another link or -ENOMEM if there's insufficient memory. If
> * KEYCTL_MOVE_EXCL is set, then -EEXIST will be returned if there's already a
> * matching key in @to_keyring.
> @@ -1608,6 +1609,10 @@ int key_move(struct key *key,
> key_check(from_keyring);
> key_check(to_keyring);
>
> + if (test_bit(KEY_FLAG_KEEP, &from_keyring->flags) &&
> + test_bit(KEY_FLAG_KEEP, &key->flags))
> + return -EPERM;
> +
> ret = __key_move_lock(from_keyring, to_keyring, &key->index_key);
> if (ret < 0)
> goto out;
> --
> 2.51.2.windows.1
>
That would makeobvious that something is missing here.
BR, Jarkko
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-06-08 3:45 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-31 20:51 [PATCH v2 0/2] keys: Enforce keep guard when moving keys Liu Mingyu
2026-05-31 20:51 ` [PATCH v2 1/2] " Liu Mingyu
2026-06-08 3:45 ` Jarkko Sakkinen
2026-05-31 20:51 ` [PATCH v2 2/2] keys: Add KUnit coverage for KEYCTL_MOVE keep guard Liu Mingyu
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.