diff mbox series

[v3] keys: update key quotas in key_put()

Message ID 20240130101344.28936-1-lhenriques@suse.de (mailing list archive)
State New
Headers show
Series [v3] keys: update key quotas in key_put() | expand

Commit Message

Luis Henriques Jan. 30, 2024, 10:13 a.m. UTC
Delaying key quotas update when key's refcount reaches 0 in key_put() has
been causing some issues in fscrypt testing, specifically in fstest
generic/581.  This commit fixes this test flakiness by dealing with the
quotas immediately, and leaving all the other clean-ups to the key garbage
collector.

This is done by moving the updates to the qnkeys and qnbytes fields in
struct key_user from key_gc_unused_keys() into key_put().  Unfortunately,
this also means that we need to switch to the irq-version of the spinlock
that protects these fields and use spin_lock_{irqsave,irqrestore} in all the
code that touches these fields.

Signed-off-by: Luis Henriques <lhenriques@suse.de>
---
Changes since v2:
- Updated commit message as suggested by Jarkko, adding details on the
  implementation

 security/keys/gc.c     |  8 --------
 security/keys/key.c    | 32 ++++++++++++++++++++++----------
 security/keys/keyctl.c | 11 ++++++-----
 3 files changed, 28 insertions(+), 23 deletions(-)

Comments

Jarkko Sakkinen Jan. 30, 2024, 5:27 p.m. UTC | #1
On Tue Jan 30, 2024 at 12:13 PM EET, Luis Henriques wrote:
> Delaying key quotas update when key's refcount reaches 0 in key_put() has
> been causing some issues in fscrypt testing, specifically in fstest
> generic/581.  This commit fixes this test flakiness by dealing with the
> quotas immediately, and leaving all the other clean-ups to the key garbage
> collector.
>
> This is done by moving the updates to the qnkeys and qnbytes fields in
> struct key_user from key_gc_unused_keys() into key_put().  Unfortunately,
> this also means that we need to switch to the irq-version of the spinlock
> that protects these fields and use spin_lock_{irqsave,irqrestore} in all the
> code that touches these fields.
>
> Signed-off-by: Luis Henriques <lhenriques@suse.de>

OK this is great. I mean in this commit it is pretty essentiual to
document that there is an ownership change. Such changes have by
far the biggest impact to kernel semantics, and thus very useful
to mark such commits for e.g. bisection.

Reviewed-by: Jarkko Sakkinen <jarkko@kernel.org>

BR, Jarkko
David Howells Feb. 5, 2024, 12:04 p.m. UTC | #2
Luis Henriques <lhenriques@suse.de> wrote:

> Delaying key quotas update when key's refcount reaches 0 in key_put() has
> been causing some issues in fscrypt testing, specifically in fstest
> generic/581.  This commit fixes this test flakiness by dealing with the
> quotas immediately, and leaving all the other clean-ups to the key garbage
> collector.

Okay, I'll accept this.

> This is done by moving the updates to the qnkeys and qnbytes fields in
> struct key_user from key_gc_unused_keys() into key_put().  Unfortunately,
> this also means that we need to switch to the irq-version of the spinlock
> that protects these fields and use spin_lock_{irqsave,irqrestore} in all the
> code that touches these fields.

... Which shouldn't be that often.  It only happens when a key is created or
finally let go of.

> Signed-off-by: Luis Henriques <lhenriques@suse.de>

Acked-by: David Howells <dhowells@redhat.com>

Jarkko - could you pick this up?
Luis Henriques Feb. 5, 2024, 1:54 p.m. UTC | #3
David Howells <dhowells@redhat.com> writes:

> Luis Henriques <lhenriques@suse.de> wrote:
>
>> Delaying key quotas update when key's refcount reaches 0 in key_put() has
>> been causing some issues in fscrypt testing, specifically in fstest
>> generic/581.  This commit fixes this test flakiness by dealing with the
>> quotas immediately, and leaving all the other clean-ups to the key garbage
>> collector.
>
> Okay, I'll accept this.
>

That's awesome, thanks a lot David.  And, as Eric requested, I'll send out
shortly a follow-up fscrypt-specific patch, which will make generic/581
fstest finally pass.

