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