github.com/kata-containers/runtime@v0.0.0-20210505125100-04f29832a923/virtcontainers/mount_test.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 "bytes" 10 "context" 11 "fmt" 12 "io/ioutil" 13 "os" 14 "os/exec" 15 "path/filepath" 16 "strconv" 17 "strings" 18 "syscall" 19 "testing" 20 21 ktu "github.com/kata-containers/runtime/pkg/katatestutils" 22 "github.com/kata-containers/runtime/virtcontainers/types" 23 "github.com/stretchr/testify/assert" 24 ) 25 26 const ( 27 testDirMode = os.FileMode(0750) 28 ) 29 30 var tc ktu.TestConstraint 31 32 func init() { 33 tc = ktu.NewTestConstraint(false) 34 } 35 36 func TestIsSystemMount(t *testing.T) { 37 assert := assert.New(t) 38 tests := []struct { 39 mnt string 40 expected bool 41 }{ 42 {"/sys", true}, 43 {"/sys/", true}, 44 {"/sys//", true}, 45 {"/sys/fs", true}, 46 {"/sys/fs/", true}, 47 {"/sys/fs/cgroup", true}, 48 {"/sysfoo", false}, 49 {"/home", false}, 50 {"/dev/block/", false}, 51 {"/mnt/dev/foo", false}, 52 } 53 54 for _, test := range tests { 55 result := isSystemMount(test.mnt) 56 assert.Exactly(result, test.expected) 57 } 58 } 59 60 func TestIsHostDevice(t *testing.T) { 61 assert := assert.New(t) 62 tests := []struct { 63 mnt string 64 expected bool 65 }{ 66 {"/dev", true}, 67 {"/dev/zero", true}, 68 {"/dev/block", true}, 69 {"/mnt/dev/block", false}, 70 } 71 72 for _, test := range tests { 73 result := isHostDevice(test.mnt) 74 assert.Equal(result, test.expected) 75 } 76 } 77 78 func TestIsHostDeviceCreateFile(t *testing.T) { 79 assert := assert.New(t) 80 if tc.NotValid(ktu.NeedRoot()) { 81 t.Skip(ktu.TestDisabledNeedRoot) 82 } 83 // Create regular file in /dev 84 85 path := "/dev/foobar" 86 f, err := os.Create(path) 87 assert.NoError(err) 88 f.Close() 89 90 assert.False(isHostDevice(path)) 91 assert.NoError(os.Remove(path)) 92 } 93 94 func TestMajorMinorNumber(t *testing.T) { 95 assert := assert.New(t) 96 devices := []string{"/dev/zero", "/dev/net/tun"} 97 98 for _, device := range devices { 99 cmdStr := fmt.Sprintf("ls -l %s | awk '{print $5$6}'", device) 100 cmd := exec.Command("sh", "-c", cmdStr) 101 output, err := cmd.Output() 102 assert.NoError(err) 103 104 data := bytes.Split(output, []byte(",")) 105 assert.False(len(data) < 2) 106 107 majorStr := strings.TrimSpace(string(data[0])) 108 minorStr := strings.TrimSpace(string(data[1])) 109 110 majorNo, err := strconv.Atoi(majorStr) 111 assert.NoError(err) 112 113 minorNo, err := strconv.Atoi(minorStr) 114 assert.NoError(err) 115 116 stat := syscall.Stat_t{} 117 err = syscall.Stat(device, &stat) 118 assert.NoError(err) 119 120 // Get major and minor numbers for the device itself. Note the use of stat.Rdev instead of Dev. 121 major := major(stat.Rdev) 122 minor := minor(stat.Rdev) 123 124 assert.Equal(minor, minorNo) 125 assert.Equal(major, majorNo) 126 } 127 } 128 129 func TestGetDeviceForPathRoot(t *testing.T) { 130 assert := assert.New(t) 131 dev, err := getDeviceForPath("/") 132 assert.NoError(err) 133 134 expected := "/" 135 136 assert.Equal(dev.mountPoint, expected) 137 } 138 139 func TestGetDeviceForPathValidMount(t *testing.T) { 140 assert := assert.New(t) 141 dev, err := getDeviceForPath("/proc") 142 assert.NoError(err) 143 144 expected := "/proc" 145 146 assert.Equal(dev.mountPoint, expected) 147 } 148 149 func TestGetDeviceForPathEmptyPath(t *testing.T) { 150 assert := assert.New(t) 151 _, err := getDeviceForPath("") 152 assert.Error(err) 153 } 154 155 func TestGetDeviceForPath(t *testing.T) { 156 assert := assert.New(t) 157 158 dev, err := getDeviceForPath("///") 159 assert.NoError(err) 160 161 assert.Equal(dev.mountPoint, "/") 162 163 _, err = getDeviceForPath("/../../.././././../.") 164 assert.NoError(err) 165 166 _, err = getDeviceForPath("/root/file with spaces") 167 assert.Error(err) 168 } 169 170 func TestGetDeviceForPathBindMount(t *testing.T) { 171 assert := assert.New(t) 172 173 if tc.NotValid(ktu.NeedRoot()) { 174 t.Skip(ktu.TestDisabledNeedRoot) 175 } 176 177 source := filepath.Join(testDir, "testDeviceDirSrc") 178 dest := filepath.Join(testDir, "testDeviceDirDest") 179 syscall.Unmount(dest, 0) 180 os.Remove(source) 181 os.Remove(dest) 182 183 err := os.MkdirAll(source, mountPerm) 184 assert.NoError(err) 185 186 defer os.Remove(source) 187 188 err = os.MkdirAll(dest, mountPerm) 189 assert.NoError(err) 190 191 defer os.Remove(dest) 192 193 err = bindMount(context.Background(), source, dest, false, "private") 194 assert.NoError(err) 195 196 defer syscall.Unmount(dest, syscall.MNT_DETACH) 197 198 destFile := filepath.Join(dest, "test") 199 _, err = os.Create(destFile) 200 assert.NoError(err) 201 202 defer os.Remove(destFile) 203 204 sourceDev, _ := getDeviceForPath(source) 205 destDev, _ := getDeviceForPath(destFile) 206 207 assert.Equal(sourceDev, destDev) 208 } 209 210 func TestIsDeviceMapper(t *testing.T) { 211 assert := assert.New(t) 212 213 // known major, minor for /dev/tty 214 major := 5 215 minor := 0 216 217 isDM, err := isDeviceMapper(major, minor) 218 assert.NoError(err) 219 assert.False(isDM) 220 221 // fake the block device format 222 blockFormatTemplate = "/sys/dev/char/%d:%d" 223 isDM, err = isDeviceMapper(major, minor) 224 assert.NoError(err) 225 assert.True(isDM) 226 } 227 228 func TestIsDockerVolume(t *testing.T) { 229 assert := assert.New(t) 230 path := "/var/lib/docker/volumes/00da1347c7cf4f15db35f/_data" 231 isDockerVolume := IsDockerVolume(path) 232 assert.True(isDockerVolume) 233 234 path = "/var/lib/testdir" 235 isDockerVolume = IsDockerVolume(path) 236 assert.False(isDockerVolume) 237 } 238 239 func TestIsEphemeralStorage(t *testing.T) { 240 assert := assert.New(t) 241 if tc.NotValid(ktu.NeedRoot()) { 242 t.Skip(ktu.TestDisabledNeedRoot) 243 } 244 245 dir, err := ioutil.TempDir(testDir, "foo") 246 assert.NoError(err) 247 defer os.RemoveAll(dir) 248 249 sampleEphePath := filepath.Join(dir, K8sEmptyDir, "tmp-volume") 250 err = os.MkdirAll(sampleEphePath, testDirMode) 251 assert.Nil(err) 252 253 err = syscall.Mount("tmpfs", sampleEphePath, "tmpfs", 0, "") 254 assert.NoError(err) 255 defer syscall.Unmount(sampleEphePath, 0) 256 257 isEphe := IsEphemeralStorage(sampleEphePath) 258 assert.True(isEphe) 259 260 isHostEmptyDir := Isk8sHostEmptyDir(sampleEphePath) 261 assert.False(isHostEmptyDir) 262 263 sampleEphePath = "/var/lib/kubelet/pods/366c3a75-4869-11e8-b479-507b9ddd5ce4/volumes/cache-volume" 264 isEphe = IsEphemeralStorage(sampleEphePath) 265 assert.False(isEphe) 266 267 isHostEmptyDir = Isk8sHostEmptyDir(sampleEphePath) 268 assert.False(isHostEmptyDir) 269 } 270 271 func TestBindMountInvalidSourceSymlink(t *testing.T) { 272 source := filepath.Join(testDir, "fooFile") 273 os.Remove(source) 274 275 err := bindMount(context.Background(), source, "", false, "private") 276 assert.Error(t, err) 277 } 278 279 func TestBindMountFailingMount(t *testing.T) { 280 source := filepath.Join(testDir, "fooLink") 281 fakeSource := filepath.Join(testDir, "fooFile") 282 os.Remove(source) 283 os.Remove(fakeSource) 284 assert := assert.New(t) 285 286 _, err := os.OpenFile(fakeSource, os.O_CREATE, mountPerm) 287 assert.NoError(err) 288 289 err = os.Symlink(fakeSource, source) 290 assert.NoError(err) 291 292 err = bindMount(context.Background(), source, "", false, "private") 293 assert.Error(err) 294 } 295 296 func TestBindMountSuccessful(t *testing.T) { 297 assert := assert.New(t) 298 if tc.NotValid(ktu.NeedRoot()) { 299 t.Skip(testDisabledAsNonRoot) 300 } 301 302 source := filepath.Join(testDir, "fooDirSrc") 303 dest := filepath.Join(testDir, "fooDirDest") 304 syscall.Unmount(dest, 0) 305 os.Remove(source) 306 os.Remove(dest) 307 308 err := os.MkdirAll(source, mountPerm) 309 assert.NoError(err) 310 311 err = os.MkdirAll(dest, mountPerm) 312 assert.NoError(err) 313 314 err = bindMount(context.Background(), source, dest, false, "private") 315 assert.NoError(err) 316 317 syscall.Unmount(dest, 0) 318 } 319 320 func TestBindMountReadonlySuccessful(t *testing.T) { 321 assert := assert.New(t) 322 if tc.NotValid(ktu.NeedRoot()) { 323 t.Skip(testDisabledAsNonRoot) 324 } 325 326 source := filepath.Join(testDir, "fooDirSrc") 327 dest := filepath.Join(testDir, "fooDirDest") 328 syscall.Unmount(dest, 0) 329 os.Remove(source) 330 os.Remove(dest) 331 332 err := os.MkdirAll(source, mountPerm) 333 assert.NoError(err) 334 335 err = os.MkdirAll(dest, mountPerm) 336 assert.NoError(err) 337 338 err = bindMount(context.Background(), source, dest, true, "private") 339 assert.NoError(err) 340 341 defer syscall.Unmount(dest, 0) 342 343 // should not be able to create file in read-only mount 344 destFile := filepath.Join(dest, "foo") 345 _, err = os.OpenFile(destFile, os.O_CREATE, mountPerm) 346 assert.Error(err) 347 } 348 349 func TestBindMountInvalidPgtypes(t *testing.T) { 350 assert := assert.New(t) 351 if tc.NotValid(ktu.NeedRoot()) { 352 t.Skip(testDisabledAsNonRoot) 353 } 354 355 source := filepath.Join(testDir, "fooDirSrc") 356 dest := filepath.Join(testDir, "fooDirDest") 357 syscall.Unmount(dest, 0) 358 os.Remove(source) 359 os.Remove(dest) 360 361 err := os.MkdirAll(source, mountPerm) 362 assert.NoError(err) 363 364 err = os.MkdirAll(dest, mountPerm) 365 assert.NoError(err) 366 367 err = bindMount(context.Background(), source, dest, false, "foo") 368 expectedErr := fmt.Sprintf("Wrong propagation type %s", "foo") 369 assert.EqualError(err, expectedErr) 370 } 371 372 // TestBindUnmountContainerRootfsENOENTNotError tests that if a file 373 // or directory attempting to be unmounted doesn't exist, then it 374 // is not considered an error 375 func TestBindUnmountContainerRootfsENOENTNotError(t *testing.T) { 376 if os.Getuid() != 0 { 377 t.Skip("Test disabled as requires root user") 378 } 379 testMnt := "/tmp/test_mount" 380 sID := "sandIDTest" 381 cID := "contIDTest" 382 container := &Container{ 383 id: cID, 384 } 385 386 assert := assert.New(t) 387 388 // check to make sure the file doesn't exist 389 testPath := filepath.Join(testMnt, sID, cID, rootfsDir) 390 if _, err := os.Stat(testPath); !os.IsNotExist(err) { 391 assert.NoError(os.Remove(testPath)) 392 } 393 394 err := bindUnmountContainerRootfs(context.Background(), filepath.Join(testMnt, sID), container) 395 assert.NoError(err) 396 } 397 398 func TestBindUnmountContainerRootfsRemoveRootfsDest(t *testing.T) { 399 assert := assert.New(t) 400 if tc.NotValid(ktu.NeedRoot()) { 401 t.Skip(ktu.TestDisabledNeedRoot) 402 } 403 404 sID := "sandIDTestRemoveRootfsDest" 405 cID := "contIDTestRemoveRootfsDest" 406 container := &Container{ 407 id: cID, 408 } 409 410 testPath := filepath.Join(testDir, sID, cID, rootfsDir) 411 syscall.Unmount(testPath, 0) 412 os.Remove(testPath) 413 414 err := os.MkdirAll(testPath, mountPerm) 415 assert.NoError(err) 416 defer os.RemoveAll(filepath.Join(testDir, sID)) 417 418 bindUnmountContainerRootfs(context.Background(), filepath.Join(testDir, sID), container) 419 420 if _, err := os.Stat(testPath); err == nil { 421 t.Fatal("empty rootfs dest should be removed") 422 } else if !os.IsNotExist(err) { 423 t.Fatal(err) 424 } 425 } 426 427 func TestBindUnmountContainerRootfsDevicemapper(t *testing.T) { 428 assert := assert.New(t) 429 if tc.NotValid(ktu.NeedRoot()) { 430 t.Skip(ktu.TestDisabledNeedRoot) 431 } 432 433 sID := "sandIDDevicemapper" 434 435 cID1 := "contIDDevicemapper1" 436 container := &Container{ 437 id: cID1, 438 state: types.ContainerState{ 439 Fstype: "xfs", 440 BlockDeviceID: "4b073a87f3c9242a", 441 }, 442 } 443 444 testPath1 := filepath.Join(testDir, sID, cID1, rootfsDir) 445 err := os.MkdirAll(testPath1, mountPerm) 446 assert.NoError(err) 447 448 bindUnmountContainerRootfs(context.Background(), filepath.Join(testDir, sID), container) 449 450 // expect testPath1 exist, if not exist, it means this case failed. 451 if _, err := os.Stat(testPath1); err != nil && !os.IsExist(err) { 452 t.Fatal("testPath should not be removed") 453 } 454 455 cID2 := "contIDDevicemapper2" 456 container.state.Fstype = "" 457 container.id = cID2 458 testPath2 := filepath.Join(testDir, sID, cID2, rootfsDir) 459 err = os.MkdirAll(testPath2, mountPerm) 460 assert.NoError(err) 461 462 defer os.RemoveAll(filepath.Join(testDir, sID)) 463 464 // expect testPath2 not exist, if exist, it means this case failed. 465 bindUnmountContainerRootfs(context.Background(), filepath.Join(testDir, sID), container) 466 if _, err := os.Stat(testPath2); err == nil || os.IsExist(err) { 467 t.Fatal("testPath should be removed") 468 } 469 }