Revisions for untitled paste

View the changes made to this paste.

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

asjail.c

@@ -0,0 +1,193 @@

+/* 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 */
+}