packages feed

voyeur-0.1.0.0: libvoyeur/src/voyeur-exec.c

#ifndef __APPLE__
#define _GNU_SOURCE
#endif

#include <pthread.h>
#include <spawn.h>
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

#include "dyld.h"
#include "env.h"
#include "net.h"
#include "util.h"


//////////////////////////////////////////////////
// Shared code for all exec*() functions.
//////////////////////////////////////////////////

static void write_exec_event(int sock, uint8_t options, const char* path,
                             char* const argv[], char* const envp[],
                             pid_t pid, pid_t ppid)
{
  if (options & OBSERVE_EXEC_SILENT) {
    // We're just here to propagate libvoyeur instrumentation.
    return;
  }

  if (!(options & OBSERVE_EXEC_NOACCESS)) {
    // Make sure this exec() call could succeed before reporting the event.
    if (access(path, X_OK) < 0) {
      return;
    }
  }

  voyeur_write_msg_type(sock, VOYEUR_MSG_EVENT);
  voyeur_write_event_type(sock, VOYEUR_EVENT_EXEC);
  voyeur_write_string(sock, path, 0);

  int argc = 0;
  while (argv[argc]) {
    ++argc;
  }
  voyeur_write_int(sock, argc);
  for (int i = 0 ; i < argc ; ++i) {
    voyeur_write_string(sock, argv[i], 0);
  }

  if (options & OBSERVE_EXEC_ENV) {
    int envc = 0;
    while (envp[envc]) {
      ++envc;
    }
    voyeur_write_int(sock, envc);
    for (int i = 0 ; i < envc ; ++i) {
      voyeur_write_string(sock, envp[i], 0);
    }
  }

  if (options & OBSERVE_EXEC_PATH) {
    voyeur_write_string(sock, getenv("PATH"), 0);
  }

  if (options & OBSERVE_EXEC_CWD) {
    voyeur_write_string(sock, getcwd(NULL, 0), 0);
  }

  voyeur_write_pid(sock, pid);
  voyeur_write_pid(sock, ppid);
}


//////////////////////////////////////////////////
// Replace vfork() with fork()
//////////////////////////////////////////////////

typedef pid_t (*vfork_fptr_t)();

pid_t VOYEUR_FUNC(vfork)(void)
{
    return fork();
}

VOYEUR_INTERPOSE(vfork)


//////////////////////////////////////////////////
// execve
//////////////////////////////////////////////////

typedef int (*execve_fptr_t)(const char*, char* const[], char* const []);

int VOYEUR_FUNC(execve)(const char* path, char* const argv[], char* const envp[])
{
  // In the case of exec we don't bother caching anything, since exec
  // will wipe out this whole process image anyway.
  const char* libs = getenv("LIBVOYEUR_LIBS");
  const char* opts = getenv("LIBVOYEUR_OPTS");
  uint8_t options = voyeur_decode_options(opts, VOYEUR_EVENT_EXEC);
  const char* sockpath = getenv("LIBVOYEUR_SOCKET");

  // Write the event to the socket.
  int sock = voyeur_create_client_socket(sockpath);
  if (sock >= 0) {
    write_exec_event(sock, options, path, argv, envp, getpid(), getppid());

    // We might as well close the socket since there's no chance we'll
    // ever be called a second time by the same process. (Even if the
    // exec fails, generally the fork'd process will just bail.)
    voyeur_write_msg_type(sock, VOYEUR_MSG_DONE);
    voyeur_close_socket(sock);
  }

  // Add libvoyeur-specific environment variables. (We don't bother
  // freeing 'buf' since we need it until the execve call and we have
  // no way of freeing it after that.)
  void* buf;
  char** voyeur_envp =
    voyeur_augment_environment(envp, libs, opts, sockpath, &buf);

  // Pass through the call to the real execve.
  VOYEUR_DECLARE_NEXT(execve_fptr_t, execve);
  VOYEUR_LOOKUP_NEXT(execve_fptr_t, execve);
  return VOYEUR_CALL_NEXT(execve, path, argv, voyeur_envp);
}

