2016-04-23 21:39:42 +08:00
|
|
|
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 {
|
2017-03-18 01:32:16 +08:00
|
|
|
rootuid, err := config.HostRootUID()
|
2016-04-23 21:39:42 +08:00
|
|
|
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")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-18 01:32:16 +08:00
|
|
|
rootgid, err := config.HostRootGID()
|
2016-04-23 21:39:42 +08:00
|
|
|
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
|
|
|
|
}
|