package validate import ( "fmt" "os" "reflect" "strings" "github.com/opencontainers/runc/libcontainer/configs" ) var ( geteuid = os.Geteuid getegid = os.Getegid ) func (v *ConfigValidator) rootless(config *configs.Config) error { if err := rootlessMappings(config); err != nil { return err } if err := rootlessMount(config); err != nil { return err } // Currently, cgroups cannot effectively be used in rootless containers. // The new cgroup namespace doesn't really help us either because it doesn't // have nice interactions with the user namespace (we're working with upstream // to fix this). if err := rootlessCgroup(config); err != nil { return err } // XXX: We currently can't verify the user config at all, because // configs.Config doesn't store the user-related configs. So this // has to be verified by setupUser() in init_linux.go. 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) } 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.GidMappings) != 1 || config.GidMappings[0].Size != 1 { return fmt.Errorf("rootless containers cannot map more than one group") } return nil } // cgroup verifies that the user isn't trying to set any cgroup limits or paths. func rootlessCgroup(config *configs.Config) error { // Nothing set at all. if config.Cgroups == nil || config.Cgroups.Resources == nil { return nil } // Used for comparing to the zero value. left := reflect.ValueOf(*config.Cgroups.Resources) right := reflect.Zero(left.Type()) // This is all we need to do, since specconv won't add cgroup options in // rootless mode. if !reflect.DeepEqual(left.Interface(), right.Interface()) { return fmt.Errorf("cannot specify resource limits in rootless container") } return nil } // mount verifies that the user isn't trying to set up any mounts they don't have // the rights to do. In addition, it makes sure that no mount has a `uid=` or // `gid=` option that doesn't resolve to root. func rootlessMount(config *configs.Config) error { // XXX: We could whitelist allowed devices at this point, but I'm not // convinced that's a good idea. The kernel is the best arbiter of // access control. for _, mount := range config.Mounts { // 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, "gid=") && opt != "gid=0" { return fmt.Errorf("cannot specify gid= mount options in rootless containers where argument isn't 0") } } } return nil }