• Tejun Heo's avatar
    ptrace: Clean transitions between TASK_STOPPED and TRACED · d79fdd6d
    Tejun Heo authored
    Currently, if the task is STOPPED on ptrace attach, it's left alone
    and the state is silently changed to TRACED on the next ptrace call.
    The behavior breaks the assumption that arch_ptrace_stop() is called
    before any task is poked by ptrace and is ugly in that a task
    manipulates the state of another task directly.
    
    With GROUP_STOP_PENDING, the transitions between TASK_STOPPED and
    TRACED can be made clean.  The tracer can use the flag to tell the
    tracee to retry stop on attach and detach.  On retry, the tracee will
    enter the desired state in the correct way.  The lower 16bits of
    task->group_stop is used to remember the signal number which caused
    the last group stop.  This is used while retrying for ptrace attach as
    the original group_exit_code could have been consumed with wait(2) by
    then.
    
    As the real parent may wait(2) and consume the group_exit_code
    anytime, the group_exit_code needs to be saved separately so that it
    can be used when switching from regular sleep to ptrace_stop().  This
    is recorded in the lower 16bits of task->group_stop.
    
    If a task is already stopped and there's no intervening SIGCONT, a
    ptrace request immediately following a successful PTRACE_ATTACH should
    always succeed even if the tracer doesn't wait(2) for attach
    completion; however, with this change, the tracee might still be
    TASK_RUNNING trying to enter TASK_TRACED which would cause the
    following request to fail with -ESRCH.
    
    This intermediate state is hidden from the ptracer by setting
    GROUP_STOP_TRAPPING on attach and making ptrace_check_attach() wait
    for it to clear on its signal->wait_chldexit.  Completing the
    transition or getting killed clears TRAPPING and wakes up the tracer.
    
    Note that the STOPPED -> RUNNING -> TRACED transition is still visible
    to other threads which are in the same group as the ptracer and the
    reverse transition is visible to all.  Please read the comments for
    details.
    
    Oleg:
    
    * Spotted a race condition where a task may retry group stop without
      proper bookkeeping.  Fixed by redoing bookkeeping on retry.
    
    * Spotted that the transition is visible to userland in several
      different ways.  Most are fixed with GROUP_STOP_TRAPPING.  Unhandled
      corner case is documented.
    
    * Pointed out not setting GROUP_STOP_SIGMASK on an already stopped
      task would result in more consistent behavior.
    
    * Pointed out that calling ptrace_stop() from do_signal_stop() in
      TASK_STOPPED can race with group stop start logic and then confuse
      the TRAPPING wait in ptrace_check_attach().  ptrace_stop() is now
      called with TASK_RUNNING.
    
    * Suggested using signal->wait_chldexit instead of bit wait.
    
    * Spotted a race condition between TRACED transition and clearing of
      TRAPPING.
    Signed-off-by: default avatarTejun Heo <tj@kernel.org>
    Acked-by: default avatarOleg Nesterov <oleg@redhat.com>
    Cc: Roland McGrath <roland@redhat.com>
    Cc: Jan Kratochvil <jan.kratochvil@redhat.com>
    d79fdd6d
ptrace.c 21.7 KB