[PATCH v2 4/4] futex: Verify under the lock if hash can be replaced

From: Sebastian Andrzej Siewior
Date: Tue Jun 10 2025 - 06:44:37 EST


Once the global hash is requested there is no way back to switch back to
the per-task private hash. This is checked at the begin of the function.

It is possible that two threads simultaneously request the global hash
and both pass the initial check and block later on the
mm::futex_hash_lock. In this case the first thread performs the switch
to the global hash. The second thread will also attempt to switch to the
global hash and while doing so, accessing the nonexisting slot 1 of the
struct futex_private_hash.
The same applies if the hash is made immutable: There is no reference
counting and the hash must not be replaced.

Verify under mm_struct::futex_phash that neither the global hash nor an
immutable hash in use.

Tested-by: "Lai, Yi" <yi1.lai@xxxxxxxxxxxxxxx>
Reported-by: "Lai, Yi" <yi1.lai@xxxxxxxxxxxxxxx>
Closes: https://lore.kernel.org/all/aDwDw9Aygqo6oAx+@ly-workstation/
Fixes: bd54df5ea7cad ("futex: Allow to resize the private local hash")
Signed-off-by: Sebastian Andrzej Siewior <bigeasy@xxxxxxxxxxxxx>
---
kernel/futex/core.c | 10 ++++++++++
1 file changed, 10 insertions(+)

diff --git a/kernel/futex/core.c b/kernel/futex/core.c
index b652d2f60c409..18804b2bf38e8 100644
--- a/kernel/futex/core.c
+++ b/kernel/futex/core.c
@@ -1629,6 +1629,16 @@ static int futex_hash_allocate(unsigned int hash_slots, unsigned int flags)
mm->futex_phash_new = NULL;

if (fph) {
+ if (cur && (!cur->hash_mask || cur->immutable)) {
+ /*
+ * If two threads simultaneously request a hash which
+ * can not be changed then the first one performs
+ * the switch, the second one returns here.
+ */
+ free = fph;
+ mm->futex_phash_new = new;
+ return -EBUSY;
+ }
if (cur && !new) {
/*
* If we have an existing hash, but do not yet have
--
2.49.0