08dac2
commit 2fe64148a81f0d78050c302f34a6853d21f7cae4
08dac2
Author: DJ Delorie <dj@redhat.com>
08dac2
Date:   Mon Mar 28 23:53:33 2022 -0400
08dac2
08dac2
    Allow for unpriviledged nested containers
08dac2
    
08dac2
    If the build itself is run in a container, we may not be able to
08dac2
    fully set up a nested container for test-container testing.
08dac2
    Notably is the mounting of /proc, since it's critical that it
08dac2
    be mounted from within the same PID namespace as its users, and
08dac2
    thus cannot be bind mounted from outside the container like other
08dac2
    mounts.
08dac2
    
08dac2
    This patch defaults to using the parent's PID namespace instead of
08dac2
    creating a new one, as this is more likely to be allowed.
08dac2
    
08dac2
    If the test needs an isolated PID namespace, it should add the "pidns"
08dac2
    command to its init script.
08dac2
    
08dac2
    Reviewed-by: Carlos O'Donell <carlos@redhat.com>
08dac2
08dac2
Conflicts:
08dac2
	nss/tst-reload2.c
08dac2
          (not in RHEL-8)
08dac2
	support/Makefile
08dac2
          (RHEL-8 missing some routines in libsupport-routines)
08dac2
08dac2
diff --git a/elf/tst-pldd.c b/elf/tst-pldd.c
08dac2
index f381cb0fa7e6b93d..45ac033a0f897088 100644
08dac2
--- a/elf/tst-pldd.c
08dac2
+++ b/elf/tst-pldd.c
08dac2
@@ -85,6 +85,8 @@ in_str_list (const char *libname, const char *const strlist[])
08dac2
 static int
08dac2
 do_test (void)
