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.
This commit is contained in:
Ludovic Courtès 2020-05-07 21:37:03 +02:00
parent 14928af2af
commit bdb9b4e8d3
No known key found for this signature in database
GPG Key ID: 090B11993D9AEBB5
1 changed files with 85 additions and 66 deletions

View File

@ -219,6 +219,83 @@ disallow_setgroups (pid_t pid)
close (fd); 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 #ifdef PROOT_PROGRAM
@ -285,81 +362,23 @@ main (int argc, char *argv[])
if (strcmp (store, "@STORE_DIRECTORY@") != 0 if (strcmp (store, "@STORE_DIRECTORY@") != 0
&& lstat ("@WRAPPED_PROGRAM@", &statbuf) != 0) && lstat ("@WRAPPED_PROGRAM@", &statbuf) != 0)
{ {
/* Spawn @WRAPPED_PROGRAM@ in a separate namespace where STORE is /* Buffer stderr so that nothing's displayed if 'exec_in_user_namespace'
bind-mounted in the right place. */ fails but 'exec_with_proot' works. */
int err; static char stderr_buffer[4096];
char *new_root = mkdtemp (strdup ("/tmp/guix-exec-XXXXXX")); setvbuf (stderr, stderr_buffer, _IOFBF, sizeof stderr_buffer);
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 exec_in_user_namespace (store, argc, argv);
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);
break;
case -1:
rm_rf (new_root);
#ifdef PROOT_PROGRAM #ifdef PROOT_PROGRAM
exec_with_proot (store, argc, argv); exec_with_proot (store, argc, argv);
#else #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\ This may be because \"user namespaces\" are not supported on this system.\n\
Consequently, we cannot run '@WRAPPED_PROGRAM@',\n\ Consequently, we cannot run '@WRAPPED_PROGRAM@',\n\
unless you move it to the '@STORE_DIRECTORY@' directory.\n\ unless you move it to the '@STORE_DIRECTORY@' directory.\n\
\n\ \n\
Please refer to the 'guix pack' documentation for more information.\n"); Please refer to the 'guix pack' documentation for more information.\n");
#endif #endif
return EXIT_FAILURE; 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);
}
}
} }
/* The executable is available under @STORE_DIRECTORY@, so we can now /* The executable is available under @STORE_DIRECTORY@, so we can now