Skip to content

Commit 8781993

Browse files
committed
[1.1] rootfs: consolidate mountpoint creation logic
The logic for how we create mountpoints is spread over each mountpoint preparation function, when in reality the behaviour is pretty uniform with only a handful of exceptions. So just move it all to one function that is easier to understand. Signed-off-by: Aleksa Sarai <[email protected]>
1 parent 6419fba commit 8781993

File tree

3 files changed

+94
-109
lines changed

3 files changed

+94
-109
lines changed

libcontainer/container_linux.go

Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1276,8 +1276,7 @@ func (c *linuxContainer) restoreNetwork(req *criurpc.CriuReq, criuOpts *CriuOpts
12761276
// restore using CRIU. This function is inspired from the code in
12771277
// rootfs_linux.go
12781278
func (c *linuxContainer) makeCriuRestoreMountpoints(m *configs.Mount) error {
1279-
switch m.Device {
1280-
case "cgroup":
1279+
if m.Device == "cgroup" {
12811280
// No mount point(s) need to be created:
12821281
//
12831282
// * for v1, mount points are saved by CRIU because
@@ -1286,26 +1285,11 @@ func (c *linuxContainer) makeCriuRestoreMountpoints(m *configs.Mount) error {
12861285
// * for v2, /sys/fs/cgroup is a real mount, but
12871286
// the mountpoint appears as soon as /sys is mounted
12881287
return nil
1289-
case "bind":
1290-
// The prepareBindMount() function checks if source
1291-
// exists. So it cannot be used for other filesystem types.
1292-
// TODO: pass something else than nil? Not sure if criu is
1293-
// impacted by issue #2484
1294-
if err := prepareBindMount(m, c.config.Rootfs, nil); err != nil {
1295-
return err
1296-
}
1297-
default:
1298-
// for all other filesystems just create the mountpoints
1299-
dest, err := securejoin.SecureJoin(c.config.Rootfs, m.Destination)
1300-
if err != nil {
1301-
return err
1302-
}
1303-
if err := checkProcMount(c.config.Rootfs, dest, m, ""); err != nil {
1304-
return err
1305-
}
1306-
if err := os.MkdirAll(dest, 0o755); err != nil {
1307-
return err
1308-
}
1288+
}
1289+
// TODO: pass something else than nil? Not sure if criu is
1290+
// impacted by issue #2484
1291+
if _, err := createMountpoint(c.config.Rootfs, m, nil, ""); err != nil {
1292+
return fmt.Errorf("create criu restore mount for %s mount: %w", m.Destination, err)
13091293
}
13101294
return nil
13111295
}

libcontainer/rootfs_linux.go

Lines changed: 73 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -224,36 +224,6 @@ func mountCmd(cmd configs.Command) error {
224224
return nil
225225
}
226226

227-
func prepareBindMount(m *configs.Mount, rootfs string, mountFd *int) error {
228-
source := m.Source
229-
if mountFd != nil {
230-
source = "/proc/self/fd/" + strconv.Itoa(*mountFd)
231-
}
232-
233-
stat, err := os.Stat(source)
234-
if err != nil {
235-
// error out if the source of a bind mount does not exist as we will be
236-
// unable to bind anything to it.
237-
return err
238-
}
239-
// ensure that the destination of the bind mount is resolved of symlinks at mount time because
240-
// any previous mounts can invalidate the next mount's destination.
241-
// this can happen when a user specifies mounts within other mounts to cause breakouts or other
242-
// evil stuff to try to escape the container's rootfs.
243-
var dest string
244-
if dest, err = securejoin.SecureJoin(rootfs, m.Destination); err != nil {
245-
return err
246-
}
247-
if err := checkProcMount(rootfs, dest, m, source); err != nil {
248-
return err
249-
}
250-
if err := createIfNotExists(dest, stat.IsDir()); err != nil {
251-
return err
252-
}
253-
254-
return nil
255-
}
256-
257227
func mountCgroupV1(m *configs.Mount, c *mountConfig) error {
258228
binds, err := getCgroupMounts(m)
259229
if err != nil {
@@ -282,6 +252,7 @@ func mountCgroupV1(m *configs.Mount, c *mountConfig) error {
282252
for _, b := range binds {
283253
if c.cgroupns {
284254
subsystemPath := filepath.Join(c.root, b.Destination)
255+
subsystemName := filepath.Base(b.Destination)
285256
if err := os.MkdirAll(subsystemPath, 0o755); err != nil {
286257
return err
287258
}
@@ -292,7 +263,7 @@ func mountCgroupV1(m *configs.Mount, c *mountConfig) error {
292263
}
293264
var (
294265
source = "cgroup"
295-
data = filepath.Base(subsystemPath)
266+
data = subsystemName
296267
)
297268
if data == "systemd" {
298269
data = cgroups.CgroupNamePrefix + data
@@ -322,14 +293,7 @@ func mountCgroupV1(m *configs.Mount, c *mountConfig) error {
322293
}
323294

324295
func mountCgroupV2(m *configs.Mount, c *mountConfig) error {
325-
dest, err := securejoin.SecureJoin(c.root, m.Destination)
326-
if err != nil {
327-
return err
328-
}
329-
if err := os.MkdirAll(dest, 0o755); err != nil {
330-
return err
331-
}
332-
err = utils.WithProcfd(c.root, m.Destination, func(procfd string) error {
296+
err := utils.WithProcfd(c.root, m.Destination, func(procfd string) error {
333297
return mount(m.Source, m.Destination, procfd, "cgroup2", uintptr(m.Flags), m.Data)
334298
})
335299
if err == nil || !(errors.Is(err, unix.EPERM) || errors.Is(err, unix.EBUSY)) {
@@ -411,6 +375,70 @@ func doTmpfsCopyUp(m *configs.Mount, rootfs, mountLabel string) (Err error) {
411375
})
412376
}
413377

378+
var errRootfsToFile = errors.New("config tries to change rootfs to file")
379+
380+
func createMountpoint(rootfs string, m *configs.Mount, mountFd *int, source string) (string, error) {
381+
dest, err := securejoin.SecureJoin(rootfs, m.Destination)
382+
if err != nil {
383+
return "", err
384+
}
385+
if err := checkProcMount(rootfs, dest, m, source); err != nil {
386+
return "", fmt.Errorf("check proc-safety of %s mount: %w", m.Destination, err)
387+
}
388+
389+
switch m.Device {
390+
case "bind":
391+
source := m.Source
392+
if mountFd != nil {
393+
source = "/proc/self/fd/" + strconv.Itoa(*mountFd)
394+
}
395+
396+
fi, err := os.Stat(source)
397+
if err != nil {
398+
// Error out if the source of a bind mount does not exist as we
399+
// will be unable to bind anything to it.
400+
return "", fmt.Errorf("bind mount source stat: %w", err)
401+
}
402+
// If the original source is not a directory, make the target a file.
403+
if !fi.IsDir() {
404+
// Make sure we aren't tricked into trying to make the root a file.
405+
if rootfs == dest {
406+
return "", fmt.Errorf("%w: file bind mount over rootfs", errRootfsToFile)
407+
}
408+
// Make the parent directory.
409+
if err := os.MkdirAll(filepath.Dir(dest), 0o755); err != nil {
410+
return "", fmt.Errorf("make parent dir of file bind-mount: %w", err)
411+
}
412+
// Make the target file.
413+
f, err := os.OpenFile(dest, os.O_CREATE, 0o755)
414+
if err != nil {
415+
return "", fmt.Errorf("create target of file bind-mount: %w", err)
416+
}
417+
_ = f.Close()
418+
// Nothing left to do.
419+
return dest, nil
420+
}
421+
422+
case "tmpfs":
423+
// If the original target exists, copy the mode for the tmpfs mount.
424+
if stat, err := os.Stat(dest); err == nil {
425+
dt := fmt.Sprintf("mode=%04o", syscallMode(stat.Mode()))
426+
if m.Data != "" {
427+
dt = dt + "," + m.Data
428+
}
429+
m.Data = dt
430+
431+
// Nothing left to do.
432+
return dest, nil
433+
}
434+
}
435+
436+
if err := os.MkdirAll(dest, 0o755); err != nil {
437+
return "", err
438+
}
439+
return dest, nil
440+
}
441+
414442
func mountToRootfs(m *configs.Mount, c *mountConfig) error {
415443
rootfs := c.root
416444

@@ -442,46 +470,27 @@ func mountToRootfs(m *configs.Mount, c *mountConfig) error {
442470
return mountPropagate(m, rootfs, "", nil)
443471
}
444472

445-
mountLabel := c.label
446473
mountFd := c.fd
447-
dest, err := securejoin.SecureJoin(rootfs, m.Destination)
474+
dest, err := createMountpoint(rootfs, m, mountFd, m.Source)
448475
if err != nil {
449-
return err
476+
return fmt.Errorf("create mount destination for %s mount: %w", m.Destination, err)
450477
}
478+
mountLabel := c.label
451479

452480
switch m.Device {
453481
case "mqueue":
454-
if err := os.MkdirAll(dest, 0o755); err != nil {
455-
return err
456-
}
457482
if err := mountPropagate(m, rootfs, "", nil); err != nil {
458483
return err
459484
}
460485
return label.SetFileLabel(dest, mountLabel)
461486
case "tmpfs":
462-
if stat, err := os.Stat(dest); err != nil {
463-
if err := os.MkdirAll(dest, 0o755); err != nil {
464-
return err
465-
}
466-
} else {
467-
dt := fmt.Sprintf("mode=%04o", syscallMode(stat.Mode()))
468-
if m.Data != "" {
469-
dt = dt + "," + m.Data
470-
}
471-
m.Data = dt
472-
}
473-
474487
if m.Extensions&configs.EXT_COPYUP == configs.EXT_COPYUP {
475488
err = doTmpfsCopyUp(m, rootfs, mountLabel)
476489
} else {
477490
err = mountPropagate(m, rootfs, mountLabel, nil)
478491
}
479-
480492
return err
481493
case "bind":
482-
if err := prepareBindMount(m, rootfs, mountFd); err != nil {
483-
return err
484-
}
485494
if err := mountPropagate(m, rootfs, mountLabel, mountFd); err != nil {
486495
return err
487496
}
@@ -509,12 +518,6 @@ func mountToRootfs(m *configs.Mount, c *mountConfig) error {
509518
}
510519
return mountCgroupV1(m, c)
511520
default:
512-
if err := checkProcMount(rootfs, dest, m, m.Source); err != nil {
513-
return err
514-
}
515-
if err := os.MkdirAll(dest, 0o755); err != nil {
516-
return err
517-
}
518521
return mountPropagate(m, rootfs, mountLabel, mountFd)
519522
}
520523
if err := setRecAttr(m, rootfs); err != nil {
@@ -745,6 +748,9 @@ func createDeviceNode(rootfs string, node *devices.Device, bind bool) error {
745748
if err != nil {
746749
return err
747750
}
751+
if dest == rootfs {
752+
return fmt.Errorf("%w: mknod over rootfs", errRootfsToFile)
753+
}
748754
if err := os.MkdirAll(filepath.Dir(dest), 0o755); err != nil {
749755
return err
750756
}
@@ -1011,26 +1017,6 @@ func chroot() error {
10111017
return nil
10121018
}
10131019

1014-
// createIfNotExists creates a file or a directory only if it does not already exist.
1015-
func createIfNotExists(path string, isDir bool) error {
1016-
if _, err := os.Stat(path); err != nil {
1017-
if os.IsNotExist(err) {
1018-
if isDir {
1019-
return os.MkdirAll(path, 0o755)
1020-
}
1021-
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
1022-
return err
1023-
}
1024-
f, err := os.OpenFile(path, os.O_CREATE, 0o755)
1025-
if err != nil {
1026-
return err
1027-
}
1028-
_ = f.Close()
1029-
}
1030-
}
1031-
return nil
1032-
}
1033-
10341020
// readonlyPath will make a path read only.
10351021
func readonlyPath(path string) error {
10361022
if err := mount(path, path, "", "", unix.MS_BIND|unix.MS_REC, ""); err != nil {

libcontainer/utils/utils_unix.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"fmt"
88
"os"
99
"strconv"
10+
"strings"
1011
_ "unsafe" // for go:linkname
1112

1213
"golang.org/x/sys/unix"
@@ -115,3 +116,17 @@ func NewSockPair(name string) (parent *os.File, child *os.File, err error) {
115116
}
116117
return os.NewFile(uintptr(fds[1]), name+"-p"), os.NewFile(uintptr(fds[0]), name+"-c"), nil
117118
}
119+
120+
// IsLexicallyInRoot is shorthand for strings.HasPrefix(path+"/", root+"/"),
121+
// but properly handling the case where path or root are "/".
122+
//
123+
// NOTE: The return value only make sense if the path doesn't contain "..".
124+
func IsLexicallyInRoot(root, path string) bool {
125+
if root != "/" {
126+
root += "/"
127+
}
128+
if path != "/" {
129+
path += "/"
130+
}
131+
return strings.HasPrefix(path, root)
132+
}

0 commit comments

Comments
 (0)