diff --git a/nptl/Makefile b/nptl/Makefile
index 584e0ffd96..6c37271c8f 100644
--- a/nptl/Makefile
+++ b/nptl/Makefile
@@ -286,7 +286,7 @@ tests = tst-attr1 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 \
@@ -600,6 +600,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-join7: $(libdl) $(shared-thread-library)
diff --git a/nptl/tst-cancel28.c b/nptl/tst-cancel28.c
new file mode 100644
index 0000000000..ad70aeb548
--- /dev/null
+++ b/nptl/tst-cancel28.c
@@ -0,0 +1,80 @@
+/* Check if the thread created by POSIX timer using SIGEV_THREAD is
+   cancellable.
+   Copyright (C) 2019 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.  */
+#define TIMEOUT 5
+#include <support/test-driver.c>
diff --git a/rt/Makefile b/rt/Makefile
index 935d968716..dab5d62a57 100644
--- a/rt/Makefile
+++ b/rt/Makefile
@@ -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)
diff --git a/rt/tst-timer-sigmask.c b/rt/tst-timer-sigmask.c
new file mode 100644
index 0000000000..063004120a
--- /dev/null
+++ b/rt/tst-timer-sigmask.c
@@ -0,0 +1,85 @@
+/* Check resulting signal mask from POSIX timer using SIGEV_THREAD.
+   Copyright (C) 2019 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 bool thread_handler_failure = false;
+
+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++)
+    {
+#ifdef __linux__
+      /* POSIX timers threads created to handle SIGEV_THREAD block all
+	 signals except SIGKILL, SIGSTOP and glibc internals one except
+	 SIGTIMER.  */
+      if (!sigismember (&ss, sig)
+	  && (sig != SIGSETXID && sig != SIGCANCEL
+	      && sig != SIGKILL && sig != SIGSTOP))
+	{
+	  thread_handler_failure = true;
+	}
+#endif
+      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);
+
+  TEST_COMPARE (thread_handler_failure, false);
+
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/sysdeps/unix/sysv/linux/internal-signals.h b/sysdeps/unix/sysv/linux/internal-signals.h
index bc2f23aea4..3840505050 100644
--- a/sysdeps/unix/sysv/linux/internal-signals.h
+++ b/sysdeps/unix/sysv/linux/internal-signals.h
@@ -58,6 +58,17 @@ static const sigset_t sigall_set = {
    .__val = {[0 ...  _SIGSET_NWORDS-1 ] =  -1 }
 };
 
+static const sigset_t sigtimer_set = {
+#if ULONG_MAX == 0xffffffff
+  .__val = { [0]                      = 0,
+             [1]                      = __sigmask (SIGTIMER),
+             [2 ... _SIGSET_NWORDS-1] = 0 }
+#else
+  .__val = { [0]                      = __sigmask (SIGTIMER),
+	     [1 ... _SIGSET_NWORDS-1] = 0 }
+#endif
+};
+
 /* Block all signals, including internal glibc ones.  */
 static inline void
 __libc_signal_block_all (sigset_t *set)
@@ -78,6 +89,24 @@ __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_DECL (err);
+  INTERNAL_SYSCALL_CALL (rt_sigprocmask, err, 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_DECL (err);
+  INTERNAL_SYSCALL_CALL (rt_sigprocmask, err, SIG_UNBLOCK, &sigtimer_set, set,
+			 _NSIG / 8);
+}
+
 /* Restore current process signal mask.  */
 static inline void
 __libc_signal_restore_set (const sigset_t *set)
diff --git a/sysdeps/unix/sysv/linux/timer_routines.c b/sysdeps/unix/sysv/linux/timer_routines.c
index abafffdfd5..63083f6f91 100644
--- a/sysdeps/unix/sysv/linux/timer_routines.c
+++ b/sysdeps/unix/sysv/linux/timer_routines.c
@@ -42,16 +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_DECL (err);
-  INTERNAL_SYSCALL (rt_sigprocmask, err, 4, 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;
 
@@ -69,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;
+	  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);
 
-	      /* 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;
 
-	      struct timer *runp = __active_timer_sigev_thread;
-	      while (runp != NULL)
-		if (runp == tk)
-		  break;
-		else
-		  runp = runp->next;
+	  if (runp != NULL)
+	    {
+	      struct thread_start_data *td = malloc (sizeof (*td));
 
-	      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));
-
-		  /* 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);
-		    }
-		}
+		  /* This is the signal we are waiting for.  */
+		  td->thrfunc = tk->thrfunc;
+		  td->sival = tk->sival;
 
-	      pthread_mutex_unlock (&__active_timer_sigev_thread_lock);
+		  pthread_t th;
+		  pthread_create (&th, &tk->attr, timer_sigev_thread, td);
+		}
 	    }
-	  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);
     }
 }
 
@@ -161,15 +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_DECL (err);
-  INTERNAL_SYSCALL (rt_sigprocmask, err, 4, 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;
@@ -179,8 +151,7 @@ __start_helper_thread (void)
     __helper_tid = ((struct pthread *) th)->tid;
 
   /* Restore the signal mask.  */
-  INTERNAL_SYSCALL (rt_sigprocmask, err, 4, SIG_SETMASK, &oss, NULL,
-		    _NSIG / 8);
+  __libc_signal_restore_set (&ss);
 
   /* No need for the attribute anymore.  */
   (void) pthread_attr_destroy (&attr);
