aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLudovic Courtès <ludo@gnu.org>2020-05-07 21:37:03 +0200
committerLudovic Courtès <ludo@gnu.org>2020-05-07 23:52:25 +0200
commitbdb9b4e8d3ebbc50f4725af12d91df300de05d15 (patch)
tree8b93ca049a108ea2a4bc82e578b72d8e9fa20c38
parent14928af2af501516aba13d569eae0853e769fb91 (diff)
downloadguix-bdb9b4e8d3ebbc50f4725af12d91df300de05d15.tar.gz
guix-bdb9b4e8d3ebbc50f4725af12d91df300de05d15.zip
pack: Factorize 'exec_in_user_namespace' wrapper.
* gnu/packages/aux-files/run-in-namespace.c (exec_in_user_namespace): New function, with code taken from... (main): ... here. Call it.
-rw-r--r--gnu/packages/aux-files/run-in-namespace.c151
1 files changed, 85 insertions, 66 deletions
diff --git a/gnu/packages/aux-files/run-in-namespace.c b/gnu/packages/aux-files/run-in-namespace.c
index 501f096dc6..23e7875173 100644
--- a/gnu/packages/aux-files/run-in-namespace.c
+++ b/gnu/packages/aux-files/run-in-namespace.c
@@ -219,6 +219,83 @@ disallow_setgroups (pid_t pid)
close (fd);
}
+/* Run the wrapper program in a separate mount user namespace. Return only
+ upon failure. */
+static void
+exec_in_user_namespace (const char *store, int argc, char *argv[])
+{
+ /* Spawn @WRAPPED_PROGRAM@ in a separate namespace where STORE is
+ bind-mounted in the right place. */
+ int err;
+ char *new_root = mkdtemp (strdup ("/tmp/guix-exec-XXXXXX"));
+ char *new_store = concat (new_root, "@STORE_DIRECTORY@");
+ char *cwd = get_current_dir_name ();
+
+ /* Create a child with separate namespaces and set up bind-mounts from
+ there. That way, bind-mounts automatically disappear when the child
+ exits, which simplifies cleanup for the parent. Note: clone is more
+ convenient than fork + unshare since the parent can directly write
+ the child uid_map/gid_map files. */
+ pid_t child = syscall (SYS_clone, SIGCHLD | CLONE_NEWNS | CLONE_NEWUSER,
+ NULL, NULL, NULL);
+ switch (child)
+ {
+ case 0:
+ /* Note: Due to <https://bugzilla.kernel.org/show_bug.cgi?id=183461>
+ we cannot make NEW_ROOT a tmpfs (which would have saved the need
+ for 'rm_rf'.) */
+ bind_mount ("/", new_root);
+ mkdir_p (new_store);
+ err = mount (store, new_store, "none", MS_BIND | MS_REC | MS_RDONLY,
+ NULL);
+ if (err < 0)
+ assert_perror (errno);
+
+ chdir (new_root);
+ err = chroot (new_root);
+ if (err < 0)
+ assert_perror (errno);
+
+ /* Change back to where we were before chroot'ing. */
+ chdir (cwd);
+
+ int err = execv ("@WRAPPED_PROGRAM@", argv);
+ if (err < 0)
+ assert_perror (errno);
+ break;
+
+ case -1:
+ /* Failure: user namespaces not supported. */
+ fprintf (stderr, "%s: error: 'clone' failed: %m\n", argv[0]);
+ rm_rf (new_root);
+ break;
+
+ default:
+ {
+ /* Map the current user/group ID in the child's namespace (the
+ default is to get the "overflow UID", i.e., the UID of
+ "nobody"). We must first disallow 'setgroups' for that
+ process. */
+ disallow_setgroups (child);
+ write_id_map (child, "uid_map", getuid ());
+ write_id_map (child, "gid_map", getgid ());
+
+ int status;
+ waitpid (child, &status, 0);
+ chdir ("/"); /* avoid EBUSY */
+ rm_rf (new_root);
+ free (new_root);
+
+ if (WIFEXITED (status))
+ exit (WEXITSTATUS (status));
+ else
+ /* Abnormal termination cannot really be reproduced, so exit
+ with 255. */
+ exit (255);
+ }
+ }
+}
+
#ifdef PROOT_PROGRAM
@@ -285,81 +362,23 @@ main (int argc, char *argv[])
if (strcmp (store, "@STORE_DIRECTORY@") != 0
&& lstat ("@WRAPPED_PROGRAM@", &statbuf) != 0)
{
- /* Spawn @WRAPPED_PROGRAM@ in a separate namespace where STORE is
- bind-mounted in the right place. */
- int err;
- char *new_root = mkdtemp (strdup ("/tmp/guix-exec-XXXXXX"));
- char *new_store = concat (new_root, "@STORE_DIRECTORY@");
- char *cwd = get_current_dir_name ();
-
- /* Create a child with separate namespaces and set up bind-mounts from
- there. That way, bind-mounts automatically disappear when the child
- exits, which simplifies cleanup for the parent. Note: clone is more
- convenient than fork + unshare since the parent can directly write
- the child uid_map/gid_map files. */
- pid_t child = syscall (SYS_clone, SIGCHLD | CLONE_NEWNS | CLONE_NEWUSER,
- NULL, NULL, NULL);
- switch (child)
- {
- case 0:
- /* Note: Due to <https://bugzilla.kernel.org/show_bug.cgi?id=183461>
- we cannot make NEW_ROOT a tmpfs (which would have saved the need
- for 'rm_rf'.) */
- bind_mount ("/", new_root);
- mkdir_p (new_store);
- err = mount (store, new_store, "none", MS_BIND | MS_REC | MS_RDONLY,
- NULL);
- if (err < 0)
- assert_perror (errno);
-
- chdir (new_root);
- err = chroot (new_root);
- if (err < 0)
- assert_perror (errno);
+ /* Buffer stderr so that nothing's displayed if 'exec_in_user_namespace'
+ fails but 'exec_with_proot' works. */
+ static char stderr_buffer[4096];
+ setvbuf (stderr, stderr_buffer, _IOFBF, sizeof stderr_buffer);
- /* Change back to where we were before chroot'ing. */
- chdir (cwd);
- break;
-
- case -1:
- rm_rf (new_root);
+ exec_in_user_namespace (store, argc, argv);
#ifdef PROOT_PROGRAM
- exec_with_proot (store, argc, argv);
+ exec_with_proot (store, argc, argv);
#else
- fprintf (stderr, "%s: error: 'clone' failed: %m\n", argv[0]);
- fprintf (stderr, "\
+ fprintf (stderr, "\
This may be because \"user namespaces\" are not supported on this system.\n\
Consequently, we cannot run '@WRAPPED_PROGRAM@',\n\
unless you move it to the '@STORE_DIRECTORY@' directory.\n\
\n\
Please refer to the 'guix pack' documentation for more information.\n");
#endif
- return EXIT_FAILURE;
-
- default:
- {
- /* Map the current user/group ID in the child's namespace (the
- default is to get the "overflow UID", i.e., the UID of
- "nobody"). We must first disallow 'setgroups' for that
- process. */
- disallow_setgroups (child);
- write_id_map (child, "uid_map", getuid ());
- write_id_map (child, "gid_map", getgid ());
-
- int status;
- waitpid (child, &status, 0);
- chdir ("/"); /* avoid EBUSY */
- rm_rf (new_root);
- free (new_root);
-
- if (WIFEXITED (status))
- exit (WEXITSTATUS (status));
- else
- /* Abnormal termination cannot really be reproduced, so exit
- with 255. */
- exit (255);
- }
- }
+ return EXIT_FAILURE;
}
/* The executable is available under @STORE_DIRECTORY@, so we can now