Skip to content

Commit df7a996

Browse files
committed
signal: Queue ignored posixtimers on ignore list
Queue posixtimers which have their signal ignored on the ignored list: 1) When the timer fires and the signal has SIG_IGN set 2) When SIG_IGN is installed via sigaction() and a timer signal is already queued This only happens when the signal is for a valid timer, which delivered the signal in periodic mode. One-shot timer signals are correctly dropped. Due to the lock order constraints (sighand::siglock nests inside timer::lock) the signal code cannot access any of the timer fields which are relevant to make this decision, e.g. timer::it_status. This is addressed by establishing a protection scheme which requires to lock both locks on the timer side for modifying decision fields in the timer struct and therefore makes it possible for the signal delivery to evaluate with only sighand:siglock being held: 1) Move the NULLification of timer->it_signal into the sighand::siglock protected section of timer_delete() and check timer::it_signal in the code path which determines whether the signal is dropped or queued on the ignore list. This ensures that a deleted timer cannot be moved onto the ignore list, which would prevent it from being freed on exit() as it is not longer in the process' posix timer list. If the timer got moved to the ignored list before deletion then it is removed from the ignored list under sighand lock in timer_delete(). 2) Provide a new timer::it_sig_periodic flag, which gets set in the signal queue path with both timer and sighand locks held if the timer is actually in periodic mode at expiry time. The ignore list code checks this flag under sighand::siglock and drops the signal when it is not set. If it is set, then the signal is moved to the ignored list independent of the actual state of the timer. When the signal is un-ignored later then the signal is moved back to the signal queue. On signal delivery the posix timer side decides about dropping the signal if the timer was re-armed, dis-armed or deleted based on the signal sequence counter check. If the thread/process exits then not yet delivered signals are discarded which means the reference of the timer containing the sigqueue is dropped and frees the timer. This is way cheaper than requiring all code paths to lock sighand::siglock of the target thread/process on any modification of timer::it_status or going all the way and removing pending signals from the signal queues on every rearm, disarm or delete operation. So the protection scheme here is that on the timer side both timer::lock and sighand::siglock have to be held for modifying timer::it_signal timer::it_sig_periodic which means that on the signal side holding sighand::siglock is enough to evaluate these fields. In posixtimer_deliver_signal() holding timer::lock is sufficient to do the sequence validation against timer::it_signal_seq because a concurrent expiry is waiting on timer::lock to be released. This completes the SIG_IGN handling and such timers are not longer self rearmed which avoids pointless wakeups. Signed-off-by: Thomas Gleixner <[email protected]> Reviewed-by: Frederic Weisbecker <[email protected]> Acked-by: Peter Zijlstra (Intel) <[email protected]> Link: https://lore.kernel.org/all/[email protected]
1 parent caf7743 commit df7a996

File tree

3 files changed

+83
-6
lines changed

3 files changed

+83
-6
lines changed

include/linux/posix-timers.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ static inline void posix_cputimers_init_work(void) { }
160160
* @it_clock: The posix timer clock id
161161
* @it_id: The posix timer id for identifying the timer
162162
* @it_status: The status of the timer
163+
* @it_sig_periodic: The periodic status at signal delivery
163164
* @it_overrun: The overrun counter for pending signals
164165
* @it_overrun_last: The overrun at the time of the last delivered signal
165166
* @it_signal_seq: Sequence count to control signal delivery
@@ -184,6 +185,7 @@ struct k_itimer {
184185
clockid_t it_clock;
185186
timer_t it_id;
186187
int it_status;
188+
bool it_sig_periodic;
187189
s64 it_overrun;
188190
s64 it_overrun_last;
189191
unsigned int it_signal_seq;

kernel/signal.c

Lines changed: 75 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@
5959
#include <asm/cacheflush.h>
6060
#include <asm/syscall.h> /* for syscall_get_* */
6161

62+
#include "time/posix-timers.h"
63+
6264
/*
6365
* SLAB caches for signal bits.
6466
*/
@@ -731,6 +733,16 @@ void signal_wake_up_state(struct task_struct *t, unsigned int state)
731733
kick_process(t);
732734
}
733735