Cheers,
Luis Henriques (SUSE) March 13, 2024, 12:37 p.m. UTC | #4
Luis Henriques <lhenriques@suse.de> writes:

> David Howells <dhowells@redhat.com> writes:
>
>> Luis Henriques <lhenriques@suse.de> wrote:
>>
>>> Delaying key quotas update when key's refcount reaches 0 in key_put() has
>>> been causing some issues in fscrypt testing, specifically in fstest
>>> generic/581.  This commit fixes this test flakiness by dealing with the
>>> quotas immediately, and leaving all the other clean-ups to the key garbage
>>> collector.
>>
>> Okay, I'll accept this.
>>
>
> That's awesome, thanks a lot David.  And, as Eric requested, I'll send out
> shortly a follow-up fscrypt-specific patch, which will make generic/581
> fstest finally pass.

Ping.  Looks like this fell through the cracks...?

I took a quick look at some git trees ('jarkko' and 'dhowells') but
couldn't see this patch anywhere.

Cheers,
Jarkko Sakkinen March 18, 2024, 9:14 p.m. UTC | #5
On Wed Mar 13, 2024 at 2:37 PM EET, Luis Henriques wrote:
> Luis Henriques <lhenriques@suse.de> writes:
>
> > David Howells <dhowells@redhat.com> writes:
> >
> >> Luis Henriques <lhenriques@suse.de> wrote:
> >>
> >>> Delaying key quotas update when key's refcount reaches 0 in key_put() has
> >>> been causing some issues in fscrypt testing, specifically in fstest
> >>> generic/581.  This commit fixes this test flakiness by dealing with the
> >>> quotas immediately, and leaving all the other clean-ups to the key garbage
> >>> collector.
> >>
> >> Okay, I'll accept this.
> >>
> >
> > That's awesome, thanks a lot David.  And, as Eric requested, I'll send out
> > shortly a follow-up fscrypt-specific patch, which will make generic/581
> > fstest finally pass.
>
> Ping.  Looks like this fell through the cracks...?
>
> I took a quick look at some git trees ('jarkko' and 'dhowells') but
> couldn't see this patch anywhere.
>
> Cheers,

My bad! I'll pick this up now.

BR, Jarkko
Jarkko Sakkinen March 18, 2024, 9:37 p.m. UTC | #6
On Mon Mar 18, 2024 at 11:14 PM EET, Jarkko Sakkinen wrote:
> On Wed Mar 13, 2024 at 2:37 PM EET, Luis Henriques wrote:
> > Luis Henriques <lhenriques@suse.de> writes:
> >
> > > David Howells <dhowells@redhat.com> writes:
> > >
> > >> Luis Henriques <lhenriques@suse.de> wrote:
> > >>
> > >>> Delaying key quotas update when key's refcount reaches 0 in key_put() has
> > >>> been causing some issues in fscrypt testing, specifically in fstest
> > >>> generic/581.  This commit fixes this test flakiness by dealing with the
> > >>> quotas immediately, and leaving all the other clean-ups to the key garbage
> > >>> collector.
> > >>
> > >> Okay, I'll accept this.
> > >>
> > >
> > > That's awesome, thanks a lot David.  And, as Eric requested, I'll send out
> > > shortly a follow-up fscrypt-specific patch, which will make generic/581
> > > fstest finally pass.
> >
> > Ping.  Looks like this fell through the cracks...?
> >
> > I took a quick look at some git trees ('jarkko' and 'dhowells') but
> > couldn't see this patch anywhere.
> >
> > Cheers,
>
> My bad! I'll pick this up now.

I applied it.

Only for completeness sake I mention that I tuned the 2nd paragraph so
that lines do no exceed 75 characters :-) Otherwise, I did not modify
the commit.

I'll schedule this to a PR after rc1 is out (just to see if there is
something else that needs to be picked before that).

BR, Jarkko
Luis Henriques (SUSE) March 18, 2024, 9:38 p.m. UTC | #7
"Jarkko Sakkinen" <jarkko@kernel.org> writes:

