Skip to content

Commit c3ad2c3

Browse files
committed
signal: Don't restart fork when signals come in.
Wen Yang <[email protected]> and majiang <[email protected]> report that a periodic signal received during fork can cause fork to continually restart preventing an application from making progress. The code was being overly pessimistic. Fork needs to guarantee that a signal sent to multiple processes is logically delivered before the fork and just to the forking process or logically delivered after the fork to both the forking process and it's newly spawned child. For signals like periodic timers that are always delivered to a single process fork can safely complete and let them appear to logically delivered after the fork(). While examining this issue I also discovered that fork today will miss signals delivered to multiple processes during the fork and handled by another thread. Similarly the current code will also miss blocked signals that are delivered to multiple process, as those signals will not appear pending during fork. Add a list of each thread that is currently forking, and keep on that list a signal set that records all of the signals sent to multiple processes. When fork completes initialize the new processes shared_pending signal set with it. The calculate_sigpending function will see those signals and set TIF_SIGPENDING causing the new task to take the slow path to userspace to handle those signals. Making it appear as if those signals were received immediately after the fork. It is not possible to send real time signals to multiple processes and exceptions don't go to multiple processes, which means that that are no signals sent to multiple processes that require siginfo. This means it is safe to not bother collecting siginfo on signals sent during fork. The sigaction of a child of fork is initially the same as the sigaction of the parent process. So a signal the parent ignores the child will also initially ignore. Therefore it is safe to ignore signals sent to multiple processes and ignored by the forking process. Signals sent to only a single process or only a single thread and delivered during fork are treated as if they are received after the fork, and generally not dealt with. They won't cause any problems. V2: Added removal from the multiprocess list on failure. V3: Use -ERESTARTNOINTR directly V4: - Don't queue both SIGCONT and SIGSTOP - Initialize signal_struct.multiprocess in init_task - Move setting of shared_pending to before the new task is visible to signals. This prevents signals from comming in before shared_pending.signal is set to delayed.signal and being lost. V5: - rework list add and delete to account for idle threads v6: - Use sigdelsetmask when removing stop signals Bugzilla: https://bugzilla.kernel.org/show_bug.cgi?id=200447 Reported-by: Wen Yang <[email protected]> and Reported-by: majiang <[email protected]> Fixes: 4a2c7a7 ("[PATCH] make fork() atomic wrt pgrp/session signals") Signed-off-by: "Eric W. Biederman" <[email protected]>
1 parent 924de3b commit c3ad2c3

File tree

4 files changed

+49
-18
lines changed

4 files changed

+49
-18
lines changed

include/linux/sched/signal.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,11 @@ struct thread_group_cputimer {
6969
bool checking_timer;
7070
};
7171

72+
struct multiprocess_signals {
73+
sigset_t signal;
74+
struct hlist_node node;
75+
};
76+
7277
/*
7378
* NOTE! "signal_struct" does not have its own
7479
* locking, because a shared signal_struct always
@@ -90,6 +95,9 @@ struct signal_struct {
9095
/* shared signal handling: */
9196
struct sigpending shared_pending;
9297

98+
/* For collecting multiprocess signals during fork */
99+
struct hlist_head multiprocess;
100+
93101
/* thread group exit support */
94102
int group_exit_code;
95103
/* overloaded:

init/init_task.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ static struct signal_struct init_signals = {
2222
.list = LIST_HEAD_INIT(init_signals.shared_pending.list),
2323
.signal = {{0}}
2424
},
25+
.multiprocess = HLIST_HEAD_INIT,
2526
.rlim = INIT_RLIMITS,
2627
.cred_guard_mutex = __MUTEX_INITIALIZER(init_signals.cred_guard_mutex),
2728
#ifdef CONFIG_POSIX_TIMERS

kernel/fork.c

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1456,6 +1456,7 @@ static int copy_signal(unsigned long clone_flags, struct task_struct *tsk)
14561456
init_waitqueue_head(&sig->wait_chldexit);
14571457
sig->curr_target = tsk;
14581458
init_sigpending(&sig->shared_pending);
1459+
INIT_HLIST_HEAD(&sig->multiprocess);
14591460
seqlock_init(&sig->stats_lock);
14601461
prev_cputime_init(&sig->prev_cputime);
14611462

@@ -1602,6 +1603,7 @@ static __latent_entropy struct task_struct *copy_process(
16021603
{
16031604
int retval;
16041605
struct task_struct *p;
1606+
struct multiprocess_signals delayed;
16051607

16061608
/*
16071609
* Don't allow sharing the root directory with processes in a different
@@ -1649,6 +1651,24 @@ static __latent_entropy struct task_struct *copy_process(
16491651
return ERR_PTR(-EINVAL);
16501652
}
16511653

1654+
/*
1655+
* Force any signals received before this point to be delivered
1656+
* before the fork happens. Collect up signals sent to multiple
1657+
* processes that happen during the fork and delay them so that
1658+
* they appear to happen after the fork.
1659+
*/
1660+
sigemptyset(&delayed.signal);
1661+
INIT_HLIST_NODE(&delayed.node);
1662+
1663+
spin_lock_irq(&current->sighand->siglock);
1664+
if (!(clone_flags & CLONE_THREAD))
1665+
hlist_add_head(&delayed.node, &current->signal->multiprocess);
1666+
recalc_sigpending();
1667+
spin_unlock_irq(&current->sighand->siglock);
1668+
retval = -ERESTARTNOINTR;
1669+
if (signal_pending(current))
1670+
goto fork_out;
1671+
16521672
retval = -ENOMEM;
16531673
p = dup_task_struct(current, node);
16541674
if (!p)
@@ -1934,22 +1954,6 @@ static __latent_entropy struct task_struct *copy_process(
19341954
goto bad_fork_cancel_cgroup;
19351955
}
19361956

