* bpf: Is the nmi_uaccess_okay() check needed for bpf_send_signal_task()?
@ 2026-08-19 12:43 Aditya Sharma
2026-08-21 18:21 ` Andrii Nakryiko
0 siblings, 1 reply; 4+ messages in thread
From: Aditya Sharma @ 2026-08-19 12:43 UTC (permalink / raw)
To: bpf, song; +Cc: ast, daniel, andrii, puranjay, yonghong.song, jolsa
Hi,
I hit this from a bpf_timer callback that signals a userspace task: the
signal was not delivered some of the time. It turned out that
bpf_send_signal_task() was returning -EPERM depending on which task the
softirq happened to interrupt, and the source is this check in
bpf_send_signal_common():
bpf_trace.c:872: if (unlikely(task->flags & (PF_KTHREAD | PF_EXITING)))
bpf_trace.c:873: return -EPERM;
bpf_trace.c:874: if (unlikely(!nmi_uaccess_okay()))
bpf_trace.c:875: return -EPERM;
Is the nmi_uaccess_okay() check necessary in the bpf_send_signal_task()
case?
When commit 6280cf7 ("bpf: Implement bpf_send_signal_task() kfunc")
made the function take a task, the PF_KTHREAD and is_global_init()
started testing task, while nmi_uaccess_okay() kept testing current.
As far as I can tell the path here does no user memory access, so
there is nothing for the check to guard.
Also, it compiles to true outside x86 (include/asm-generic/tlb.h:26),
so if it were necessary here the kfunc would be broken on every other
architecture.
Thus, whether a signal can be sent depends on the caller's context,
rather than the task it is to be sent to. And at the bpf_timer callback,
we never know deterministically whether the signal would go through,
or it would get rejected here.
A quick reproducibility test by Claude Opus 5 (the issue I was facing
from the timer callback context was not reproducible deterministically):
SEC("tp_btf/workqueue_execute_start") /* current is a kworker */
SEC("tp_btf/sys_enter") /* current is a user task */
static __always_inline int probe(void)
{
struct task_struct *t;
int ret;
t = bpf_task_from_pid(target_pid);
if (!t)
return -ESRCH;
/* sig == 0: permission check only, nothing is delivered */
ret = bpf_send_signal_task(t, 0, PIDTYPE_TGID, 0);
bpf_task_release(t);
return ret;
}
On bpf-next commit f79066c7 (selftests/bpf: Add tests for a store
on a fault prone qdisc pointer), x86_64 under qemu:
bpf_send_signal_task() from kworker context : -1 (-EPERM)
bpf_send_signal_task() from syscall context : 0
Same result on v6.18.29, x86_64, bare metal.
I may be missing the reason for it to be there as well. If this check
is intentional, what is it protecting in the _task case?
Thanks,
Aditya Sharma
^ permalink raw reply [flat|nested] 4+ messages in thread* Re: bpf: Is the nmi_uaccess_okay() check needed for bpf_send_signal_task()? 2026-08-19 12:43 bpf: Is the nmi_uaccess_okay() check needed for bpf_send_signal_task()? Aditya Sharma @ 2026-08-21 18:21 ` Andrii Nakryiko 2026-08-31 3:40 ` Yonghong Song 0 siblings, 1 reply; 4+ messages in thread From: Andrii Nakryiko @ 2026-08-21 18:21 UTC (permalink / raw) To: Aditya Sharma, yonghong.song Cc: bpf, song, ast, daniel, andrii, puranjay, jolsa On Wed, Aug 19, 2026 at 5:44 AM Aditya Sharma <adi.sharma@zohomail.in> wrote: > > Hi, > > I hit this from a bpf_timer callback that signals a userspace task: the > signal was not delivered some of the time. It turned out that > bpf_send_signal_task() was returning -EPERM depending on which task the > softirq happened to interrupt, and the source is this check in > bpf_send_signal_common(): > > bpf_trace.c:872: if (unlikely(task->flags & (PF_KTHREAD | PF_EXITING))) > bpf_trace.c:873: return -EPERM; > bpf_trace.c:874: if (unlikely(!nmi_uaccess_okay())) > bpf_trace.c:875: return -EPERM; > > Is the nmi_uaccess_okay() check necessary in the bpf_send_signal_task() > case? > > When commit 6280cf7 ("bpf: Implement bpf_send_signal_task() kfunc") > made the function take a task, the PF_KTHREAD and is_global_init() > started testing task, while nmi_uaccess_okay() kept testing current. Yeah, this is weird, at the very least it should be checking it for the provided task. But I also am not sure why we have to check this... Yonghong, do you remember why this was necessary? Can we just drop this? > > As far as I can tell the path here does no user memory access, so > there is nothing for the check to guard. > > Also, it compiles to true outside x86 (include/asm-generic/tlb.h:26), > so if it were necessary here the kfunc would be broken on every other > architecture. > > Thus, whether a signal can be sent depends on the caller's context, > rather than the task it is to be sent to. And at the bpf_timer callback, > we never know deterministically whether the signal would go through, > or it would get rejected here. > > A quick reproducibility test by Claude Opus 5 (the issue I was facing > from the timer callback context was not reproducible deterministically): > > SEC("tp_btf/workqueue_execute_start") /* current is a kworker */ > SEC("tp_btf/sys_enter") /* current is a user task */ > > static __always_inline int probe(void) > { > struct task_struct *t; > int ret; > > t = bpf_task_from_pid(target_pid); > if (!t) > return -ESRCH; > /* sig == 0: permission check only, nothing is delivered */ > ret = bpf_send_signal_task(t, 0, PIDTYPE_TGID, 0); > bpf_task_release(t); > return ret; > } > > On bpf-next commit f79066c7 (selftests/bpf: Add tests for a store > on a fault prone qdisc pointer), x86_64 under qemu: > > bpf_send_signal_task() from kworker context : -1 (-EPERM) > bpf_send_signal_task() from syscall context : 0 > > Same result on v6.18.29, x86_64, bare metal. > > I may be missing the reason for it to be there as well. If this check > is intentional, what is it protecting in the _task case? > > Thanks, > Aditya Sharma ^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: bpf: Is the nmi_uaccess_okay() check needed for bpf_send_signal_task()? 2026-08-21 18:21 ` Andrii Nakryiko @ 2026-08-31 3:40 ` Yonghong Song 2026-09-08 23:20 ` Andrii Nakryiko 0 siblings, 1 reply; 4+ messages in thread From: Yonghong Song @ 2026-08-31 3:40 UTC (permalink / raw) To: Andrii Nakryiko, Aditya Sharma Cc: bpf, song, ast, daniel, andrii, puranjay, jolsa On 8/21/26 11:21 AM, Andrii Nakryiko wrote: > On Wed, Aug 19, 2026 at 5:44 AM Aditya Sharma <adi.sharma@zohomail.in> wrote: >> Hi, >> >> I hit this from a bpf_timer callback that signals a userspace task: the >> signal was not delivered some of the time. It turned out that >> bpf_send_signal_task() was returning -EPERM depending on which task the >> softirq happened to interrupt, and the source is this check in >> bpf_send_signal_common(): >> >> bpf_trace.c:872: if (unlikely(task->flags & (PF_KTHREAD | PF_EXITING))) >> bpf_trace.c:873: return -EPERM; >> bpf_trace.c:874: if (unlikely(!nmi_uaccess_okay())) >> bpf_trace.c:875: return -EPERM; >> >> Is the nmi_uaccess_okay() check necessary in the bpf_send_signal_task() >> case? >> >> When commit 6280cf7 ("bpf: Implement bpf_send_signal_task() kfunc") >> made the function take a task, the PF_KTHREAD and is_global_init() >> started testing task, while nmi_uaccess_okay() kept testing current. > Yeah, this is weird, at the very least it should be checking it for > the provided task. But I also am not sure why we have to check this... > > Yonghong, do you remember why this was necessary? Can we just drop this? I went through the history. In 5.3: we have bpf_send_signal() helper: BPF_CALL_1(bpf_send_signal, u32, sig) { ... /* Similar to bpf_probe_write_user, task needs to be * in a sound condition and kernel memory access be * permitted in order to send signal to the current * task. */ if (unlikely(current->flags & (PF_KTHREAD | PF_EXITING))) return -EPERM; if (unlikely(uaccess_kernel())) return -EPERM; if (unlikely(!nmi_uaccess_okay())) return -EPERM; ... } nmi_uaccess_okay() is needed for *current* task. In 5.6, bpf_send_signal_thread() is added, but still for the same task, so we still have: static int bpf_send_signal_common(u32 sig, enum pid_type type) { ... /* Similar to bpf_probe_write_user, task needs to be * in a sound condition and kernel memory access be * permitted in order to send signal to the current * task. */ if (unlikely(current->flags & (PF_KTHREAD | PF_EXITING))) return -EPERM; if (unlikely(uaccess_kernel())) return -EPERM; if (unlikely(!nmi_uaccess_okay())) return -EPERM; ... } In the current master: __bpf_kfunc int bpf_send_signal_task(struct task_struct *task, int sig, enum pid_type type, u64 value) { if (type != PIDTYPE_PID && type != PIDTYPE_TGID) return -EINVAL; return bpf_send_signal_common(sig, type, task, value); } static int bpf_send_signal_common(u32 sig, enum pid_type type, struct task_struct *task, u64 value) { ... /* Similar to bpf_probe_write_user, task needs to be * in a sound condition and kernel memory access be * permitted in order to send signal to the current * task. */ if (unlikely(task->flags & (PF_KTHREAD | PF_EXITING))) return -EPERM; if (unlikely(!nmi_uaccess_okay())) return -EPERM; /* Task should not be pid=1 to avoid kernel panic. */ if (unlikely(is_global_init(task))) return -EPERM; ... } We probably cannot remove if (unlikely(!nmi_uaccess_okay())) return -EPERM; unconditionally. The above 'if' statement should be guarded with *current* task if it is different from parameter "task" since nmi_uaccess_okay() is only for the *current* task. > >> As far as I can tell the path here does no user memory access, so >> there is nothing for the check to guard. >> >> Also, it compiles to true outside x86 (include/asm-generic/tlb.h:26), >> so if it were necessary here the kfunc would be broken on every other >> architecture. >> >> Thus, whether a signal can be sent depends on the caller's context, >> rather than the task it is to be sent to. And at the bpf_timer callback, >> we never know deterministically whether the signal would go through, >> or it would get rejected here. >> >> A quick reproducibility test by Claude Opus 5 (the issue I was facing >> from the timer callback context was not reproducible deterministically): >> >> SEC("tp_btf/workqueue_execute_start") /* current is a kworker */ >> SEC("tp_btf/sys_enter") /* current is a user task */ >> >> static __always_inline int probe(void) >> { >> struct task_struct *t; >> int ret; >> >> t = bpf_task_from_pid(target_pid); >> if (!t) >> return -ESRCH; >> /* sig == 0: permission check only, nothing is delivered */ >> ret = bpf_send_signal_task(t, 0, PIDTYPE_TGID, 0); >> bpf_task_release(t); >> return ret; >> } >> >> On bpf-next commit f79066c7 (selftests/bpf: Add tests for a store >> on a fault prone qdisc pointer), x86_64 under qemu: >> >> bpf_send_signal_task() from kworker context : -1 (-EPERM) >> bpf_send_signal_task() from syscall context : 0 >> >> Same result on v6.18.29, x86_64, bare metal. >> >> I may be missing the reason for it to be there as well. If this check >> is intentional, what is it protecting in the _task case? >> >> Thanks, >> Aditya Sharma ^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: bpf: Is the nmi_uaccess_okay() check needed for bpf_send_signal_task()? 2026-08-31 3:40 ` Yonghong Song @ 2026-09-08 23:20 ` Andrii Nakryiko 0 siblings, 0 replies; 4+ messages in thread From: Andrii Nakryiko @ 2026-09-08 23:20 UTC (permalink / raw) To: Yonghong Song Cc: Aditya Sharma, bpf, song, ast, daniel, andrii, puranjay, jolsa On Sun, Aug 30, 2026 at 8:40 PM Yonghong Song <yonghong.song@linux.dev> wrote: > > > > On 8/21/26 11:21 AM, Andrii Nakryiko wrote: > > On Wed, Aug 19, 2026 at 5:44 AM Aditya Sharma <adi.sharma@zohomail.in> wrote: > >> Hi, > >> > >> I hit this from a bpf_timer callback that signals a userspace task: the > >> signal was not delivered some of the time. It turned out that > >> bpf_send_signal_task() was returning -EPERM depending on which task the > >> softirq happened to interrupt, and the source is this check in > >> bpf_send_signal_common(): > >> > >> bpf_trace.c:872: if (unlikely(task->flags & (PF_KTHREAD | PF_EXITING))) > >> bpf_trace.c:873: return -EPERM; > >> bpf_trace.c:874: if (unlikely(!nmi_uaccess_okay())) > >> bpf_trace.c:875: return -EPERM; > >> > >> Is the nmi_uaccess_okay() check necessary in the bpf_send_signal_task() > >> case? > >> > >> When commit 6280cf7 ("bpf: Implement bpf_send_signal_task() kfunc") > >> made the function take a task, the PF_KTHREAD and is_global_init() > >> started testing task, while nmi_uaccess_okay() kept testing current. > > Yeah, this is weird, at the very least it should be checking it for > > the provided task. But I also am not sure why we have to check this... > > > > Yonghong, do you remember why this was necessary? Can we just drop this? > > I went through the history. > In 5.3: we have bpf_send_signal() helper: > > BPF_CALL_1(bpf_send_signal, u32, sig) > { > ... > /* Similar to bpf_probe_write_user, task needs to be > * in a sound condition and kernel memory access be > * permitted in order to send signal to the current > * task. > */ > if (unlikely(current->flags & (PF_KTHREAD | PF_EXITING))) > return -EPERM; > if (unlikely(uaccess_kernel())) > return -EPERM; > if (unlikely(!nmi_uaccess_okay())) > return -EPERM; > ... > } > > nmi_uaccess_okay() is needed for *current* task. but why did we have that nmi_uaccess_okay() check in the first place?... that seems to be something useful for bpf_probe_read_user(), but for sending signals, assuming we have CAP_PERFMON/CAP_BPF, we should just allow it. > > In 5.6, bpf_send_signal_thread() is added, but still for the same task, > so we still have: > > static int bpf_send_signal_common(u32 sig, enum pid_type type) > { > ... > /* Similar to bpf_probe_write_user, task needs to be > * in a sound condition and kernel memory access be > * permitted in order to send signal to the current > * task. > */ > if (unlikely(current->flags & (PF_KTHREAD | PF_EXITING))) > return -EPERM; > if (unlikely(uaccess_kernel())) > return -EPERM; > if (unlikely(!nmi_uaccess_okay())) > return -EPERM; > ... > } > > In the current master: > > __bpf_kfunc int bpf_send_signal_task(struct task_struct *task, int sig, enum pid_type type, > u64 value) > { > if (type != PIDTYPE_PID && type != PIDTYPE_TGID) > return -EINVAL; > > return bpf_send_signal_common(sig, type, task, value); > } > > static int bpf_send_signal_common(u32 sig, enum pid_type type, struct task_struct *task, u64 value) > { > ... > /* Similar to bpf_probe_write_user, task needs to be > * in a sound condition and kernel memory access be > * permitted in order to send signal to the current > * task. > */ > if (unlikely(task->flags & (PF_KTHREAD | PF_EXITING))) > return -EPERM; > if (unlikely(!nmi_uaccess_okay())) > return -EPERM; > /* Task should not be pid=1 to avoid kernel panic. */ > if (unlikely(is_global_init(task))) > return -EPERM; > ... > } > > We probably cannot remove > if (unlikely(!nmi_uaccess_okay())) > return -EPERM; > unconditionally. The above 'if' statement should be guarded > with *current* task if it is different from parameter "task" > since nmi_uaccess_okay() is only for the *current* task. > > > > >> As far as I can tell the path here does no user memory access, so > >> there is nothing for the check to guard. > >> > >> Also, it compiles to true outside x86 (include/asm-generic/tlb.h:26), > >> so if it were necessary here the kfunc would be broken on every other > >> architecture. > >> > >> Thus, whether a signal can be sent depends on the caller's context, > >> rather than the task it is to be sent to. And at the bpf_timer callback, > >> we never know deterministically whether the signal would go through, > >> or it would get rejected here. > >> > >> A quick reproducibility test by Claude Opus 5 (the issue I was facing > >> from the timer callback context was not reproducible deterministically): > >> > >> SEC("tp_btf/workqueue_execute_start") /* current is a kworker */ > >> SEC("tp_btf/sys_enter") /* current is a user task */ > >> > >> static __always_inline int probe(void) > >> { > >> struct task_struct *t; > >> int ret; > >> > >> t = bpf_task_from_pid(target_pid); > >> if (!t) > >> return -ESRCH; > >> /* sig == 0: permission check only, nothing is delivered */ > >> ret = bpf_send_signal_task(t, 0, PIDTYPE_TGID, 0); > >> bpf_task_release(t); > >> return ret; > >> } > >> > >> On bpf-next commit f79066c7 (selftests/bpf: Add tests for a store > >> on a fault prone qdisc pointer), x86_64 under qemu: > >> > >> bpf_send_signal_task() from kworker context : -1 (-EPERM) > >> bpf_send_signal_task() from syscall context : 0 > >> > >> Same result on v6.18.29, x86_64, bare metal. > >> > >> I may be missing the reason for it to be there as well. If this check > >> is intentional, what is it protecting in the _task case? > >> > >> Thanks, > >> Aditya Sharma > ^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-09-08 23:20 UTC | newest] Thread overview: 4+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-08-19 12:43 bpf: Is the nmi_uaccess_okay() check needed for bpf_send_signal_task()? Aditya Sharma 2026-08-21 18:21 ` Andrii Nakryiko 2026-08-31 3:40 ` Yonghong Song 2026-09-08 23:20 ` Andrii Nakryiko
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.