[RFC PATCH v2 29/41] signal: report the sender's UUID or GUID uid in si_uid_r

sashiko-bot at kernel.org sashiko-bot at kernel.org
Thu Oct 22 03:17:55 UTC 2026


Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] signal: broken userns UID mapping in send_signal_locked() and callers
- [Medium] bpf: missing siginfo_set_kernel_uid() call in bpf_send_signal_common()
--

commit 3fc8400fa91445ed3a591553195996d33af32cb3
Author: Jan Koester <jan.koester at tuxist.de>

signal: report the sender's UUID or GUID uid in si_uid_r

This commit adds si_uid_r (__kernel_uid_r_t) at offset 96 in siginfo_t and
compat_siginfo_t, along with ssi_uid_r in signalfd_siginfo, to report the
sender's UUID or GUID UID for signals and waitid(). It introduces
siginfo_set_uid() and siginfo_set_kernel_uid() to populate and translate
si_uid and si_uid_r across signal generation, delivery, and wait paths.

> diff --git a/include/linux/signal.h b/include/linux/signal.h
> index f19816832f055..928e67e283b60 100644
> --- a/include/linux/signal.h
> +++ b/include/linux/signal.h
> @@ -6,6 +6,7 @@
>  #include <linux/list.h>
>  #include <linux/signal_types.h>
>  #include <linux/string.h>
> +#include <linux/uidgid.h>
>  
>  struct task_struct;
>  
> @@ -23,6 +24,27 @@ static inline void clear_siginfo(kernel_siginfo_t *info)
>  	memset(info, 0, sizeof(*info));
>  }
>  
> +struct user_namespace;
> +
> +/*
> + * Sets si_uid and si_uid_r to @uid as seen from @ns (the receiver's user
> + * namespace), the overflow id if it has no mapping there.
> + */
> +static inline void siginfo_set_uid(kernel_siginfo_t *info,
> +				   struct user_namespace *ns, kuid_t uid)
> +{
> +	info->si_uid = from_kuid_munged(ns, uid);
> +	from_kuid_r_munged(ns, uid, &info->si_uid_r);
> +}
> +
> +/* si_uid and si_uid_r of a signal from the kernel: 0 */
> +static inline void siginfo_set_kernel_uid(kernel_siginfo_t *info)
> +{
> +	info->si_uid = 0;
> +	memset(&info->si_uid_r, 0, sizeof(info->si_uid_r));
> +	info->si_uid_r.type = UID_R_LEGACY;
> +}

[Severity: Medium]
Should bpf_send_signal_common() in kernel/trace/bpf_trace.c also call
siginfo_set_kernel_uid(&info) when sending a signal to a specific task?

When a BPF program invokes bpf_send_signal_task() with a non-NULL task,
bpf_send_signal_common() takes the else branch and initializes info manually:

kernel/trace/bpf_trace.c:bpf_send_signal_common() {
	if (!task) {
		task = current;
		siginfo = SEND_SIG_PRIV;
	} else {
		clear_siginfo(&info);
		info.si_signo = sig;
		info.si_errno = 0;
		info.si_code = SI_KERNEL;
		info.si_pid = 0;
		info.si_uid = 0;
		info.si_value.sival_ptr = (void __user __force *)(unsigned long)value;
		siginfo = &info;
	}
}

Because info.si_uid = 0 is set directly after clear_siginfo(&info),
info.si_uid_r remains UID_R_NONE instead of UID_R_LEGACY with uid 0, whereas
calling bpf_send_signal() or bpf_send_signal_thread() (where task is NULL)
uses SEND_SIG_PRIV and sets info.si_uid_r via siginfo_set_kernel_uid().

[ ... ]

