pax_global_header00006660000000000000000000000064124632767630014532gustar00rootroot0000000000000052 comment=01a1668003745275491931734b06c51535821975 reptyr-reptyr-0.6.2/000077500000000000000000000000001246327676300144075ustar00rootroot00000000000000reptyr-reptyr-0.6.2/.gitignore000066400000000000000000000000351246327676300163750ustar00rootroot00000000000000reptyr ptrace *.o /.vagrant/ reptyr-reptyr-0.6.2/.travis.yml000066400000000000000000000003011246327676300165120ustar00rootroot00000000000000script: make test env: - CFLAGS=-m32 LDFLAGS=-m32 DISABLE_TESTS=1 - CFLAGS=-m64 LDFLAGS=-m64 before_install: - sudo apt-get update - sudo apt-get -y install gcc-multilib python-pexpect reptyr-reptyr-0.6.2/COPYING000066400000000000000000000020441246327676300154420ustar00rootroot00000000000000Copyright (C) 2011 by Nelson Elhage Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. reptyr-reptyr-0.6.2/ChangeLog000066400000000000000000000015751246327676300161710ustar00rootroot00000000000000* 0.6.2 (Jan 31, 2015) - Fix tty-stealing on i386 * 0.6.1 (Jan 18, 2015) - Fix compilation when chown is declared as `warn_unused_result`` * 0.6 (Jan 4, 2015) - Add -T ("tty-stealing") mode, which works on processes with children. See https://blog.nelhage.com/2014/08/new-reptyr-feature-tty-stealing/ - Added FreeBSD support - Minor bugfixes * 0.5 (Jun 5, 2013) - Add an early check and error if attaching a process with children. * 0.4 (Aug 16, 2012) - Add support for scripting with the -l flag. - Add a French translation of the man page - Add a -V flag * 0.3 (May 27, 2011) - Add support for attaching 32-bit programs on x86_64. - Fix a bug on ARM. * 0.2 (Mar 1, 2011) - Add a man page - Add a 'make install' target - Improve detection of which fd's to attach, - Add a '-s' option to attach programs not currently on a tty. * 0.1 (Jan 27, 2011) - Initial release reptyr-reptyr-0.6.2/Makefile000066400000000000000000000025701246327676300160530ustar00rootroot00000000000000override CFLAGS+=-Wall -Werror -D_GNU_SOURCE -g OBJS=reptyr.o reallocarray.o attach.o UNAME_S := $(shell uname -s) ifeq ($(UNAME_S),Linux) OBJS += platform/linux/linux_ptrace.o platform/linux/linux.o endif ifeq ($(UNAME_S),FreeBSD) OBJS += platform/freebsd/freebsd_ptrace.o platform/freebsd/freebsd.o LDFLAGS += -lprocstat endif # Note that because of how Make works, this can be overriden from the # command-line. # # e.g. install to /usr with `make PREFIX=/usr` PREFIX=/usr/local all: reptyr reptyr: $(OBJS) ifeq ($(DISABLE_TESTS),) test: reptyr test/victim PHONY python test/basic.py python test/tty-steal.py else test: all endif VICTIM_CFLAGS ?= $(CFLAGS) VICTIM_LDFLAGS ?= $(LDFLAGS) test/victim: test/victim.o test/victim: override CFLAGS := $(VICTIM_CFLAGS) test/victim: override LDFLAGS := $(VICTIM_LDFLAGS) attach.o: reptyr.h ptrace.h reptyr.o: reptyr.h reallocarray.h ptrace.o: ptrace.h platform/platform.h $(wildcard platform/*/arch/*.h) clean: rm -f reptyr $(OBJS) test/victim.o test/victim install: reptyr install -d -m 755 $(DESTDIR)$(PREFIX)/bin/ install -m 755 reptyr $(DESTDIR)$(PREFIX)/bin/reptyr install -d -m 755 $(DESTDIR)$(PREFIX)/share/man/man1 install -m 644 reptyr.1 $(DESTDIR)$(PREFIX)/share/man/man1/reptyr.1 install -d -m 755 $(DESTDIR)$(PREFIX)/share/man/fr/man1 install -m 644 reptyr.fr.1 $(DESTDIR)$(PREFIX)/share/man/fr/man1/reptyr.1 .PHONY: PHONY reptyr-reptyr-0.6.2/NOTES000066400000000000000000000007101246327676300152200ustar00rootroot00000000000000Attaching: - Find an fd corresponding to the tty in the child - Open the new pty in the child - Copy the termios settings over - dup() it over the old ones - Make the new tty the controlling tty: - Fork a dummy child - Find all processes in the child's process group. - For each one, move them to the dummy child's process group - Make the child setsid() - Set the terminal as the controlling tty - Close the newly allocated tty - Detach reptyr-reptyr-0.6.2/README.md000066400000000000000000000067471246327676300157040ustar00rootroot00000000000000reptyr - A tool for "re-ptying" programs. ========================================= reptyr is a utility for taking an existing running program and attaching it to a new terminal. Started a long-running process over ssh, but have to leave and don't want to interrupt it? Just start a screen, use reptyr to grab it, and then kill the ssh session and head on home. USAGE ----- reptyr PID "reptyr PID" will grab the process with id PID and attach it to your current terminal. After attaching, the process will take input from and write output to the new terminal, including ^C and ^Z. (Unfortunately, if you background it, you will still have to run "bg" or "fg" in the old terminal. This is likely impossible to fix in a reasonable way without patching your shell.) "But wait, isn't this just screenify?" -------------------------------------- There's a shell script called "screenify" that's been going around the internet for nigh on 10 years now that uses gdb to (supposedly) accomplish the same thing. The difference is that reptyr works much, much, better. If you attach a "less" using screenify, it will still take input from the old terminal. If you attach an ncurses program using screenify, and resize the window, your program won't notice. If you attach a process with screenify, ^C in the new terminal won't work. reptyr fixes all of these problems, and is the only such tool I know of that does so. See below for some more details on how it accomplishes this. PORTABILITY ----------- reptyr is Linux-only. It uses ptrace to attach to the target and control it at the syscall level, so it is highly dependent on Linux's particular syscall API, syscalls, and terminal ioctl()s. A port to Solaris or BSD may be technically feasible, but would probably require significant re-architecting to abstract out the platform-specific bits. reptyr works on i386, x86_64, and ARM. Ports to other architectures should be straightforward, and should in most cases be as simple as adding an arch/ARCH.h file and adding a clause to the ifdef ladder in ptrace.c. ptrace_scope on Ubuntu Maverick and up -------------------------------------- `reptyr` depends on the `ptrace` system call to attach to the remote program. On Ubuntu Maverick and higher, this ability is disabled by default for security reasons. You can enable it temporarily by doing # echo 0 > /proc/sys/kernel/yama/ptrace_scope as root, or permanently by editing the file /etc/sysctl.d/10-ptrace.conf, which also contains more information about exactly what this setting accomplishes. reptyr -l --------- As a bonus feature, if you run "reptyr -l", reptyr will create a new pseudo-terminal pair with nothing attached to the slave end, and print its name out. If you are debugging a program in gdb, you can pass that name to "set inferior-pty". Because there is no existing program listening to that tty, this will work much better than passing an existing shell's terminal. How does it work? ----------------- The main thing that reptyr does that no one else does is that it actually changes the controlling terminal of the process you are attaching. I wrote a [blog post](https://blog.nelhage.com/2011/02/changing-ctty/) explaining just what the shenanigans involved are. PRONUNCIATION ------------- I pronounce it like "repeater", but since that's easily ambiguous, "re-P-T-Y-er" is also acceptable. CREDITS ------- reptyr was written by Nelson Elhage . Contact him with any questions or bug reports. URL --- [http://github.com/nelhage/reptyr]() reptyr-reptyr-0.6.2/Vagrantfile000066400000000000000000000012601246327676300165730ustar00rootroot00000000000000# -*- mode: ruby -*- # vi: set ft=ruby : VAGRANTFILE_API_VERSION = "2" # This Vagrantfile is only for FreeBSD testing; I do Linux development # natively. Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| config.vm.define "freebsd" do |machine| machine.vm.box = 'chef/freebsd-10.0' machine.vm.provision 'shell', inline: < #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ptrace.h" #include "reptyr.h" #include "reallocarray.h" #include "platform/platform.h" int fd_array_push(struct fd_array *fda, int fd) { int *tmp; if (fda->n == fda->allocated) { fda->allocated = fda->allocated ? 2 * fda->allocated : 2; tmp = xreallocarray(fda->fds, fda->allocated, sizeof *tmp); if (tmp == NULL) { free(fda->fds); fda->fds = NULL; fda->allocated = 0; return -1; } fda->fds = tmp; } fda->fds[fda->n++] = fd; return 0; } static void do_unmap(struct ptrace_child *child, child_addr_t addr, unsigned long len) { if (addr == (child_addr_t) - 1) return; do_syscall(child, munmap, (unsigned long)addr, len, 0, 0, 0, 0); } int do_setsid(struct ptrace_child *child) { int err = 0; struct ptrace_child dummy; err = do_syscall(child, fork, 0, 0, 0, 0, 0, 0); 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_detach_child(&dummy); //ptrace_wait(&dummy); do_syscall(child, wait4, dummy.pid, 0, WNOHANG, 0, 0, 0); return err; } int ignore_hup(struct ptrace_child *child, child_addr_t scratch_page) { int err; struct sigaction act = { .sa_handler = SIG_IGN, }; err = ptrace_memcpy_to_child(child, scratch_page, &act, sizeof act); if (err < 0) return err; err = do_syscall(child, rt_sigaction, SIGHUP, (unsigned long)scratch_page, 0, 8, 0, 0); return err; } /* * Wait for the specific pid to enter state 'T', or stopped. We have to pull the * /proc file rather than attaching with ptrace() and doing a wait() because * half the point of this exercise is for the process's real parent (the shell) * to see the TSTP. * * In case the process is masking or ignoring SIGTSTP, we time out after a * 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, int fd) { struct timeval start, now; struct timespec sleep; gettimeofday(&start, NULL); while (1) { gettimeofday(&now, NULL); if ((now.tv_sec > start.tv_sec && now.tv_usec > start.tv_usec) || (now.tv_sec - start.tv_sec > 1)) { error("Timed out waiting for child stop."); break; } /* * If anything goes wrong reading or parsing the stat node, just give * up. */ if (check_proc_stopped(pid, fd)) break; sleep.tv_sec = 0; sleep.tv_nsec = 10000000; nanosleep(&sleep, NULL); } } int copy_tty_state(pid_t pid, const char *pty) { int fd, err = EINVAL; struct termios tio; err = get_process_tty_termios(pid, &tio); if (err) return err; if ((fd = open(pty, O_RDONLY)) < 0) return -assert_nonzero(errno); if (tcsetattr(fd, TCSANOW, &tio) < 0) err = assert_nonzero(errno); close(fd); return -err; } int mmap_scratch(struct ptrace_child *child, child_addr_t *addr) { long mmap_syscall; child_addr_t scratch_page; 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, sysconf(_SC_PAGE_SIZE), PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0); //MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); if (scratch_page > (unsigned long) - 1000) { return -(signed long)scratch_page; } *addr = scratch_page; debug("Allocated scratch page: %lx", scratch_page); 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 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; } 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, 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, 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_syscall(&child, dup2, child_fd, child_tty_fds[i], 0, 0, 0, 0); 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 setup_steal_socket(struct steal_pty_state *steal) { strcpy(steal->tmpdir, "/tmp/reptyr.XXXXXX"); if (mkdtemp(steal->tmpdir) == NULL) return errno; steal->addr_un.sun_family = AF_UNIX; snprintf(steal->addr_un.sun_path, sizeof(steal->addr_un.sun_path), "%s/reptyr.sock", steal->tmpdir); if ((steal->sockfd = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) return errno; if (bind(steal->sockfd, &steal->addr, sizeof(steal->addr_un)) < 0) return errno; if (chown(steal->addr_un.sun_path, steal->target_stat.uid, steal->target_stat.gid) < 0) debug("chown %s: %s", steal->addr_un.sun_path, strerror(errno)); if (chown(steal->tmpdir, steal->target_stat.uid, steal->target_stat.gid) < 0) debug("chown %s: %s", steal->tmpdir, strerror(errno)); return 0; } int setup_steal_socket_child(struct steal_pty_state *steal) { int err; err = do_socketcall(&steal->child, steal->child_scratch + sysconf(_SC_PAGE_SIZE)/2, socket, AF_UNIX, SOCK_DGRAM, 0, 0, 0); if (err < 0) return -err; steal->child_fd = err; debug("Opened fd %d in the child.", steal->child_fd); err = ptrace_memcpy_to_child(&steal->child, steal->child_scratch, &steal->addr_un, sizeof(steal->addr_un)); if (err < 0) return steal->child.error; err = do_socketcall(&steal->child, steal->child_scratch + sysconf(_SC_PAGE_SIZE)/2, connect, steal->child_fd, steal->child_scratch, sizeof(steal->addr_un), 0, 0); if (err < 0) return -err; debug("Connected to the shared socket."); return 0; } int steal_child_pty(struct steal_pty_state *steal) { struct { struct msghdr msg; unsigned char buf[CMSG_SPACE(sizeof(int))]; } buf = {}; struct cmsghdr *cm; int err; buf.msg.msg_control = buf.buf; buf.msg.msg_controllen = CMSG_SPACE(sizeof(int)); cm = CMSG_FIRSTHDR(&buf.msg); cm->cmsg_level = SOL_SOCKET; cm->cmsg_type = SCM_RIGHTS; cm->cmsg_len = CMSG_LEN(sizeof(int)); memcpy(CMSG_DATA(cm), &steal->master_fds.fds[0], sizeof(int)); buf.msg.msg_controllen = cm->cmsg_len; // Relocate for the child buf.msg.msg_control = (void*)(steal->child_scratch + ((uint8_t*)buf.msg.msg_control - (uint8_t*)&buf)); if (ptrace_memcpy_to_child(&steal->child, steal->child_scratch, &buf, sizeof(buf))) { return steal->child.error; } steal->child.error = 0; err = do_socketcall(&steal->child, steal->child_scratch + sysconf(_SC_PAGE_SIZE)/2, sendmsg, steal->child_fd, steal->child_scratch, MSG_DONTWAIT, 0, 0); if (err < 0) { return steal->child.error ? steal->child.error : -err; } debug("Sent the pty fd, going to receive it."); buf.msg.msg_control = buf.buf; buf.msg.msg_controllen = CMSG_SPACE(sizeof(int)); err = recvmsg(steal->sockfd, &buf.msg, MSG_DONTWAIT); if (err < 0) { error("Error receiving message."); return errno; } debug("Got a message: %d bytes, %ld control", err, (long)buf.msg.msg_controllen); if (buf.msg.msg_controllen < CMSG_LEN(sizeof(int))) { error("No fd received?"); return EINVAL; } memcpy(&steal->ptyfd, CMSG_DATA(cm), sizeof(steal->ptyfd)); debug("Got tty fd: %d", steal->ptyfd); 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; 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 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, open, steal->child_scratch, O_RDWR, 0, 0, 0, 0); if (nullfd < 0) { return steal->child.error; } int i; for (i = 0; i < steal->master_fds.n; ++i) { do_syscall(&steal->child, dup2, nullfd, steal->master_fds.fds[i], 0, 0, 0, 0); } 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 steal_pty(pid_t pid, int *pty) { int err = 0; struct steal_pty_state steal = {}; long page_size = sysconf(_SC_PAGE_SIZE); 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); 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; } reptyr-reptyr-0.6.2/platform/000077500000000000000000000000001246327676300162335ustar00rootroot00000000000000reptyr-reptyr-0.6.2/platform/freebsd/000077500000000000000000000000001246327676300176455ustar00rootroot00000000000000reptyr-reptyr-0.6.2/platform/freebsd/arch/000077500000000000000000000000001246327676300205625ustar00rootroot00000000000000reptyr-reptyr-0.6.2/platform/freebsd/arch/amd64.h000066400000000000000000000045631246327676300216560ustar00rootroot00000000000000/* * Copyright (C) 2011 by Nelson Elhage * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include "x86_common.h" #define ARCH_HAVE_MULTIPLE_PERSONALITIES static struct ptrace_personality arch_personality[2] = { { offsetof(struct reg, r_rax), offsetof(struct reg, r_rdi), offsetof(struct reg, r_rsi), offsetof(struct reg, r_rdx), offsetof(struct reg, r_rcx), //offsetof(struct reg, r_r10), offsetof(struct reg, r_r8), offsetof(struct reg, r_r9), offsetof(struct reg, r_rip), }, { offsetof(struct reg, r_rax), offsetof(struct reg, r_rbx), offsetof(struct reg, r_rcx), offsetof(struct reg, r_rdx), offsetof(struct reg, r_rsi), offsetof(struct reg, r_rdi), offsetof(struct reg, r_rbp), offsetof(struct reg, r_rip), }, }; struct x86_personality x86_personality[2] = { { offsetof(struct reg, r_rax), }, { offsetof(struct reg, r_rax), }, }; struct syscall_numbers arch_syscall_numbers[2] = { #include "default-syscalls.h" #include "default-syscalls.h" }; int arch_get_personality(struct ptrace_child *child) { unsigned long cs; cs = arch_get_register(child,offsetof(struct reg, r_cs)); if (child->error) return -1; if (cs == 0x23) child->personality = 1; return 0; } reptyr-reptyr-0.6.2/platform/freebsd/arch/arm.h000066400000000000000000000045251246327676300215200ustar00rootroot00000000000000/* * Copyright (C) 2011 by Nelson Elhage * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #error ARM not yet supported for FreeBSD static struct ptrace_personality arch_personality[1] = { { offsetof(struct user, regs.uregs[0]), offsetof(struct user, regs.uregs[0]), offsetof(struct user, regs.uregs[1]), offsetof(struct user, regs.uregs[2]), offsetof(struct user, regs.uregs[3]), offsetof(struct user, regs.uregs[4]), offsetof(struct user, regs.uregs[5]), offsetof(struct user, regs.ARM_pc), } }; static inline void arch_fixup_regs(struct ptrace_child *child) { child->user.regs.ARM_pc -= 4; } static inline int arch_set_syscall(struct ptrace_child *child, unsigned long sysno) { return ptrace_command(child, PTRACE_SET_SYSCALL, 0, sysno); } static inline int arch_save_syscall(struct ptrace_child *child) { unsigned long swi; swi = ptrace_command(child, PTRACE_PEEKTEXT, child->user.regs.ARM_pc); if (child->error) return -1; if (swi == 0xef000000) child->saved_syscall = child->user.regs.uregs[7]; else child->saved_syscall = (swi & 0x000fffff); return 0; } static inline int arch_restore_syscall(struct ptrace_child *child) { return arch_set_syscall(child, child->saved_syscall); } reptyr-reptyr-0.6.2/platform/freebsd/arch/default-syscalls.h000066400000000000000000000010451246327676300242120ustar00rootroot00000000000000#define SC(name) .nr_##name = SYS_##name { #ifdef SYS_mmap SC(mmap), #else .nr_mmap = -1, #endif #ifdef SYS_mmap2 SC(mmap2), #else .nr_mmap2 = -1, #endif SC(munmap), SC(getsid), SC(setsid), SC(setpgid), SC(fork), SC(wait4), #ifdef SYS_signal SC(signal), #else .nr_signal = -1, #endif .nr_rt_sigaction = SYS_sigaction, SC(open), SC(close), SC(ioctl), SC(dup2), #ifdef SYS_socketcall SC(socketcall), #else SC(socket), SC(connect), SC(sendmsg), #endif }, #undef SC reptyr-reptyr-0.6.2/platform/freebsd/arch/i386.h000066400000000000000000000031151246327676300214240ustar00rootroot00000000000000/* * Copyright (C) 2011 by Nelson Elhage * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include "x86_common.h" static struct ptrace_personality arch_personality[1] = { { offsetof(struct reg, r_eax), offsetof(struct reg, r_ebx), offsetof(struct reg, r_ecx), offsetof(struct reg, r_edx), offsetof(struct reg, r_esi), offsetof(struct reg, r_edi), offsetof(struct reg, r_ebp), offsetof(struct reg, r_eip), } }; struct x86_personality x86_personality[1] = { { offsetof(struct reg, r_eax), } }; reptyr-reptyr-0.6.2/platform/freebsd/arch/x86_common.h000066400000000000000000000054741246327676300227420ustar00rootroot00000000000000/* * Copyright (C) 2011 by Nelson Elhage * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ struct x86_personality { size_t ax; }; struct x86_personality x86_personality[]; static inline struct x86_personality *x86_pers(struct ptrace_child *child) { return &x86_personality[child->personality]; } static inline void arch_fixup_regs(struct ptrace_child *child) { struct x86_personality *x86pers = x86_pers(child); struct ptrace_personality *pers = personality(child); struct reg *regs = &child->regs; #define ptr(user, off) ((unsigned long*)((void*)(user)+(off))) *ptr(regs, pers->reg_ip) -= 2; *ptr(regs, x86pers->ax) = child->saved_syscall; //*ptr(user, x86pers->ax) = *ptr(user, x86pers->orig_ax); //https://lists.freebsd.org/pipermail/freebsd-hackers/2009-July/029206.html } static inline unsigned long arch_get_register(struct ptrace_child *child, unsigned long oft){ int ret; struct reg regs; ret = ptrace_command(child, PT_GETREGS, ®s); return *ptr(®s,oft); } static inline void arch_set_register(struct ptrace_child *child, unsigned long oft, unsigned long val){ int ret; struct reg regs; ret = ptrace_command(child, PT_GETREGS, ®s); *ptr(®s,oft)=val; ret = ptrace_command(child, PT_SETREGS, ®s); } static inline int arch_save_syscall(struct ptrace_child *child) { child->saved_syscall = *ptr(&child->regs, x86_pers(child)->ax); return 0; } static inline int arch_get_syscall(struct ptrace_child *child, unsigned long sysno) { return *ptr(&child->regs, personality(child)->syscall_rv); //return ptrace_command(child, PTRACE_POKEUSER, //x86_pers(child)->orig_ax, //sysno); } static inline int arch_restore_syscall(struct ptrace_child *child) { return 0; } #undef ptr reptyr-reptyr-0.6.2/platform/freebsd/freebsd.c000066400000000000000000000156231246327676300214320ustar00rootroot00000000000000/* * Copyright (C) 2014 Christian Heckendorf * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #ifdef __FreeBSD__ #include "freebsd.h" #include "../platform.h" #include "../../reptyr.h" #include "../../ptrace.h" void check_ptrace_scope(void) { } int check_pgroup(pid_t target) { struct procstat *procstat; struct kinfo_proc *kp; pid_t pg; unsigned int cnt; pg = getpgid(target); procstat = procstat_open_sysctl(); kp = procstat_getprocs(procstat, KERN_PROC_PGRP, pg, &cnt); procstat_freeprocs(procstat, kp); procstat_close(procstat); if (cnt > 1) { error("Process %d shares a process group with %d other processes. Unable to attach.\n", target, cnt - 1); return EINVAL; } return 0; } int check_proc_stopped(pid_t pid, int fd) { struct procstat *procstat; struct kinfo_proc *kp; int state; unsigned int cnt; procstat = procstat_open_sysctl(); kp = procstat_getprocs(procstat, KERN_PROC_PID, pid, &cnt); if (cnt > 0) state = kp->ki_stat; procstat_freeprocs(procstat, kp); procstat_close(procstat); if (cnt < 1) return 1; if (state == SSTOP) return 1; return 0; } struct filestat_list* get_procfiles(pid_t pid, struct kinfo_proc **kp, struct procstat **procstat, unsigned int *cnt) { int mflg = 0; // include mmapped files (*procstat) = procstat_open_sysctl(); (*kp) = procstat_getprocs(*procstat, KERN_PROC_PID, pid, cnt); if ((*kp) == NULL || *cnt < 1) return NULL; return procstat_getfiles(*procstat, *kp, mflg); } int *get_child_tty_fds(struct ptrace_child *child, int statfd, int *count) { struct filestat *fst; struct filestat_list *head; struct procstat *procstat; struct kinfo_proc *kp; unsigned int cnt; struct fd_array fds = {}; struct vnstat vn; int er; char errbuf[_POSIX2_LINE_MAX]; head = get_procfiles(child->pid, &kp, &procstat, &cnt); STAILQ_FOREACH(fst, head, next) { if (fst->fs_type == PS_FST_TYPE_VNODE) { er = procstat_get_vnode_info(procstat, fst, &vn, errbuf); if (er != 0) { error("%s", errbuf); goto out; } if (vn.vn_dev == kp->ki_tdev) { if (fd_array_push(&fds, fst->fs_fd) != 0) { error("Unable to allocate memory for fd array."); goto out; } } } } out: procstat_freefiles(procstat, head); procstat_freeprocs(procstat, kp); procstat_close(procstat); *count = fds.n; debug("Found %d tty fds in child %d.", fds.n, child->pid); return fds.fds; } // Find the PID of the terminal emulator for `target's terminal. // // We assume that the terminal emulator is the parent of the session // leader. This is true in most cases, although in principle you can // construct situations where it is false. We should fail safe later // on if this turns out to be wrong, however. int find_terminal_emulator(struct steal_pty_state *steal) { struct procstat *procstat; struct kinfo_proc *kp; unsigned int cnt; procstat = procstat_open_sysctl(); kp = procstat_getprocs(procstat, KERN_PROC_PID, steal->target_stat.sid, &cnt); if (kp && cnt > 0) steal->emulator_pid = kp->ki_ppid; procstat_freeprocs(procstat, kp); procstat_close(procstat); return 0; } int get_terminal_state(struct steal_pty_state *steal, pid_t target) { struct procstat *procstat; struct kinfo_proc *kp; unsigned int cnt; int err = 0; procstat = procstat_open_sysctl(); kp = procstat_getprocs(procstat, KERN_PROC_PID, target, &cnt); if (kp == NULL || cnt < 1) goto done; if (kp->ki_tdev == NODEV) { error("Child is not connected to a pseudo-TTY. Unable to steal TTY."); err = EINVAL; goto done; } if ((err = find_terminal_emulator(steal))) return err; done: procstat_freeprocs(procstat, kp); procstat_close(procstat); return err; } int find_master_fd(struct steal_pty_state *steal) { error("How do I find master in FreeBSD? FIXME."); return EINVAL; } int get_pt() { return posix_openpt(O_RDWR | O_NOCTTY); } int get_process_tty_termios(pid_t pid, struct termios *tio) { int err = EINVAL; struct kinfo_proc *kp; unsigned int cnt; struct filestat_list *head; struct filestat *fst; struct procstat *procstat; int fd = -1; head = get_procfiles(pid, &kp, &procstat, &cnt); STAILQ_FOREACH(fst, head, next) { if (fst->fs_type == PS_FST_TYPE_VNODE) { if (fst->fs_path) { fd = open(fst->fs_path, O_RDONLY); if (fd >= 0 && isatty(fd)) { if (tcgetattr(fd, tio) < 0) { err = -assert_nonzero(errno); } else { close(fd); err = 0; goto done; } } close(fd); } } } done: procstat_freefiles(procstat, head); procstat_freeprocs(procstat, kp); procstat_close(procstat); return err; } void move_process_group(struct ptrace_child *child, pid_t from, pid_t to) { struct procstat *procstat; struct kinfo_proc *kp; unsigned int cnt; int i; int err; procstat = procstat_open_sysctl(); kp = procstat_getprocs(procstat, KERN_PROC_PGRP, from, &cnt); for (i = 0; i < cnt; i++) { debug("Change pgid for pid %d to %d", kp[i].ki_pid, to); err = do_syscall(child, setpgid, kp[i].ki_pid, to, 0, 0, 0, 0); if (err < 0) error(" failed: %s", strerror(-err)); } procstat_freeprocs(procstat, kp); procstat_close(procstat); } void copy_user(struct ptrace_child *d, struct ptrace_child *s) { memcpy(&d->regs, &s->regs, sizeof(s->regs)); } #endif reptyr-reptyr-0.6.2/platform/freebsd/freebsd.h000066400000000000000000000036461246327676300214410ustar00rootroot00000000000000/* * Copyright (C) 2014 Christian Heckendorf * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #ifndef FREEBSD_H #define FREEBSD_H #ifdef __FreeBSD__ #include #include #include #include #include #include #include #include #include #include #include #include #include #define do_socketcall(child, name, a0, a1, a2, a3, a4) \ ({ \ int __ret=-1; \ if (ptrace_syscall_numbers((child))->nr_##name) { \ __ret = do_syscall((child), name, a0, a1, a2, a3, a4, 0); \ } \ __ret; }) #endif #endif reptyr-reptyr-0.6.2/platform/freebsd/freebsd_ptrace.c000066400000000000000000000222371246327676300227670ustar00rootroot00000000000000/* * Copyright (C) 2011 by Nelson Elhage * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../../ptrace.h" #include "../platform.h" #define min(x, y) ({ \ typeof(x) _min1 = (x); \ typeof(y) _min2 = (y); \ _min1 < _min2 ? _min1 : _min2; }) static int __ptrace_command(struct ptrace_child *child, int req, void *, int); #define ptrace_command(cld, req, ...) _ptrace_command(cld, req, ## __VA_ARGS__, NULL, NULL) #define _ptrace_command(cld, req, addr, data, ...) __ptrace_command((cld), (req), (void*)(addr), (int)(data)) struct ptrace_personality { size_t syscall_rv; size_t syscall_arg0; size_t syscall_arg1; size_t syscall_arg2; size_t syscall_arg3; size_t syscall_arg4; size_t syscall_arg5; size_t reg_ip; }; static struct ptrace_personality *personality(struct ptrace_child *child); #if defined(__amd64__) #include "arch/amd64.h" #elif defined(__i386__) #include "arch/i386.h" #elif defined(__arm__) #include "arch/arm.h" #else #error Unsupported architecture. #endif #ifndef ARCH_HAVE_MULTIPLE_PERSONALITIES int arch_get_personality(struct ptrace_child *child) { return 0; } struct syscall_numbers arch_syscall_numbers[] = { #include "arch/default-syscalls.h" }; #endif static struct ptrace_personality *personality(struct ptrace_child *child) { return &arch_personality[child->personality]; } struct syscall_numbers *ptrace_syscall_numbers(struct ptrace_child *child) { return &arch_syscall_numbers[child->personality]; } int ptrace_attach_child(struct ptrace_child *child, pid_t pid) { memset(child, 0, sizeof(*child)); child->pid = pid; if (ptrace_command(child, PT_ATTACH, 0, 0) < 0) return -1; return ptrace_finish_attach(child, pid); } int ptrace_finish_attach(struct ptrace_child *child, pid_t pid) { memset(child, 0, sizeof(*child)); child->pid = pid; if (ptrace_wait(child) < 0) goto detach; ptrace_command(child, PT_FOLLOW_FORK, 0, 1); if (arch_get_personality(child)) goto detach; kill(pid, SIGCONT); return 0; detach: /* Don't clobber child->error */ ptrace(PT_DETACH, child->pid, (caddr_t)1, 0); return -1; } int ptrace_detach_child(struct ptrace_child *child) { if (ptrace_command(child, PT_DETACH, (caddr_t)1, 0) < 0) return -1; child->state = ptrace_detached; return 0; } int ptrace_wait(struct ptrace_child *child) { struct ptrace_lwpinfo lwpinfo; if (waitpid(child->pid, &child->status, 0) < 0) { child->error = errno; return -1; } if (WIFEXITED(child->status) || WIFSIGNALED(child->status)) { child->state = ptrace_exited; } else if (WIFSTOPPED(child->status)) { ptrace_command(child, PT_LWPINFO, &lwpinfo, sizeof(lwpinfo)); child->state = ptrace_stopped; if (lwpinfo.pl_flags & PL_FLAG_FORKED) child->forked_pid = lwpinfo.pl_child_pid; if (lwpinfo.pl_flags & PL_FLAG_SCE) child->state = ptrace_at_syscall; if (lwpinfo.pl_flags & PL_FLAG_SCX) child->state = ptrace_after_syscall; } else { child->error = EINVAL; return -1; } return 0; } int ptrace_advance_to_state(struct ptrace_child *child, enum child_state desired) { int err; while (child->state != desired) { switch (desired) { case ptrace_after_syscall: if (WIFSTOPPED(child->status) && WSTOPSIG(child->status) == SIGSEGV) { child->error = EAGAIN; return -1; } err = ptrace_command(child, PT_TO_SCX, (caddr_t)1, 0); break; case ptrace_at_syscall: if (WIFSTOPPED(child->status) && WSTOPSIG(child->status) == SIGSEGV) { child->error = EAGAIN; return -1; } err = ptrace_command(child, PT_TO_SCE, (caddr_t)1, 0); break; case ptrace_running: return ptrace_command(child, PT_CONTINUE, (caddr_t)1, 0); case ptrace_stopped: err = kill(child->pid, SIGSTOP); if (err < 0) child->error = errno; break; default: child->error = EINVAL; return -1; } if (err < 0) return err; if (ptrace_wait(child) < 0) return -1; } return 0; } int ptrace_save_regs(struct ptrace_child *child) { if (ptrace_advance_to_state(child, ptrace_at_syscall) < 0) return -1; if (ptrace_command(child, PT_GETREGS, &child->regs, 0) < 0) return -1; arch_save_syscall(child); arch_fixup_regs(child); if (arch_save_syscall(child) < 0) return -1; return 0; } int ptrace_restore_regs(struct ptrace_child *child) { int err; err = ptrace_command(child, PT_SETREGS, &child->regs, 0); if (err < 0) return err; return arch_restore_syscall(child); } unsigned long ptrace_remote_syscall(struct ptrace_child *child, unsigned long sysno, unsigned long p0, unsigned long p1, unsigned long p2, unsigned long p3, unsigned long p4, unsigned long p5) { unsigned long rv; if (ptrace_advance_to_state(child, ptrace_at_syscall) < 0) return -1; #define setreg(r, v) arch_set_register(child,personality(child)->r,v) //if (arch_set_syscall(child, sysno) < 0) //return -1; setreg(syscall_rv, sysno); setreg(syscall_arg0, p0); setreg(syscall_arg1, p1); setreg(syscall_arg2, p2); setreg(syscall_arg3, p3); setreg(syscall_arg4, p4); setreg(syscall_arg5, p5); if (ptrace_advance_to_state(child, ptrace_after_syscall) < 0) return -1; rv = arch_get_register(child, personality(child)->syscall_rv); if (child->error) return -1; setreg(reg_ip, *(unsigned long*)((void*)&child->regs + personality(child)->reg_ip)); #undef setreg return rv; } int ptrace_memcpy_to_child(struct ptrace_child *child, child_addr_t dst, const void *src, size_t n) { int scratch; while (n >= sizeof(int)) { if (ptrace_command(child, PT_WRITE_D, dst, *((int*)src)) < 0) return -1; dst += sizeof(int); src += sizeof(int); n -= sizeof(int); } if (n) { scratch = ptrace_command(child, PT_READ_D, dst); if (child->error) return -1; memcpy(&scratch, src, n); if (ptrace_command(child, PT_WRITE_D, dst, scratch) < 0) return -1; } return 0; } int ptrace_memcpy_from_child(struct ptrace_child *child, void *dst, child_addr_t src, size_t n) { int scratch; while (n) { scratch = ptrace_command(child, PT_READ_D, src); if (child->error) return -1; memcpy(dst, &scratch, min(n, sizeof(int))); dst += sizeof(int); src += sizeof(int); if (n >= sizeof(int)) n -= sizeof(int); else n = 0; } return 0; } static int __ptrace_command(struct ptrace_child *child, int req, void *addr, int data) { long rv; errno = 0; rv = ptrace(req, child->pid, addr, data); child->error = errno; return rv; } #ifdef BUILD_PTRACE_MAIN int main(int argc, char **argv) { struct ptrace_child child; pid_t pid; if (argc < 2) { printf("Usage: %s pid\n", argv[0]); return 1; } pid = atoi(argv[1]); assert(!ptrace_attach_child(&child, pid)); assert(!ptrace_save_regs(&child)); printf("mmap = %lx\n", ptrace_remote_syscall(&child, 477, 0, 4096, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0)); //reset_user_struct(&child.regs); assert(!ptrace_restore_regs(&child)); assert(!ptrace_detach_child(&child)); return 0; } #endif reptyr-reptyr-0.6.2/platform/linux/000077500000000000000000000000001246327676300173725ustar00rootroot00000000000000reptyr-reptyr-0.6.2/platform/linux/arch/000077500000000000000000000000001246327676300203075ustar00rootroot00000000000000reptyr-reptyr-0.6.2/platform/linux/arch/amd64.h000066400000000000000000000064011246327676300213740ustar00rootroot00000000000000/* * Copyright (C) 2011 by Nelson Elhage * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include "x86_common.h" #define ARCH_HAVE_MULTIPLE_PERSONALITIES static struct ptrace_personality arch_personality[2] = { { offsetof(struct user, regs.rax), offsetof(struct user, regs.rdi), offsetof(struct user, regs.rsi), offsetof(struct user, regs.rdx), offsetof(struct user, regs.r10), offsetof(struct user, regs.r8), offsetof(struct user, regs.r9), offsetof(struct user, regs.rip), }, { offsetof(struct user, regs.rax), offsetof(struct user, regs.rbx), offsetof(struct user, regs.rcx), offsetof(struct user, regs.rdx), offsetof(struct user, regs.rsi), offsetof(struct user, regs.rdi), offsetof(struct user, regs.rbp), offsetof(struct user, regs.rip), }, }; struct x86_personality x86_personality[2] = { { offsetof(struct user, regs.orig_rax), offsetof(struct user, regs.rax), }, { offsetof(struct user, regs.orig_rax), offsetof(struct user, regs.rax), }, }; struct syscall_numbers arch_syscall_numbers[2] = { #include "default-syscalls.h" { /* * These don't seem to be available in any convenient header. We could * include unistd_32.h, but those definitions would conflict with the * standard ones. So, let's just hardcode the values for now. Probably * we should generate this from unistd_32.h during the build process or * soemthing. */ .nr_mmap = 90, .nr_mmap2 = 192, .nr_munmap = 91, .nr_getsid = 147, .nr_setsid = 66, .nr_setpgid = 57, .nr_fork = 2, .nr_wait4 = 114, .nr_signal = 48, .nr_rt_sigaction = 174, .nr_open = 5, .nr_close = 6, .nr_ioctl = 54, .nr_dup2 = 63, .nr_socketcall = 102, } }; int arch_get_personality(struct ptrace_child *child) { unsigned long cs; cs = ptrace_command(child, PTRACE_PEEKUSER, offsetof(struct user, regs.cs)); if (child->error) return -1; if (cs == 0x23) child->personality = 1; return 0; } reptyr-reptyr-0.6.2/platform/linux/arch/arm.h000066400000000000000000000044521246327676300212440ustar00rootroot00000000000000/* * Copyright (C) 2011 by Nelson Elhage * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ static struct ptrace_personality arch_personality[1] = { { offsetof(struct user, regs.uregs[0]), offsetof(struct user, regs.uregs[0]), offsetof(struct user, regs.uregs[1]), offsetof(struct user, regs.uregs[2]), offsetof(struct user, regs.uregs[3]), offsetof(struct user, regs.uregs[4]), offsetof(struct user, regs.uregs[5]), offsetof(struct user, regs.ARM_pc), } }; static inline void arch_fixup_regs(struct ptrace_child *child) { child->user.regs.ARM_pc -= 4; } static inline int arch_set_syscall(struct ptrace_child *child, unsigned long sysno) { return ptrace_command(child, PTRACE_SET_SYSCALL, 0, sysno); } static inline int arch_save_syscall(struct ptrace_child *child) { unsigned long swi; swi = ptrace_command(child, PTRACE_PEEKTEXT, child->user.regs.ARM_pc); if (child->error) return -1; if (swi == 0xef000000) child->saved_syscall = child->user.regs.uregs[7]; else child->saved_syscall = (swi & 0x000fffff); return 0; } static inline int arch_restore_syscall(struct ptrace_child *child) { return arch_set_syscall(child, child->saved_syscall); } reptyr-reptyr-0.6.2/platform/linux/arch/default-syscalls.h000066400000000000000000000010321246327676300237330ustar00rootroot00000000000000#define SC(name) .nr_##name = __NR_##name { #ifdef __NR_mmap SC(mmap), #else .nr_mmap = -1, #endif #ifdef __NR_mmap2 SC(mmap2), #else .nr_mmap2 = -1, #endif SC(munmap), SC(getsid), SC(setsid), SC(setpgid), SC(fork), SC(wait4), #ifdef __NR_signal SC(signal), #else .nr_signal = -1, #endif SC(rt_sigaction), SC(open), SC(close), SC(ioctl), SC(dup2), #ifdef __NR_socketcall SC(socketcall), #else SC(socket), SC(connect), SC(sendmsg), #endif }, #undef SC reptyr-reptyr-0.6.2/platform/linux/arch/i386.h000066400000000000000000000032371246327676300211560ustar00rootroot00000000000000/* * Copyright (C) 2011 by Nelson Elhage * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include "x86_common.h" static struct ptrace_personality arch_personality[1] = { { offsetof(struct user, regs.eax), offsetof(struct user, regs.ebx), offsetof(struct user, regs.ecx), offsetof(struct user, regs.edx), offsetof(struct user, regs.esi), offsetof(struct user, regs.edi), offsetof(struct user, regs.ebp), offsetof(struct user, regs.eip), } }; struct x86_personality x86_personality[1] = { { offsetof(struct user, regs.orig_eax), offsetof(struct user, regs.eax), } }; reptyr-reptyr-0.6.2/platform/linux/arch/x86_common.h000066400000000000000000000043101246327676300224530ustar00rootroot00000000000000/* * Copyright (C) 2011 by Nelson Elhage * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ struct x86_personality { size_t orig_ax; size_t ax; }; struct x86_personality x86_personality[]; static inline struct x86_personality *x86_pers(struct ptrace_child *child) { return &x86_personality[child->personality]; } static inline void arch_fixup_regs(struct ptrace_child *child) { struct x86_personality *x86pers = x86_pers(child); struct ptrace_personality *pers = personality(child); struct user *user = &child->user; #define ptr(user, off) ((unsigned long*)((void*)(user)+(off))) *ptr(user, pers->reg_ip) -= 2; *ptr(user, x86pers->ax) = *ptr(user, x86pers->orig_ax); } static inline int arch_set_syscall(struct ptrace_child *child, unsigned long sysno) { return ptrace_command(child, PTRACE_POKEUSER, x86_pers(child)->orig_ax, sysno); } static inline int arch_save_syscall(struct ptrace_child *child) { child->saved_syscall = *ptr(&child->user, x86_pers(child)->orig_ax); return 0; } static inline int arch_restore_syscall(struct ptrace_child *child) { return 0; } #undef ptr reptyr-reptyr-0.6.2/platform/linux/linux.c000066400000000000000000000267731246327676300207140ustar00rootroot00000000000000/* * Copyright (C) 2011 by Nelson Elhage * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #ifdef __linux__ #include "linux.h" #include "../platform.h" #include "../../reptyr.h" #include "../../ptrace.h" #include int parse_proc_stat(int statfd, struct proc_stat *out) { char buf[1024]; int n; unsigned dev; lseek(statfd, 0, SEEK_SET); if (read(statfd, buf, sizeof buf) < 0) return assert_nonzero(errno); n = sscanf(buf, "%d (%16[^)]) %c %d %d %d %u", &out->pid, out->comm, &out->state, &out->ppid, &out->pgid, &out->sid, &dev); if (n == EOF) return assert_nonzero(errno); if (n != 7) { return EINVAL; } out->ctty = dev; struct stat st; if (fstat(statfd, &st) != 0) return assert_nonzero(errno); out->uid = st.st_uid; out->gid = st.st_gid; return 0; } int read_proc_stat(pid_t pid, struct proc_stat *out) { char stat_path[PATH_MAX]; int statfd; int 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; } err = parse_proc_stat(statfd, out); close(statfd); return err; } // Find the PID of the terminal emulator for `target's terminal. // // We assume that the terminal emulator is the parent of the session // leader. This is true in most cases, although in principle you can // construct situations where it is false. We should fail safe later // on if this turns out to be wrong, however. int find_terminal_emulator(struct steal_pty_state *steal) { debug("session leader of pid %d = %d", (int)steal->target_stat.pid, (int)steal->target_stat.sid); struct proc_stat leader_st; int err; if ((err = read_proc_stat(steal->target_stat.sid, &leader_st))) return err; debug("found terminal emulator process: %d", (int) leader_st.ppid); steal->emulator_pid = leader_st.ppid; return 0; } void check_ptrace_scope(void) { int fd = open("/proc/sys/kernel/yama/ptrace_scope", O_RDONLY); if (fd >= 0) { char buf[256]; int n; n = read(fd, buf, sizeof buf); close(fd); if (n > 0) { if (!atoi(buf)) { return; } } } else if (errno == ENOENT) return; fprintf(stderr, "The kernel denied permission while attaching. If your uid matches\n"); fprintf(stderr, "the target's, check the value of /proc/sys/kernel/yama/ptrace_scope.\n"); fprintf(stderr, "For more information, see /etc/sysctl.d/10-ptrace.conf\n"); } int check_pgroup(pid_t target) { pid_t pg; DIR *dir; struct dirent *d; pid_t pid; char *p; int err = 0; struct proc_stat pid_stat; debug("Checking for problematic process group members..."); pg = getpgid(target); if (pg < 0) { error("Unable to get pgid for pid %d", (int)target); return errno; } if ((dir = opendir("/proc/")) == NULL) return assert_nonzero(errno); while ((d = readdir(dir)) != NULL) { if (d->d_name[0] == '.') continue; pid = strtol(d->d_name, &p, 10); if (*p) continue; if (pid == target) continue; if (getpgid(pid) == pg) { /* * We are actually being somewhat overly-conservative here * -- if pid is a child of target, and has not yet called * execve(), reptyr's setpgid() strategy may suffice. That * is a fairly rare case, and annoying to check for, so * for now let's just bail out. */ if ((err = read_proc_stat(pid, &pid_stat))) { memcpy(pid_stat.comm, "???", 4); } error("Process %d (%.*s) shares %d's process group. Unable to attach.\n" "(This most commonly means that %d has suprocesses).", (int)pid, TASK_COMM_LENGTH, pid_stat.comm, (int)target, (int)target); err = EINVAL; goto out; } } out: closedir(dir); return err; } int check_proc_stopped(pid_t pid, int fd) { struct proc_stat st; if (parse_proc_stat(fd, &st)) return 1; if (st.state == 'T') return 1; return 0; } int *get_child_tty_fds(struct ptrace_child *child, int statfd, int *count) { struct proc_stat child_status; struct stat tty_st, console_st, st; char buf[PATH_MAX]; struct fd_array fds = {}; DIR *dir; struct dirent *d; debug("Looking up fds for tty in child."); if ((child->error = parse_proc_stat(statfd, &child_status))) return NULL; debug("Resolved child tty: %x", (unsigned)child_status.ctty); if (stat("/dev/tty", &tty_st) < 0) { child->error = assert_nonzero(errno); error("Unable to stat /dev/tty"); return NULL; } if (stat("/dev/console", &console_st) < 0) { child->error = errno; error("Unable to stat /dev/console"); return NULL; } snprintf(buf, sizeof buf, "/proc/%d/fd/", child->pid); if ((dir = opendir(buf)) == NULL) return NULL; while ((d = readdir(dir)) != NULL) { if (d->d_name[0] == '.') continue; snprintf(buf, sizeof buf, "/proc/%d/fd/%s", child->pid, d->d_name); if (stat(buf, &st) < 0) continue; if (st.st_rdev == child_status.ctty || st.st_rdev == tty_st.st_rdev || st.st_rdev == console_st.st_rdev) { debug("Found an alias for the tty: %s", d->d_name); if (fd_array_push(&fds, atoi(d->d_name)) != 0) { child->error = assert_nonzero(errno); error("Unable to allocate memory for fd array."); goto out; } } } out: *count = fds.n; closedir(dir); return fds.fds; } int get_terminal_state(struct steal_pty_state *steal, pid_t target) { int err; if ((err = read_proc_stat(target, &steal->target_stat))) return err; if (major(steal->target_stat.ctty) != UNIX98_PTY_SLAVE_MAJOR) { error("Child is not connected to a pseudo-TTY. Unable to steal TTY."); return EINVAL; } if ((err = find_terminal_emulator(steal))) return err; return 0; } // ptmx(4) and Linux Documentation/devices.txt document // /dev/ptmx has having major 5 and minor 2. I can't find any // constants in headers after a brief glance that I should be // using here. #define PTMX_DEVICE (makedev(5, 2)) // Find the fd(s) in the terminal emulator process that corresponds to // the master side of the target's pty. Store the result in // steal->master_fds. int find_master_fd(struct steal_pty_state *steal) { DIR *dir; struct dirent *d; struct stat st; int err; char buf[PATH_MAX]; snprintf(buf, sizeof buf, "/proc/%d/fd/", steal->child.pid); if ((dir = opendir(buf)) == NULL) return errno; while ((d = readdir(dir)) != NULL) { if (d->d_name[0] == '.') continue; snprintf(buf, sizeof buf, "/proc/%d/fd/%s", steal->child.pid, d->d_name); if (stat(buf, &st) < 0) continue; debug("Checking fd: %s: st_dev=%x", d->d_name, (int)st.st_rdev); if (st.st_rdev != PTMX_DEVICE) continue; debug("found a ptmx fd: %s", d->d_name); err = do_syscall(&steal->child, ioctl, atoi(d->d_name), TIOCGPTN, steal->child_scratch, 0, 0, 0); if (err < 0) { debug(" error doing TIOCGPTN: %s", strerror(-err)); continue; } int ptn; err = ptrace_memcpy_from_child(&steal->child, &ptn, steal->child_scratch, sizeof(ptn)); if (err < 0) { debug(" error getting ptn: %s", strerror(steal->child.error)); continue; } if (ptn == (int)minor(steal->target_stat.ctty)) { debug("found a master fd: %d", atoi(d->d_name)); if (fd_array_push(&steal->master_fds, atoi(d->d_name)) != 0) { error("unable to allocate memory for fd array!"); return ENOMEM; } } } if (steal->master_fds.n == 0) { return ESRCH; } return 0; } /* Homebrew posix_openpt() */ int get_pt() { return open("/dev/ptmx", O_RDWR | O_NOCTTY); } int get_process_tty_termios(pid_t pid, struct termios *tio) { int err = EINVAL; char buf[PATH_MAX]; int i; int fd; for (i = 0; i < 3 && err; i++) { err = 0; snprintf(buf, sizeof buf, "/proc/%d/fd/%d", pid, i); if ((fd = open(buf, O_RDONLY)) < 0) { err = -fd; continue; } if (!isatty(fd)) { err = ENOTTY; goto retry; } if (tcgetattr(fd, tio) < 0) { err = -assert_nonzero(errno); } retry: close(fd); } return err; } void move_process_group(struct ptrace_child *child, pid_t from, pid_t to) { DIR *dir; struct dirent *d; pid_t pid; char *p; int err; if ((dir = opendir("/proc/")) == NULL) return; while ((d = readdir(dir)) != NULL) { if (d->d_name[0] == '.') continue; pid = strtol(d->d_name, &p, 10); if (*p) continue; if (getpgid(pid) == from) { debug("Change pgid for pid %d", pid); err = do_syscall(child, setpgid, pid, to, 0, 0, 0, 0); if (err < 0) error(" failed: %s", strerror(-err)); } } closedir(dir); } void copy_user(struct ptrace_child *d, struct ptrace_child *s) { memcpy(&d->user, &s->user, sizeof(s->user)); } unsigned long ptrace_socketcall(struct ptrace_child *child, unsigned long scratch, unsigned long socketcall, unsigned long p0, unsigned long p1, unsigned long p2, unsigned long p3, unsigned long p4) { // We assume that socketcall is only used on 32-bit // architectures. If there are any 64-bit architectures that do // socketcall, and we port to them, this will need to change. uint32_t args[] = {p0, p1, p2, p3, p4}; int err; err = ptrace_memcpy_to_child(child, scratch, &args, sizeof args); if (err < 0) return (unsigned long)err; return do_syscall(child, socketcall, socketcall, scratch, 0, 0, 0, 0); } #endif reptyr-reptyr-0.6.2/platform/linux/linux.h000066400000000000000000000060151246327676300207040ustar00rootroot00000000000000/* * Copyright (C) 2011 by Nelson Elhage * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #ifndef LINUX_H #define LINUX_H #ifdef __linux__ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct ptrace_child; unsigned long ptrace_socketcall(struct ptrace_child *child, unsigned long scratch, unsigned long socketcall, unsigned long p0, unsigned long p1, unsigned long p2, unsigned long p3, unsigned long p4); #define socketcall_socket SYS_SOCKET #define socketcall_connect SYS_CONNECT #define socketcall_sendmsg SYS_SENDMSG // Define lowercased versions of the socketcall numbers, so that we // can assemble them with ## in the macro below #define do_socketcall(child, scratch, name, a0, a1, a2, a3, a4) \ ({ \ int __ret; \ if (ptrace_syscall_numbers((child))->nr_##name) { \ __ret = do_syscall((child), name, a0, a1, a2, a3, a4, 0); \ } else { \ __ret = ptrace_socketcall((child), (scratch), \ socketcall_##name, \ a0, a1, a2, a3, a4); \ } \ __ret; }) #endif #endif reptyr-reptyr-0.6.2/platform/linux/linux_ptrace.c000066400000000000000000000233101246327676300222320ustar00rootroot00000000000000/* * Copyright (C) 2011 by Nelson Elhage * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #ifdef __linux__ #include "../../ptrace.h" #include "../platform.h" /* * RHEL 5's kernel supports these flags, but their libc doesn't ship a ptrace.h * that defines them. Define them here, and if our kernel doesn't support them, * we'll find out when PTRACE_SETOPTIONS fails. */ #ifndef PTRACE_O_TRACESYSGOOD #define PTRACE_O_TRACESYSGOOD 0x00000001 #endif #ifndef PTRACE_O_TRACEFORK #define PTRACE_O_TRACEFORK 0x00000002 #endif #ifndef PTRACE_EVENT_FORK #define PTRACE_EVENT_FORK 1 #endif #define min(x, y) ({ \ typeof(x) _min1 = (x); \ typeof(y) _min2 = (y); \ _min1 < _min2 ? _min1 : _min2; }) static long __ptrace_command(struct ptrace_child *child, enum __ptrace_request req, void *, void*); #define ptrace_command(cld, req, ...) _ptrace_command(cld, req, ## __VA_ARGS__, NULL, NULL) #define _ptrace_command(cld, req, addr, data, ...) __ptrace_command((cld), (req), (void*)(addr), (void*)(data)) struct ptrace_personality { size_t syscall_rv; size_t syscall_arg0; size_t syscall_arg1; size_t syscall_arg2; size_t syscall_arg3; size_t syscall_arg4; size_t syscall_arg5; size_t reg_ip; }; static struct ptrace_personality *personality(struct ptrace_child *child); #if defined(__amd64__) #include "arch/amd64.h" #elif defined(__i386__) #include "arch/i386.h" #elif defined(__arm__) #include "arch/arm.h" #else #error Unsupported architecture. #endif #ifndef ARCH_HAVE_MULTIPLE_PERSONALITIES int arch_get_personality(struct ptrace_child *child) { return 0; } struct syscall_numbers arch_syscall_numbers[] = { #include "arch/default-syscalls.h" }; #endif static struct ptrace_personality *personality(struct ptrace_child *child) { return &arch_personality[child->personality]; } struct syscall_numbers *ptrace_syscall_numbers(struct ptrace_child *child) { return &arch_syscall_numbers[child->personality]; } int ptrace_attach_child(struct ptrace_child *child, pid_t pid) { memset(child, 0, sizeof * child); child->pid = pid; if (ptrace_command(child, PTRACE_ATTACH) < 0) return -1; return ptrace_finish_attach(child, pid); } int ptrace_finish_attach(struct ptrace_child *child, pid_t pid) { memset(child, 0, sizeof * child); child->pid = pid; kill(pid, SIGCONT); if (ptrace_wait(child) < 0) goto detach; if (arch_get_personality(child)) goto detach; if (ptrace_command(child, PTRACE_SETOPTIONS, 0, PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEFORK) < 0) goto detach; return 0; detach: /* Don't clobber child->error */ ptrace(PTRACE_DETACH, child->pid, 0, 0); return -1; } int ptrace_detach_child(struct ptrace_child *child) { if (ptrace_command(child, PTRACE_DETACH, 0, 0) < 0) return -1; child->state = ptrace_detached; return 0; } int ptrace_wait(struct ptrace_child *child) { if (waitpid(child->pid, &child->status, 0) < 0) { child->error = errno; return -1; } if (WIFEXITED(child->status) || WIFSIGNALED(child->status)) { child->state = ptrace_exited; } else if (WIFSTOPPED(child->status)) { int sig = WSTOPSIG(child->status); if (sig & 0x80) { child->state = (child->state == ptrace_at_syscall) ? ptrace_after_syscall : ptrace_at_syscall; } else { if (sig == SIGTRAP && (((child->status >> 8) & PTRACE_EVENT_FORK) == PTRACE_EVENT_FORK)) ptrace_command(child, PTRACE_GETEVENTMSG, 0, &child->forked_pid); if (child->state != ptrace_at_syscall) child->state = ptrace_stopped; } } else { child->error = EINVAL; return -1; } return 0; } int ptrace_advance_to_state(struct ptrace_child *child, enum child_state desired) { int err; while (child->state != desired) { switch (desired) { case ptrace_after_syscall: case ptrace_at_syscall: if (WIFSTOPPED(child->status) && WSTOPSIG(child->status) == SIGSEGV) { child->error = EAGAIN; return -1; } err = ptrace_command(child, PTRACE_SYSCALL, 0, 0); break; case ptrace_running: return ptrace_command(child, PTRACE_CONT, 0, 0); case ptrace_stopped: err = kill(child->pid, SIGSTOP); if (err < 0) child->error = errno; break; default: child->error = EINVAL; return -1; } if (err < 0) return err; if (ptrace_wait(child) < 0) return -1; } return 0; } int ptrace_save_regs(struct ptrace_child *child) { if (ptrace_advance_to_state(child, ptrace_at_syscall) < 0) return -1; if (ptrace_command(child, PTRACE_GETREGS, 0, &child->user) < 0) return -1; arch_fixup_regs(child); if (arch_save_syscall(child) < 0) return -1; return 0; } int ptrace_restore_regs(struct ptrace_child *child) { int err; err = ptrace_command(child, PTRACE_SETREGS, 0, &child->user); if (err < 0) return err; return arch_restore_syscall(child); } unsigned long ptrace_remote_syscall(struct ptrace_child *child, unsigned long sysno, unsigned long p0, unsigned long p1, unsigned long p2, unsigned long p3, unsigned long p4, unsigned long p5) { unsigned long rv; if (ptrace_advance_to_state(child, ptrace_at_syscall) < 0) return -1; #define setreg(r, v) do { \ if (ptrace_command(child, PTRACE_POKEUSER, \ personality(child)->r, \ (v)) < 0) \ return -1; \ } while (0) if (arch_set_syscall(child, sysno) < 0) return -1; setreg(syscall_arg0, p0); setreg(syscall_arg1, p1); setreg(syscall_arg2, p2); setreg(syscall_arg3, p3); setreg(syscall_arg4, p4); setreg(syscall_arg5, p5); if (ptrace_advance_to_state(child, ptrace_after_syscall) < 0) return -1; rv = ptrace_command(child, PTRACE_PEEKUSER, personality(child)->syscall_rv); if (child->error) return -1; setreg(reg_ip, *(unsigned long*)((void*)&child->user + personality(child)->reg_ip)); #undef setreg return rv; } int ptrace_memcpy_to_child(struct ptrace_child *child, child_addr_t dst, const void *src, size_t n) { unsigned long scratch; while (n >= sizeof(unsigned long)) { if (ptrace_command(child, PTRACE_POKEDATA, dst, *((unsigned long*)src)) < 0) return -1; dst += sizeof(unsigned long); src += sizeof(unsigned long); n -= sizeof(unsigned long); } if (n) { scratch = ptrace_command(child, PTRACE_PEEKDATA, dst); if (child->error) return -1; memcpy(&scratch, src, n); if (ptrace_command(child, PTRACE_POKEDATA, dst, scratch) < 0) return -1; } return 0; } int ptrace_memcpy_from_child(struct ptrace_child *child, void *dst, child_addr_t src, size_t n) { unsigned long scratch; while (n) { scratch = ptrace_command(child, PTRACE_PEEKDATA, src); if (child->error) return -1; memcpy(dst, &scratch, min(n, sizeof(unsigned long))); dst += sizeof(unsigned long); src += sizeof(unsigned long); if (n >= sizeof(unsigned long)) n -= sizeof(unsigned long); else n = 0; } return 0; } static long __ptrace_command(struct ptrace_child *child, enum __ptrace_request req, void *addr, void *data) { long rv; errno = 0; rv = ptrace(req, child->pid, addr, data); child->error = errno; return rv; } #ifdef BUILD_PTRACE_MAIN int main(int argc, char **argv) { struct ptrace_child child; pid_t pid; if (argc < 2) { printf("Usage: %s pid\n", argv[0]); return 1; } pid = atoi(argv[1]); assert(!ptrace_attach_child(&child, pid)); assert(!ptrace_save_regs(&child)); printf("mmap = %lx\n", ptrace_remote_syscall(&child, mmap_syscall, 0, 4096, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0)); reset_user_struct(&child.user); assert(!ptrace_restore_regs(&child)); assert(!ptrace_detach_child(&child)); return 0; } #endif #endif reptyr-reptyr-0.6.2/platform/platform.h000066400000000000000000000050551246327676300202350ustar00rootroot00000000000000/* * Copyright (C) 2011 by Nelson Elhage * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #ifndef PLATFORM_H #define PLATFORM_H #include "linux/linux.h" #include "freebsd/freebsd.h" #include "../ptrace.h" struct fd_array { int *fds; int n; int allocated; }; int fd_array_push(struct fd_array *fda, int fd); #define do_syscall(child, name, a0, a1, a2, a3, a4, a5) \ ptrace_remote_syscall((child), ptrace_syscall_numbers((child))->nr_##name, \ a0, a1, a2, a3, a4, a5) #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; uid_t uid; gid_t gid; }; struct steal_pty_state { struct proc_stat target_stat; pid_t emulator_pid; struct fd_array master_fds; char tmpdir[PATH_MAX]; union { struct sockaddr addr; struct sockaddr_un addr_un; }; int sockfd; struct ptrace_child child; child_addr_t child_scratch; int child_fd; int ptyfd; }; void check_ptrace_scope(void); int check_pgroup(pid_t target); int check_proc_stopped(pid_t pid, int fd); int *get_child_tty_fds(struct ptrace_child *child, int statfd, int *count); int get_terminal_state(struct steal_pty_state *steal, pid_t target); int find_master_fd(struct steal_pty_state *steal); int get_pt(); int get_process_tty_termios(pid_t pid, struct termios *tio); void move_process_group(struct ptrace_child *child, pid_t from, pid_t to); void copy_user(struct ptrace_child *d, struct ptrace_child *s); #endif reptyr-reptyr-0.6.2/ptrace.h000066400000000000000000000073451246327676300160470ustar00rootroot00000000000000/* * Copyright (C) 2011 by Nelson Elhage * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #ifndef PTRACE_H #define PTRACE_H #include #include #include #include /* * See https://github.com/nelhage/reptyr/issues/25 and * https://github.com/nelhage/reptyr/issues/26. * * Older glibc's don't define PTRACE_{SETOPTIONS,GETEVENTMSG}, (but do * in linux/ptrace.h), but on newer systems sys/ptrace.h and * linux/ptrace.h conflict. If we were using autoconf or something, we * could potentially detect the right headers at configure-time, but * I'd like to avoid adding autoconf. These numbers can't ever change * for ABI-compatibility reasons, at least. */ #ifndef PTRACE_SETOPTIONS #define PTRACE_SETOPTIONS 0x4200 #endif #ifndef PTRACE_GETEVENTMSG #define PTRACE_GETEVENTMSG 0x4201 #endif enum child_state { ptrace_detached = 0, ptrace_at_syscall, ptrace_after_syscall, ptrace_running, ptrace_stopped, ptrace_exited }; struct ptrace_child { pid_t pid; enum child_state state; int personality; int status; int error; unsigned long forked_pid; unsigned long saved_syscall; #ifdef __linux__ struct user user; #endif #ifdef __FreeBSD__ struct reg regs; #endif }; struct syscall_numbers { long nr_mmap; long nr_mmap2; long nr_munmap; long nr_getsid; long nr_setsid; long nr_setpgid; long nr_fork; long nr_wait4; long nr_signal; long nr_rt_sigaction; long nr_open; long nr_close; long nr_ioctl; long nr_dup2; long nr_socket; long nr_connect; long nr_sendmsg; long nr_socketcall; }; typedef unsigned long child_addr_t; int ptrace_wait(struct ptrace_child *child); int ptrace_attach_child(struct ptrace_child *child, pid_t pid); int ptrace_finish_attach(struct ptrace_child *child, pid_t pid); int ptrace_detach_child(struct ptrace_child *child); int ptrace_wait(struct ptrace_child *child); int ptrace_advance_to_state(struct ptrace_child *child, enum child_state desired); int ptrace_save_regs(struct ptrace_child *child); int ptrace_restore_regs(struct ptrace_child *child); unsigned long ptrace_remote_syscall(struct ptrace_child *child, unsigned long sysno, unsigned long p0, unsigned long p1, unsigned long p2, unsigned long p3, unsigned long p4, unsigned long p5); int ptrace_memcpy_to_child(struct ptrace_child *, child_addr_t, const void*, size_t); int ptrace_memcpy_from_child(struct ptrace_child *, void*, child_addr_t, size_t); struct syscall_numbers *ptrace_syscall_numbers(struct ptrace_child *child); #endif reptyr-reptyr-0.6.2/reallocarray.c000066400000000000000000000026111246327676300172330ustar00rootroot00000000000000/* $OpenBSD: reallocarray.c,v 1.1 2014/05/08 21:43:49 deraadt Exp $ */ /* * Copyright (c) 2008 Otto Moerbeek * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include "reallocarray.h" /* * This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX * if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW */ #define MUL_NO_OVERFLOW (1UL << (sizeof(size_t) * 4)) void * xreallocarray(void *optr, size_t nmemb, size_t size) { if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && nmemb > 0 && SIZE_MAX / nmemb < size) { errno = ENOMEM; return NULL; } return realloc(optr, size * nmemb); } reptyr-reptyr-0.6.2/reallocarray.h000066400000000000000000000001711246327676300172370ustar00rootroot00000000000000#ifndef __reallocarray_h #define __reallocarray_h void * xreallocarray(void *optr, size_t nmemb, size_t size); #endif reptyr-reptyr-0.6.2/release.sh000077500000000000000000000010521246327676300163640ustar00rootroot00000000000000#!/bin/sh set -e version="$1" changelog="$2" if [ -z "$version" ]; then echo "Usage: $0 [NEW-VERSION]" >&2 exit 2 fi { cat < ChangeLog.tmp mv -f ChangeLog.tmp ChangeLog if ! [ "$changelog" ]; then "$EDITOR" ChangeLog fi version="$version" perl -i -lape '/^#define REPTYR_VERSION/ && s/".*"/"$ENV{version}"/' reptyr.h git add ChangeLog reptyr.h git commit -m "reptyr $version" git tag "reptyr-$version" -m "reptyr $version" -s reptyr-reptyr-0.6.2/reptyr.1000066400000000000000000000107211246327676300160170ustar00rootroot00000000000000.mso www.tmac .TH reptyr 1 "03 Feb 2011" .SH NAME reptyr \- Reparent a running program to a new terminal .SH SYNOPSIS .B reptyr .I PID .B reptyr \-l|\-L [COMMAND [ARGS]] .SH DESCRIPTION .B reptyr is a utility for taking an existing running program and attaching it to a new terminal. Started a long-running process over ssh, but have to leave and don't want to interrupt it? Just start a screen, use .B reptyr to grab it, and then kill the ssh session and head on home. .LP .B reptyr works by attaching to the target program using .BR ptrace (2), redirecting relevant file descriptors, and changing the program's controlling terminal (See .BR tty (4)) It is this last detail that makes .B reptyr work much better than alternatives such as .BR retty (1). .LP After attaching a program, the program will appear to be either backgrounded or suspended to the shell it was launched from (depending on the shell). For maximal safety you can run .IP bg; disown .LP in the old shell to remove the association with the program, but .B reptyr will attempt to ensure that the target program remains running even if you close the shell without doing so. .SH OPTIONS .B \-T .IP Use an alternate mode of attaching, "TTY-stealing". In this mode, .B reptyr will not .BR ptrace (2) the target process, but will attempt to discover the terminal emulator for that process' pty, and steal the master end of the pty. This mode is more reliable and flexible in many circumstances (for instance, it can attach all processes on a tty, rather than just a single process). However, as a downside, children of .BR sshd(8) cannot be attached via .B -T unless .B reptyr is run as root. See .URL https://blog.nelhage.com/2014/08/new-reptyr-feature-tty-stealing/ for more information about tty-stealing. .LP .B \-l, \-L [COMMAND [ARGS]] .IP Instead of attaching to a new process, create a new pty pair, proxy the master end to the current terminal, and then print the name of the slave pty. This can be passed to e.g. .B gdb\'s .I set inferior-tty option. If an optional .B COMMAND and .B ARGS are passed in conjunction with .B -l, that command will be executed as a child of .B reptyr with the .B REPTYR_PTY environment variable set to the name of the slave pty. If .B -L is used instead of .B -l, then fds 0-2 of the child will also be redirected to point to the slave, and the child will be run in a fresh session with the slave as its controlling terminal. .LP .B \-s .IP By default, reptyr will move any file descriptors in the target that were connected to the target's controlling terminal to point to the new terminal. The .B -s option will cause reptyr to unconditionally attach file descriptors 0, 1, and 2 in the target, even if the target has no controlling terminal or they are not connected to a terminal. .LP .B \-v .IP Print the version of .B reptyr and exit. .LP .B \-h .IP Print a usage message and exit. .LP .B \-V .IP Print verbose debug output while running. .LP .SH NOTES .B reptyr depends on the .BR ptrace (2) system call to attach to the remote program. On Ubuntu Maverick and higher, this ability is disabled by default for security reasons. You can enable it temporarily by doing .IP # echo 0 > /proc/sys/kernel/yama/ptrace_scope .LP as root, or permanently by editing the file .IR /etc/sysctl.d/10-ptrace.conf , which also contains more information about this setting. .SH BUGS When attaching to some curses programs, they will not redraw the screen right away, and a .B ^L or similar will be needed to force a redraw. Similarly, after attaching to certain programs, the old terminal will be left in an odd state, and a .B clear or even .B reset may be required before the old terminal is usable again. Attaching to rtorrent (and probably some other apps) doesn't work right (rtorrent stops accepting input) (The problem is that rtorrent is using epoll to poll stdin, and we don't update the internal reference that the epoll fd has to the old tty). Attaching to a process with children doesn't work right. This should be possible to fix -- I just need to ptrace each child individually and do the same games to it. Attaching a .BR less (1) process doesn't work if you have a .I .lessfilter file, as .BR less leaves around a zombie child in this case. This could be worked around. Bugs should be reported to the author (see below) or via the issue tracker on GitHub. .SH AUTHORS reptyr was written by Nelson Elhage . .SH HOMEPAGE .URL https://github.com/nelhage/reptyr .SH SEE ALSO .BR neercs (1), .BR screen (1) reptyr-reptyr-0.6.2/reptyr.c000066400000000000000000000214121246327676300161000ustar00rootroot00000000000000/* * Copyright (C) 2011 by Nelson Elhage * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "reptyr.h" #include "reallocarray.h" #include "platform/platform.h" static int verbose = 0; void _debug(const char *pfx, const char *msg, va_list ap) { if (pfx) fprintf(stderr, "%s", pfx); vfprintf(stderr, msg, ap); fprintf(stderr, "\n"); } void die(const char *msg, ...) { va_list ap; va_start(ap, msg); _debug("[!] ", msg, ap); va_end(ap); exit(1); } void debug(const char *msg, ...) { va_list ap; if (!verbose) return; va_start(ap, msg); _debug("[+] ", msg, ap); va_end(ap); } void error(const char *msg, ...) { va_list ap; va_start(ap, msg); _debug("[-] ", msg, ap); va_end(ap); } void setup_raw(struct termios *save) { struct termios set; if (tcgetattr(0, save) < 0) { fprintf(stderr, "Unable to read terminal attributes: %m"); return; } set = *save; cfmakeraw(&set); if (tcsetattr(0, TCSANOW, &set) < 0) die("Unable to set terminal attributes: %m"); } void resize_pty(int pty) { struct winsize sz; if (ioctl(0, TIOCGWINSZ, &sz) < 0) { // provide fake size to workaround some problems struct winsize defaultsize = {30, 80, 640, 480}; if (ioctl(pty, TIOCSWINSZ, &defaultsize) < 0) { fprintf(stderr, "Cannot set terminal size\n"); } return; } ioctl(pty, TIOCSWINSZ, &sz); } int writeall(int fd, const void *buf, ssize_t count) { ssize_t rv; while (count > 0) { rv = write(fd, buf, count); if (rv < 0) { if (errno == EINTR) continue; return rv; } count -= rv; buf += rv; } return 0; } volatile sig_atomic_t winch_happened = 0; void do_winch(int signal) { winch_happened = 1; } void do_proxy(int pty) { char buf[4096]; ssize_t count; fd_set set; struct timeval timeout; while (1) { if (winch_happened) { winch_happened = 0; /* * FIXME: If a signal comes in after this point but before * select(), the resize will be delayed until we get more * input. signalfd() is probably the cleanest solution. */ resize_pty(pty); } FD_ZERO(&set); FD_SET(0, &set); FD_SET(pty, &set); timeout.tv_sec = 0; timeout.tv_usec = 1000; if (select(pty + 1, &set, NULL, NULL, &timeout) < 0) { if (errno == EINTR) continue; fprintf(stderr, "select: %m"); return; } if (FD_ISSET(0, &set)) { count = read(0, buf, sizeof buf); if (count < 0) return; writeall(pty, buf, count); } if (FD_ISSET(pty, &set)) { count = read(pty, buf, sizeof buf); if (count <= 0) return; writeall(1, buf, count); } } } void usage(char *me) { fprintf(stderr, "Usage: %s [-s] PID\n", me); fprintf(stderr, " %s -l|-L [COMMAND [ARGS]]\n", me); fprintf(stderr, " -l Create a new pty pair and print the name of the slave.\n"); fprintf(stderr, " if there are command-line arguments after -l\n"); fprintf(stderr, " they are executed with REPTYR_PTY set to path of pty.\n"); fprintf(stderr, " -L Like '-l', but also redirect the child's stdio to the slave.\n"); fprintf(stderr, " -s Attach fds 0-2 on the target, even if it is not attached to a tty.\n"); fprintf(stderr, " -T Steal the entire terminal session of the target.\n"); fprintf(stderr, " [experimental] May be more reliable, and will attach all\n"); fprintf(stderr, " processes running on the terminal.\n"); fprintf(stderr, " -h Print this help message and exit.\n"); fprintf(stderr, " -v Print the version number and exit.\n"); fprintf(stderr, " -V Print verbose debug output.\n"); } int main(int argc, char **argv) { struct termios saved_termios; struct sigaction act; int pty; int opt; int err; int do_attach = 1; int force_stdio = 0; int do_steal = 0; int unattached_script_redirection = 0; while ((opt = getopt(argc, argv, "hlLsTvV")) != -1) { switch (opt) { case 'h': usage(argv[0]); return 0; case 'l': do_attach = 0; break; case 'L': do_attach = 0; unattached_script_redirection = 1; break; case 's': force_stdio = 1; break; case 'T': do_steal = 1; break; case 'v': printf("This is reptyr version %s.\n", REPTYR_VERSION); printf(" by Nelson Elhage \n"); printf("http://github.com/nelhage/reptyr/\n"); return 0; case 'V': verbose = 1; break; default: usage(argv[0]); return 1; } if (opt == 'l' || opt == 'L') break; // the rest is a command line } if (do_attach && optind >= argc) { fprintf(stderr, "%s: No pid specified to attach\n", argv[0]); usage(argv[0]); return 1; } if (!do_steal) { if ((pty = get_pt()) < 0) die("Unable to allocate a new pseudo-terminal: %m"); if (unlockpt(pty) < 0) die("Unable to unlockpt: %m"); if (grantpt(pty) < 0) die("Unable to grantpt: %m"); } if (do_attach) { char *endptr = NULL; errno = 0; long t = strtol(argv[optind], &endptr, 10); if (errno == ERANGE) die("Invalid pid: %m"); if (*endptr) die("Invalid pid: must be integer"); /* check for overflow/underflow */ pid_t child = (pid_t)t; if (child < t || t < 1) /* pids can't be < 1, so no *real* underflow check */ die("Invalid pid: %s", strerror(ERANGE)); if (do_steal) { err = steal_pty(child, &pty); } else { err = attach_child(child, ptsname(pty), force_stdio); } if (err) { fprintf(stderr, "Unable to attach to pid %d: %s\n", child, strerror(err)); if (err == EPERM) { check_ptrace_scope(); } return 1; } } else { printf("Opened a new pty: %s\n", ptsname(pty)); fflush(stdout); if (argc > 2) { if (!fork()) { setenv("REPTYR_PTY", ptsname(pty), 1); if (unattached_script_redirection) { int f; setpgid(0, getppid()); setsid(); f = open(ptsname(pty), O_RDONLY, 0); dup2(f, 0); close(f); f = open(ptsname(pty), O_WRONLY, 0); dup2(f, 1); dup2(f, 2); close(f); } close(pty); execvp(argv[2], argv + 2); exit(1); } } } setup_raw(&saved_termios); memset(&act, 0, sizeof act); act.sa_handler = do_winch; act.sa_flags = 0; sigaction(SIGWINCH, &act, NULL); resize_pty(pty); do_proxy(pty); do { errno = 0; if (tcsetattr(0, TCSANOW, &saved_termios) && errno != EINTR) die("Unable to tcsetattr: %m"); } while (errno == EINTR); return 0; } reptyr-reptyr-0.6.2/reptyr.fr.1000066400000000000000000000076721246327676300164400ustar00rootroot00000000000000.\" Traduction Laurent GAUTROT - 2011-08-06 .mso www.tmac .TH reptyr 1 "03 Feb 2011" .SH NOM reptyr \- Reassoccie un programme en cours d'exécution à un nouveau terminal .SH SYNOPSIS .B reptyr .I PID .B reptyr \-l .SH DESCRIPTION .B reptyr est un utilitaire qui prend un programme en cours d'exécution et l'attache à un nouveau terminal. Vous avez démarré un programme long à travers ssh, mais vous devez partir et vous ne voulez pas l'interrompre\ ? Démarrez simplement un screen, utilisez .B reptyr pour l'attraper, puis tuez la session ssh et vous pouvez rentrer à la maison. .LP .B reptyr fonctionne en s'attachant au programme visé à l'aide de .BR ptrace (2), en redirigeant les descripteurs de fichiers appropriés et en modifiant le terminal de contrôle du programme (Voir .BR tty (4)) C'est le détail qui fait que .B reptyr focntionne bien mieux que les autres programmes du même type, comme .BR retty (1). .LP Après avoir attaché un programme, il apparaît soit à l'arrière-plan, soit suspendu pour le shell qui l'a lancé (variable en fonction du shell). Pour une sécurité maximale, vous pouvez exécuter .IP bg; disown .LP dans le vieux shell pour supprimer l'association avec le programme, mais .B reptyr tente de s'assurer que le programme visé reste en cours d'exécution même si vous fermez le shell sans le faire. .SH OPTIONS .B \-l .IP Plutôt que d'attacher un nouveau processus, crée un couveau couple de pty, redirige l'extrémité maîtresse vers le terminal en corus, puis affiche le nom du pty esclave. Il pourra être passé en argument par exemple à l'option .I set inferior-tty de .B gdb. .LP .B \-s .IP Par défaut, reptyr déplace tout descripteur de fichier de la cible qui était connecté au terminal de contrôle vers le nouveau terminal. L'option .B -s fait que reptyr attache les descripteurs de fichiers 0, 1 et 2 sans condition même si la cible n'a pas de terminal de contrôle ou qu'elle n'est pas connectée à un terminal. .LP .B \-v .IP Affiche la version de .B reptyr et sort. .LP .B \-h .IP Affiche un message d'usage et sort. .LP .B \-V .IP Affiche des messages verbeux. .LP .SH NOTES .B reptyr dépend de l'appel système .BR ptrace (2) pour s'attacher au programme distant. Sur Ubuntu Maverick et suivant cette possibilité est désactivée par défaut pour des raisons de sécurité. Vous pouvez l'activer temporairement avec .IP # echo 0 > /proc/sys/kernel/yama/ptrace_scope .LP en tant que rootn ou de manière permanente en éditant le fichier .IR /etc/sysctl.d/10-ptrace.conf , ui contient aussi plus d'information sur ce réglage. .SH BUGS Quand on s'attache à quelques programmes curses, ils ne redessinent pas immédiatement l'écran, et un .B ^L ou équivalent est nécessaire pour forcer l'actualisation. De la même manière, après avoir attaché certains programmes, le vieux terminal est dans un état étrange et un .B clear ou même un .B reset est nécessaire avant que le vieux terminal ne soit à nouveau utilisable. L'attachement à rtorrent (et peut-être à d'autres applications) ne fonctionne pas (rtorrent arrête d'accepter des entrées). Le problème est que rtorrent utilise epoll pour vérifier l'entrée standard et qu'on ne met pas à jour la référence interne que le descripteur de fichier d'epoll a de l'ancien terminal. L'attachement à un processus avec des fils ne fonctionne pas correctement. Il devrait être possible de le corriger. Il faut juste ptracer chaque fils individuellement et de jouer avec lui. L'attachement à un processus .BR less (1) ne fonctionne pas si vous avez un fichier .I .lessfilter parce que .BR less abandonne un fils zombie dans ce cas. Ça devrait pouvoir être corrigé. Vous pouvez rapporter des bugs à l'auteur (voir ci-dessous) ou par l'issue tracker sur GitHub. .SH AUTEURS reptyr est écrit par Nelson Elhage . .SH HOMEPAGE .URL https://github.com/nelhage/reptyr .SH VOIR AUSSI .BR neercs (1), .BR screen (1) reptyr-reptyr-0.6.2/reptyr.h000066400000000000000000000033431246327676300161100ustar00rootroot00000000000000/* * Copyright (C) 2011 by Nelson Elhage * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #define REPTYR_VERSION "0.6.2" #define assert_nonzero(expr) ({ \ typeof(expr) __val = expr; \ if (__val == 0) \ die("Unexpected: %s == 0!\n", #expr); \ __val; \ }) int attach_child(pid_t pid, const char *pty, int force_stdio); int steal_pty(pid_t pid, int *pty); #define __printf __attribute__((format(printf, 1, 2))) void __printf die(const char *msg, ...) __attribute__((noreturn)); void __printf debug(const char *msg, ...); void __printf error(const char *msg, ...); reptyr-reptyr-0.6.2/test/000077500000000000000000000000001246327676300153665ustar00rootroot00000000000000reptyr-reptyr-0.6.2/test/.gitignore000066400000000000000000000000201246327676300173460ustar00rootroot00000000000000victim.o victim reptyr-reptyr-0.6.2/test/basic.py000066400000000000000000000005461246327676300170260ustar00rootroot00000000000000import pexpect child = pexpect.spawn("test/victim") child.setecho(False) child.sendline("hello") child.expect("ECHO: hello") reptyr = pexpect.spawn("./reptyr %d" % (child.pid,)) reptyr.sendline("world") reptyr.expect("ECHO: world") child.sendline("final") child.expect(pexpect.EOF) reptyr.sendeof() reptyr.expect(pexpect.EOF) assert not reptyr.isalive() reptyr-reptyr-0.6.2/test/matrix.sh000077500000000000000000000003561246327676300172350ustar00rootroot00000000000000#!/bin/sh echo "64-bit..." make clean make CFLAGS=-m64 LDFLAGS=-m64 test echo "32-bit..." make clean env NO_TEST_STEAL=1 make CFLAGS=-m32 LDFLAGS=-m32 test echo "32-on-64..." make clean make VICTIM_CFLAGS=-m32 VICTIM_LDFLAGS=-m32 test reptyr-reptyr-0.6.2/test/tty-steal.py000066400000000000000000000011111246327676300176600ustar00rootroot00000000000000import pexpect import os import sys if os.getenv("NO_TEST_STEAL") is not None: print "Skipping tty-stealing tests because $NO_TEST_STEAL is set." sys.exit(0) child = pexpect.spawn("test/victim") child.setecho(False) child.sendline("hello") child.expect("ECHO: hello") reptyr = pexpect.spawn("./reptyr -T %d" % (child.pid,)) reptyr.sendline("world") reptyr.expect("ECHO: world") child.sendline("final") child.expect(pexpect.EOF) assert os.stat("/dev/null").st_rdev == os.fstat(child.fileno()).st_rdev reptyr.sendeof() reptyr.expect(pexpect.EOF) assert not reptyr.isalive() reptyr-reptyr-0.6.2/test/victim.c000066400000000000000000000003121246327676300170210ustar00rootroot00000000000000#include int main(int argc, char **argv) { char *line = NULL; size_t cap = 0; while(getline(&line, &cap, stdin) != -1) { printf("ECHO: %s", line); } return 0; }