package validate import ( "fmt" "os" "path/filepath" "strings" "github.com/opencontainers/runc/libcontainer/configs" "github.com/opencontainers/runc/libcontainer/selinux" ) type Validator interface { Validate(*configs.Config) error } func New() Validator { return &ConfigValidator{} } type ConfigValidator struct { } func (v *ConfigValidator) Validate(config *configs.Config) error { if err := v.rootfs(config); err != nil { return err } if err := v.network(config); err != nil { return err } if err := v.hostname(config); err != nil { return err } if err := v.security(config); err != nil { return err } if err := v.usernamespace(config); err != nil { return err } if err := v.sysctl(config); err != nil { return err } return nil } // rootfs validates if the rootfs is an absolute path and is not a symlink // to the container's root filesystem. func (v *ConfigValidator) rootfs(config *configs.Config) error { cleaned, err := filepath.Abs(config.Rootfs) if err != nil { return err } if cleaned, err = filepath.EvalSymlinks(cleaned); err != nil { return err } if filepath.Clean(config.Rootfs) != cleaned { return fmt.Errorf("%s is not an absolute path or is a symlink", config.Rootfs) } return nil } func (v *ConfigValidator) network(config *configs.Config) error { if !config.Namespaces.Contains(configs.NEWNET) { if len(config.Networks) > 0 || len(config.Routes) > 0 { return fmt.Errorf("unable to apply network settings without a private NET namespace") } } return nil } func (v *ConfigValidator) hostname(config *configs.Config) error { if config.Hostname != "" && !config.Namespaces.Contains(configs.NEWUTS) { return fmt.Errorf("unable to set hostname without a private UTS namespace") } return nil } func (v *ConfigValidator) security(config *configs.Config) error { // restrict sys without mount namespace if (len(config.MaskPaths) > 0 || len(config.ReadonlyPaths) > 0) && !config.Namespaces.Contains(configs.NEWNS) { return fmt.Errorf("unable to restrict sys entries without a private MNT namespace") } if config.ProcessLabel != "" && !selinux.SelinuxEnabled() { return fmt.Errorf("selinux label is specified in config, but selinux is disabled or not supported") } return nil } func (v *ConfigValidator) usernamespace(config *configs.Config) error { if config.Namespaces.Contains(configs.NEWUSER) { if _, err := os.Stat("/proc/self/ns/user"); os.IsNotExist(err) { return fmt.Errorf("USER namespaces aren't enabled in the kernel") } } else { if config.UidMappings != nil || config.GidMappings != nil { return fmt.Errorf("User namespace mappings specified, but USER namespace isn't enabled in the config") } } return nil } // sysctl validates that the specified sysctl keys are valid or not. // /proc/sys isn't completely namespaced and depending on which namespaces // are specified, a subset of sysctls are permitted. func (v *ConfigValidator) sysctl(config *configs.Config) error { validSysctlMap := map[string]bool{ "kernel.msgmax": true, "kernel.msgmnb": true, "kernel.msgmni": true, "kernel.sem": true, "kernel.shmall": true, "kernel.shmmax": true, "kernel.shmmni": true, "kernel.shm_rmid_forced": true, } for s := range config.Sysctl { if validSysctlMap[s] || strings.HasPrefix(s, "fs.mqueue.") { if config.Namespaces.Contains(configs.NEWIPC) { continue } else { return fmt.Errorf("sysctl %q is not allowed in the hosts ipc namespace", s) } } if strings.HasPrefix(s, "net.") { if config.Namespaces.Contains(configs.NEWNET) { continue } else { return fmt.Errorf("sysctl %q is not allowed in the hosts network namespace", s) } } return fmt.Errorf("sysctl %q is not in a separate kernel namespace", s) } return nil }