2023-11-30 20:48:51

by Jann Horn

[permalink] [raw]
Subject: [PATCH] locking: Document that mutex_unlock() is non-atomic

I have seen several cases of attempts to use mutex_unlock() to release an
object such that the object can then be freed by another task.
My understanding is that this is not safe because mutex_unlock(), in the
MUTEX_FLAG_WAITERS && !MUTEX_FLAG_HANDOFF case, accesses the mutex
structure after having marked it as unlocked; so mutex_unlock() requires
its caller to ensure that the mutex stays alive until mutex_unlock()
returns.

If MUTEX_FLAG_WAITERS is set and there are real waiters, those waiters
have to keep the mutex alive, I think; but we could have a spurious
MUTEX_FLAG_WAITERS left if an interruptible/killable waiter bailed
between the points where __mutex_unlock_slowpath() did the cmpxchg
reading the flags and where it acquired the wait_lock.

(With spinlocks, that kind of code pattern is allowed and, from what I
remember, used in several places in the kernel.)

If my understanding of this is correct, we should probably document this -
I think such a semantic difference between mutexes and spinlocks is fairly
unintuitive.

Signed-off-by: Jann Horn <[email protected]>
---
I hope for some thorough review on this patch to make sure the comments
I'm adding are actually true, and to confirm that mutexes intentionally
do not support this usage pattern.

Documentation/locking/mutex-design.rst | 6 ++++++
kernel/locking/mutex.c | 5 +++++
2 files changed, 11 insertions(+)

diff --git a/Documentation/locking/mutex-design.rst b/Documentation/locking/mutex-design.rst
index 78540cd7f54b..087716bfa7b2 100644
--- a/Documentation/locking/mutex-design.rst
+++ b/Documentation/locking/mutex-design.rst
@@ -101,6 +101,12 @@ features that make lock debugging easier and faster:
- Detects multi-task circular deadlocks and prints out all affected
locks and tasks (and only those tasks).

+Releasing a mutex is not an atomic operation: Once a mutex release operation
+has begun, another context may be able to acquire the mutex before the release
+operation has completed. The mutex user must ensure that the mutex is not
+destroyed while a release operation is still in progress - in other words,
+callers of 'mutex_unlock' must ensure that the mutex stays alive until
+'mutex_unlock' has returned.

