kselftests: Add test to check for rlimit changes in different user namespaces
authorAlexey Gladkov <legion@kernel.org>
Thu, 22 Apr 2021 12:27:15 +0000 (14:27 +0200)
committerEric W. Biederman <ebiederm@xmission.com>
Fri, 30 Apr 2021 19:14:03 +0000 (14:14 -0500)
The testcase runs few instances of the program with RLIMIT_NPROC=1 from
user uid=60000, in different user namespaces.

Signed-off-by: Alexey Gladkov <legion@kernel.org>
Link: https://lkml.kernel.org/r/28cafdcdd4abd8494b34a27f1970b666b30de8bf.1619094428.git.legion@kernel.org
Signed-off-by: Eric W. Biederman <ebiederm@xmission.com>
tools/testing/selftests/Makefile
tools/testing/selftests/rlimits/.gitignore [new file with mode: 0644]
tools/testing/selftests/rlimits/Makefile [new file with mode: 0644]
tools/testing/selftests/rlimits/config [new file with mode: 0644]
tools/testing/selftests/rlimits/rlimits-per-userns.c [new file with mode: 0644]

index 6c575cf34a71f3c5eff0f62c55d622111896451f..a4ea1481bd9acd236b5b9119ee70f1f028c2f697 100644 (file)
@@ -48,6 +48,7 @@ TARGETS += proc
 TARGETS += pstore
 TARGETS += ptrace
 TARGETS += openat2
+TARGETS += rlimits
 TARGETS += rseq
 TARGETS += rtc
 TARGETS += seccomp
diff --git a/tools/testing/selftests/rlimits/.gitignore b/tools/testing/selftests/rlimits/.gitignore
new file mode 100644 (file)
index 0000000..091021f
--- /dev/null
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0-only
+rlimits-per-userns
diff --git a/tools/testing/selftests/rlimits/Makefile b/tools/testing/selftests/rlimits/Makefile
new file mode 100644 (file)
index 0000000..03aadb4
--- /dev/null
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+CFLAGS += -Wall -O2 -g
+TEST_GEN_PROGS := rlimits-per-userns
+
+include ../lib.mk
diff --git a/tools/testing/selftests/rlimits/config b/tools/testing/selftests/rlimits/config
new file mode 100644 (file)
index 0000000..416bd53
--- /dev/null
@@ -0,0 +1 @@
+CONFIG_USER_NS=y
diff --git a/tools/testing/selftests/rlimits/rlimits-per-userns.c b/tools/testing/selftests/rlimits/rlimits-per-userns.c
new file mode 100644 (file)
index 0000000..26dc949
--- /dev/null
@@ -0,0 +1,161 @@
+// 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);
+}