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+
462588func 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