blob: 1b172cef0dc8310a8385c96c913ae9a0a3879e79 [file] [edit]
/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*-
*
* This file is part of PRoot.
*
* Copyright (C) 2013 STMicroelectronics
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*/
#include <sys/ptrace.h> /* PTRACE_*, */
#include <errno.h> /* E*, */
#include <assert.h> /* assert(3), */
#include <stdbool.h> /* bool, true, false, */
#include <signal.h> /* SIG*, */
#include <talloc.h> /* talloc*, */
#include "ptrace/wait.h"
#include "ptrace/ptrace.h"
#include "syscall/sysnum.h"
#include "syscall/chain.h"
#include "tracee/tracee.h"
#include "tracee/event.h"
#include "tracee/reg.h"
#include "tracee/mem.h"
#include "attribute.h"
static const char *stringify_event(int event) UNUSED;
static const char *stringify_event(int event)
{
if (WIFEXITED(event))
return "exited";
else if (WIFSIGNALED(event))
return "signaled";
else if (WIFCONTINUED(event))
return "continued";
else if (WIFSTOPPED(event)) {
switch ((event & 0xfff00) >> 8) {
case SIGTRAP:
return "stopped: SIGTRAP";
case SIGTRAP | 0x80:
return "stopped: SIGTRAP: 0x80";
case SIGTRAP | PTRACE_EVENT_VFORK << 8:
return "stopped: SIGTRAP: PTRACE_EVENT_VFORK";
case SIGTRAP | PTRACE_EVENT_FORK << 8:
return "stopped: SIGTRAP: PTRACE_EVENT_FORK";
case SIGTRAP | PTRACE_EVENT_VFORK_DONE << 8:
return "stopped: SIGTRAP: PTRACE_EVENT_VFORK_DONE";
case SIGTRAP | PTRACE_EVENT_CLONE << 8:
return "stopped: SIGTRAP: PTRACE_EVENT_CLONE";
case SIGTRAP | PTRACE_EVENT_EXEC << 8:
return "stopped: SIGTRAP: PTRACE_EVENT_EXEC";
case SIGTRAP | PTRACE_EVENT_EXIT << 8:
return "stopped: SIGTRAP: PTRACE_EVENT_EXIT";
case SIGTRAP | PTRACE_EVENT_SECCOMP2 << 8:
return "stopped: SIGTRAP: PTRACE_EVENT_SECCOMP2";
case SIGTRAP | PTRACE_EVENT_SECCOMP << 8:
return "stopped: SIGTRAP: PTRACE_EVENT_SECCOMP";
case SIGSTOP:
return "stopped: SIGSTOP";
default:
return "stopped: unknown";
}
}
return "unknown";
}
/**
* Translate the wait syscall made by @ptracer into a "void" syscall
* if the expected pid is one of its ptracees, in order to emulate the
* ptrace mechanism within PRoot. This function returns -errno if an
* error occured (unsupported request), otherwise 0.
*/
int translate_wait_enter(Tracee *ptracer)
{
Tracee *ptracee;
pid_t pid;
PTRACER.waits_in = WAITS_IN_KERNEL;
/* Don't emulate the ptrace mechanism if it's not a ptracer. */
if (PTRACER.nb_ptracees == 0)
return 0;
/* Don't emulate the ptrace mechanism if the requested pid is
* not a ptracee. */
pid = (pid_t) peek_reg(ptracer, ORIGINAL, SYSARG_1);
if (pid != -1) {
ptracee = get_tracee(ptracer, pid, false);
if (ptracee == NULL || PTRACEE.ptracer != ptracer)
return 0;
}
/* This syscall is canceled at the enter stage in order to be
* handled at the exit stage. */
set_sysnum(ptracer, PR_void);
PTRACER.waits_in = WAITS_IN_PROOT;
return 0;
}
/**
* Update pid & wait status of @ptracer's wait(2) for the given
* @ptracee. This function returns -errno if an error occurred, 0 if
* the wait syscall will be restarted (ie. the event is discarded),
* otherwise @ptracee's pid.
*/
static int update_wait_status(Tracee *ptracer, Tracee *ptracee)
{
word_t address;
int result;
/* Special case: the Linux kernel reports the terminating
* event issued by a process to both its parent and its
* tracer, except when they are the same. In this case the
* Linux kernel reports the terminating event only once to the
* tracing parent ... */
if (PTRACEE.ptracer == ptracee->parent
&& (WIFEXITED(PTRACEE.event4.ptracer.value)
|| WIFSIGNALED(PTRACEE.event4.ptracer.value))) {
/* ... So hide this terminating event (toward its
* tracer, ie. PRoot) and make the second one appear
* (towards its parent, ie. the ptracer). This will
* ensure its exit status is collected from a kernel
* point-of-view (ie. it doesn't stay a zombie
* forever). */
restart_original_syscall(ptracer);
/* Detach this ptracee from its ptracer, PRoot doesn't
* have anything else to emulate. */
detach_from_ptracer(ptracee);
/* Zombies can rest in peace once the ptracer is
* notified. */
if (PTRACEE.is_zombie)
TALLOC_FREE(ptracee);
return 0;
}
address = peek_reg(ptracer, ORIGINAL, SYSARG_2);
if (address != 0) {
poke_int32(ptracer, address, PTRACEE.event4.ptracer.value);
if (errno != 0)
return -errno;
}
PTRACEE.event4.ptracer.pending = false;
/* Be careful; ptracee might get freed before its pid is
* returned. */
result = ptracee->pid;
/* Zombies can rest in peace once the ptracer is notified. */
if (PTRACEE.is_zombie) {
detach_from_ptracer(ptracee);
TALLOC_FREE(ptracee);
}
return result;
}
/**
* Emulate the wait* syscall made by @ptracer if it was in the context
* of the ptrace mechanism. This function returns -errno if an error
* occured, otherwise the pid of the expected tracee.
*/
int translate_wait_exit(Tracee *ptracer)
{
Tracee *ptracee;
word_t options;
int status;
pid_t pid;
assert(PTRACER.waits_in == WAITS_IN_PROOT);
PTRACER.waits_in = DOESNT_WAIT;
pid = (pid_t) peek_reg(ptracer, ORIGINAL, SYSARG_1);
options = peek_reg(ptracer, ORIGINAL, SYSARG_3);
/* Is there such a stopped ptracee with an event not yet
* passed to its ptracer? */
ptracee = get_stopped_ptracee(ptracer, pid, true, options);
if (ptracee == NULL) {
/* Is there still living ptracees? */
if (PTRACER.nb_ptracees == 0)
return -ECHILD;
/* Non blocking wait(2) ? */
if ((options & WNOHANG) != 0) {
/* if WNOHANG was specified and one or more
* child(ren) specified by pid exist, but have
* not yet changed state, then 0 is returned.
* On error, -1 is returned.
*
* -- man 2 waitpid */
return (has_ptracees(ptracer, pid, options) ? 0 : -ECHILD);
}
/* Otherwise put this ptracer in the "waiting for
* ptracee" state, it will be woken up in
* handle_ptracee_event() later. */
PTRACER.wait_pid = pid;
PTRACER.wait_options = options;
return 0;
}
status = update_wait_status(ptracer, ptracee);
if (status < 0)
return status;
return status;
}
/**
* For the given @ptracee, pass its current @event to its ptracer if
* this latter is waiting for it, otherwise put the @ptracee in the
* "waiting for ptracer" state. This function returns whether
* @ptracee shall be kept in the stop state or not.
*/
bool handle_ptracee_event(Tracee *ptracee, int event)
{
bool handled_by_proot_first = false;
Tracee *ptracer = PTRACEE.ptracer;
bool keep_stopped;
assert(ptracer != NULL);
/* Remember what the event initially was, this will be
* required by PRoot to handle this event later. */
PTRACEE.event4.proot.value = event;
PTRACEE.event4.proot.pending = true;
/* By default, this ptracee should be kept stopped until its
* ptracer restarts it. */
keep_stopped = true;
/* Not all events are expected for this ptracee. */
if (WIFSTOPPED(event)) {
switch ((event & 0xfff00) >> 8) {
case SIGTRAP | 0x80:
if (PTRACEE.ignore_syscalls || PTRACEE.ignore_loader_syscalls)
return false;
if ((PTRACEE.options & PTRACE_O_TRACESYSGOOD) == 0)
event &= ~(0x80 << 8);
handled_by_proot_first = IS_IN_SYSEXIT(ptracee);
break;
#define PTRACE_EVENT_VFORKDONE PTRACE_EVENT_VFORK_DONE
#define CASE_FILTER_EVENT(name) \
case SIGTRAP | PTRACE_EVENT_ ##name << 8: \
if ((PTRACEE.options & PTRACE_O_TRACE ##name) == 0) \
return false; \
PTRACEE.tracing_started = true; \
handled_by_proot_first = true; \
break;
CASE_FILTER_EVENT(FORK);
CASE_FILTER_EVENT(VFORK);
CASE_FILTER_EVENT(VFORKDONE);
CASE_FILTER_EVENT(CLONE);
CASE_FILTER_EVENT(EXIT);
CASE_FILTER_EVENT(EXEC);
/* Never reached. */
assert(0);
case SIGTRAP | PTRACE_EVENT_SECCOMP2 << 8:
case SIGTRAP | PTRACE_EVENT_SECCOMP << 8:
/* These events are not supported [yet?] under
* ptrace emulation. */
return false;
default:
PTRACEE.tracing_started = true;
break;
}
}
/* In these cases, the ptracee isn't really alive anymore. To
* ensure it will not be in limbo, PRoot restarts it whether
* its ptracer is waiting for it or not. */
else if (WIFEXITED(event) || WIFSIGNALED(event)) {
PTRACEE.tracing_started = true;
keep_stopped = false;
}
/* A process is not traced right from the TRACEME request; it
* is traced from the first received signal, whether it was
* raised by the process itself (implicitly or explicitly), or
* it was induced by a PTRACE_EVENT_*. */
if (!PTRACEE.tracing_started)
return false;
/* Under some circumstances, the event must be handled by
* PRoot first. */
if (handled_by_proot_first) {
int signal;
signal = handle_tracee_event(ptracee, PTRACEE.event4.proot.value);
PTRACEE.event4.proot.value = signal;
/* The computed signal is always 0 since we can come
* in this block only on sysexit and special events
* (as for now). */
assert(signal == 0);
}
/* Remember what the new event is, this will be required by
the ptracer in translate_ptrace_exit() in order to restart
this ptracee. */
PTRACEE.event4.ptracer.value = event;
PTRACEE.event4.ptracer.pending = true;
/* Notify asynchronously the ptracer about this event, as the
* kernel does. */
kill(ptracer->pid, SIGCHLD);
/* Note: wait_pid is set in translate_wait_exit() if no
* ptracee event was pending when the ptracer started to
* wait. */
if ( (PTRACER.wait_pid == -1 || PTRACER.wait_pid == ptracee->pid)
&& EXPECTED_WAIT_CLONE(PTRACER.wait_options, ptracee)) {
bool restarted;
int status;
status = update_wait_status(ptracer, ptracee);
if (status == 0)
chain_next_syscall(ptracer);
else
poke_reg(ptracer, SYSARG_RESULT, (word_t) status);
/* Write ptracer's register cache back. */
(void) push_regs(ptracer);
/* Restart the ptracer. */
PTRACER.wait_pid = 0;
restarted = restart_tracee(ptracer, 0);
if (!restarted)
keep_stopped = false;
return keep_stopped;
}
return keep_stopped;
}