> diff --git a/ipc/mqueue.c b/ipc/mqueue.c
> index d1dd36a651b0d..f0a319ed97515 100644
> --- a/ipc/mqueue.c
> +++ b/ipc/mqueue.c
> @@ -802,8 +802,8 @@ static void __do_notify(struct mqueue_inode_info *info)
>  			/* map current pid/uid into info->owner's namespaces */
>  			sig_i.si_pid = task_tgid_nr_ns(current,
>  						ns_of_pid(info->notify_owner));
> -			sig_i.si_uid = from_kuid_munged(info->notify_user_ns,
> -						current_uid());
> +			siginfo_set_uid(&sig_i, info->notify_user_ns,
> +					current_uid());
>  			/*
>  			 * We can't use kill_pid_info(), this signal should
>  			 * bypass check_kill_permission(). It is from kernel

[ ... ]

> diff --git a/kernel/signal.c b/kernel/signal.c
> index d31ebcb6ed4db..af0793d04b640 100644
> --- a/kernel/signal.c
> +++ b/kernel/signal.c

[ ... ]

> @@ -1229,6 +1228,13 @@ int send_signal_locked(int sig, struct kernel_siginfo *info,
>  			info = &rewritten;
>  			uid = make_kuid(current_user_ns(), info->si_uid);
>  			rewritten.si_uid = from_kuid_munged(t_user_ns, uid);
> +			if (rewritten.si_uid_r.type != UID_R_NONE) {
> +				kuid_t uid_r = make_kuid_r(current_user_ns(),
> +							   &rewritten.si_uid_r);
> +
> +				from_kuid_r_munged(t_user_ns, uid_r,
> +						   &rewritten.si_uid_r);
> +			}
>  		}
>  		rcu_read_unlock();
>  #endif

[Severity: High]
Can this cross-user-namespace translation in send_signal_locked() corrupt
both si_uid and si_uid_r in several signal delivery paths?

First, in send_signal_locked(), rewritten.si_uid is still derived from the
32-bit info->si_uid via make_kuid(current_user_ns(), info->si_uid) rather
than from uid_r. When a process with a UUID or GUID UID in init_user_ns
signals a task in a child user namespace t_user_ns (via
prepare_kill_siginfo() in tkill(), tgkill(), or pidfd_send_signal() with
info == NULL, or via rt_sigqueueinfo()), info->si_uid has already been munged
to overflowuid (65534). make_kuid(&init_user_ns, 65534) then fabricates the
legacy host UID KUIDT_INIT(65534). If t_user_ns maps legacy host UID 65534 to
a valid container UID, rewritten.si_uid is set to that container UID instead
of overflowuid, while rewritten.si_uid_r is munged to overflowuid.

Second, prepare_kill_siginfo() in kernel/signal.c pre-maps current_uid() into
current_user_ns() before send_signal_locked() knows the target's user
namespace:

kernel/signal.c:prepare_kill_siginfo() {
	clear_siginfo(info);
	info->si_signo = sig;
	info->si_errno = 0;
	info->si_code = (type == PIDTYPE_PID) ? SI_TKILL : SI_USER;
	info->si_pid = task_tgid_vnr(current);
	siginfo_set_uid(info, current_user_ns(), current_uid());
}

If a task with a UUID or GUID UID enters a child user namespace via
unshare(CLONE_NEWUSER) or setns() and calls tkill(), tgkill(), or
pidfd_send_signal() (with info == NULL) targeting a task in init_user_ns,
siginfo_set_uid() in prepare_kill_siginfo() immediately munges info->si_uid_r
to UID_R_LEGACY / overflowuid because current_user_ns() != &init_user_ns.
When send_signal_locked() subsequently translates from current_user_ns() to
t_user_ns (&init_user_ns), the original UUID or GUID has already been lost.

Third, both do_notify_parent_cldstop() in kernel/signal.c and __do_notify()
in ipc/mqueue.c already translate the sender's kuid_t into the receiver's
user namespace via siginfo_set_uid(), but then pass the resulting siginfo to
send_signal_locked():

kernel/signal.c:do_notify_parent_cldstop()
  -> siginfo_set_uid(&info, task_cred_xxx(parent, user_ns), task_uid(tsk))
  -> send_signal_locked(SIGCHLD, &info, parent, PIDTYPE_TGID)

ipc/mqueue.c:__do_notify()
  -> siginfo_set_uid(&sig_i, info->notify_user_ns, current_uid())
  -> do_send_sig_info(info->notify.sigev_signo, &sig_i, task, PIDTYPE_TGID)
    -> send_signal_locked(sig, info, p, type)

When current_user_ns() != t_user_ns, send_signal_locked() translates si_uid
and si_uid_r a second time assuming they were formatted in current_user_ns().
For example, if a child or mqueue sender with a UUID or GUID UID is in a
child user namespace and the parent or notify_owner is in init_user_ns,
siginfo_set_uid() writes the UUID or GUID into si_uid_r, and
send_signal_locked() then calls make_kuid_r(current_user_ns(), ...), which
fails in kid_r_to_128() and returns INVALID_UID, overwriting the valid UUID
or GUID with overflowuid.

[ ... ]

完美! Let's double check everything before finalizing. Everything adheres strictly to the guidelines.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1792552161.git.jan.koester@tuxist.de?part=29



More information about the Linux-security-module-archive mailing list