You've already forked runc
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:
committed by
Aleksa Sarai
parent
fdf85e35b3
commit
d8b669400a
@@ -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")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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)) {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user