Interfaces
----------
diff --git a/kernel/locking/mutex.c b/kernel/locking/mutex.c
index 2deeeca3e71b..4c6b83bab643 100644
--- a/kernel/locking/mutex.c
+++ b/kernel/locking/mutex.c
@@ -532,6 +532,11 @@ static noinline void __sched __mutex_unlock_slowpath(struct mutex *lock, unsigne
* This function must not be used in interrupt context. Unlocking
* of a not locked mutex is not allowed.
*
+ * The caller must ensure that the mutex stays alive until this function has
+ * returned - mutex_unlock() can NOT directly be used to release an object such
+ * that another concurrent task can free it.
+ * Mutexes are different from spinlocks in this aspect.
+ *
* This function is similar to (but not equivalent to) up().
*/
void __sched mutex_unlock(struct mutex *lock)

base-commit: 3b47bc037bd44f142ac09848e8d3ecccc726be99
--
2.43.0.rc2.451.g8631bc7472-goog


2023-11-30 21:53:57

by Waiman Long

[permalink] [raw]
Subject: Re: [PATCH] locking: Document that mutex_unlock() is non-atomic

On 11/30/23 15:48, Jann Horn wrote:
> I have seen several cases of attempts to use mutex_unlock() to release an
> object such that the object can then be freed by another task.
> My understanding is that this is not safe because mutex_unlock(), in the
> MUTEX_FLAG_WAITERS && !MUTEX_FLAG_HANDOFF case, accesses the mutex
> structure after having marked it as unlocked; so mutex_unlock() requires
> its caller to ensure that the mutex stays alive until mutex_unlock()
> returns.
>
> If MUTEX_FLAG_WAITERS is set and there are real waiters, those waiters
> have to keep the mutex alive, I think; but we could have a spurious
> MUTEX_FLAG_WAITERS left if an interruptible/killable waiter bailed
> between the points where __mutex_unlock_slowpath() did the cmpxchg
> reading the flags and where it acquired the wait_lock.

Could you clarify under what condition a concurrent task can decide to
free the object holding the mutex? Is it !mutex_is_locked() or after a
mutex_lock()/mutex_unlock sequence?

mutex_is_locked() will return true if the mutex has waiter even if it 
is currently free.

Cheers,
Longman

>
> (With spinlocks, that kind of code pattern is allowed and, from what I
> remember, used in several places in the kernel.)
>
> If my understanding of this is correct, we should probably document this -
> I think such a semantic difference between mutexes and spinlocks is fairly
> unintuitive.
>
> Signed-off-by: Jann Horn <[email protected]>
> ---
> I hope for some thorough review on this patch to make sure the comments
> I'm adding are actually true, and to confirm that mutexes intentionally
> do not support this usage pattern.
>
> Documentation/locking/mutex-design.rst | 6 ++++++
> kernel/locking/mutex.c | 5 +++++
> 2 files changed, 11 insertions(+)
>
> diff --git a/Documentation/locking/mutex-design.rst b/Documentation/locking/mutex-design.rst
> index 78540cd7f54b..087716bfa7b2 100644
> --- a/Documentation/locking/mutex-design.rst
> +++ b/Documentation/locking/mutex-design.rst
> @@ -101,6 +101,12 @@ features that make lock debugging easier and faster:
> - Detects multi-task circular deadlocks and prints out all affected
> locks and tasks (and only those tasks).
>
> +Releasing a mutex is not an atomic operation: Once a mutex release operation
> +has begun, another context may be able to acquire the mutex before the release
> +operation has completed. The mutex user must ensure that the mutex is not
> +destroyed while a release operation is still in progress - in other words,
> +callers of 'mutex_unlock' must ensure that the mutex stays alive until
> +'mutex_unlock' has returned.
>
> Interfaces
> ----------
> diff --git a/kernel/locking/mutex.c b/kernel/locking/mutex.c
> index 2deeeca3e71b..4c6b83bab643 100644
> --- a/kernel/locking/mutex.c
> +++ b/kernel/locking/mutex.c
> @@ -532,6 +532,11 @@ static noinline void __sched __mutex_unlock_slowpath(struct mutex *lock, unsigne
> * This function must not be used in interrupt context. Unlocking
> * of a not locked mutex is not allowed.
> *
> + * The caller must ensure that the mutex stays alive until this function has
> + * returned - mutex_unlock() can NOT directly be used to release an object such
> + * that another concurrent task can free it.
> + * Mutexes are different from spinlocks in this aspect.
> + *
> * This function is similar to (but not equivalent to) up().
> */
> void __sched mutex_unlock(struct mutex *lock)
>
> base-commit: 3b47bc037bd44f142ac09848e8d3ecccc726be99

2023-11-30 22:25:08

by Jann Horn

[permalink] [raw]
Subject: Re: [PATCH] locking: Document that mutex_unlock() is non-atomic

On Thu, Nov 30, 2023 at 10:53 PM Waiman Long <[email protected]> wrote:
> On 11/30/23 15:48, Jann Horn wrote:
> > I have seen several cases of attempts to use mutex_unlock() to release an
> > object such that the object can then be freed by another task.
> > My understanding is that this is not safe because mutex_unlock(), in the
> > MUTEX_FLAG_WAITERS && !MUTEX_FLAG_HANDOFF case, accesses the mutex
> > structure after having marked it as unlocked; so mutex_unlock() requires
> > its caller to ensure that the mutex stays alive until mutex_unlock()
> > returns.
> >
> > If MUTEX_FLAG_WAITERS is set and there are real waiters, those waiters
> > have to keep the mutex alive, I think; but we could have a spurious
> > MUTEX_FLAG_WAITERS left if an interruptible/killable waiter bailed
> > between the points where __mutex_unlock_slowpath() did the cmpxchg
> > reading the flags and where it acquired the wait_lock.
>
> Could you clarify under what condition a concurrent task can decide to
> free the object holding the mutex? Is it !mutex_is_locked() or after a
> mutex_lock()/mutex_unlock sequence?

I mean a mutex_lock()+mutex_unlock() sequence.

> mutex_is_locked() will return true if the mutex has waiter even if it
> is currently free.

I don't understand your point, and maybe I also don't understand what
you mean by "free". Isn't mutex_is_locked() defined such that it only
looks at whether a mutex has an owner, and doesn't look at the waiter
list?

2023-11-30 23:57:19

by Waiman Long

[permalink] [raw]
Subject: Re: [PATCH] locking: Document that mutex_unlock() is non-atomic


On 11/30/23 17:24, Jann Horn wrote:
> On Thu, Nov 30, 2023 at 10:53 PM Waiman Long <[email protected]> wrote:
>> On 11/30/23 15:48, Jann Horn wrote:
>>> I have seen several cases of attempts to use mutex_unlock() to release an
>>> object such that the object can then be freed by another task.
>>> My understanding is that this is not safe because mutex_unlock(), in the
>>> MUTEX_FLAG_WAITERS && !MUTEX_FLAG_HANDOFF case, accesses the mutex
>>> structure after having marked it as unlocked; so mutex_unlock() requires
>>> its caller to ensure that the mutex stays alive until mutex_unlock()
>>> returns.
>>>
>>> If MUTEX_FLAG_WAITERS is set and there are real waiters, those waiters
>>> have to keep the mutex alive, I think; but we could have a spurious
>>> MUTEX_FLAG_WAITERS left if an interruptible/killable waiter bailed
>>> between the points where __mutex_unlock_slowpath() did the cmpxchg
>>> reading the flags and where it acquired the wait_lock.
>> Could you clarify under what condition a concurrent task can decide to
>> free the object holding the mutex? Is it !mutex_is_locked() or after a
>> mutex_lock()/mutex_unlock sequence?
> I mean a mutex_lock()+mutex_unlock() sequence.
Because of optimistic spinning, a mutex_lock()/mutex_unlock() can
succeed even if there are still waiters waiting for the lock.
>
>> mutex_is_locked() will return true if the mutex has waiter even if it
>> is currently free.
> I don't understand your point, and maybe I also don't understand what
> you mean by "free". Isn't mutex_is_locked() defined such that it only
> looks at whether a mutex has an owner, and doesn't look at the waiter
> list?

What I mean is that the mutex is in an unlocked state ready to be
acquired by another locker. mutex_is_locked() considers the state of the
mutex as locked if any of the owner flags is set.

Beside the mutex_lock()/mutex_unlock() sequence, I will suggest adding a
mutex_is_locked() check just to be sure.

Cheers,
Longman

2023-12-01 00:34:06

by Waiman Long

[permalink] [raw]
Subject: Re: [PATCH] locking: Document that mutex_unlock() is non-atomic


On 11/30/23 15:48, Jann Horn wrote:
> I have seen several cases of attempts to use mutex_unlock() to release an
> object such that the object can then be freed by another task.
> My understanding is that this is not safe because mutex_unlock(), in the
> MUTEX_FLAG_WAITERS && !MUTEX_FLAG_HANDOFF case, accesses the mutex
> structure after having marked it as unlocked; so mutex_unlock() requires
> its caller to ensure that the mutex stays alive until mutex_unlock()
> returns.
>
> If MUTEX_FLAG_WAITERS is set and there are real waiters, those waiters
> have to keep the mutex alive, I think; but we could have a spurious
> MUTEX_FLAG_WAITERS left if an interruptible/killable waiter bailed
> between the points where __mutex_unlock_slowpath() did the cmpxchg
> reading the flags and where it acquired the wait_lock.
>
> (With spinlocks, that kind of code pattern is allowed and, from what I
> remember, used in several places in the kernel.)
>
> If my understanding of this is correct, we should probably document this -
> I think such a semantic difference between mutexes and spinlocks is fairly
> unintuitive.

Spinlocks are fair. So doing a lock/unlock sequence will make sure that
all the previously waiting waiters are done with the lock. Para-virtual
spinlocks, however, can be a bit unfair so doing a lock/unlock sequence
may not be enough to guarantee there is no waiter. The same is true for
mutex. Adding a spin_is_locked() or mutex_is_locked() check can make
sure that all the waiters are gone.

Also the term "non-atomc" is kind of ambiguous as to what is the exact
meaning of this word.

Cheers,
Longman

>
> Signed-off-by: Jann Horn <[email protected]>
> ---
> I hope for some thorough review on this patch to make sure the comments
> I'm adding are actually true, and to confirm that mutexes intentionally
> do not support this usage pattern.
>
> Documentation/locking/mutex-design.rst | 6 ++++++
> kernel/locking/mutex.c | 5 +++++
> 2 files changed, 11 insertions(+)
>
> diff --git a/Documentation/locking/mutex-design.rst b/Documentation/locking/mutex-design.rst
> index 78540cd7f54b..087716bfa7b2 100644
> --- a/Documentation/locking/mutex-design.rst
> +++ b/Documentation/locking/mutex-design.rst
> @@ -101,6 +101,12 @@ features that make lock debugging easier and faster:
> - Detects multi-task circular deadlocks and prints out all affected
> locks and tasks (and only those tasks).
>
> +Releasing a mutex is not an atomic operation: Once a mutex release operation
> +has begun, another context may be able to acquire the mutex before the release
> +operation has completed. The mutex user must ensure that the mutex is not
> +destroyed while a release operation is still in progress - in other words,
> +callers of 'mutex_unlock' must ensure that the mutex stays alive until
> +'mutex_unlock' has returned.
>
> Interfaces
> ----------
> diff --git a/kernel/locking/mutex.c b/kernel/locking/mutex.c
> index 2deeeca3e71b..4c6b83bab643 100644
> --- a/kernel/locking/mutex.c
> +++ b/kernel/locking/mutex.c
> @@ -532,6 +532,11 @@ static noinline void __sched __mutex_unlock_slowpath(struct mutex *lock, unsigne
> * This function must not be used in interrupt context. Unlocking
> * of a not locked mutex is not allowed.
> *
> + * The caller must ensure that the mutex stays alive until this function has
> + * returned - mutex_unlock() can NOT directly be used to release an object such
> + * that another concurrent task can free it.
> + * Mutexes are different from spinlocks in this aspect.
> + *
> * This function is similar to (but not equivalent to) up().
> */
> void __sched mutex_unlock(struct mutex *lock)
>
> base-commit: 3b47bc037bd44f142ac09848e8d3ecccc726be99

2023-12-01 09:11:27

by Peter Zijlstra

[permalink] [raw]
Subject: Re: [PATCH] locking: Document that mutex_unlock() is non-atomic

On Thu, Nov 30, 2023 at 09:48:17PM +0100, Jann Horn wrote:
> I have seen several cases of attempts to use mutex_unlock() to release an
> object such that the object can then be freed by another task.
> My understanding is that this is not safe because mutex_unlock(), in the
> MUTEX_FLAG_WAITERS && !MUTEX_FLAG_HANDOFF case, accesses the mutex
> structure after having marked it as unlocked; so mutex_unlock() requires
> its caller to ensure that the mutex stays alive until mutex_unlock()
> returns.
>
> If MUTEX_FLAG_WAITERS is set and there are real waiters, those waiters
> have to keep the mutex alive, I think; but we could have a spurious
> MUTEX_FLAG_WAITERS left if an interruptible/killable waiter bailed
> between the points where __mutex_unlock_slowpath() did the cmpxchg
> reading the flags and where it acquired the wait_lock.
>
> (With spinlocks, that kind of code pattern is allowed and, from what I
> remember, used in several places in the kernel.)
>
> If my understanding of this is correct, we should probably document this -
> I think such a semantic difference between mutexes and spinlocks is fairly
> unintuitive.

IIRC this is true of all sleeping locks, and I think completion was the
explcicit exception here, but it's been a while.


> index 78540cd7f54b..087716bfa7b2 100644
> --- a/Documentation/locking/mutex-design.rst
> +++ b/Documentation/locking/mutex-design.rst
> @@ -101,6 +101,12 @@ features that make lock debugging easier and faster:
> - Detects multi-task circular deadlocks and prints out all affected
> locks and tasks (and only those tasks).
>
> +Releasing a mutex is not an atomic operation: Once a mutex release operation

Well, it very much is an atomic store-release. That is, I object to your
confusing use of atomic here :-)

2023-12-01 10:21:47

by Ingo Molnar

[permalink] [raw]
Subject: Re: [PATCH] locking: Document that mutex_unlock() is non-atomic


* Waiman Long <[email protected]> wrote:

> On 11/30/23 15:48, Jann Horn wrote:
> > I have seen several cases of attempts to use mutex_unlock() to release an
> > object such that the object can then be freed by another task.
> > My understanding is that this is not safe because mutex_unlock(), in the
> > MUTEX_FLAG_WAITERS && !MUTEX_FLAG_HANDOFF case, accesses the mutex
> > structure after having marked it as unlocked; so mutex_unlock() requires
> > its caller to ensure that the mutex stays alive until mutex_unlock()
> > returns.
> >
> > If MUTEX_FLAG_WAITERS is set and there are real waiters, those waiters
> > have to keep the mutex alive, I think; but we could have a spurious
> > MUTEX_FLAG_WAITERS left if an interruptible/killable waiter bailed
> > between the points where __mutex_unlock_slowpath() did the cmpxchg
> > reading the flags and where it acquired the wait_lock.
>
> Could you clarify under what condition a concurrent task can decide to free
> the object holding the mutex? Is it !mutex_is_locked() or after a
> mutex_lock()/mutex_unlock sequence?
>
> mutex_is_locked() will return true if the mutex has waiter even if it? is
> currently free.

I believe the correct condition is what the changelog already says:

"until mutex_unlock() returns".

What happens within mutex_unlock() is kernel implementation specific and
once a caller has called mutex_unlock(), the mutex must remain alive until
it returns. No other call can substitute for this: neither
mutex_is_locked(), nor some sort of mutex_lock()+mutex_unlock() sequence.

Thanks,

Ingo

2023-12-01 10:33:41

by Ingo Molnar

[permalink] [raw]
Subject: [PATCH -v2] locking/mutex: Document that mutex_unlock() is non-atomic


* Jann Horn <[email protected]> wrote:

> On Thu, Nov 30, 2023 at 10:53 PM Waiman Long <[email protected]> wrote:
> > On 11/30/23 15:48, Jann Horn wrote:
> > > I have seen several cases of attempts to use mutex_unlock() to release an
> > > object such that the object can then be freed by another task.
> > > My understanding is that this is not safe because mutex_unlock(), in the
> > > MUTEX_FLAG_WAITERS && !MUTEX_FLAG_HANDOFF case, accesses the mutex
> > > structure after having marked it as unlocked; so mutex_unlock() requires
> > > its caller to ensure that the mutex stays alive until mutex_unlock()
> > > returns.
> > >
> > > If MUTEX_FLAG_WAITERS is set and there are real waiters, those waiters
> > > have to keep the mutex alive, I think; but we could have a spurious
> > > MUTEX_FLAG_WAITERS left if an interruptible/killable waiter bailed
> > > between the points where __mutex_unlock_slowpath() did the cmpxchg
> > > reading the flags and where it acquired the wait_lock.
> >
> > Could you clarify under what condition a concurrent task can decide to
> > free the object holding the mutex? Is it !mutex_is_locked() or after a
> > mutex_lock()/mutex_unlock sequence?
>
> I mean a mutex_lock()+mutex_unlock() sequence.
>
> > mutex_is_locked() will return true if the mutex has waiter even if it
> > is currently free.
>
> I don't understand your point, and maybe I also don't understand what
> you mean by "free". Isn't mutex_is_locked() defined such that it only
> looks at whether a mutex has an owner, and doesn't look at the waiter
> list?

Yeah, mutex_is_locked() is not a sufficient check - and mutexes have no
implicit refcount properties like spinlocks. Once you call a mutex API, you
have to guarantee the lifetime of the object until the function returns.

I.e. entering a mutex_lock()-ed critical section cannot be used to
guarantee that all mutex_unlock() instances have stopped using the mutex.
I agree that this is a bit unintuitive, and differs from spinlocks.

I've clarified all this a bit more in the final patch (added a 'fully'
qualifier, etc.), and made the changelog more assertive - see the attached
patch.

Thanks,

Ingo

=======================>
From: Jann Horn <[email protected]>
Date: Thu, 30 Nov 2023 21:48:17 +0100
Subject: [PATCH] locking/mutex: Document that mutex_unlock() is non-atomic

I have seen several cases of attempts to use mutex_unlock() to release an
object such that the object can then be freed by another task.

This is not safe because mutex_unlock(), in the
MUTEX_FLAG_WAITERS && !MUTEX_FLAG_HANDOFF case, accesses the mutex
structure after having marked it as unlocked; so mutex_unlock() requires
its caller to ensure that the mutex stays alive until mutex_unlock()
returns.

If MUTEX_FLAG_WAITERS is set and there are real waiters, those waiters
have to keep the mutex alive, but we could have a spurious
MUTEX_FLAG_WAITERS left if an interruptible/killable waiter bailed
between the points where __mutex_unlock_slowpath() did the cmpxchg
reading the flags and where it acquired the wait_lock.

( With spinlocks, that kind of code pattern is allowed and, from what I
remember, used in several places in the kernel. )

Document this, such a semantic difference between mutexes and spinlocks
is fairly unintuitive.

[ mingo: Made the changelog a bit more assertive, refined the comments. ]

Signed-off-by: Jann Horn <[email protected]>
Signed-off-by: Ingo Molnar <[email protected]>
Link: https://lore.kernel.org/r/[email protected]
---
Documentation/locking/mutex-design.rst | 6 ++++++
kernel/locking/mutex.c | 5 +++++
2 files changed, 11 insertions(+)

diff --git a/Documentation/locking/mutex-design.rst b/Documentation/locking/mutex-design.rst
index 78540cd7f54b..7572339b2f12 100644
--- a/Documentation/locking/mutex-design.rst
+++ b/Documentation/locking/mutex-design.rst
@@ -101,6 +101,12 @@ features that make lock debugging easier and faster:
- Detects multi-task circular deadlocks and prints out all affected
locks and tasks (and only those tasks).

+Releasing a mutex is not an atomic operation: Once a mutex release operation
+has begun, another context may be able to acquire the mutex before the release
+operation has fully completed. The mutex user must ensure that the mutex is not
+destroyed while a release operation is still in progress - in other words,
+callers of mutex_unlock() must ensure that the mutex stays alive until
+mutex_unlock() has returned.

Interfaces
----------
diff --git a/kernel/locking/mutex.c b/kernel/locking/mutex.c
index 2deeeca3e71b..cbae8c0b89ab 100644
--- a/kernel/locking/mutex.c
+++ b/kernel/locking/mutex.c
@@ -532,6 +532,11 @@ static noinline void __sched __mutex_unlock_slowpath(struct mutex *lock, unsigne
* This function must not be used in interrupt context. Unlocking
* of a not locked mutex is not allowed.
*
+ * The caller must ensure that the mutex stays alive until this function has
+ * returned - mutex_unlock() can NOT directly be used to release an object such
+ * that another concurrent task can free it.
+ * Mutexes are different from spinlocks & refcounts in this aspect.
+ *
* This function is similar to (but not equivalent to) up().
*/
void __sched mutex_unlock(struct mutex *lock)

Subject: [tip: locking/core] locking/mutex: Document that mutex_unlock() is non-atomic

The following commit has been merged into the locking/core branch of tip:

Commit-ID: a51749ab34d9e5dec548fe38ede7e01e8bb26454
Gitweb: https://git.kernel.org/tip/a51749ab34d9e5dec548fe38ede7e01e8bb26454
Author: Jann Horn <[email protected]>
AuthorDate: Thu, 30 Nov 2023 21:48:17 +01:00
Committer: Ingo Molnar <[email protected]>
CommitterDate: Fri, 01 Dec 2023 11:27:43 +01:00

locking/mutex: Document that mutex_unlock() is non-atomic

I have seen several cases of attempts to use mutex_unlock() to release an
object such that the object can then be freed by another task.

This is not safe because mutex_unlock(), in the
MUTEX_FLAG_WAITERS && !MUTEX_FLAG_HANDOFF case, accesses the mutex
structure after having marked it as unlocked; so mutex_unlock() requires
its caller to ensure that the mutex stays alive until mutex_unlock()
returns.

If MUTEX_FLAG_WAITERS is set and there are real waiters, those waiters
have to keep the mutex alive, but we could have a spurious
MUTEX_FLAG_WAITERS left if an interruptible/killable waiter bailed
between the points where __mutex_unlock_slowpath() did the cmpxchg
reading the flags and where it acquired the wait_lock.

( With spinlocks, that kind of code pattern is allowed and, from what I
remember, used in several places in the kernel. )

Document this, such a semantic difference between mutexes and spinlocks
is fairly unintuitive.

[ mingo: Made the changelog a bit more assertive, refined the comments. ]

Signed-off-by: Jann Horn <[email protected]>
Signed-off-by: Ingo Molnar <[email protected]>
Link: https://lore.kernel.org/r/[email protected]
---
Documentation/locking/mutex-design.rst | 6 ++++++
kernel/locking/mutex.c | 5 +++++
2 files changed, 11 insertions(+)

diff --git a/Documentation/locking/mutex-design.rst b/Documentation/locking/mutex-design.rst
index 78540cd..7572339 100644
--- a/Documentation/locking/mutex-design.rst
+++ b/Documentation/locking/mutex-design.rst
@@ -101,6 +101,12 @@ features that make lock debugging easier and faster:
- Detects multi-task circular deadlocks and prints out all affected
locks and tasks (and only those tasks).

+Releasing a mutex is not an atomic operation: Once a mutex release operation
+has begun, another context may be able to acquire the mutex before the release
+operation has fully completed. The mutex user must ensure that the mutex is not
+destroyed while a release operation is still in progress - in other words,
+callers of mutex_unlock() must ensure that the mutex stays alive until
+mutex_unlock() has returned.

Interfaces
----------
diff --git a/kernel/locking/mutex.c b/kernel/locking/mutex.c
index 2deeeca..cbae8c0 100644
--- a/kernel/locking/mutex.c
+++ b/kernel/locking/mutex.c
@@ -532,6 +532,11 @@ static noinline void __sched __mutex_unlock_slowpath(struct mutex *lock, unsigne
* This function must not be used in interrupt context. Unlocking
* of a not locked mutex is not allowed.
*
+ * The caller must ensure that the mutex stays alive until this function has
+ * returned - mutex_unlock() can NOT directly be used to release an object such
+ * that another concurrent task can free it.
+ * Mutexes are different from spinlocks & refcounts in this aspect.
+ *
* This function is similar to (but not equivalent to) up().
*/
void __sched mutex_unlock(struct mutex *lock)

2023-12-01 12:18:40

by Peter Zijlstra

[permalink] [raw]
Subject: Re: [tip: locking/core] locking/mutex: Document that mutex_unlock() is non-atomic

On Fri, Dec 01, 2023 at 10:44:09AM -0000, tip-bot2 for Jann Horn wrote:

> --- a/Documentation/locking/mutex-design.rst
> +++ b/Documentation/locking/mutex-design.rst
> @@ -101,6 +101,12 @@ features that make lock debugging easier and faster:
> - Detects multi-task circular deadlocks and prints out all affected
> locks and tasks (and only those tasks).
>
> +Releasing a mutex is not an atomic operation: Once a mutex release operation

I still object to this confusing usage of atomic. Also all this also
applies to all sleeping locks, rwsem etc. I don't see why we need to
special case mutex here.

Also completion_done() has an explicit lock+unlock on wait.lock to
deal with this there.

2023-12-01 15:02:30

by Jann Horn

[permalink] [raw]
Subject: Re: [PATCH] locking: Document that mutex_unlock() is non-atomic

On Fri, Dec 1, 2023 at 1:33 AM Waiman Long <[email protected]> wrote:
> On 11/30/23 15:48, Jann Horn wrote:
> > I have seen several cases of attempts to use mutex_unlock() to release an
> > object such that the object can then be freed by another task.
> > My understanding is that this is not safe because mutex_unlock(), in the
> > MUTEX_FLAG_WAITERS && !MUTEX_FLAG_HANDOFF case, accesses the mutex
> > structure after having marked it as unlocked; so mutex_unlock() requires
> > its caller to ensure that the mutex stays alive until mutex_unlock()
> > returns.
> >
> > If MUTEX_FLAG_WAITERS is set and there are real waiters, those waiters
> > have to keep the mutex alive, I think; but we could have a spurious
> > MUTEX_FLAG_WAITERS left if an interruptible/killable waiter bailed
> > between the points where __mutex_unlock_slowpath() did the cmpxchg
> > reading the flags and where it acquired the wait_lock.
> >
> > (With spinlocks, that kind of code pattern is allowed and, from what I
> > remember, used in several places in the kernel.)
> >
> > If my understanding of this is correct, we should probably document this -
> > I think such a semantic difference between mutexes and spinlocks is fairly
> > unintuitive.
>
> Spinlocks are fair. So doing a lock/unlock sequence will make sure that
> all the previously waiting waiters are done with the lock. Para-virtual
> spinlocks, however, can be a bit unfair so doing a lock/unlock sequence
> may not be enough to guarantee there is no waiter. The same is true for
> mutex. Adding a spin_is_locked() or mutex_is_locked() check can make
> sure that all the waiters are gone.

I think this pattern anyway only works when you're only trying to wait
for the current holder of the lock, not tasks that are queued up on
the lock as waiters - so a task initially holds a stable reference to
some object, then acquires the object's lock, then drops the original
reference, and then later drops the lock.
You can see an example of such mutex usage (which is explicitly legal
with userspace POSIX mutexes, but is forbidden with kernel mutexes) at
the bottom of the POSIX manpage for pthread_mutex_destroy() at
<https://pubs.opengroup.org/onlinepubs/007904875/functions/pthread_mutex_destroy.html>,
in the section "Destroying Mutexes".

(I think trying to wait for pending waiters before destroying a mutex
wouldn't make sense because if there can still be pending waiters,
there can almost always also be tasks that are about to _become_
pending waiters but that haven't called mutex_lock() yet.)

2023-12-01 15:59:03

by Jann Horn

[permalink] [raw]
Subject: Re: [PATCH] locking: Document that mutex_unlock() is non-atomic

On Fri, Dec 1, 2023 at 10:10 AM Peter Zijlstra <[email protected]> wrote:
> On Thu, Nov 30, 2023 at 09:48:17PM +0100, Jann Horn wrote:
> > I have seen several cases of attempts to use mutex_unlock() to release an
> > object such that the object can then be freed by another task.
> > My understanding is that this is not safe because mutex_unlock(), in the
> > MUTEX_FLAG_WAITERS && !MUTEX_FLAG_HANDOFF case, accesses the mutex
> > structure after having marked it as unlocked; so mutex_unlock() requires
> > its caller to ensure that the mutex stays alive until mutex_unlock()
> > returns.
> >
> > If MUTEX_FLAG_WAITERS is set and there are real waiters, those waiters
> > have to keep the mutex alive, I think; but we could have a spurious
> > MUTEX_FLAG_WAITERS left if an interruptible/killable waiter bailed
> > between the points where __mutex_unlock_slowpath() did the cmpxchg
> > reading the flags and where it acquired the wait_lock.
> >
> > (With spinlocks, that kind of code pattern is allowed and, from what I
> > remember, used in several places in the kernel.)
> >
> > If my understanding of this is correct, we should probably document this -
> > I think such a semantic difference between mutexes and spinlocks is fairly
> > unintuitive.
>
> IIRC this is true of all sleeping locks, and I think completion was the
> explcicit exception here, but it's been a while.

In addition to completions, I think this also applies to up()? But I
don't know if that's intentionally supported or just an implementation
detail.

Is there some central place where this should be documented instead of
Documentation/locking/mutex-design.rst as a more general kernel
locking design thing? Maybe Documentation/locking/locktypes.rst?

I think it should also be documented on top of the relevant locking
function(s) though, since I don't think everyone who uses locking
functions necessarily reads the separate documentation files first.
Mutexes kind of stand out as the most common locking type, but I guess
to be consistent, we'd have to put the same comment on functions like
up_read() and up_write()? And maybe drop the "Mutexes are different
from spinlocks in this aspect" part?

(Sidenote: Someone pointed out to me that an additional source of
confusion could be that userspace POSIX mutexes support this usage
pattern.)

> > index 78540cd7f54b..087716bfa7b2 100644
> > --- a/Documentation/locking/mutex-design.rst
> > +++ b/Documentation/locking/mutex-design.rst
> > @@ -101,6 +101,12 @@ features that make lock debugging easier and faster:
> > - Detects multi-task circular deadlocks and prints out all affected
> > locks and tasks (and only those tasks).
> >
> > +Releasing a mutex is not an atomic operation: Once a mutex release operation
>
> Well, it very much is an atomic store-release. That is, I object to your
> confusing use of atomic here :-)

I'd say it involves an atomic store-release, but the whole operation
is not atomic. :P

But yeah, I see how this is confusing wording, and I'm not
particularly attached to my specific choice of words.

2023-12-01 16:05:04

by Jann Horn

[permalink] [raw]
Subject: Re: [PATCH] locking: Document that mutex_unlock() is non-atomic

On Fri, Dec 1, 2023 at 4:52 PM Waiman Long <[email protected]> wrote:
> On 12/1/23 10:01, Jann Horn wrote:
>> I think this pattern anyway only works when you're only trying to wait
>> for the current holder of the lock, not tasks that are queued up on
>> the lock as waiters - so a task initially holds a stable reference to
>> some object, then acquires the object's lock, then drops the original
>> reference, and then later drops the lock.
>> You can see an example of such mutex usage (which is explicitly legal
>> with userspace POSIX mutexes, but is forbidden with kernel mutexes) at
>> the bottom of the POSIX manpage for pthread_mutex_destroy() at
>> <https://pubs.opengroup.org/onlinepubs/007904875/functions/pthread_mutex_destroy.html>,
>> in the section "Destroying Mutexes".
>
> The POSIX mutex is reference-counted.

I don't understand what you mean by that.

Anyway, I guess this thread of discussion is moot - I'm not suggesting
that kernel mutexes should support this behavior.

2023-12-01 18:12:55

by David Laight

[permalink] [raw]
Subject: RE: [PATCH] locking: Document that mutex_unlock() is non-atomic

From: Jann Horn
> Sent: 01 December 2023 15:02
>
> On Fri, Dec 1, 2023 at 1:33 AM Waiman Long <[email protected]> wrote:
> > On 11/30/23 15:48, Jann Horn wrote:
> > > I have seen several cases of attempts to use mutex_unlock() to release an
> > > object such that the object can then be freed by another task.
> > > My understanding is that this is not safe because mutex_unlock(), in the
> > > MUTEX_FLAG_WAITERS && !MUTEX_FLAG_HANDOFF case, accesses the mutex
> > > structure after having marked it as unlocked; so mutex_unlock() requires
> > > its caller to ensure that the mutex stays alive until mutex_unlock()
> > > returns.
> > >
> > > If MUTEX_FLAG_WAITERS is set and there are real waiters, those waiters
> > > have to keep the mutex alive, I think; but we could have a spurious
> > > MUTEX_FLAG_WAITERS left if an interruptible/killable waiter bailed
> > > between the points where __mutex_unlock_slowpath() did the cmpxchg
> > > reading the flags and where it acquired the wait_lock.
> > >
> > > (With spinlocks, that kind of code pattern is allowed and, from what I
> > > remember, used in several places in the kernel.)
> > >
> > > If my understanding of this is correct, we should probably document this -
> > > I think such a semantic difference between mutexes and spinlocks is fairly
> > > unintuitive.
> >
> > Spinlocks are fair. So doing a lock/unlock sequence will make sure that
> > all the previously waiting waiters are done with the lock. Para-virtual
> > spinlocks, however, can be a bit unfair so doing a lock/unlock sequence
> > may not be enough to guarantee there is no waiter. The same is true for
> > mutex. Adding a spin_is_locked() or mutex_is_locked() check can make
> > sure that all the waiters are gone.
>
> I think this pattern anyway only works when you're only trying to wait
> for the current holder of the lock, not tasks that are queued up on
> the lock as waiters - so a task initially holds a stable reference to
> some object, then acquires the object's lock, then drops the original
> reference, and then later drops the lock.
> You can see an example of such mutex usage (which is explicitly legal
> with userspace POSIX mutexes, but is forbidden with kernel mutexes) at
> the bottom of the POSIX manpage for pthread_mutex_destroy() at
> <https://pubs.opengroup.org/onlinepubs/007904875/functions/pthread_mutex_destroy.html>,
> in the section "Destroying Mutexes".

I don't understand at all what any of this is about.
You cannot de-initialise, free (etc) a mutex (or any other piece of
memory for that matter) if another thread can have a reference to it.
If some other code might be holding the mutex it also might be just
about to acquire it - you always need another lock of some kind to
ensure that doesn't happen.

IIRC pretty much the only time you need to acquire the mutex in the
free path is if locks are chained, eg:
lock(table)
entry = find_entry();
lock(entry)
unlock(table)
...
unlock(entry)

Then the free code has to:
lock(table)
remove_from_table(entry)
lock(entry)
unlock(entry)
unlock(table)
free(entry)

ISTR something about mutex_unlock() not being a full memory barrier.
But that is entirely different to this discussion.

David

-
Registered Address Lakeside, Bramley Road, Mount Farm, Milton Keynes, MK1 1PT, UK
Registration No: 1397386 (Wales)

2023-12-01 18:18:55

by Jann Horn

[permalink] [raw]
Subject: Re: [PATCH] locking: Document that mutex_unlock() is non-atomic

On Fri, Dec 1, 2023 at 7:12 PM David Laight <[email protected]> wrote:
> From: Jann Horn
> > Sent: 01 December 2023 15:02
> >
> > On Fri, Dec 1, 2023 at 1:33 AM Waiman Long <[email protected]> wrote:
> > > On 11/30/23 15:48, Jann Horn wrote:
> > > > I have seen several cases of attempts to use mutex_unlock() to release an
> > > > object such that the object can then be freed by another task.
> > > > My understanding is that this is not safe because mutex_unlock(), in the
> > > > MUTEX_FLAG_WAITERS && !MUTEX_FLAG_HANDOFF case, accesses the mutex
> > > > structure after having marked it as unlocked; so mutex_unlock() requires
> > > > its caller to ensure that the mutex stays alive until mutex_unlock()
> > > > returns.
> > > >
> > > > If MUTEX_FLAG_WAITERS is set and there are real waiters, those waiters
> > > > have to keep the mutex alive, I think; but we could have a spurious
> > > > MUTEX_FLAG_WAITERS left if an interruptible/killable waiter bailed
> > > > between the points where __mutex_unlock_slowpath() did the cmpxchg
> > > > reading the flags and where it acquired the wait_lock.
> > > >
> > > > (With spinlocks, that kind of code pattern is allowed and, from what I
> > > > remember, used in several places in the kernel.)
> > > >
> > > > If my understanding of this is correct, we should probably document this -
> > > > I think such a semantic difference between mutexes and spinlocks is fairly
> > > > unintuitive.
> > >
> > > Spinlocks are fair. So doing a lock/unlock sequence will make sure that
> > > all the previously waiting waiters are done with the lock. Para-virtual
> > > spinlocks, however, can be a bit unfair so doing a lock/unlock sequence
> > > may not be enough to guarantee there is no waiter. The same is true for
> > > mutex. Adding a spin_is_locked() or mutex_is_locked() check can make
> > > sure that all the waiters are gone.
> >
> > I think this pattern anyway only works when you're only trying to wait
> > for the current holder of the lock, not tasks that are queued up on
> > the lock as waiters - so a task initially holds a stable reference to
> > some object, then acquires the object's lock, then drops the original
> > reference, and then later drops the lock.
> > You can see an example of such mutex usage (which is explicitly legal
> > with userspace POSIX mutexes, but is forbidden with kernel mutexes) at
> > the bottom of the POSIX manpage for pthread_mutex_destroy() at
> > <https://pubs.opengroup.org/onlinepubs/007904875/functions/pthread_mutex_destroy.html>,
> > in the section "Destroying Mutexes".
>
> I don't understand at all what any of this is about.
> You cannot de-initialise, free (etc) a mutex (or any other piece of
> memory for that matter) if another thread can have a reference to it.
> If some other code might be holding the mutex it also might be just
> about to acquire it - you always need another lock of some kind to
> ensure that doesn't happen.
>
> IIRC pretty much the only time you need to acquire the mutex in the
> free path is if locks are chained, eg:
> lock(table)
> entry = find_entry();
> lock(entry)
> unlock(table)
> ...
> unlock(entry)
>
> Then the free code has to:
> lock(table)
> remove_from_table(entry)
> lock(entry)
> unlock(entry)
> unlock(table)
> free(entry)

Yep, this is exactly the kind of code pattern for which I'm trying to
document that it is forbidden with mutexes (while it is allowed with
spinlocks).

2023-12-01 19:21:46

by Waiman Long

[permalink] [raw]
Subject: Re: [PATCH] locking: Document that mutex_unlock() is non-atomic

On 12/1/23 13:44, David Laight wrote:
>
> (Top post due to perverted outluck rules on html)
>
> Pending waiters aren't the problem.
>
Pending waiters can still be a problem if code decides to free the lock
containing object after a lock/unlock sequence as it may cause
use-after-free.
>
> You have to ensure there aren't any, but the mutex() can be held.
>
Using reference count to track the number of active users is one way to
prevent that if you only release the reference count after
mutex_unlock() returns but not in the lock critical section.

Cheers,
Longman

> David
>
> *From:*Waiman Long <[email protected]>
> *Sent:* 01 December 2023 18:40
> *To:* Jann Horn <[email protected]>; David Laight <[email protected]>
> *Cc:* Peter Zijlstra <[email protected]>; Ingo Molnar
> <[email protected]>; Will Deacon <[email protected]>; Jonathan Corbet
> <[email protected]>; [email protected]; [email protected]
> *Subject:* Re: [PATCH] locking: Document that mutex_unlock() is non-atomic
>
> On 12/1/23 13:18, Jann Horn wrote:
>
> On Fri, Dec 1, 2023 at 7:12 PM David Laight<[email protected]> <mailto:[email protected]> wrote:
>
> From: Jann Horn
>
> I think this pattern anyway only works when you're only trying to wait
>
> for the current holder of the lock, not tasks that are queued up on
>
> the lock as waiters - so a task initially holds a stable reference to
>
> some object, then acquires the object's lock, then drops the original
>
> reference, and then later drops the lock.
>
> You can see an example of such mutex usage (which is explicitly legal
>
> with userspace POSIX mutexes, but is forbidden with kernel mutexes) at
>
> the bottom of the POSIX manpage for pthread_mutex_destroy() at
>
> <https://pubs.opengroup.org/onlinepubs/007904875/functions/pthread_mutex_destroy.html> <https://pubs.opengroup.org/onlinepubs/007904875/functions/pthread_mutex_destroy.html>,
>
> in the section "Destroying Mutexes".
>
> I don't understand at all what any of this is about.
>
> You cannot de-initialise, free (etc) a mutex (or any other piece of
>
> memory for that matter) if another thread can have a reference to it.
>
> If some other code might be holding the mutex it also might be just
>
> about to acquire it - you always need another lock of some kind to
>
> ensure that doesn't happen.
>
> IIRC pretty much the only time you need to acquire the mutex in the
>
> free path is if locks are chained, eg:
>
>         lock(table)
>
>         entry = find_entry();
>
>         lock(entry)
>
>         unlock(table)
>
>         ...
>
>         unlock(entry)
>
> Then the free code has to:
>
>         lock(table)
>
>         remove_from_table(entry)
>
>         lock(entry)
>
>         unlock(entry)
>
>         unlock(table)
>
>         free(entry)
>
> Yep, this is exactly the kind of code pattern for which I'm trying to
>
> document that it is forbidden with mutexes (while it is allowed with
>
> spinlocks).
>
> Actually, even spinlocks may not guarantee the lock/unlock sequence
> will flush out all the pending waiters in the case of paravirt spinlocks.
>
> Cheers,
> Longman
>
>
>
> Registered Address Lakeside, Bramley Road, Mount Farm, Milton Keynes,
> MK1 1PT, UK
> Registration No: 1397386 (Wales)
>
> P *Please consider the environment and don't print this e-mail unless
> you really need to*
>

2023-12-02 01:38:44

by Bagas Sanjaya

[permalink] [raw]
Subject: Re: [PATCH -v2] locking/mutex: Document that mutex_unlock() is non-atomic

On Fri, Dec 01, 2023 at 11:33:19AM +0100, Ingo Molnar wrote:
> =======================>
> From: Jann Horn <[email protected]>
> Date: Thu, 30 Nov 2023 21:48:17 +0100
> Subject: [PATCH] locking/mutex: Document that mutex_unlock() is non-atomic
>
> I have seen several cases of attempts to use mutex_unlock() to release an
> object such that the object can then be freed by another task.
>
> This is not safe because mutex_unlock(), in the
> MUTEX_FLAG_WAITERS && !MUTEX_FLAG_HANDOFF case, accesses the mutex
> structure after having marked it as unlocked; so mutex_unlock() requires
> its caller to ensure that the mutex stays alive until mutex_unlock()
> returns.
>
> If MUTEX_FLAG_WAITERS is set and there are real waiters, those waiters
> have to keep the mutex alive, but we could have a spurious
> MUTEX_FLAG_WAITERS left if an interruptible/killable waiter bailed
> between the points where __mutex_unlock_slowpath() did the cmpxchg
> reading the flags and where it acquired the wait_lock.
>
> ( With spinlocks, that kind of code pattern is allowed and, from what I
> remember, used in several places in the kernel. )
>
> Document this, such a semantic difference between mutexes and spinlocks
> is fairly unintuitive.
>
> [ mingo: Made the changelog a bit more assertive, refined the comments. ]
>
> Signed-off-by: Jann Horn <[email protected]>
> Signed-off-by: Ingo Molnar <[email protected]>
> Link: https://lore.kernel.org/r/[email protected]
> ---
> Documentation/locking/mutex-design.rst | 6 ++++++
> kernel/locking/mutex.c | 5 +++++
> 2 files changed, 11 insertions(+)
>
> diff --git a/Documentation/locking/mutex-design.rst b/Documentation/locking/mutex-design.rst
> index 78540cd7f54b..7572339b2f12 100644
> --- a/Documentation/locking/mutex-design.rst
> +++ b/Documentation/locking/mutex-design.rst
> @@ -101,6 +101,12 @@ features that make lock debugging easier and faster:
> - Detects multi-task circular deadlocks and prints out all affected
> locks and tasks (and only those tasks).
>
> +Releasing a mutex is not an atomic operation: Once a mutex release operation
> +has begun, another context may be able to acquire the mutex before the release
> +operation has fully completed. The mutex user must ensure that the mutex is not
> +destroyed while a release operation is still in progress - in other words,
> +callers of mutex_unlock() must ensure that the mutex stays alive until
> +mutex_unlock() has returned.
>
> Interfaces
> ----------
> diff --git a/kernel/locking/mutex.c b/kernel/locking/mutex.c
> index 2deeeca3e71b..cbae8c0b89ab 100644
> --- a/kernel/locking/mutex.c
> +++ b/kernel/locking/mutex.c
> @@ -532,6 +532,11 @@ static noinline void __sched __mutex_unlock_slowpath(struct mutex *lock, unsigne
> * This function must not be used in interrupt context. Unlocking
> * of a not locked mutex is not allowed.
> *
> + * The caller must ensure that the mutex stays alive until this function has
> + * returned - mutex_unlock() can NOT directly be used to release an object such
> + * that another concurrent task can free it.
> + * Mutexes are different from spinlocks & refcounts in this aspect.
> + *
> * This function is similar to (but not equivalent to) up().
> */
> void __sched mutex_unlock(struct mutex *lock)

Hi Ingo and Jann, thanks for the patch.

The patch LGTM, thanks!

Reviewed-by: Bagas Sanjaya <[email protected]>

--
An old man doll... just what I always wanted! - Clara


Attachments:
(No filename) (3.50 kB)
signature.asc (235.00 B)
Download all attachments

2023-12-02 15:52:18

by David Laight

[permalink] [raw]
Subject: RE: [PATCH] locking: Document that mutex_unlock() is non-atomic

From: Waiman Long
> Sent: 01 December 2023 19:16
>
> On 12/1/23 13:44, David Laight wrote:
> >
> > Pending waiters aren't the problem.
> >
> Pending waiters can still be a problem if code decides to free the lock
> containing object after a lock/unlock sequence as it may cause
> use-after-free.
> >
> > You have to ensure there aren't any, but the mutex() can be held.
> >
> Using reference count to track the number of active users is one way to
> prevent that if you only release the reference count after
> mutex_unlock() returns but not in the lock critical section.

I suspect the documentation need to be more explicit than just saying
it is non-atomic.
Saying something like:

The mutex structure may be accessed by mutex_unlock() after another
thread has locked and unlocked the mutex.

So if a reference count is used to ensure a structure remains valid when
a lock is released (with the item being freed when the count becomes zero)
the reference count itself cannot be protected by a mutex in the structure.
So code like:
...
count = --item->refcount;
mutex_unlock(item->mtx);
if (!count)
free(item);
can lead to a 'use after free' in mutex_unlock().
However if the refcount is atomic and decremented without the
mutex held there isn't a problem.

David

-
Registered Address Lakeside, Bramley Road, Mount Farm, Milton Keynes, MK1 1PT, UK
Registration No: 1397386 (Wales)

2023-12-02 22:39:55

by Waiman Long

[permalink] [raw]
Subject: Re: [PATCH] locking: Document that mutex_unlock() is non-atomic

On 12/2/23 10:51, David Laight wrote:
> From: Waiman Long
>> Sent: 01 December 2023 19:16
>>
>> On 12/1/23 13:44, David Laight wrote:
>>> Pending waiters aren't the problem.
>>>
>> Pending waiters can still be a problem if code decides to free the lock
>> containing object after a lock/unlock sequence as it may cause
>> use-after-free.
>>> You have to ensure there aren't any, but the mutex() can be held.
>>>
>> Using reference count to track the number of active users is one way to
>> prevent that if you only release the reference count after
>> mutex_unlock() returns but not in the lock critical section.
> I suspect the documentation need to be more explicit than just saying
> it is non-atomic.
> Saying something like:
>
> The mutex structure may be accessed by mutex_unlock() after another
> thread has locked and unlocked the mutex.
>
> So if a reference count is used to ensure a structure remains valid when
> a lock is released (with the item being freed when the count becomes zero)
> the reference count itself cannot be protected by a mutex in the structure.
> So code like:
> ...
> count = --item->refcount;
> mutex_unlock(item->mtx);
> if (!count)
> free(item);
> can lead to a 'use after free' in mutex_unlock().
> However if the refcount is atomic and decremented without the
> mutex held there isn't a problem.
>
> David

That is definitely better than saying it is non-atomic which is vague in
meaning.

Cheers,
Longman

2024-01-08 08:45:29

by Ingo Molnar

[permalink] [raw]
Subject: [PATCH] locking/mutex: Clarify that mutex_unlock(), and most other sleeping locks, cannot be used to reference-count objects


* Peter Zijlstra <[email protected]> wrote:

> On Fri, Dec 01, 2023 at 10:44:09AM -0000, tip-bot2 for Jann Horn wrote:
>
> > --- a/Documentation/locking/mutex-design.rst
> > +++ b/Documentation/locking/mutex-design.rst
> > @@ -101,6 +101,12 @@ features that make lock debugging easier and faster:
> > - Detects multi-task circular deadlocks and prints out all affected
> > locks and tasks (and only those tasks).
> >
> > +Releasing a mutex is not an atomic operation: Once a mutex release operation
>
> I still object to this confusing usage of atomic. Also all this also
> applies to all sleeping locks, rwsem etc. I don't see why we need to
> special case mutex here.
>
> Also completion_done() has an explicit lock+unlock on wait.lock to
> deal with this there.

Fair enough - but Jan's original observation stands: mutexes are the
sleeping locks most similar to spinlocks, so the locking & object lifetime
pattern that works under spinlocks cannot be carried over to mutexes in all
cases, and it's fair to warn about this pitfall.

We single out mutex_lock(), because they are the most similar in behavior
to spinlocks, and because this concern isn't hypothethical, it has been
observed in the wild with mutex users.

How about the language in the attached patch?

Thanks,

Ingo

================>
From: Ingo Molnar <[email protected]>
Date: Mon, 8 Jan 2024 09:31:16 +0100
Subject: [PATCH] locking/mutex: Clarify that mutex_unlock(), and most other sleeping locks, cannot be used to reference-count objects

Clarify the mutex_unlock() lock lifetime rules a bit more.

Signed-off-by: Ingo Molnar <[email protected]>
Cc: Peter Zijlstra <[email protected]>
Cc: Jann Horn <[email protected]>
Link: https://lore.kernel.org/r/[email protected]
---
Documentation/locking/mutex-design.rst | 21 +++++++++++++++------
1 file changed, 15 insertions(+), 6 deletions(-)

diff --git a/Documentation/locking/mutex-design.rst b/Documentation/locking/mutex-design.rst
index 7572339b2f12..f5270323cf0b 100644
--- a/Documentation/locking/mutex-design.rst
+++ b/Documentation/locking/mutex-design.rst
@@ -101,12 +101,21 @@ features that make lock debugging easier and faster:
- Detects multi-task circular deadlocks and prints out all affected
locks and tasks (and only those tasks).

-Releasing a mutex is not an atomic operation: Once a mutex release operation
-has begun, another context may be able to acquire the mutex before the release
-operation has fully completed. The mutex user must ensure that the mutex is not
-destroyed while a release operation is still in progress - in other words,
-callers of mutex_unlock() must ensure that the mutex stays alive until
-mutex_unlock() has returned.
+A mutex - and most other sleeping locks like rwsems - do not provide an
+implicit refcount for the memory they occupy, which could then be released
+with mutex_unlock().
+
+[ This is in contrast with spin_unlock() [or completion_done()], which APIs can
+ be used to guarantee that the memory is not touched by the lock implementation
+ after spin_unlock() releases the lock. ]
+
+Once a mutex release operation has begun within mutex_unlock(), another context
+may be able to acquire the mutex before the release operation has fully completed,
+and it's not safe to free the object then.
+
+The mutex user must ensure that the mutex is not destroyed while a release operation
+is still in progress - in other words, callers of mutex_unlock() must ensure that
+the mutex stays alive until mutex_unlock() has returned.

Interfaces
----------

2024-01-08 09:02:23

by Ingo Molnar

[permalink] [raw]
Subject: [PATCH -v2] locking/mutex: Clarify that mutex_unlock(), and most other sleeping locks, can still use the lock object after it's unlocked


* Ingo Molnar <[email protected]> wrote:

> > > +Releasing a mutex is not an atomic operation: Once a mutex release operation
> >
> > I still object to this confusing usage of atomic. Also all this also
> > applies to all sleeping locks, rwsem etc. I don't see why we need to
> > special case mutex here.
> >
> > Also completion_done() has an explicit lock+unlock on wait.lock to deal
> > with this there.
>
> Fair enough - but Jan's original observation stands: mutexes are the
> sleeping locks most similar to spinlocks, so the locking & object
> lifetime pattern that works under spinlocks cannot be carried over to
> mutexes in all cases, and it's fair to warn about this pitfall.
>
> We single out mutex_lock(), because they are the most similar in behavior
> to spinlocks, and because this concern isn't hypothethical, it has been
> observed in the wild with mutex users.
>
> How about the language in the attached patch?

Refined the language a bit more in the -v2 patch below.

Thanks,

Ingo

=============>
From: Ingo Molnar <[email protected]>
Date: Mon, 8 Jan 2024 09:31:16 +0100
Subject: [PATCH] locking/mutex: Clarify that mutex_unlock(), and most other sleeping locks, can still use the lock object after it's unlocked

Clarify the mutex lock lifetime rules a bit more.

Signed-off-by: Ingo Molnar <[email protected]>
Cc: Peter Zijlstra <[email protected]>
Cc: Jann Horn <[email protected]>
Cc: Linus Torvalds <[email protected]>
Link: https://lore.kernel.org/r/[email protected]
---
Documentation/locking/mutex-design.rst | 24 ++++++++++++++++++------
1 file changed, 18 insertions(+), 6 deletions(-)

diff --git a/Documentation/locking/mutex-design.rst b/Documentation/locking/mutex-design.rst
index 7572339b2f12..7c30b4aa5e28 100644
--- a/Documentation/locking/mutex-design.rst
+++ b/Documentation/locking/mutex-design.rst
@@ -101,12 +101,24 @@ features that make lock debugging easier and faster:
- Detects multi-task circular deadlocks and prints out all affected
locks and tasks (and only those tasks).

-Releasing a mutex is not an atomic operation: Once a mutex release operation
-has begun, another context may be able to acquire the mutex before the release
-operation has fully completed. The mutex user must ensure that the mutex is not
-destroyed while a release operation is still in progress - in other words,
-callers of mutex_unlock() must ensure that the mutex stays alive until
-mutex_unlock() has returned.
+Mutexes - and most other sleeping locks like rwsems - do not provide an
+implicit reference for the memory they occupy, which reference is released
+with mutex_unlock().
+
+[ This is in contrast with spin_unlock() [or completion_done()], which
+ APIs can be used to guarantee that the memory is not touched by the
+ lock implementation after spin_unlock()/completion_done() releases
+ the lock. ]
+
+mutex_unlock() may access the mutex structure even after it has internally
+released the lock already - so it's not safe for another context to
+acquire the mutex and assume that the mutex_unlock() context is not using
+the structure anymore.
+
+The mutex user must ensure that the mutex is not destroyed while a
+release operation is still in progress - in other words, callers of
+mutex_unlock() must ensure that the mutex stays alive until mutex_unlock()
+has returned.

Interfaces
----------

Subject: [tip: locking/core] locking/mutex: Clarify that mutex_unlock(), and most other sleeping locks, can still use the lock object after it's unlocked

The following commit has been merged into the locking/core branch of tip:

Commit-ID: 2b9d9e0a9ba0e24cb9c78336481f0ed8b2bc1ff2
Gitweb: https://git.kernel.org/tip/2b9d9e0a9ba0e24cb9c78336481f0ed8b2bc1ff2
Author: Ingo Molnar <[email protected]>
AuthorDate: Mon, 08 Jan 2024 09:31:16 +01:00
Committer: Ingo Molnar <[email protected]>
CommitterDate: Mon, 08 Jan 2024 09:55:31 +01:00

locking/mutex: Clarify that mutex_unlock(), and most other sleeping locks, can still use the lock object after it's unlocked

Clarify the mutex lock lifetime rules a bit more.

Signed-off-by: Ingo Molnar <[email protected]>
Cc: Peter Zijlstra <[email protected]>
Cc: Jann Horn <[email protected]>
Cc: Linus Torvalds <[email protected]>
Link: https://lore.kernel.org/r/[email protected]
---
Documentation/locking/mutex-design.rst | 24 ++++++++++++++++++------
1 file changed, 18 insertions(+), 6 deletions(-)

diff --git a/Documentation/locking/mutex-design.rst b/Documentation/locking/mutex-design.rst
index 7572339..7c30b4a 100644
--- a/Documentation/locking/mutex-design.rst
+++ b/Documentation/locking/mutex-design.rst
@@ -101,12 +101,24 @@ features that make lock debugging easier and faster:
- Detects multi-task circular deadlocks and prints out all affected
locks and tasks (and only those tasks).

-Releasing a mutex is not an atomic operation: Once a mutex release operation
-has begun, another context may be able to acquire the mutex before the release
-operation has fully completed. The mutex user must ensure that the mutex is not
-destroyed while a release operation is still in progress - in other words,
-callers of mutex_unlock() must ensure that the mutex stays alive until
-mutex_unlock() has returned.
+Mutexes - and most other sleeping locks like rwsems - do not provide an
+implicit reference for the memory they occupy, which reference is released
+with mutex_unlock().
+
+[ This is in contrast with spin_unlock() [or completion_done()], which
+ APIs can be used to guarantee that the memory is not touched by the
+ lock implementation after spin_unlock()/completion_done() releases
+ the lock. ]
+
+mutex_unlock() may access the mutex structure even after it has internally
+released the lock already - so it's not safe for another context to
+acquire the mutex and assume that the mutex_unlock() context is not using
+the structure anymore.
+
+The mutex user must ensure that the mutex is not destroyed while a
+release operation is still in progress - in other words, callers of
+mutex_unlock() must ensure that the mutex stays alive until mutex_unlock()
+has returned.

Interfaces
----------

2024-01-08 15:40:49

by Jann Horn

[permalink] [raw]
Subject: Re: [PATCH] locking/mutex: Clarify that mutex_unlock(), and most other sleeping locks, cannot be used to reference-count objects

On Mon, Jan 8, 2024 at 9:45 AM Ingo Molnar <[email protected]> wrote:
> * Peter Zijlstra <[email protected]> wrote:
>
> > On Fri, Dec 01, 2023 at 10:44:09AM -0000, tip-bot2 for Jann Horn wrote:
> >
> > > --- a/Documentation/locking/mutex-design.rst
> > > +++ b/Documentation/locking/mutex-design.rst
> > > @@ -101,6 +101,12 @@ features that make lock debugging easier and faster:
> > > - Detects multi-task circular deadlocks and prints out all affected
> > > locks and tasks (and only those tasks).
> > >
> > > +Releasing a mutex is not an atomic operation: Once a mutex release operation
> >
> > I still object to this confusing usage of atomic. Also all this also
> > applies to all sleeping locks, rwsem etc. I don't see why we need to
> > special case mutex here.
> >
> > Also completion_done() has an explicit lock+unlock on wait.lock to
> > deal with this there.
>
> Fair enough - but Jan's original observation stands: mutexes are the
> sleeping locks most similar to spinlocks, so the locking & object lifetime
> pattern that works under spinlocks cannot be carried over to mutexes in all
> cases, and it's fair to warn about this pitfall.
>
> We single out mutex_lock(), because they are the most similar in behavior
> to spinlocks, and because this concern isn't hypothethical, it has been
> observed in the wild with mutex users.
>
> How about the language in the attached patch?

In case you missed it, I sent this rewritten documentation patch in
response to the feedback I got, intended to replace the patch that is
now sitting in the tip tree (but I don't know how that works
procedurally for something that's already in the tip tree, whether
you'd want to just swap out the patch with a forced update, or revert
out the old version, or something else):
<https://lore.kernel.org/all/[email protected]/>

Since there were comments on how this is really a more general rule
than a mutex-specific one, that version doesn't touch
Documentation/locking/mutex-design.rst and instead documents the rule
in Documentation/locking/locktypes.rst; and then it adds comments
above some of the most common unlock-type functions that would be
affected.