int steal_cleanup_child(struct steal_pty_state *steal) { if (ptrace_memcpy_to_child(&steal->child, steal->child_scratch, "/dev/null", sizeof("/dev/null"))) { return steal->child.error; } int nullfd = do_syscall(&steal->child, openat, -1, steal->child_scratch, O_RDWR, 0, 0, 0); if (nullfd < 0) { return steal->child.error; } int i; for (i = 0; i < steal->master_fds.n; ++i) { do_dup2(&steal->child, nullfd, steal->master_fds.fds[i]); } do_syscall(&steal->child, close, nullfd, 0, 0, 0, 0, 0); do_syscall(&steal->child, close, steal->child_fd, 0, 0, 0, 0, 0); steal->child_fd = 0; ptrace_restore_regs(&steal->child); ptrace_detach_child(&steal->child); ptrace_wait(&steal->child); return 0; }
int grab_pid(pid_t pid, struct ptrace_child *child, child_addr_t *scratch) { int err; if (ptrace_attach_child(child, pid)) { err = child->error; goto out; } if (ptrace_advance_to_state(child, ptrace_at_syscall)) { err = child->error; goto out; } if (ptrace_save_regs(child)) { err = child->error; goto out; } if ((err = mmap_scratch(child, scratch))) goto out_restore_regs; return 0; out_restore_regs: ptrace_restore_regs(child); out: ptrace_detach_child(child); return err; }
int preflight_check(pid_t pid) { struct ptrace_child child; debug("Making sure we have permission to attach..."); if (ptrace_attach_child(&child, pid)) { return child.error; } ptrace_detach_child(&child); return 0; }
// Attach to the session leader of the stolen session, and block // SIGHUP so that if and when the terminal emulator tries to HUP it, // it doesn't die. int steal_block_hup(struct steal_pty_state *steal) { struct ptrace_child leader; child_addr_t scratch = 0; int err = 0; if ((err = grab_pid(steal->target_stat.sid, &leader, &scratch))) return err; err = ignore_hup(&leader, scratch); ptrace_restore_regs(&leader); ptrace_detach_child(&leader); return err; }
int do_setsid(struct ptrace_child *child) { int err = 0; struct ptrace_child dummy; err = do_fork(child); if (err < 0) return err; debug("Forked a child: %ld", child->forked_pid); err = ptrace_finish_attach(&dummy, child->forked_pid); if (err < 0) goto out_kill; dummy.state = ptrace_after_syscall; copy_user(&dummy, child); if (ptrace_restore_regs(&dummy)) { err = dummy.error; goto out_kill; } err = do_syscall(&dummy, setpgid, 0, 0, 0, 0, 0, 0); if (err < 0) { error("Failed to setpgid: %s", strerror(-err)); goto out_kill; } move_process_group(child, child->pid, dummy.pid); err = do_syscall(child, setsid, 0, 0, 0, 0, 0, 0); if (err < 0) { error("Failed to setsid: %s", strerror(-err)); move_process_group(child, dummy.pid, child->pid); goto out_kill; } debug("Did setsid()"); out_kill: kill(dummy.pid, SIGKILL); ptrace_wait(&dummy); ptrace_detach_child(&dummy); do_syscall(child, wait4, dummy.pid, 0, WNOHANG, 0, 0, 0); return err; }
int steal_pty(pid_t pid, int *pty) { int err = 0; struct steal_pty_state steal = {}; long page_size = sysconf(_SC_PAGE_SIZE); if ((err = preflight_check(pid))) goto out; if ((err = get_terminal_state(&steal, pid))) goto out; if ((err = setup_steal_socket(&steal))) goto out; debug("Listening on socket: %s", steal.addr_un.sun_path); debug("Attaching terminal emulator pid=%d", steal.emulator_pid); if ((err = grab_pid(steal.emulator_pid, &steal.child, &steal.child_scratch))) goto out; debug("Attached to terminal emulator (pid %d)", (int)steal.emulator_pid); if ((err = find_master_fd(&steal))) { error("Unable to find the fd for the pty!"); goto out; } if ((err = setup_steal_socket_child(&steal))) goto out; if ((err = steal_child_pty(&steal))) goto out; if ((err = steal_block_hup(&steal))) goto out; if ((err = steal_cleanup_child(&steal))) goto out; goto out_no_child; out: if (steal.ptyfd) { close(steal.ptyfd); steal.ptyfd = 0; } if (steal.child_fd > 0) do_syscall(&steal.child, close, steal.child_fd, 0, 0, 0, 0, 0); if (steal.child_scratch > 0) do_unmap(&steal.child, steal.child_scratch, page_size); if (steal.child.state != ptrace_detached) { ptrace_restore_regs(&steal.child); ptrace_detach_child(&steal.child); } out_no_child: if (steal.sockfd > 0) { close(steal.sockfd); unlink(steal.addr_un.sun_path); } if (steal.tmpdir[0]) { rmdir(steal.tmpdir); } if (steal.ptyfd) *pty = steal.ptyfd; free(steal.master_fds.fds); return err; }
int attach_child(pid_t pid, const char *pty, int force_stdio) { struct ptrace_child child; child_addr_t scratch_page = -1; int *child_tty_fds = NULL, n_fds, child_fd, statfd = -1; int i; int err = 0; long page_size = sysconf(_SC_PAGE_SIZE); #ifdef __linux__ char stat_path[PATH_MAX]; #endif if ((err = check_pgroup(pid))) { return err; } if ((err = preflight_check(pid))) { return err; } debug("Using tty: %s", pty); 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; } } #ifdef __linux__ 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; } #endif kill(pid, SIGTSTP); wait_for_stop(pid, statfd); if ((err = grab_pid(pid, &child, &scratch_page))) { goto out_cont; } 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)) { err = child.error; error("Unable to memcpy the pty path to child."); goto out_free_fds; } child_fd = do_syscall(&child, openat, -1, scratch_page, O_RDWR | O_NOCTTY, 0, 0, 0); if (child_fd < 0) { err = child_fd; error("Unable to open the tty in the child."); goto out_free_fds; } debug("Opened the new tty in the child: %d", child_fd); err = ignore_hup(&child, scratch_page); if (err < 0) goto out_close; err = do_syscall(&child, getsid, 0, 0, 0, 0, 0, 0); if (err != child.pid) { debug("Target is not a session leader, attempting to setsid."); err = do_setsid(&child); } else { do_syscall(&child, ioctl, child_tty_fds[0], TIOCNOTTY, 0, 0, 0, 0); } if (err < 0) goto out_close; err = do_syscall(&child, ioctl, child_fd, TIOCSCTTY, 1, 0, 0, 0); if (err != 0) { /* Seems to be returning >0 for error */ error("Unable to set controlling terminal: %s", strerror(err)); goto out_close; } debug("Set the controlling tty"); for (i = 0; i < n_fds; i++) { err = do_dup2(&child, child_fd, child_tty_fds[i]); if (err < 0) error("Problem moving child fd number %d to new tty: %s", child_tty_fds[i], strerror(errno)); } err = 0; out_close: do_syscall(&child, close, child_fd, 0, 0, 0, 0, 0); out_free_fds: free(child_tty_fds); out_unmap: do_unmap(&child, scratch_page, page_size); ptrace_restore_regs(&child); ptrace_detach_child(&child); if (err == 0) { kill(child.pid, SIGSTOP); wait_for_stop(child.pid, statfd); } kill(child.pid, SIGWINCH); out_cont: kill(child.pid, SIGCONT); #ifdef __linux__ close(statfd); #endif return err < 0 ? -err : err; }
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, statfd; int i; int err = 0; long page_size = sysconf(_SC_PAGE_SIZE); char stat_path[PATH_MAX]; long mmap_syscall; 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, statfd); if (ptrace_attach_child(&child, pid)) { err = child.error; goto out_cont; } if (ptrace_advance_to_state(&child, ptrace_at_syscall)) { err = child.error; goto out_detach; } if (ptrace_save_regs(&child)) { err = child.error; goto out_detach; } mmap_syscall = ptrace_syscall_numbers(&child)->nr_mmap2; if (mmap_syscall == -1) mmap_syscall = ptrace_syscall_numbers(&child)->nr_mmap; scratch_page = ptrace_remote_syscall(&child, mmap_syscall, 0, page_size, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, 0, 0); if (scratch_page > (unsigned long)-1000) { err = -(signed long)scratch_page; goto out_unmap; } debug("Allocated scratch page: %lx", scratch_page); 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)) { err = child.error; error("Unable to memcpy the pty path to child."); goto out_free_fds; } child_fd = do_syscall(&child, open, scratch_page, O_RDWR|O_NOCTTY, 0, 0, 0, 0); if (child_fd < 0) { err = child_fd; error("Unable to open the tty in the child."); goto out_free_fds; } debug("Opened the new tty in the child: %d", child_fd); err = ignore_hup(&child, scratch_page); if (err < 0) goto out_close; err = do_syscall(&child, getsid, 0, 0, 0, 0, 0, 0); if (err != child.pid) { debug("Target is not a session leader, attempting to setsid."); err = do_setsid(&child); } else { do_syscall(&child, ioctl, child_tty_fds[0], TIOCNOTTY, 0, 0, 0, 0); } if (err < 0) goto out_close; err = do_syscall(&child, ioctl, child_fd, TIOCSCTTY, 0, 0, 0, 0); if (err < 0) { error("Unable to set controlling terminal."); goto out_close; } debug("Set the controlling tty"); for (i = 0; i < n_fds; i++) do_syscall(&child, dup2, child_fd, child_tty_fds[i], 0, 0, 0, 0); err = 0; out_close: do_syscall(&child, close, child_fd, 0, 0, 0, 0, 0); out_free_fds: free(child_tty_fds); out_unmap: do_unmap(&child, scratch_page, page_size); ptrace_restore_regs(&child); out_detach: ptrace_detach_child(&child); if (err == 0) { kill(child.pid, SIGSTOP); wait_for_stop(child.pid, statfd); } kill(child.pid, SIGWINCH); out_cont: kill(child.pid, SIGCONT); close(statfd); return err < 0 ? -err : err; }