> On Wed Mar 13, 2024 at 2:37 PM EET, Luis Henriques wrote:
>> Luis Henriques <lhenriques@suse.de> writes:
>>
>> > David Howells <dhowells@redhat.com> writes:
>> >
>> >> Luis Henriques <lhenriques@suse.de> wrote:
>> >>
>> >>> Delaying key quotas update when key's refcount reaches 0 in key_put() has
>> >>> been causing some issues in fscrypt testing, specifically in fstest
>> >>> generic/581.  This commit fixes this test flakiness by dealing with the
>> >>> quotas immediately, and leaving all the other clean-ups to the key garbage
>> >>> collector.
>> >>
>> >> Okay, I'll accept this.
>> >>
>> >
>> > That's awesome, thanks a lot David.  And, as Eric requested, I'll send out
>> > shortly a follow-up fscrypt-specific patch, which will make generic/581
>> > fstest finally pass.
>>
>> Ping.  Looks like this fell through the cracks...?
>>
>> I took a quick look at some git trees ('jarkko' and 'dhowells') but
>> couldn't see this patch anywhere.
>>
>> Cheers,
>
> My bad! I'll pick this up now.

Awesome, thanks a lot Jarkko.

Cheers,
diff mbox series

Patch

diff --git a/security/keys/gc.c b/security/keys/gc.c
index eaddaceda14e..7d687b0962b1 100644
--- a/security/keys/gc.c
+++ b/security/keys/gc.c
@@ -155,14 +155,6 @@  static noinline void key_gc_unused_keys(struct list_head *keys)
 
 		security_key_free(key);
 
-		/* deal with the user's key tracking and quota */
-		if (test_bit(KEY_FLAG_IN_QUOTA, &key->flags)) {
-			spin_lock(&key->user->lock);
-			key->user->qnkeys--;
-			key->user->qnbytes -= key->quotalen;
-			spin_unlock(&key->user->lock);
-		}
-
 		atomic_dec(&key->user->nkeys);
 		if (state != KEY_IS_UNINSTANTIATED)
 			atomic_dec(&key->user->nikeys);