VOYEUR_INTERPOSE(execve)


//////////////////////////////////////////////////
// posix_spawn
//////////////////////////////////////////////////

typedef int (*posix_spawn_fptr_t)(pid_t* restrict,
                                  const char* restrict,
                                  const posix_spawn_file_actions_t*,
                                  const posix_spawnattr_t* restrict,
                                  char* const[restrict],
                                  char* const[restrict]);

static pthread_mutex_t voyeur_posix_spawn_mutex = PTHREAD_MUTEX_INITIALIZER;
static char voyeur_posix_spawn_initialized = 0;
static char* voyeur_posix_spawn_libs = NULL;
static char* voyeur_posix_spawn_opts = NULL;
static uint8_t voyeur_posix_spawn_options = 0;
static char* voyeur_posix_spawn_sockpath = NULL;
static int voyeur_posix_spawn_sock = 0;
VOYEUR_STATIC_DECLARE_NEXT(posix_spawn_fptr_t, posix_spawn);
VOYEUR_STATIC_DECLARE_NEXT(posix_spawn_fptr_t, posix_spawnp);

__attribute__((destructor)) void voyeur_cleanup_posix_spawn()
{
  pthread_mutex_lock(&voyeur_posix_spawn_mutex);

  if (voyeur_posix_spawn_initialized) {
    if (voyeur_posix_spawn_sock >= 0) {
      voyeur_write_msg_type(voyeur_posix_spawn_sock, VOYEUR_MSG_DONE);
      voyeur_close_socket(voyeur_posix_spawn_sock);
      voyeur_posix_spawn_sock = -1;
    }

    voyeur_posix_spawn_initialized = 0;
  }

  pthread_mutex_unlock(&voyeur_posix_spawn_mutex);
}

static void voyeur_init_posix_spawn()
{
  if (!voyeur_posix_spawn_initialized) {
    voyeur_posix_spawn_libs = getenv("LIBVOYEUR_LIBS");
    voyeur_posix_spawn_opts = getenv("LIBVOYEUR_OPTS");
    voyeur_posix_spawn_options =
      voyeur_decode_options(voyeur_posix_spawn_opts, VOYEUR_EVENT_EXEC);
    voyeur_posix_spawn_sockpath = getenv("LIBVOYEUR_SOCKET");
    voyeur_posix_spawn_sock =
      voyeur_create_client_socket(voyeur_posix_spawn_sockpath);
    VOYEUR_LOOKUP_NEXT(posix_spawn_fptr_t, posix_spawn);
    VOYEUR_LOOKUP_NEXT(posix_spawn_fptr_t, posix_spawnp);
    voyeur_posix_spawn_initialized = 1;
  }
}

int VOYEUR_FUNC(posix_spawn)(pid_t* pid,
                             const char* restrict path,
                             const posix_spawn_file_actions_t* file_actions,
                             const posix_spawnattr_t* restrict attrp,
                             char* const argv[restrict],
                             char* const envp[restrict])
{
  pthread_mutex_lock(&voyeur_posix_spawn_mutex);

  voyeur_init_posix_spawn();

  // Add libvoyeur-specific environment variables.
  void* buf;
  char** voyeur_envp =
    voyeur_augment_environment(envp,
                               voyeur_posix_spawn_libs,
                               voyeur_posix_spawn_opts,
                               voyeur_posix_spawn_sockpath,
                               &buf);

  // Pass through the call to the real posix_spawn.
  pid_t child_pid;
  int retval = VOYEUR_CALL_NEXT(posix_spawn, &child_pid, path,
                                file_actions, attrp,
                                argv, voyeur_envp);

  // Write the event to the socket.
  if (voyeur_posix_spawn_sock >= 0) {
    write_exec_event(voyeur_posix_spawn_sock,
                     voyeur_posix_spawn_options,
                     path, argv, envp,
                     child_pid, getpid());
  }

  pthread_mutex_unlock(&voyeur_posix_spawn_mutex);

  // Free the resources we allocated.
  free(voyeur_envp);
  free(buf);

  // It's legal to pass NULL for the pid argument, so double-check we
  // have somewhere to write the pid to before doing it.
  if (pid) {
    *pid = child_pid;
  }

  return retval;
}

