| // 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); | |
| } |