1package validate 2 3import ( 4 "fmt" 5 "os" 6 "path/filepath" 7 "strings" 8 9 "github.com/opencontainers/runc/libcontainer/configs" 10) 11 12type Validator interface { 13 Validate(*configs.Config) error 14} 15 16func New() Validator { 17 return &ConfigValidator{} 18} 19 20type ConfigValidator struct { 21} 22 23func (v *ConfigValidator) Validate(config *configs.Config) error { 24 if err := v.rootfs(config); err != nil { 25 return err 26 } 27 if err := v.network(config); err != nil { 28 return err 29 } 30 if err := v.hostname(config); err != nil { 31 return err 32 } 33 if err := v.security(config); err != nil { 34 return err 35 } 36 if err := v.usernamespace(config); err != nil { 37 return err 38 } 39 if err := v.sysctl(config); err != nil { 40 return err 41 } 42 return nil 43} 44 45// rootfs validates if the rootfs is an absolute path and is not a symlink 46// to the container's root filesystem. 47func (v *ConfigValidator) rootfs(config *configs.Config) error { 48 cleaned, err := filepath.Abs(config.Rootfs) 49 if err != nil { 50 return err 51 } 52 if cleaned, err = filepath.EvalSymlinks(cleaned); err != nil { 53 return err 54 } 55 if filepath.Clean(config.Rootfs) != cleaned { 56 return fmt.Errorf("%s is not an absolute path or is a symlink", config.Rootfs) 57 } 58 return nil 59} 60 61func (v *ConfigValidator) network(config *configs.Config) error { 62 if !config.Namespaces.Contains(configs.NEWNET) { 63 if len(config.Networks) > 0 || len(config.Routes) > 0 { 64 return fmt.Errorf("unable to apply network settings without a private NET namespace") 65 } 66 } 67 return nil 68} 69 70func (v *ConfigValidator) hostname(config *configs.Config) error { 71 if config.Hostname != "" && !config.Namespaces.Contains(configs.NEWUTS) { 72 return fmt.Errorf("unable to set hostname without a private UTS namespace") 73 } 74 return nil 75} 76 77func (v *ConfigValidator) security(config *configs.Config) error { 78 // restrict sys without mount namespace 79 if (len(config.MaskPaths) > 0 || len(config.ReadonlyPaths) > 0) && 80 !config.Namespaces.Contains(configs.NEWNS) { 81 return fmt.Errorf("unable to restrict sys entries without a private MNT namespace") 82 } 83 return nil 84} 85 86func (v *ConfigValidator) usernamespace(config *configs.Config) error { 87 if config.Namespaces.Contains(configs.NEWUSER) { 88 if _, err := os.Stat("/proc/self/ns/user"); os.IsNotExist(err) { 89 return fmt.Errorf("USER namespaces aren't enabled in the kernel") 90 } 91 } else { 92 if config.UidMappings != nil || config.GidMappings != nil { 93 return fmt.Errorf("User namespace mappings specified, but USER namespace isn't enabled in the config") 94 } 95 } 96 return nil 97} 98 99// sysctl validates that the specified sysctl keys are valid or not. 100// /proc/sys isn't completely namespaced and depending on which namespaces 101// are specified, a subset of sysctls are permitted. 102func (v *ConfigValidator) sysctl(config *configs.Config) error { 103 validSysctlMap := map[string]bool{ 104 "kernel.msgmax": true, 105 "kernel.msgmnb": true, 106 "kernel.msgmni": true, 107 "kernel.sem": true, 108 "kernel.shmall": true, 109 "kernel.shmmax": true, 110 "kernel.shmmni": true, 111 "kernel.shm_rmid_forced": true, 112 } 113 114 for s := range config.Sysctl { 115 if validSysctlMap[s] || strings.HasPrefix(s, "fs.mqueue.") { 116 if config.Namespaces.Contains(configs.NEWIPC) { 117 continue 118 } else { 119 return fmt.Errorf("sysctl %q is not allowed in the hosts ipc namespace", s) 120 } 121 } 122 if strings.HasPrefix(s, "net.") { 123 if config.Namespaces.Contains(configs.NEWNET) { 124 continue 125 } else { 126 return fmt.Errorf("sysctl %q is not allowed in the hosts network namespace", s) 127 } 128 } 129 return fmt.Errorf("sysctl %q is not in a separate kernel namespace", s) 130 } 131 132 return nil 133} 134