> Two threads T1 and T2, and some process X (not the child of T1/T2). > > T1 ptraces X. > > X exits and becomes zombie. > > T2 calls do_wait(WEXITED) and then wait_task_zombie(), it sets EXIT_DEAD > and drops tasklist. > > T1 exits, calls exit_ptrace(). __ptrace_detach() does __ptrace_unlink() > and nothing more. From now X->ptrace == 0. > > X->real_parent calls do_wait() and gets -ECHLD, because X is EXIT_DEAD > and not traced.
I see. I don't think we should worry especially about fixing this existing bug (ancient, as you call it) on its own. So let's only consider this in the context of the new locking and data structure plans, if that is simpler. > Of course, we can add the really nasty hacks to __ptrace_unlink() path, > but I think we should never set EXIT_DEAD unless we know for sure the > child will be released. I take your point. But I think we should try very hard to find solutions covering all the ptrace problems without taking write_lock_irq (before release_task will take it). > Minor, but note the comment in find_new_reaper() about EXIT_DEAD tasks. > I thought I can give more arguments against EXIT_DEAD task on ->children, > but either I forgot, or they don't really exist ;) I am convinced that "unless we know for sure the child will be released" is a nice invariant to have if we can get it. That is, EXIT_DEAD can be on ->children but always means "you want to ignore it". The ptrace not-really-reaping case is the only time that rule is ever violated, right? So, we can just try to close that hole. The stronger "no EXIT_DEAD on ->children" invariant means that the non-ptrace do_wait() has to use a different method than the current xchg() to settle races. I don't see any good reason to perturb that. (At least, let it be a later concern far removed from ptrace work.) So, how can we do the ptrace_reparented() case better in wait_task_zombie? If we just don't touch exit_state, then we don't violate the invariant. Then all we need is a new way to settle the races between ptrace_do_wait() calls, right? We can drop tasklist_lock (maybe need get_task_struct?), take ptrace_mutex, do ptrace detaching/clear ->ptrace, drop ptrace_mutex. Then re-take tasklist_lock (read_lock) for do_notify_parent and do the auto-reap handling. (There's no reason I see that this juggling shouldn't happen right after dropping tasklist_lock, before getrusage/put_user, only release_task has to be after.) Something along these lines feels like the right direction. Thanks, Roland