736+
static inline void posixtimer_sig_ignore(struct task_struct *tsk, struct sigqueue *q);
737+
738+
static void sigqueue_free_ignored(struct task_struct *tsk, struct sigqueue *q)
739+
{
740+
if (likely(!(q->flags & SIGQUEUE_PREALLOC) || q->info.si_code != SI_TIMER))
741+
__sigqueue_free(q);
742+
else
743+
posixtimer_sig_ignore(tsk, q);
744+
}
745+
734746
/* Remove signals in mask from the pending set and queue. */
735747
static void flush_sigqueue_mask(struct task_struct *p, sigset_t *mask, struct sigpending *s)
736748
{
@@ -747,7 +759,7 @@ static void flush_sigqueue_mask(struct task_struct *p, sigset_t *mask, struct si
747759
list_for_each_entry_safe(q, n, &s->list, list) {
748760
if (sigismember(mask, q->info.si_signo)) {
749761
list_del_init(&q->list);
750-
__sigqueue_free(q);
762+
sigqueue_free_ignored(p, q);
751763
}
752764
}
753765
}
@@ -1964,7 +1976,7 @@ int posixtimer_send_sigqueue(struct k_itimer *tmr)
19641976
int sig = q->info.si_signo;
19651977
struct task_struct *t;
19661978
unsigned long flags;
1967-
int ret, result;
1979+
int result;
19681980

19691981
guard(rcu)();
19701982

@@ -1981,13 +1993,55 @@ int posixtimer_send_sigqueue(struct k_itimer *tmr)
19811993
*/
19821994
tmr->it_sigqueue_seq = tmr->it_signal_seq;
19831995

1984-
ret = 1; /* the signal is ignored */
1996+
/*
1997+
* Set the signal delivery status under sighand lock, so that the
1998+
* ignored signal handling can distinguish between a periodic and a
1999+
* non-periodic timer.
2000+
*/
2001+
tmr->it_sig_periodic = tmr->it_status == POSIX_TIMER_REQUEUE_PENDING;
2002+
19852003
if (!prepare_signal(sig, t, false)) {
19862004
result = TRACE_SIGNAL_IGNORED;
2005+
2006+
/* Paranoia check. Try to survive. */
2007+
if (WARN_ON_ONCE(!list_empty(&q->list)))
2008+
goto out;
2009+
2010+
/* Periodic timers with SIG_IGN are queued on the ignored list */
2011+
if (tmr->it_sig_periodic) {
2012+
/*
2013+
* Already queued means the timer was rearmed after
2014+
* the previous expiry got it on the ignore list.
2015+
* Nothing to do for that case.
2016+
*/
2017+
if (hlist_unhashed(&tmr->ignored_list)) {
2018+
/*
2019+
* Take a signal reference and queue it on
2020+
* the ignored list.
2021+
*/
2022+
posixtimer_sigqueue_getref(q);
2023+
posixtimer_sig_ignore(t, q);
2024+
}
2025+
} else if (!hlist_unhashed(&tmr->ignored_list)) {
2026+
/*
2027+
* Covers the case where a timer was periodic and
2028+
* then the signal was ignored. Later it was rearmed
2029+
* as oneshot timer. The previous signal is invalid
2030+
* now, and this oneshot signal has to be dropped.
2031+
* Remove it from the ignored list and drop the
2032+
* reference count as the signal is not longer
2033+
* queued.
2034+
*/
2035+
hlist_del_init(&tmr->ignored_list);
2036+
posixtimer_putref(tmr);
2037+
}
19872038
goto out;
19882039
}
19892040

1990-
ret = 0;
2041+
/* This should never happen and leaks a reference count */
2042+
if (WARN_ON_ONCE(!hlist_unhashed(&tmr->ignored_list)))
2043+
hlist_del_init(&tmr->ignored_list);
2044+
19912045
if (unlikely(!list_empty(&q->list))) {
19922046
/* This holds a reference count already */
19932047
result = TRACE_SIGNAL_ALREADY_PENDING;
@@ -2000,7 +2054,22 @@ int posixtimer_send_sigqueue(struct k_itimer *tmr)
20002054
out:
20012055
trace_signal_generate(sig, &q->info, t, tmr->it_pid_type != PIDTYPE_PID, result);
20022056
unlock_task_sighand(t, &flags);
2003-
return ret;
2057+
return 0;
2058+
}
2059+
2060+
static inline void posixtimer_sig_ignore(struct task_struct *tsk, struct sigqueue *q)
2061+
{
2062+
struct k_itimer *tmr = container_of(q, struct k_itimer, sigq);
2063+
2064+
/*
2065+
* If the timer is marked deleted already or the signal originates
2066+
* from a non-periodic timer, then just drop the reference
2067+
* count. Otherwise queue it on the ignored list.
2068+
*/
2069+
if (tmr->it_signal && tmr->it_sig_periodic)
2070+
hlist_add_head(&tmr->ignored_list, &tsk->signal->ignored_posix_timers);
2071+
else
2072+
posixtimer_putref(tmr);
20042073
}
20052074

20062075
static void posixtimer_sig_unignore(struct task_struct *tsk, int sig)
@@ -2048,6 +2117,7 @@ static void posixtimer_sig_unignore(struct task_struct *tsk, int sig)
20482117
}
20492118
}
20502119
#else /* CONFIG_POSIX_TIMERS */
2120+
static inline void posixtimer_sig_ignore(struct task_struct *tsk, struct sigqueue *q) { }
20512121
static inline void posixtimer_sig_unignore(struct task_struct *tsk, int sig) { }
20522122
#endif /* !CONFIG_POSIX_TIMERS */
20532123

kernel/time/posix-timers.c

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1072,12 +1072,17 @@ SYSCALL_DEFINE1(timer_delete, timer_t, timer_id)
10721072
spin_lock(&current->sighand->siglock);
10731073
hlist_del(&timer->list);
10741074
posix_timer_cleanup_ignored(timer);
1075-
spin_unlock(&current->sighand->siglock);
10761075
/*
10771076
* A concurrent lookup could check timer::it_signal lockless. It
10781077
* will reevaluate with timer::it_lock held and observe the NULL.
1078+
*
1079+
* It must be written with siglock held so that the signal code
1080+
* observes timer->it_signal == NULL in do_sigaction(SIG_IGN),
1081+
* which prevents it from moving a pending signal of a deleted
1082+
* timer to the ignore list.
10791083
*/
10801084
WRITE_ONCE(timer->it_signal, NULL);
1085+
spin_unlock(&current->sighand->siglock);
10811086

10821087
unlock_timer(timer, flags);
10831088
posix_timer_unhash_and_free(timer);

0 commit comments

Comments
 (0)