This is an automated email from the ASF dual-hosted git repository. acassis pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/nuttx.git
commit 003048e7f853b5c95c3aa461adbc32bb1c62f9bc Author: Marco Casaroli <[email protected]> AuthorDate: Wed Nov 11 09:42:59 2026 +0100 sched/signal: Fail sigqueue() with EAGAIN at SIGQUEUE_MAX. sigqueue() never failed with EAGAIN. Each queued signal takes an entry from the kernel heap, so a sender could queue signals without a limit to a task that blocks them, or that does not run. POSIX requires EAGAIN when {SIGQUEUE_MAX} signals that the process queued are still pending at the receiver. sigqueue() and pthread_sigqueue() now fail with EAGAIN when SIGQUEUE_MAX (32) signals are pending in the task group of the receiver, or queued there for a signal handler. nxsig_tcbdispatch() counts them in the critical section that queues the signal, so senders on other CPUs cannot pass the limit. The count reads the lists, so no counter must follow the paths that deliver or discard a signal. A new argument of nxsig_dispatch(), group_signal() and nxsig_tcbdispatch() applies the limit; only sigqueue() and pthread_sigqueue() set it. kill(), raise(), nxsig_queue() (aio, drivers), the timers and the message queue notifications are not limited, but their signals count. Impact: sigqueue() and pthread_sigqueue() of more than 32 signals to a receiver that does not take them now fail with EAGAIN, as POSIX requires. The other senders do not change. Assisted-by: Claude Code:claude-opus-5-5 Signed-off-by: Marco Casaroli <[email protected]> --- arch/xtensa/src/esp32s3/esp32s3_isolation.c | 2 +- arch/xtensa/src/esp32s3/esp32s3_userfault.c | 2 +- sched/group/group.h | 2 +- sched/group/group_signal.c | 14 +++-- sched/pthread/pthread_sigqueue.c | 2 +- sched/signal/sig_dispatch.c | 68 ++++++++++++++++++++-- sched/signal/sig_kill.c | 2 +- sched/signal/sig_notification.c | 2 +- sched/signal/sig_queue.c | 88 +++++++++++++++++------------ sched/signal/sig_tgkill.c | 2 +- sched/signal/sig_unmaskpendingsignal.c | 2 +- sched/signal/signal.h | 5 +- sched/task/task_exithook.c | 4 +- 13 files changed, 139 insertions(+), 56 deletions(-) diff --git a/arch/xtensa/src/esp32s3/esp32s3_isolation.c b/arch/xtensa/src/esp32s3/esp32s3_isolation.c index a655e7c82d4..f05e19455b3 100644 --- a/arch/xtensa/src/esp32s3/esp32s3_isolation.c +++ b/arch/xtensa/src/esp32s3/esp32s3_isolation.c @@ -220,7 +220,7 @@ static int IRAM_ATTR pms_violation_isr(int cpuint, void *context, void *arg) info.si_errno = 0; info.si_value.sival_ptr = NULL; - nxsig_tcbdispatch(tcb, &info, false, NULL); + nxsig_tcbdispatch(tcb, &info, false, NULL, false); return OK; } #endif diff --git a/arch/xtensa/src/esp32s3/esp32s3_userfault.c b/arch/xtensa/src/esp32s3/esp32s3_userfault.c index c065a829369..ad4f9ed5fcc 100644 --- a/arch/xtensa/src/esp32s3/esp32s3_userfault.c +++ b/arch/xtensa/src/esp32s3/esp32s3_userfault.c @@ -110,7 +110,7 @@ uint32_t *esp32s3_userfault_abort(int exccause, uint32_t *regs) info.si_errno = 0; info.si_value.sival_ptr = (FAR void *)regs[REG_EXCVADDR]; - nxsig_tcbdispatch(tcb, &info, false, NULL); + nxsig_tcbdispatch(tcb, &info, false, NULL, false); regs = tcb->xcp.regs; tcb->xcp.regs = NULL; diff --git a/sched/group/group.h b/sched/group/group.h index 7465f028a30..202ea554823 100644 --- a/sched/group/group.h +++ b/sched/group/group.h @@ -90,7 +90,7 @@ FAR struct task_group_s *task_getgroup(pid_t pid); /* Signaling group members */ int group_signal(FAR struct task_group_s *group, FAR siginfo_t *siginfo, - FAR const void *source); + FAR const void *source, bool limit); /* Parent/child data management */ diff --git a/sched/group/group_signal.c b/sched/group/group_signal.c index 17e3ccf0232..952c6a20cd3 100644 --- a/sched/group/group_signal.c +++ b/sched/group/group_signal.c @@ -47,6 +47,7 @@ struct group_signal_s { FAR siginfo_t *siginfo; /* Signal to be dispatched */ FAR const void *source; /* The POSIX timer of the signal, or NULL */ + bool limit; /* Apply SIGQUEUE_MAX (sigqueue()) */ FAR struct tcb_s *dtcb; /* Default, valid TCB */ FAR struct tcb_s *utcb; /* TCB with this signal unblocked */ FAR struct tcb_s *atcb; /* This TCB was awakened */ @@ -112,7 +113,8 @@ static int group_signal_handler(pid_t pid, FAR void *arg) * receive the signal. */ - ret = nxsig_tcbdispatch(tcb, info->siginfo, true, info->source); + ret = nxsig_tcbdispatch(tcb, info->siginfo, true, info->source, + info->limit); if (ret < 0) { return ret; @@ -154,7 +156,8 @@ static int group_signal_handler(pid_t pid, FAR void *arg) * blocking the signal will receive the signal. */ - ret = nxsig_tcbdispatch(tcb, info->siginfo, true, info->source); + ret = nxsig_tcbdispatch(tcb, info->siginfo, true, info->source, + info->limit); if (ret < 0) { return ret; @@ -187,6 +190,7 @@ static int group_signal_handler(pid_t pid, FAR void *arg) * * Input Parameters: * group - The task group that needs to be signalled. + * limit - true for sigqueue(): fail with -EAGAIN at SIGQUEUE_MAX. * * Returned Value: * 0 (OK) on success; a negated errno value on failure. @@ -200,7 +204,7 @@ static int group_signal_handler(pid_t pid, FAR void *arg) ****************************************************************************/ int group_signal(FAR struct task_group_s *group, FAR siginfo_t *siginfo, - FAR const void *source) + FAR const void *source, bool limit) { #ifdef HAVE_GROUP_MEMBERS struct group_signal_s info; @@ -211,6 +215,7 @@ int group_signal(FAR struct task_group_s *group, FAR siginfo_t *siginfo, info.siginfo = siginfo; info.source = source; + info.limit = limit; info.dtcb = NULL; /* Default, valid TCB */ info.utcb = NULL; /* TCB with this signal unblocked */ info.atcb = NULL; /* This TCB was awakened */ @@ -264,7 +269,7 @@ int group_signal(FAR struct task_group_s *group, FAR siginfo_t *siginfo, /* Now deliver the signal to the selected group member */ - ret = nxsig_tcbdispatch(tcb, siginfo, true, source); + ret = nxsig_tcbdispatch(tcb, siginfo, true, source, limit); } errout: @@ -280,6 +285,7 @@ errout: UNUSED(group); UNUSED(siginfo); UNUSED(source); + UNUSED(limit); return -ENOSYS; #endif diff --git a/sched/pthread/pthread_sigqueue.c b/sched/pthread/pthread_sigqueue.c index 655f8130cce..a2ab3b2c03b 100644 --- a/sched/pthread/pthread_sigqueue.c +++ b/sched/pthread/pthread_sigqueue.c @@ -73,7 +73,7 @@ int pthread_sigqueue(pthread_t thread, int sig, const union sigval value) /* Send the signal */ - err = nxsig_dispatch((pid_t)thread, &info, true, NULL); + err = nxsig_dispatch((pid_t)thread, &info, true, NULL, true); if (err < 0) { return -err; diff --git a/sched/signal/sig_dispatch.c b/sched/signal/sig_dispatch.c index 67edadd9803..14e80f02b7c 100644 --- a/sched/signal/sig_dispatch.c +++ b/sched/signal/sig_dispatch.c @@ -27,6 +27,7 @@ #include <nuttx/config.h> #include <inttypes.h> +#include <limits.h> #include <string.h> #include <signal.h> #include <unistd.h> @@ -529,6 +530,48 @@ static bool nxsig_action_queued(FAR struct tcb_s *tcb, } #endif +/**************************************************************************** + * Name: nxsig_queue_full + * + * Description: + * Return true if SIGQUEUE_MAX signals are pending, or queued for a signal + * handler, in the task group of stcb. + * + * Assumptions: + * Called in a critical section. + * + ****************************************************************************/ + +static bool nxsig_queue_full(FAR struct tcb_s *stcb) +{ + FAR struct task_group_s *group = stcb->group; + size_t count; +#if defined(CONFIG_ENABLE_ALL_SIGNALS) && defined(HAVE_GROUP_MEMBERS) + FAR sq_entry_t *entry; + FAR struct tcb_s *tcb; +#endif + + /* tg_lock also keeps the list of the threads */ + + spin_lock(&group->tg_lock); + count = sq_count(&group->tg_sigpendingq); + +#ifdef CONFIG_ENABLE_ALL_SIGNALS +# ifdef HAVE_GROUP_MEMBERS + for (entry = group->tg_members.head; entry != NULL; entry = entry->flink) + { + tcb = container_of(entry, struct tcb_s, member); + count += sq_count(&tcb->sigpendactionq); + } +# else + count += sq_count(&stcb->sigpendactionq); +# endif +#endif + + spin_unlock(&group->tg_lock); + return count >= SIGQUEUE_MAX; +} + /**************************************************************************** * Public Functions ****************************************************************************/ @@ -554,13 +597,17 @@ static bool nxsig_action_queued(FAR struct tcb_s *tcb, * the pending or queued entry, so that the timer can see that its signal * is still queued (nxsig_source_queued()). * + * limit is true for sigqueue() and pthread_sigqueue(): the signal is not + * sent if SIGQUEUE_MAX signals are queued in the task group of stcb. + * * Returned Value: * Returns 0 (OK) on success or a negated errno value on failure. * ****************************************************************************/ int nxsig_tcbdispatch(FAR struct tcb_s *stcb, FAR siginfo_t *info, - bool group_dispatch, FAR const void *source) + bool group_dispatch, FAR const void *source, + bool limit) { FAR struct tcb_s *rtcb = this_task(); irqstate_t flags; @@ -622,6 +669,16 @@ int nxsig_tcbdispatch(FAR struct tcb_s *stcb, FAR siginfo_t *info, return -ESRCH; } + /* Count the queued signals in the critical section that queues this one, + * so that other senders cannot pass the limit meanwhile. + */ + + if (limit && nxsig_queue_full(stcb)) + { + leave_critical_section(flags); + return -EAGAIN; + } + masked = nxsig_ismember(&stcb->sigprocmask, info->si_signo); #ifdef CONFIG_ENABLE_ALL_SIGNALS @@ -915,7 +972,8 @@ int nxsig_tcbdispatch(FAR struct tcb_s *stcb, FAR siginfo_t *info, * - Get the TCB associated with the pid. * - Call nxsig_tcbdispatch with the TCB * - * source is the POSIX timer that sends the signal, or NULL. + * source is the POSIX timer that sends the signal, or NULL. limit is + * true for sigqueue() and pthread_sigqueue() (see nxsig_tcbdispatch()). * * Returned Value: * Returns 0 (OK) on success or a negated errno value on failure. @@ -923,7 +981,7 @@ int nxsig_tcbdispatch(FAR struct tcb_s *stcb, FAR siginfo_t *info, ****************************************************************************/ int nxsig_dispatch(pid_t pid, FAR siginfo_t *info, bool thread, - FAR const void *source) + FAR const void *source, bool limit) { #ifdef HAVE_GROUP_MEMBERS if (!thread) @@ -936,7 +994,7 @@ int nxsig_dispatch(pid_t pid, FAR siginfo_t *info, bool thread, if (group != NULL) { - return group_signal(group, info, source); + return group_signal(group, info, source, limit); } } else @@ -948,7 +1006,7 @@ int nxsig_dispatch(pid_t pid, FAR siginfo_t *info, bool thread, if (stcb != NULL) { - return nxsig_tcbdispatch(stcb, info, false, source); + return nxsig_tcbdispatch(stcb, info, false, source, limit); } } diff --git a/sched/signal/sig_kill.c b/sched/signal/sig_kill.c index 3320dbc8157..31af2fb405c 100644 --- a/sched/signal/sig_kill.c +++ b/sched/signal/sig_kill.c @@ -139,7 +139,7 @@ int nxsig_kill(pid_t pid, int signo) /* Send the signal */ - return nxsig_dispatch(pid, &info, false, NULL); + return nxsig_dispatch(pid, &info, false, NULL, false); #endif } diff --git a/sched/signal/sig_notification.c b/sched/signal/sig_notification.c index 1a87ccc7d59..a7b9125763e 100644 --- a/sched/signal/sig_notification.c +++ b/sched/signal/sig_notification.c @@ -205,7 +205,7 @@ int nxsig_notification_source(pid_t pid, FAR struct sigevent *event, /* Send the signal */ - return nxsig_dispatch(pid, &info, thread, source); + return nxsig_dispatch(pid, &info, thread, source, false); } #ifdef CONFIG_SIG_EVTHREAD diff --git a/sched/signal/sig_queue.c b/sched/signal/sig_queue.c index cfc9a998f14..98b13a4161e 100644 --- a/sched/signal/sig_queue.c +++ b/sched/signal/sig_queue.c @@ -38,6 +38,54 @@ #include "sched/sched.h" #include "signal/signal.h" +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: nxsig_queue_send + * + * Description: + * Send the signal of nxsig_queue() and sigqueue(). If limit is true, fail + * with -EAGAIN if SIGQUEUE_MAX signals are queued at the receiver. + * + ****************************************************************************/ + +static int nxsig_queue_send(int pid, int signo, union sigval value, + bool limit) +{ +#ifdef CONFIG_SCHED_HAVE_PARENT + FAR struct tcb_s *rtcb = this_task(); +#endif + siginfo_t info; + + sinfo("pid=0x%08x signo=%d value=%d\n", pid, signo, value.sival_int); + + /* Sanity checks */ + + if (!GOOD_SIGNO(signo)) + { + return -EINVAL; + } + + /* Create the siginfo structure */ + + info.si_signo = signo; + info.si_code = SI_QUEUE; + info.si_errno = OK; + info.si_value = value; +#ifdef CONFIG_SCHED_HAVE_PARENT + info.si_pid = rtcb->pid; + info.si_uid = getuid(); + info.si_status = OK; +#endif + info.si_user = NULL; /* Will be set in sig_dispatch.c */ + + /* Send the signal */ + + return nxsig_dispatch(pid, &info, false, NULL, limit); +} + /**************************************************************************** * Public Functions ****************************************************************************/ @@ -57,7 +105,8 @@ * once. * * This is an internal OS interface. It is functionally equivalent to - * sigqueue() except that it does not modify the errno value. + * sigqueue() except that it does not modify the errno value, and that + * SIGQUEUE_MAX does not apply: the OS uses it for notifications. * * Input Parameters: * pid - Process ID of task to receive signal @@ -79,36 +128,7 @@ int nxsig_queue(int pid, int signo, union sigval value) { -#ifdef CONFIG_SCHED_HAVE_PARENT - FAR struct tcb_s *rtcb = this_task(); -#endif - siginfo_t info; - - sinfo("pid=0x%08x signo=%d value=%d\n", pid, signo, value.sival_int); - - /* Sanity checks */ - - if (!GOOD_SIGNO(signo)) - { - return -EINVAL; - } - - /* Create the siginfo structure */ - - info.si_signo = signo; - info.si_code = SI_QUEUE; - info.si_errno = OK; - info.si_value = value; -#ifdef CONFIG_SCHED_HAVE_PARENT - info.si_pid = rtcb->pid; - info.si_uid = getuid(); - info.si_status = OK; -#endif - info.si_user = NULL; /* Will be set in sig_dispatch.c */ - - /* Send the signal */ - - return nxsig_dispatch(pid, &info, false, NULL); + return nxsig_queue_send(pid, signo, value, false); } /**************************************************************************** @@ -135,7 +155,7 @@ int nxsig_queue(int pid, int signo, union sigval value) * any failure, -1 (ERROR) is returned and errno variable is set * appropriately: * - * EGAIN - The limit of signals which may be queued has been reached. + * EAGAIN - SIGQUEUE_MAX signals are still pending at the receiver. * EINVAL - sig was invalid. * EPERM - The process does not have permission to send the * signal to the receiving process. @@ -153,9 +173,7 @@ int sigqueue(int pid, int signo, union sigval value) return ERROR; } - /* Let nxsig_queue() do all of the real work */ - - ret = nxsig_queue(pid, signo, value); + ret = nxsig_queue_send(pid, signo, value, true); if (ret < 0) { set_errno(-ret); diff --git a/sched/signal/sig_tgkill.c b/sched/signal/sig_tgkill.c index ac049443e35..b8237c6a7f6 100644 --- a/sched/signal/sig_tgkill.c +++ b/sched/signal/sig_tgkill.c @@ -112,7 +112,7 @@ int nxsig_tgkill(pid_t pid, pid_t tid, int signo) info.si_status = OK; #endif - ret = nxsig_dispatch(tid, &info, pid == (pid_t)-1, NULL); + ret = nxsig_dispatch(tid, &info, pid == (pid_t)-1, NULL, false); errout: return ret; diff --git a/sched/signal/sig_unmaskpendingsignal.c b/sched/signal/sig_unmaskpendingsignal.c index 00a42307da9..9da0f238c22 100644 --- a/sched/signal/sig_unmaskpendingsignal.c +++ b/sched/signal/sig_unmaskpendingsignal.c @@ -115,7 +115,7 @@ static bool nxsig_dispatch_unmasked(FAR struct tcb_s *rtcb) * other recipient for the signal other than this thread. */ - nxsig_tcbdispatch(rtcb, &info, group, source); + nxsig_tcbdispatch(rtcb, &info, group, source, false); } } } diff --git a/sched/signal/signal.h b/sched/signal/signal.h index b783c0ab10d..1f5901dc320 100644 --- a/sched/signal/signal.h +++ b/sched/signal/signal.h @@ -192,9 +192,10 @@ int nxsig_default_initialize(FAR struct tcb_s *tcb); int nxsig_tcbdispatch(FAR struct tcb_s *stcb, FAR siginfo_t *info, bool group_dispatch, - FAR const void *source); + FAR const void *source, bool limit); int nxsig_dispatch(pid_t pid, FAR siginfo_t *info, - bool thread, FAR const void *source); + bool thread, FAR const void *source, + bool limit); bool nxsig_source_queued(pid_t pid, FAR const void *source, int signo); diff --git a/sched/task/task_exithook.c b/sched/task/task_exithook.c index c40dbbf32df..2944dc16ab6 100644 --- a/sched/task/task_exithook.c +++ b/sched/task/task_exithook.c @@ -205,7 +205,7 @@ static inline void nxtask_sigchild(pid_t ppid, FAR struct tcb_s *ctcb, /* Send the signal to one thread in the group */ - group_signal(pgrp, &info, NULL); + group_signal(pgrp, &info, NULL, false); } } @@ -261,7 +261,7 @@ static inline void nxtask_sigchild(FAR struct tcb_s *ptcb, * can provide the correct si_code value with the signal. */ - nxsig_tcbdispatch(ptcb, &info, true, NULL); + nxsig_tcbdispatch(ptcb, &info, true, NULL, false); } }
