[
  {
    "path": ".gitignore",
    "content": "*.so\ntardis\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2017 David Buchanan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": "all: tardis novdso.so\n\ntardis: tardis.c\n\tgcc tardis.c -o tardis -lm -Wall -Ofast -std=c99 -DPID_MAX=$(shell cat /proc/sys/kernel/pid_max)\n\nnovdso.so: novdso.c\n\tgcc -std=c99 -Wall -fPIC -shared -o novdso.so novdso.c\n\nclean:\n\trm -f tardis novdso.so\n"
  },
  {
    "path": "README.md",
    "content": "# TARDIS\n\nTrace And Rewrite Delays In Syscalls: Hooking time-related Linux syscalls to warp a process's perspective of time.\n\nThis code is rather buggy, mainly due to my lack of understanding of the ptrace API.\nYou probably shouldn't use it for anything serious, although it could be useful for\ntesting/debugging certain applications.\n\n## Things to try:\n\n```\n$ ./tardis 10000 10000 xclock\n$ ./tardis 1 3 glxgears\n$ ./tardis 1 -1 glxgears\n$ ./tardis 10 10 firefox\n$ ./tardis 10 10 /bin/sh\n```\n\n![xclock demo](https://i.imgur.com/UnFYuLs.gif)\n\n## Notes:\n\n- Currently only x86_64 Linux is supported. It should be possible to port to i386 with fairly minimal effort.\n\n- I used `PTRACE_SEIZE`, which only exists since kernel version 3.4.\n\n- `novdso.so` is preloaded to prevent libc from using vDSO - otherwise `ptrace(PTRACE_SYSCALL, ...)`\nwouldn't work for those syscalls (Take a look at `man vdso` for more information). You might need to\nmodify the `LD_PRELOAD` value to be an absolute path for some programs/environments, I only made it\nrelative for simplicity.\n\n- Certain simple programs, like `glxgears`, don't mind being run with time flowing in reverse! Most programs don't however, and of course there's no way to have a negative delay.\n\n- There are many more syscalls that I still need to handle.\n\nCurrently handled syscalls:\n\n- `nanosleep`\n- `clock_nanosleep`\n- `select`\n- `poll`\n- `gettimeofday`\n- `clock_gettime`\n- `time`\n"
  },
  {
    "path": "novdso.c",
    "content": "#define _GNU_SOURCE\n#include <unistd.h>\n#include <sys/syscall.h>\n\n// this is a simple shim to disable vDSO use in libc\n\nint clock_gettime(void * clk_id, void * tp) {\n\treturn syscall(SYS_clock_gettime, clk_id, tp);\n}\n\nint gettimeofday(void * tv, void * tz) {\n\treturn syscall(SYS_gettimeofday, tv, tz);\n}\n\nint time(void * tloc) {\n\treturn syscall(SYS_time, tloc);\n}\n\nint nanosleep(const struct timespec *req, struct timespec *rem) {\n        return syscall(SYS_nanosleep,req,rem);\n}\n"
  },
  {
    "path": "tardis.c",
    "content": "#define _GNU_SOURCE\n\n#include <stdio.h>\n#include <unistd.h>\n#include <stdlib.h>\n#include <signal.h>\n#include <wait.h>\n#include <time.h>\n#include <sys/ptrace.h>\n#include <elf.h>\n#include <sys/user.h>\n#include <sys/uio.h>\n#include <sys/syscall.h>\n#include <math.h>\n#include <stdbool.h>\n\n#define MICROSECONDS 1000000\n#define NANOSECONDS (MICROSECONDS*1000)\n#define NUM_SYSCALLS 512 // this is higher than the real number, but shouldn't matter\n#ifndef PID_MAX\n#define PID_MAX 32768 // XXX: assumption\n#endif\n#define NUM_CLKIDS 16 // XXX: incorrect, but \"works\" anyway\n\ndouble starttimes[NUM_CLKIDS], delayfactor, timefactor;\nbool leavesys[PID_MAX];\nvoid (*before_handlers[NUM_SYSCALLS])(pid_t, struct user_regs_struct *);\nvoid (*after_handlers[NUM_SYSCALLS])(pid_t, struct user_regs_struct *);\n\nint is64bit(pid_t pid) {\n\tstruct user_regs_struct x64regs;\n\tstruct iovec iov = {\n\t\t.iov_base = &x64regs,\n\t\t.iov_len = sizeof(x64regs)\n\t};\n\t\n\tptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, &iov);\n\treturn iov.iov_len == sizeof(x64regs);\n}\n\nvoid read_block(pid_t pid, void * dst, void * src, size_t len) {\n\tfor (size_t i = 0; i < len; i += sizeof(void *)) {\n\t\t*(void **)(dst + i) = (void *)ptrace(PTRACE_PEEKDATA, pid, src + i, NULL); // XXX\n\t}\n}\n\nvoid write_block(pid_t pid, void * src, void * dst, size_t len) {\n\tfor (size_t i = 0; i < len; i += sizeof(void *)) {\n\t\tptrace(PTRACE_POKEDATA, pid, dst + i, *(void **)(src + i)); // XXX\n\t}\n}\n\nvoid scale_timespec(struct timespec * ts, double factor, double starttime) {\n\tdouble time = ts->tv_sec + (double)ts->tv_nsec / NANOSECONDS;\n\tif (starttime != 0) {\n\t\ttime = starttime + (time - starttime) * factor;\n\t} else {\n\t\ttime *= factor;\n\t}\n\tts->tv_sec = time;\n\tts->tv_nsec = fmod(time, 1) * NANOSECONDS;\n}\n\nvoid scale_timeval(struct timeval * tv, double factor, double starttime) {\n\tdouble time = tv->tv_sec + (double)tv->tv_usec / MICROSECONDS;\n\tif (starttime != 0) {\n\t\ttime = starttime + (time - starttime) * factor;\n\t} else {\n\t\ttime *= factor;\n\t}\n\ttv->tv_sec = time;\n\ttv->tv_usec = fmod(time, 1) * MICROSECONDS;\n}\n\n/* pre-syscall handlers */\n\nvoid before_nanosleep(pid_t pid, struct user_regs_struct * uregs) {\n\tstruct timespec ts;\n\tread_block(pid, &ts, (void *)uregs->rdi, sizeof(struct timespec));\n\tscale_timespec(&ts, 1.0/delayfactor, 0);\n\twrite_block(pid, &ts, (void *)uregs->rdi, sizeof(struct timespec));\n}\n\nvoid before_poll(pid_t pid, struct user_regs_struct * uregs) {\n\tint timeout = uregs->rdx & 0xFFFFFFFF; // isolate edx\n\tif (timeout > 0) {\n\t\turegs->rdx = timeout / delayfactor; // not sure if this behaves the way I want\n\t}\n\tptrace(PTRACE_SETREGS, pid, 0, uregs);\n}\n\nvoid before_select(pid_t pid, struct user_regs_struct * uregs) {\n\tif (uregs->r8 != 0) {\n\t\tstruct timeval tv;\n\t\tread_block(pid, &tv, (void *)uregs->r8, sizeof(struct timeval));\n\t\tscale_timeval(&tv, 1.0/delayfactor, 0);\n\t\twrite_block(pid, &tv, (void *)uregs->r8, sizeof(struct timeval));\n\t}\n}\n\nvoid before_clock_nanosleep(pid_t pid, struct user_regs_struct * uregs) {\n\tstruct timespec rqtp;\n\tread_block(pid, &rqtp, (void *)uregs->rdx, sizeof(struct timespec));\n\tscale_timespec(&rqtp, 1.0/delayfactor, 0);\n\twrite_block(pid, &rqtp, (void *)uregs->rdx, sizeof(struct timespec));\n}\n\n/* post-syscall handlers */\n\nvoid after_gettimeofday(pid_t pid, struct user_regs_struct * uregs) {\n\tstruct timeval tv;\n\tread_block(pid, &tv, (void *)uregs->rdi, sizeof(struct timeval));\n\tscale_timeval(&tv, timefactor, starttimes[CLOCK_REALTIME]);\n\twrite_block(pid, &tv, (void *)uregs->rdi, sizeof(struct timeval));\n}\n\nvoid after_clock_gettime(pid_t pid, struct user_regs_struct * uregs) {\n\tstruct timespec ts;\n\tread_block(pid, &ts, (void *)uregs->rsi, sizeof(struct timespec));\n\tscale_timespec(&ts, timefactor, starttimes[uregs->rdi]); // FIXME check bounds\n\twrite_block(pid, &ts, (void *)uregs->rsi, sizeof(struct timespec));\n}\n\nvoid after_time(pid_t pid, struct user_regs_struct * uregs) {\n\turegs->rdi = starttimes[CLOCK_REALTIME] + (uregs->rdi - starttimes[CLOCK_REALTIME]) * timefactor;\n\tptrace(PTRACE_SETREGS, pid, 0, uregs);\n}\n\nvoid after_clock_nanosleep(pid_t pid, struct user_regs_struct * uregs) {\n\tstruct timespec rmtp;\n\tread_block(pid, &rmtp, (void *)uregs->rcx, sizeof(struct timespec));\n\tscale_timespec(&rmtp, 1.0/delayfactor, 0);\n\twrite_block(pid, &rmtp, (void *)uregs->rcx, sizeof(struct timespec));\n}\n\nint main(int argc, char *argv[], char *envp[]) {\n\t\n\tif (argc < 3) {\n\t\tprintf(\"USAGE: %s DELAY_FACTOR TIME_FACTOR COMMAND [ARGS]...\\n\", argv[0]);\n\t\texit(EXIT_FAILURE);\n\t}\n\t\n\tdelayfactor = strtod(argv[1], NULL);\n\ttimefactor = strtod(argv[2], NULL);\n\t\n\tbefore_handlers[SYS_nanosleep] = before_nanosleep;\n\tbefore_handlers[SYS_poll] = before_poll;\n\tbefore_handlers[SYS_select] = before_select;\n\tbefore_handlers[SYS_clock_nanosleep] = before_clock_nanosleep;\n\t\n\tafter_handlers[SYS_gettimeofday] = after_gettimeofday;\n\tafter_handlers[SYS_clock_gettime] = after_clock_gettime;\n\tafter_handlers[SYS_time] = after_time;\n\tafter_handlers[SYS_clock_nanosleep] = after_clock_nanosleep;\n\t\n\tstruct timespec sts;\n\tfor (clockid_t id = 0; id < NUM_CLKIDS; id++) {\n\t\tclock_gettime(id, &sts); // sometimes id will be invalid, but it shouldn't matter\n\t\tstarttimes[id] = sts.tv_sec + (double)sts.tv_nsec / NANOSECONDS;\n\t}\n\t\n\tpid_t child = fork();\n\t\n\tif(child == 0) {\n\t\t/* child */\n\t\tenvp[0] = \"LD_PRELOAD=./novdso.so\"; // FIXME: Do something more sensible\n\t\tkill(getpid(), SIGSTOP);\n\t\texecvpe(argv[3], &argv[3], envp);\n\t\tperror(\"execvpe\"); // execvpe only returns on error\n\t\texit(-1);\n\t}\n\t#ifdef DEBUG\n\t    fprintf(stderr, \"Child spawned with PID %d\\n\", child);\n\t#endif\n\tptrace(PTRACE_SEIZE, child, 0, PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEEXEC | PTRACE_O_EXITKILL | PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK | PTRACE_O_TRACECLONE);\n\twait(NULL); // wait for SIGSTOP to happen\n\tptrace(PTRACE_SYSCALL, child, 0, 0); // continue execution\n\t\n\tif (is64bit(child)) {\n\t\t#ifdef DEBUG\n\t\t    fprintf(stderr, \"Child is 64-bit\\n\");\n\t\t#endif\n\t} else {\n\t\tfprintf(stderr, \"ERROR: 32-bit processes are currently unsupported\\n\");\n\t\texit(-1);\n\t}\n\t\n\tfor (;;) { // TODO: Understand ptrace, simplify code structure\n\t\tstruct user_regs_struct uregs;\n\t\tint status;\n\t\tpid_t pid = waitpid(-1, &status, 0);\n\t\tif (WIFEXITED(status)) {\n\t\t\tif (pid == child) {\n\t\t\t\texit(WEXITSTATUS(status));\n\t\t\t} else {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\t\tif (WIFSTOPPED(status) && WSTOPSIG(status) != SIGTRAP && WSTOPSIG(status) != SIGSTOP) {\n\t\t\tif (WSTOPSIG(status) & 0x80) {\n\t\t\t\t// handle syscall\n\t\t\t} else {\n\t\t\t\tptrace(PTRACE_SYSCALL, pid, 0, WSTOPSIG(status));\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t} else {\n\t\t\tptrace(PTRACE_SYSCALL, pid, 0, 0);\n\t\t\tcontinue;\n\t\t}\n\t\t\n\t\tptrace(PTRACE_GETREGS, pid, 0, &uregs);\n\t\tif (!leavesys[pid]) {\n\t\t\t#ifdef DEBUG\n\t\t\t\tfprintf(stderr, \"[pid %d]   syscall(%llu)\\t0x%016llX 0x%016llX 0x%016llX = ...\\n\", pid, uregs.orig_rax, uregs.rdi, uregs.rsi, uregs.rdx);\n\t\t\t#endif\n\t\t\tif (uregs.orig_rax < NUM_SYSCALLS && before_handlers[uregs.orig_rax] != NULL) {\n\t\t\t\tbefore_handlers[uregs.orig_rax](pid, &uregs);\n\t\t\t}\n\t\t} else {\n\t\t\t#ifdef DEBUG\n\t\t\t\tfprintf(stderr, \"... 0x%llX\\n\", uregs.rax);\n\t\t\t#endif\n\t\t\tif (uregs.orig_rax < NUM_SYSCALLS && after_handlers[uregs.orig_rax] != NULL) {\n\t\t\t\tafter_handlers[uregs.orig_rax](pid, &uregs);\n\t\t\t}\n\t\t}\n\t\tleavesys[pid] ^= true;\n\t\tptrace(PTRACE_SYSCALL, pid, 0, 0);\n\t}\n}\n"
  }
]