gitee.com/leisunstar/runtime@v0.0.0-20200521203717-5cef3e7b53f9/virtcontainers/mount.go (about)

     1  // Copyright (c) 2017 Intel Corporation
     2  //
     3  // SPDX-License-Identifier: Apache-2.0
     4  //
     5  
     6  package virtcontainers
     7  
     8  import (
     9  	"context"
    10  	"errors"
    11  	"fmt"
    12  	"os"
    13  	"path/filepath"
    14  	"strings"
    15  	"syscall"
    16  
    17  	merr "github.com/hashicorp/go-multierror"
    18  	"github.com/kata-containers/runtime/virtcontainers/utils"
    19  	"github.com/sirupsen/logrus"
    20  )
    21  
    22  // DefaultShmSize is the default shm size to be used in case host
    23  // IPC is used.
    24  const DefaultShmSize = 65536 * 1024
    25  
    26  // Sadly golang/sys doesn't have UmountNoFollow although it's there since Linux 2.6.34
    27  const UmountNoFollow = 0x8
    28  
    29  var rootfsDir = "rootfs"
    30  
    31  var systemMountPrefixes = []string{"/proc", "/sys"}
    32  
    33  var propagationTypes = map[string]uintptr{
    34  	"shared":  syscall.MS_SHARED,
    35  	"private": syscall.MS_PRIVATE,
    36  	"slave":   syscall.MS_SLAVE,
    37  	"ubind":   syscall.MS_UNBINDABLE,
    38  }
    39  
    40  func isSystemMount(m string) bool {
    41  	for _, p := range systemMountPrefixes {
    42  		if m == p || strings.HasPrefix(m, p+"/") {
    43  			return true
    44  		}
    45  	}
    46  
    47  	return false
    48  }
    49  
    50  func isHostDevice(m string) bool {
    51  	if m == "/dev" {
    52  		return true
    53  	}
    54  
    55  	if strings.HasPrefix(m, "/dev/") {
    56  		// Check if regular file
    57  		s, err := os.Stat(m)
    58  
    59  		// This should not happen. In case file does not exist let the
    60  		// error be handled by the agent, simply return false here.
    61  		if err != nil {
    62  			return false
    63  		}
    64  
    65  		if s.Mode().IsRegular() {
    66  			return false
    67  		}
    68  
    69  		// This is not a regular file in /dev. It is either a
    70  		// device file, directory or any other special file which is
    71  		// specific to the host system.
    72  		return true
    73  	}
    74  
    75  	return false
    76  }
    77  
    78  func major(dev uint64) int {
    79  	return int((dev >> 8) & 0xfff)
    80  }
    81  
    82  func minor(dev uint64) int {
    83  	return int((dev & 0xff) | ((dev >> 12) & 0xfff00))
    84  }
    85  
    86  type device struct {
    87  	major      int
    88  	minor      int
    89  	mountPoint string
    90  }
    91  
    92  var errMountPointNotFound = errors.New("Mount point not found")
    93  
    94  // getDeviceForPath gets the underlying device containing the file specified by path.
    95  // The device type constitutes the major-minor number of the device and the dest mountPoint for the device
    96  //
    97  // eg. if /dev/sda1 is mounted on /a/b/c, a call to getDeviceForPath("/a/b/c/file") would return
    98  //
    99  //	device {
   100  //		major : major(/dev/sda1)
   101  //		minor : minor(/dev/sda1)
   102  //		mountPoint: /a/b/c
   103  //	}
   104  //
   105  //	if the path is a device path file such as /dev/sda1, it would return
   106  //
   107  //	device {
   108  //		major : major(/dev/sda1)
   109  //		minor : minor(/dev/sda1)
   110  //		mountPoint:
   111  
   112  func getDeviceForPath(path string) (device, error) {
   113  	var devMajor int
   114  	var devMinor int
   115  
   116  	if path == "" {
   117  		return device{}, fmt.Errorf("Path cannot be empty")
   118  	}
   119  
   120  	stat := syscall.Stat_t{}
   121  	err := syscall.Stat(path, &stat)
   122  	if err != nil {
   123  		return device{}, err
   124  	}
   125  
   126  	if isHostDevice(path) {
   127  		// stat.Rdev describes the device that this file (inode) represents.
   128  		devMajor = major(stat.Rdev)
   129  		devMinor = minor(stat.Rdev)
   130  
   131  		return device{
   132  			major:      devMajor,
   133  			minor:      devMinor,
   134  			mountPoint: "",
   135  		}, nil
   136  	}
   137  	// stat.Dev points to the underlying device containing the file
   138  	devMajor = major(stat.Dev)
   139  	devMinor = minor(stat.Dev)
   140  
   141  	path, err = filepath.Abs(path)
   142  	if err != nil {
   143  		return device{}, err
   144  	}
   145  
   146  	mountPoint := path
   147  
   148  	if path == "/" {
   149  		return device{
   150  			major:      devMajor,
   151  			minor:      devMinor,
   152  			mountPoint: mountPoint,
   153  		}, nil
   154  	}
   155  
   156  	// We get the mount point by recursively peforming stat on the path
   157  	// The point where the device changes indicates the mountpoint
   158  	for {
   159  		if mountPoint == "/" {
   160  			return device{}, errMountPointNotFound
   161  		}
   162  
   163  		parentStat := syscall.Stat_t{}
   164  		parentDir := filepath.Dir(path)
   165  
   166  		err := syscall.Lstat(parentDir, &parentStat)
   167  		if err != nil {
   168  			return device{}, err
   169  		}
   170  
   171  		if parentStat.Dev != stat.Dev {
   172  			break
   173  		}
   174  
   175  		mountPoint = parentDir
   176  		stat = parentStat
   177  		path = parentDir
   178  	}
   179  
   180  	dev := device{
   181  		major:      devMajor,
   182  		minor:      devMinor,
   183  		mountPoint: mountPoint,
   184  	}
   185  
   186  	return dev, nil
   187  }
   188  
   189  var blockFormatTemplate = "/sys/dev/block/%d:%d/dm"
   190  
   191  var checkStorageDriver = isDeviceMapper
   192  
   193  // isDeviceMapper checks if the device with the major and minor numbers is a devicemapper block device
   194  func isDeviceMapper(major, minor int) (bool, error) {
   195  
   196  	//Check if /sys/dev/block/${major}-${minor}/dm exists
   197  	sysPath := fmt.Sprintf(blockFormatTemplate, major, minor)
   198  
   199  	_, err := os.Stat(sysPath)
   200  	if err == nil {
   201  		return true, nil
   202  	} else if os.IsNotExist(err) {
   203  		return false, nil
   204  	}
   205  
   206  	return false, err
   207  }
   208  
   209  const mountPerm = os.FileMode(0755)
   210  
   211  // bindMount bind mounts a source in to a destination. This will
   212  // do some bookkeeping:
   213  // * evaluate all symlinks
   214  // * ensure the source exists
   215  // * recursively create the destination
   216  // pgtypes stands for propagation types, which are shared, private, slave, and ubind.
   217  func bindMount(ctx context.Context, source, destination string, readonly bool, pgtypes string) error {
   218  	span, _ := trace(ctx, "bindMount")
   219  	defer span.Finish()
   220  
   221  	if source == "" {
   222  		return fmt.Errorf("source must be specified")
   223  	}
   224  	if destination == "" {
   225  		return fmt.Errorf("destination must be specified")
   226  	}
   227  
   228  	absSource, err := filepath.EvalSymlinks(source)
   229  	if err != nil {
   230  		return fmt.Errorf("Could not resolve symlink for source %v", source)
   231  	}
   232  
   233  	if err := ensureDestinationExists(absSource, destination); err != nil {
   234  		return fmt.Errorf("Could not create destination mount point %v: %v", destination, err)
   235  	}
   236  
   237  	if err := syscall.Mount(absSource, destination, "bind", syscall.MS_BIND, ""); err != nil {
   238  		return fmt.Errorf("Could not bind mount %v to %v: %v", absSource, destination, err)
   239  	}
   240  
   241  	if pgtype, exist := propagationTypes[pgtypes]; exist {
   242  		if err := syscall.Mount("none", destination, "", pgtype, ""); err != nil {
   243  			return fmt.Errorf("Could not make mount point %v %s: %v", destination, pgtypes, err)
   244  		}
   245  	} else {
   246  		return fmt.Errorf("Wrong propagation type %s", pgtypes)
   247  	}
   248  
   249  	// For readonly bind mounts, we need to remount with the readonly flag.
   250  	// This is needed as only very recent versions of libmount/util-linux support "bind,ro"
   251  	if readonly {
   252  		return syscall.Mount(absSource, destination, "bind", uintptr(syscall.MS_BIND|syscall.MS_REMOUNT|syscall.MS_RDONLY), "")
   253  	}
   254  
   255  	return nil
   256  }
   257  
   258  // An existing mount may be remounted by specifying `MS_REMOUNT` in
   259  // mountflags.
   260  // This allows you to change the mountflags of an existing mount.
   261  // The mountflags should match the values used in the original mount() call,
   262  // except for those parameters that you are trying to change.
   263  func remount(ctx context.Context, mountflags uintptr, src string) error {
   264  	absSrc, err := filepath.EvalSymlinks(src)
   265  	if err != nil {
   266  		return fmt.Errorf("Could not resolve symlink for %s", src)
   267  	}
   268  
   269  	if err := syscall.Mount(absSrc, absSrc, "", syscall.MS_REMOUNT|mountflags, ""); err != nil {
   270  		return fmt.Errorf("remount %s failed: %v", absSrc, err)
   271  	}
   272  
   273  	return nil
   274  }
   275  
   276  // bindMountContainerRootfs bind mounts a container rootfs into a 9pfs shared
   277  // directory between the guest and the host.
   278  func bindMountContainerRootfs(ctx context.Context, sharedDir, sandboxID, cID, cRootFs string, readonly bool) error {
   279  	span, _ := trace(ctx, "bindMountContainerRootfs")
   280  	defer span.Finish()
   281  
   282  	rootfsDest := filepath.Join(sharedDir, sandboxID, cID, rootfsDir)
   283  
   284  	return bindMount(ctx, cRootFs, rootfsDest, readonly, "private")
   285  }
   286  
   287  // Mount describes a container mount.
   288  type Mount struct {
   289  	Source      string
   290  	Destination string
   291  
   292  	// Type specifies the type of filesystem to mount.
   293  	Type string
   294  
   295  	// Options list all the mount options of the filesystem.
   296  	Options []string
   297  
   298  	// HostPath used to store host side bind mount path
   299  	HostPath string
   300  
   301  	// ReadOnly specifies if the mount should be read only or not
   302  	ReadOnly bool
   303  
   304  	// BlockDeviceID represents block device that is attached to the
   305  	// VM in case this mount is a block device file or a directory
   306  	// backed by a block device.
   307  	BlockDeviceID string
   308  }
   309  
   310  func isSymlink(path string) bool {
   311  	stat, err := os.Stat(path)
   312  	if err != nil {
   313  		return false
   314  	}
   315  	return stat.Mode()&os.ModeSymlink != 0
   316  }
   317  
   318  func bindUnmountContainerRootfs(ctx context.Context, sharedDir, sandboxID, cID string) error {
   319  	span, _ := trace(ctx, "bindUnmountContainerRootfs")
   320  	defer span.Finish()
   321  
   322  	rootfsDest := filepath.Join(sharedDir, sandboxID, cID, rootfsDir)
   323  	if isSymlink(filepath.Join(sharedDir, sandboxID, cID)) || isSymlink(rootfsDest) {
   324  		logrus.Warnf("container dir %s is a symlink, malicious guest?", cID)
   325  		return nil
   326  	}
   327  
   328  	err := syscall.Unmount(rootfsDest, syscall.MNT_DETACH|UmountNoFollow)
   329  	if err == syscall.ENOENT {
   330  		logrus.Warnf("%s: %s", err, rootfsDest)
   331  		return nil
   332  	}
   333  	if err := syscall.Rmdir(rootfsDest); err != nil {
   334  		logrus.WithError(err).WithField("rootfs-dir", rootfsDest).Warn("Could not remove container rootfs dir")
   335  	}
   336  
   337  	return err
   338  }
   339  
   340  func bindUnmountAllRootfs(ctx context.Context, sharedDir string, sandbox *Sandbox) error {
   341  	span, _ := trace(ctx, "bindUnmountAllRootfs")
   342  	defer span.Finish()
   343  
   344  	var errors *merr.Error
   345  	for _, c := range sandbox.containers {
   346  		if isSymlink(filepath.Join(sharedDir, sandbox.id, c.id)) {
   347  			logrus.Warnf("container dir %s is a symlink, malicious guest?", c.id)
   348  			continue
   349  		}
   350  		c.unmountHostMounts()
   351  		if c.state.Fstype == "" {
   352  			// even if error found, don't break out of loop until all mounts attempted
   353  			// to be unmounted, and collect all errors
   354  			errors = merr.Append(errors, bindUnmountContainerRootfs(c.ctx, sharedDir, sandbox.id, c.id))
   355  		}
   356  	}
   357  	return errors.ErrorOrNil()
   358  }
   359  
   360  const (
   361  	dockerVolumePrefix = "/var/lib/docker/volumes"
   362  	dockerVolumeSuffix = "_data"
   363  )
   364  
   365  // IsDockerVolume returns true if the given source path is
   366  // a docker volume.
   367  // This uses a very specific path that is used by docker.
   368  func IsDockerVolume(path string) bool {
   369  	if strings.HasPrefix(path, dockerVolumePrefix) && filepath.Base(path) == dockerVolumeSuffix {
   370  		return true
   371  	}
   372  	return false
   373  }
   374  
   375  const (
   376  	// K8sEmptyDir is the k8s specific path for `empty-dir` volumes
   377  	K8sEmptyDir = "kubernetes.io~empty-dir"
   378  )
   379  
   380  // IsEphemeralStorage returns true if the given path
   381  // to the storage belongs to kubernetes ephemeral storage
   382  //
   383  // This method depends on a specific path used by k8s
   384  // to detect if it's of type ephemeral. As of now,
   385  // this is a very k8s specific solution that works
   386  // but in future there should be a better way for this
   387  // method to determine if the path is for ephemeral
   388  // volume type
   389  func IsEphemeralStorage(path string) bool {
   390  	if !isEmptyDir(path) {
   391  		return false
   392  	}
   393  
   394  	if _, fsType, _ := utils.GetDevicePathAndFsType(path); fsType == "tmpfs" {
   395  		return true
   396  	}
   397  
   398  	return false
   399  }
   400  
   401  // Isk8sHostEmptyDir returns true if the given path
   402  // to the storage belongs to kubernetes empty-dir of medium "default"
   403  // i.e volumes that are directories on the host.
   404  func Isk8sHostEmptyDir(path string) bool {
   405  	if !isEmptyDir(path) {
   406  		return false
   407  	}
   408  
   409  	if _, fsType, _ := utils.GetDevicePathAndFsType(path); fsType != "tmpfs" {
   410  		return true
   411  	}
   412  	return false
   413  }
   414  
   415  func isEmptyDir(path string) bool {
   416  	splitSourceSlice := strings.Split(path, "/")
   417  	if len(splitSourceSlice) > 1 {
   418  		storageType := splitSourceSlice[len(splitSourceSlice)-2]
   419  		if storageType == K8sEmptyDir {
   420  			return true
   421  		}
   422  	}
   423  	return false
   424  }