From 02203311e96a90608c30e6604dc1f7bda0a777f0 Mon Sep 17 00:00:00 2001 From: Roland McGrath Date: Mon, 11 Jun 2007 22:06:31 +0000 Subject: [PATCH] 2007-06-11 Jan Kratochvil Never interrupt when the attached traced process would be left stopped. * strace.c (main): `-p' attaching moved to ... (startup_attach): ... a new function, renamed a variable C to TCBI. Block interrupting signals since the first tracee has been attached. New comment about INTERRUPTED in the nonthreaded case. [LINUX] (startup_attach): Check INTERRUPTED after each attached thread. (main): Command spawning moved to ... (startup_child): ... a new function, replaced RETURN with EXIT. [LINUX] (detach): New variable CATCH_SIGSTOP, do not signal new SIGSTOP for processes still in TCB_STARTUP. (main): Move signals and BLOCKED_SET init before the tracees attaching, [SUNOS4] (trace): Removed fixvfork () call as a dead code, SIGSTOP must have been already caught before clearing TCB_STARTUP. (trace): Removed the `!WIFSTOPPED(status)' dead code. Clear TCB_STARTUP only in the case the received signal was SIGSTOP. New comment when `TCB_BPTSET && TCB_STARTUP' combination can be set. Code advisory: Roland McGrath Fixes RH#240986. --- strace.c | 581 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 307 insertions(+), 274 deletions(-) diff --git a/strace.c b/strace.c index 1acfd0aa..4e860403 100644 --- a/strace.c +++ b/strace.c @@ -334,6 +334,280 @@ newoutf(struct tcb *tcp) return 0; } +static void +startup_attach(void) +{ + int tcbi; + struct tcb *tcp; + + /* + * Block user interruptions as we would leave the traced + * process stopped (process state T) if we would terminate in + * between PTRACE_ATTACH and wait4 () on SIGSTOP. + * We rely on cleanup () from this point on. + */ + if (interactive) + sigprocmask(SIG_BLOCK, &blocked_set, NULL); + + for (tcbi = 0; tcbi < tcbtabsize; tcbi++) { + tcp = tcbtab[tcbi]; + if (!(tcp->flags & TCB_INUSE) || !(tcp->flags & TCB_ATTACHED)) + continue; +#ifdef LINUX + if (tcp->flags & TCB_CLONE_THREAD) + continue; +#endif + /* Reinitialize the output since it may have changed. */ + tcp->outf = outf; + if (newoutf(tcp) < 0) + exit(1); + +#ifdef USE_PROCFS + if (proc_open(tcp, 1) < 0) { + fprintf(stderr, "trouble opening proc file\n"); + droptcb(tcp); + continue; + } +#else /* !USE_PROCFS */ +# ifdef LINUX + if (followfork) { + char procdir[MAXPATHLEN]; + DIR *dir; + + sprintf(procdir, "/proc/%d/task", tcp->pid); + dir = opendir(procdir); + if (dir != NULL) { + unsigned int ntid = 0, nerr = 0; + struct dirent *de; + int tid; + while ((de = readdir(dir)) != NULL) { + if (de->d_fileno == 0 || + de->d_name[0] == '.') + continue; + tid = atoi(de->d_name); + if (tid <= 0) + continue; + ++ntid; + if (ptrace(PTRACE_ATTACH, tid, + (char *) 1, 0) < 0) + ++nerr; + else if (tid != tcbtab[tcbi]->pid) { + if (nprocs == tcbtabsize && + expand_tcbtab()) + tcp = NULL; + else + tcp = alloctcb(tid); + if (tcp == NULL) + exit(1); + tcp->flags |= TCB_ATTACHED|TCB_CLONE_THREAD|TCB_CLONE_DETACHED|TCB_FOLLOWFORK; + tcbtab[tcbi]->nchildren++; + tcbtab[tcbi]->nclone_threads++; + tcbtab[tcbi]->nclone_detached++; + tcp->parent = tcbtab[tcbi]; + } + if (interactive) { + sigprocmask(SIG_SETMASK, &empty_set, NULL); + if (interrupted) + return; + sigprocmask(SIG_BLOCK, &blocked_set, NULL); + } + } + closedir(dir); + if (nerr == ntid) { + perror("attach: ptrace(PTRACE_ATTACH, ...)"); + droptcb(tcp); + continue; + } + if (!qflag) { + ntid -= nerr; + if (ntid > 1) + fprintf(stderr, "\ +Process %u attached with %u threads - interrupt to quit\n", + tcp->pid, ntid); + else + fprintf(stderr, "\ +Process %u attached - interrupt to quit\n", + tcp->pid); + } + continue; + } + } +# endif + if (ptrace(PTRACE_ATTACH, tcp->pid, (char *) 1, 0) < 0) { + perror("attach: ptrace(PTRACE_ATTACH, ...)"); + droptcb(tcp); + continue; + } + /* INTERRUPTED is going to be checked at the top of TRACE. */ +#endif /* !USE_PROCFS */ + if (!qflag) + fprintf(stderr, + "Process %u attached - interrupt to quit\n", + tcp->pid); + } + + if (interactive) + sigprocmask(SIG_SETMASK, &empty_set, NULL); +} + +static void +startup_child (char **argv) +{ + struct stat statbuf; + const char *filename; + char pathname[MAXPATHLEN]; + int pid = 0; + struct tcb *tcp; + + filename = argv[0]; + if (strchr(filename, '/')) { + if (strlen(filename) > sizeof pathname - 1) { + errno = ENAMETOOLONG; + perror("strace: exec"); + exit(1); + } + strcpy(pathname, filename); + } +#ifdef USE_DEBUGGING_EXEC + /* + * Debuggers customarily check the current directory + * first regardless of the path but doing that gives + * security geeks a panic attack. + */ + else if (stat(filename, &statbuf) == 0) + strcpy(pathname, filename); +#endif /* USE_DEBUGGING_EXEC */ + else { + char *path; + int m, n, len; + + for (path = getenv("PATH"); path && *path; path += m) { + if (strchr(path, ':')) { + n = strchr(path, ':') - path; + m = n + 1; + } + else + m = n = strlen(path); + if (n == 0) { + if (!getcwd(pathname, MAXPATHLEN)) + continue; + len = strlen(pathname); + } + else if (n > sizeof pathname - 1) + continue; + else { + strncpy(pathname, path, n); + len = n; + } + if (len && pathname[len - 1] != '/') + pathname[len++] = '/'; + strcpy(pathname + len, filename); + if (stat(pathname, &statbuf) == 0 && + /* Accept only regular files + with some execute bits set. + XXX not perfect, might still fail */ + S_ISREG(statbuf.st_mode) && + (statbuf.st_mode & 0111)) + break; + } + } + if (stat(pathname, &statbuf) < 0) { + fprintf(stderr, "%s: %s: command not found\n", + progname, filename); + exit(1); + } + switch (pid = fork()) { + case -1: + perror("strace: fork"); + cleanup(); + exit(1); + break; + case 0: { +#ifdef USE_PROCFS + if (outf != stderr) close (fileno (outf)); +#ifdef MIPS + /* Kludge for SGI, see proc_open for details. */ + sa.sa_handler = foobar; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sigaction(SIGINT, &sa, NULL); +#endif /* MIPS */ +#ifndef FREEBSD + pause(); +#else /* FREEBSD */ + kill(getpid(), SIGSTOP); /* stop HERE */ +#endif /* FREEBSD */ +#else /* !USE_PROCFS */ + if (outf!=stderr) + close(fileno (outf)); + + if (ptrace(PTRACE_TRACEME, 0, (char *) 1, 0) < 0) { + perror("strace: ptrace(PTRACE_TRACEME, ...)"); + exit(1); + } + if (debug) + kill(getpid(), SIGSTOP); + + if (username != NULL || geteuid() == 0) { + uid_t run_euid = run_uid; + gid_t run_egid = run_gid; + + if (statbuf.st_mode & S_ISUID) + run_euid = statbuf.st_uid; + if (statbuf.st_mode & S_ISGID) + run_egid = statbuf.st_gid; + + /* + * It is important to set groups before we + * lose privileges on setuid. + */ + if (username != NULL) { + if (initgroups(username, run_gid) < 0) { + perror("initgroups"); + exit(1); + } + if (setregid(run_gid, run_egid) < 0) { + perror("setregid"); + exit(1); + } + if (setreuid(run_uid, run_euid) < 0) { + perror("setreuid"); + exit(1); + } + } + } + else + setreuid(run_uid, run_uid); + + /* + * Induce an immediate stop so that the parent + * will resume us with PTRACE_SYSCALL and display + * this execve call normally. + */ + kill(getpid(), SIGSTOP); +#endif /* !USE_PROCFS */ + + execv(pathname, argv); + perror("strace: exec"); + _exit(1); + break; + } + default: + if ((tcp = alloctcb(pid)) == NULL) { + cleanup(); + exit(1); + } +#ifdef USE_PROCFS + if (proc_open(tcp, 0) < 0) { + fprintf(stderr, "trouble opening proc file\n"); + cleanup(); + exit(1); + } +#endif /* USE_PROCFS */ + break; + } +} + int main(argc, argv) int argc; @@ -532,250 +806,6 @@ char *argv[]; qflag = 1; } - for (c = 0; c < tcbtabsize; c++) { - tcp = tcbtab[c]; - if (!(tcp->flags & TCB_INUSE) || !(tcp->flags & TCB_ATTACHED)) - continue; -#ifdef LINUX - if (tcp->flags & TCB_CLONE_THREAD) - continue; -#endif - /* Reinitialize the output since it may have changed. */ - tcp->outf = outf; - if (newoutf(tcp) < 0) - exit(1); - -#ifdef USE_PROCFS - if (proc_open(tcp, 1) < 0) { - fprintf(stderr, "trouble opening proc file\n"); - droptcb(tcp); - continue; - } -#else /* !USE_PROCFS */ -# ifdef LINUX - if (followfork) { - char procdir[MAXPATHLEN]; - DIR *dir; - - sprintf(procdir, "/proc/%d/task", tcp->pid); - dir = opendir(procdir); - if (dir != NULL) { - unsigned int ntid = 0, nerr = 0; - struct dirent *de; - int tid; - while ((de = readdir(dir)) != NULL) { - if (de->d_fileno == 0 || - de->d_name[0] == '.') - continue; - tid = atoi(de->d_name); - if (tid <= 0) - continue; - ++ntid; - if (ptrace(PTRACE_ATTACH, tid, - (char *) 1, 0) < 0) - ++nerr; - else if (tid != tcbtab[c]->pid) { - if (nprocs == tcbtabsize && - expand_tcbtab()) - tcp = NULL; - else - tcp = alloctcb(tid); - if (tcp == NULL) - exit(1); - tcp->flags |= TCB_ATTACHED|TCB_CLONE_THREAD|TCB_CLONE_DETACHED|TCB_FOLLOWFORK; - tcbtab[c]->nchildren++; - tcbtab[c]->nclone_threads++; - tcbtab[c]->nclone_detached++; - tcp->parent = tcbtab[c]; - } - } - closedir(dir); - if (nerr == ntid) { - perror("attach: ptrace(PTRACE_ATTACH, ...)"); - droptcb(tcp); - continue; - } - if (!qflag) { - ntid -= nerr; - if (ntid > 1) - fprintf(stderr, "\ -Process %u attached with %u threads - interrupt to quit\n", - tcp->pid, ntid); - else - fprintf(stderr, "\ -Process %u attached - interrupt to quit\n", - tcp->pid); - } - continue; - } - } -# endif - if (ptrace(PTRACE_ATTACH, tcp->pid, (char *) 1, 0) < 0) { - perror("attach: ptrace(PTRACE_ATTACH, ...)"); - droptcb(tcp); - continue; - } -#endif /* !USE_PROCFS */ - if (!qflag) - fprintf(stderr, - "Process %u attached - interrupt to quit\n", - tcp->pid); - } - - if (!pflag_seen) { - struct stat statbuf; - char *filename; - char pathname[MAXPATHLEN]; - - filename = argv[optind]; - if (strchr(filename, '/')) { - if (strlen(filename) > sizeof pathname - 1) { - errno = ENAMETOOLONG; - perror("strace: exec"); - exit(1); - } - strcpy(pathname, filename); - } -#ifdef USE_DEBUGGING_EXEC - /* - * Debuggers customarily check the current directory - * first regardless of the path but doing that gives - * security geeks a panic attack. - */ - else if (stat(filename, &statbuf) == 0) - strcpy(pathname, filename); -#endif /* USE_DEBUGGING_EXEC */ - else { - char *path; - int m, n, len; - - for (path = getenv("PATH"); path && *path; path += m) { - if (strchr(path, ':')) { - n = strchr(path, ':') - path; - m = n + 1; - } - else - m = n = strlen(path); - if (n == 0) { - if (!getcwd(pathname, MAXPATHLEN)) - continue; - len = strlen(pathname); - } - else if (n > sizeof pathname - 1) - continue; - else { - strncpy(pathname, path, n); - len = n; - } - if (len && pathname[len - 1] != '/') - pathname[len++] = '/'; - strcpy(pathname + len, filename); - if (stat(pathname, &statbuf) == 0 && - /* Accept only regular files - with some execute bits set. - XXX not perfect, might still fail */ - S_ISREG(statbuf.st_mode) && - (statbuf.st_mode & 0111)) - break; - } - } - if (stat(pathname, &statbuf) < 0) { - fprintf(stderr, "%s: %s: command not found\n", - progname, filename); - exit(1); - } - switch (pid = fork()) { - case -1: - perror("strace: fork"); - cleanup(); - exit(1); - break; - case 0: { -#ifdef USE_PROCFS - if (outf != stderr) close (fileno (outf)); -#ifdef MIPS - /* Kludge for SGI, see proc_open for details. */ - sa.sa_handler = foobar; - sa.sa_flags = 0; - sigemptyset(&sa.sa_mask); - sigaction(SIGINT, &sa, NULL); -#endif /* MIPS */ -#ifndef FREEBSD - pause(); -#else /* FREEBSD */ - kill(getpid(), SIGSTOP); /* stop HERE */ -#endif /* FREEBSD */ -#else /* !USE_PROCFS */ - if (outf!=stderr) - close(fileno (outf)); - - if (ptrace(PTRACE_TRACEME, 0, (char *) 1, 0) < 0) { - perror("strace: ptrace(PTRACE_TRACEME, ...)"); - return -1; - } - if (debug) - kill(getpid(), SIGSTOP); - - if (username != NULL || geteuid() == 0) { - uid_t run_euid = run_uid; - gid_t run_egid = run_gid; - - if (statbuf.st_mode & S_ISUID) - run_euid = statbuf.st_uid; - if (statbuf.st_mode & S_ISGID) - run_egid = statbuf.st_gid; - - /* - * It is important to set groups before we - * lose privileges on setuid. - */ - if (username != NULL) { - if (initgroups(username, run_gid) < 0) { - perror("initgroups"); - exit(1); - } - if (setregid(run_gid, run_egid) < 0) { - perror("setregid"); - exit(1); - } - if (setreuid(run_uid, run_euid) < 0) { - perror("setreuid"); - exit(1); - } - } - } - else - setreuid(run_uid, run_uid); - - /* - * Induce an immediate stop so that the parent - * will resume us with PTRACE_SYSCALL and display - * this execve call normally. - */ - kill(getpid(), SIGSTOP); -#endif /* !USE_PROCFS */ - - execv(pathname, &argv[optind]); - perror("strace: exec"); - _exit(1); - break; - } - default: - if ((tcp = alloctcb(pid)) == NULL) { - cleanup(); - exit(1); - } -#ifdef USE_PROCFS - if (proc_open(tcp, 0) < 0) { - fprintf(stderr, "trouble opening proc file\n"); - cleanup(); - exit(1); - } -#endif /* USE_PROCFS */ - break; - } - } - sigemptyset(&empty_set); sigemptyset(&blocked_set); sa.sa_handler = SIG_IGN; @@ -813,6 +843,11 @@ Process %u attached - interrupt to quit\n", sigaction(SIGCHLD, &sa, NULL); #endif /* USE_PROCFS */ + if (pflag_seen) + startup_attach(); + else + startup_child(&argv[optind]); + if (trace() < 0) exit(1); cleanup(); @@ -1306,7 +1341,7 @@ int sig; { int error = 0; #ifdef LINUX - int status, resumed; + int status, resumed, catch_sigstop; struct tcb *zombie = NULL; /* If the group leader is lingering only because of this other @@ -1330,6 +1365,12 @@ int sig; #undef PTRACE_DETACH #define PTRACE_DETACH PTRACE_SUNDETACH #endif + /* + * On TCB_STARTUP we did PTRACE_ATTACH but still did not get the + * expected SIGSTOP. We must catch exactly one as otherwise the + * detached process would be left stopped (process state T). + */ + catch_sigstop = (tcp->flags & TCB_STARTUP); if ((error = ptrace(PTRACE_DETACH, tcp->pid, (char *) 1, sig)) == 0) { /* On a clear day, you can see forever. */ } @@ -1343,13 +1384,15 @@ int sig; if (errno != ESRCH) perror("detach: checking sanity"); } - else if (my_tgkill((tcp->flags & TCB_CLONE_THREAD ? tcp->parent->pid - : tcp->pid), - tcp->pid, SIGSTOP) < 0) { + else if (!catch_sigstop && my_tgkill((tcp->flags & TCB_CLONE_THREAD + ? tcp->parent->pid : tcp->pid), + tcp->pid, SIGSTOP) < 0) { if (errno != ESRCH) perror("detach: stopping child"); } - else { + else + catch_sigstop = 1; + if (catch_sigstop) for (;;) { #ifdef __WALL if (wait4(tcp->pid, &status, __WALL, NULL) < 0) { @@ -1400,7 +1443,6 @@ int sig; break; } } - } #endif /* LINUX */ #if defined(SUNOS4) @@ -2121,6 +2163,8 @@ trace() #endif /* LINUX */ while (nprocs != 0) { + if (interrupted) + return 0; if (interactive) sigprocmask(SIG_SETMASK, &empty_set, NULL); #ifdef LINUX @@ -2153,9 +2197,6 @@ trace() if (interactive) sigprocmask(SIG_BLOCK, &blocked_set, NULL); - if (interrupted) - return 0; - if (pid == -1) { switch (wait_errno) { case EINTR: @@ -2301,35 +2342,24 @@ Process %d attached (waiting for parent)\n", fprintf(stderr, "pid %u stopped, [%s]\n", pid, signame(WSTOPSIG(status))); - if (tcp->flags & TCB_STARTUP) { + /* + * Interestingly, the process may stop + * with STOPSIG equal to some other signal + * than SIGSTOP if we happend to attach + * just before the process takes a signal. + */ + if ((tcp->flags & TCB_STARTUP) && WSTOPSIG(status) == SIGSTOP) { /* * This flag is there to keep us in sync. * Next time this process stops it should * really be entering a system call. */ tcp->flags &= ~TCB_STARTUP; - if (tcp->flags & TCB_ATTACHED) { + if (tcp->flags & TCB_BPTSET) { /* - * Interestingly, the process may stop - * with STOPSIG equal to some other signal - * than SIGSTOP if we happend to attach - * just before the process takes a signal. + * One example is a breakpoint inherited from + * parent through fork (). */ - if (!WIFSTOPPED(status)) { - fprintf(stderr, - "pid %u not stopped\n", pid); - detach(tcp, WSTOPSIG(status)); - continue; - } - } - else { -#ifdef SUNOS4 - /* A child of us stopped at exec */ - if (WSTOPSIG(status) == SIGTRAP && followvfork) - fixvfork(tcp); -#endif /* SUNOS4 */ - } - if (tcp->flags & TCB_BPTSET) { if (clearbpt(tcp) < 0) /* Pretty fatal */ { droptcb(tcp); cleanup(); @@ -2404,6 +2434,9 @@ Process %d attached (waiting for parent)\n", tcp->flags &= ~TCB_SUSPENDED; continue; } + /* we handled the STATUS, we are permitted to interrupt now. */ + if (interrupted) + return 0; if (trace_syscall(tcp) < 0) { if (tcp->flags & TCB_ATTACHED) detach(tcp, 0); -- 2.40.0