carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1//go:build linux
2
3package host
4
5import (
6 "errors"
7 "fmt"
8 "log"
9 "os"
10 "path/filepath"
11 "strings"
12
13 "golang.org/x/sys/unix"
14)
15
16func bootstrapOpenDir(path string) (int, error) {
17 return unix.Openat2(unix.AT_FDCWD, path, &unix.OpenHow{
18 Flags: unix.O_RDONLY | unix.O_DIRECTORY | unix.O_CLOEXEC | unix.O_NOFOLLOW,
19 Resolve: unix.RESOLVE_NO_SYMLINKS,
20 })
21}
22
23// bootstrapMakeDir anchors every component before creating or changing it.
24func bootstrapMakeDir(path string, mode os.FileMode) error {
25 absolute, err := filepath.Abs(path)
26 if err != nil {
27 return err
28 }
29 fd, err := bootstrapOpenDir("/")
30 if err != nil {
31 return err
32 }
33 defer func() { unix.Close(fd) }()
34 parts := strings.Split(strings.TrimPrefix(absolute, "/"), "/")
35 for _, name := range parts {
36 if name == "" {
37 continue
38 }
39 next, err := unix.Openat2(fd, name, &unix.OpenHow{Flags: unix.O_RDONLY | unix.O_DIRECTORY | unix.O_CLOEXEC | unix.O_NOFOLLOW, Resolve: unix.RESOLVE_BENEATH | unix.RESOLVE_NO_SYMLINKS})
40 if errors.Is(err, unix.ENOENT) {
41 if err = unix.Mkdirat(fd, name, uint32(mode.Perm())); err != nil && !errors.Is(err, unix.EEXIST) {
42 return err
43 }
44 next, err = unix.Openat2(fd, name, &unix.OpenHow{Flags: unix.O_RDONLY | unix.O_DIRECTORY | unix.O_CLOEXEC | unix.O_NOFOLLOW, Resolve: unix.RESOLVE_BENEATH | unix.RESOLVE_NO_SYMLINKS})
45 }
46 if err != nil {
47 return err
48 }
49 unix.Close(fd)
50 fd = next
51 }
52 bits := uint32(mode.Perm())
53 if mode&os.ModeSetgid != 0 {
54 bits |= unix.S_ISGID
55 }
56 if mode&os.ModeSetuid != 0 {
57 bits |= unix.S_ISUID
58 }
59 if mode&os.ModeSticky != 0 {
60 bits |= unix.S_ISVTX
61 }
62 return unix.Fchmod(fd, bits)
63}
64
65func bootstrapCreateRegistry(path string) (bool, error) {
66 parent, err := bootstrapOpenDir(filepath.Dir(path))
67 if err != nil {
68 return false, err
69 }
70 defer unix.Close(parent)
71 fd, err := unix.Openat2(parent, filepath.Base(path), &unix.OpenHow{
72 Flags: unix.O_CREAT | unix.O_EXCL | unix.O_WRONLY | unix.O_NOFOLLOW | unix.O_CLOEXEC,
73 Mode: 0o640,
74 Resolve: unix.RESOLVE_BENEATH | unix.RESOLVE_NO_SYMLINKS | unix.RESOLVE_NO_XDEV,
75 })
76 if errors.Is(err, unix.EEXIST) {
77 var st unix.Stat_t
78 if err := unix.Fstatat(parent, filepath.Base(path), &st, unix.AT_SYMLINK_NOFOLLOW); err != nil {
79 return false, err
80 }
81 if st.Mode&unix.S_IFMT != unix.S_IFREG || st.Nlink != 1 {
82 log.Printf("ALERT: bootstrap: refusing unsafe registry file %s", path)
83 }
84 return false, nil
85 }
86 if err != nil {
87 return false, err
88 }
89 defer unix.Close(fd)
90 var st unix.Stat_t
91 if err := unix.Fstat(fd, &st); err != nil {
92 return false, err
93 }
94 if st.Mode&unix.S_IFMT != unix.S_IFREG || st.Nlink != 1 {
95 return false, fmt.Errorf("unsafe registry file %s", path)
96 }
97 return true, nil
98}
99
100func bootstrapChownTree(path string, uid, gid int) error {
101 fd, err := bootstrapOpenDir(path)
102 if err != nil {
103 return err
104 }
105 return bootstrapChownDir(os.NewFile(uintptr(fd), path), uid, gid)
106}
107
108func bootstrapChownDir(dir *os.File, uid, gid int) error {
109 defer dir.Close()
110 // The name '.' is resolved against the opened directory, not its mutable parent.
111 if err := unix.Fchownat(int(dir.Fd()), ".", uid, gid, unix.AT_SYMLINK_NOFOLLOW); err != nil {
112 return err
113 }
114 entries, err := dir.ReadDir(-1)
115 if err != nil {
116 return err
117 }
118 for _, entry := range entries {
119 path := filepath.Join(dir.Name(), entry.Name())
120 // NO_XDEV refuses bind mounts as well as different-device mounts.
121 fd, err := unix.Openat2(int(dir.Fd()), entry.Name(), &unix.OpenHow{
122 Flags: unix.O_PATH | unix.O_NOFOLLOW | unix.O_CLOEXEC,
123 Resolve: unix.RESOLVE_BENEATH | unix.RESOLVE_NO_SYMLINKS | unix.RESOLVE_NO_XDEV,
124 })
125 if err != nil {
126 if errors.Is(err, unix.ELOOP) || errors.Is(err, unix.EXDEV) || errors.Is(err, unix.ENOENT) || errors.Is(err, unix.ENXIO) {
127 log.Printf("ALERT: bootstrap: skipping unsafe ownership path %s: %v", path, err)
128 continue
129 }
130 return err
131 }
132 f := os.NewFile(uintptr(fd), path)
133 var st unix.Stat_t
134 if err := unix.Fstat(fd, &st); err != nil {
135 f.Close()
136 return err
137 }
138 switch st.Mode & unix.S_IFMT {
139 case unix.S_IFDIR:
140 child, err := unix.Openat2(fd, ".", &unix.OpenHow{Flags: unix.O_RDONLY | unix.O_DIRECTORY | unix.O_CLOEXEC | unix.O_NOFOLLOW, Resolve: unix.RESOLVE_NO_SYMLINKS | unix.RESOLVE_NO_XDEV})
141 f.Close()
142 if err != nil {
143 return err
144 }
145 if err := bootstrapChownDir(os.NewFile(uintptr(child), path), uid, gid); err != nil {
146 return err
147 }
148 case unix.S_IFREG:
149 if st.Nlink != 1 {
150 log.Printf("ALERT: bootstrap: skipping unsafe ownership path %s: multiple links", path)
151 f.Close()
152 continue
153 }
154 err := unix.Fchownat(fd, "", uid, gid, unix.AT_EMPTY_PATH|unix.AT_SYMLINK_NOFOLLOW)
155 f.Close()
156 if err != nil {
157 return err
158 }
159 default:
160 log.Printf("ALERT: bootstrap: skipping unsafe ownership path %s: special file", path)
161 f.Close()
162 }
163 }
164 return nil
165}
166