podman

Форк
0
304 строки · 10.3 Кб
1
//go:build linux
2
// +build linux
3

4
package bind
5

6
import (
7
	"errors"
8
	"fmt"
9
	"os"
10
	"path/filepath"
11
	"syscall"
12

13
	"github.com/containers/buildah/util"
14
	"github.com/containers/storage/pkg/idtools"
15
	"github.com/containers/storage/pkg/mount"
16
	"github.com/opencontainers/runtime-spec/specs-go"
17
	"github.com/sirupsen/logrus"
18
	"golang.org/x/exp/slices"
19
	"golang.org/x/sys/unix"
20
)
21

22
// SetupIntermediateMountNamespace creates a new mount namespace and bind
23
// mounts all bind-mount sources into a subdirectory of bundlePath that can
24
// only be reached by the root user of the container's user namespace, except
25
// for Mounts which include the NoBindOption option in their options list.  The
26
// NoBindOption will then merely be removed.
27
func SetupIntermediateMountNamespace(spec *specs.Spec, bundlePath string) (unmountAll func() error, err error) {
28
	defer stripNoBindOption(spec)
29

30
	// We expect a root directory to be defined.
31
	if spec.Root == nil {
32
		return nil, errors.New("configuration has no root filesystem?")
33
	}
34
	rootPath := spec.Root.Path
35

36
	// Create a new mount namespace in which to do the things we're doing.
37
	if err := unix.Unshare(unix.CLONE_NEWNS); err != nil {
38
		return nil, fmt.Errorf("creating new mount namespace for %v: %w", spec.Process.Args, err)
39
	}
40

41
	// Make all of our mounts private to our namespace.
42
	if err := mount.MakeRPrivate("/"); err != nil {
43
		return nil, fmt.Errorf("making mounts private to mount namespace for %v: %w", spec.Process.Args, err)
44
	}
45

46
	// Make sure the bundle directory is searchable.  We created it with
47
	// TempDir(), so it should have started with permissions set to 0700.
48
	info, err := os.Stat(bundlePath)
49
	if err != nil {
50
		return nil, fmt.Errorf("checking permissions on %q: %w", bundlePath, err)
51
	}
52
	if err = os.Chmod(bundlePath, info.Mode()|0111); err != nil {
53
		return nil, fmt.Errorf("loosening permissions on %q: %w", bundlePath, err)
54
	}
55

56
	// Figure out who needs to be able to reach these bind mounts in order
57
	// for the container to be started.
58
	rootUID, rootGID, err := util.GetHostRootIDs(spec)
59
	if err != nil {
60
		return nil, err
61
	}
62

63
	// Hand back a callback that the caller can use to clean up everything
64
	// we're doing here.
65
	unmount := []string{}
66
	unmountAll = func() (err error) {
67
		for _, mountpoint := range unmount {
68
			// Unmount it and anything under it.
69
			if err2 := UnmountMountpoints(mountpoint, nil); err2 != nil {
70
				logrus.Warnf("pkg/bind: error unmounting %q: %v", mountpoint, err2)
71
				if err == nil {
72
					err = err2
73
				}
74
			}
75
			if err2 := unix.Unmount(mountpoint, unix.MNT_DETACH); err2 != nil {
76
				if errno, ok := err2.(syscall.Errno); !ok || errno != syscall.EINVAL {
77
					logrus.Warnf("pkg/bind: error detaching %q: %v", mountpoint, err2)
78
					if err == nil {
79
						err = err2
80
					}
81
				}
82
			}
83
			// Remove just the mountpoint.
84
			retry := 10
85
			remove := unix.Unlink
86
			err2 := remove(mountpoint)
87
			for err2 != nil && retry > 0 {
88
				if errno, ok := err2.(syscall.Errno); ok {
89
					switch errno {
90
					default:
91
						retry = 0
92
						continue
93
					case syscall.EISDIR:
94
						remove = unix.Rmdir
95
						err2 = remove(mountpoint)
96
					case syscall.EBUSY:
97
						if err3 := unix.Unmount(mountpoint, unix.MNT_DETACH); err3 == nil {
98
							err2 = remove(mountpoint)
99
						}
100
					}
101
					retry--
102
				}
103
			}
104
			if err2 != nil {
105
				logrus.Warnf("pkg/bind: error removing %q: %v", mountpoint, err2)
106
				if err == nil {
107
					err = err2
108
				}
109
			}
110
		}
111
		return err
112
	}
113

114
	// Create a top-level directory that the "root" user will be able to
115
	// access, that "root" from containers which use different mappings, or
116
	// other unprivileged users outside of containers, shouldn't be able to
117
	// access.
118
	mnt := filepath.Join(bundlePath, "mnt")
119
	if err = idtools.MkdirAndChown(mnt, 0100, idtools.IDPair{UID: int(rootUID), GID: int(rootGID)}); err != nil {
120
		return unmountAll, fmt.Errorf("creating %q owned by the container's root user: %w", mnt, err)
121
	}
122

123
	// Make that directory private, and add it to the list of locations we
124
	// unmount at cleanup time.
125
	if err = mount.MakeRPrivate(mnt); err != nil {
126
		return unmountAll, fmt.Errorf("marking filesystem at %q as private: %w", mnt, err)
127
	}
128
	unmount = append([]string{mnt}, unmount...)
129

130
	// Create a bind mount for the root filesystem and add it to the list.
131
	rootfs := filepath.Join(mnt, "rootfs")
132
	if err = os.Mkdir(rootfs, 0000); err != nil {
133
		return unmountAll, fmt.Errorf("creating directory %q: %w", rootfs, err)
134
	}
135
	if err = unix.Mount(rootPath, rootfs, "", unix.MS_BIND|unix.MS_REC|unix.MS_PRIVATE, ""); err != nil {
136
		return unmountAll, fmt.Errorf("bind mounting root filesystem from %q to %q: %w", rootPath, rootfs, err)
137
	}
138
	logrus.Debugf("bind mounted %q to %q", rootPath, rootfs)
139
	unmount = append([]string{rootfs}, unmount...)
140
	spec.Root.Path = rootfs
141

142
	// Do the same for everything we're binding in.
143
	mounts := make([]specs.Mount, 0, len(spec.Mounts))
144
	for i := range spec.Mounts {
145
		// If we're not using an intermediate, leave it in the list.
146
		if leaveBindMountAlone(spec.Mounts[i]) {
147
			mounts = append(mounts, spec.Mounts[i])
148
			continue
149
		}
150
		// Check if the source is a directory or something else.
151
		info, err := os.Stat(spec.Mounts[i].Source)
152
		if err != nil {
153
			if errors.Is(err, os.ErrNotExist) {
154
				logrus.Warnf("couldn't find %q on host to bind mount into container", spec.Mounts[i].Source)
155
				continue
156
			}
157
			return unmountAll, fmt.Errorf("checking if %q is a directory: %w", spec.Mounts[i].Source, err)
158
		}
159
		stage := filepath.Join(mnt, fmt.Sprintf("buildah-bind-target-%d", i))
160
		if info.IsDir() {
161
			// If the source is a directory, make one to use as the
162
			// mount target.
163
			if err = os.Mkdir(stage, 0000); err != nil {
164
				return unmountAll, fmt.Errorf("creating directory %q: %w", stage, err)
165
			}
166
		} else {
167
			// If the source is not a directory, create an empty
168
			// file to use as the mount target.
169
			file, err := os.OpenFile(stage, os.O_WRONLY|os.O_CREATE, 0000)
170
			if err != nil {
171
				return unmountAll, fmt.Errorf("creating file %q: %w", stage, err)
172
			}
173
			file.Close()
174
		}
175
		// Bind mount the source from wherever it is to a place where
176
		// we know the runtime helper will be able to get to it...
177
		if err = unix.Mount(spec.Mounts[i].Source, stage, "", unix.MS_BIND|unix.MS_REC|unix.MS_PRIVATE, ""); err != nil {
178
			return unmountAll, fmt.Errorf("bind mounting bind object from %q to %q: %w", spec.Mounts[i].Source, stage, err)
179
		}
180
		logrus.Debugf("bind mounted %q to %q", spec.Mounts[i].Source, stage)
181
		spec.Mounts[i].Source = stage
182
		// ... and update the source location that we'll pass to the
183
		// runtime to our intermediate location.
184
		mounts = append(mounts, spec.Mounts[i])
185
		unmount = append([]string{stage}, unmount...)
186
	}
187
	spec.Mounts = mounts
188

189
	return unmountAll, nil
190
}
191

