1658 lines
50 KiB
C
Executable File
1658 lines
50 KiB
C
Executable File
#!/usr/bin/env compileAndGo
|
|
# -*- coding: utf-8 mode: c -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
|
|
# cf https://github.com/nelhage/reptyr
|
|
language=c
|
|
compiler=gcc
|
|
compilerArgs=-O2 -D_GNU_SOURCE -o $cacheDir/$executableFilename $cacheDir/$sourceFilename
|
|
!#
|
|
|
|
//@inc[lib/reptyr/ptrace.h
|
|
/*
|
|
* 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 <unistd.h>
|
|
#include <sys/ptrace.h>
|
|
#include <sys/user.h>
|
|
|
|
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;
|
|
struct user user;
|
|
unsigned long saved_syscall;
|
|
};
|
|
|
|
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;
|
|
};
|
|
|
|
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);
|
|
//@inc]lib/reptyr/ptrace.h
|
|
//@inc[lib/reptyr/reptyr.h
|
|
/*
|
|
* 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.4dev"
|
|
|
|
int attach_child(pid_t pid, const char *pty, int force_stdio);
|
|
#define __printf __attribute__((format(printf, 1, 2)))
|
|
void __printf die(const char *msg, ...);
|
|
void __printf debug(const char *msg, ...);
|
|
void __printf error(const char *msg, ...);
|
|
//@inc]lib/reptyr/reptyr.h
|
|
//@inc[lib/reptyr/attach.c
|
|
/*
|
|
* 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 <sys/types.h>
|
|
#include <dirent.h>
|
|
#include <sys/syscall.h>
|
|
#include <sys/mman.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#include <termios.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/wait.h>
|
|
#include <signal.h>
|
|
#include <limits.h>
|
|
#include <time.h>
|
|
#include <sys/time.h>
|
|
#include <sys/stat.h>
|
|
|
|
//#include "ptrace.h"
|
|
//#include "reptyr.h"
|
|
|
|
#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;
|
|
};
|
|
|
|
#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)
|
|
|
|
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 errno;
|
|
n = sscanf(buf, "%d (%16[^)]) %c %d %d %d %u",
|
|
&out->pid, out->comm,
|
|
&out->state, &out->ppid, &out->sid,
|
|
&out->pgid, &dev);
|
|
if (n == EOF)
|
|
return errno;
|
|
if (n != 7) {
|
|
return EINVAL;
|
|
}
|
|
out->ctty = dev;
|
|
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;
|
|
}
|
|
|
|
static void do_unmap(struct ptrace_child *child, child_addr_t addr, unsigned long len) {
|
|
if (addr == (unsigned long)-1)
|
|
return;
|
|
do_syscall(child, munmap, addr, len, 0, 0, 0, 0);
|
|
}
|
|
|
|
int *get_child_tty_fds(struct ptrace_child *child, int statfd, int *count) {
|
|
struct proc_stat child_status;
|
|
struct stat tty_st, st;
|
|
char buf[PATH_MAX];
|
|
int n = 0, allocated = 0;
|
|
int *fds = NULL;
|
|
DIR *dir;
|
|
struct dirent *d;
|
|
int *tmp = NULL;
|
|
|
|
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 = errno;
|
|
error("Unable to stat /dev/tty");
|
|
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) {
|
|
if (n == allocated) {
|
|
allocated = allocated ? 2 * allocated : 2;
|
|
tmp = realloc(fds, allocated * sizeof *tmp);
|
|
if (tmp == NULL) {
|
|
child->error = errno;
|
|
error("Unable to allocate memory for fd array.");
|
|
free(fds);
|
|
fds = NULL;
|
|
goto out;
|
|
}
|
|
fds = tmp;
|
|
}
|
|
debug("Found an alias for the tty: %s", d->d_name);
|
|
fds[n++] = atoi(d->d_name);
|
|
}
|
|
}
|
|
out:
|
|
*count = n;
|
|
closedir(dir);
|
|
return fds;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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;
|
|
memcpy(&dummy.user, &child->user, sizeof child->user);
|
|
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, unsigned long scratch_page) {
|
|
int err;
|
|
if (ptrace_syscall_numbers(child)->nr_signal != -1) {
|
|
err = do_syscall(child, signal, SIGHUP, (unsigned long)SIG_IGN, 0, 0, 0, 0);
|
|
} else {
|
|
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, 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;
|
|
struct proc_stat st;
|
|
|
|
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 (parse_proc_stat(fd, &st))
|
|
break;
|
|
if (st.state == 'T')
|
|
break;
|
|
|
|
sleep.tv_sec = 0;
|
|
sleep.tv_nsec = 10000000;
|
|
nanosleep(&sleep, NULL);
|
|
}
|
|
}
|
|
|
|
int copy_tty_state(pid_t pid, const char *pty) {
|
|
char buf[PATH_MAX];
|
|
int fd, err = EINVAL;
|
|
struct termios tio;
|
|
int i;
|
|
|
|
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 = -errno;
|
|
}
|
|
retry:
|
|
close(fd);
|
|
}
|
|
|
|
if (err)
|
|
return err;
|
|
|
|
if ((fd = open(pty, O_RDONLY)) < 0)
|
|
return -errno;
|
|
|
|
if (tcsetattr(fd, TCSANOW, &tio) < 0)
|
|
err = errno;
|
|
close(fd);
|
|
return -err;
|
|
}
|
|
|
|
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 (does process %d exist?)", (int)target);
|
|
return pg;
|
|
}
|
|
|
|
if ((dir = opendir("/proc/")) == NULL)
|
|
return 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 a suprocesses).",
|
|
(int)pid, TASK_COMM_LENGTH, pid_stat.comm, (int)target, (int)target);
|
|
err = EINVAL;
|
|
goto out;
|
|
}
|
|
}
|
|
out:
|
|
closedir(dir);
|
|
return err;
|
|
}
|
|
|
|
int attach_child(pid_t pid, const char *pty, int force_stdio) {
|
|
struct ptrace_child child;
|
|
unsigned long scratch_page = -1;
|
|
int *child_tty_fds = NULL, n_fds, child_fd, statfd;
|
|
int i;
|
|
int err = 0;
|
|
long page_size = sysconf(_SC_PAGE_SIZE);
|
|
char stat_path[PATH_MAX];
|
|
long mmap_syscall;
|
|
|
|
if ((err = check_pgroup(pid))) {
|
|
return err;
|
|
}
|
|
|
|
if ((err = copy_tty_state(pid, pty))) {
|
|
if (err == ENOTTY && !force_stdio) {
|
|
error("Target is not connected to a terminal.\n"
|
|
" Use -s to force attaching anyways.");
|
|
return err;
|
|
}
|
|
}
|
|
|
|
snprintf(stat_path, sizeof stat_path, "/proc/%d/stat", pid);
|
|
statfd = open(stat_path, O_RDONLY);
|
|
if (statfd < 0) {
|
|
error("Unable to open %s: %s", stat_path, strerror(errno));
|
|
return -statfd;
|
|
}
|
|
|
|
kill(pid, SIGTSTP);
|
|
wait_for_stop(pid, statfd);
|
|
|
|
if (ptrace_attach_child(&child, pid)) {
|
|
err = child.error;
|
|
goto out_cont;
|
|
}
|
|
|
|
if (ptrace_advance_to_state(&child, ptrace_at_syscall)) {
|
|
err = child.error;
|
|
goto out_detach;
|
|
}
|
|
if (ptrace_save_regs(&child)) {
|
|
err = child.error;
|
|
goto out_detach;
|
|
}
|
|
|
|
mmap_syscall = ptrace_syscall_numbers(&child)->nr_mmap2;
|
|
if (mmap_syscall == -1)
|
|
mmap_syscall = ptrace_syscall_numbers(&child)->nr_mmap;
|
|
scratch_page = ptrace_remote_syscall(&child, mmap_syscall, 0,
|
|
page_size, PROT_READ|PROT_WRITE,
|
|
MAP_ANONYMOUS|MAP_PRIVATE, 0, 0);
|
|
|
|
if (scratch_page > (unsigned long)-1000) {
|
|
err = -(signed long)scratch_page;
|
|
goto out_unmap;
|
|
}
|
|
|
|
debug("Allocated scratch page: %lx", scratch_page);
|
|
|
|
if (force_stdio) {
|
|
child_tty_fds = malloc(3 * sizeof(int));
|
|
if (!child_tty_fds) {
|
|
err = ENOMEM;
|
|
goto out_unmap;
|
|
}
|
|
n_fds = 3;
|
|
child_tty_fds[0] = 0;
|
|
child_tty_fds[1] = 1;
|
|
child_tty_fds[2] = 2;
|
|
} else {
|
|
child_tty_fds = get_child_tty_fds(&child, statfd, &n_fds);
|
|
if (!child_tty_fds) {
|
|
err = child.error;
|
|
goto out_unmap;
|
|
}
|
|
}
|
|
|
|
if (ptrace_memcpy_to_child(&child, scratch_page, pty, strlen(pty)+1)) {
|
|
err = child.error;
|
|
error("Unable to memcpy the pty path to child.");
|
|
goto out_free_fds;
|
|
}
|
|
|
|
child_fd = do_syscall(&child, open,
|
|
scratch_page, O_RDWR|O_NOCTTY,
|
|
0, 0, 0, 0);
|
|
if (child_fd < 0) {
|
|
err = child_fd;
|
|
error("Unable to open the tty in the child.");
|
|
goto out_free_fds;
|
|
}
|
|
|
|
debug("Opened the new tty in the child: %d", child_fd);
|
|
|
|
err = ignore_hup(&child, scratch_page);
|
|
if (err < 0)
|
|
goto out_close;
|
|
|
|
err = do_syscall(&child, getsid, 0, 0, 0, 0, 0, 0);
|
|
if (err != child.pid) {
|
|
debug("Target is not a session leader, attempting to setsid.");
|
|
err = do_setsid(&child);
|
|
} else {
|
|
do_syscall(&child, ioctl, child_tty_fds[0], TIOCNOTTY, 0, 0, 0, 0);
|
|
}
|
|
if (err < 0)
|
|
goto out_close;
|
|
|
|
err = do_syscall(&child, ioctl, child_fd, TIOCSCTTY, 0, 0, 0, 0);
|
|
if (err < 0) {
|
|
error("Unable to set controlling terminal.");
|
|
goto out_close;
|
|
}
|
|
|
|
debug("Set the controlling tty");
|
|
|
|
for (i = 0; i < n_fds; i++)
|
|
do_syscall(&child, dup2, child_fd, child_tty_fds[i], 0, 0, 0, 0);
|
|
|
|
|
|
err = 0;
|
|
|
|
out_close:
|
|
do_syscall(&child, close, child_fd, 0, 0, 0, 0, 0);
|
|
out_free_fds:
|
|
free(child_tty_fds);
|
|
|
|
out_unmap:
|
|
do_unmap(&child, scratch_page, page_size);
|
|
|
|
ptrace_restore_regs(&child);
|
|
out_detach:
|
|
ptrace_detach_child(&child);
|
|
|
|
if (err == 0) {
|
|
kill(child.pid, SIGSTOP);
|
|
wait_for_stop(child.pid, statfd);
|
|
}
|
|
kill(child.pid, SIGWINCH);
|
|
out_cont:
|
|
kill(child.pid, SIGCONT);
|
|
close(statfd);
|
|
|
|
return err < 0 ? -err : err;
|
|
}
|
|
//@inc]lib/reptyr/attach.c
|
|
//@inc[lib/reptyr/ptrace.c
|
|
/*
|
|
* 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 <sys/ptrace.h>
|
|
#include <asm/ptrace.h>
|
|
#include <sys/types.h>
|
|
#include <sys/user.h>
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <sys/syscall.h>
|
|
#include <sys/mman.h>
|
|
#include <assert.h>
|
|
#include <stddef.h>
|
|
|
|
//#include "ptrace.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__)
|
|
//@inc[arch/amd64.h
|
|
/*
|
|
* 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.
|
|
*/
|
|
//@inc[x86_common.h
|
|
/*
|
|
* 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
|
|
//@inc]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] = {
|
|
//@inc[default-syscalls.h
|
|
#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),
|
|
},
|
|
|
|
#undef SC
|
|
//@inc]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 = 173,
|
|
.nr_open = 5,
|
|
.nr_close = 6,
|
|
.nr_ioctl = 54,
|
|
.nr_dup2 = 63
|
|
}
|
|
};
|
|
|
|
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;
|
|
}
|
|
//@inc]arch/amd64.h
|
|
#elif defined(__i386__)
|
|
//@inc[arch/i386.h
|
|
/*
|
|
* 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.
|
|
*/
|
|
//@inc[x86_common.h
|
|
/*
|
|
* 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
|
|
//@inc]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),
|
|
}
|
|
};
|
|
//@inc]arch/i386.h
|
|
#elif defined(__arm__)
|
|
//@inc[arch/arm.h
|
|
/*
|
|
* 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);
|
|
}
|
|
//@inc]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[] = {
|
|
//@inc[arch/default-syscalls.h
|
|
#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),
|
|
},
|
|
|
|
#undef SC
|
|
//@inc]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
|
|
//@inc]lib/reptyr/ptrace.c
|
|
//@inc[lib/reptyr/reptyr.c
|
|
/*
|
|
* 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 <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <sys/types.h>
|
|
#include <sys/select.h>
|
|
#include <sys/ioctl.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <stdarg.h>
|
|
#include <termios.h>
|
|
#include <signal.h>
|
|
|
|
//#include "reptyr.h"
|
|
|
|
#ifndef __linux__
|
|
#error reptyr is currently Linux-only.
|
|
#endif
|
|
|
|
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)
|
|
die("Unable to read terminal attributes: %m");
|
|
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)
|
|
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;
|
|
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);
|
|
if (select(pty+1, &set, NULL, NULL, NULL) < 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, FILE* out) {
|
|
if (out == NULL) out = stderr;
|
|
fprintf(out, "Usage: %s [-s] PID\n", me);
|
|
fprintf(out, " %s -l|-L [COMMAND [ARGS]]\n", me);
|
|
fprintf(out, " -l Create a new pty pair and print the name of the slave.\n");
|
|
fprintf(out, " if there are command-line arguments after -l\n");
|
|
fprintf(out, " they are executed with REPTYR_PTY set to path of pty.\n");
|
|
fprintf(out, " -L Like '-l', but also redirect the child's stdio to the slave.\n");
|
|
fprintf(out, " -s Attach fds 0-2 on the target, even if it is not attached to a tty.\n");
|
|
fprintf(out, " -h Print this help message and exit.\n");
|
|
fprintf(out, " -v Print the version number and exit.\n");
|
|
fprintf(out, " -V Print verbose debug output.\n");
|
|
}
|
|
|
|
void check_yama_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 main(int argc, char **argv) {
|
|
struct termios saved_termios;
|
|
struct sigaction act;
|
|
int pty;
|
|
int arg = 1;
|
|
int do_attach = 1;
|
|
int force_stdio = 0;
|
|
int unattached_script_redirection = 0;
|
|
|
|
if (argc < 2) {
|
|
usage(argv[0], NULL);
|
|
return 2;
|
|
} else if (argc == 2 && !strcmp(argv[1], "--help")) {
|
|
usage(argv[0], stdout);
|
|
return 0;
|
|
}
|
|
if (argv[arg][0] == '-') {
|
|
switch(argv[arg][1]) {
|
|
case 'h':
|
|
usage(argv[0], stdout);
|
|
return 0;
|
|
case 'l':
|
|
do_attach = 0;
|
|
break;
|
|
case 'L':
|
|
do_attach = 0;
|
|
unattached_script_redirection = 1;
|
|
break;
|
|
case 's':
|
|
arg++;
|
|
force_stdio = 1;
|
|
break;
|
|
case 'v':
|
|
printf("This is reptyr version %s.\n", REPTYR_VERSION);
|
|
printf(" by Nelson Elhage <nelhage@nelhage.com>\n");
|
|
printf("http://github.com/nelhage/reptyr/\n");
|
|
return 0;
|
|
case 'V':
|
|
arg++;
|
|
verbose = 1;
|
|
break;
|
|
default:
|
|
usage(argv[0], NULL);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
if (do_attach && arg >= argc) {
|
|
fprintf(stderr, "%s: No pid specified to attach\n", argv[0]);
|
|
usage(argv[0], NULL);
|
|
return 1;
|
|
}
|
|
|
|
if ((pty = open("/dev/ptmx", O_RDWR|O_NOCTTY)) < 0)
|
|
die("Unable to open /dev/ptmx: %m");
|
|
if (unlockpt(pty) < 0)
|
|
die("Unable to unlockpt: %m");
|
|
if (grantpt(pty) < 0)
|
|
die("Unable to grantpt: %m");
|
|
|
|
if (do_attach) {
|
|
pid_t child = atoi(argv[arg]);
|
|
int err;
|
|
if ((err = attach_child(child, ptsname(pty), force_stdio))) {
|
|
fprintf(stderr, "Unable to attach to pid %d: %s\n", child, strerror(err));
|
|
if (err == EPERM) {
|
|
check_yama_ptrace_scope();
|
|
}
|
|
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);
|
|
tcsetattr(0, TCSANOW, &saved_termios);
|
|
|
|
return 0;
|
|
}
|
|
//@inc]lib/reptyr/reptyr.c
|