VOYEUR_INTERPOSE(posix_spawn)

#ifdef __linux__

// On Linux we need to interpose on every exec variant separately because they
// don't just forward the work to execve and posix_spawn like OS X's versions do.

//////////////////////////////////////////////////
// execl, execle, execv
//////////////////////////////////////////////////

#define VARARGS_TO_ARGV(_start, _path, _argv, _envp)     \
  do {                                                   \
    va_list args;                                        \
                                                         \
    /* Get total number of entries. */                   \
    unsigned length = 0;                                 \
    va_start(args, (_start));                            \
    while (va_arg(args, char*)) {                        \
      ++length;                                          \
    }                                                    \
    va_end(args);                                        \
                                                         \
    /* Increase size to fit argv[0] and final NULL. */   \
    length += 2;                                         \
                                                         \
    /* Create an appropriately sized _argv. */           \
    _argv = malloc(sizeof(const char*) * length);        \
    _argv[0] = (char *) _path;                           \
                                                         \
    /* Copy. */                                          \
    unsigned index = 1;                                  \
    va_start(args, (_start));                            \
    while (index < length) {                             \
      _argv[index] = va_arg(args, char*);                \
      ++index;                                           \
    }                                                    \
                                                         \
    /* Pull out envp from one past the end of argv. */   \
    _envp = va_arg(args, char**);                        \
                                                         \
    va_end(args);                                        \
  } while(0)


int VOYEUR_FUNC(execl)(const char* path, const char* start, ...)
{
  char** argv;
  char** dummy_envp;
  VARARGS_TO_ARGV(start, path, argv, dummy_envp);

  return execve(path, argv, environ);
}

VOYEUR_INTERPOSE(execl)


int VOYEUR_FUNC(execle)(const char* path, const char* start, ...)
{
  char** argv;
  char** envp;
  VARARGS_TO_ARGV(start, path, argv, envp);

  return execve(path, argv, envp);
}

VOYEUR_INTERPOSE(execle)


int VOYEUR_FUNC(execv)(const char* path, char* const argv[])
{
  return execve(path, argv, environ);
}

VOYEUR_INTERPOSE(execv)


//////////////////////////////////////////////////
// execlp, execvp, execvpe
//////////////////////////////////////////////////

typedef int (*execvpe_fptr_t)(const char*, char* const [], char* const []);

int VOYEUR_FUNC(execlp)(const char* path, const char* start, ...)
{
  const char* libs = getenv("LIBVOYEUR_LIBS");
  const char* opts = getenv("LIBVOYEUR_OPTS");
  uint8_t options = voyeur_decode_options(opts, VOYEUR_EVENT_EXEC);
  const char* sockpath = getenv("LIBVOYEUR_SOCKET");

  char** argv;
  char** dummy_envp;
  VARARGS_TO_ARGV(start, path, argv, dummy_envp);
  char** envp = environ;

  int sock = voyeur_create_client_socket(sockpath);
  if (sock >= 0) {
    write_exec_event(sock, options, path, argv, envp, getpid(), getppid());
    voyeur_write_msg_type(sock, VOYEUR_MSG_DONE);
    voyeur_close_socket(sock);
  }

  void* buf;
  char** voyeur_envp =
    voyeur_augment_environment(envp, libs, opts, sockpath, &buf);

  // Need to pass through to execvpe since we need to provide an environment.
  VOYEUR_DECLARE_NEXT(execvpe_fptr_t, execvpe);
  VOYEUR_LOOKUP_NEXT(execvpe_fptr_t, execvpe);
  return VOYEUR_CALL_NEXT(execvpe, path, argv, voyeur_envp);
}

