untitled paste

public ⁨1⁩ ⁨file⁩ 2023-03-26 12:39:06 UTC

asjail.c

Raw
/* Uses x86 specifics! Uses x86 stack direction! */

/* Backend tool */
/* Программа нужна как минимум потому, что один раз не сработал systemd-nspawn, а asjail-max сработал (один раз так было из-за того, что там было что-то смонтировано, reported here: https://github.com/systemd/systemd/issues/3695 ) */
/* Существует программа unshare, так что эта программа deprecated */
/* Самописная альтернатива systemd-nspawn нужна снова и снова. 2020-12-30 мне снова понадобился asjail, чтобы отдебажить проблему с statx: https://bugzilla.redhat.com/show_bug.cgi?id=1760300 */

/* поддерживает только архитектуры, где стек растёт вниз (т. е. почти все) */
/* Мы не подменяем текущий процесс (как это делает chroot), то есть PID другой */
/* Новые ядра поддерживают всё больше и больше опций */
/* CLONE_NEWNET позволяет слушать занятый порт и делает интернет недоступным внутри asjail */

/*
 * Список опций, упомянутых в man page от 2010-09-10 (те из них, которые пригодны для создания контейнеров, отмечены; те, что непригодны - не отмечены; точно):
 * CLONE_CHILD_CLEARTID
 * CLONE_CHILD_SETTID
 * CLONE_FILES
 * CLONE_FS
 * CLONE_IO
 * CLONE_NEWIPC [*]
 * CLONE_NEWNET [*]
 * CLONE_NEWNS [*]
 * CLONE_NEWPID [*]
 * CLONE_NEWUTS [*]
 * CLONE_PARENT
 * CLONE_PARENT_SETTID
 * CLONE_PID
 * CLONE_PTRACE
 * CLONE_SETTLS
 * CLONE_SIGHAND
 * CLONE_STOPPED
 * CLONE_SYSVSEM
 * CLONE_THREAD
 * CLONE_UNTRACES
 * CLONE_VFORK
 * CLONE_VM
 * Все отмеченные опции требуют прав рута (а точнее, определённых CAP_*)
 */

/* TO DO: сделать понятные сообщения об ошибках */
/* TO DO: ядро может быть без поддержки некоторых опций (при конфигурировании не заданы) */
/* TO DO: с сигналами более-менее разобрался, но всё же: когда я нажимаю Ctrl-C, сигнал отсылается всей группе? А если не с клавиатуры, то не всей? А можно вообще послать не всей? */
/* TO DO: на самом деле надо было сделать cgroup, чтобы процесс не смог от нас отфоркнуться (хотя можно просто всегда использовать CLONE_NEWPID) */
/* TO DO: почему-то CLONE_NEWUSER не работает на ядре 3.9-rc5 с допкоммитами на Sid-2013-Apr в Qemu */

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

#include <unistd.h>
#include <sys/wait.h>

#include <sched.h>

/* Yes, this is (probably) bad function */
#define dbg(...) (fprintf(stderr, "asjail: debug: " __VA_ARGS__), fputc('\n', stderr), (void)0)
#define my_assert(...) do { if (!(__VA_ARGS__)) { fprintf (stderr, "assertion failed\n"); exit (EXIT_FAILURE); } } while (0)

sigset_t all_but_chld_and_term;

static int fn(void *argv)
{
  dbg("child: started; setting procmask");
  if (sigprocmask(SIG_UNBLOCK, &all_but_chld_and_term, NULL) != 0)
    {
      abort();
    }

  dbg("child: execvp");
  char *const *cargv = (char **)argv;
  execvp(cargv[0], cargv);
  perror(cargv[0]);
  exit (EXIT_FAILURE);
}

pid_t child = -1;

static void kill_child(int signum /* unused */)
{
  if (child == -1)
    {
      dbg("основной процесс убит до того, как успел создать дочерний процесс");
      exit (EXIT_FAILURE); /* Нас убили до того, как мы успели запустить процесс */
    }

  dbg("got signal, убиваем дочерний процесс");
  kill(child, SIGKILL); /* Не проверяем код возврата, так как процесс может уже завершиться (а мы ещё не успели убрать обработчик) */
}

int main(int argc, char *argv[])
{
  int flags = SIGCHLD;
  int opt;

  while ((opt = getopt(argc, argv, "inmpuU")) != -1) {
    switch (opt) {
      case 'i':
        flags |= CLONE_NEWIPC;
        break;
      case 'n':
        flags |= CLONE_NEWNET;
        break;
      case 'm':
        flags |= CLONE_NEWNS; /* Mount namespace */
        break;
      case 'p':
        flags |= CLONE_NEWPID;
        break;
      case 'u':
        flags |= CLONE_NEWUTS;
        break;
      case 'U':
        flags |= CLONE_NEWUSER;
        break;
      default:
        fprintf (stderr, "Usage: %s [OPTION]... PROGRAM [ARG]...\n", argv[0]);
        exit (EXIT_FAILURE);
    }
  }

  if (optind >= argc)
    {
      fprintf (stderr, "no program\n");
      exit (EXIT_FAILURE);
    }

  if (sigfillset(&all_but_chld_and_term) == -1)
    {
      fprintf (stderr, "cannot initialize signal set; this should never happen\n");
      exit (EXIT_FAILURE);
    }

  sigdelset(&all_but_chld_and_term, SIGCHLD);
  sigdelset(&all_but_chld_and_term, SIGTERM);

  dbg("sigprocmask");
  my_assert(sigprocmask(SIG_BLOCK, &all_but_chld_and_term, NULL) == 0);

  dbg("signal");
  signal(SIGTERM, &kill_child); /* TO DO: use sigaction */

  const int stack_size = 65536; /* TO DO: why this size? */
  void *child_stack = malloc(stack_size);
  my_assert (child_stack != NULL);

  /* Внутри child не нужно перемонтировать /proc (если используется CLONE_NEWNET). Он и так содержит новую информацию в /proc/net. А вот при использовании CLONE_NEWPID /proc надо перемонтировать */
  /* Более того, если в child с NEWPID смонтировать /proc, то он будет выглядеть снаружи довольно странно */
  dbg("clone");
  child = clone(&fn, (char *)child_stack + stack_size, flags, argv + optind);

  if (child == -1)
    {
      fprintf(stderr, "clone failed (are you superuser?)\n");
      exit (EXIT_FAILURE);
    }

  int status;
  dbg("waitpid");
  my_assert (waitpid(child, &status, 0) != -1);

  dbg("waited, restore sigprocmask and signal");
  my_assert (sigprocmask(SIG_UNBLOCK, &all_but_chld_and_term, NULL) == 0);
  signal(SIGTERM, SIG_DFL);

  if (WIFEXITED(status))
    {
      exit (WEXITSTATUS(status));
    }
  else if (WIFSIGNALED(status))
    {
      signal(WTERMSIG(status), SIG_DFL); /* We don't check return value, because it may be SIGKILL */

      /* TO DO: если он сгеренил core dump, то мне это делать уже не нужно */
      if (kill(getpid(), WTERMSIG(status)) == -1)
        {
          fprintf(stderr, "I cannot kill myself; this should never happen\n");
          exit (EXIT_FAILURE);
        }

      pause();
    }
  else
    {
      fprintf(stderr, "bug: child didn't exit and isn't signaled\n"); /* TO DO: is this possible? is WIFCONTINUED possible? */
      exit (EXIT_FAILURE);
    }

  /* NOTREACHED */
}