From: Hillf Danton hdanton@sina.com
Once a key's reference count has been reduced to 0, the garbage collector thread may destroy it at any time and so key_put() is not allowed to touch the key after that point. The most key_put() is normally allowed to do is to touch key_gc_work as that's a static global variable.
However, in an effort to speed up the reclamation of quota, this is now done in key_put() once the key's usage is reduced to 0 - but now the code is looking at the key after the deadline, which is forbidden.
Fix this on an expedited basis[*] by taking a ref on the key->user struct and caching the key length before dropping the refcount so that we can reduce the quota afterwards if we dropped the last ref.
[*] This is going to hurt key_put() performance, so a better way is probably necessary, such as sticking the dead key onto a queue for the garbage collector to pick up rather than having it scan the serial number registry.
Fixes: 9578e327b2b4 ("keys: update key quotas in key_put()") Reported-by: syzbot+6105ffc1ded71d194d6d@syzkaller.appspotmail.com Tested-by: syzbot+6105ffc1ded71d194d6d@syzkaller.appspotmail.com Suggested-by: Hillf Danton hdanton@sina.com Signed-off-by: David Howells dhowells@redhat.com cc: Jarkko Sakkinen jarkko@kernel.org cc: Kees Cook kees@kernel.org cc: keyrings@vger.kernel.org cc: stable@vger.kernel.org # v6.10+ --- security/keys/key.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-)
diff --git a/security/keys/key.c b/security/keys/key.c index 3d7d185019d3..1e6028492355 100644 --- a/security/keys/key.c +++ b/security/keys/key.c @@ -645,21 +645,30 @@ EXPORT_SYMBOL(key_reject_and_link); */ void key_put(struct key *key) { + int quota_flag; + unsigned short len; + struct key_user *user; + if (key) { key_check(key);
+ quota_flag = test_bit(KEY_FLAG_IN_QUOTA, &key->flags); + len = key->quotalen; + user = key->user; + refcount_inc(&user->usage); if (refcount_dec_and_test(&key->usage)) { unsigned long flags;
/* deal with the user's key tracking and quota */ - if (test_bit(KEY_FLAG_IN_QUOTA, &key->flags)) { - spin_lock_irqsave(&key->user->lock, flags); - key->user->qnkeys--; - key->user->qnbytes -= key->quotalen; - spin_unlock_irqrestore(&key->user->lock, flags); + if (quota_flag) { + spin_lock_irqsave(&user->lock, flags); + user->qnkeys--; + user->qnbytes -= len; + spin_unlock_irqrestore(&user->lock, flags); } schedule_work(&key_gc_work); } + key_user_put(user); } } EXPORT_SYMBOL(key_put);
On 03/18, David Howells wrote:
--- a/security/keys/key.c +++ b/security/keys/key.c @@ -645,21 +645,30 @@ EXPORT_SYMBOL(key_reject_and_link); */ void key_put(struct key *key) {
- int quota_flag;
- unsigned short len;
- struct key_user *user;
- if (key) { key_check(key);
quota_flag = test_bit(KEY_FLAG_IN_QUOTA, &key->flags);
len = key->quotalen;
user = key->user;
if (refcount_dec_and_test(&key->usage)) { unsigned long flags;refcount_inc(&user->usage);
/* deal with the user's key tracking and quota */
if (test_bit(KEY_FLAG_IN_QUOTA, &key->flags)) {
spin_lock_irqsave(&key->user->lock, flags);
key->user->qnkeys--;
key->user->qnbytes -= key->quotalen;
spin_unlock_irqrestore(&key->user->lock, flags);
if (quota_flag) {
spin_lock_irqsave(&user->lock, flags);
user->qnkeys--;
user->qnbytes -= len;
}spin_unlock_irqrestore(&user->lock, flags); } schedule_work(&key_gc_work);
key_user_put(user);
Do we really need the unconditional refcount_inc / key_user_put ?
void key_put(struct key *key) { if (key) { struct key_user *user = NULL; unsigned short len;
key_check(key);
if (test_bit(KEY_FLAG_IN_QUOTA, &key->flags)) { len = key->quotalen; user = key->user; refcount_inc(&user->usage); }
if (refcount_dec_and_test(&key->usage)) { unsigned long flags;
/* deal with the user's key tracking and quota */ if (user) { spin_lock_irqsave(&user->lock, flags); user->qnkeys--; user->qnbytes -= len; spin_unlock_irqrestore(&user->lock, flags); } schedule_work(&key_gc_work); }
if (user) key_user_put(user); } }
looks a bit more clear/simple to me...
Oleg.
Either way... I know nothing about security/key, but grep -w key_put finds
* When it is no longer required, the key should be released using::
void key_put(struct key *key);
Or::
void key_ref_put(key_ref_t key_ref);
These can be called from interrupt context. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
in Documentation/security/keys/core.rst
and since key_user_put() takes key_user_lock with irqs enabled, key_put()->key_user_put() doesn't look correct...
Oleg.
On 03/18, Oleg Nesterov wrote:
On 03/18, David Howells wrote:
--- a/security/keys/key.c +++ b/security/keys/key.c @@ -645,21 +645,30 @@ EXPORT_SYMBOL(key_reject_and_link); */ void key_put(struct key *key) {
int quota_flag;
unsigned short len;
struct key_user *user;
if (key) { key_check(key);
quota_flag = test_bit(KEY_FLAG_IN_QUOTA, &key->flags);
len = key->quotalen;
user = key->user;
refcount_inc(&user->usage);
if (refcount_dec_and_test(&key->usage)) { unsigned long flags;
/* deal with the user's key tracking and quota */
if (test_bit(KEY_FLAG_IN_QUOTA, &key->flags)) {
spin_lock_irqsave(&key->user->lock, flags);
key->user->qnkeys--;
key->user->qnbytes -= key->quotalen;
spin_unlock_irqrestore(&key->user->lock, flags);
if (quota_flag) {
spin_lock_irqsave(&user->lock, flags);
user->qnkeys--;
user->qnbytes -= len;
}spin_unlock_irqrestore(&user->lock, flags); } schedule_work(&key_gc_work);
key_user_put(user);
Do we really need the unconditional refcount_inc / key_user_put ?
void key_put(struct key *key) { if (key) { struct key_user *user = NULL; unsigned short len;
key_check(key); if (test_bit(KEY_FLAG_IN_QUOTA, &key->flags)) { len = key->quotalen; user = key->user; refcount_inc(&user->usage); } if (refcount_dec_and_test(&key->usage)) { unsigned long flags; /* deal with the user's key tracking and quota */ if (user) { spin_lock_irqsave(&user->lock, flags); user->qnkeys--; user->qnbytes -= len; spin_unlock_irqrestore(&user->lock, flags); } schedule_work(&key_gc_work); } if (user) key_user_put(user); }
}
looks a bit more clear/simple to me...
Oleg.
Oleg Nesterov oleg@redhat.com wrote:
and since key_user_put() takes key_user_lock with irqs enabled, key_put()->key_user_put() doesn't look correct...
Meh. Yeah. I think it's time to do it the other way (i.e. putting keys to be destroyed onto an explicit cleanup queue).
David
linux-stable-mirror@lists.linaro.org