diff --git a/attach.c b/attach.c index 63d114a..b43d040 100644 --- a/attach.c +++ b/attach.c @@ -35,6 +35,7 @@ #include #include #include +#include #include "ptrace.h" #include "reptyr.h" @@ -45,30 +46,60 @@ #define mmap_syscall __NR_mmap #endif +#define TASK_COMM_LENGTH 16 +struct proc_stat { + pid_t pid; + char comm[TASK_COMM_LENGTH+1]; + char state; + pid_t ppid, sid, pgid; + dev_t ctty; +}; + +int parse_proc_stat(int statfd, struct proc_stat *out) { + char buf[1024]; + int n; + lseek(statfd, 0, SEEK_SET); + if (read(statfd, buf, sizeof buf) < 0) + return errno; + n = sscanf(buf, "%d (%16[^)]) %c %d %d %d %u", + &out->pid, out->comm, + &out->state, &out->ppid, &out->sid, + &out->pgid, (unsigned*)&out->ctty); + if (n == EOF) + return errno; + if (n != 7) { + return EINVAL; + } + return 0; +} + static void do_unmap(struct ptrace_child *child, child_addr_t addr, unsigned long len) { if (addr == (unsigned long)-1) return; ptrace_remote_syscall(child, __NR_munmap, addr, len, 0, 0, 0, 0); } -int *get_child_tty_fds(struct ptrace_child *child, int *count) { +int *get_child_tty_fds(struct ptrace_child *child, int statfd, int *count) { + struct proc_stat child_status; + struct stat tty_st, st; char buf[PATH_MAX]; - char child_tty[PATH_MAX]; int n = 0, allocated = 0; int *fds = NULL; DIR *dir; - ssize_t len; struct dirent *d; int *tmp = NULL; debug("Looking up fds for tty in child."); - snprintf(buf, sizeof buf, "/proc/%d/fd/0", child->pid); - len = readlink(buf, child_tty, PATH_MAX); - if(len < 0) + if ((child->error = parse_proc_stat(statfd, &child_status))) return NULL; - child_tty[len] = 0; - debug("Resolved child tty: %s", child_tty); + debug("Resolved child tty: %u", child_status.ctty); + + if (stat("/dev/tty", &tty_st) < 0) { + child->error = errno; + error("Unable to stat /dev/tty"); + return NULL; + } snprintf(buf, sizeof buf, "/proc/%d/fd/", child->pid); if ((dir = opendir(buf)) == NULL) @@ -76,12 +107,11 @@ int *get_child_tty_fds(struct ptrace_child *child, int *count) { while ((d = readdir(dir)) != NULL) { if (d->d_name[0] == '.') continue; snprintf(buf, sizeof buf, "/proc/%d/fd/%s", child->pid, d->d_name); - len = readlink(buf, buf, PATH_MAX); - if (len < 0) + if(stat(buf, &st) < 0) continue; - buf[len] = 0; - if (strcmp(buf, child_tty) == 0 - || strcmp(buf, "/dev/tty") == 0) { + + if (st.st_rdev == child_status.ctty + || st.st_rdev == tty_st.st_rdev) { if (n == allocated) { allocated = allocated ? 2 * allocated : 2; tmp = realloc(fds, allocated * sizeof *tmp); @@ -212,18 +242,11 @@ int ignore_hup(struct ptrace_child *child, unsigned long scratch_page) { * second and continue with the attach -- it'll still work mostly right, you * just won't get the old shell back. */ -void wait_for_stop(pid_t pid) { +void wait_for_stop(pid_t pid, int fd) { struct timeval start, now; struct timespec sleep; - char stat_path[PATH_MAX], buf[256], *p; - int fd; - - snprintf(stat_path, sizeof stat_path, "/proc/%d/stat", pid); - fd = open(stat_path, O_RDONLY); - if (!fd) { - error("Unable to open %s: %s", stat_path, strerror(errno)); - return; - } + struct proc_stat st; + gettimeofday(&start, NULL); while (1) { gettimeofday(&now, NULL); @@ -236,73 +259,86 @@ void wait_for_stop(pid_t pid) { * If anything goes wrong reading or parsing the stat node, just give * up. */ - lseek(fd, 0, SEEK_SET); - if (read(fd, buf, sizeof buf) <= 0) - break; - p = strchr(buf, ' '); - if (!p) + if (parse_proc_stat(fd, &st)) break; - p = strchr(p+1, ' '); - if (!p) - break; - if (*(p+1) == 'T') + if (st.state == 'T') break; sleep.tv_sec = 0; sleep.tv_nsec = 10000000; nanosleep(&sleep, NULL); } - close(fd); } int copy_tty_state(pid_t pid, const char *pty) { char buf[PATH_MAX]; int fd, err = 0; struct termios tio; + int i; - snprintf(buf, sizeof buf, "/proc/%d/fd/0", pid); - if ((fd = open(buf, O_RDONLY)) < 0) - return -errno; + for (i = 0; i < 3; i++) { + err = 0; + snprintf(buf, sizeof buf, "/proc/%d/fd/%d", pid, i); - if (!isatty(fd)) { - err = ENOTTY; - error("Target is not connected to a terminal."); - goto out; - } - - if (tcgetattr(fd, &tio) < 0) { - err = errno; - goto out; + if ((fd = open(buf, O_RDONLY)) < 0) { + err = -fd; + continue; + } + + if (!isatty(fd)) { + err = ENOTTY; + goto retry; + } + + if (tcgetattr(fd, &tio) < 0) { + err = -errno; + } + retry: + close(fd); } - close(fd); + + if (err) + return err; if ((fd = open(pty, O_RDONLY)) < 0) return -errno; if (tcsetattr(fd, TCSANOW, &tio) < 0) err = errno; -out: close(fd); return -err; } -int attach_child(pid_t pid, const char *pty) { +int attach_child(pid_t pid, const char *pty, int force_stdio) { struct ptrace_child child; unsigned long scratch_page = -1; - int *child_tty_fds = NULL, n_fds, child_fd; + int *child_tty_fds = NULL, n_fds, child_fd, statfd; int i; int err = 0; long page_size = sysconf(_SC_PAGE_SIZE); + char stat_path[PATH_MAX]; - if ((err = copy_tty_state(pid, pty)) < 0) - return -err; + if ((err = copy_tty_state(pid, pty))) { + if (err == ENOTTY && !force_stdio) { + error("Target is not connected to a terminal.\n" + " Use -s to force attaching anyways."); + return err; + } + } + + snprintf(stat_path, sizeof stat_path, "/proc/%d/stat", pid); + statfd = open(stat_path, O_RDONLY); + if (statfd < 0) { + error("Unable to open %s: %s", stat_path, strerror(errno)); + return -statfd; + } kill(pid, SIGTSTP); - wait_for_stop(pid); + wait_for_stop(pid, statfd); if (ptrace_attach_child(&child, pid)) { - kill(pid, SIGCONT); - return child.error; + err = child.error; + goto out_cont; } if (ptrace_advance_to_state(&child, ptrace_at_syscall)) { @@ -325,10 +361,22 @@ int attach_child(pid_t pid, const char *pty) { debug("Allocated scratch page: %lx", scratch_page); - child_tty_fds = get_child_tty_fds(&child, &n_fds); - if (!child_tty_fds) { - err = child.error; - goto out_unmap; + if (force_stdio) { + child_tty_fds = malloc(3 * sizeof(int)); + if (!child_tty_fds) { + err = ENOMEM; + goto out_unmap; + } + n_fds = 3; + child_tty_fds[0] = 0; + child_tty_fds[1] = 1; + child_tty_fds[2] = 2; + } else { + child_tty_fds = get_child_tty_fds(&child, statfd, &n_fds); + if (!child_tty_fds) { + err = child.error; + goto out_unmap; + } } if (ptrace_memcpy_to_child(&child, scratch_page, pty, strlen(pty)+1)) { @@ -398,10 +446,12 @@ int attach_child(pid_t pid, const char *pty) { if (err == 0) { kill(child.pid, SIGSTOP); - wait_for_stop(child.pid); + wait_for_stop(child.pid, statfd); } - kill(child.pid, SIGCONT); kill(child.pid, SIGWINCH); + out_cont: + kill(child.pid, SIGCONT); + close(statfd); return err < 0 ? -err : err; } diff --git a/reptyr.c b/reptyr.c index 68dd32e..a9cdd70 100644 --- a/reptyr.c +++ b/reptyr.c @@ -139,7 +139,10 @@ void do_proxy(int pty) { } void usage(char *me) { - fprintf(stderr, "Usage: %s [-l | PID]\n", me); + fprintf(stderr, "Usage: %s [-s] PID\n", me); + fprintf(stderr, " %s -l\n", me); + fprintf(stderr, " -l Create a new pty pair and print the name of the slave.\n"); + fprintf(stderr, " -s Attach fds 0-2 on the target, even if it is not attached to a tty.\n"); } void check_yama_ptrace_scope(void) { @@ -165,20 +168,26 @@ int main(int argc, char **argv) { struct termios saved_termios; struct sigaction act; int pty; + int arg = 1; int do_attach = 1; + int force_stdio = 0; if (argc < 2) { usage(argv[0]); return 2; } - if(argv[1][0] == '-') { - switch(argv[1][1]) { + if(argv[arg][0] == '-') { + switch(argv[arg][1]) { case 'h': usage(argv[0]); return 0; case 'l': do_attach = 0; break; + case 's': + arg++; + force_stdio = 1; + break; case 'v': printf("This is reptyr version %s.\n", REPTYR_VERSION); printf(" by Nelson Elhage \n"); @@ -198,9 +207,9 @@ int main(int argc, char **argv) { die("Unable to unlockpt: %m"); if (do_attach) { - pid_t child = atoi(argv[1]); + pid_t child = atoi(argv[arg]); int err; - if ((err = attach_child(child, ptsname(pty)))) { + if ((err = attach_child(child, ptsname(pty), force_stdio))) { fprintf(stderr, "Unable to attach to pid %d: %s\n", child, strerror(err)); if (err == EPERM) { check_yama_ptrace_scope(); diff --git a/reptyr.h b/reptyr.h index a0ec7a8..1d83e02 100644 --- a/reptyr.h +++ b/reptyr.h @@ -22,7 +22,7 @@ #define REPTYR_VERSION "0.1" -int attach_child(pid_t pid, const char *pty); +int attach_child(pid_t pid, const char *pty, int force_stdio); void die(const char *msg, ...); void debug(const char *msg, ...); void error(const char *msg, ...);