diff -urN samba-3.4.0.orig/lib/tevent/tevent_signal.c samba-3.4.0/lib/tevent/tevent_signal.c --- samba-3.4.0.orig/lib/tevent/tevent_signal.c 2009-07-03 04:21:14.000000000 -0700 +++ samba-3.4.0/lib/tevent/tevent_signal.c 2009-08-11 20:50:35.000000000 -0700 @@ -32,8 +32,8 @@ #define NUM_SIGNALS 64 -/* maximum number of SA_SIGINFO signals to hold in the queue */ -#define SA_INFO_QUEUE_COUNT 100 +/* maximum number of SA_SIGINFO signals to hold in the queue. Must be power of two. */ +#define SA_INFO_QUEUE_COUNT 64 struct sigcounter { uint32_t count; @@ -44,6 +44,9 @@ #define SIG_SEEN(s, n) (s).seen += (n) #define SIG_PENDING(s) ((s).seen != (s).count) +#define SIG_QUEUE_IDX(sig) (sig_state->signal_count[(sig)].count % SA_INFO_QUEUE_COUNT) +#define SIG_DEQUEUE_IDX(sig, idx) ((sig_state->signal_count[(sig)].seen + (idx)) % SA_INFO_QUEUE_COUNT) + struct tevent_common_signal_list { struct tevent_common_signal_list *prev, *next; struct tevent_signal *se; @@ -70,10 +73,7 @@ */ static uint32_t sig_count(struct sigcounter s) { - if (s.count >= s.seen) { - return s.count - s.seen; - } - return 1 + (0xFFFFFFFF & ~(s.seen - s.count)); + return s.count - s.seen; } /* @@ -97,7 +97,7 @@ void *uctx) { uint32_t count = sig_count(sig_state->signal_count[signum]); - sig_state->sig_info[signum][count] = *info; + sig_state->sig_info[signum][SIG_QUEUE_IDX(signum)] = *info; tevent_common_signal_handler(signum); @@ -307,21 +307,11 @@ for (j=0;jhandler(ev, se, i, 1, - (void*)&sig_state->sig_info[i][ofs], + (void*)&sig_state->sig_info[i][ofs], se->private_data); } - if (SIG_PENDING(sig_state->sig_blocked[i])) { - /* we'd filled the queue, unblock the - signal now */ - sigset_t set; - sigemptyset(&set); - sigaddset(&set, i); - SIG_SEEN(sig_state->sig_blocked[i], - sig_count(sig_state->sig_blocked[i])); - sigprocmask(SIG_UNBLOCK, &set, NULL); - } if (se->sa_flags & SA_RESETHAND) { talloc_free(se); } @@ -335,6 +325,35 @@ } SIG_SEEN(sig_state->signal_count[i], count); SIG_SEEN(sig_state->got_signal, count); +#ifdef SA_SIGINFO + /* It does not matter whether we first look at pending, and then + increment seen, or vice versa, there will be always race. + + If we first unblock signal, and then increment seen, it can + happen that hundreds of signals will get delivered immediately + after unblocking - and queue will overflow, as end condition + checks for count+1==MAX, and we are already beyond that point. + + If instead we first increment seen, and then do unblocking, it is + instead possible that (at least two) signals which got us into + blocked state were just delivered after incrementing seen. In + that case we'll errorneously enable signal, although queue is + full, again potentially overflowing queue. + + As I can trigger first crash by sitting in gdb for long time, + while I cannot easily trigger second, let's first increment + seen, and then look at pending. */ + if (SIG_PENDING(sig_state->sig_blocked[i])) { + /* we'd filled the queue, unblock the + signal now */ + sigset_t set; + sigemptyset(&set); + sigaddset(&set, i); + SIG_SEEN(sig_state->sig_blocked[i], + sig_count(sig_state->sig_blocked[i])); + sigprocmask(SIG_UNBLOCK, &set, NULL); + } +#endif } return 1;