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 <gscrivan@redhat.com>
[ rebased to handle intelrdt changes. ]
Signed-off-by: Aleksa Sarai <asarai@suse.de>
  • Loading branch information
giuseppe authored and cyphar committed Sep 9, 2017
1 parent fdf85e3 commit d8b6694
Show file tree
Hide file tree
Showing 6 changed files with 169 additions and 39 deletions.
61 changes: 34 additions & 27 deletions libcontainer/configs/validate/rootless.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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")
}
}
}
}
Expand Down
10 changes: 10 additions & 0 deletions libcontainer/container_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ type linuxContainer struct {
initProcess parentProcess
initProcessStartTime uint64
criuPath string
newuidmapPath string
newgidmapPath string
m sync.Mutex
criuVersion int
state containerState
Expand Down Expand Up @@ -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
Expand All @@ -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 {
Expand Down
30 changes: 30 additions & 0 deletions libcontainer/factory_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
}
}
2 changes: 2 additions & 0 deletions libcontainer/message_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ const (
SetgroupAttr uint16 = 27285
OomScoreAdjAttr uint16 = 27286
RootlessAttr uint16 = 27287
UidmapPathAttr uint16 = 27288
GidmapPathAttr uint16 = 27289
)

type Int32msg struct {
Expand Down
92 changes: 84 additions & 8 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 @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -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)
{
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;
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_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/uid_map", pid) < 0)
bail("failed to update /proc/%d/uid_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(int pid, char *map, size_t map_len)
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)
bail("failed to update /proc/%d/gid_map", pid);
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)
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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)) {
Expand Down
13 changes: 9 additions & 4 deletions utils_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit d8b6694

Please sign in to comment.