/* 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 #include #include #include #include #include /* 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 */ }