1 package validate
2
3 import (
4 "errors"
5 "fmt"
6 "os"
7 "path/filepath"
8 "strings"
9 "sync"
10
11 "github.com/opencontainers/runc/libcontainer/cgroups"
12 "github.com/opencontainers/runc/libcontainer/configs"
13 "github.com/opencontainers/runc/libcontainer/intelrdt"
14 selinux "github.com/opencontainers/selinux/go-selinux"
15 "github.com/sirupsen/logrus"
16 "golang.org/x/sys/unix"
17 )
18
19 type Validator interface {
20 Validate(*configs.Config) error
21 }
22
23 func New() Validator {
24 return &ConfigValidator{}
25 }
26
27 type ConfigValidator struct{}
28
29 type check func(config *configs.Config) error
30
31 func (v *ConfigValidator) Validate(config *configs.Config) error {
32 checks := []check{
33 v.cgroups,
34 v.rootfs,
35 v.network,
36 v.hostname,
37 v.security,
38 v.usernamespace,
39 v.cgroupnamespace,
40 v.sysctl,
41 v.intelrdt,
42 v.rootlessEUID,
43 }
44 for _, c := range checks {
45 if err := c(config); err != nil {
46 return err
47 }
48 }
49
50 warns := []check{
51 v.mounts,
52 }
53 for _, c := range warns {
54 if err := c(config); err != nil {
55 logrus.WithError(err).Warn("invalid configuration")
56 }
57 }
58 return nil
59 }
60
61
62
63 func (v *ConfigValidator) rootfs(config *configs.Config) error {
64 if _, err := os.Stat(config.Rootfs); err != nil {
65 return fmt.Errorf("invalid rootfs: %w", err)
66 }
67 cleaned, err := filepath.Abs(config.Rootfs)
68 if err != nil {
69 return fmt.Errorf("invalid rootfs: %w", err)
70 }
71 if cleaned, err = filepath.EvalSymlinks(cleaned); err != nil {
72 return fmt.Errorf("invalid rootfs: %w", err)
73 }
74 if filepath.Clean(config.Rootfs) != cleaned {
75 return errors.New("invalid rootfs: not an absolute path, or a symlink")
76 }
77 return nil
78 }
79
80 func (v *ConfigValidator) network(config *configs.Config) error {
81 if !config.Namespaces.Contains(configs.NEWNET) {
82 if len(config.Networks) > 0 || len(config.Routes) > 0 {
83 return errors.New("unable to apply network settings without a private NET namespace")
84 }
85 }
86 return nil
87 }
88
89 func (v *ConfigValidator) hostname(config *configs.Config) error {
90 if config.Hostname != "" && !config.Namespaces.Contains(configs.NEWUTS) {
91 return errors.New("unable to set hostname without a private UTS namespace")
92 }
93 return nil
94 }
95
96 func (v *ConfigValidator) security(config *configs.Config) error {
97
98 if (len(config.MaskPaths) > 0 || len(config.ReadonlyPaths) > 0) &&
99 !config.Namespaces.Contains(configs.NEWNS) {
100 return errors.New("unable to restrict sys entries without a private MNT namespace")
101 }
102 if config.ProcessLabel != "" && !selinux.GetEnabled() {
103 return errors.New("selinux label is specified in config, but selinux is disabled or not supported")
104 }
105
106 return nil
107 }
108
109 func (v *ConfigValidator) usernamespace(config *configs.Config) error {
110 if config.Namespaces.Contains(configs.NEWUSER) {
111 if _, err := os.Stat("/proc/self/ns/user"); os.IsNotExist(err) {
112 return errors.New("user namespaces aren't enabled in the kernel")
113 }
114 hasPath := config.Namespaces.PathOf(configs.NEWUSER) != ""
115 hasMappings := config.UidMappings != nil || config.GidMappings != nil
116 if !hasPath && !hasMappings {
117 return errors.New("user namespaces enabled, but no namespace path to join nor mappings to apply specified")
118 }
119
120
121
122 } else {
123 if config.UidMappings != nil || config.GidMappings != nil {
124 return errors.New("user namespace mappings specified, but user namespace isn't enabled in the config")
125 }
126 }
127 return nil
128 }
129
130 func (v *ConfigValidator) cgroupnamespace(config *configs.Config) error {
131 if config.Namespaces.Contains(configs.NEWCGROUP) {
132 if _, err := os.Stat("/proc/self/ns/cgroup"); os.IsNotExist(err) {
133 return errors.New("cgroup namespaces aren't enabled in the kernel")
134 }
135 }
136 return nil
137 }
138
139
140
141
142
143
144
145
146
147 func convertSysctlVariableToDotsSeparator(val string) string {
148 if val == "" {
149 return val
150 }
151 firstSepIndex := strings.IndexAny(val, "./")
152 if firstSepIndex == -1 || val[firstSepIndex] == '.' {
153 return val
154 }
155
156 f := func(r rune) rune {
157 switch r {
158 case '.':
159 return '/'
160 case '/':
161 return '.'
162 }
163 return r
164 }
165 return strings.Map(f, val)
166 }
167
168
169
170
171 func (v *ConfigValidator) sysctl(config *configs.Config) error {
172 validSysctlMap := map[string]bool{
173 "kernel.msgmax": true,
174 "kernel.msgmnb": true,
175 "kernel.msgmni": true,
176 "kernel.sem": true,
177 "kernel.shmall": true,
178 "kernel.shmmax": true,
179 "kernel.shmmni": true,
180 "kernel.shm_rmid_forced": true,
181 }
182
183 var (
184 netOnce sync.Once
185 hostnet bool
186 hostnetErr error
187 )
188
189 for s := range config.Sysctl {
190 s := convertSysctlVariableToDotsSeparator(s)
191 if validSysctlMap[s] || strings.HasPrefix(s, "fs.mqueue.") {
192 if config.Namespaces.Contains(configs.NEWIPC) {
193 continue
194 } else {
195 return fmt.Errorf("sysctl %q is not allowed in the hosts ipc namespace", s)
196 }
197 }
198 if strings.HasPrefix(s, "net.") {
199
200
201 netOnce.Do(func() {
202 if !config.Namespaces.Contains(configs.NEWNET) {
203 hostnet = true
204 return
205 }
206 path := config.Namespaces.PathOf(configs.NEWNET)
207 if path == "" {
208
209 return
210 }
211 hostnet, hostnetErr = isHostNetNS(path)
212 })
213 if hostnetErr != nil {
214 return fmt.Errorf("invalid netns path: %w", hostnetErr)
215 }
216 if hostnet {
217 return fmt.Errorf("sysctl %q not allowed in host network namespace", s)
218 }
219 continue
220 }
221 if config.Namespaces.Contains(configs.NEWUTS) {
222 switch s {
223 case "kernel.domainname":
224
225 continue
226 case "kernel.hostname":
227
228 return fmt.Errorf("sysctl %q is not allowed as it conflicts with the OCI %q field", s, "hostname")
229 }
230 }
231 return fmt.Errorf("sysctl %q is not in a separate kernel namespace", s)
232 }
233
234 return nil
235 }
236
237 func (v *ConfigValidator) intelrdt(config *configs.Config) error {
238 if config.IntelRdt != nil {
239 if config.IntelRdt.ClosID == "." || config.IntelRdt.ClosID == ".." || strings.Contains(config.IntelRdt.ClosID, "/") {
240 return fmt.Errorf("invalid intelRdt.ClosID %q", config.IntelRdt.ClosID)
241 }
242
243 if !intelrdt.IsCATEnabled() && config.IntelRdt.L3CacheSchema != "" {
244 return errors.New("intelRdt.l3CacheSchema is specified in config, but Intel RDT/CAT is not enabled")
245 }
246 if !intelrdt.IsMBAEnabled() && config.IntelRdt.MemBwSchema != "" {
247 return errors.New("intelRdt.memBwSchema is specified in config, but Intel RDT/MBA is not enabled")
248 }
249 }
250
251 return nil
252 }
253
254 func (v *ConfigValidator) cgroups(config *configs.Config) error {
255 c := config.Cgroups
256 if c == nil {
257 return nil
258 }
259
260 if (c.Name != "" || c.Parent != "") && c.Path != "" {
261 return fmt.Errorf("cgroup: either Path or Name and Parent should be used, got %+v", c)
262 }
263
264 r := c.Resources
265 if r == nil {
266 return nil
267 }
268
269 if !cgroups.IsCgroup2UnifiedMode() && r.Unified != nil {
270 return cgroups.ErrV1NoUnified
271 }
272
273 if cgroups.IsCgroup2UnifiedMode() {
274 _, err := cgroups.ConvertMemorySwapToCgroupV2Value(r.MemorySwap, r.Memory)
275 if err != nil {
276 return err
277 }
278 }
279
280 return nil
281 }
282
283 func (v *ConfigValidator) mounts(config *configs.Config) error {
284 for _, m := range config.Mounts {
285 if !filepath.IsAbs(m.Destination) {
286 return fmt.Errorf("invalid mount %+v: mount destination not absolute", m)
287 }
288 }
289
290 return nil
291 }
292
293 func isHostNetNS(path string) (bool, error) {
294 const currentProcessNetns = "/proc/self/ns/net"
295
296 var st1, st2 unix.Stat_t
297
298 if err := unix.Stat(currentProcessNetns, &st1); err != nil {
299 return false, &os.PathError{Op: "stat", Path: currentProcessNetns, Err: err}
300 }
301 if err := unix.Stat(path, &st2); err != nil {
302 return false, &os.PathError{Op: "stat", Path: path, Err: err}
303 }
304
305 return (st1.Dev == st2.Dev) && (st1.Ino == st2.Ino), nil
306 }
307
View as plain text