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 }