Skip to content

Commit a282e7c

Browse files
committed
runtimetest: add validation of mount order
In order to make sure that runtimes correctly implement the ordering of []spec.Mount, we need to check /proc/1/mountinfo (keeping in mind that Unix allows a user to mount over the top of an existing mountpoint -- thus masking it). We only run this test if there happen to be two mountpoints in the list where one is a parent of the other. We also don't run it on Windows. An extension of this might be to check all of the nested mounts specified in the spec. Signed-off-by: Aleksa Sarai <asarai@suse.de>
1 parent a05c891 commit a282e7c

1 file changed

Lines changed: 127 additions & 0 deletions

File tree

cmd/runtimetest/main.go

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"io/ioutil"
1010
"os"
1111
"path/filepath"
12+
"runtime"
1213
"strconv"
1314
"strings"
1415
"syscall"
@@ -459,6 +460,131 @@ func validateMountsExist(spec *rspec.Spec) error {
459460
return nil
460461
}
461462

463+
func isParent(parent, child string) bool {
464+
if parent == child {
465+
return false
466+
}
467+
468+
parent = filepath.ToSlash(parent)
469+
child = filepath.ToSlash(child)
470+
471+
cparts := strings.Split(child, "/")
472+
for i, part := range strings.Split(parent, "/") {
473+
if cparts[i] != part {
474+
return false
475+
}
476+
}
477+
478+
return true
479+
}
480+
481+
func isMountPoint(path string, mountinfos []*mount.Info) (bool, error) {
482+
// Find the mountpoint for path.
483+
var mounts []string
484+
pathindex := -1
485+
for idx, mi := range mountinfos {
486+
if mi.Mountpoint == path {
487+
pathindex = idx
488+
}
489+
mounts = append(mounts, mi.Mountpoint)
490+
}
491+
492+
// It isn't in mountinfo.
493+
if pathindex < 0 {
494+
return false, nil
495+
}
496+
497+
// Check that the mount isn't followed by a mount on a parent directory.
498+
hasParent := false
499+
for _, other := range mounts[pathindex+1:] {
500+
// If we see our mountpoint again, we reset the assumption.
501+
if other == path {
502+
hasParent = false
503+
continue
504+
}
505+
506+
// If there's a case where something was mounted over then we
507+
// invalidate the assumption.
508+
if isParent(other, path) {
509+
hasParent = true
510+
}
511+
}
512+
513+
return !hasParent, nil
514+
}
515+
516+
// Finds and returns any two paths in the given slice where pathA is a parent of
517+
// pathB. Otherwise it returns "", "", false.
518+
func findNestedPaths(paths []string) (string, string, bool) {
519+
for _, parent := range paths {
520+
for _, child := range paths {
521+
if isParent(parent, child) {
522+
return parent, child, true
523+
}
524+
}
525+
}
526+
527+
return "", "", false
528+
}
529+
530+
func validateMountOrder(spec *rspec.Spec) error {
531+
// Windows doesn't support the concept of nested mounts, so this test
532+
// doesn't make any sense on that platform.
533+
if runtime.GOOS == "windows" {
534+
return nil
535+
}
536+
537+
fmt.Println("validating mount order")
538+
539+
var mounts []string
540+
for _, m := range spec.Mounts {
541+
mounts = append(mounts, filepath.Clean(m.Destination))
542+
}
543+
544+
// Get the mountinfo for us.
545+
mountinfos, err := mount.GetMounts()
546+
if err != nil {
547+
return err
548+
}
549+
550+
// If there are two mount options where A is a parent of B, then we can
551+
// verify that the right order is maintained no matter which order they are
552+
// in the mounts.
553+
A, B, ok := findNestedPaths(mounts)
554+
if !ok {
555+
return nil
556+
}
557+
558+
// Figure out the order of A and B.
559+
var first string
560+
for _, m := range mounts {
561+
if A == m || B == m {
562+
first = m
563+
break
564+
}
565+
}
566+
567+
// A must *always* be a mountpoint.
568+
if ok, err := isMountPoint(A, mountinfos); err != nil {
569+
return fmt.Errorf("failed to get whether %q is a mountpoint: %q", A, err)
570+
} else if !ok {
571+
return fmt.Errorf("expected %q to be a mountpoint", A)
572+
}
573+
574+
// B must be a mountpoint iff A was first.
575+
if ok, err := isMountPoint(B, mountinfos); err != nil {
576+
return fmt.Errorf("failed to get whether %q is a mountpoint: %q", A, err)
577+
} else {
578+
if first == A && !ok {
579+
return fmt.Errorf("expected %q to be a mountpoint", B)
580+
} else if first == B && ok {
581+
return fmt.Errorf("expected %q to not be a mountpoint", B)
582+
}
583+
}
584+
585+
return nil
586+
}
587+
462588
func validate(context *cli.Context) error {
463589
logLevelString := context.String("log-level")
464590
logLevel, err := logrus.ParseLevel(logLevelString)
@@ -479,6 +605,7 @@ func validate(context *cli.Context) error {
479605
validateHostname,
480606
validateRlimits,
481607
validateMountsExist,
608+
validateMountOrder,
482609
}
483610

484611
linuxValidations := []validation{

0 commit comments

Comments
 (0)