VOYEUR_INTERPOSE(execlp)


int VOYEUR_FUNC(execvp)(const char* path, char* const argv[])
{
  const char* libs = getenv("LIBVOYEUR_LIBS");
  const char* opts = getenv("LIBVOYEUR_OPTS");
  uint8_t options = voyeur_decode_options(opts, VOYEUR_EVENT_EXEC);
  const char* sockpath = getenv("LIBVOYEUR_SOCKET");

  char** envp = environ;

  int sock = voyeur_create_client_socket(sockpath);
  if (sock >= 0) {
    write_exec_event(sock, options, path, argv, envp, getpid(), getppid());
    voyeur_write_msg_type(sock, VOYEUR_MSG_DONE);
    voyeur_close_socket(sock);
  }

  void* buf;
  char** voyeur_envp =
    voyeur_augment_environment(envp, libs, opts, sockpath, &buf);

  // Need to pass through to execvpe since we need to provide an environment.
  VOYEUR_DECLARE_NEXT(execvpe_fptr_t, execvpe);
  VOYEUR_LOOKUP_NEXT(execvpe_fptr_t, execvpe);
  return VOYEUR_CALL_NEXT(execvpe, path, argv, voyeur_envp);
}

VOYEUR_INTERPOSE(execvp)


int VOYEUR_FUNC(execvpe)(const char* path, char* const argv[], char* const envp[])
{
  const char* libs = getenv("LIBVOYEUR_LIBS");
  const char* opts = getenv("LIBVOYEUR_OPTS");
  uint8_t options = voyeur_decode_options(opts, VOYEUR_EVENT_EXEC);
  const char* sockpath = getenv("LIBVOYEUR_SOCKET");

  int sock = voyeur_create_client_socket(sockpath);
  if (sock >= 0) {
    write_exec_event(sock, options, path, argv, envp, getpid(), getppid());
    voyeur_write_msg_type(sock, VOYEUR_MSG_DONE);
    voyeur_close_socket(sock);
  }

  void* buf;
  char** voyeur_envp =
    voyeur_augment_environment(envp, libs, opts, sockpath, &buf);

  // Pass through the call to the real execvpe.
  VOYEUR_DECLARE_NEXT(execvpe_fptr_t, execvpe);
  VOYEUR_LOOKUP_NEXT(execvpe_fptr_t, execvpe);
  return VOYEUR_CALL_NEXT(execvpe, path, argv, voyeur_envp);
}

VOYEUR_INTERPOSE(execvpe)


//////////////////////////////////////////////////
// posix_spawnp
//////////////////////////////////////////////////

int VOYEUR_FUNC(posix_spawnp)(pid_t* pid,
                              const char* restrict path,
                              const posix_spawn_file_actions_t* file_actions,
                              const posix_spawnattr_t* restrict attrp,
                              char* const argv[restrict],
                              char* const envp[restrict])
{
  pthread_mutex_lock(&voyeur_posix_spawn_mutex);

  voyeur_init_posix_spawn();

  void* buf;
  char** voyeur_envp =
    voyeur_augment_environment(envp,
                               voyeur_posix_spawn_libs,
                               voyeur_posix_spawn_opts,
                               voyeur_posix_spawn_sockpath,
                               &buf);

  // Pass through the call to the real posix_spawnp.
  pid_t child_pid;
  int retval = VOYEUR_CALL_NEXT(posix_spawnp, &child_pid, path,
                                file_actions, attrp,
                                argv, voyeur_envp);

  if (voyeur_posix_spawn_sock >= 0) {
    write_exec_event(voyeur_posix_spawn_sock,
                     voyeur_posix_spawn_options,
                     path, argv, envp,
                     child_pid, getpid());
  }

  pthread_mutex_unlock(&voyeur_posix_spawn_mutex);

  free(voyeur_envp);
  free(buf);

  if (pid) {
    *pid = child_pid;
  }

  return retval;
}

VOYEUR_INTERPOSE(posix_spawnp)

#endif