aboutsummaryrefslogtreecommitdiffstats
path: root/kernel
diff options
context:
space:
mode:
authorOleg Nesterov <oleg@redhat.com>2009-04-02 19:58:17 -0400
committerLinus Torvalds <torvalds@linux-foundation.org>2009-04-02 22:04:59 -0400
commit7f5d3652d469cdf9eb2365dfea7ce3fb9e1409cc (patch)
tree590f845665e166694ba0f9ba0e6d2267e15d8aae /kernel
parentb1442b055c154699a6a2c436f3352f71b6beede3 (diff)
reparent_thread: fix a zombie leak if /sbin/init ignores SIGCHLD
If /sbin/init ignores SIGCHLD and we re-parent a zombie, it is leaked. reparent_thread() does do_notify_parent() which sets ->exit_signal = -1 in this case. This means that nobody except us can reap it, the detached task is not visible to do_wait(). Change reparent_thread() to return a boolean (like __pthread_detach) to indicate that the thread is dead and must be released. Also change forget_original_parent() to add the child to ptrace_dead list in this case. The naming becomes insane, the next patch does the cleanup. Signed-off-by: Oleg Nesterov <oleg@redhat.com> Cc: Roland McGrath <roland@redhat.com> Cc: "Eric W. Biederman" <ebiederm@xmission.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'kernel')
-rw-r--r--kernel/exit.c22
1 files changed, 17 insertions, 5 deletions
diff --git a/kernel/exit.c b/kernel/exit.c
index 5be0a406faeb..3e09b7cb3b20 100644
--- a/kernel/exit.c
+++ b/kernel/exit.c
@@ -810,8 +810,11 @@ static void ptrace_exit_finish(struct task_struct *parent,
810 } 810 }
811} 811}
812 812
813static void reparent_thread(struct task_struct *p, struct task_struct *father) 813/* Returns nonzero if the child should be released. */
814static int reparent_thread(struct task_struct *p, struct task_struct *father)
814{ 815{
816 int dead;
817
815 if (p->pdeath_signal) 818 if (p->pdeath_signal)
816 /* We already hold the tasklist_lock here. */ 819 /* We already hold the tasklist_lock here. */
817 group_send_sig_info(p->pdeath_signal, SEND_SIG_NOINFO, p); 820 group_send_sig_info(p->pdeath_signal, SEND_SIG_NOINFO, p);
@@ -819,12 +822,12 @@ static void reparent_thread(struct task_struct *p, struct task_struct *father)
819 list_move_tail(&p->sibling, &p->real_parent->children); 822 list_move_tail(&p->sibling, &p->real_parent->children);
820 823
821 if (task_detached(p)) 824 if (task_detached(p))
822 return; 825 return 0;
823 /* If this is a threaded reparent there is no need to 826 /* If this is a threaded reparent there is no need to
824 * notify anyone anything has happened. 827 * notify anyone anything has happened.
825 */ 828 */
826 if (same_thread_group(p->real_parent, father)) 829 if (same_thread_group(p->real_parent, father))
827 return; 830 return 0;
828 831
829 /* We don't want people slaying init. */ 832 /* We don't want people slaying init. */
830 p->exit_signal = SIGCHLD; 833 p->exit_signal = SIGCHLD;
@@ -832,11 +835,19 @@ static void reparent_thread(struct task_struct *p, struct task_struct *father)
832 /* If we'd notified the old parent about this child's death, 835 /* If we'd notified the old parent about this child's death,
833 * also notify the new parent. 836 * also notify the new parent.
834 */ 837 */
838 dead = 0;
835 if (!p->ptrace && 839 if (!p->ptrace &&
836 p->exit_state == EXIT_ZOMBIE && thread_group_empty(p)) 840 p->exit_state == EXIT_ZOMBIE && thread_group_empty(p)) {
837 do_notify_parent(p, p->exit_signal); 841 do_notify_parent(p, p->exit_signal);
842 if (task_detached(p)) {
843 p->exit_state = EXIT_DEAD;
844 dead = 1;
845 }
846 }
838 847
839 kill_orphaned_pgrp(p, father); 848 kill_orphaned_pgrp(p, father);
849
850 return dead;
840} 851}
841 852
842/* 853/*
@@ -896,7 +907,8 @@ static void forget_original_parent(struct task_struct *father)
896 BUG_ON(p->ptrace); 907 BUG_ON(p->ptrace);
897 p->parent = p->real_parent; 908 p->parent = p->real_parent;
898 } 909 }
899 reparent_thread(p, father); 910 if (reparent_thread(p, father))
911 list_add(&p->ptrace_entry, &ptrace_dead);;
900 } 912 }
901 913
902 write_unlock_irq(&tasklist_lock); 914 write_unlock_irq(&tasklist_lock);