1package validate
2
3import (
4	"errors"
5	"fmt"
6	"strings"
7
8	"github.com/opencontainers/runc/libcontainer/configs"
9)
10
11// rootlessEUID makes sure that the config can be applied when runc
12// is being executed as a non-root user (euid != 0) in the current user namespace.
13func (v *ConfigValidator) rootlessEUID(config *configs.Config) error {
14	if !config.RootlessEUID {
15		return nil
16	}
17	if err := rootlessEUIDMappings(config); err != nil {
18		return err
19	}
20	if err := rootlessEUIDMount(config); err != nil {
21		return err
22	}
23
24	// XXX: We currently can't verify the user config at all, because
25	//      configs.Config doesn't store the user-related configs. So this
26	//      has to be verified by setupUser() in init_linux.go.
27
28	return nil
29}
30
31func hasIDMapping(id int, mappings []configs.IDMap) bool {
32	for _, m := range mappings {
33		if id >= m.ContainerID && id < m.ContainerID+m.Size {
34			return true
35		}
36	}
37	return false
38}
39
40func rootlessEUIDMappings(config *configs.Config) error {
41	if !config.Namespaces.Contains(configs.NEWUSER) {
42		return errors.New("rootless container requires user namespaces")
43	}
44
45	if len(config.UidMappings) == 0 {
46		return errors.New("rootless containers requires at least one UID mapping")
47	}
48	if len(config.GidMappings) == 0 {
49		return errors.New("rootless containers requires at least one GID mapping")
50	}
51	return nil
52}
53
54// mount verifies that the user isn't trying to set up any mounts they don't have
55// the rights to do. In addition, it makes sure that no mount has a `uid=` or
56// `gid=` option that doesn't resolve to root.
57func rootlessEUIDMount(config *configs.Config) error {
58	// XXX: We could whitelist allowed devices at this point, but I'm not
59	//      convinced that's a good idea. The kernel is the best arbiter of
60	//      access control.
61
62	for _, mount := range config.Mounts {
63		// Check that the options list doesn't contain any uid= or gid= entries
64		// that don't resolve to root.
65		for _, opt := range strings.Split(mount.Data, ",") {
66			if strings.HasPrefix(opt, "uid=") {
67				var uid int
68				n, err := fmt.Sscanf(opt, "uid=%d", &uid)
69				if n != 1 || err != nil {
70					// Ignore unknown mount options.
71					continue
72				}
73				if !hasIDMapping(uid, config.UidMappings) {
74					return errors.New("cannot specify uid= mount options for unmapped uid in rootless containers")
75				}
76			}
77
78			if strings.HasPrefix(opt, "gid=") {
79				var gid int
80				n, err := fmt.Sscanf(opt, "gid=%d", &gid)
81				if n != 1 || err != nil {
82					// Ignore unknown mount options.
83					continue
84				}
85				if !hasIDMapping(gid, config.GidMappings) {
86					return errors.New("cannot specify gid= mount options for unmapped gid in rootless containers")
87				}
88			}
89		}
90	}
91
92	return nil
93}
94