Block all signals on timer_create thread (BZ#10815)

The behavior of the signal mask on threads created by timer_create
for SIGEV_THREAD timers are implementation-defined and glibc explicit
unblocks all signals before calling the user-defined function.

This behavior, although not incorrect standard-wise, opens a race if a
program using a blocked rt-signal plus sigwaitinfo (and without an
installed signal handler for the rt-signal) receives a signal while
executing the used-defined function for SIGEV_THREAD.

A better alternative discussed in bug report is to rather block all
signals (besides the internal ones not available to application
usage).

This patch fixes this issue by only unblocking SIGSETXID (used on
set*uid function) and SIGCANCEL (used for thread cancellation).

Checked on x86_64-linux-gnu and i686-linux-gnu.
This commit is contained in:
Adhemerval Zanella 2019-12-09 14:33:33 -03:00
parent 8d42bf859a
commit 27d83441a2
6 changed files with 221 additions and 63 deletions

View File

@ -266,7 +266,7 @@ tests = tst-attr2 tst-attr3 tst-default-attr \
tst-cancel11 tst-cancel12 tst-cancel13 tst-cancel14 tst-cancel15 \
tst-cancel16 tst-cancel17 tst-cancel18 tst-cancel19 tst-cancel20 \
tst-cancel21 tst-cancel22 tst-cancel23 tst-cancel24 \
tst-cancel26 tst-cancel27 \
tst-cancel26 tst-cancel27 tst-cancel28 \
tst-cancel-self tst-cancel-self-cancelstate \
tst-cancel-self-canceltype tst-cancel-self-testcancel \
tst-cleanup0 tst-cleanup1 tst-cleanup2 tst-cleanup3 tst-cleanup4 \
@ -574,6 +574,9 @@ $(objpfx)tst-tls6.out: tst-tls6.sh $(objpfx)tst-tls5 \
$(BASH) $< $(common-objpfx) '$(test-via-rtld-prefix)' \
'$(test-wrapper-env)' '$(run-program-env)' > $@; \
$(evaluate-test)
$(objpfx)tst-cancel28: $(common-objpfx)rt/librt.so
else
$(objpfx)tst-cancel28: $(common-objpfx)rt/librt.a
endif
$(objpfx)tst-dlsym1: $(libdl) $(shared-thread-library)

79
nptl/tst-cancel28.c Normal file
View File

@ -0,0 +1,79 @@
/* Check if the thread created by POSIX timer using SIGEV_THREAD is
cancellable.
Copyright (C) 2020 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */
#include <stdio.h>
#include <time.h>
#include <signal.h>
#include <unistd.h>
#include <stdbool.h>
#include <support/check.h>
#include <support/test-driver.h>
#include <support/xthread.h>
static pthread_barrier_t barrier;
static pthread_t timer_thread;
static void
cl (void *arg)
{
xpthread_barrier_wait (&barrier);
}
static void
thread_handler (union sigval sv)
{
timer_thread = pthread_self ();
xpthread_barrier_wait (&barrier);
pthread_cleanup_push (cl, NULL);
while (1)
clock_nanosleep (CLOCK_REALTIME, 0, &(struct timespec) { 1, 0 }, NULL);
pthread_cleanup_pop (0);
}
static int
do_test (void)
{
struct sigevent sev = { 0 };
sev.sigev_notify = SIGEV_THREAD;
sev.sigev_notify_function = &thread_handler;
timer_t timerid;
TEST_COMPARE (timer_create (CLOCK_REALTIME, &sev, &timerid), 0);
xpthread_barrier_init (&barrier, NULL, 2);
struct itimerspec trigger = { 0 };
trigger.it_value.tv_nsec = 1000000;
TEST_COMPARE (timer_settime (timerid, 0, &trigger, NULL), 0);
xpthread_barrier_wait (&barrier);
xpthread_cancel (timer_thread);
xpthread_barrier_init (&barrier, NULL, 2);
xpthread_barrier_wait (&barrier);
return 0;
}
/* A stall in cancellation is a regression. */
#include <support/test-driver.c>

View File

@ -47,6 +47,7 @@ tests := tst-shm tst-timer tst-timer2 \
tst-timer3 tst-timer4 tst-timer5 \
tst-cpuclock2 tst-cputimer1 tst-cputimer2 tst-cputimer3 \
tst-shm-cancel
tests-internal := tst-timer-sigmask
extra-libs := librt
extra-libs-others := $(extra-libs)
@ -63,9 +64,11 @@ LDFLAGS-rt.so = -Wl,--enable-new-dtags,-z,nodelete
$(objpfx)librt.so: $(shared-thread-library)
ifeq (yes,$(build-shared))
$(addprefix $(objpfx),$(tests)): $(objpfx)librt.so $(shared-thread-library)
$(addprefix $(objpfx),$(tests) $(tests-internal)): \
$(objpfx)librt.so $(shared-thread-library)
else
$(addprefix $(objpfx),$(tests)): $(objpfx)librt.a $(static-thread-library)
$(addprefix $(objpfx),$(tests)) $(tests-internal): \
$(objpfx)librt.a $(static-thread-library)
endif
tst-mqueue7-ARGS = -- $(host-test-program-cmd)

78
rt/tst-timer-sigmask.c Normal file
View File

@ -0,0 +1,78 @@
/* Check resulting signal mask from POSIX timer using SIGEV_THREAD.
Copyright (C) 2020 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */
#include <stdio.h>
#include <time.h>
#include <signal.h>
#include <stdbool.h>
#include <support/check.h>
#include <support/test-driver.h>
#include <support/xthread.h>
#include <internal-signals.h>
static pthread_barrier_t barrier;
static void
thread_handler (union sigval sv)
{
sigset_t ss;
sigprocmask (SIG_BLOCK, NULL, &ss);
if (test_verbose > 0)
printf ("%s: blocked signal mask = { ", __func__);
for (int sig = 1; sig < NSIG; sig++)
{
/* POSIX timers threads created to handle SIGEV_THREAD block all
signals except SIGKILL, SIGSTOP and glibc internals ones. */
if (sigismember (&ss, sig))
{
TEST_VERIFY (sig != SIGKILL && sig != SIGSTOP);
TEST_VERIFY (!__is_internal_signal (sig));
}
if (test_verbose && sigismember (&ss, sig))
printf ("%d, ", sig);
}
if (test_verbose > 0)
printf ("}\n");
xpthread_barrier_wait (&barrier);
}
static int
do_test (void)
{
struct sigevent sev = { 0 };
sev.sigev_notify = SIGEV_THREAD;
sev.sigev_notify_function = &thread_handler;
timer_t timerid;
TEST_COMPARE (timer_create (CLOCK_REALTIME, &sev, &timerid), 0);
xpthread_barrier_init (&barrier, NULL, 2);
struct itimerspec trigger = { 0 };
trigger.it_value.tv_nsec = 1000000;
TEST_COMPARE (timer_settime (timerid, 0, &trigger, NULL), 0);
xpthread_barrier_wait (&barrier);
return 0;
}
#include <support/test-driver.c>

View File

@ -58,6 +58,11 @@ static const sigset_t sigall_set = {
.__val = {[0 ... _SIGSET_NWORDS-1 ] = -1 }
};
static const sigset_t sigtimer_set = {
.__val = { [0] = __sigmask (SIGTIMER),
[1 ... _SIGSET_NWORDS-1] = 0 }
};
/* Block all signals, including internal glibc ones. */
static inline void
__libc_signal_block_all (sigset_t *set)
@ -76,6 +81,22 @@ __libc_signal_block_app (sigset_t *set)
_NSIG / 8);
}
/* Block only SIGTIMER and return the previous set on SET. */
static inline void
__libc_signal_block_sigtimer (sigset_t *set)
{
INTERNAL_SYSCALL_CALL (rt_sigprocmask, SIG_BLOCK, &sigtimer_set, set,
_NSIG / 8);
}
/* Unblock only SIGTIMER and return the previous set on SET. */
static inline void
__libc_signal_unblock_sigtimer (sigset_t *set)
{
INTERNAL_SYSCALL_CALL (rt_sigprocmask, SIG_UNBLOCK, &sigtimer_set, set,
_NSIG / 8);
}
/* Restore current process signal mask. */
static inline void
__libc_signal_restore_set (const sigset_t *set)

