Skip to content

Commit

Permalink
rootless: allow multiple user/group mappings
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
giuseppe committed Aug 28, 2017
1 parent 9e84f46 commit d677ad0
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 33 deletions.
58 changes: 31 additions & 27 deletions libcontainer/configs/validate/rootless.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"os"
"reflect"
"strconv"
"strings"

"github.com/opencontainers/runc/libcontainer/configs"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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")
}
}
}
}
Expand Down
76 changes: 70 additions & 6 deletions libcontainer/nsenter/nsexec.c
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

#define _GNU_SOURCE
#include <endian.h>
#include <errno.h>
Expand All @@ -19,6 +20,8 @@
#include <sys/prctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>


#include <linux/limits.h>
#include <linux/netlink.h>
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)) {
Expand Down

0 comments on commit d677ad0

Please sign in to comment.