From 9ce252618ed4791d9b7e6218f7b9f7c121768078 Mon Sep 17 00:00:00 2001 From: Nelson Elhage Date: Sat, 19 Feb 2011 10:57:33 -0800 Subject: [PATCH 1/2] Add a -s option to force attaching stdio. --- attach.c | 34 +++++++++++++++++++++++++--------- reptyr.c | 19 ++++++++++++++----- reptyr.h | 2 +- 3 files changed, 40 insertions(+), 15 deletions(-) diff --git a/attach.c b/attach.c index 32cd65d..4372252 100644 --- a/attach.c +++ b/attach.c @@ -259,10 +259,9 @@ int copy_tty_state(pid_t pid, const char *pty) { if (!isatty(fd)) { err = ENOTTY; - error("Target is not connected to a terminal."); goto out; } - + if (tcgetattr(fd, &tio) < 0) { err = errno; goto out; @@ -279,7 +278,7 @@ int copy_tty_state(pid_t pid, const char *pty) { 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; @@ -287,8 +286,13 @@ int attach_child(pid_t pid, const char *pty) { int err = 0; long page_size = sysconf(_SC_PAGE_SIZE); - if ((err = copy_tty_state(pid, pty)) < 0) - return -err; + if ((err = copy_tty_state(pid, pty)) < 0) { + if (err == -ENOTTY && !force_stdio) { + error("Target is not connected to a terminal.\n" + " Use -s to force attaching anyways."); + return -err; + } + } kill(pid, SIGTSTP); wait_for_stop(pid); @@ -318,10 +322,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, &n_fds); + if (!child_tty_fds) { + err = child.error; + goto out_unmap; + } } if (ptrace_memcpy_to_child(&child, scratch_page, pty, strlen(pty)+1)) { 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, ...); From b81b708fe46888ca5c17f5632efd031ba373954b Mon Sep 17 00:00:00 2001 From: Nelson Elhage Date: Sun, 20 Feb 2011 00:38:11 -0800 Subject: [PATCH 2/2] Be smarter about choosing which fd's to attach. Instead of using readlink, stat() the child's file descriptors and compare the result to the device in /proc/$pid/stat and /dev/tty. This lets us properly attach a 'less' that is taking input from a pipe. --- attach.c | 144 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 89 insertions(+), 55 deletions(-) diff --git a/attach.c b/attach.c index 4372252..8d2bd52 100644 --- a/attach.c +++ b/attach.c @@ -35,6 +35,7 @@ #include #include #include +#include #include "ptrace.h" #include "reptyr.h" @@ -45,29 +46,59 @@ #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; 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) @@ -75,12 +106,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; fds = realloc(fds, sizeof(int) * allocated); @@ -205,18 +235,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); @@ -229,51 +252,52 @@ 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; - goto out; - } + if ((fd = open(buf, O_RDONLY)) < 0) { + err = -fd; + continue; + } - if (tcgetattr(fd, &tio) < 0) { - err = errno; - goto out; + 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; } @@ -281,25 +305,33 @@ int copy_tty_state(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) { - if (err == -ENOTTY && !force_stdio) { + 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; + 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)) { @@ -333,7 +365,7 @@ int attach_child(pid_t pid, const char *pty, int force_stdio) { child_tty_fds[1] = 1; child_tty_fds[2] = 2; } else { - child_tty_fds = get_child_tty_fds(&child, &n_fds); + child_tty_fds = get_child_tty_fds(&child, statfd, &n_fds); if (!child_tty_fds) { err = child.error; goto out_unmap; @@ -407,10 +439,12 @@ int attach_child(pid_t pid, const char *pty, int force_stdio) { 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; }