From 30da3173d5ee69777d0e1de4f9c977dea72e8a77 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Fri, 11 May 2018 16:41:35 +0200 Subject: pack: Relocatable wrapper now properly maps the current UID/GID. * gnu/packages/aux-files/run-in-namespace.c (write_id_map) (disallow_setgroups): New functions. (main): Use 'clone' via 'syscall' instead of 'fork' followed by 'unshare'. Add calls to 'disallow_setgroups' and 'write_id_map' in the parent process. --- gnu/packages/aux-files/run-in-namespace.c | 87 ++++++++++++++++++++++++------- 1 file changed, 68 insertions(+), 19 deletions(-) (limited to 'gnu') diff --git a/gnu/packages/aux-files/run-in-namespace.c b/gnu/packages/aux-files/run-in-namespace.c index d0ab05c5db..f0cff88552 100644 --- a/gnu/packages/aux-files/run-in-namespace.c +++ b/gnu/packages/aux-files/run-in-namespace.c @@ -40,6 +40,7 @@ #include #include #include +#include /* Concatenate DIRECTORY, a slash, and FILE. Return the result, which the caller must eventually free. */ @@ -168,6 +169,48 @@ bind_mount (const char *source, const char *target) closedir (stream); } +/* Write the user/group ID map for PID to FILE, mapping ID to itself. See + user_namespaces(7). */ +static void +write_id_map (pid_t pid, const char *file, int id) +{ + char id_map_file[100]; + snprintf (id_map_file, sizeof id_map_file, "/proc/%d/%s", pid, file); + + char id_map[100]; + + /* Map root and the current user. */ + int len = snprintf (id_map, sizeof id_map, "%d %d 1\n", id, id); + int fd = open (id_map_file, O_WRONLY); + if (fd < 0) + assert_perror (errno); + + int n = write (fd, id_map, len); + if (n < 0) + assert_perror (errno); + + close (fd); +} + +/* Disallow setgroups(2) for PID. */ +static void +disallow_setgroups (pid_t pid) +{ + char file[100]; + + snprintf (file, sizeof file, "/proc/%d/setgroups", pid); + + int fd = open (file, O_WRONLY); + if (fd < 0) + assert_perror (errno); + + int err = write (fd, "deny", 5); + if (err < 0) + assert_perror (errno); + + close (fd); +} + int main (int argc, char *argv[]) @@ -201,26 +244,16 @@ main (int argc, char *argv[]) char *new_store = concat (new_root, "@STORE_DIRECTORY@"); char *cwd = get_current_dir_name (); - pid_t child = fork (); + /* 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: - /* Unshare namespaces in the child and set up bind-mounts from - there. That way, bind-mounts automatically disappear when the - child exits, which simplifies cleanup for the parent. */ - err = unshare (CLONE_NEWNS | CLONE_NEWUSER); - if (err < 0) - { - fprintf (stderr, "%s: error: 'unshare' failed: %m\n", argv[0]); - 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"); - return EXIT_FAILURE; - } - /* Note: Due to we cannot make NEW_ROOT a tmpfs (which would have saved the need for 'rm_rf'.) */ @@ -239,11 +272,27 @@ Please refer to the 'guix pack' documentation for more information.\n"); /* Change back to where we were before chroot'ing. */ chdir (cwd); break; + case -1: - assert_perror (errno); - break; + fprintf (stderr, "%s: error: 'clone' failed: %m\n", argv[0]); + 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"); + 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 */ -- cgit v1.2.3