Subject: [PATCH] Fixup pspace termination From: Eric W. Biederman Date: 1130705666 -0700 - Fix __kill_pspace_info to properly filter out the process sending the signal - When a pspace exits give make the topmost init their parent but also set exit_signal = -1 so no one else sees the other processes exiting. This allows the pspace leader to just go ahead and exit, like any other process. - Moved the killing of other processes in the pspace as early as possible in do_exit. - Added a PSPACE_EXIT flag so I can tell when a pspace is dying this should prevent races with fork. Although my setting and clearing of PSPACE_EXIT may still be racy. - Removed delay_pspace_leader the concept is totally bogus now. --- include/linux/pspace.h | 12 +++++------ kernel/exit.c | 54 +++++++++++++++++++----------------------------- kernel/fork.c | 8 +++++++ kernel/signal.c | 4 ++-- 4 files changed, 37 insertions(+), 41 deletions(-) 4b18d4c2a00d24afbed0e3b6d85448c688e3423c diff --git a/include/linux/pspace.h b/include/linux/pspace.h index 5283aaa..21d3a40 100644 --- a/include/linux/pspace.h +++ b/include/linux/pspace.h @@ -15,6 +15,8 @@ struct pidmap struct pspace { atomic_t count; + unsigned int flags; +#define PSPACE_EXIT 0x00000001 /* pspace exit in progress */ struct pspace *parent; struct task_struct *child_reaper; int nr_threads; @@ -62,8 +64,11 @@ extern int copy_pspace(int flags, struct static inline void exit_pspace(struct task_struct *tsk) { - put_pspace(tsk->pspace); + struct pspace *pspace = tsk->pspace; tsk->pspace = NULL; + if (pspace->child_reaper == tsk) + pspace->child_reaper = NULL; + put_pspace(pspace); } static inline int pspace_leader(struct task_struct *tsk) @@ -85,9 +90,4 @@ static inline int in_pspace(struct pspac return test == pspace; } -static inline int delay_pspace_leader(struct task_struct *tsk) -{ - return pspace_leader(tsk) && (tsk->pspace->nr_processes > 1); -} - #endif /* _LINUX_PSPACE_H */ diff --git a/kernel/exit.c b/kernel/exit.c index 10570c7..09cf567 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -541,6 +541,12 @@ static inline void choose_new_parent(tas * the parent is not a zombie. */ BUG_ON(p == reaper || reaper->exit_state >= EXIT_ZOMBIE); + /* If the pspaces of our parents differ don't become a zombie + * or allow ourselves to be waited on, effecitvely this means + * the process just disappears. + */ + if (p->real_parent->pspace != reaper->pspace) + p->exit_signal = -1; p->real_parent = reaper; } @@ -621,7 +627,10 @@ static inline void forget_original_paren do { reaper = next_thread(reaper); if (reaper == father) { - reaper = father->pspace->child_reaper; + if (!(father->pspace->flags & PSPACE_EXIT)) + reaper = father->pspace->child_reaper; + else + reaper = init_pspace.child_reaper; break; } } while (reaper->exit_state); @@ -717,7 +726,7 @@ static void exit_notify(struct task_stru INIT_LIST_HEAD(&ptrace_dead); forget_original_parent(tsk, &ptrace_dead); - BUG_ON(!pspace_leader(tsk) && !list_empty(&tsk->children)); + BUG_ON(!list_empty(&tsk->children)); BUG_ON(!list_empty(&tsk->ptrace_children)); /* @@ -740,29 +749,6 @@ static void exit_notify(struct task_stru __kill_pg_info(SIGCONT, (void *)1, tsk->pspace, process_group(tsk)); } - - /* - * Check to see if we are the pspace leader. - * If so it is nonsense for the pspace to continue so set - * the SIGCHLD handler to SIG_IGN and kill everyone - * else in the pspace. - */ - if (pspace_leader(tsk)) { - DECLARE_WAITQUEUE(wait, current); - tsk->sighand->action[SIGCHLD-1].sa.sa_handler = SIG_IGN; - __kill_pspace_info(SIGKILL, (void *)1, tsk->pspace); - /* FIXME how does this work for signal/thread groups? */ - add_wait_queue(&tsk->signal->wait_chldexit, &wait); - while(!list_empty(&tsk->children)) { - write_unlock_irq(&tasklist_lock); - current->state = TASK_INTERRUPTIBLE; - schedule(); - write_lock_irq(&tasklist_lock); - } - current->state = TASK_RUNNING; - remove_wait_queue(&tsk->signal->wait_chldexit, &wait); - } - /* Let father know we died * * Thread signals are configurable, but you aren't going to use @@ -836,6 +822,16 @@ fastcall NORET_TYPE void do_exit(long co panic("Attempted to kill the idle task!"); if (unlikely((tsk->tid == 1) && (tsk->pspace == &init_pspace))) panic("Attempted to kill init!"); + + /* + * If we are the pspace leader it is nonsense for the pspace + * to continue so kill everyone else in the pspace. + */ + if (pspace_leader(tsk)) { + tsk->pspace->flags = PSPACE_EXIT; + kill_pspace_info(SIGKILL, (void *)1, tsk->pspace); + } + if (tsk->io_context) exit_io_context(); @@ -1009,14 +1005,6 @@ static int eligible_child(pid_t pid, int if (current->tgid != p->tgid && delay_group_leader(p)) return 2; - - /* - * Do not consider pspace leaders that are in - * a non-empty pspace. - */ - if (delay_pspace_leader(p)) - return 2; - if (security_task_wait(p)) return 0; diff --git a/kernel/fork.c b/kernel/fork.c index ee9cc65..93957a3 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -895,6 +895,14 @@ static task_t *copy_process(unsigned lon if ((clone_flags & CLONE_SIGHAND) && !(clone_flags & CLONE_VM)) return ERR_PTR(-EINVAL); + /* + * Important: If an exit-all has been started then + * do not create this new process - the whole pspace + * supposed to exit. + */ + if (current->pspace->flags & PSPACE_EXIT) + return ERR_PTR(-EAGAIN); + retval = security_task_create(clone_flags); if (retval) goto fork_out; diff --git a/kernel/signal.c b/kernel/signal.c index 101d627..00d450e 100644 --- a/kernel/signal.c +++ b/kernel/signal.c @@ -1163,13 +1163,13 @@ int __kill_pspace_info(int sig, struct s continue; /* Skip the sender of the signal */ - if (p->tgid == current->tgid) + if (p->signal == current->signal) continue; /* Skip processes outside the target process space */ if (!in_pspace(pspace, p)) continue; - + /* Finally it is a good process send the signal. */ err = group_send_sig_info(sig, info, p); ++count; -- 1.0.GIT