Skip to content

Commit e0f6d38

Browse files
committed
Fix pcntl_signal_dispatch() undoing the signal mask changes of its handlers
The dispatch blocked every signal while its handlers ran and then restored the mask it found. A handler's pcntl_sigprocmask() was undone, and so was an unblock done by pcntl_signal() (zend_sigaction()) or by an extension a handler calls into, after which nothing unblocked that signal again. A signal such an unblock let in during the handlers was lost: the end of the dispatch cleared pending_signals, or overwrote the queue's head after a throwing handler, and the engine interrupt it raised was spent on a nested dispatch that returned at once. Each handler now runs under the thread's own mask, and signals are blocked only while the queue changes. A signal that arrives meanwhile stays queued, after what a throwing handler left, and with async signals the engine is asked to come back for it. A block an extension takes in a handler on a signal that was not blocked before the dispatch now stays too.
1 parent 357fcf1 commit e0f6d38

5 files changed

Lines changed: 175 additions & 8 deletions

‎ext/pcntl/pcntl.c‎

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1326,6 +1326,7 @@ void pcntl_signal_dispatch(void)
13261326
const zend_op *old_opline = NULL;
13271327
sigset_t mask;
13281328
sigset_t old_mask;
1329+
sigset_t handler_mask;
13291330

13301331
if(!PCNTL_G(pending_signals)) {
13311332
return;
@@ -1351,6 +1352,10 @@ void pcntl_signal_dispatch(void)
13511352
PCNTL_G(head) = NULL; /* simple stores are atomic */
13521353
PCNTL_G(tail) = NULL;
13531354

1355+
/* Handlers run under the thread's own mask, and the dispatch ends with the mask they left; what
1356+
* arrives meanwhile is queued for the next dispatch. */
1357+
handler_mask = old_mask;
1358+
13541359
/* Dispatching can happen with an exception pending, e.g. from the interrupt check that runs
13551360
* right after an internal function threw. call_user_function() does nothing in that state,
13561361
* so set the exception aside while the handlers run. The frame is left as found: depending
@@ -1382,7 +1387,9 @@ void pcntl_signal_dispatch(void)
13821387

13831388
/* Call php signal handler - Note that we do not report errors, and we ignore the return value */
13841389
/* FIXME: this is probably broken when multiple signals are handled in this while loop (retval) */
1390+
sigprocmask(SIG_SETMASK, &handler_mask, NULL);
13851391
call_user_function(NULL, NULL, handle, &retval, 2, params);
1392+
sigprocmask(SIG_BLOCK, &mask, &handler_mask);
13861393
zval_ptr_dtor(&retval);
13871394
#ifdef HAVE_STRUCT_SIGINFO_T
13881395
zval_ptr_dtor(&params[1]);
@@ -1417,21 +1424,26 @@ void pcntl_signal_dispatch(void)
14171424
if (UNEXPECTED(queue)) {
14181425
/* Put back what the throwing handler did not get to, instead of dropping it, and ask
14191426
* the engine to come back once the exception has been handled. Signals are still
1420-
* blocked here, so PCNTL_G(head) cannot have been repopulated in the meantime. */
1427+
* blocked here, but PCNTL_G(head) may hold what arrived while the handlers ran: it goes
1428+
* after these. */
14211429
next = queue;
14221430

14231431
while (next->next) {
14241432
next = next->next;
14251433
}
14261434

1435+
next->next = PCNTL_G(head);
1436+
if (!PCNTL_G(head)) {
1437+
PCNTL_G(tail) = next;
1438+
}
14271439
PCNTL_G(head) = queue;
1428-
PCNTL_G(tail) = next;
1440+
}
14291441

1430-
if (PCNTL_G(async_signals)) {
1431-
zend_atomic_bool_store_ex(&EG(vm_interrupt), true);
1432-
}
1433-
} else {
1434-
PCNTL_G(pending_signals) = 0;
1442+
/* What arrived while the handlers ran is still queued, and a nested dispatch may have spent its
1443+
* interrupt. */
1444+
PCNTL_G(pending_signals) = PCNTL_G(head) != NULL;
1445+
if (PCNTL_G(head) && PCNTL_G(async_signals)) {
1446+
zend_atomic_bool_store_ex(&EG(vm_interrupt), true);
14351447
}
14361448

14371449
/* Re-enable queue */
@@ -1441,7 +1453,7 @@ void pcntl_signal_dispatch(void)
14411453
zend_fiber_switch_unblock();
14421454

14431455
/* return signal mask to previous state */
1444-
sigprocmask(SIG_SETMASK, &old_mask, NULL);
1456+
sigprocmask(SIG_SETMASK, &handler_mask, NULL);
14451457
}
14461458

