From: Kees Cook <kees@kernel.org>
To: Pranav Tyagi <pranav.tyagi03@gmail.com>
Cc: tglx@linutronix.de, mingo@redhat.com, peterz@infradead.org,
dvhart@infradead.org, dave@stgolabs.net, andrealmeid@igalia.com,
linux-kernel@vger.kernel.org, jann@thejh.net,
skhan@linuxfoundation.org, linux-kernel-mentees@lists.linux.dev
Subject: Re: [PATCH v2] futex: don't leak robust_list pointer on exec race
Date: Mon, 4 Aug 2025 08:12:31 -0700 [thread overview]
Message-ID: <202508040811.3DB8AA242@keescook> (raw)
In-Reply-To: <20250804115533.14186-1-pranav.tyagi03@gmail.com>
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
next prev parent reply other threads:[~2025-08-04 15:12 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
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 [this message]
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
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=202508040811.3DB8AA242@keescook \
--to=kees@kernel.org \
--cc=andrealmeid@igalia.com \
--cc=dave@stgolabs.net \
--cc=dvhart@infradead.org \
--cc=jann@thejh.net \
--cc=linux-kernel-mentees@lists.linux.dev \
--cc=linux-kernel@vger.kernel.org \
--cc=mingo@redhat.com \
--cc=peterz@infradead.org \
--cc=pranav.tyagi03@gmail.com \
--cc=skhan@linuxfoundation.org \
--cc=tglx@linutronix.de \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.