Skip to content

Commit

Permalink
runtimetest: add validation of mount order
Browse files Browse the repository at this point in the history
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.

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>
  • Loading branch information
cyphar committed Jun 2, 2016
1 parent 89907b6 commit 8eb8d60
Showing 1 changed file with 127 additions and 0 deletions.
127 changes: 127 additions & 0 deletions cmd/runtimetest/main.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"bufio"
"bytes"
"encoding/json"
"fmt"
Expand Down Expand Up @@ -244,6 +245,131 @@ func validateROPaths(spec *rspec.Spec) error {
return nil
}

func isParent(parent, child string) bool {
if parent == child {
return false
}

cparts := filepath.SplitList(child)
for i, part := range filepath.SplitList(parent) {
if cparts[i] != part {
return false
}
}

return true
}

func isMountPoint(path string) (bool, error) {
// Checking lstat(path) and lstat(path/..) won't work because it doesn't
// treat bindmounts properly. We have to parse /proc/1/mountinfo. This
// doesn't contain symlinks so we don't have to worry about that whole
// mess.
mi, err := os.Open("/proc/1/mountinfo")
if err != nil {
return false, err
}
defer mi.Close()

var mounts []string
pathindex := -1
scan := bufio.NewScanner(mi)
for i := 0; scan.Scan(); i++ {
// The fifth field is the mountpoint. However, we can't assume that
// seeing a path in /proc/1/mountinfo means that it resolves to a
// mountpoint. Unix allows you to mount over a filesystem tree.
field := strings.Split(scan.Text(), " ")[4]
if field == path {
pathindex = i
}

mounts = append(mounts, field)
}

// It isn't in mountinfo.
if pathindex < 0 {
return false, nil
}

// Check that the mount isn't followed by a mount on a parent directory.
hasParent := false
for _, other := range mounts[pathindex+1:] {
// If we see our mountpoint again, we reset the assumption.
if other == path {
hasParent = false
continue
}

// If there's a case where something was mounted over then we
// invalidate the assumption.
if isParent(other, path) {
hasParent = true
}
}

return !hasParent, nil
}

// Finds and returns any two paths in the given slice where pathA is a parent of
// pathB. Otherwise it returns "", "", false.
func findNestedPaths(paths []string) (string, string, bool) {
for _, parent := range paths {
for _, child := range paths {
if isParent(parent, child) {
return parent, child, true
}
}
}

return "", "", false
}

func validateMountOrder(spec *rspec.Spec) error {
fmt.Println("validating mount order")

var mounts []string
for _, m := range spec.Mounts {
mounts = append(mounts, filepath.Clean(m.Destination))
}

// If there are two mount options where A is a parent of B, then we can
// verify that the right order is maintained no matter which order they are
// in the mounts.
A, B, ok := findNestedPaths(mounts)
if !ok {
return nil
}

// Figure out the order of A and B.
var first string
for _, m := range mounts {
if A == m || B == m {
first = m
break
}
}

// A must *always* be a mountpoint.
if ok, err := isMountPoint(A); err != nil {
return fmt.Errorf("failed to get whether %q is a mountpoint: %q", A, err)
} else if !ok {
return fmt.Errorf("expected %q to be a mountpoint", A)
}

// B must be a mountpoint iff A was first.
if ok, err := isMountPoint(B); err != nil {
return fmt.Errorf("failed to get whether %q is a mountpoint: %q", A, err)
} else {
if first == A && !ok {
return fmt.Errorf("expected %q to be a mountpoint", B)
} else if first == B && ok {
return fmt.Errorf("expected %q to not be a mountpoint", B)
}
}

return nil
}

func main() {
spec, err := loadSpecConfig()
if err != nil {
Expand All @@ -259,6 +385,7 @@ func main() {
validateSysctls,
validateMaskedPaths,
validateROPaths,
validateMountOrder,
}

for _, v := range validations {
Expand Down

0 comments on commit 8eb8d60

Please sign in to comment.