* [PATCH v3] futex: don't leak robust_list pointer on exec race
@ 2025-08-05 15:47 Pranav Tyagi
2025-08-05 21:47 ` Thomas Gleixner
2025-08-08 18:14 ` kernel test robot
0 siblings, 2 replies; 4+ messages in thread
From: Pranav Tyagi @ 2025-08-05 15:47 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.
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
---
changed in v3:
- replaced RCU with scoped_guard(rcu)
- corrected error return type cast
- added IS_ENABLED(CONFIG_COMPAT) for ensuring compatability
- removed stray newlines and unnecessary line breaks
changed in v2:
- improved changelog
- helper function for common part of compat and native syscalls
kernel/futex/syscalls.c | 102 ++++++++++++++++++++--------------------
1 file changed, 52 insertions(+), 50 deletions(-)
diff --git a/kernel/futex/syscalls.c b/kernel/futex/syscalls.c
index 4b6da9116aa6..c342c16d6d00 100644
--- a/kernel/futex/syscalls.c
+++ b/kernel/futex/syscalls.c
@@ -39,6 +39,52 @@ SYSCALL_DEFINE2(set_robust_list, struct robust_list_head __user *, head,
return 0;
}
+static void __user *get_robust_list_common(int pid, bool compat)
+{
+ struct task_struct *p;
+ void __user *head;
+ unsigned long ret;
+
+ 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);
+ }
+
+ /*
+ * 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;
+
+ if (IS_ENABLED(CONFIG_COMPAT) && compat)
+ head = p->compat_robust_list;
+ else
+ head = p->robust_list;
+
+ up_read(&p->signal->exec_update_lock);
+ put_task_struct(p);
+
+ return head;
+
+err_unlock:
+ up_read(&p->signal->exec_update_lock);
+err_put:
+ put_task_struct(p);
+ return (void __user *)ERR_PTR(ret);
+}
+
/**
* sys_get_robust_list() - Get the robust-futex list head of a task
* @pid: pid of the process [zero for current task]
@@ -49,36 +95,14 @@ 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;
- unsigned long ret;
- struct task_struct *p;
+ struct robust_list_head __user *head = get_robust_list_common(pid, false);
- 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->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(head, head_ptr);
-
-err_unlock:
- rcu_read_unlock();
-
- return ret;
}
long do_futex(u32 __user *uaddr, int op, u32 val, ktime_t *timeout,
@@ -455,36 +479,14 @@ 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] 4+ messages in thread
* Re: [PATCH v3] futex: don't leak robust_list pointer on exec race
2025-08-05 15:47 [PATCH v3] futex: don't leak robust_list pointer on exec race Pranav Tyagi
@ 2025-08-05 21:47 ` Thomas Gleixner
2025-08-13 7:52 ` Pranav Tyagi
2025-08-08 18:14 ` kernel test robot
1 sibling, 1 reply; 4+ messages in thread
From: Thomas Gleixner @ 2025-08-05 21:47 UTC (permalink / raw)
To: Pranav Tyagi, mingo, peterz, dvhart, dave, andrealmeid,
linux-kernel
Cc: jann, keescook, skhan, linux-kernel-mentees, Pranav Tyagi
On Tue, Aug 05 2025 at 21:17, Pranav Tyagi wrote:
> +
> + if (IS_ENABLED(CONFIG_COMPAT) && compat)
> + head = p->compat_robust_list;
This still does not compile because the dead code elimination comes
_after_ the compiler decodes this line. I don't even need to fire up a
compiler to predict the error emitted when CONFIG_COMPAT=n:
error: ‘struct task_struct’ has no member named ‘compat_robust_list’
No?
There is a reason why I suggested you to use that helper function.
You are obviously free to ignore me, but then please make sure that the
stuff you submit compiles _AND_ works. Otherwise if you are not sure,
why I told you, ask.
Please take your time and stop rushing out half baken crap, which wastes
everybodys time. I don't care about your time wasted, but I pretty much
care about mine.
To be clear: I don't want to see this in my inbox again before next week
and then it better be correct.
Thanks,
tglx
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH v3] futex: don't leak robust_list pointer on exec race
2025-08-05 15:47 [PATCH v3] futex: don't leak robust_list pointer on exec race Pranav Tyagi
2025-08-05 21:47 ` Thomas Gleixner
@ 2025-08-08 18:14 ` kernel test robot
1 sibling, 0 replies; 4+ messages in thread
From: kernel test robot @ 2025-08-08 18:14 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 errors:
[auto build test ERROR on tip/locking/core]
[also build test ERROR on linus/master v6.16 next-20250808]
[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/20250806-121303
base: tip/locking/core
patch link: https://lore.kernel.org/r/20250805154725.22031-1-pranav.tyagi03%40gmail.com
patch subject: [PATCH v3] futex: don't leak robust_list pointer on exec race
config: loongarch-allyesconfig (https://download.01.org/0day-ci/archive/20250809/202508090125.VIm8fAXD-lkp@intel.com/config)
compiler: clang version 22.0.0git (https://github.com/llvm/llvm-project 7b8dea265e72c3037b6b1e54d5ab51b7e14f328b)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250809/202508090125.VIm8fAXD-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/202508090125.VIm8fAXD-lkp@intel.com/
All errors (new ones prefixed by >>):
>> kernel/futex/syscalls.c:72:13: error: no member named 'compat_robust_list' in 'struct task_struct'
72 | head = p->compat_robust_list;
| ~ ^
1 error generated.
vim +72 kernel/futex/syscalls.c
41
42 static void __user *get_robust_list_common(int pid, bool compat)
43 {
44 struct task_struct *p;
45 void __user *head;
46 unsigned long ret;
47
48 p = current;
49
50 scoped_guard(rcu) {
51 if (pid) {
52 p = find_task_by_vpid(pid);
53 if (!p)
54 return (void __user *)ERR_PTR(-ESRCH);
55 }
56 get_task_struct(p);
57 }
58
59 /*
60 * Hold exec_update_lock to serialize with concurrent exec()
61 * so ptrace_may_access() is checked against stable credentials
62 */
63 ret = down_read_killable(&p->signal->exec_update_lock);
64 if (ret)
65 goto err_put;
66
67 ret = -EPERM;
68 if (!ptrace_may_access(p, PTRACE_MODE_READ_REALCREDS))
69 goto err_unlock;
70
71 if (IS_ENABLED(CONFIG_COMPAT) && compat)
> 72 head = p->compat_robust_list;
73 else
74 head = p->robust_list;
75
76 up_read(&p->signal->exec_update_lock);
77 put_task_struct(p);
78
79 return head;
80
81 err_unlock:
82 up_read(&p->signal->exec_update_lock);
83 err_put:
84 put_task_struct(p);
85 return (void __user *)ERR_PTR(ret);
86 }
87
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH v3] futex: don't leak robust_list pointer on exec race
2025-08-05 21:47 ` Thomas Gleixner
@ 2025-08-13 7:52 ` Pranav Tyagi
0 siblings, 0 replies; 4+ messages in thread
From: Pranav Tyagi @ 2025-08-13 7:52 UTC (permalink / raw)
To: Thomas Gleixner
Cc: mingo, peterz, dvhart, dave, andrealmeid, linux-kernel, jann,
keescook, skhan, linux-kernel-mentees
On Wed, Aug 6, 2025 at 3:17 AM Thomas Gleixner <tglx@linutronix.de> wrote:
>
> On Tue, Aug 05 2025 at 21:17, Pranav Tyagi wrote:
> > +
> > + if (IS_ENABLED(CONFIG_COMPAT) && compat)
> > + head = p->compat_robust_list;
>
> This still does not compile because the dead code elimination comes
> _after_ the compiler decodes this line. I don't even need to fire up a
> compiler to predict the error emitted when CONFIG_COMPAT=n:
>
> error: ‘struct task_struct’ has no member named ‘compat_robust_list’
>
> No?
>
> There is a reason why I suggested you to use that helper function.
>
> You are obviously free to ignore me, but then please make sure that the
> stuff you submit compiles _AND_ works. Otherwise if you are not sure,
> why I told you, ask.
>
> Please take your time and stop rushing out half baken crap, which wastes
> everybodys time. I don't care about your time wasted, but I pretty much
> care about mine.
>
> To be clear: I don't want to see this in my inbox again before next week
> and then it better be correct.
>
> Thanks,
>
> tglx
>
>
>
Hello Sir,
There is no question of ignoring you. It is my privilege to be
communicating with you.
Your guidance in the course of this specific patch has only enhanced
my understanding.
I always try to respect everyone's time and agree that I hurried the last one as
time of my mentorship program is closing fast. My apologies.
I have again sent the reworked patch (v4) duly corrected based on your
observations.
I have compiled and checked it with CONFIG_COMPAT=n. And to the extent
my little wisdom
allowed, I have tested it using the following custom testing code on a
virtual machine:
#define _GNU_SOURCE
#include <unistd.h>
#include <sys/syscall.h>
#include <linux/futex.h>
#include <linux/unistd.h>
#include <stdio.h>
int main() {
pid_t pid = 0; // 0 = self
struct robust_list_head *head;
size_t len;
long ret;
ret = syscall(SYS_get_robust_list, pid, &head, &len);
if (ret == -1) {
perror("get_robust_list");
return 1;
}
printf("Robust list head: %p, length: %zu\n", head, len);
return 0;
}
Regards
Pranav Tyagi
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2025-08-13 7:53 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-08-05 15:47 [PATCH v3] futex: don't leak robust_list pointer on exec race Pranav Tyagi
2025-08-05 21:47 ` Thomas Gleixner
2025-08-13 7:52 ` Pranav Tyagi
2025-08-08 18:14 ` 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).