14471459
static void pcntl_signal_dispatch_tick_function(int dummy_int, void *dummy_pointer)
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
--TEST--
2+
pcntl_async_signals(): a signal that arrives while a handler runs is dispatched after it
3+
--EXTENSIONS--
4+
pcntl
5+
posix
6+
--FILE--
7+
<?php
8+
9+
pcntl_async_signals(true);
10+
11+
pcntl_signal(SIGHUP, function () {
12+
echo "HUP\n";
13+
});
14+
pcntl_sigprocmask(SIG_BLOCK, [SIGHUP]);
15+
posix_kill(posix_getpid(), SIGHUP);
16+
17+
pcntl_signal(SIGUSR1, function () {
18+
echo "USR1\n";
19+
pcntl_sigprocmask(SIG_UNBLOCK, [SIGHUP]);
20+
echo "Unblocked\n";
21+
});
22+
23+
posix_kill(posix_getpid(), SIGUSR1);
24+
// A check point for the interrupt the dispatch raised again: the return of an internal call.
25+
posix_getpid();
26+
echo "Done\n";
27+
28+
?>
29+
--EXPECT--
30+
USR1
31+
Unblocked
32+
HUP
33+
Done
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
--TEST--
2+
pcntl_signal_dispatch() keeps a signal that arrives in a throwing handler after the ones that handler left
3+
--EXTENSIONS--
4+
pcntl
5+
posix
6+
--FILE--
7+
<?php
8+
9+
pcntl_signal(SIGHUP, function () {
10+
echo "HUP\n";
11+
});
12+
pcntl_sigprocmask(SIG_BLOCK, [SIGHUP]);
13+
posix_kill(posix_getpid(), SIGHUP);
14+
15+
pcntl_signal(SIGUSR1, function () {
16+
pcntl_sigprocmask(SIG_UNBLOCK, [SIGHUP]);
17+
throw new Exception("USR1");
18+
});
19+
pcntl_signal(SIGUSR2, function () {
20+
echo "USR2\n";
21+
});
22+
23+
posix_kill(posix_getpid(), SIGUSR1);
24+
posix_kill(posix_getpid(), SIGUSR2);
25+
26+
try {
27+
pcntl_signal_dispatch();
28+
} catch (Exception $e) {
29+
echo $e->getMessage(), "\n";
30+
}
31+
posix_kill(posix_getpid(), SIGUSR2);
32+
pcntl_signal_dispatch();
33+
34+
?>
35+
--EXPECT--
36+
USR1
37+
USR2
38+
HUP
39+
USR2
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
--TEST--
2+
pcntl_signal_dispatch() runs its handlers under the thread's mask and keeps the changes they made
3+
--EXTENSIONS--
4+
pcntl
5+
posix
6+
--FILE--
7+
<?php
8+
9+
function blocked(): array {
10+
// SIGCHLD, unused here: the call refuses an empty list.
11+
pcntl_sigprocmask(SIG_BLOCK, [SIGCHLD], $mask);
12+
pcntl_sigprocmask(SIG_SETMASK, $mask);
13+
return $mask;
14+
}
15+
16+
function is_blocked(int $signo): bool {
17+
return in_array($signo, blocked(), true);
18+
}
19+
20+
pcntl_sigprocmask(SIG_BLOCK, [SIGHUP, SIGALRM]);
21+
22+
pcntl_signal(SIGUSR1, function () {
23+
pcntl_sigprocmask(SIG_BLOCK, [SIGUSR2]);
24+
pcntl_sigprocmask(SIG_UNBLOCK, [SIGHUP]);
25+
});
26+
27+
posix_kill(posix_getpid(), SIGUSR1);
28+
pcntl_signal_dispatch();
29+
30+
var_dump(is_blocked(SIGUSR2));
31+
var_dump(is_blocked(SIGHUP));
32+
var_dump(is_blocked(SIGALRM));
33+
34+
echo "Saved and restored in a handler\n";
35+
36+
$before = blocked();
37+
38+
pcntl_signal(SIGUSR1, function () {
39+
pcntl_sigprocmask(SIG_BLOCK, [SIGTERM], $old);
40+
pcntl_sigprocmask(SIG_SETMASK, $old);
41+
});
42+
43+
posix_kill(posix_getpid(), SIGUSR1);
44+
pcntl_signal_dispatch();
45+
46+
var_dump(blocked() === $before);
47+
48+
?>
49+
--EXPECT--
50+
bool(true)
51+
bool(false)
52+
bool(true)
53+
Saved and restored in a handler
54+
bool(true)
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
--TEST--
2+
pcntl_signal_dispatch() keeps a signal that arrives while its handlers run
3+
--EXTENSIONS--
4+
pcntl
5+
posix
6+
--FILE--
7+
<?php
8+
9+
pcntl_signal(SIGHUP, function () {
10+
echo "HUP\n";
11+
});
12+
pcntl_sigprocmask(SIG_BLOCK, [SIGHUP]);
13+
posix_kill(posix_getpid(), SIGHUP);
14+
15+
pcntl_signal(SIGUSR1, function () {
16+
echo "USR1\n";
17+
pcntl_sigprocmask(SIG_UNBLOCK, [SIGHUP]);
18+
});
19+
20+
posix_kill(posix_getpid(), SIGUSR1);
21+
pcntl_signal_dispatch();
22+
echo "Dispatched\n";
23+
pcntl_signal_dispatch();
24+
25+
?>
26+
--EXPECT--
27+
USR1
28+
Dispatched
29+
HUP

0 commit comments

Comments
 (0)