From: Chengfeng Ye <nicoyip.dev@gmail.com>
To: David Howells <dhowells@redhat.com>, Jarkko Sakkinen <jarkko@kernel.org>
Cc: Paul Moore <paul@paul-moore.com>,
James Morris <jmorris@namei.org>,
"Serge E . Hallyn" <serge@hallyn.com>,
keyrings@vger.kernel.org, linux-security-module@vger.kernel.org,
linux-kernel@vger.kernel.org,
Chengfeng Ye <nicoyip.dev@gmail.com>,
stable@vger.kernel.org
Subject: [PATCH v3 2/2] keys: Serialize ownership transfers with key accounting
Date: Mon, 28 Sep 2026 00:25:28 +0800 [thread overview]
Message-ID: <20260927162528.943886-3-nicoyip.dev@gmail.com> (raw)
In-Reply-To: <20260927162528.943886-1-nicoyip.dev@gmail.com>
Protecting individual accesses to key->user does not make ownership
transfers atomic with accounting updates. keyctl_chown_key() holds
key->sem, but instantiation is serialized by key_construction_mutex and
need not hold that semaphore. KEY_LOOKUP_PARTIAL also permits chown of
an uninstantiated key.
The instantiated-key count can therefore be charged to the wrong owner:
instantiate keyctl_chown_key()
lock key_user_lock
increment old->nikeys
unlock key_user_lock
observe KEY_IS_UNINSTANTIATED
skip the nikeys transfer
replace key->user
mark key instantiated
The key becomes instantiated under the new owner while the increment
remains with the old owner. Negative instantiation has the same race.
Quota reservation can likewise run between charging the new owner and
replacing key->user. It then adjusts the old owner's quota and changes
key->quotalen while chown is transferring that quota burden.
Extend the key_user_lock critical section in keyctl_chown_key() across
the quota and key-count transfers, state check, and owner replacement.
Also extend the instantiation critical sections across the state update,
so chown observes the count increment and instantiated state together.
The existing per-user quota locks continue to serialize quota changes
against other keys owned by the same user.
Keep allocations, notifications and reference release outside
key_user_lock, and release it on the quota-overrun path.
Fixes: 5801649d8b83 ("[PATCH] keys: let keyctl_chown() change a key's owner")
Cc: stable@vger.kernel.org
Signed-off-by: Chengfeng Ye <nicoyip.dev@gmail.com>
---
Changes in v3:
- Split from v2 as patch 2/2; see the cover letter for the full split.
- Rebase onto current mainline and retain explicit reader-side locking.
v2: https://lore.kernel.org/r/20260904080940.575882-1-nicoyip.dev@gmail.com/
security/keys/key.c | 4 ++--
security/keys/keyctl.c | 6 ++++--
2 files changed, 6 insertions(+), 4 deletions(-)
diff --git a/security/keys/key.c b/security/keys/key.c
index d0d583194b05..54c675b3b58d 100644
--- a/security/keys/key.c
+++ b/security/keys/key.c
@@ -454,8 +454,8 @@ static int __key_instantiate_and_link(struct key *key,
/* mark the key as being instantiated */
spin_lock(&key_user_lock);
atomic_inc(&key->user->nikeys);
- spin_unlock(&key_user_lock);
mark_key_instantiated(key, 0);
+ spin_unlock(&key_user_lock);
notify_key(key, NOTIFY_KEY_INSTANTIATED, 0);
if (test_and_clear_bit(KEY_FLAG_USER_CONSTRUCT, &key->flags))
@@ -613,8 +613,8 @@ int key_reject_and_link(struct key *key,
/* mark the key as being negatively instantiated */
spin_lock(&key_user_lock);
atomic_inc(&key->user->nikeys);
- spin_unlock(&key_user_lock);
mark_key_instantiated(key, -error);
+ spin_unlock(&key_user_lock);
notify_key(key, NOTIFY_KEY_INSTANTIATED, -error);
key_set_expiry(key, ktime_get_real_seconds() + timeout);
diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c
index c17924609317..83a9575b084e 100644
--- a/security/keys/keyctl.c
+++ b/security/keys/keyctl.c
@@ -1004,6 +1004,8 @@ long keyctl_chown_key(key_serial_t id, uid_t user, gid_t group)
if (!newowner)
goto error_put;
+ spin_lock(&key_user_lock);
+
/* transfer the quota burden to the new user */
if (test_bit(KEY_FLAG_IN_QUOTA, &key->flags)) {
unsigned maxkeys = uid_eq(uid, GLOBAL_ROOT_UID) ?
@@ -1036,11 +1038,10 @@ long keyctl_chown_key(key_serial_t id, uid_t user, gid_t group)
atomic_inc(&newowner->nikeys);
}
- spin_lock(&key_user_lock);
zapowner = key->user;
key->user = newowner;
- spin_unlock(&key_user_lock);
key->uid = uid;
+ spin_unlock(&key_user_lock);
}
/* change the GID */
@@ -1060,6 +1061,7 @@ long keyctl_chown_key(key_serial_t id, uid_t user, gid_t group)
quota_overrun:
spin_unlock_irqrestore(&newowner->lock, flags);
+ spin_unlock(&key_user_lock);
zapowner = newowner;
ret = -EDQUOT;
goto error_put;
--
2.43.0
prev parent reply other threads:[~2026-09-27 16:26 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-27 16:25 [PATCH v3 0/2] keys: Fix ownership lifetime and accounting races Chengfeng Ye
2026-09-27 16:25 ` [PATCH v3 1/2] keys: Protect key_user lifetime during ownership changes Chengfeng Ye
2026-09-27 16:25 ` Chengfeng Ye [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=20260927162528.943886-3-nicoyip.dev@gmail.com \
--to=nicoyip.dev@gmail.com \
--cc=dhowells@redhat.com \
--cc=jarkko@kernel.org \
--cc=jmorris@namei.org \
--cc=keyrings@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-security-module@vger.kernel.org \
--cc=paul@paul-moore.com \
--cc=serge@hallyn.com \
--cc=stable@vger.kernel.org \
/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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®