diff --git a/security/keys/key.c b/security/keys/key.c
index 5b10641debd5..ec155cfaae38 100644
--- a/security/keys/key.c
+++ b/security/keys/key.c
@@ -231,6 +231,7 @@  struct key *key_alloc(struct key_type *type, const char *desc,
 	struct key *key;
 	size_t desclen, quotalen;
 	int ret;
+	unsigned long irqflags;
 
 	key = ERR_PTR(-EINVAL);
 	if (!desc || !*desc)
@@ -260,7 +261,7 @@  struct key *key_alloc(struct key_type *type, const char *desc,
 		unsigned maxbytes = uid_eq(uid, GLOBAL_ROOT_UID) ?
 			key_quota_root_maxbytes : key_quota_maxbytes;
 
-		spin_lock(&user->lock);
+		spin_lock_irqsave(&user->lock, irqflags);
 		if (!(flags & KEY_ALLOC_QUOTA_OVERRUN)) {
 			if (user->qnkeys + 1 > maxkeys ||
 			    user->qnbytes + quotalen > maxbytes ||
@@ -270,7 +271,7 @@  struct key *key_alloc(struct key_type *type, const char *desc,
 
 		user->qnkeys++;
 		user->qnbytes += quotalen;
-		spin_unlock(&user->lock);
+		spin_unlock_irqrestore(&user->lock, irqflags);
 	}
 
 	/* allocate and initialise the key and its description */
@@ -328,10 +329,10 @@  struct key *key_alloc(struct key_type *type, const char *desc,
 	kfree(key->description);
 	kmem_cache_free(key_jar, key);
 	if (!(flags & KEY_ALLOC_NOT_IN_QUOTA)) {
-		spin_lock(&user->lock);
+		spin_lock_irqsave(&user->lock, irqflags);
 		user->qnkeys--;
 		user->qnbytes -= quotalen;
-		spin_unlock(&user->lock);
+		spin_unlock_irqrestore(&user->lock, irqflags);
 	}
 	key_user_put(user);
 	key = ERR_PTR(ret);
@@ -341,10 +342,10 @@  struct key *key_alloc(struct key_type *type, const char *desc,
 	kmem_cache_free(key_jar, key);
 no_memory_2:
 	if (!(flags & KEY_ALLOC_NOT_IN_QUOTA)) {
-		spin_lock(&user->lock);
+		spin_lock_irqsave(&user->lock, irqflags);
 		user->qnkeys--;
 		user->qnbytes -= quotalen;
-		spin_unlock(&user->lock);
+		spin_unlock_irqrestore(&user->lock, irqflags);
 	}
 	key_user_put(user);
 no_memory_1:
@@ -352,7 +353,7 @@  struct key *key_alloc(struct key_type *type, const char *desc,
 	goto error;
 
 no_quota:
-	spin_unlock(&user->lock);
+	spin_unlock_irqrestore(&user->lock, irqflags);
 	key_user_put(user);
 	key = ERR_PTR(-EDQUOT);
 	goto error;
@@ -381,8 +382,9 @@  int key_payload_reserve(struct key *key, size_t datalen)
 	if (delta != 0 && test_bit(KEY_FLAG_IN_QUOTA, &key->flags)) {
 		unsigned maxbytes = uid_eq(key->user->uid, GLOBAL_ROOT_UID) ?
 			key_quota_root_maxbytes : key_quota_maxbytes;
+		unsigned long flags;
 
-		spin_lock(&key->user->lock);
+		spin_lock_irqsave(&key->user->lock, flags);
 
 		if (delta > 0 &&
 		    (key->user->qnbytes + delta > maxbytes ||
@@ -393,7 +395,7 @@  int key_payload_reserve(struct key *key, size_t datalen)
 			key->user->qnbytes += delta;
 			key->quotalen += delta;
 		}
-		spin_unlock(&key->user->lock);
+		spin_unlock_irqrestore(&key->user->lock, flags);
 	}
 
 	/* change the recorded data length if that didn't generate an error */
@@ -646,8 +648,18 @@  void key_put(struct key *key)
 	if (key) {
 		key_check(key);
 
-		if (refcount_dec_and_test(&key->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);
+			}
 			schedule_work(&key_gc_work);
+		}
 	}
 }
 EXPORT_SYMBOL(key_put);
diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c
index 10ba439968f7..4bc3e9398ee3 100644
--- a/security/keys/keyctl.c
+++ b/security/keys/keyctl.c
@@ -954,6 +954,7 @@  long keyctl_chown_key(key_serial_t id, uid_t user, gid_t group)
 	long ret;
 	kuid_t uid;
 	kgid_t gid;
+	unsigned long flags;
 
 	uid = make_kuid(current_user_ns(), user);
 	gid = make_kgid(current_user_ns(), group);
@@ -1010,7 +1011,7 @@  long keyctl_chown_key(key_serial_t id, uid_t user, gid_t group)
 			unsigned maxbytes = uid_eq(uid, GLOBAL_ROOT_UID) ?
 				key_quota_root_maxbytes : key_quota_maxbytes;
 
-			spin_lock(&newowner->lock);
+			spin_lock_irqsave(&newowner->lock, flags);
 			if (newowner->qnkeys + 1 > maxkeys ||
 			    newowner->qnbytes + key->quotalen > maxbytes ||
 			    newowner->qnbytes + key->quotalen <
@@ -1019,12 +1020,12 @@  long keyctl_chown_key(key_serial_t id, uid_t user, gid_t group)
 
 			newowner->qnkeys++;
 			newowner->qnbytes += key->quotalen;
-			spin_unlock(&newowner->lock);
+			spin_unlock_irqrestore(&newowner->lock, flags);
 
-			spin_lock(&key->user->lock);
+			spin_lock_irqsave(&key->user->lock, flags);
 			key->user->qnkeys--;
 			key->user->qnbytes -= key->quotalen;
-			spin_unlock(&key->user->lock);
+			spin_unlock_irqrestore(&key->user->lock, flags);
 		}
 
 		atomic_dec(&key->user->nkeys);
@@ -1056,7 +1057,7 @@  long keyctl_chown_key(key_serial_t id, uid_t user, gid_t group)
 	return ret;
 
 quota_overrun:
-	spin_unlock(&newowner->lock);
+	spin_unlock_irqrestore(&newowner->lock, flags);
 	zapowner = newowner;
 	ret = -EDQUOT;
 	goto error_put;