08dac2
 {
08dac2
+  support_need_proc ("needs /proc/sys/kernel/yama/ptrace_scope and /proc/$child");
08dac2
+
08dac2
   /* Check if our subprocess can be debugged with ptrace.  */
08dac2
   {
08dac2
     int ptrace_scope = support_ptrace_scope ();
08dac2
diff --git a/nptl/tst-pthread-getattr.c b/nptl/tst-pthread-getattr.c
08dac2
index 273b6073abe9cb60..f1c0b39f3a27724c 100644
08dac2
--- a/nptl/tst-pthread-getattr.c
08dac2
+++ b/nptl/tst-pthread-getattr.c
08dac2
@@ -28,6 +28,8 @@
08dac2
 #include <unistd.h>
08dac2
 #include <inttypes.h>
08dac2
 
08dac2
+#include <support/support.h>
08dac2
+
08dac2
 /* There is an obscure bug in the kernel due to which RLIMIT_STACK is sometimes
08dac2
    returned as unlimited when it is not, which may cause this test to fail.
08dac2
    There is also the other case where RLIMIT_STACK is intentionally set as
08dac2
@@ -152,6 +154,8 @@ check_stack_top (void)
08dac2
 static int
08dac2
 do_test (void)
08dac2
 {
08dac2
+  support_need_proc ("Reads /proc/self/maps to get stack size.");
08dac2
+
08dac2
   pagesize = sysconf (_SC_PAGESIZE);
08dac2
   return check_stack_top ();
08dac2
 }
08dac2
diff --git a/support/Makefile b/support/Makefile
08dac2
index 636d69c4f8e7e139..e184fccbe7d2310c 100644
08dac2
--- a/support/Makefile
08dac2
+++ b/support/Makefile
08dac2
@@ -59,6 +59,7 @@ libsupport-routines = \
08dac2
   support_format_hostent \
08dac2
   support_format_netent \
08dac2
   support_isolate_in_subprocess \
08dac2
+  support_need_proc \
08dac2
   support_process_state \
08dac2
   support_ptrace \
08dac2
   support_openpty \
08dac2
diff --git a/support/support.h b/support/support.h
08dac2
index 96833bd4e992e6d3..1466eb29f840fa59 100644
08dac2
--- a/support/support.h
08dac2
+++ b/support/support.h
08dac2
@@ -81,6 +81,11 @@ char *support_quote_string (const char *);
08dac2
    regular file open for writing, and initially empty.  */
08dac2
 int support_descriptor_supports_holes (int fd);
08dac2
 
08dac2
+/* Predicates that a test requires a working /proc filesystem.  This
08dac2
+   call will exit with UNSUPPORTED if /proc is not available, printing
08dac2
+   WHY_MSG as part of the diagnostic.  */
08dac2
+void support_need_proc (const char *why_msg);
08dac2
+
08dac2
 /* Error-checking wrapper functions which terminate the process on
08dac2
    error.  */
08dac2
 
08dac2
diff --git a/support/support_need_proc.c b/support/support_need_proc.c
08dac2
new file mode 100644
08dac2
index 0000000000000000..9b4eab7539b2d6c3
08dac2
--- /dev/null
08dac2
+++ b/support/support_need_proc.c
08dac2
@@ -0,0 +1,35 @@
08dac2
+/* Indicate that a test requires a working /proc.
08dac2
+   Copyright (C) 2022 Free Software Foundation, Inc.
08dac2
+   This file is part of the GNU C Library.
08dac2
+
08dac2
+   The GNU C Library is free software; you can redistribute it and/or
08dac2
+   modify it under the terms of the GNU Lesser General Public
08dac2
+   License as published by the Free Software Foundation; either
08dac2
+   version 2.1 of the License, or (at your option) any later version.
08dac2
+
08dac2
+   The GNU C Library is distributed in the hope that it will be useful,
08dac2
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
08dac2
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
08dac2
+   Lesser General Public License for more details.
08dac2
+
08dac2
+   You should have received a copy of the GNU Lesser General Public
08dac2
+   License along with the GNU C Library; if not, see
08dac2
+   <https://www.gnu.org/licenses/>.  */
08dac2
+
08dac2
+#include <unistd.h>
08dac2
+#include <support/check.h>
08dac2
+#include <support/support.h>
08dac2
+
08dac2
+/* We test for /proc/self/maps since that's one of the files that one
08dac2
+   of our tests actually uses, but the general idea is if Linux's
08dac2
+   /proc/ (procfs) filesystem is mounted.  If not, the process exits
08dac2
+   with an UNSUPPORTED result code.  */
08dac2
+
08dac2
+void
08dac2
+support_need_proc (const char *why_msg)
08dac2
+{
08dac2
+#ifdef __linux__
08dac2
+  if (access ("/proc/self/maps", R_OK))
08dac2
+    FAIL_UNSUPPORTED ("/proc is not available, %s", why_msg);
08dac2
+#endif
08dac2
+}
08dac2
diff --git a/support/test-container.c b/support/test-container.c
08dac2
index 9975c8cb7bc9a955..2bce4db841ff7668 100644
08dac2
--- a/support/test-container.c
08dac2
+++ b/support/test-container.c
08dac2
@@ -95,6 +95,7 @@ int verbose = 0;
08dac2
    * mytest.root/mytest.script has a list of "commands" to run:
08dac2
        syntax:
08dac2
          # comment
08dac2
+	 pidns <comment>
08dac2
          su
08dac2
          mv FILE FILE
08dac2
 	 cp FILE FILE
08dac2
@@ -120,6 +121,8 @@ int verbose = 0;
08dac2
 
08dac2
        details:
08dac2
          - '#': A comment.
08dac2
+	 - 'pidns': Require a separate PID namespace, prints comment if it can't
08dac2
+	    (default is a shared pid namespace)
08dac2
          - 'su': Enables running test as root in the container.
08dac2
          - 'mv': A minimal move files command.
08dac2
          - 'cp': A minimal copy files command.
08dac2
@@ -143,7 +146,7 @@ int verbose = 0;
08dac2
    * Simple, easy to review code (i.e. prefer simple naive code over
08dac2
      complex efficient code)
08dac2
 
08dac2
-   * The current implementation ist parallel-make-safe, but only in
08dac2
+   * The current implementation is parallel-make-safe, but only in
08dac2
      that it uses a lock to prevent parallel access to the testroot.  */
08dac2
 
08dac2
 
08dac2
@@ -222,11 +225,37 @@ concat (const char *str, ...)
08dac2
   return bufs[n];
08dac2
 }
08dac2
 
08dac2
+/* Like the above, but put spaces between words.  Caller frees.  */
08dac2
+static char *
08dac2
+concat_words (char **words, int num_words)
08dac2
+{
08dac2
+  int len = 0;
08dac2
+  int i;
08dac2
+  char *rv, *p;
08dac2
+
08dac2
+  for (i = 0; i < num_words; i ++)
08dac2
+    {
08dac2
+      len += strlen (words[i]);
08dac2
+      len ++;
08dac2
+    }
08dac2
+
08dac2
+  p = rv = (char *) xmalloc (len);
08dac2
+
08dac2
+  for (i = 0; i < num_words; i ++)
08dac2
+    {
08dac2
+      if (i > 0)
08dac2
+	p = stpcpy (p, " ");
08dac2
+      p = stpcpy (p, words[i]);
08dac2
+    }
08dac2
+
08dac2
+  return rv;
08dac2
+}
08dac2
+
08dac2
 /* Try to mount SRC onto DEST.  */
08dac2
 static void
08dac2
 trymount (const char *src, const char *dest)
08dac2
 {
08dac2
-  if (mount (src, dest, "", MS_BIND, NULL) < 0)
08dac2
+  if (mount (src, dest, "", MS_BIND | MS_REC, NULL) < 0)
08dac2
     FAIL_EXIT1 ("can't mount %s onto %s\n", src, dest);
08dac2
 }
08dac2
 
08dac2
@@ -709,6 +738,9 @@ main (int argc, char **argv)
08dac2
   gid_t original_gid;
08dac2
   /* If set, the test runs as root instead of the user running the testsuite.  */
08dac2
   int be_su = 0;
08dac2
+  int require_pidns = 0;
08dac2
+  const char *pidns_comment = NULL;
08dac2
+  int do_proc_mounts = 0;
08dac2
   int UMAP;
08dac2
   int GMAP;
08dac2
   /* Used for "%lld %lld 1" so need not be large.  */
08dac2
@@ -991,6 +1023,12 @@ main (int argc, char **argv)
08dac2
 	      {
08dac2
 		be_su = 1;
08dac2
 	      }
08dac2
+	    else if (nt >= 1 && strcmp (the_words[0], "pidns") == 0)
08dac2
+	      {
08dac2
+		require_pidns = 1;
08dac2
+		if (nt > 1)
08dac2
+		  pidns_comment = concat_words (the_words + 1, nt - 1);
08dac2
+	      }
08dac2
 	    else if (nt == 3 && strcmp (the_words[0], "mkdirp") == 0)
08dac2
 	      {
08dac2
 		long int m;
08dac2
@@ -1048,7 +1086,8 @@ main (int argc, char **argv)
08dac2
 
08dac2
 #ifdef CLONE_NEWNS
08dac2
   /* The unshare here gives us our own spaces and capabilities.  */
08dac2
-  if (unshare (CLONE_NEWUSER | CLONE_NEWPID | CLONE_NEWNS) < 0)
08dac2
+  if (unshare (CLONE_NEWUSER | CLONE_NEWNS
08dac2
+	       | (require_pidns ? CLONE_NEWPID : 0)) < 0)
08dac2
     {
08dac2
       /* Older kernels may not support all the options, or security
08dac2
 	 policy may block this call.  */
08dac2
@@ -1059,6 +1098,11 @@ main (int argc, char **argv)
08dac2
 	    check_for_unshare_hints ();
08dac2
 	  FAIL_UNSUPPORTED ("unable to unshare user/fs: %s", strerror (saved_errno));
08dac2
 	}
08dac2
+      /* We're about to exit anyway, it's "safe" to call unshare again
08dac2
+	 just to see if the CLONE_NEWPID caused the error.  */
08dac2
+      else if (require_pidns && unshare (CLONE_NEWUSER | CLONE_NEWNS) >= 0)
08dac2
+	FAIL_EXIT1 ("unable to unshare pid ns: %s : %s", strerror (errno),
08dac2
+		    pidns_comment ? pidns_comment : "required by test");
08dac2
       else
08dac2
 	FAIL_EXIT1 ("unable to unshare user/fs: %s", strerror (errno));
08dac2
     }
08dac2
@@ -1074,6 +1118,15 @@ main (int argc, char **argv)
08dac2
   trymount (support_srcdir_root, new_srcdir_path);
08dac2
   trymount (support_objdir_root, new_objdir_path);
08dac2
 
08dac2
+  /* It may not be possible to mount /proc directly.  */
08dac2
+  if (! require_pidns)
08dac2
+  {
08dac2
+    char *new_proc = concat (new_root_path, "/proc", NULL);
08dac2
+    xmkdirp (new_proc, 0755);
08dac2
+    trymount ("/proc", new_proc);
08dac2
+    do_proc_mounts = 1;
08dac2
+  }
08dac2
+
08dac2
   xmkdirp (concat (new_root_path, "/dev", NULL), 0755);
08dac2
   devmount (new_root_path, "null");
08dac2
   devmount (new_root_path, "zero");
08dac2
@@ -1136,42 +1189,60 @@ main (int argc, char **argv)
08dac2
 
08dac2
   maybe_xmkdir ("/tmp", 0755);
08dac2
 
08dac2
-  /* Now that we're pid 1 (effectively "root") we can mount /proc  */
08dac2
-  maybe_xmkdir ("/proc", 0777);
08dac2
-  if (mount ("proc", "/proc", "proc", 0, NULL) < 0)
08dac2
-    FAIL_EXIT1 ("Unable to mount /proc: ");
08dac2
-
08dac2
-  /* We map our original UID to the same UID in the container so we
08dac2
-     can own our own files normally.  */
08dac2
-  UMAP = open ("/proc/self/uid_map", O_WRONLY);
08dac2
-  if (UMAP < 0)
08dac2
-    FAIL_EXIT1 ("can't write to /proc/self/uid_map\n");
08dac2
-
08dac2
-  sprintf (tmp, "%lld %lld 1\n",
08dac2
-	   (long long) (be_su ? 0 : original_uid), (long long) original_uid);
08dac2
-  write (UMAP, tmp, strlen (tmp));
08dac2
-  xclose (UMAP);
08dac2
-
08dac2
-  /* We must disable setgroups () before we can map our groups, else we
08dac2
-     get EPERM.  */
08dac2
-  GMAP = open ("/proc/self/setgroups", O_WRONLY);
08dac2
-  if (GMAP >= 0)
08dac2
+  if (require_pidns)
08dac2
     {
08dac2
-      /* We support kernels old enough to not have this.  */
08dac2
-      write (GMAP, "deny\n", 5);
08dac2
-      xclose (GMAP);
08dac2
+      /* Now that we're pid 1 (effectively "root") we can mount /proc  */
08dac2
+      maybe_xmkdir ("/proc", 0777);
08dac2
+      if (mount ("proc", "/proc", "proc", 0, NULL) != 0)
08dac2
+	{
08dac2
+	  /* This happens if we're trying to create a nested container,
08dac2
+	     like if the build is running under podman, and we lack
08dac2
+	     priviledges.
08dac2
+
08dac2
+	     Ideally we would WARN here, but that would just add noise to
08dac2
+	     *every* test-container test, and the ones that care should
08dac2
+	     have their own relevent diagnostics.
08dac2
+
08dac2
+	     FAIL_EXIT1 ("Unable to mount /proc: ");  */
08dac2
+	}
08dac2
+      else
08dac2
+	do_proc_mounts = 1;
08dac2
     }
08dac2
 
08dac2
-  /* We map our original GID to the same GID in the container so we
08dac2
-     can own our own files normally.  */
08dac2
-  GMAP = open ("/proc/self/gid_map", O_WRONLY);
08dac2
-  if (GMAP < 0)
08dac2
-    FAIL_EXIT1 ("can't write to /proc/self/gid_map\n");
08dac2
+  if (do_proc_mounts)
08dac2
+    {
08dac2
+      /* We map our original UID to the same UID in the container so we
08dac2
+	 can own our own files normally.  */
08dac2
+      UMAP = open ("/proc/self/uid_map", O_WRONLY);
08dac2
+      if (UMAP < 0)
08dac2
+	FAIL_EXIT1 ("can't write to /proc/self/uid_map\n");
08dac2
+
08dac2
+      sprintf (tmp, "%lld %lld 1\n",
08dac2
+	       (long long) (be_su ? 0 : original_uid), (long long) original_uid);
08dac2
+      write (UMAP, tmp, strlen (tmp));
08dac2
+      xclose (UMAP);
08dac2
+
08dac2
+      /* We must disable setgroups () before we can map our groups, else we
08dac2
+	 get EPERM.  */
08dac2
+      GMAP = open ("/proc/self/setgroups", O_WRONLY);
08dac2
+      if (GMAP >= 0)
08dac2
+	{
08dac2
+	  /* We support kernels old enough to not have this.  */
08dac2
+	  write (GMAP, "deny\n", 5);
08dac2
+	  xclose (GMAP);
08dac2
+	}
08dac2
 
08dac2
-  sprintf (tmp, "%lld %lld 1\n",
08dac2
-	   (long long) (be_su ? 0 : original_gid), (long long) original_gid);
08dac2
-  write (GMAP, tmp, strlen (tmp));
08dac2
-  xclose (GMAP);
08dac2
+      /* We map our original GID to the same GID in the container so we
08dac2
+	 can own our own files normally.  */
08dac2
+      GMAP = open ("/proc/self/gid_map", O_WRONLY);
08dac2
+      if (GMAP < 0)
08dac2
+	FAIL_EXIT1 ("can't write to /proc/self/gid_map\n");
08dac2
+
08dac2
+      sprintf (tmp, "%lld %lld 1\n",
08dac2
+	       (long long) (be_su ? 0 : original_gid), (long long) original_gid);
08dac2
+      write (GMAP, tmp, strlen (tmp));
08dac2
+      xclose (GMAP);
08dac2
+    }
08dac2
 
08dac2
   if (change_cwd)
08dac2
     {