aboutsummaryrefslogtreecommitdiffstats
path: root/kernel/ptrace.c
diff options
context:
space:
mode:
authorTejun Heo <tj@kernel.org>2011-06-14 05:20:16 -0400
committerOleg Nesterov <oleg@redhat.com>2011-06-16 15:41:53 -0400
commitfca26f260c528ee51a2e451b5b200aeb528f3e09 (patch)
tree8c64ecdcece48b55e79bbb7f376a834fc99804a3 /kernel/ptrace.c
parent3544d72a0e10d0aa1c1bd59ed77a53a59cdc12f7 (diff)
ptrace: implement PTRACE_INTERRUPT
Currently, there's no way to trap a running ptracee short of sending a signal which has various side effects. This patch implements PTRACE_INTERRUPT which traps ptracee without any signal or job control related side effect. The implementation is almost trivial. It uses the group stop trap - SIGTRAP | PTRACE_EVENT_STOP << 8. A new trap flag JOBCTL_TRAP_INTERRUPT is added, which is set on PTRACE_INTERRUPT and cleared when any trap happens. As INTERRUPT should be useable regardless of the current state of tracee, task_is_traced() test in ptrace_check_attach() is skipped for INTERRUPT. PTRACE_INTERRUPT is available iff tracee is attached with PTRACE_SEIZE. Test program follows. #define PTRACE_SEIZE 0x4206 #define PTRACE_INTERRUPT 0x4207 #define PTRACE_SEIZE_DEVEL 0x80000000 static const struct timespec ts100ms = { .tv_nsec = 100000000 }; static const struct timespec ts1s = { .tv_sec = 1 }; static const struct timespec ts3s = { .tv_sec = 3 }; int main(int argc, char **argv) { pid_t tracee; tracee = fork(); if (tracee == 0) { nanosleep(&ts100ms, NULL); while (1) { printf("tracee: alive pid=%d\n", getpid()); nanosleep(&ts1s, NULL); } } if (argc > 1) kill(tracee, SIGSTOP); nanosleep(&ts100ms, NULL); ptrace(PTRACE_SEIZE, tracee, NULL, (void *)(unsigned long)PTRACE_SEIZE_DEVEL); if (argc > 1) { waitid(P_PID, tracee, NULL, WSTOPPED); ptrace(PTRACE_CONT, tracee, NULL, NULL); } nanosleep(&ts3s, NULL); printf("tracer: INTERRUPT and DETACH\n"); ptrace(PTRACE_INTERRUPT, tracee, NULL, NULL); waitid(P_PID, tracee, NULL, WSTOPPED); ptrace(PTRACE_DETACH, tracee, NULL, NULL); nanosleep(&ts3s, NULL); printf("tracer: exiting\n"); kill(tracee, SIGKILL); return 0; } When called without argument, tracee is seized from running state, interrupted and then detached back to running state. # ./test-interrupt tracee: alive pid=4546 tracee: alive pid=4546 tracee: alive pid=4546 tracer: INTERRUPT and DETACH tracee: alive pid=4546 tracee: alive pid=4546 tracee: alive pid=4546 tracer: exiting When called with argument, tracee is seized from stopped state, continued, interrupted and then detached back to stopped state. # ./test-interrupt 1 tracee: alive pid=4548 tracee: alive pid=4548 tracee: alive pid=4548 tracer: INTERRUPT and DETACH tracer: exiting Before PTRACE_INTERRUPT, once the tracee was running, there was no way to trap tracee and do PTRACE_DETACH without causing side effect. -v2: Updated to use task_set_jobctl_pending() so that it doesn't end up scheduling TRAP_STOP if child is dying which may make the child unkillable. Spotted by Oleg. Signed-off-by: Tejun Heo <tj@kernel.org> Cc: Oleg Nesterov <oleg@redhat.com>
Diffstat (limited to 'kernel/ptrace.c')
-rw-r--r--kernel/ptrace.c29
1 files changed, 27 insertions, 2 deletions
diff --git a/kernel/ptrace.c b/kernel/ptrace.c
index dcf9f974198c..6852c0f4a916 100644
--- a/kernel/ptrace.c
+++ b/kernel/ptrace.c
@@ -658,10 +658,12 @@ static int ptrace_regset(struct task_struct *task, int req, unsigned int type,
658int ptrace_request(struct task_struct *child, long request, 658int ptrace_request(struct task_struct *child, long request,
659 unsigned long addr, unsigned long data) 659 unsigned long addr, unsigned long data)
660{ 660{
661 bool seized = child->ptrace & PT_SEIZED;
661 int ret = -EIO; 662 int ret = -EIO;
662 siginfo_t siginfo; 663 siginfo_t siginfo;
663 void __user *datavp = (void __user *) data; 664 void __user *datavp = (void __user *) data;
664 unsigned long __user *datalp = datavp; 665 unsigned long __user *datalp = datavp;
666 unsigned long flags;
665 667
666 switch (request) { 668 switch (request) {
667 case PTRACE_PEEKTEXT: 669 case PTRACE_PEEKTEXT:
@@ -694,6 +696,27 @@ int ptrace_request(struct task_struct *child, long request,
694 ret = ptrace_setsiginfo(child, &siginfo); 696 ret = ptrace_setsiginfo(child, &siginfo);
695 break; 697 break;
696 698
699 case PTRACE_INTERRUPT:
700 /*
701 * Stop tracee without any side-effect on signal or job
702 * control. At least one trap is guaranteed to happen
703 * after this request. If @child is already trapped, the
704 * current trap is not disturbed and another trap will
705 * happen after the current trap is ended with PTRACE_CONT.
706 *
707 * The actual trap might not be PTRACE_EVENT_STOP trap but
708 * the pending condition is cleared regardless.
709 */
710 if (unlikely(!seized || !lock_task_sighand(child, &flags)))
711 break;
712
713 if (likely(task_set_jobctl_pending(child, JOBCTL_TRAP_STOP)))
714 signal_wake_up(child, 0);
715
716 unlock_task_sighand(child, &flags);
717 ret = 0;
718 break;
719
697 case PTRACE_DETACH: /* detach a process that was attached. */ 720 case PTRACE_DETACH: /* detach a process that was attached. */
698 ret = ptrace_detach(child, data); 721 ret = ptrace_detach(child, data);
699 break; 722 break;
@@ -819,7 +842,8 @@ SYSCALL_DEFINE4(ptrace, long, request, long, pid, unsigned long, addr,
819 goto out_put_task_struct; 842 goto out_put_task_struct;
820 } 843 }
821 844
822 ret = ptrace_check_attach(child, request == PTRACE_KILL); 845 ret = ptrace_check_attach(child, request == PTRACE_KILL ||
846 request == PTRACE_INTERRUPT);
823 if (ret < 0) 847 if (ret < 0)
824 goto out_put_task_struct; 848 goto out_put_task_struct;
825 849
@@ -961,7 +985,8 @@ asmlinkage long compat_sys_ptrace(compat_long_t request, compat_long_t pid,
961 goto out_put_task_struct; 985 goto out_put_task_struct;
962 } 986 }
963 987
964 ret = ptrace_check_attach(child, request == PTRACE_KILL); 988 ret = ptrace_check_attach(child, request == PTRACE_KILL ||
989 request == PTRACE_INTERRUPT);
965 if (!ret) 990 if (!ret)
966 ret = compat_arch_ptrace(child, request, addr, data); 991 ret = compat_arch_ptrace(child, request, addr, data);
967 992