// SPDX-License-Identifier: GPL-2.0-or-later /* * Author: Alexey Gladkov <gladkov.alexey@gmail.com> */ #define _GNU_SOURCE #include <sys/types.h> #include <sys/wait.h> #include <sys/time.h> #include <sys/resource.h> #include <sys/prctl.h> #include <sys/stat.h> #include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <sched.h> #include <signal.h> #include <limits.h> #include <fcntl.h> #include <errno.h> #include <err.h> #define NR_CHILDS 2 static char *service_prog; static uid_t user = 60000; static uid_t group = 60000; static void setrlimit_nproc(rlim_t n) { pid_t pid = getpid(); struct rlimit limit = { .rlim_cur = n, .rlim_max = n }; warnx("(pid=%d): Setting RLIMIT_NPROC=%ld", pid, n); if (setrlimit(RLIMIT_NPROC, &limit) < 0) err(EXIT_FAILURE, "(pid=%d): setrlimit(RLIMIT_NPROC)", pid); } static pid_t fork_child(void) { pid_t pid = fork(); if (pid < 0) err(EXIT_FAILURE, "fork"); if (pid > 0) return pid; pid = getpid(); warnx("(pid=%d): New process starting ...", pid); if (prctl(PR_SET_PDEATHSIG, SIGKILL) < 0) err(EXIT_FAILURE, "(pid=%d): prctl(PR_SET_PDEATHSIG)", pid); signal(SIGUSR1, SIG_DFL); warnx("(pid=%d): Changing to uid=%d, gid=%d", pid, user, group); if (setgid(group) < 0) err(EXIT_FAILURE, "(pid=%d): setgid(%d)", pid, group); if (setuid(user) < 0) err(EXIT_FAILURE, "(pid=%d): setuid(%d)", pid, user); warnx("(pid=%d): Service running ...", pid); warnx("(pid=%d): Unshare user namespace", pid); if (unshare(CLONE_NEWUSER) < 0) err(EXIT_FAILURE, "unshare(CLONE_NEWUSER)"); char *const argv[] = { "service", NULL }; char *const envp[] = { "I_AM_SERVICE=1", NULL }; warnx("(pid=%d): Executing real service ...", pid); execve(service_prog, argv, envp); err(EXIT_FAILURE, "(pid=%d): execve", pid); } int main(int argc, char **argv) { size_t i; pid_t child[NR_CHILDS]; int wstatus[NR_CHILDS]; int childs = NR_CHILDS; pid_t pid; if (getenv("I_AM_SERVICE")) { pause(); exit(EXIT_SUCCESS); } service_prog = argv[0]; pid = getpid(); warnx("(pid=%d) Starting testcase", pid); /* * This rlimit is not a problem for root because it can be exceeded. */ setrlimit_nproc(1); for (i = 0; i < NR_CHILDS; i++) { child[i] = fork_child(); wstatus[i] = 0; usleep(250000); } while (1) { for (i = 0; i < NR_CHILDS; i++) { if (child[i] <= 0) continue; errno = 0; pid_t ret = waitpid(child[i], &wstatus[i], WNOHANG); if (!ret || (!WIFEXITED(wstatus[i]) && !WIFSIGNALED(wstatus[i]))) continue; if (ret < 0 && errno != ECHILD) warn("(pid=%d): waitpid(%d)", pid, child[i]); child[i] *= -1; childs -= 1; } if (!childs) break; usleep(250000); for (i = 0; i < NR_CHILDS; i++) { if (child[i] <= 0) continue; kill(child[i], SIGUSR1); } } for (i = 0; i < NR_CHILDS; i++) { if (WIFEXITED(wstatus[i])) warnx("(pid=%d): pid %d exited, status=%d", pid, -child[i], WEXITSTATUS(wstatus[i])); else if (WIFSIGNALED(wstatus[i])) warnx("(pid=%d): pid %d killed by signal %d", pid, -child[i], WTERMSIG(wstatus[i])); if (WIFSIGNALED(wstatus[i]) && WTERMSIG(wstatus[i]) == SIGUSR1) continue; warnx("(pid=%d): Test failed", pid); exit(EXIT_FAILURE); } warnx("(pid=%d): Test passed", pid); exit(EXIT_SUCCESS); }