1
0
mirror of https://github.com/opencontainers/runc.git synced 2025-11-09 13:00:56 +03:00

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 <gscrivan@redhat.com>
[ rebased to handle intelrdt changes. ]
Signed-off-by: Aleksa Sarai <asarai@suse.de>
This commit is contained in:
Giuseppe Scrivano
2017-07-20 19:33:01 +02:00
committed by Aleksa Sarai
parent fdf85e35b3
commit d8b669400a
6 changed files with 171 additions and 41 deletions

View File

@@ -36,37 +36,27 @@ func (v *ConfigValidator) rootless(config *configs.Config) error {
return nil return nil
} }
func rootlessMappings(config *configs.Config) error { func hasIDMapping(id int, mappings []configs.IDMap) bool {
rootuid, err := config.HostRootUID() for _, m := range mappings {
if err != nil { if id >= m.ContainerID && id < m.ContainerID+m.Size {
return fmt.Errorf("failed to get root uid from uidMappings: %v", err) return true
} }
}
return false
}
func rootlessMappings(config *configs.Config) error {
if euid := geteuid(); euid != 0 { if euid := geteuid(); euid != 0 {
if !config.Namespaces.Contains(configs.NEWUSER) { if !config.Namespaces.Contains(configs.NEWUSER) {
return fmt.Errorf("rootless containers require user namespaces") 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 len(config.UidMappings) == 0 {
if err != nil { return fmt.Errorf("rootless containers requires at least one UID mapping")
return fmt.Errorf("failed to get root gid from gidMappings: %v", err)
} }
if len(config.GidMappings) == 0 {
// Similar to the above test, we need to make sure that we aren't trying to return fmt.Errorf("rootless containers requires at least one UID mapping")
// 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")
} }
return nil 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 // Check that the options list doesn't contain any uid= or gid= entries
// that don't resolve to root. // that don't resolve to root.
for _, opt := range strings.Split(mount.Data, ",") { for _, opt := range strings.Split(mount.Data, ",") {
if strings.HasPrefix(opt, "uid=") && opt != "uid=0" { if strings.HasPrefix(opt, "uid=") {
return fmt.Errorf("cannot specify uid= mount options in rootless containers where argument isn't 0") 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=") {
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")
} }
if strings.HasPrefix(opt, "gid=") && opt != "gid=0" {
return fmt.Errorf("cannot specify gid= mount options in rootless containers where argument isn't 0")
} }
} }
} }

View File

@@ -44,6 +44,8 @@ type linuxContainer struct {
initProcess parentProcess initProcess parentProcess
initProcessStartTime uint64 initProcessStartTime uint64
criuPath string criuPath string
newuidmapPath string
newgidmapPath string
m sync.Mutex m sync.Mutex
criuVersion int criuVersion int
state containerState state containerState
@@ -1707,6 +1709,10 @@ func (c *linuxContainer) bootstrapData(cloneFlags uintptr, nsMaps map[configs.Na
if !joinExistingUser { if !joinExistingUser {
// write uid mappings // write uid mappings
if len(c.config.UidMappings) > 0 { if len(c.config.UidMappings) > 0 {
r.AddData(&Bytemsg{
Type: UidmapPathAttr,
Value: []byte(c.newuidmapPath),
})
b, err := encodeIDMapping(c.config.UidMappings) b, err := encodeIDMapping(c.config.UidMappings)
if err != nil { if err != nil {
return nil, err 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. // The following only applies if we are root.
if !c.config.Rootless { if !c.config.Rootless {
r.AddData(&Bytemsg{
Type: GidmapPathAttr,
Value: []byte(c.newgidmapPath),
})
// check if we have CAP_SETGID to setgroup properly // check if we have CAP_SETGID to setgroup properly
pid, err := capability.NewPid(os.Getpid()) pid, err := capability.NewPid(os.Getpid())
if err != nil { if err != nil {

View File

@@ -140,6 +140,9 @@ func New(root string, options ...func(*LinuxFactory) error) (Factory, error) {
} }
Cgroupfs(l) Cgroupfs(l)
for _, opt := range options { for _, opt := range options {
if opt == nil {
continue
}
if err := opt(l); err != nil { if err := opt(l); err != nil {
return nil, err return nil, err
} }
@@ -160,6 +163,11 @@ type LinuxFactory struct {
// containers. // containers.
CriuPath string 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 provides validation to container configurations.
Validator validate.Validator Validator validate.Validator
@@ -201,6 +209,8 @@ func (l *LinuxFactory) Create(id string, config *configs.Config) (Container, err
config: config, config: config,
initArgs: l.InitArgs, initArgs: l.InitArgs,
criuPath: l.CriuPath, criuPath: l.CriuPath,
newuidmapPath: l.NewuidmapPath,
newgidmapPath: l.NewgidmapPath,
cgroupManager: l.NewCgroupsManager(config.Cgroups, nil), cgroupManager: l.NewCgroupsManager(config.Cgroups, nil),
} }
c.intelRdtManager = nil c.intelRdtManager = nil
@@ -236,6 +246,8 @@ func (l *LinuxFactory) Load(id string) (Container, error) {
config: &state.Config, config: &state.Config,
initArgs: l.InitArgs, initArgs: l.InitArgs,
criuPath: l.CriuPath, criuPath: l.CriuPath,
newuidmapPath: l.NewuidmapPath,
newgidmapPath: l.NewgidmapPath,
cgroupManager: l.NewCgroupsManager(state.Config.Cgroups, state.CgroupPaths), cgroupManager: l.NewCgroupsManager(state.Config.Cgroups, state.CgroupPaths),
root: containerRoot, root: containerRoot,
created: state.Created, created: state.Created,
@@ -349,3 +361,21 @@ func (l *LinuxFactory) validateID(id string) error {
return nil 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
}
}

View File

@@ -18,6 +18,8 @@ const (
SetgroupAttr uint16 = 27285 SetgroupAttr uint16 = 27285
OomScoreAdjAttr uint16 = 27286 OomScoreAdjAttr uint16 = 27286
RootlessAttr uint16 = 27287 RootlessAttr uint16 = 27287
UidmapPathAttr uint16 = 27288
GidmapPathAttr uint16 = 27289
) )
type Int32msg struct { type Int32msg struct {

View File

@@ -1,3 +1,4 @@
#define _GNU_SOURCE #define _GNU_SOURCE
#include <endian.h> #include <endian.h>
#include <errno.h> #include <errno.h>
@@ -19,6 +20,8 @@
#include <sys/prctl.h> #include <sys/prctl.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/wait.h>
#include <linux/limits.h> #include <linux/limits.h>
#include <linux/netlink.h> #include <linux/netlink.h>
@@ -69,6 +72,10 @@ struct nlconfig_t {
size_t uidmap_len; size_t uidmap_len;
char *gidmap; char *gidmap;
size_t gidmap_len; size_t gidmap_len;
char *uidmappath;
size_t uidmappath_len;
char *gidmappath;
size_t gidmappath_len;
char *namespaces; char *namespaces;
size_t namespaces_len; size_t namespaces_len;
uint8_t is_setgroup; uint8_t is_setgroup;
@@ -89,6 +96,8 @@ struct nlconfig_t {
#define SETGROUP_ATTR 27285 #define SETGROUP_ATTR 27285
#define OOM_SCORE_ADJ_ATTR 27286 #define OOM_SCORE_ADJ_ATTR 27286
#define ROOTLESS_ATTR 27287 #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 * 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) int child = fork();
return; if (child < 0)
bail("failed to fork");
if (write_file(map, map_len, "/proc/%d/uid_map", pid) < 0) if (child == 0) {
bail("failed to update /proc/%d/uid_map", pid); #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) if (map == NULL || map_len <= 0)
return; return;
if (write_file(map, map_len, "/proc/%d/gid_map", pid) < 0) if (write_file(map, map_len, "/proc/%d/uid_map", pid) < 0) {
if(errno != EPERM)
bail("failed to update /proc/%d/gid_map", pid); 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) 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 = current;
config->gidmap_len = payload_len; config->gidmap_len = payload_len;
break; 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: case SETGROUP_ATTR:
config->is_setgroup = readint8(current); config->is_setgroup = readint8(current);
break; break;
@@ -596,8 +672,8 @@ void nsexec(void)
update_setgroups(child, SETGROUPS_DENY); update_setgroups(child, SETGROUPS_DENY);
/* Set up mappings. */ /* Set up mappings. */
update_uidmap(child, config.uidmap, config.uidmap_len); update_uidmap(config.uidmappath, child, config.uidmap, config.uidmap_len);
update_gidmap(child, config.gidmap, config.gidmap_len); update_gidmap(config.gidmappath, child, config.gidmap, config.gidmap_len);
s = SYNC_USERMAP_ACK; s = SYNC_USERMAP_ACK;
if (write(syncfd, &s, sizeof(s)) != sizeof(s)) { if (write(syncfd, &s, sizeof(s)) != sizeof(s)) {

View File

@@ -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") return nil, fmt.Errorf("systemd cgroup flag passed, but systemd support for managing cgroups is not available")
} }
} }
if intelrdt.IsEnabled() {
intelRdtManager := libcontainer.IntelRdtFs intelRdtManager := libcontainer.IntelRdtFs
return libcontainer.New(abs, cgroupManager, intelRdtManager, libcontainer.CriuPath(context.GlobalString("criu"))) 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 // getContainer returns the specified container instance by loading it from state