From d677ad0435804108a2858b090083ddad603cca17 Mon Sep 17 00:00:00 2001 From: Giuseppe Scrivano Date: Thu, 20 Jul 2017 19:33:01 +0200 Subject: [PATCH] rootless: allow multiple user/group mappings Take advantage of the newuidmap/newgidmap tools to allow multiple users/groups to be mapped into the new user namespace in the rootless case. Signed-off-by: Giuseppe Scrivano --- libcontainer/configs/validate/rootless.go | 58 +++++++++-------- libcontainer/nsenter/nsexec.c | 76 +++++++++++++++++++++-- 2 files changed, 101 insertions(+), 33 deletions(-) diff --git a/libcontainer/configs/validate/rootless.go b/libcontainer/configs/validate/rootless.go index 0cebfaf801a..c482f93044c 100644 --- a/libcontainer/configs/validate/rootless.go +++ b/libcontainer/configs/validate/rootless.go @@ -4,6 +4,7 @@ import ( "fmt" "os" "reflect" + "strconv" "strings" "github.com/opencontainers/runc/libcontainer/configs" @@ -36,37 +37,27 @@ func (v *ConfigValidator) rootless(config *configs.Config) error { return nil } -func rootlessMappings(config *configs.Config) error { - rootuid, err := config.HostRootUID() - if err != nil { - return fmt.Errorf("failed to get root uid from uidMappings: %v", err) +func hasIDMapping(id int, mappings []configs.IDMap) bool { + for _, m := range mappings { + if id >= m.ContainerID && id < m.ContainerID+m.Size { + return true + } } + return false +} + +func rootlessMappings(config *configs.Config) error { if euid := geteuid(); euid != 0 { if !config.Namespaces.Contains(configs.NEWUSER) { return fmt.Errorf("rootless containers require user namespaces") } - if rootuid != euid { - return fmt.Errorf("rootless containers cannot map container root to a different host user") - } - } - - rootgid, err := config.HostRootGID() - if err != nil { - return fmt.Errorf("failed to get root gid from gidMappings: %v", err) - } - - // Similar to the above test, we need to make sure that we aren't trying to - // map to a group ID that we don't have the right to be. - if rootgid != getegid() { - return fmt.Errorf("rootless containers cannot map container root to a different host group") } - // We can only map one user and group inside a container (our own). - if len(config.UidMappings) != 1 || config.UidMappings[0].Size != 1 { - return fmt.Errorf("rootless containers cannot map more than one user") + if len(config.UidMappings) == 0 { + return fmt.Errorf("rootless containers requires at least one UID mapping") } - if len(config.GidMappings) != 1 || config.GidMappings[0].Size != 1 { - return fmt.Errorf("rootless containers cannot map more than one group") + if len(config.GidMappings) == 0 { + return fmt.Errorf("rootless containers requires at least one UID mapping") } return nil @@ -104,11 +95,24 @@ func rootlessMount(config *configs.Config) error { // Check that the options list doesn't contain any uid= or gid= entries // that don't resolve to root. for _, opt := range strings.Split(mount.Data, ",") { - if strings.HasPrefix(opt, "uid=") && opt != "uid=0" { - return fmt.Errorf("cannot specify uid= mount options in rootless containers where argument isn't 0") + if strings.HasPrefix(opt, "uid=") { + uid, _ := strconv.Atoi(strings.Replace(opt, "uid=", "", 1)) + if !hasIDMapping(uid, config.UidMappings) { + return fmt.Errorf("cannot specify uid= mount options in rootless containers to a not mapped uid in the user namespace") + } } - if strings.HasPrefix(opt, "gid=") && opt != "gid=0" { - return fmt.Errorf("cannot specify gid= mount options in rootless containers where argument isn't 0") + if strings.HasPrefix(opt, "gid=") { + gid, _ := strconv.Atoi(strings.Replace(opt, "gid=", "", 1)) + found := false + for _, m := range config.GidMappings { + if gid >= m.ContainerID && gid < m.ContainerID+m.Size { + found = true + break + } + } + if !found { + return fmt.Errorf("cannot specify gid= mount options in rootless containers to a not mapped gid in the user namespace") + } } } } diff --git a/libcontainer/nsenter/nsexec.c b/libcontainer/nsenter/nsexec.c index 6814a5abbf1..17d34d854b9 100644 --- a/libcontainer/nsenter/nsexec.c +++ b/libcontainer/nsenter/nsexec.c @@ -1,3 +1,4 @@ + #define _GNU_SOURCE #include #include @@ -19,6 +20,8 @@ #include #include #include +#include + #include #include @@ -191,22 +194,83 @@ static void update_setgroups(int pid, enum policy_t setgroup) } } -static void update_uidmap(int pid, char *map, size_t map_len) +static int try_mapping_tool(const char *app, int pid, char *map, size_t map_len) +{ + int child = fork(); + if (child < 0) + bail("failed to fork"); + + if (child == 0) { +#define MAX_ARGV 20 + char *argv[MAX_ARGV]; + char pid_fmt[16]; + int argc = 0; + size_t i; + + sprintf (pid_fmt, "%d", pid); + + argv[argc++] = (char *) app; + argv[argc++] = pid_fmt; + /* + * Convert the map string into a list of argument that + * newuidmap/newgidmap can understand. + */ + for (i = 0; i < map_len - 1; ) { + argv[argc++] = &map[i++]; + + while (i < map_len - 1 && map[i] != '\n' + && map[i] != ' ' && map[i] != '\0') + i++; + map[i++] = '\0'; + + while (i < map_len - 1 && map[i] == '\n' && map[i] == ' ') + i++; + if (map[i] == '\0') + break; + } + execvp (app, argv); + } + else { + int status; + while (true) { + if (waitpid(child, &status, 0) < 0) { + if (errno == EINTR) + continue; + bail("failed to waitpid"); + } + if (WIFEXITED(status)) + return WEXITSTATUS(status); + } + } + return -1; +} + +static void update_uidmap(int pid, uint8_t is_rootless, char *map, size_t map_len) { if (map == NULL || map_len <= 0) return; - if (write_file(map, map_len, "/proc/%d/uid_map", pid) < 0) + if (write_file(map, map_len, "/proc/%d/uid_map", pid) < 0) { + if(errno == EPERM + && try_mapping_tool ("newuidmap", pid, map, map_len) == 0) { + return; + } bail("failed to update /proc/%d/uid_map", pid); + } } -static void update_gidmap(int pid, char *map, size_t map_len) +static void update_gidmap(int pid, uint8_t is_rootless, char *map, size_t map_len) { if (map == NULL || map_len <= 0) return; - if (write_file(map, map_len, "/proc/%d/gid_map", pid) < 0) + if (write_file(map, map_len, "/proc/%d/gid_map", pid) < 0) { + if(errno == EPERM + && try_mapping_tool ("newgidmap", pid, map, map_len) == 0) { + return; + } bail("failed to update /proc/%d/gid_map", pid); + } } static void update_oom_score_adj(char *data, size_t len) @@ -596,8 +660,8 @@ void nsexec(void) update_setgroups(child, SETGROUPS_DENY); /* Set up mappings. */ - update_uidmap(child, config.uidmap, config.uidmap_len); - update_gidmap(child, config.gidmap, config.gidmap_len); + update_uidmap(child, config.is_rootless, config.uidmap, config.uidmap_len); + update_gidmap(child, config.is_rootless, config.gidmap, config.gidmap_len); s = SYNC_USERMAP_ACK; if (write(syncfd, &s, sizeof(s)) != sizeof(s)) {