int pt_process::monitor() { bool in_syscall = false, first = true, spawned = false; struct timespec start, end, delta; int status, exit_reason = PTBOX_EXIT_NORMAL; // Set pgid to -this->pid such that -pgid becomes pid, resulting // in the initial wait be on the main thread. This allows it a chance // of creating a new process group. pid_t pid, pgid = -this->pid; #if PTBOX_FREEBSD struct ptrace_lwpinfo lwpi; #endif while (true) { clock_gettime(CLOCK_MONOTONIC, &start); pid = wait4(-pgid, &status, __WALL, &_rusage); clock_gettime(CLOCK_MONOTONIC, &end); timespec_sub(&end, &start, &delta); timespec_add(&exec_time, &delta, &exec_time); int signal = 0; //printf("pid: %d (%d)\n", pid, this->pid); if (WIFEXITED(status) || WIFSIGNALED(status)) { if (first || pid == pgid) break; else { //printf("Thread exit: %d\n", pid); continue; } } #if PTBOX_FREEBSD ptrace(PT_LWPINFO, pid, (caddr_t) &lwpi, sizeof lwpi); #endif if (first) { dispatch(PTBOX_EVENT_ATTACH, 0); #if PTBOX_FREEBSD // PTRACE_O_TRACESYSGOOD can be replaced by struct ptrace_lwpinfo.pl_flags. // No FreeBSD equivalent that I know of // * TRACECLONE makes no sense since FreeBSD has no clone(2) // * TRACEEXIT... I'm not sure about #else // This is right after SIGSTOP is received: ptrace(PTRACE_SETOPTIONS, pid, NULL, PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEEXIT | PTRACE_O_TRACECLONE); #endif // We now set the process group to the actual pgid. pgid = pid; } if (WIFSTOPPED(status)) { #if PTBOX_FREEBSD if (WSTOPSIG(status) == SIGTRAP && lwpi.pl_flags & (PL_FLAG_SCE | PL_FLAG_SCX)) { debugger->update_syscall(&lwpi); #else if (WSTOPSIG(status) == (0x80 | SIGTRAP)) { debugger->settid(pid); #endif int syscall = debugger->syscall(); #if PTBOX_FREEBSD in_syscall = lwpi.pl_flags & PL_FLAG_SCE; #else in_syscall = debugger->is_enter(); #endif //printf("%d: %s syscall %d\n", pid, in_syscall ? "Enter" : "Exit", syscall); if (!spawned) { // Does execve not return if the process hits an rlimit and gets SIGKILLed? // // It doesn't. See the strace below. // $ ulimit -Sv50000 // $ strace ./a.out // execve("./a.out", ["./a.out"], [/* 17 vars */] <unfinished ...> // +++ killed by SIGKILL +++ // Killed // // From this we can see that execve doesn't return (<unfinished ...>) if the process fails to // initialize, so we don't need to wait until the next non-execve syscall to set // _initialized to true - if it exited execve, it's good to go. if (!in_syscall && syscall == debugger->execve_syscall()) spawned = this->_initialized = true; } else if (in_syscall) { if (syscall < MAX_SYSCALL) { switch (handler[syscall]) { case PTBOX_HANDLER_ALLOW: break; case PTBOX_HANDLER_STDOUTERR: { int arg0 = debugger->arg0(); if (arg0 != 1 && arg0 != 2) exit_reason = protection_fault(syscall); break; } case PTBOX_HANDLER_CALLBACK: if (callback(context, syscall)) break; //printf("Killed by callback: %d\n", syscall); exit_reason = protection_fault(syscall); continue; default: // Default is to kill, safety first. //printf("Killed by DISALLOW or None: %d\n", syscall); exit_reason = protection_fault(syscall); continue; } } } else if (debugger->on_return_callback) { debugger->on_return_callback(debugger->on_return_context, syscall); debugger->on_return_callback = NULL; debugger->on_return_context = NULL; } } else { #if PTBOX_FREEBSD // No events aside from signal event on FreeBSD // (TODO: maybe check for PL_SIGNAL instead of both PL_SIGNAL and PL_NONE?) signal = WSTOPSIG(status); // Swallow SIGSTOP. This is because no one should send it, nor should it // be self-send, hence perfect for implementing shocker. if (signal == SIGSTOP) signal = 0; //else printf("WSTOPSIG(status): %d\n", signal); #else switch (WSTOPSIG(status)) { case SIGTRAP: switch (status >> 16) { case PTRACE_EVENT_EXIT: if (exit_reason != PTBOX_EXIT_NORMAL) dispatch(PTBOX_EVENT_EXITING, PTBOX_EXIT_NORMAL); break; case PTRACE_EVENT_CLONE: { unsigned long tid; ptrace(PTRACE_GETEVENTMSG, pid, NULL, &tid); //printf("Created thread: %d\n", tid); break; } } break; default: signal = WSTOPSIG(status); } #endif if (!first) // *** Don't set _signal to SIGSTOP if this is the /first/ SIGSTOP dispatch(PTBOX_EVENT_SIGNAL, WSTOPSIG(status)); } } // Pass NULL as signal in case of our first SIGSTOP because the runtime tends to resend it, making all our // work for naught. Like abort(), it catches the signal, prints something (^Z?) and then resends it. // Doing this prevents a second SIGSTOP from being dispatched to our event handler above. *** #if PTBOX_FREEBSD //if (signal) printf("Forwarding %d...\n", signal); ptrace(_trace_syscalls ? PT_SYSCALL : PT_CONTINUE, pid, (caddr_t) 1, first ? 0 : signal); #else ptrace(_trace_syscalls ? PTRACE_SYSCALL : PTRACE_CONT, pid, NULL, first ? NULL : (void*) signal); #endif first = false; } dispatch(PTBOX_EVENT_EXITED, exit_reason); return WIFEXITED(status) ? WEXITSTATUS(status) : -WTERMSIG(status); }
int pt_process::monitor() { bool in_syscall = false, first = true; struct timespec start, end, delta; int status, exit_reason = PTBOX_EXIT_NORMAL; siginfo_t si; while (true) { clock_gettime(CLOCK_MONOTONIC, &start); wait4(pid, &status, 0, &_rusage); clock_gettime(CLOCK_MONOTONIC, &end); timespec_sub(&end, &start, &delta); timespec_add(&exec_time, &delta, &exec_time); if (WIFEXITED(status) || WIFSIGNALED(status)) break; if (first) dispatch(PTBOX_EVENT_ATTACH, 0); if (WIFSTOPPED(status)) { if (WSTOPSIG(status) == SIGTRAP) { ptrace(PTRACE_GETSIGINFO, pid, NULL, &si); if (si.si_code == SIGTRAP || si.si_code == (SIGTRAP|0x80)) { int syscall = debugger->syscall(); //printf("%s syscall %d\n", in_syscall ? "Exit" : "Enter", syscall); if (!in_syscall) { switch (handler[syscall]) { case PTBOX_HANDLER_ALLOW: break; case PTBOX_HANDLER_STDOUTERR: { int arg0 = debugger->arg0(); if (arg0 != 1 && arg0 != 2) exit_reason = protection_fault(syscall); break; } case PTBOX_HANDLER_CALLBACK: if (callback(context, syscall)) break; //printf("Killed by callback: %d\n", syscall); exit_reason = protection_fault(syscall); continue; default: // Default is to kill, safety first. //printf("Killed by DISALLOW or None: %d\n", syscall); exit_reason = protection_fault(syscall); continue; } if (debugger->is_exit(syscall)) dispatch(PTBOX_EVENT_EXITING, PTBOX_EXIT_NORMAL); } else if (debugger->on_return_callback) { debugger->on_return_callback(debugger->on_return_context, syscall); debugger->on_return_callback = NULL; debugger->on_return_context = NULL; } in_syscall ^= true; } } else { switch (WSTOPSIG(status)) { case SIGSEGV: dispatch(PTBOX_EVENT_EXITING, exit_reason = PTBOX_EXIT_SEGFAULT); puts("Child Segfault"); kill(pid, SIGKILL); break; case SIGFPE: puts("Child SIGFPE"); kill(pid, SIGKILL); break; } dispatch(PTBOX_EVENT_SIGNAL, WSTOPSIG(status)); } } ptrace(PTRACE_SYSCALL, pid, NULL, NULL); first = false; } dispatch(PTBOX_EVENT_EXITED, exit_reason); return WIFEXITED(status) ? WEXITSTATUS(status) : -WTERMSIG(status); }