Skip to content

Commit 4a7afbf

Browse files
Pranav Tyagigregkh
authored andcommitted
futex: Don't leak robust_list pointer on exec race
[ Upstream commit 6b54082 ] 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. Suggested-by: Jann Horn <[email protected]> Signed-off-by: Pranav Tyagi <[email protected]> Signed-off-by: Thomas Gleixner <[email protected]> Link: https://lore.kernel.org/linux-fsdevel/[email protected]/ Link: KSPP#119 Signed-off-by: Sasha Levin <[email protected]>
1 parent 5bd376c commit 4a7afbf

File tree

1 file changed

+56
-50
lines changed

1 file changed

+56
-50
lines changed

kernel/futex/syscalls.c

Lines changed: 56 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,56 @@ SYSCALL_DEFINE2(set_robust_list, struct robust_list_head __user *, head,
3939
return 0;
4040
}
4141

42+
static inline void __user *futex_task_robust_list(struct task_struct *p, bool compat)
43+
{
44+
#ifdef CONFIG_COMPAT
45+
if (compat)
46+
return p->compat_robust_list;
47+
#endif
48+
return p->robust_list;
49+
}
50+
51+
static void __user *futex_get_robust_list_common(int pid, bool compat)
52+
{
53+
struct task_struct *p = current;
54+
void __user *head;
55+
int ret;
56+
57+
scoped_guard(rcu) {
58+
if (pid) {
59+
p = find_task_by_vpid(pid);
60+
if (!p)
61+
return (void __user *)ERR_PTR(-ESRCH);
62+
}
63+
get_task_struct(p);
64+
}
65+
66+
/*
67+
* Hold exec_update_lock to serialize with concurrent exec()
68+
* so ptrace_may_access() is checked against stable credentials
69+
*/
70+
ret = down_read_killable(&p->signal->exec_update_lock);
71+
if (ret)
72+
goto err_put;
73+
74+
ret = -EPERM;
75+
if (!ptrace_may_access(p, PTRACE_MODE_READ_REALCREDS))
76+
goto err_unlock;
77+
78+
head = futex_task_robust_list(p, compat);
79+
80+
up_read(&p->signal->exec_update_lock);
81+
put_task_struct(p);
82+
83+
return head;
84+
85+
err_unlock:
86+
up_read(&p->signal->exec_update_lock);
87+
err_put:
88+
put_task_struct(p);
89+
return (void __user *)ERR_PTR(ret);
90+
}
91+
4292
/**
4393
* sys_get_robust_list() - Get the robust-futex list head of a task
4494
* @pid: pid of the process [zero for current task]
@@ -49,36 +99,14 @@ SYSCALL_DEFINE3(get_robust_list, int, pid,
4999
struct robust_list_head __user * __user *, head_ptr,
50100
size_t __user *, len_ptr)
51101
{
52-
struct robust_list_head __user *head;
53-
unsigned long ret;
54-
struct task_struct *p;
55-
56-
rcu_read_lock();
57-
58-
ret = -ESRCH;
59-
if (!pid)
60-
p = current;
61-
else {
62-
p = find_task_by_vpid(pid);
63-
if (!p)
64-
goto err_unlock;
65-
}
66-
67-
ret = -EPERM;
68-
if (!ptrace_may_access(p, PTRACE_MODE_READ_REALCREDS))
69-
goto err_unlock;
102+
struct robust_list_head __user *head = futex_get_robust_list_common(pid, false);
70103

71-
head = p->robust_list;
72-
rcu_read_unlock();
104+
if (IS_ERR(head))
105+
return PTR_ERR(head);
73106

74107
if (put_user(sizeof(*head), len_ptr))
75108
return -EFAULT;
76109
return put_user(head, head_ptr);
77-
78-
err_unlock:
79-
rcu_read_unlock();
80-
81-
return ret;
82110
}
83111

84112
long do_futex(u32 __user *uaddr, int op, u32 val, ktime_t *timeout,
@@ -455,36 +483,14 @@ COMPAT_SYSCALL_DEFINE3(get_robust_list, int, pid,
455483
compat_uptr_t __user *, head_ptr,
456484
compat_size_t __user *, len_ptr)
457485
{
458-
struct compat_robust_list_head __user *head;
459-
unsigned long ret;
460-
struct task_struct *p;
461-
462-
rcu_read_lock();
463-
464-
ret = -ESRCH;
465-
if (!pid)
466-
p = current;
467-
else {
468-
p = find_task_by_vpid(pid);
469-
if (!p)
470-
goto err_unlock;
471-
}
472-
473-
ret = -EPERM;
474-
if (!ptrace_may_access(p, PTRACE_MODE_READ_REALCREDS))
475-
goto err_unlock;
486+
struct compat_robust_list_head __user *head = futex_get_robust_list_common(pid, true);
476487

477-
head = p->compat_robust_list;
478-
rcu_read_unlock();
488+
if (IS_ERR(head))
489+
return PTR_ERR(head);
479490

480491
if (put_user(sizeof(*head), len_ptr))
481492
return -EFAULT;
482493
return put_user(ptr_to_compat(head), head_ptr);
483-
484-
err_unlock:
485-
rcu_read_unlock();
486-
487-
return ret;
488494
}
489495
#endif /* CONFIG_COMPAT */
490496

0 commit comments

Comments
 (0)