View File

@ -42,15 +42,9 @@ struct thread_start_data
static void *
timer_sigev_thread (void *arg)
{
/* The parent thread has all signals blocked. This is a bit
surprising for user code, although valid. We unblock all
signals. */
sigset_t ss;
sigemptyset (&ss);
INTERNAL_SYSCALL_CALL (rt_sigprocmask, SIG_SETMASK, &ss, NULL, _NSIG / 8);
__libc_signal_unblock_sigtimer (NULL);
struct thread_start_data *td = (struct thread_start_data *) arg;
void (*thrfunc) (sigval_t) = td->thrfunc;
sigval_t sival = td->sival;
@ -68,65 +62,49 @@ timer_sigev_thread (void *arg)
static void *
timer_helper_thread (void *arg)
{
/* Wait for the SIGTIMER signal, allowing the setXid signal, and
none else. */
sigset_t ss;
sigemptyset (&ss);
__sigaddset (&ss, SIGTIMER);
/* Endless loop of waiting for signals. The loop is only ended when
the thread is canceled. */
while (1)
{
siginfo_t si;
/* sigwaitinfo cannot be used here, since it deletes
SIGCANCEL == SIGTIMER from the set. */
/* XXX The size argument hopefully will have to be changed to the
real size of the user-level sigset_t. */
int result = SYSCALL_CANCEL (rt_sigtimedwait, &ss, &si, NULL, _NSIG / 8);
if (result > 0)
while (sigwaitinfo (&sigtimer_set, &si) < 0);
if (si.si_code == SI_TIMER)
{
if (si.si_code == SI_TIMER)
struct timer *tk = (struct timer *) si.si_ptr;
/* Check the timer is still used and will not go away
while we are reading the values here. */
pthread_mutex_lock (&__active_timer_sigev_thread_lock);
struct timer *runp = __active_timer_sigev_thread;
while (runp != NULL)
if (runp == tk)
break;
else
runp = runp->next;
if (runp != NULL)
{
struct timer *tk = (struct timer *) si.si_ptr;
struct thread_start_data *td = malloc (sizeof (*td));
/* Check the timer is still used and will not go away
while we are reading the values here. */
pthread_mutex_lock (&__active_timer_sigev_thread_lock);
struct timer *runp = __active_timer_sigev_thread;
while (runp != NULL)
if (runp == tk)
break;
else
runp = runp->next;
if (runp != NULL)
/* There is not much we can do if the allocation fails. */
if (td != NULL)
{
struct thread_start_data *td = malloc (sizeof (*td));
/* This is the signal we are waiting for. */
td->thrfunc = tk->thrfunc;
td->sival = tk->sival;
/* There is not much we can do if the allocation fails. */
if (td != NULL)
{
/* This is the signal we are waiting for. */
td->thrfunc = tk->thrfunc;
td->sival = tk->sival;
pthread_t th;
(void) pthread_create (&th, &tk->attr,
timer_sigev_thread, td);
}
pthread_t th;
pthread_create (&th, &tk->attr, timer_sigev_thread, td);
}
pthread_mutex_unlock (&__active_timer_sigev_thread_lock);
}
else if (si.si_code == SI_TKILL)
/* The thread is canceled. */
pthread_exit (NULL);
pthread_mutex_unlock (&__active_timer_sigev_thread_lock);
}
else if (si.si_code == SI_TKILL)
/* The thread is canceled. */
pthread_exit (NULL);
}
}
@ -160,14 +138,10 @@ __start_helper_thread (void)
/* Block all signals in the helper thread but SIGSETXID. To do this
thoroughly we temporarily have to block all signals here. The
helper can lose wakeups if SIGCANCEL is not blocked throughout,
but sigfillset omits it SIGSETXID. So, we add SIGCANCEL back
explicitly here. */
helper can lose wakeups if SIGTIMER is not blocked throughout. */
sigset_t ss;
sigset_t oss;
sigfillset (&ss);
__sigaddset (&ss, SIGCANCEL);
INTERNAL_SYSCALL_CALL (rt_sigprocmask, SIG_SETMASK, &ss, &oss, _NSIG / 8);
__libc_signal_block_app (&ss);
__libc_signal_block_sigtimer (NULL);
/* Create the helper thread for this timer. */
pthread_t th;
@ -177,7 +151,7 @@ __start_helper_thread (void)
__helper_tid = ((struct pthread *) th)->tid;
/* Restore the signal mask. */
INTERNAL_SYSCALL_CALL (rt_sigprocmask, SIG_SETMASK, &oss, NULL, _NSIG / 8);
__libc_signal_restore_set (&ss);
/* No need for the attribute anymore. */
(void) pthread_attr_destroy (&attr);