From d8b669400adc5807db3aa23d8da8df6a8f90fb70 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 [ rebased to handle intelrdt changes. ] Signed-off-by: Aleksa Sarai --- libcontainer/configs/validate/rootless.go | 61 +++++++------- libcontainer/container_linux.go | 10 +++ libcontainer/factory_linux.go | 30 +++++++ libcontainer/message_linux.go | 2 + libcontainer/nsenter/nsexec.c | 96 ++++++++++++++++++++--- utils_linux.go | 13 ++- 6 files changed, 171 insertions(+), 41 deletions(-) diff --git a/libcontainer/configs/validate/rootless.go b/libcontainer/configs/validate/rootless.go index 0cebfaf80..92bd167cd 100644 --- a/libcontainer/configs/validate/rootless.go +++ b/libcontainer/configs/validate/rootless.go @@ -36,37 +36,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) + if len(config.UidMappings) == 0 { + return fmt.Errorf("rootless containers requires at least one UID mapping") } - - // 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.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 +94,28 @@ 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=") { + var uid int + n, err := fmt.Sscanf(opt, "uid=%d", &uid) + if n != 1 || err != nil { + // Ignore unknown mount options. + continue + } + if !hasIDMapping(uid, config.UidMappings) { + return fmt.Errorf("cannot specify uid= mount options for unmapped uid in rootless containers") + } } - 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=") { + var gid int + n, err := fmt.Sscanf(opt, "gid=%d", &gid) + if n != 1 || err != nil { + // Ignore unknown mount options. + continue + } + if !hasIDMapping(gid, config.GidMappings) { + return fmt.Errorf("cannot specify gid= mount options for unmapped gid in rootless containers") + } } } } diff --git a/libcontainer/container_linux.go b/libcontainer/container_linux.go index 836dd2973..3623f7da3 100644 --- a/libcontainer/container_linux.go +++ b/libcontainer/container_linux.go @@ -44,6 +44,8 @@ type linuxContainer struct { initProcess parentProcess initProcessStartTime uint64 criuPath string + newuidmapPath string + newgidmapPath string m sync.Mutex criuVersion int state containerState @@ -1707,6 +1709,10 @@ func (c *linuxContainer) bootstrapData(cloneFlags uintptr, nsMaps map[configs.Na if !joinExistingUser { // write uid mappings if len(c.config.UidMappings) > 0 { + r.AddData(&Bytemsg{ + Type: UidmapPathAttr, + Value: []byte(c.newuidmapPath), + }) b, err := encodeIDMapping(c.config.UidMappings) if err != nil { return nil, err @@ -1729,6 +1735,10 @@ func (c *linuxContainer) bootstrapData(cloneFlags uintptr, nsMaps map[configs.Na }) // The following only applies if we are root. if !c.config.Rootless { + r.AddData(&Bytemsg{ + Type: GidmapPathAttr, + Value: []byte(c.newgidmapPath), + }) // check if we have CAP_SETGID to setgroup properly pid, err := capability.NewPid(os.Getpid()) if err != nil { diff --git a/libcontainer/factory_linux.go b/libcontainer/factory_linux.go index 3395f8c26..89deb96ef 100644 --- a/libcontainer/factory_linux.go +++ b/libcontainer/factory_linux.go @@ -140,6 +140,9 @@ func New(root string, options ...func(*LinuxFactory) error) (Factory, error) { } Cgroupfs(l) for _, opt := range options { + if opt == nil { + continue + } if err := opt(l); err != nil { return nil, err } @@ -160,6 +163,11 @@ type LinuxFactory struct { // containers. CriuPath string + // New{u,g}uidmapPath is the path to the binaries used for mapping with + // rootless containers. + NewuidmapPath string + NewgidmapPath string + // Validator provides validation to container configurations. Validator validate.Validator @@ -201,6 +209,8 @@ func (l *LinuxFactory) Create(id string, config *configs.Config) (Container, err config: config, initArgs: l.InitArgs, criuPath: l.CriuPath, + newuidmapPath: l.NewuidmapPath, + newgidmapPath: l.NewgidmapPath, cgroupManager: l.NewCgroupsManager(config.Cgroups, nil), } c.intelRdtManager = nil @@ -236,6 +246,8 @@ func (l *LinuxFactory) Load(id string) (Container, error) { config: &state.Config, initArgs: l.InitArgs, criuPath: l.CriuPath, + newuidmapPath: l.NewuidmapPath, + newgidmapPath: l.NewgidmapPath, cgroupManager: l.NewCgroupsManager(state.Config.Cgroups, state.CgroupPaths), root: containerRoot, created: state.Created, @@ -349,3 +361,21 @@ func (l *LinuxFactory) validateID(id string) error { return nil } + +// NewuidmapPath returns an option func to configure a LinuxFactory with the +// provided .. +func NewuidmapPath(newuidmapPath string) func(*LinuxFactory) error { + return func(l *LinuxFactory) error { + l.NewuidmapPath = newuidmapPath + return nil + } +} + +// NewgidmapPath returns an option func to configure a LinuxFactory with the +// provided .. +func NewgidmapPath(newgidmapPath string) func(*LinuxFactory) error { + return func(l *LinuxFactory) error { + l.NewgidmapPath = newgidmapPath + return nil + } +} diff --git a/libcontainer/message_linux.go b/libcontainer/message_linux.go index 8829b71ad..ab453cde9 100644 --- a/libcontainer/message_linux.go +++ b/libcontainer/message_linux.go @@ -18,6 +18,8 @@ const ( SetgroupAttr uint16 = 27285 OomScoreAdjAttr uint16 = 27286 RootlessAttr uint16 = 27287 + UidmapPathAttr uint16 = 27288 + GidmapPathAttr uint16 = 27289 ) type Int32msg struct { diff --git a/libcontainer/nsenter/nsexec.c b/libcontainer/nsenter/nsexec.c index 6814a5abb..e17162692 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 @@ -69,6 +72,10 @@ struct nlconfig_t { size_t uidmap_len; char *gidmap; size_t gidmap_len; + char *uidmappath; + size_t uidmappath_len; + char *gidmappath; + size_t gidmappath_len; char *namespaces; size_t namespaces_len; uint8_t is_setgroup; @@ -89,6 +96,8 @@ struct nlconfig_t { #define SETGROUP_ATTR 27285 #define OOM_SCORE_ADJ_ATTR 27286 #define ROOTLESS_ATTR 27287 +#define UIDMAPPATH_ATTR 27288 +#define GIDMAPPATH_ATTR 27289 /* * Use the raw syscall for versions of glibc which don't include a function for @@ -191,22 +200,81 @@ 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) { - if (map == NULL || map_len <= 0) - return; + int child = fork(); + if (child < 0) + bail("failed to fork"); - if (write_file(map, map_len, "/proc/%d/uid_map", pid) < 0) - bail("failed to update /proc/%d/uid_map", pid); + if (child == 0) { +#define MAX_ARGV 20 + char *argv[MAX_ARGV]; + char pid_fmt[16]; + int argc = 0; + char *next; + + snprintf (pid_fmt, 16, "%d", pid); + + argv[argc++] = (char *) app; + argv[argc++] = pid_fmt; + /* + * Convert the map string into a list of argument that + * newuidmap/newgidmap can understand. + */ + + while (argc < MAX_ARGV) { + if (*map == '\0') { + argv[argc++] = NULL; + break; + } + argv[argc++] = map; + next = strpbrk (map, "\n "); + if (next == NULL) + break; + *next++ = '\0'; + map = next + strspn(next, "\n "); + } + 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) || WIFSIGNALED(status)) + return WEXITSTATUS(status); + } + } + return -1; } -static void update_gidmap(int pid, char *map, size_t map_len) +static void update_uidmap(const char *path, int pid, 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) - bail("failed to update /proc/%d/gid_map", pid); + if (write_file(map, map_len, "/proc/%d/uid_map", pid) < 0) { + if(errno != EPERM) + bail("failed to update /proc/%d/gid_map", pid); + if (try_mapping_tool (path, pid, map, map_len)) + bail("failed to use newuid map on %d", pid); + } +} + +static void update_gidmap(const char *path, int pid, 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(errno != EPERM) + bail("failed to update /proc/%d/gid_map", pid); + if (try_mapping_tool (path, pid, map, map_len)) + bail("failed to use newgid map on %d", pid); + } } static void update_oom_score_adj(char *data, size_t len) @@ -350,6 +418,14 @@ static void nl_parse(int fd, struct nlconfig_t *config) config->gidmap = current; config->gidmap_len = payload_len; break; + case UIDMAPPATH_ATTR: + config->uidmappath = current; + config->uidmappath_len = payload_len; + break; + case GIDMAPPATH_ATTR: + config->gidmappath = current; + config->gidmappath_len = payload_len; + break; case SETGROUP_ATTR: config->is_setgroup = readint8(current); break; @@ -596,8 +672,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(config.uidmappath, child, config.uidmap, config.uidmap_len); + update_gidmap(config.gidmappath, child, config.gidmap, config.gidmap_len); s = SYNC_USERMAP_ACK; if (write(syncfd, &s, sizeof(s)) != sizeof(s)) { diff --git a/utils_linux.go b/utils_linux.go index 791e52d31..f1bb59448 100644 --- a/utils_linux.go +++ b/utils_linux.go @@ -43,11 +43,16 @@ func loadFactory(context *cli.Context) (libcontainer.Factory, error) { return nil, fmt.Errorf("systemd cgroup flag passed, but systemd support for managing cgroups is not available") } } - if intelrdt.IsEnabled() { - intelRdtManager := libcontainer.IntelRdtFs - return libcontainer.New(abs, cgroupManager, intelRdtManager, libcontainer.CriuPath(context.GlobalString("criu"))) + + intelRdtManager := libcontainer.IntelRdtFs + if !intelrdt.IsEnabled() { + intelRdtManager = nil } - return libcontainer.New(abs, cgroupManager, libcontainer.CriuPath(context.GlobalString("criu"))) + + return libcontainer.New(abs, cgroupManager, intelRdtManager, + libcontainer.CriuPath(context.GlobalString("criu")), + libcontainer.NewuidmapPath("newuidmap"), + libcontainer.NewgidmapPath("newgidmap")) } // getContainer returns the specified container instance by loading it from state