diff mbox series

[09/22] fast_dput(): handle underflows gracefully

Message ID 20231109062056.3181775-9-viro@zeniv.linux.org.uk (mailing list archive)
State New
Headers show
Series [01/22] struct dentry: get rid of randomize_layout idiocy | expand

Commit Message

Al Viro Nov. 9, 2023, 6:20 a.m. UTC
If refcount is less than 1, we should just warn, unlock dentry and
return true, so that the caller doesn't try to do anything else.

Taking care of that leaves the rest of "lockref_put_return() has
failed" case equivalent to "decrement refcount and rejoin the
normal slow path after the point where we grab ->d_lock".

NOTE: lockref_put_return() is strictly a fastpath thing - unlike
the rest of lockref primitives, it does not contain a fallback.
Caller (and it looks like fast_dput() is the only legitimate one
in the entire kernel) has to do that itself.  Reasons for
lockref_put_return() failures:
	* ->d_lock held by somebody
	* refcount <= 0
	* ... or an architecture not supporting lockref use of
cmpxchg - sparc, anything non-SMP, config with spinlock debugging...

We could add a fallback, but it would be a clumsy API - we'd have
to distinguish between:
	(1) refcount > 1 - decremented, lock not held on return
	(2) refcount < 1 - left alone, probably no sense to hold the lock
	(3) refcount is 1, no cmphxcg - decremented, lock held on return
	(4) refcount is 1, cmphxcg supported - decremented, lock *NOT* held
	    on return.
We want to return with no lock held in case (4); that's the whole point of that
thing.  We very much do not want to have the fallback in case (3) return without
a lock, since the caller might have to retake it in that case.
So it wouldn't be more convenient than doing the fallback in the caller and
it would be very easy to screw up, especially since the test coverage would
suck - no way to test (3) and (4) on the same kernel build.

Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
---
 fs/dcache.c | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

Comments

Christian Brauner Nov. 9, 2023, 2:46 p.m. UTC | #1
On Thu, Nov 09, 2023 at 06:20:43AM +0000, Al Viro wrote:
> If refcount is less than 1, we should just warn, unlock dentry and
> return true, so that the caller doesn't try to do anything else.

That's effectively to guard against bugs in filesystems, not in dcache
itself, right? Have we observed this frequently?

> 
> Taking care of that leaves the rest of "lockref_put_return() has
> failed" case equivalent to "decrement refcount and rejoin the
> normal slow path after the point where we grab ->d_lock".
> 
> NOTE: lockref_put_return() is strictly a fastpath thing - unlike
> the rest of lockref primitives, it does not contain a fallback.
> Caller (and it looks like fast_dput() is the only legitimate one
> in the entire kernel) has to do that itself.  Reasons for
> lockref_put_return() failures:
> 	* ->d_lock held by somebody
> 	* refcount <= 0
> 	* ... or an architecture not supporting lockref use of
> cmpxchg - sparc, anything non-SMP, config with spinlock debugging...
> 
> We could add a fallback, but it would be a clumsy API - we'd have
> to distinguish between:
> 	(1) refcount > 1 - decremented, lock not held on return
> 	(2) refcount < 1 - left alone, probably no sense to hold the lock
> 	(3) refcount is 1, no cmphxcg - decremented, lock held on return
> 	(4) refcount is 1, cmphxcg supported - decremented, lock *NOT* held
> 	    on return.
> We want to return with no lock held in case (4); that's the whole point of that
> thing.  We very much do not want to have the fallback in case (3) return without
> a lock, since the caller might have to retake it in that case.
> So it wouldn't be more convenient than doing the fallback in the caller and
> it would be very easy to screw up, especially since the test coverage would
> suck - no way to test (3) and (4) on the same kernel build.
> 
> Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
> ---

Looks like a good idea,
Reviewed-by: Christian Brauner <brauner@kernel.org>
Al Viro Nov. 9, 2023, 8:39 p.m. UTC | #2
On Thu, Nov 09, 2023 at 03:46:21PM +0100, Christian Brauner wrote:
> On Thu, Nov 09, 2023 at 06:20:43AM +0000, Al Viro wrote:
> > If refcount is less than 1, we should just warn, unlock dentry and
> > return true, so that the caller doesn't try to do anything else.
> 
> That's effectively to guard against bugs in filesystems, not in dcache
> itself, right? Have we observed this frequently?

Hard to tell - it doesn't happen often, but... extra dput() somewhere
is not an impossible class of bugs.  I remember running into that
while doing work in namei.c, I certainly have seen failure exits in
random places that fucked refcounting up by dropping the wrong things.
diff mbox series

Patch

diff --git a/fs/dcache.c b/fs/dcache.c
index 0d15e8852ac1..e02b3c81bc02 100644
--- a/fs/dcache.c
+++ b/fs/dcache.c
@@ -779,12 +779,12 @@  static inline bool fast_dput(struct dentry *dentry)
 	 */
 	if (unlikely(ret < 0)) {
 		spin_lock(&dentry->d_lock);
-		if (dentry->d_lockref.count > 1) {
-			dentry->d_lockref.count--;
+		if (WARN_ON_ONCE(dentry->d_lockref.count <= 0)) {
 			spin_unlock(&dentry->d_lock);
 			return true;
 		}
-		return false;
+		dentry->d_lockref.count--;
+		goto locked;
 	}
 
 	/*
@@ -842,6 +842,7 @@  static inline bool fast_dput(struct dentry *dentry)
 	 * else could have killed it and marked it dead. Either way, we
 	 * don't need to do anything else.
 	 */
+locked:
 	if (dentry->d_lockref.count) {
 		spin_unlock(&dentry->d_lock);
 		return true;