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 instancefs
4
5import (
6 "errors"
7 "fmt"
8 "os"
9 "path/filepath"
10
11 "golang.org/x/sys/unix"
12)
13
14// These are the directory operations the layout pass performs on the raw
15// directories behind the instance bind mounts (isolated-exec design §3.2).
16// A flat tenant shares the agent's uid during the transition and can plant
17// a symlink anywhere the agent can write, so every one of them works from a
18// directory fd that openDir opened without following any symlink, and names
19// exactly one entry beneath it. The *at calls do not follow a symlink at
20// that last component either: mkdirat answers EEXIST, renameat and unlinkat
21// act on the link itself, and fstatat is told not to.
22
23// EnsureDirBeneath makes sure name, directly under dir, is a real
24// directory and reports what is there. It creates it with mkdirat when it
25// is absent, so its group and setgid come from dir the way the kernel
26// hands them out, and leaves an existing directory exactly as it is. It
27// never changes a mode or an owner: a chmod by a caller outside the
28// directory's group silently clears setgid, and a chown into a group the
29// caller is not in fails, so the caller checks the returned stat and
30// refuses what it cannot vouch for. Anything else at name, a symlink or a
31// file, is refused and left untouched.
32func EnsureDirBeneath(dir, name string, perm os.FileMode) (DirStat, error) {
33 if err := bareName(name); err != nil {
34 return DirStat{}, err
35 }
36 dfd, err := openDir(dir)
37 if err != nil {
38 return DirStat{}, err
39 }
40 defer unix.Close(dfd)
41 path := filepath.Join(dir, name)
42 if err := unix.Mkdirat(dfd, name, uint32(perm.Perm())); err != nil && !errors.Is(err, unix.EEXIST) {
43 return DirStat{}, &os.PathError{Op: "mkdirat", Path: path, Err: err}
44 }
45 var st unix.Stat_t
46 if err := unix.Fstatat(dfd, name, &st, unix.AT_SYMLINK_NOFOLLOW); err != nil {
47 return DirStat{}, &os.PathError{Op: "fstatat", Path: path, Err: err}
48 }
49 switch st.Mode & unix.S_IFMT {
50 case unix.S_IFDIR:
51 return dirStatOf(&st), nil
52 case unix.S_IFLNK:
53 return DirStat{}, fmt.Errorf("%w: %s", ErrSymlink, path)
54 }
55 return DirStat{}, fmt.Errorf("instancefs: %s is not a directory", path)
56}
57
58// StatDir reports dir itself, opened without following a symlink at any
59// component.
60func StatDir(dir string) (DirStat, error) {
61 fd, err := openDir(dir)
62 if err != nil {
63 return DirStat{}, err
64 }
65 defer unix.Close(fd)
66 var st unix.Stat_t
67 if err := unix.Fstat(fd, &st); err != nil {
68 return DirStat{}, &os.PathError{Op: "fstat", Path: dir, Err: err}
69 }
70 return dirStatOf(&st), nil
71}
72
73func dirStatOf(st *unix.Stat_t) DirStat {
74 mode := os.ModeDir | os.FileMode(st.Mode&0o777)
75 if st.Mode&unix.S_ISUID != 0 {
76 mode |= os.ModeSetuid
77 }
78 if st.Mode&unix.S_ISGID != 0 {
79 mode |= os.ModeSetgid
80 }
81 if st.Mode&unix.S_ISVTX != 0 {
82 mode |= os.ModeSticky
83 }
84 return DirStat{Mode: mode, UID: int(st.Uid), GID: int(st.Gid)}
85}
86
87// RenameBetween renames srcName in srcDir to dstName in dstDir. Both
88// directories are opened without following a symlink at any component, so
89// neither side of the rename can be redirected; the entry itself moves as
90// whatever it is, a link included. An existing entry at the destination is
91// replaced, as rename(2) always does: callers that must not replace
92// something check for it first.
93func RenameBetween(srcDir, srcName, dstDir, dstName string) error {
94 if err := bareName(srcName); err != nil {
95 return err
96 }
97 if err := bareName(dstName); err != nil {
98 return err
99 }
100 sfd, err := openDir(srcDir)
101 if err != nil {
102 return err
103 }
104 defer unix.Close(sfd)
105 dfd, err := openDir(dstDir)
106 if err != nil {
107 return err
108 }
109 defer unix.Close(dfd)
110 if err := unix.Renameat(sfd, srcName, dfd, dstName); err != nil {
111 return &os.LinkError{Op: "renameat", Old: filepath.Join(srcDir, srcName), New: filepath.Join(dstDir, dstName), Err: err}
112 }
113 return nil
114}
115
116// RemoveBeneath unlinks name, a non-directory entry directly under dir. A
117// symlink there is removed, never its target.
118func RemoveBeneath(dir, name string) error {
119 if err := bareName(name); err != nil {
120 return err
121 }
122 dfd, err := openDir(dir)
123 if err != nil {
124 return err
125 }
126 defer unix.Close(dfd)
127 if err := unix.Unlinkat(dfd, name, 0); err != nil {
128 return &os.PathError{Op: "unlinkat", Path: filepath.Join(dir, name), Err: err}
129 }
130 return nil
131}
132
133// RenameBetweenNoReplace is RenameBetween that refuses to replace anything
134// at the destination (RENAME_NOREPLACE): EEXIST comes back instead. It is
135// for a caller that must not lose what is already at a name, however the
136// name came to be taken.
137func RenameBetweenNoReplace(srcDir, srcName, dstDir, dstName string) error {
138 if err := bareName(srcName); err != nil {
139 return err
140 }
141 if err := bareName(dstName); err != nil {
142 return err
143 }
144 sfd, err := openDir(srcDir)
145 if err != nil {
146 return err
147 }
148 defer unix.Close(sfd)
149 dfd, err := openDir(dstDir)
150 if err != nil {
151 return err
152 }
153 defer unix.Close(dfd)
154 if err := unix.Renameat2(sfd, srcName, dfd, dstName, unix.RENAME_NOREPLACE); err != nil {
155 return &os.LinkError{Op: "renameat2", Old: filepath.Join(srcDir, srcName), New: filepath.Join(dstDir, dstName), Err: err}
156 }
157 return nil
158}
159
160// RemoveAllBeneath removes name, directly under dir, and everything below
161// it. No symlink is followed at any depth: dir itself is opened without
162// following one (ErrSymlink), every entry is examined with fstatat
163// AT_SYMLINK_NOFOLLOW, and a symlink is unlinked, never its target. A
164// tenant sharing the agent's uid can plant links and directories in a run
165// directory (run spec §5.2); this is the only way the agent deletes one.
166// A missing name is not an error.
167func RemoveAllBeneath(dir, name string) error {
168 if err := bareName(name); err != nil {
169 return err
170 }
171 dfd, err := openDir(dir)
172 if err != nil {
173 return err
174 }
175 defer unix.Close(dfd)
176 return removeAt(dfd, name, filepath.Join(dir, name), 0)
177}
178
179// removeAt removes name under the directory fd parent; path is for errors.
180func removeAt(parent int, name, path string, depth int) error {
181 if depth > 64 {
182 return fmt.Errorf("instancefs: %s is nested more than 64 deep", path)
183 }
184 var st unix.Stat_t
185 if err := unix.Fstatat(parent, name, &st, unix.AT_SYMLINK_NOFOLLOW); err != nil {
186 if errors.Is(err, unix.ENOENT) {
187 return nil
188 }
189 return &os.PathError{Op: "fstatat", Path: path, Err: err}
190 }
191 if st.Mode&unix.S_IFMT != unix.S_IFDIR {
192 if err := unix.Unlinkat(parent, name, 0); err != nil && !errors.Is(err, unix.ENOENT) {
193 return &os.PathError{Op: "unlinkat", Path: path, Err: err}
194 }
195 return nil
196 }
197 fd, err := unix.Openat(parent, name, unix.O_RDONLY|unix.O_DIRECTORY|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
198 if err != nil {
199 return &os.PathError{Op: "openat", Path: path, Err: err}
200 }
201 f := os.NewFile(uintptr(fd), path)
202 names, err := f.Readdirnames(-1)
203 if err != nil {
204 f.Close()
205 return err
206 }
207 for _, n := range names {
208 if err := removeAt(fd, n, filepath.Join(path, n), depth+1); err != nil {
209 f.Close()
210 return err
211 }
212 }
213 f.Close()
214 if err := unix.Unlinkat(parent, name, unix.AT_REMOVEDIR); err != nil && !errors.Is(err, unix.ENOENT) {
215 return &os.PathError{Op: "unlinkat", Path: path, Err: err}
216 }
217 return nil
218}
219