PTRACE_SEIZE && fork() fixes
Subsystems (4)
Oleg Nesterov
maintainercover PTRACE_SEIZE && fork() fixes
Quoted 3 lines · Oleg Nesterov
Just to remind,
tracehook_report_clone()shouldn't send SIGSTOP if the auto-attached tracee isPT_SEIZED.
I think it is the time to do this, everything else looks consistent.
The patches are simple but 3/3 needs the discussion and acks, this API is new.
Oleg.
Oleg Nesterov
maintainerPATCH 1/3 ptrace_init_task: initialize child->jobctl explicitly
new_child->jobctl is not initialized during the fork, it is copied from parent->jobctl. Currently this is harmless, the forking task is running and copy_process() can't succeed if signal_pending() is true, so only JOBCTL_STOP_DEQUEUED can be copied. Still this is a bit fragile, it would be more clean to set ->jobctl = 0 explicitly.
Also, check ->ptrace != 0 instead of PT_PTRACED, move the CONFIG_HAVE_HW_BREAKPOINT code up.
---
include/linux/ptrace.h | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-)
include/linux/ptrace.h renamed from include/linux/ptrace.h~3_init_jobctl +7 -6
@@ -217,16 +217,17 @@ static inline void ptrace_init_task(stru {INIT_LIST_HEAD (&child->ptrace_entry);INIT_LIST_HEAD (&child->ptraced); - child->parent = child->real_parent; +#ifdef CONFIG_HAVE_HW_BREAKPOINT +atomic_set (&child->ptrace_bp_refcnt,1 ); +#endif + child->jobctl =0 ; child->ptrace =0 ; -if (unlikely (ptrace) && (current->ptrace &PT_PTRACED )) { + child->parent = child->real_parent; + +if (unlikely (ptrace) && current->ptrace) { child->ptrace = current->ptrace;__ptrace_link (child, current->parent); } - -#ifdef CONFIG_HAVE_HW_BREAKPOINT -atomic_set (&child->ptrace_bp_refcnt,1 ); -#endif }/**
Re: [PATCH 1/3] ptrace_init_task: initialize child->jobctl explicitly
Quoted 10 lines · Oleg Nesterov
new_child->jobctlis not initialized during the fork, it is copied fromparent->jobctl. Currently this is harmless, the forking task is running andcopy_process()can't succeed ifsignal_pending()is true, so onlyJOBCTL_STOP_DEQUEUEDcan be copied. Still this is a bit fragile, it would be more clean to set ->jobctl = 0 explicitly.Also, check ->ptrace != 0 instead of
PT_PTRACED, move theCONFIG_HAVE_HW_BREAKPOINTcode up.Signed-off-by Oleg Nesterov <[email protected]>
Thanks.
Signature · 1 line
tejun
Oleg Nesterov
maintainerPATCH 2/3 ptrace: mv send-SIGSTOP from do_fork() to ptrace_init_task()
In reply to Oleg NesterovIf the new child is traced, do_fork() adds the pending SIGSTOP. It assumes that either it is traced because of auto-attach or the tracer attached later, in both cases sigaddset/set_thread_flag is correct even if SIGSTOP is already pending.
Now that we have PTRACE_SEIZE this is no longer right in the latter case. If the tracer does PTRACE_SEIZE after copy_process() makes the child visible the queued SIGSTOP is wrong.
We could check PT_SEIZED bit and change ptrace_attach() to set both PT_PTRACED and PT_SEIZED bits simultaneously but see the next patch, we need to know whether this child was auto-attached or not anyway.
So this patch simply moves this code to ptrace_init_task(), this way we can never race with ptrace_attach().
---
include/linux/ptrace.h | 3 +++ kernel/fork.c | 12 ------------ 2 files changed, 3 insertions(+), 12 deletions(-)
include/linux/ptrace.h renamed from include/linux/ptrace.h~4_send_stop_from_ptrace_init +3 -0
@@ -227,6 +227,9 @@ static inline void ptrace_init_task(struif (unlikely (ptrace) && current->ptrace) { child->ptrace = current->ptrace;__ptrace_link (child, current->parent); + +sigaddset (&child->pending.signal,SIGSTOP ); +set_tsk_thread_flag (child,TIF_SIGPENDING ); } }
kernel/fork.c renamed from kernel/fork.c~4_send_stop_from_ptrace_init +0 -12
@@ -37,7 +37,6 @@#include <linux/swap.h> #include <linux/syscalls.h> #include <linux/jiffies.h> -#include <linux/tracehook.h> #include <linux/futex.h> #include <linux/compat.h> #include <linux/kthread.h>
@@ -1522,17 +1521,6 @@ long do_fork(unsigned long clone_flags,audit_finish_fork (p);/* -* Child is ready but hasn't started running yet. Queue -* SIGSTOP if it's gonna be ptraced - it doesn't matter who -* attached/attaching to this task, the pending SIGSTOP is -* right in any case. -*/ -if (unlikely (p->ptrace)) { -sigaddset (&p->pending.signal,SIGSTOP ); -set_tsk_thread_flag (p,TIF_SIGPENDING ); - } - -/* * We set PF_STARTING at creation in case tracing wants to * use this to distinguish a fully live task from one that * hasn't finished SIGSTOP raising yet. Now we clear it
Oleg Nesterov
maintainerRFC PATCH 3/3 ptrace: dont send SIGSTOP on auto-attach if PT_SEIZED
In reply to Oleg NesterovThe fake SIGSTOP during attach has numerous problems. PTRACE_SEIZE is already fine, but we have basically the same problems is SIGSTOP is sent on auto-attach, the tracer can't know if this signal signal should be cancelled or not.
Change ptrace_event() to set JOBCTL_TRAP_STOP if the new child is PT_SEIZED, this triggers the PTRACE_EVENT_STOP report.
Thereafter a PT_SEIZED task can never report the bogus SIGSTOP.
Test-case:
#define PTRACE_SEIZE 0x4206
#define PTRACE_SEIZE_DEVEL 0x80000000
#define PTRACE_EVENT_STOP 7
#define WEVENT(s) ((s & 0xFF0000) >> 16)
int main(void)
{
int child, grand_child, status;
long message;
child = fork();
if (!child) {
kill(getpid(), SIGSTOP);
fork();
assert(0);
return 0x23;
}
assert(ptrace(PTRACE_SEIZE, child, 0,PTRACE_SEIZE_DEVEL) == 0);
assert(wait(&status) == child);
assert(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP);
assert(ptrace(PTRACE_SETOPTIONS, child, 0, PTRACE_O_TRACEFORK) == 0);
assert(ptrace(PTRACE_CONT, child, 0,0) == 0);
assert(waitpid(child, &status, 0) == child);
assert(WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP);
assert(WEVENT(status) == PTRACE_EVENT_FORK);
assert(ptrace(PTRACE_GETEVENTMSG, child, 0, &message) == 0);
grand_child = message;
assert(waitpid(grand_child, &status, 0) == grand_child);
assert(WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP);
assert(WEVENT(status) == PTRACE_EVENT_STOP);
kill(child, SIGKILL);
kill(grand_child, SIGKILL);
return 0;
}---
include/linux/ptrace.h | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-)
include/linux/ptrace.h renamed from include/linux/ptrace.h~5_clone_no_stop_if_seized +5 -1
@@ -228,7 +228,11 @@ static inline void ptrace_init_task(stru child->ptrace = current->ptrace;__ptrace_link (child, current->parent); -sigaddset (&child->pending.signal,SIGSTOP ); +if (child->ptrace &PT_SEIZED ) +task_set_jobctl_pending (child,JOBCTL_TRAP_STOP ); +else +sigaddset (&child->pending.signal,SIGSTOP ); +set_tsk_thread_flag (child,TIF_SIGPENDING ); } }
Re: [RFC PATCH 3/3] ptrace: dont send SIGSTOP on auto-attach if PT_SEIZED
Hello,
Quoted 11 lines · Oleg Nesterov
The fake SIGSTOP during attach has numerous problems.
PTRACE_SEIZEis already fine, but we have basically the same problems is SIGSTOP is sent on auto-attach, the tracer can't know if this signal signal should be cancelled or not.Change
ptrace_event()to setJOBCTL_TRAP_STOPif the new child isPT_SEIZED, this triggers thePTRACE_EVENT_STOPreport.Thereafter a
PT_SEIZEDtask can never report the bogus SIGSTOP.Test-case:
...
Quoted 1 line
Signed-off-by Oleg Nesterov <[email protected]>
Looks good to me. For 2 and 3,
So, yeah, with this change, we're almost there.
Thanks.
Signature · 1 line
tejun