192
// Decide if the mount should not be redirected to an intermediate location first.
193
func leaveBindMountAlone(mount specs.Mount) bool {
194
	// If we know we shouldn't do a redirection for this mount, skip it.
195
	if slices.Contains(mount.Options, NoBindOption) {
196
		return true
197
	}
198
	// If we're not bind mounting it in, we don't need to do anything for it.
199
	if mount.Type != "bind" && !slices.Contains(mount.Options, "bind") && !slices.Contains(mount.Options, "rbind") {
200
		return true
201
	}
202
	return false
203
}
204

205
// UnmountMountpoints unmounts the given mountpoints and anything that's hanging
206
// off of them, rather aggressively.  If a mountpoint also appears in the
207
// mountpointsToRemove slice, the mountpoints are removed after they are
208
// unmounted.
209
func UnmountMountpoints(mountpoint string, mountpointsToRemove []string) error {
210
	mounts, err := mount.GetMounts()
211
	if err != nil {
212
		return fmt.Errorf("retrieving list of mounts: %w", err)
213
	}
214
	// getChildren returns the list of mount IDs that hang off of the
215
	// specified ID.
216
	getChildren := func(id int) []int {
217
		var list []int
218
		for _, info := range mounts {
219
			if info.Parent == id {
220
				list = append(list, info.ID)
221
			}
222
		}
223
		return list
224
	}
225
	// getTree returns the list of mount IDs that hang off of the specified
226
	// ID, and off of those mount IDs, etc.
227
	getTree := func(id int) []int {
228
		mounts := []int{id}
229
		i := 0
230
		for i < len(mounts) {
231
			children := getChildren(mounts[i])
232
			mounts = append(mounts, children...)
233
			i++
234
		}
235
		return mounts
236
	}
237
	// getMountByID looks up the mount info with the specified ID
238
	getMountByID := func(id int) *mount.Info {
239
		for i := range mounts {
240
			if mounts[i].ID == id {
241
				return mounts[i]
242
			}
243
		}
244
		return nil
245
	}
246
	// getMountByPoint looks up the mount info with the specified mountpoint
247
	getMountByPoint := func(mountpoint string) *mount.Info {
248
		for i := range mounts {
249
			if mounts[i].Mountpoint == mountpoint {
250
				return mounts[i]
251
			}
252
		}
253
		return nil
254
	}
255
	// find the top of the tree we're unmounting
256
	top := getMountByPoint(mountpoint)
257
	if top == nil {
258
		if err != nil {
259
			return fmt.Errorf("%q is not mounted: %w", mountpoint, err)
260
		}
261
		return nil
262
	}
263
	// add all of the mounts that are hanging off of it
264
	tree := getTree(top.ID)
265
	// unmount each mountpoint, working from the end of the list (leaf nodes) to the top
266
	for i := range tree {
267
		var st unix.Stat_t
268
		id := tree[len(tree)-i-1]
269
		mount := getMountByID(id)
270
		// check if this mountpoint is mounted
271
		if err := unix.Lstat(mount.Mountpoint, &st); err != nil {
272
			if errors.Is(err, os.ErrNotExist) {
273
				logrus.Debugf("mountpoint %q is not present(?), skipping", mount.Mountpoint)
274
				continue
275
			}
276
			return fmt.Errorf("checking if %q is mounted: %w", mount.Mountpoint, err)
277
		}
278
		if uint64(mount.Major) != uint64(st.Dev) || uint64(mount.Minor) != uint64(st.Dev) { //nolint:unconvert // (required for some OS/arch combinations)
279
			logrus.Debugf("%q is apparently not really mounted, skipping", mount.Mountpoint)
280
			continue
281
		}
282
		// do the unmount
283
		if err := unix.Unmount(mount.Mountpoint, 0); err != nil {
284
			// if it was busy, detach it
285
			if errno, ok := err.(syscall.Errno); ok && errno == syscall.EBUSY {
286
				err = unix.Unmount(mount.Mountpoint, unix.MNT_DETACH)
287
			}
288
			if err != nil {
289
				// if it was invalid (not mounted), hide the error, else return it
290
				if errno, ok := err.(syscall.Errno); !ok || errno != syscall.EINVAL {
291
					logrus.Warnf("error unmounting %q: %v", mount.Mountpoint, err)
292
					continue
293
				}
294
			}
295
		}
296
		// if we're also supposed to remove this thing, do that, too
297
		if slices.Contains(mountpointsToRemove, mount.Mountpoint) {
298
			if err := os.Remove(mount.Mountpoint); err != nil {
299
				return fmt.Errorf("removing %q: %w", mount.Mountpoint, err)
300
			}
301
		}
302
	}
303
	return nil
304
}
305

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.