Skip to content

Commit a86c3d8

Browse files
authored
Merge commit from fork
[1.1] rootfs: try to scope MkdirAll to stay inside the rootfs
2 parents 6419fba + f0b652e commit a86c3d8

File tree

4 files changed

+263
-112
lines changed

4 files changed

+263
-112
lines changed

libcontainer/container_linux.go

+6-22
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

+87-90
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,7 +252,8 @@ 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)
285-
if err := os.MkdirAll(subsystemPath, 0o755); err != nil {
255+
subsystemName := filepath.Base(b.Destination)
256+
if err := utils.MkdirAllInRoot(c.root, subsystemPath, 0o755); err != nil {
286257
return err
287258
}
288259
if err := utils.WithProcfd(c.root, b.Destination, func(procfd string) error {
@@ -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,81 @@ 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+
destDir, destBase := filepath.Split(dest)
410+
destDirFd, err := utils.MkdirAllInRootOpen(rootfs, destDir, 0o755)
411+
if err != nil {
412+
return "", fmt.Errorf("make parent dir of file bind-mount: %w", err)
413+
}
414+
defer destDirFd.Close()
415+
// Make the target file. We want to avoid opening any file that is
416+
// already there because it could be a "bad" file like an invalid
417+
// device or hung tty that might cause a DoS, so we use mknodat.
418+
// destBase does not contain any "/" components, and mknodat does
419+
// not follow trailing symlinks, so we can safely just call mknodat
420+
// here.
421+
if err := unix.Mknodat(int(destDirFd.Fd()), destBase, unix.S_IFREG|0o644, 0); err != nil {
422+
// If we get EEXIST, there was already an inode there and
423+
// we can consider that a success.
424+
if !errors.Is(err, unix.EEXIST) {
425+
err = &os.PathError{Op: "mknod regular file", Path: dest, Err: err}
426+
return "", fmt.Errorf("create target of file bind-mount: %w", err)
427+
}
428+
}
429+
// Nothing left to do.
430+
return dest, nil
431+
}
432+
433+
case "tmpfs":
434+
// If the original target exists, copy the mode for the tmpfs mount.
435+
if stat, err := os.Stat(dest); err == nil {
436+
dt := fmt.Sprintf("mode=%04o", syscallMode(stat.Mode()))
437+
if m.Data != "" {
438+
dt = dt + "," + m.Data
439+
}
440+
m.Data = dt
441+
442+
// Nothing left to do.
443+
return dest, nil
444+
}
445+
}
446+
447+
if err := utils.MkdirAllInRoot(rootfs, dest, 0o755); err != nil {
448+
return "", err
449+
}
450+
return dest, nil
451+
}
452+
414453
func mountToRootfs(m *configs.Mount, c *mountConfig) error {
415454
rootfs := c.root
416455

@@ -435,53 +474,34 @@ func mountToRootfs(m *configs.Mount, c *mountConfig) error {
435474
} else if !fi.IsDir() {
436475
return fmt.Errorf("filesystem %q must be mounted on ordinary directory", m.Device)
437476
}
438-
if err := os.MkdirAll(dest, 0o755); err != nil {
477+
if err := utils.MkdirAllInRoot(rootfs, dest, 0o755); err != nil {
439478
return err
440479
}
441480
// Selinux kernels do not support labeling of /proc or /sys.
442481
return mountPropagate(m, rootfs, "", nil)
443482
}
444483

445-
mountLabel := c.label
446484
mountFd := c.fd
447-
dest, err := securejoin.SecureJoin(rootfs, m.Destination)
485+
dest, err := createMountpoint(rootfs, m, mountFd, m.Source)
448486
if err != nil {
449-
return err
487+
return fmt.Errorf("create mount destination for %s mount: %w", m.Destination, err)
450488
}
489+
mountLabel := c.label
451490

452491
switch m.Device {
453492
case "mqueue":
454-
if err := os.MkdirAll(dest, 0o755); err != nil {
455-
return err
456-
}
457493
if err := mountPropagate(m, rootfs, "", nil); err != nil {
458494
return err
459495
}
460496
return label.SetFileLabel(dest, mountLabel)
461497
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-
474498
if m.Extensions&configs.EXT_COPYUP == configs.EXT_COPYUP {
475499
err = doTmpfsCopyUp(m, rootfs, mountLabel)
476500
} else {
477501
err = mountPropagate(m, rootfs, mountLabel, nil)
478502
}
479-
480503
return err
481504
case "bind":
482-
if err := prepareBindMount(m, rootfs, mountFd); err != nil {
483-
return err
484-
}
485505
if err := mountPropagate(m, rootfs, mountLabel, mountFd); err != nil {
486506
return err
487507
}
@@ -509,12 +529,6 @@ func mountToRootfs(m *configs.Mount, c *mountConfig) error {
509529
}
510530
return mountCgroupV1(m, c)
511531
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-
}
518532
return mountPropagate(m, rootfs, mountLabel, mountFd)
519533
}
520534
if err := setRecAttr(m, rootfs); err != nil {
@@ -745,7 +759,10 @@ func createDeviceNode(rootfs string, node *devices.Device, bind bool) error {
745759
if err != nil {
746760
return err
747761
}
748-
if err := os.MkdirAll(filepath.Dir(dest), 0o755); err != nil {
762+
if dest == rootfs {
763+
return fmt.Errorf("%w: mknod over rootfs", errRootfsToFile)
764+
}
765+
if err := utils.MkdirAllInRoot(rootfs, filepath.Dir(dest), 0o755); err != nil {
749766
return err
750767
}
751768
if bind {
@@ -1011,26 +1028,6 @@ func chroot() error {
10111028
return nil
10121029
}
10131030

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-
10341031
// readonlyPath will make a path read only.
10351032
func readonlyPath(path string) error {
10361033
if err := mount(path, path, "", "", unix.MS_BIND|unix.MS_REC, ""); err != nil {

libcontainer/system/linux.go

+41
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ package system
66
import (
77
"os"
88
"os/exec"
9+
"runtime"
10+
"strings"
911
"unsafe"
1012

1113
"golang.org/x/sys/unix"
@@ -102,3 +104,42 @@ func GetSubreaper() (int, error) {
102104

103105
return int(i), nil
104106
}
107+
108+
func prepareAt(dir *os.File, path string) (int, string) {
109+
if dir == nil {
110+
return unix.AT_FDCWD, path
111+
}
112+
113+
// Rather than just filepath.Join-ing path here, do it manually so the
114+
// error and handle correctly indicate cases like path=".." as being
115+
// relative to the correct directory. The handle.Name() might end up being
116+
// wrong but because this is (currently) only used in MkdirAllInRoot, that
117+
// isn't a problem.
118+
dirName := dir.Name()
119+
if !strings.HasSuffix(dirName, "/") {
120+
dirName += "/"
121+
}
122+
fullPath := dirName + path
123+
124+
return int(dir.Fd()), fullPath
125+
}
126+
127+
func Openat(dir *os.File, path string, flags int, mode uint32) (*os.File, error) {
128+
dirFd, fullPath := prepareAt(dir, path)
129+
fd, err := unix.Openat(dirFd, path, flags, mode)
130+
if err != nil {
131+
return nil, &os.PathError{Op: "openat", Path: fullPath, Err: err}
132+
}
133+
runtime.KeepAlive(dir)
134+
return os.NewFile(uintptr(fd), fullPath), nil
135+
}
136+
137+
func Mkdirat(dir *os.File, path string, mode uint32) error {
138+
dirFd, fullPath := prepareAt(dir, path)
139+
err := unix.Mkdirat(dirFd, path, mode)
140+
if err != nil {
141+
err = &os.PathError{Op: "mkdirat", Path: fullPath, Err: err}
142+
}
143+
runtime.KeepAlive(dir)
144+
return err
145+
}

0 commit comments

Comments
 (0)