* [PATCH v2] futex: don't leak robust_list pointer on exec race
@ 2025-08-04 11:55 Pranav Tyagi
2025-08-04 15:12 ` Kees Cook
` (3 more replies)
0 siblings, 4 replies; 6+ messages in thread
From: Pranav Tyagi @ 2025-08-04 11:55 UTC (permalink / raw)
To: tglx, mingo, peterz, dvhart, dave, andrealmeid, linux-kernel
Cc: jann, keescook, skhan, linux-kernel-mentees, Pranav Tyagi
sys_get_robust_list() and compat_get_robust_list() use
ptrace_may_access() to check if the calling task is allowed to access
another task's robust_list pointer. This check is racy against a
concurrent exec() in the target process.
During exec(), a task may transition from a non-privileged binary to a
privileged one (e.g., setuid binary) and its credentials/memory mappings
may change. If get_robust_list() performs ptrace_may_access() before
this transition, it may erroneously allow access to sensitive information
after the target becomes privileged.
A racy access allows an attacker to exploit a window
during which ptrace_may_access() passes before a target process
transitions to a privileged state via exec().
For example, consider a non-privileged task T that is about to execute a
setuid-root binary. An attacker task A calls get_robust_list(T) while T
is still unprivileged. Since ptrace_may_access() checks permissions
based on current credentials, it succeeds. However, if T begins exec
immediately afterwards, it becomes privileged and may change its memory
mappings. Because get_robust_list() proceeds to access T->robust_list
without synchronizing with exec() it may read user-space pointers from a
now-privileged process.
This violates the intended post-exec access restrictions and could
expose sensitive memory addresses or be used as a primitive in a larger
exploit chain. Consequently, the race can lead to unauthorized
disclosure of information across privilege boundaries and poses a
potential security risk.
Take a read lock on signal->exec_update_lock prior to invoking
ptrace_may_access() and accessing the robust_list/compat_robust_list.
This ensures that the target task's exec state remains stable during the
check, allowing for consistent and synchronized validation of
credentials.
changed in v2:
- improved changelog
- helper function for common part of the compat and native syscalls
Signed-off-by: Pranav Tyagi <pranav.tyagi03@gmail.com>
Suggested-by: Jann Horn <jann@thejh.net>
Link: https://lore.kernel.org/linux-fsdevel/1477863998-3298-5-git-send-email-jann@thejh.net/
Link: https://github.com/KSPP/linux/issues/119
---
kernel/futex/syscalls.c | 110 ++++++++++++++++++++++------------------
1 file changed, 62 insertions(+), 48 deletions(-)
diff --git a/kernel/futex/syscalls.c b/kernel/futex/syscalls.c
index 4b6da9116aa6..3278d91d95ce 100644
--- a/kernel/futex/syscalls.c
+++ b/kernel/futex/syscalls.c
@@ -39,46 +39,81 @@ SYSCALL_DEFINE2(set_robust_list, struct robust_list_head __user *, head,
return 0;
}
-/**
- * sys_get_robust_list() - Get the robust-futex list head of a task
- * @pid: pid of the process [zero for current task]
- * @head_ptr: pointer to a list-head pointer, the kernel fills it in
- * @len_ptr: pointer to a length field, the kernel fills in the header size
- */
-SYSCALL_DEFINE3(get_robust_list, int, pid,
- struct robust_list_head __user * __user *, head_ptr,
- size_t __user *, len_ptr)
+static void __user *get_robust_list_common(int pid,
+ bool compat)
{
- struct robust_list_head __user *head;
+ void __user *head;
unsigned long ret;
- struct task_struct *p;
- rcu_read_lock();
+ struct task_struct *p;
- ret = -ESRCH;
- if (!pid)
+ if (!pid) {
p = current;
- else {
+ get_task_struct(p);
+ } else {
+ rcu_read_lock();
p = find_task_by_vpid(pid);
+ /*
+ * pin the task to permit dropping the RCU read lock before
+ * acquiring the semaphore
+ */
+ if (p)
+ get_task_struct(p);
+ rcu_read_unlock();
if (!p)
- goto err_unlock;
+ return ERR_PTR(-ESRCH);
}
+ /*
+ * Hold exec_update_lock to serialize with concurrent exec()
+ * so ptrace_may_access() is checked against stable credentials
+ */
+
+ ret = down_read_killable(&p->signal->exec_update_lock);
+ if (ret)
+ goto err_put;
+
ret = -EPERM;
if (!ptrace_may_access(p, PTRACE_MODE_READ_REALCREDS))
goto err_unlock;
- head = p->robust_list;
- rcu_read_unlock();
+ if (compat)
+ head = p->compat_robust_list;
+ else
+ head = p->robust_list;
- if (put_user(sizeof(*head), len_ptr))
- return -EFAULT;
- return put_user(head, head_ptr);
+ up_read(&p->signal->exec_update_lock);
+ put_task_struct(p);
+
+ return head;
err_unlock:
- rcu_read_unlock();
+ up_read(&p->signal->exec_update_lock);
+err_put:
+ put_task_struct(p);
+ return ERR_PTR(ret);
+}
- return ret;
+
+/**
+ * sys_get_robust_list() - Get the robust-futex list head of a task
+ * @pid: pid of the process [zero for current task]
+ * @head_ptr: pointer to a list-head pointer, the kernel fills it in
+ * @len_ptr: pointer to a length field, the kernel fills in the header size
+ */
+SYSCALL_DEFINE3(get_robust_list, int, pid,
+ struct robust_list_head __user * __user *, head_ptr,
+ size_t __user *, len_ptr)
+{
+ struct robust_list_head __user *head =
+ get_robust_list_common(pid, false);
+
+ if (IS_ERR(head))
+ return PTR_ERR(head);
+
+ if (put_user(sizeof(*head), len_ptr))
+ return -EFAULT;
+ return put_user(head, head_ptr);
}
long do_futex(u32 __user *uaddr, int op, u32 val, ktime_t *timeout,
@@ -455,36 +490,15 @@ COMPAT_SYSCALL_DEFINE3(get_robust_list, int, pid,
compat_uptr_t __user *, head_ptr,
compat_size_t __user *, len_ptr)
{
- struct compat_robust_list_head __user *head;
- unsigned long ret;
- struct task_struct *p;
+ struct compat_robust_list_head __user *head =
+ get_robust_list_common(pid, true);
- rcu_read_lock();
-
- ret = -ESRCH;
- if (!pid)
- p = current;
- else {
- p = find_task_by_vpid(pid);
- if (!p)
- goto err_unlock;
- }
-
- ret = -EPERM;
- if (!ptrace_may_access(p, PTRACE_MODE_READ_REALCREDS))
- goto err_unlock;
-
- head = p->compat_robust_list;
- rcu_read_unlock();
+ if (IS_ERR(head))
+ return PTR_ERR(head);
if (put_user(sizeof(*head), len_ptr))
return -EFAULT;
return put_user(ptr_to_compat(head), head_ptr);
-
-err_unlock:
- rcu_read_unlock();
-
- return ret;
}
#endif /* CONFIG_COMPAT */
--
2.49.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH v2] futex: don't leak robust_list pointer on exec race
2025-08-04 11:55 [PATCH v2] futex: don't leak robust_list pointer on exec race Pranav Tyagi
@ 2025-08-04 15:12 ` Kees Cook
2025-08-05 8:19 ` Thomas Gleixner
` (2 subsequent siblings)
3 siblings, 0 replies; 6+ messages in thread
From: Kees Cook @ 2025-08-04 15:12 UTC (permalink / raw)
To: Pranav Tyagi
Cc: tglx, mingo, peterz, dvhart, dave, andrealmeid, linux-kernel,
jann, skhan, linux-kernel-mentees
On Mon, Aug 04, 2025 at 05:25:33PM +0530, Pranav Tyagi wrote:
> sys_get_robust_list() and compat_get_robust_list() use
> ptrace_may_access() to check if the calling task is allowed to access
> another task's robust_list pointer. This check is racy against a
> concurrent exec() in the target process.
>
> During exec(), a task may transition from a non-privileged binary to a
> privileged one (e.g., setuid binary) and its credentials/memory mappings
> may change. If get_robust_list() performs ptrace_may_access() before
> this transition, it may erroneously allow access to sensitive information
> after the target becomes privileged.
>
> A racy access allows an attacker to exploit a window
> during which ptrace_may_access() passes before a target process
> transitions to a privileged state via exec().
>
> For example, consider a non-privileged task T that is about to execute a
> setuid-root binary. An attacker task A calls get_robust_list(T) while T
> is still unprivileged. Since ptrace_may_access() checks permissions
> based on current credentials, it succeeds. However, if T begins exec
> immediately afterwards, it becomes privileged and may change its memory
> mappings. Because get_robust_list() proceeds to access T->robust_list
> without synchronizing with exec() it may read user-space pointers from a
> now-privileged process.
>
> This violates the intended post-exec access restrictions and could
> expose sensitive memory addresses or be used as a primitive in a larger
> exploit chain. Consequently, the race can lead to unauthorized
> disclosure of information across privilege boundaries and poses a
> potential security risk.
>
> Take a read lock on signal->exec_update_lock prior to invoking
> ptrace_may_access() and accessing the robust_list/compat_robust_list.
> This ensures that the target task's exec state remains stable during the
> check, allowing for consistent and synchronized validation of
> credentials.
Thanks for this commit log; I think it captures the concern very well.
>
> changed in v2:
> - improved changelog
> - helper function for common part of the compat and native syscalls
>
> Signed-off-by: Pranav Tyagi <pranav.tyagi03@gmail.com>
> Suggested-by: Jann Horn <jann@thejh.net>
> Link: https://lore.kernel.org/linux-fsdevel/1477863998-3298-5-git-send-email-jann@thejh.net/
> Link: https://github.com/KSPP/linux/issues/119
> ---
> kernel/futex/syscalls.c | 110 ++++++++++++++++++++++------------------
> 1 file changed, 62 insertions(+), 48 deletions(-)
>
> diff --git a/kernel/futex/syscalls.c b/kernel/futex/syscalls.c
> index 4b6da9116aa6..3278d91d95ce 100644
> --- a/kernel/futex/syscalls.c
> +++ b/kernel/futex/syscalls.c
> @@ -39,46 +39,81 @@ SYSCALL_DEFINE2(set_robust_list, struct robust_list_head __user *, head,
> return 0;
> }
>
> -/**
> - * sys_get_robust_list() - Get the robust-futex list head of a task
> - * @pid: pid of the process [zero for current task]
> - * @head_ptr: pointer to a list-head pointer, the kernel fills it in
> - * @len_ptr: pointer to a length field, the kernel fills in the header size
> - */
> -SYSCALL_DEFINE3(get_robust_list, int, pid,
> - struct robust_list_head __user * __user *, head_ptr,
> - size_t __user *, len_ptr)
> +static void __user *get_robust_list_common(int pid,
> + bool compat)
> {
> - struct robust_list_head __user *head;
> + void __user *head;
> unsigned long ret;
> - struct task_struct *p;
>
> - rcu_read_lock();
> + struct task_struct *p;
>
> - ret = -ESRCH;
> - if (!pid)
> + if (!pid) {
> p = current;
> - else {
> + get_task_struct(p);
> + } else {
> + rcu_read_lock();
> p = find_task_by_vpid(pid);
> + /*
> + * pin the task to permit dropping the RCU read lock before
> + * acquiring the semaphore
> + */
> + if (p)
> + get_task_struct(p);
> + rcu_read_unlock();
> if (!p)
> - goto err_unlock;
> + return ERR_PTR(-ESRCH);
> }
>
> + /*
> + * Hold exec_update_lock to serialize with concurrent exec()
> + * so ptrace_may_access() is checked against stable credentials
> + */
> +
> + ret = down_read_killable(&p->signal->exec_update_lock);
> + if (ret)
> + goto err_put;
> +
> ret = -EPERM;
> if (!ptrace_may_access(p, PTRACE_MODE_READ_REALCREDS))
> goto err_unlock;
>
> - head = p->robust_list;
> - rcu_read_unlock();
> + if (compat)
> + head = p->compat_robust_list;
> + else
> + head = p->robust_list;
>
> - if (put_user(sizeof(*head), len_ptr))
> - return -EFAULT;
> - return put_user(head, head_ptr);
> + up_read(&p->signal->exec_update_lock);
> + put_task_struct(p);
> +
> + return head;
>
> err_unlock:
> - rcu_read_unlock();
> + up_read(&p->signal->exec_update_lock);
> +err_put:
> + put_task_struct(p);
> + return ERR_PTR(ret);
> +}
>
> - return ret;
> +
> +/**
> + * sys_get_robust_list() - Get the robust-futex list head of a task
> + * @pid: pid of the process [zero for current task]
> + * @head_ptr: pointer to a list-head pointer, the kernel fills it in
> + * @len_ptr: pointer to a length field, the kernel fills in the header size
> + */
> +SYSCALL_DEFINE3(get_robust_list, int, pid,
> + struct robust_list_head __user * __user *, head_ptr,
> + size_t __user *, len_ptr)
> +{
> + struct robust_list_head __user *head =
> + get_robust_list_common(pid, false);
> +
> + if (IS_ERR(head))
> + return PTR_ERR(head);
> +
> + if (put_user(sizeof(*head), len_ptr))
> + return -EFAULT;
> + return put_user(head, head_ptr);
> }
>
> long do_futex(u32 __user *uaddr, int op, u32 val, ktime_t *timeout,
> @@ -455,36 +490,15 @@ COMPAT_SYSCALL_DEFINE3(get_robust_list, int, pid,
> compat_uptr_t __user *, head_ptr,
> compat_size_t __user *, len_ptr)
> {
> - struct compat_robust_list_head __user *head;
> - unsigned long ret;
> - struct task_struct *p;
> + struct compat_robust_list_head __user *head =
> + get_robust_list_common(pid, true);
>
> - rcu_read_lock();
> -
> - ret = -ESRCH;
> - if (!pid)
> - p = current;
> - else {
> - p = find_task_by_vpid(pid);
> - if (!p)
> - goto err_unlock;
> - }
> -
> - ret = -EPERM;
> - if (!ptrace_may_access(p, PTRACE_MODE_READ_REALCREDS))
> - goto err_unlock;
> -
> - head = p->compat_robust_list;
> - rcu_read_unlock();
> + if (IS_ERR(head))
> + return PTR_ERR(head);
>
> if (put_user(sizeof(*head), len_ptr))
> return -EFAULT;
> return put_user(ptr_to_compat(head), head_ptr);
> -
> -err_unlock:
> - rcu_read_unlock();
> -
> - return ret;
> }
> #endif /* CONFIG_COMPAT */
>
> --
> 2.49.0
>
This looks good to me; it needs to use a void * for the common helper,
but it's nice to have it all in one place.
Reviewed-by: Kees Cook <kees@kernel.org>
-Kees
--
Kees Cook
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH v2] futex: don't leak robust_list pointer on exec race
2025-08-04 11:55 [PATCH v2] futex: don't leak robust_list pointer on exec race Pranav Tyagi
2025-08-04 15:12 ` Kees Cook
@ 2025-08-05 8:19 ` Thomas Gleixner
2025-08-05 13:57 ` Pranav Tyagi
2025-08-05 9:01 ` kernel test robot
2025-08-05 18:27 ` kernel test robot
3 siblings, 1 reply; 6+ messages in thread
From: Thomas Gleixner @ 2025-08-05 8:19 UTC (permalink / raw)
To: Pranav Tyagi, mingo, peterz, dvhart, dave, andrealmeid,
linux-kernel
Cc: jann, keescook, skhan, linux-kernel-mentees, Pranav Tyagi
On Mon, Aug 04 2025 at 17:25, Pranav Tyagi wrote:
> Take a read lock on signal->exec_update_lock prior to invoking
> ptrace_may_access() and accessing the robust_list/compat_robust_list.
> This ensures that the target task's exec state remains stable during the
> check, allowing for consistent and synchronized validation of
> credentials.
>
> changed in v2:
> - improved changelog
> - helper function for common part of the compat and native syscalls
Please put version log below the --- line. That's not part of the change log.
> Signed-off-by: Pranav Tyagi <pranav.tyagi03@gmail.com>
> Suggested-by: Jann Horn <jann@thejh.net>
> Link: https://lore.kernel.org/linux-fsdevel/1477863998-3298-5-git-send-email-jann@thejh.net/
> Link: https://github.com/KSPP/linux/issues/119
> ---
> kernel/futex/syscalls.c | 110 ++++++++++++++++++++++------------------
> 1 file changed, 62 insertions(+), 48 deletions(-)
>
> diff --git a/kernel/futex/syscalls.c b/kernel/futex/syscalls.c
> index 4b6da9116aa6..3278d91d95ce 100644
> --- a/kernel/futex/syscalls.c
> +++ b/kernel/futex/syscalls.c
> @@ -39,46 +39,81 @@ SYSCALL_DEFINE2(set_robust_list, struct robust_list_head __user *, head,
> return 0;
> }
>
> -/**
> - * sys_get_robust_list() - Get the robust-futex list head of a task
> - * @pid: pid of the process [zero for current task]
> - * @head_ptr: pointer to a list-head pointer, the kernel fills it in
> - * @len_ptr: pointer to a length field, the kernel fills in the header size
> - */
> -SYSCALL_DEFINE3(get_robust_list, int, pid,
> - struct robust_list_head __user * __user *, head_ptr,
> - size_t __user *, len_ptr)
> +static void __user *get_robust_list_common(int pid,
> + bool compat)
What is this random line break for?
> {
> - struct robust_list_head __user *head;
> + void __user *head;
> unsigned long ret;
> - struct task_struct *p;
>
Stray new line and please use reverse fir tree ordering of variables:
https://www.kernel.org/doc/html/latest/process/maintainer-tip.html#variable-declarations
> - rcu_read_lock();
> + struct task_struct *p;
>
> - ret = -ESRCH;
> - if (!pid)
> + if (!pid) {
> p = current;
> - else {
> + get_task_struct(p);
> + } else {
> + rcu_read_lock();
> p = find_task_by_vpid(pid);
> + /*
> + * pin the task to permit dropping the RCU read lock before
> + * acquiring the semaphore
> + */
> + if (p)
> + get_task_struct(p);
> + rcu_read_unlock();
> if (!p)
> - goto err_unlock;
> + return ERR_PTR(-ESRCH);
scoped_guard(rcu) {
p = find_task_by_vpid(pid);
if (!p)
return (void __user *)ERR_PTR(-ESRCH);
get_task_struct(p);
}
No need for a comment about pinning the task. This is obvious and a
common pattern all over the place. And note the type case on the error
return.
But you can simplify this whole thing even further:
struct task_struct *p = current;
scoped_guard(rcu) {
if (pid) {
p = find_task_by_vpid(pid);
if (!p)
return (void __user *)ERR_PTR(-ESRCH);
}
get_task_struct(p);
}
Yes, RCU is not required for the !pid case, but this is not a hot path.
>
> + /*
> + * Hold exec_update_lock to serialize with concurrent exec()
> + * so ptrace_may_access() is checked against stable credentials
> + */
> +
Stray newline.
> + ret = down_read_killable(&p->signal->exec_update_lock);
> + if (ret)
> + goto err_put;
> +
> ret = -EPERM;
> if (!ptrace_may_access(p, PTRACE_MODE_READ_REALCREDS))
> goto err_unlock;
>
> - head = p->robust_list;
> - rcu_read_unlock();
> + if (compat)
> + head = p->compat_robust_list;
> + else
> + head = p->robust_list;
Brain compiler complains about a build fail with CONFIG_COMPAT=n
static inline void __user *task_robust_list(struct task_struct *p, bool compat)
{
#ifdef COMPAT
if (compat)
return p->compat_robust_list;
#endif
return p->robust_list;
}
So you don't have the #ifdef ugly in this function..
> - if (put_user(sizeof(*head), len_ptr))
> - return -EFAULT;
> - return put_user(head, head_ptr);
> + up_read(&p->signal->exec_update_lock);
> + put_task_struct(p);
> +
> + return head;
>
> err_unlock:
> - rcu_read_unlock();
> + up_read(&p->signal->exec_update_lock);
> +err_put:
> + put_task_struct(p);
> + return ERR_PTR(ret);
> +}
>
> - return ret;
> +
> +/**
> + * sys_get_robust_list() - Get the robust-futex list head of a task
> + * @pid: pid of the process [zero for current task]
> + * @head_ptr: pointer to a list-head pointer, the kernel fills it in
> + * @len_ptr: pointer to a length field, the kernel fills in the header size
> + */
> +SYSCALL_DEFINE3(get_robust_list, int, pid,
> + struct robust_list_head __user * __user *, head_ptr,
> + size_t __user *, len_ptr)
> +{
> + struct robust_list_head __user *head =
> + get_robust_list_common(pid, false);
No line break required.
> + if (IS_ERR(head))
> + return PTR_ERR(head);
> +
> + if (put_user(sizeof(*head), len_ptr))
> + return -EFAULT;
> + return put_user(head, head_ptr);
> }
>
> long do_futex(u32 __user *uaddr, int op, u32 val, ktime_t *timeout,
> @@ -455,36 +490,15 @@ COMPAT_SYSCALL_DEFINE3(get_robust_list, int, pid,
> compat_uptr_t __user *, head_ptr,
> compat_size_t __user *, len_ptr)
> {
> - struct compat_robust_list_head __user *head;
> - unsigned long ret;
> - struct task_struct *p;
> + struct compat_robust_list_head __user *head =
> + get_robust_list_common(pid, true);
Ditto
Thanks,
tglx
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH v2] futex: don't leak robust_list pointer on exec race
2025-08-04 11:55 [PATCH v2] futex: don't leak robust_list pointer on exec race Pranav Tyagi
2025-08-04 15:12 ` Kees Cook
2025-08-05 8:19 ` Thomas Gleixner
@ 2025-08-05 9:01 ` kernel test robot
2025-08-05 18:27 ` kernel test robot
3 siblings, 0 replies; 6+ messages in thread
From: kernel test robot @ 2025-08-05 9:01 UTC (permalink / raw)
To: Pranav Tyagi, tglx, mingo, peterz, dvhart, dave, andrealmeid,
linux-kernel
Cc: llvm, oe-kbuild-all, jann, keescook, skhan, linux-kernel-mentees,
Pranav Tyagi
Hi Pranav,
kernel test robot noticed the following build errors:
[auto build test ERROR on tip/locking/core]
[also build test ERROR on linus/master v6.16 next-20250805]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Pranav-Tyagi/futex-don-t-leak-robust_list-pointer-on-exec-race/20250804-195739
base: tip/locking/core
patch link: https://lore.kernel.org/r/20250804115533.14186-1-pranav.tyagi03%40gmail.com
patch subject: [PATCH v2] futex: don't leak robust_list pointer on exec race
config: riscv-randconfig-001-20250805 (https://download.01.org/0day-ci/archive/20250805/202508051607.VJkmdtGV-lkp@intel.com/config)
compiler: clang version 18.1.8 (https://github.com/llvm/llvm-project 3b5b5c1ec4a3095ab096dd780e84d7ab81f3d7ff)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250805/202508051607.VJkmdtGV-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202508051607.VJkmdtGV-lkp@intel.com/
All errors (new ones prefixed by >>):
>> kernel/futex/syscalls.c:81:13: error: no member named 'compat_robust_list' in 'struct task_struct'
81 | head = p->compat_robust_list;
| ~ ^
1 error generated.
vim +81 kernel/futex/syscalls.c
41
42 static void __user *get_robust_list_common(int pid,
43 bool compat)
44 {
45 void __user *head;
46 unsigned long ret;
47
48 struct task_struct *p;
49
50 if (!pid) {
51 p = current;
52 get_task_struct(p);
53 } else {
54 rcu_read_lock();
55 p = find_task_by_vpid(pid);
56 /*
57 * pin the task to permit dropping the RCU read lock before
58 * acquiring the semaphore
59 */
60 if (p)
61 get_task_struct(p);
62 rcu_read_unlock();
63 if (!p)
64 return ERR_PTR(-ESRCH);
65 }
66
67 /*
68 * Hold exec_update_lock to serialize with concurrent exec()
69 * so ptrace_may_access() is checked against stable credentials
70 */
71
72 ret = down_read_killable(&p->signal->exec_update_lock);
73 if (ret)
74 goto err_put;
75
76 ret = -EPERM;
77 if (!ptrace_may_access(p, PTRACE_MODE_READ_REALCREDS))
78 goto err_unlock;
79
80 if (compat)
> 81 head = p->compat_robust_list;
82 else
83 head = p->robust_list;
84
85 up_read(&p->signal->exec_update_lock);
86 put_task_struct(p);
87
88 return head;
89
90 err_unlock:
91 up_read(&p->signal->exec_update_lock);
92 err_put:
93 put_task_struct(p);
94 return ERR_PTR(ret);
95 }
96
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH v2] futex: don't leak robust_list pointer on exec race
2025-08-05 8:19 ` Thomas Gleixner
@ 2025-08-05 13:57 ` Pranav Tyagi
0 siblings, 0 replies; 6+ messages in thread
From: Pranav Tyagi @ 2025-08-05 13:57 UTC (permalink / raw)
To: Thomas Gleixner
Cc: mingo, peterz, dvhart, dave, andrealmeid, linux-kernel, jann,
keescook, skhan, linux-kernel-mentees
On Tue, Aug 5, 2025 at 1:49 PM Thomas Gleixner <tglx@linutronix.de> wrote:
>
> On Mon, Aug 04 2025 at 17:25, Pranav Tyagi wrote:
> > Take a read lock on signal->exec_update_lock prior to invoking
> > ptrace_may_access() and accessing the robust_list/compat_robust_list.
> > This ensures that the target task's exec state remains stable during the
> > check, allowing for consistent and synchronized validation of
> > credentials.
> >
> > changed in v2:
> > - improved changelog
> > - helper function for common part of the compat and native syscalls
>
> Please put version log below the --- line. That's not part of the change log.
>
> > Signed-off-by: Pranav Tyagi <pranav.tyagi03@gmail.com>
> > Suggested-by: Jann Horn <jann@thejh.net>
> > Link: https://lore.kernel.org/linux-fsdevel/1477863998-3298-5-git-send-email-jann@thejh.net/
> > Link: https://github.com/KSPP/linux/issues/119
> > ---
> > kernel/futex/syscalls.c | 110 ++++++++++++++++++++++------------------
> > 1 file changed, 62 insertions(+), 48 deletions(-)
> >
> > diff --git a/kernel/futex/syscalls.c b/kernel/futex/syscalls.c
> > index 4b6da9116aa6..3278d91d95ce 100644
> > --- a/kernel/futex/syscalls.c
> > +++ b/kernel/futex/syscalls.c
> > @@ -39,46 +39,81 @@ SYSCALL_DEFINE2(set_robust_list, struct robust_list_head __user *, head,
> > return 0;
> > }
> >
> > -/**
> > - * sys_get_robust_list() - Get the robust-futex list head of a task
> > - * @pid: pid of the process [zero for current task]
> > - * @head_ptr: pointer to a list-head pointer, the kernel fills it in
> > - * @len_ptr: pointer to a length field, the kernel fills in the header size
> > - */
> > -SYSCALL_DEFINE3(get_robust_list, int, pid,
> > - struct robust_list_head __user * __user *, head_ptr,
> > - size_t __user *, len_ptr)
> > +static void __user *get_robust_list_common(int pid,
> > + bool compat)
>
> What is this random line break for?
>
> > {
> > - struct robust_list_head __user *head;
> > + void __user *head;
> > unsigned long ret;
> > - struct task_struct *p;
> >
>
> Stray new line and please use reverse fir tree ordering of variables:
>
> https://www.kernel.org/doc/html/latest/process/maintainer-tip.html#variable-declarations
>
> > - rcu_read_lock();
> > + struct task_struct *p;
> >
> > - ret = -ESRCH;
> > - if (!pid)
> > + if (!pid) {
> > p = current;
> > - else {
> > + get_task_struct(p);
> > + } else {
> > + rcu_read_lock();
> > p = find_task_by_vpid(pid);
> > + /*
> > + * pin the task to permit dropping the RCU read lock before
> > + * acquiring the semaphore
> > + */
> > + if (p)
> > + get_task_struct(p);
> > + rcu_read_unlock();
> > if (!p)
> > - goto err_unlock;
> > + return ERR_PTR(-ESRCH);
>
> scoped_guard(rcu) {
> p = find_task_by_vpid(pid);
> if (!p)
> return (void __user *)ERR_PTR(-ESRCH);
> get_task_struct(p);
> }
>
> No need for a comment about pinning the task. This is obvious and a
> common pattern all over the place. And note the type case on the error
> return.
>
> But you can simplify this whole thing even further:
>
> struct task_struct *p = current;
>
> scoped_guard(rcu) {
> if (pid) {
> p = find_task_by_vpid(pid);
> if (!p)
> return (void __user *)ERR_PTR(-ESRCH);
> }
> get_task_struct(p);
> }
>
> Yes, RCU is not required for the !pid case, but this is not a hot path.
>
> >
> > + /*
> > + * Hold exec_update_lock to serialize with concurrent exec()
> > + * so ptrace_may_access() is checked against stable credentials
> > + */
> > +
>
> Stray newline.
>
> > + ret = down_read_killable(&p->signal->exec_update_lock);
> > + if (ret)
> > + goto err_put;
> > +
> > ret = -EPERM;
> > if (!ptrace_may_access(p, PTRACE_MODE_READ_REALCREDS))
> > goto err_unlock;
> >
> > - head = p->robust_list;
> > - rcu_read_unlock();
> > + if (compat)
> > + head = p->compat_robust_list;
> > + else
> > + head = p->robust_list;
>
> Brain compiler complains about a build fail with CONFIG_COMPAT=n
>
> static inline void __user *task_robust_list(struct task_struct *p, bool compat)
> {
> #ifdef COMPAT
> if (compat)
> return p->compat_robust_list;
> #endif
> return p->robust_list;
> }
>
> So you don't have the #ifdef ugly in this function..
>
> > - if (put_user(sizeof(*head), len_ptr))
> > - return -EFAULT;
> > - return put_user(head, head_ptr);
> > + up_read(&p->signal->exec_update_lock);
> > + put_task_struct(p);
> > +
> > + return head;
> >
> > err_unlock:
> > - rcu_read_unlock();
> > + up_read(&p->signal->exec_update_lock);
> > +err_put:
> > + put_task_struct(p);
> > + return ERR_PTR(ret);
> > +}
> >
> > - return ret;
> > +
> > +/**
> > + * sys_get_robust_list() - Get the robust-futex list head of a task
> > + * @pid: pid of the process [zero for current task]
> > + * @head_ptr: pointer to a list-head pointer, the kernel fills it in
> > + * @len_ptr: pointer to a length field, the kernel fills in the header size
> > + */
> > +SYSCALL_DEFINE3(get_robust_list, int, pid,
> > + struct robust_list_head __user * __user *, head_ptr,
> > + size_t __user *, len_ptr)
> > +{
> > + struct robust_list_head __user *head =
> > + get_robust_list_common(pid, false);
>
> No line break required.
>
> > + if (IS_ERR(head))
> > + return PTR_ERR(head);
> > +
> > + if (put_user(sizeof(*head), len_ptr))
> > + return -EFAULT;
> > + return put_user(head, head_ptr);
> > }
> >
> > long do_futex(u32 __user *uaddr, int op, u32 val, ktime_t *timeout,
> > @@ -455,36 +490,15 @@ COMPAT_SYSCALL_DEFINE3(get_robust_list, int, pid,
> > compat_uptr_t __user *, head_ptr,
> > compat_size_t __user *, len_ptr)
> > {
> > - struct compat_robust_list_head __user *head;
> > - unsigned long ret;
> > - struct task_struct *p;
> > + struct compat_robust_list_head __user *head =
> > + get_robust_list_common(pid, true);
>
> Ditto
>
> Thanks,
>
> tglx
Hi,
Thanks for pointing out the shortcomings in my patch. I will
resend a v3 for the same shortly.
Regards
Pranav Tyagi
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH v2] futex: don't leak robust_list pointer on exec race
2025-08-04 11:55 [PATCH v2] futex: don't leak robust_list pointer on exec race Pranav Tyagi
` (2 preceding siblings ...)
2025-08-05 9:01 ` kernel test robot
@ 2025-08-05 18:27 ` kernel test robot
3 siblings, 0 replies; 6+ messages in thread
From: kernel test robot @ 2025-08-05 18:27 UTC (permalink / raw)
To: Pranav Tyagi, tglx, mingo, peterz, dvhart, dave, andrealmeid,
linux-kernel
Cc: oe-kbuild-all, jann, keescook, skhan, linux-kernel-mentees,
Pranav Tyagi
Hi Pranav,
kernel test robot noticed the following build warnings:
[auto build test WARNING on tip/locking/core]
[also build test WARNING on linus/master v6.16 next-20250805]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Pranav-Tyagi/futex-don-t-leak-robust_list-pointer-on-exec-race/20250804-195739
base: tip/locking/core
patch link: https://lore.kernel.org/r/20250804115533.14186-1-pranav.tyagi03%40gmail.com
patch subject: [PATCH v2] futex: don't leak robust_list pointer on exec race
config: x86_64-randconfig-r132-20250805 (https://download.01.org/0day-ci/archive/20250806/202508060225.urZa7B2V-lkp@intel.com/config)
compiler: clang version 20.1.8 (https://github.com/llvm/llvm-project 87f0227cb60147a26a1eeb4fb06e3b505e9c7261)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250806/202508060225.urZa7B2V-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202508060225.urZa7B2V-lkp@intel.com/
sparse warnings: (new ones prefixed by >>)
>> kernel/futex/syscalls.c:64:39: sparse: sparse: incorrect type in return expression (different address spaces) @@ expected void [noderef] __user * @@ got void * @@
kernel/futex/syscalls.c:64:39: sparse: expected void [noderef] __user *
kernel/futex/syscalls.c:64:39: sparse: got void *
kernel/futex/syscalls.c:94:23: sparse: sparse: incorrect type in return expression (different address spaces) @@ expected void [noderef] __user * @@ got void * @@
kernel/futex/syscalls.c:94:23: sparse: expected void [noderef] __user *
kernel/futex/syscalls.c:94:23: sparse: got void *
vim +64 kernel/futex/syscalls.c
41
42 static void __user *get_robust_list_common(int pid,
43 bool compat)
44 {
45 void __user *head;
46 unsigned long ret;
47
48 struct task_struct *p;
49
50 if (!pid) {
51 p = current;
52 get_task_struct(p);
53 } else {
54 rcu_read_lock();
55 p = find_task_by_vpid(pid);
56 /*
57 * pin the task to permit dropping the RCU read lock before
58 * acquiring the semaphore
59 */
60 if (p)
61 get_task_struct(p);
62 rcu_read_unlock();
63 if (!p)
> 64 return ERR_PTR(-ESRCH);
65 }
66
67 /*
68 * Hold exec_update_lock to serialize with concurrent exec()
69 * so ptrace_may_access() is checked against stable credentials
70 */
71
72 ret = down_read_killable(&p->signal->exec_update_lock);
73 if (ret)
74 goto err_put;
75
76 ret = -EPERM;
77 if (!ptrace_may_access(p, PTRACE_MODE_READ_REALCREDS))
78 goto err_unlock;
79
80 if (compat)
81 head = p->compat_robust_list;
82 else
83 head = p->robust_list;
84
85 up_read(&p->signal->exec_update_lock);
86 put_task_struct(p);
87
88 return head;
89
90 err_unlock:
91 up_read(&p->signal->exec_update_lock);
92 err_put:
93 put_task_struct(p);
94 return ERR_PTR(ret);
95 }
96
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2025-08-05 18:28 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-08-04 11:55 [PATCH v2] futex: don't leak robust_list pointer on exec race Pranav Tyagi
2025-08-04 15:12 ` Kees Cook
2025-08-05 8:19 ` Thomas Gleixner
2025-08-05 13:57 ` Pranav Tyagi
2025-08-05 9:01 ` kernel test robot
2025-08-05 18:27 ` kernel test robot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).