[PATCH 2/3] futex: reject the unused PR_FUTEX_HASH arguments

From: Bradley Morgan

Date: Wed Nov 04 2026 - 21:45:56 EST


PR_SET_MM, PR_SET_MDWE and the shadow stack cases all check that the
args they do not use are zero before doing anything. PR_FUTEX_HASH is
the one that does not. arg5 never even reaches futex_hash_prctl(), and
the GET_SLOTS branch never looks at arg3 or arg4, so
prctl(PR_FUTEX_HASH, PR_FUTEX_HASH_GET_SLOTS, 1, 2, 3) hands back the
slot count.

Once an arg is ignored it is burned, whatever userspace sends there
today becomes part of the ABI and we can never use it for anything
without breaking whoever already passes garbage. Take arg5 too and
reject the unused args per sub command, same as the neighbours do.

prctl() is variadic, so the two callers under tools/ leave those args as
whatever the register holds, they only work today because nothing was
checked. The preceding patch in this series writes the zeros out first,
so this does not break them.

Fixes: 80367ad01d93 ("futex: Add basic infrastructure for local task local hash")
Signed-off-by: Bradley Morgan <brads@xxxxxxxxxxxxxx>
---
include/linux/futex.h | 6 ++++--
kernel/futex/core.c | 7 +++++--
kernel/sys.c | 2 +-
3 files changed, 10 insertions(+), 5 deletions(-)

diff --git a/include/linux/futex.h b/include/linux/futex.h
index a895d8c69d663..70adc9fce6e63 100644
--- a/include/linux/futex.h
+++ b/include/linux/futex.h
@@ -80,7 +80,8 @@ void futex_exec_done(struct task_struct *tsk);

long do_futex(u32 __user *uaddr, int op, u32 val, ktime_t *timeout,
u32 __user *uaddr2, u32 val2, u32 val3);
-int futex_hash_prctl(unsigned long arg2, unsigned long arg3, unsigned long arg4);
+int futex_hash_prctl(unsigned long arg2, unsigned long arg3, unsigned long arg4,
+ unsigned long arg5);

#ifdef CONFIG_FUTEX_PRIVATE_HASH
int futex_hash_allocate_default(void);
@@ -100,7 +101,8 @@ static inline long do_futex(u32 __user *uaddr, int op, u32 val, ktime_t *timeout
{
return -EINVAL;
}
-static inline int futex_hash_prctl(unsigned long arg2, unsigned long arg3, unsigned long arg4)
+static inline int futex_hash_prctl(unsigned long arg2, unsigned long arg3,
+ unsigned long arg4, unsigned long arg5)
{
return -EINVAL;
}
diff --git a/kernel/futex/core.c b/kernel/futex/core.c
index a240638f69e5c..eabf8abc2624b 100644
--- a/kernel/futex/core.c
+++ b/kernel/futex/core.c
@@ -2068,19 +2068,22 @@ void futex_mm_init(struct mm_struct *mm)
futex_robust_unlock_init_mm(&mm->futex);
}

-int futex_hash_prctl(unsigned long arg2, unsigned long arg3, unsigned long arg4)
+int futex_hash_prctl(unsigned long arg2, unsigned long arg3, unsigned long arg4,
+ unsigned long arg5)
{
unsigned int flags = FH_CUSTOM;
int ret;

switch (arg2) {
case PR_FUTEX_HASH_SET_SLOTS:
- if (arg4)
+ if (arg4 || arg5)
return -EINVAL;
ret = futex_hash_allocate(arg3, flags);
break;

case PR_FUTEX_HASH_GET_SLOTS:
+ if (arg3 || arg4 || arg5)
+ return -EINVAL;
ret = futex_hash_get_slots();
break;

diff --git a/kernel/sys.c b/kernel/sys.c
index 755bfa5ccd92c..c4839c986f5e0 100644
--- a/kernel/sys.c
+++ b/kernel/sys.c
@@ -2885,7 +2885,7 @@ SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3,
error = posixtimer_create_prctl(arg2);
break;
case PR_FUTEX_HASH:
- error = futex_hash_prctl(arg2, arg3, arg4);
+ error = futex_hash_prctl(arg2, arg3, arg4, arg5);
break;
case PR_RSEQ_SLICE_EXTENSION:
if (arg4 || arg5)
--
2.53.0