Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
lima-vm
GitHub Repository: lima-vm/lima
Path: blob/master/pkg/networks/validate.go
2601 views
1
// SPDX-FileCopyrightText: Copyright The Lima Authors
2
// SPDX-License-Identifier: Apache-2.0
3
4
package networks
5
6
import (
7
"errors"
8
"fmt"
9
"io/fs"
10
"os"
11
"path/filepath"
12
"reflect"
13
"runtime"
14
"strings"
15
16
"github.com/lima-vm/lima/v2/pkg/osutil"
17
)
18
19
func (c *Config) Validate() error {
20
// validate all paths.* values
21
paths := reflect.ValueOf(&c.Paths).Elem()
22
pathsMap := make(map[string]string, paths.NumField())
23
var socketVMNetNotFound bool
24
for i := range paths.NumField() {
25
// extract YAML name from struct tag; strip options like "omitempty"
26
name := paths.Type().Field(i).Tag.Get("yaml")
27
if i := strings.IndexRune(name, ','); i > -1 {
28
name = name[:i]
29
}
30
path := paths.Field(i).Interface().(string)
31
pathsMap[name] = path
32
// varPath will be created securely, but any existing parent directories must already be secure
33
if name == "varRun" {
34
path = findBaseDirectory(path)
35
}
36
err := validatePath(path, name == "varRun")
37
if err != nil {
38
if errors.Is(err, os.ErrNotExist) {
39
switch name {
40
// sudoers file does not need to exist; otherwise `limactl sudoers` couldn't bootstrap
41
case "sudoers":
42
continue
43
case "socketVMNet":
44
socketVMNetNotFound = true
45
continue
46
}
47
}
48
return fmt.Errorf("networks.yaml field `paths.%s` error: %w", name, err)
49
}
50
}
51
if socketVMNetNotFound {
52
return fmt.Errorf("networks.yaml: %q (`paths.socketVMNet`) has to be installed", pathsMap["socketVMNet"])
53
}
54
// TODO(jandubois): validate network definitions
55
return nil
56
}
57
58
// findBaseDirectory removes non-existing directories from the end of the path.
59
func findBaseDirectory(path string) string {
60
if _, err := os.Lstat(path); errors.Is(err, os.ErrNotExist) {
61
if path != "/" {
62
return findBaseDirectory(filepath.Dir(path))
63
}
64
}
65
return path
66
}
67
68
func validatePath(path string, allowDaemonGroupWritable bool) error {
69
if path == "" {
70
return nil
71
}
72
if path[0] != '/' {
73
return fmt.Errorf("path %q is not an absolute path", path)
74
}
75
if strings.ContainsRune(path, ' ') {
76
return fmt.Errorf("path %q contains whitespace", path)
77
}
78
fi, err := os.Lstat(path)
79
if err != nil {
80
return err
81
}
82
file := "file"
83
if fi.Mode().IsDir() {
84
file = "dir"
85
}
86
// TODO: should we allow symlinks when both the link and the target are secure?
87
// E.g. on macOS /var is a symlink to /private/var, /etc to /private/etc
88
if (fi.Mode() & fs.ModeSymlink) != 0 {
89
return fmt.Errorf("%s %q is a symlink", file, path)
90
}
91
stat, ok := osutil.SysStat(fi)
92
if !ok {
93
// should never happen
94
return fmt.Errorf("could not retrieve stat buffer for %q", path)
95
}
96
if runtime.GOOS != "darwin" {
97
return errors.New("vmnet code must not be called on non-Darwin") // TODO: move to *_darwin.go
98
}
99
// TODO: cache looked up UIDs/GIDs
100
root, err := osutil.LookupUser("root")
101
if err != nil {
102
return err
103
}
104
if stat.Uid != root.Uid {
105
return fmt.Errorf(`%s %q is not owned by %q (uid: %d), but by uid %d`, file, path, root.User, root.Uid, stat.Uid)
106
}
107
if allowDaemonGroupWritable {
108
daemon, err := osutil.LookupUser("daemon")
109
if err != nil {
110
return err
111
}
112
if fi.Mode()&0o20 != 0 && stat.Gid != root.Gid && stat.Gid != daemon.Gid {
113
return fmt.Errorf(`%s %q is group-writable and group is neither %q (gid: %d) nor %q (gid: %d), but is gid: %d`,
114
file, path, root.User, root.Gid, daemon.User, daemon.Gid, stat.Gid)
115
}
116
if fi.Mode().IsDir() && fi.Mode()&1 == 0 && (fi.Mode()&0o010 == 0 || stat.Gid != daemon.Gid) {
117
return fmt.Errorf(`%s %q is not executable by the %q (gid: %d)" group`, file, path, daemon.User, daemon.Gid)
118
}
119
} else if fi.Mode()&0o20 != 0 && stat.Gid != root.Gid {
120
return fmt.Errorf(`%s %q is group-writable and group is not %q (gid: %d), but is gid: %d`,
121
file, path, root.User, root.Gid, stat.Gid)
122
}
123
if fi.Mode()&0o02 != 0 {
124
return fmt.Errorf("%s %q is world-writable", file, path)
125
}
126
if path != "/" {
127
return validatePath(filepath.Dir(path), allowDaemonGroupWritable)
128
}
129
return nil
130
}
131
132