From: Jarkko Sakkinen <jarkko@kernel.org>
To: Liu Mingyu <Edwardliu0214@outlook.com>
Cc: David Howells <dhowells@redhat.com>,
"keyrings@vger.kernel.org" <keyrings@vger.kernel.org>,
"linux-kernel@vger.kernel.org" <linux-kernel@vger.kernel.org>,
Eric Biggers <ebiggers@kernel.org>, Kees Cook <kees@kernel.org>,
Mimi Zohar <zohar@linux.ibm.com>,
Randy Dunlap <rdunlap@infradead.org>
Subject: Re: [PATCH] keys: Enforce keep guard when moving keys
Date: Sat, 30 May 2026 03:49:36 +0300 [thread overview]
Message-ID: <aho0IDYyV4B81KZq@kernel.org> (raw)
In-Reply-To: <ME0P300MB0618CDE63E5F370D2790CDE9AA162@ME0P300MB0618.AUSP300.PROD.OUTLOOK.COM>
On Fri, May 29, 2026 at 04:54:52AM +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.
Got it.
>
> 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 and add
> KUnit coverage for the normal move, protected rejection and no-op paths.
>
> Fixes: ed0ac5c7ec37 ("keys: Add a keyctl to move a key between keyrings")
> Signed-off-by: Mingyu Liu <edwardliu0214@outlook.com>
It'd be better to split kunit into separate patch as otherwise this is
not too fluid to backport.
> ---
> 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
>
> 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/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/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;
> 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");
>
> base-commit: 8fde5d1d47f69db6082dfa34500c27f8485389a5
> --
> 2.51.2.windows.1
>
BR, Jarkko
prev parent reply other threads:[~2026-05-30 0:49 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-05-29 4:54 [PATCH] keys: Enforce keep guard when moving keys Liu Mingyu
2026-05-30 0:49 ` Jarkko Sakkinen [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=aho0IDYyV4B81KZq@kernel.org \
--to=jarkko@kernel.org \
--cc=Edwardliu0214@outlook.com \
--cc=dhowells@redhat.com \
--cc=ebiggers@kernel.org \
--cc=kees@kernel.org \
--cc=keyrings@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=rdunlap@infradead.org \
--cc=zohar@linux.ibm.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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.