BPF List
 help / color / mirror / Atom feed
* 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; 3+ 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] 3+ 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; 3+ 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] 3+ 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
  0 siblings, 0 replies; 3+ 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] 3+ messages in thread

end of thread, other threads:[~2026-08-31  3:40 UTC | newest]

Thread overview: 3+ 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

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox