Problem with signal 0 being delivered to SIGUSR1 handler

Jilles Tjoelker jilles at stack.nl
Fri Nov 22 13:36:09 UTC 2013


On Thu, Nov 21, 2013 at 11:15:46PM +0200, Konstantin Belousov wrote:
> On Thu, Nov 21, 2013 at 02:39:02PM +0200, Vitaly Magerya wrote:
> > Hi, folks. I'm investigating a test case failure that devel/boehm-gc
> > has on recent FreeBSD releases. The problem is that a signal
> > handler registered for SIGUSR1 is sometimes called with signum=0,
> > which should not be possible under any conditions.

> > Here's a simple test case that demonstrates this behavior:

> > /* Compile with 'c99 -o example example.c -pthread'
> >  */
> > #include <pthread.h>
> > #include <signal.h>
> > #include <stdio.h>
> > #include <stdlib.h>
> > 
> > void signal_handler(int signum, siginfo_t *si, void *context) {
> >     if (signum != SIGUSR1) {
> >         printf("bad signal, signum=%d\n", signum);
> >         exit(1);
> >     }
> > }
> > 
> > void *thread_func(void *arg) {
> >     return arg;
> > }
> > 
> > int main(void) {
> >     struct sigaction sa = { 0 };
> >     sa.sa_flags = SA_SIGINFO;
> >     sa.sa_sigaction = signal_handler;
> >     if (sigfillset(&sa.sa_mask) != 0) abort();
> >     if (sigaction(SIGUSR1, &sa, NULL) != 0) abort();
> >     for (int i = 0; i < 10000; i++) {
> >         pthread_t t;
> >         pthread_create(&t, NULL, thread_func, NULL);
> >         pthread_kill(t, SIGUSR1);
> Side note.  pthread_kill(3) call behaviour is undefined if pthread_create(3)
> in the line before failed.
> 
> >     }
> >     return 0;
> > }

> > Under FreeBSD 9.2-RELEASE amd64 I pretty consistently get
> > "signum=0" from this program, but you may need to run it a few
> > times or increase the number of iterations to see the same.

> > Interestingly enough, I don't see this behavior under 9.0-RELEASE.

This is because the bug was introduced with AVX support. (It also occurs
on systems without AVX.)

> > So, any ideas what the problem here is?

> It happens when libthr deferred signal handling path is taken for signal
> delivery and for some reason the code inside the deferred path called
> into rtld for symbol binding. Than, rtld lock is locked, some code in
> rtld is executed, and rtld lock is unlocked. Unlock causes _thr_ast()
> run, which results in the nested check_deferred_signal() execution.
> The check_deferred_signal() clearks si_signo, so on return the same
> signal is delivered one more time, but is advertized as signo zero.

> The _thr_rtld_init() approach of doing dummy calls does not really work,
> since it is not practically possible to enumerate the symbols needed
> during signal delivery.

> My first attempt to fix this was to increment curthread->critical_count
> around the calls to check_* functions in the _thr_ast(), but it causes
> reverse problem of losing _thr_ast() runs on unlock.

> I ended up with the flag to indicate that deferred delivery is running,
> so check_deferred_signal() should avoid doing anything. A delicate
> moment is that user signal handler is allowed to modify the passed
> machine context to result the return from the signal handler to cause
> arbitrary jump, or just do longjmp(). For this case, I also clear the
> flag in thr_sighandler(), since kernel signal delivery means that nested
> delivery code should not run right now.

This analysis suggests an easier approach: just move the check for
deferred_siginfo.si_signo == 0 downward. If __fillcontextx2 or sysarch
need to be looked up by rtld, the resulting _thr_ast() will invoke the
signal handler and the original call to check_deferred_signal() will do
nothing.

This patch fixes the problem for me on stable/9 and head.

Index: lib/libthr/thread/thr_sig.c
===================================================================
--- lib/libthr/thread/thr_sig.c	(revision 258178)
+++ lib/libthr/thread/thr_sig.c	(working copy)
@@ -326,12 +326,12 @@ check_deferred_signal(struct pthread *curthread)
 	uc_len = __getcontextx_size();
 	uc = alloca(uc_len);
 	getcontext(uc);
-	if (curthread->deferred_siginfo.si_signo == 0)
-		return;
 	__fillcontextx2((char *)uc);
 	act = curthread->deferred_sigact;
 	uc->uc_sigmask = curthread->deferred_sigmask;
 	memcpy(&info, &curthread->deferred_siginfo, sizeof(siginfo_t));
+	if (curthread->deferred_siginfo.si_signo == 0)
+		return;
 	/* remove signal */
 	curthread->deferred_siginfo.si_signo = 0;
 	handle_signal(&act, info.si_signo, &info, uc);

-- 
Jilles Tjoelker


More information about the freebsd-threads mailing list