1937-
if (!(clone_flags & CLONE_THREAD)) {
1938-
/*
1939-
* Process group and session signals need to be delivered to just the
1940-
* parent before the fork or both the parent and the child after the
1941-
* fork. Restart if a signal comes in before we add the new process to
1942-
* it's process group.
1943-
* A fatal signal pending means that current will exit, so the new
1944-
* thread can't slip out of an OOM kill (or normal SIGKILL).
1945-
*/
1946-
recalc_sigpending();
1947-
if (signal_pending(current)) {
1948-
retval = -ERESTARTNOINTR;
1949-
goto bad_fork_cancel_cgroup;
1950-
}
1951-
}
1952-
19531957

19541958
init_task_pid_links(p);
19551959
if (likely(p->pid)) {
@@ -1965,7 +1969,7 @@ static __latent_entropy struct task_struct *copy_process(
19651969
ns_of_pid(pid)->child_reaper = p;
19661970
p->signal->flags |= SIGNAL_UNKILLABLE;
19671971
}
1968-
1972+
p->signal->shared_pending.signal = delayed.signal;
19691973
p->signal->tty = tty_kref_get(current->signal->tty);
19701974
/*
19711975
* Inherit has_child_subreaper flag under the same
@@ -1993,8 +1997,8 @@ static __latent_entropy struct task_struct *copy_process(
19931997
attach_pid(p, PIDTYPE_PID);
19941998
nr_threads++;
19951999
}
1996-
19972000
total_forks++;
2001+
hlist_del_init(&delayed.node);
19982002
spin_unlock(&current->sighand->siglock);
19992003
syscall_tracepoint_update(p);
20002004
write_unlock_irq(&tasklist_lock);
@@ -2059,6 +2063,9 @@ static __latent_entropy struct task_struct *copy_process(
20592063
put_task_stack(p);
20602064
free_task(p);
20612065
fork_out:
2066+
spin_lock_irq(&current->sighand->siglock);
2067+
hlist_del_init(&delayed.node);
2068+
spin_unlock_irq(&current->sighand->siglock);
20622069
return ERR_PTR(retval);
20632070
}
20642071

kernel/signal.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1121,6 +1121,21 @@ static int __send_signal(int sig, struct siginfo *info, struct task_struct *t,
11211121
out_set:
11221122
signalfd_notify(t, sig);
11231123
sigaddset(&pending->signal, sig);
1124+
1125+
/* Let multiprocess signals appear after on-going forks */
1126+
if (type > PIDTYPE_TGID) {
1127+
struct multiprocess_signals *delayed;
1128+
hlist_for_each_entry(delayed, &t->signal->multiprocess, node) {
1129+
sigset_t *signal = &delayed->signal;
1130+
/* Can't queue both a stop and a continue signal */
1131+
if (sig == SIGCONT)
1132+
sigdelsetmask(signal, SIG_KERNEL_STOP_MASK);
1133+
else if (sig_kernel_stop(sig))
1134+
sigdelset(signal, SIGCONT);
1135+
sigaddset(signal, sig);
1136+
}
1137+
}
1138+
11241139
complete_signal(sig, t, type);
11251140
ret:
11261141
trace_signal_generate(sig, info, t, type != PIDTYPE_PID, result);

0 commit comments

Comments
 (0)