diff options
author | Oleg Nesterov <oleg@redhat.com> | 2009-04-02 19:58:17 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2009-04-02 22:04:59 -0400 |
commit | 7f5d3652d469cdf9eb2365dfea7ce3fb9e1409cc (patch) | |
tree | 590f845665e166694ba0f9ba0e6d2267e15d8aae /kernel | |
parent | b1442b055c154699a6a2c436f3352f71b6beede3 (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.c | 22 |
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 | ||
813 | static void reparent_thread(struct task_struct *p, struct task_struct *father) | 813 | /* Returns nonzero if the child should be released. */ |
814 | static 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); |