github.com/opencontainers/runc@v1.2.0-rc.1.0.20240520010911-492dc558cdd6/libcontainer/specconv/spec_linux_test.go (about) 1 package specconv 2 3 import ( 4 "os" 5 "strings" 6 "testing" 7 8 dbus "github.com/godbus/dbus/v5" 9 "github.com/opencontainers/runc/libcontainer/configs" 10 "github.com/opencontainers/runc/libcontainer/configs/validate" 11 "github.com/opencontainers/runc/libcontainer/devices" 12 "github.com/opencontainers/runtime-spec/specs-go" 13 "golang.org/x/sys/unix" 14 ) 15 16 func TestCreateCommandHookTimeout(t *testing.T) { 17 timeout := 3600 18 hook := specs.Hook{ 19 Path: "/some/hook/path", 20 Args: []string{"--some", "thing"}, 21 Env: []string{"SOME=value"}, 22 Timeout: &timeout, 23 } 24 command := createCommandHook(hook) 25 timeoutStr := command.Timeout.String() 26 if timeoutStr != "1h0m0s" { 27 t.Errorf("Expected the Timeout to be 1h0m0s, got: %s", timeoutStr) 28 } 29 } 30 31 func TestCreateHooks(t *testing.T) { 32 rspec := &specs.Spec{ 33 Hooks: &specs.Hooks{ 34 Prestart: []specs.Hook{ 35 { 36 Path: "/some/hook/path", 37 }, 38 { 39 Path: "/some/hook2/path", 40 Args: []string{"--some", "thing"}, 41 }, 42 }, 43 CreateRuntime: []specs.Hook{ 44 { 45 Path: "/some/hook/path", 46 }, 47 { 48 Path: "/some/hook2/path", 49 Args: []string{"--some", "thing"}, 50 }, 51 }, 52 CreateContainer: []specs.Hook{ 53 { 54 Path: "/some/hook/path", 55 }, 56 { 57 Path: "/some/hook2/path", 58 Args: []string{"--some", "thing"}, 59 }, 60 }, 61 StartContainer: []specs.Hook{ 62 { 63 Path: "/some/hook/path", 64 }, 65 { 66 Path: "/some/hook2/path", 67 Args: []string{"--some", "thing"}, 68 }, 69 }, 70 Poststart: []specs.Hook{ 71 { 72 Path: "/some/hook/path", 73 Args: []string{"--some", "thing"}, 74 Env: []string{"SOME=value"}, 75 }, 76 { 77 Path: "/some/hook2/path", 78 }, 79 { 80 Path: "/some/hook3/path", 81 }, 82 }, 83 Poststop: []specs.Hook{ 84 { 85 Path: "/some/hook/path", 86 Args: []string{"--some", "thing"}, 87 Env: []string{"SOME=value"}, 88 }, 89 { 90 Path: "/some/hook2/path", 91 }, 92 { 93 Path: "/some/hook3/path", 94 }, 95 { 96 Path: "/some/hook4/path", 97 Args: []string{"--some", "thing"}, 98 }, 99 }, 100 }, 101 } 102 conf := &configs.Config{} 103 createHooks(rspec, conf) 104 105 prestart := conf.Hooks[configs.Prestart] 106 107 if len(prestart) != 2 { 108 t.Error("Expected 2 Prestart hooks") 109 } 110 111 createRuntime := conf.Hooks[configs.CreateRuntime] 112 113 if len(createRuntime) != 2 { 114 t.Error("Expected 2 createRuntime hooks") 115 } 116 117 createContainer := conf.Hooks[configs.CreateContainer] 118 119 if len(createContainer) != 2 { 120 t.Error("Expected 2 createContainer hooks") 121 } 122 123 startContainer := conf.Hooks[configs.StartContainer] 124 125 if len(startContainer) != 2 { 126 t.Error("Expected 2 startContainer hooks") 127 } 128 129 poststart := conf.Hooks[configs.Poststart] 130 131 if len(poststart) != 3 { 132 t.Error("Expected 3 Poststart hooks") 133 } 134 135 poststop := conf.Hooks[configs.Poststop] 136 137 if len(poststop) != 4 { 138 t.Error("Expected 4 Poststop hooks") 139 } 140 } 141 142 func TestSetupSeccompNil(t *testing.T) { 143 seccomp, err := SetupSeccomp(nil) 144 if err != nil { 145 t.Error("Expected error to be nil") 146 } 147 148 if seccomp != nil { 149 t.Error("Expected seccomp to be nil") 150 } 151 } 152 153 func TestSetupSeccompEmpty(t *testing.T) { 154 conf := &specs.LinuxSeccomp{} 155 seccomp, err := SetupSeccomp(conf) 156 if err != nil { 157 t.Error("Expected error to be nil") 158 } 159 160 if seccomp != nil { 161 t.Error("Expected seccomp to be nil") 162 } 163 } 164 165 // TestSetupSeccompWrongAction tests that a wrong action triggers an error 166 func TestSetupSeccompWrongAction(t *testing.T) { 167 conf := &specs.LinuxSeccomp{ 168 DefaultAction: "SCMP_ACT_NON_EXIXTENT_ACTION", 169 } 170 _, err := SetupSeccomp(conf) 171 if err == nil { 172 t.Error("Expected error") 173 } 174 } 175 176 // TestSetupSeccompWrongArchitecture tests that a wrong architecture triggers an error 177 func TestSetupSeccompWrongArchitecture(t *testing.T) { 178 conf := &specs.LinuxSeccomp{ 179 DefaultAction: "SCMP_ACT_ALLOW", 180 Architectures: []specs.Arch{"SCMP_ARCH_NON_EXISTENT_ARCH"}, 181 } 182 _, err := SetupSeccomp(conf) 183 if err == nil { 184 t.Error("Expected error") 185 } 186 } 187 188 func TestSetupSeccomp(t *testing.T) { 189 errnoRet := uint(55) 190 conf := &specs.LinuxSeccomp{ 191 DefaultAction: "SCMP_ACT_ERRNO", 192 Architectures: []specs.Arch{specs.ArchX86_64, specs.ArchARM}, 193 ListenerPath: "/var/run/mysocket", 194 ListenerMetadata: "mymetadatastring", 195 Syscalls: []specs.LinuxSyscall{ 196 { 197 Names: []string{"clone"}, 198 Action: "SCMP_ACT_ALLOW", 199 Args: []specs.LinuxSeccompArg{ 200 { 201 Index: 0, 202 Value: unix.CLONE_NEWNS | unix.CLONE_NEWUTS | unix.CLONE_NEWIPC | unix.CLONE_NEWUSER | unix.CLONE_NEWPID | unix.CLONE_NEWNET | unix.CLONE_NEWCGROUP, 203 ValueTwo: 0, 204 Op: "SCMP_CMP_MASKED_EQ", 205 }, 206 }, 207 }, 208 { 209 Names: []string{"semctl"}, 210 Action: "SCMP_ACT_KILL", 211 }, 212 { 213 Names: []string{"semget"}, 214 Action: "SCMP_ACT_ERRNO", 215 }, 216 { 217 Names: []string{"send"}, 218 Action: "SCMP_ACT_ERRNO", 219 ErrnoRet: &errnoRet, 220 }, 221 { 222 Names: []string{"lchown"}, 223 Action: "SCMP_ACT_TRAP", 224 }, 225 { 226 Names: []string{"lremovexattr"}, 227 Action: "SCMP_ACT_TRACE", 228 }, 229 { 230 Names: []string{"mbind"}, 231 Action: "SCMP_ACT_LOG", 232 }, 233 { 234 Names: []string{"mknod"}, 235 Action: "SCMP_ACT_NOTIFY", 236 }, 237 { 238 Names: []string{"rmdir"}, 239 Action: "SCMP_ACT_KILL_THREAD", 240 }, 241 { 242 Names: []string{"mkdir"}, 243 Action: "SCMP_ACT_KILL_PROCESS", 244 }, 245 }, 246 } 247 seccomp, err := SetupSeccomp(conf) 248 if err != nil { 249 t.Errorf("Couldn't create Seccomp config: %v", err) 250 } 251 252 if seccomp.DefaultAction != configs.Errno { 253 t.Error("Wrong conversion for DefaultAction") 254 } 255 256 if len(seccomp.Architectures) != 2 { 257 t.Error("Wrong number of architectures") 258 } 259 260 if seccomp.Architectures[0] != "amd64" || seccomp.Architectures[1] != "arm" { 261 t.Error("Expected architectures are not found") 262 } 263 264 if seccomp.ListenerPath != "/var/run/mysocket" { 265 t.Error("Expected ListenerPath is wrong") 266 } 267 268 if seccomp.ListenerMetadata != "mymetadatastring" { 269 t.Error("Expected ListenerMetadata is wrong") 270 } 271 272 calls := seccomp.Syscalls 273 274 if len(calls) != len(conf.Syscalls) { 275 t.Error("Mismatched number of syscalls") 276 } 277 278 for _, call := range calls { 279 switch call.Name { 280 case "clone": 281 if call.Action != configs.Allow { 282 t.Error("Wrong conversion for the clone syscall action") 283 } 284 expectedCloneSyscallArgs := configs.Arg{ 285 Index: 0, 286 Op: configs.MaskEqualTo, 287 Value: unix.CLONE_NEWNS | unix.CLONE_NEWUTS | unix.CLONE_NEWIPC | unix.CLONE_NEWUSER | unix.CLONE_NEWPID | unix.CLONE_NEWNET | unix.CLONE_NEWCGROUP, 288 ValueTwo: 0, 289 } 290 if expectedCloneSyscallArgs != *call.Args[0] { 291 t.Errorf("Wrong arguments conversion for the clone syscall under test") 292 } 293 case "semctl": 294 if call.Action != configs.Kill { 295 t.Errorf("Wrong conversion for the %s syscall action", call.Name) 296 } 297 case "semget": 298 if call.Action != configs.Errno { 299 t.Errorf("Wrong conversion for the %s syscall action", call.Name) 300 } 301 if call.ErrnoRet != nil { 302 t.Errorf("Wrong error ret for the %s syscall", call.Name) 303 } 304 case "send": 305 if call.Action != configs.Errno { 306 t.Errorf("Wrong conversion for the %s syscall action", call.Name) 307 } 308 if *call.ErrnoRet != errnoRet { 309 t.Errorf("Wrong error ret for the %s syscall", call.Name) 310 } 311 case "lchown": 312 if call.Action != configs.Trap { 313 t.Errorf("Wrong conversion for the %s syscall action", call.Name) 314 } 315 case "lremovexattr": 316 if call.Action != configs.Trace { 317 t.Errorf("Wrong conversion for the %s syscall action", call.Name) 318 } 319 case "mbind": 320 if call.Action != configs.Log { 321 t.Errorf("Wrong conversion for the %s syscall action", call.Name) 322 } 323 case "mknod": 324 if call.Action != configs.Notify { 325 t.Errorf("Wrong conversion for the %s syscall action", call.Name) 326 } 327 case "rmdir": 328 if call.Action != configs.KillThread { 329 t.Errorf("Wrong conversion for the %s syscall action", call.Name) 330 } 331 case "mkdir": 332 if call.Action != configs.KillProcess { 333 t.Errorf("Wrong conversion for the %s syscall action", call.Name) 334 } 335 default: 336 t.Errorf("Unexpected syscall %s found", call.Name) 337 } 338 } 339 } 340 341 func TestLinuxCgroupWithMemoryResource(t *testing.T) { 342 cgroupsPath := "/user/cgroups/path/id" 343 344 spec := &specs.Spec{} 345 devices := []specs.LinuxDeviceCgroup{ 346 { 347 Allow: false, 348 Access: "rwm", 349 }, 350 } 351 352 limit := int64(100) 353 reservation := int64(50) 354 swap := int64(20) 355 kernel := int64(40) 356 kernelTCP := int64(45) 357 swappiness := uint64(1) 358 swappinessPtr := &swappiness 359 disableOOMKiller := true 360 resources := &specs.LinuxResources{ 361 Devices: devices, 362 Memory: &specs.LinuxMemory{ 363 Limit: &limit, 364 Reservation: &reservation, 365 Swap: &swap, 366 Kernel: &kernel, 367 KernelTCP: &kernelTCP, 368 Swappiness: swappinessPtr, 369 DisableOOMKiller: &disableOOMKiller, 370 }, 371 } 372 spec.Linux = &specs.Linux{ 373 CgroupsPath: cgroupsPath, 374 Resources: resources, 375 } 376 377 opts := &CreateOpts{ 378 CgroupName: "ContainerID", 379 UseSystemdCgroup: false, 380 Spec: spec, 381 } 382 383 cgroup, err := CreateCgroupConfig(opts, nil) 384 if err != nil { 385 t.Errorf("Couldn't create Cgroup config: %v", err) 386 } 387 388 if cgroup.Path != cgroupsPath { 389 t.Errorf("Wrong cgroupsPath, expected '%s' got '%s'", cgroupsPath, cgroup.Path) 390 } 391 if cgroup.Resources.Memory != limit { 392 t.Errorf("Expected to have %d as memory limit, got %d", limit, cgroup.Resources.Memory) 393 } 394 if cgroup.Resources.MemoryReservation != reservation { 395 t.Errorf("Expected to have %d as memory reservation, got %d", reservation, cgroup.Resources.MemoryReservation) 396 } 397 if cgroup.Resources.MemorySwap != swap { 398 t.Errorf("Expected to have %d as swap, got %d", swap, cgroup.Resources.MemorySwap) 399 } 400 if cgroup.Resources.MemorySwappiness != swappinessPtr { 401 t.Errorf("Expected to have %d as memory swappiness, got %d", swappinessPtr, cgroup.Resources.MemorySwappiness) 402 } 403 if cgroup.Resources.OomKillDisable != disableOOMKiller { 404 t.Errorf("The OOMKiller should be enabled") 405 } 406 } 407 408 func TestLinuxCgroupSystemd(t *testing.T) { 409 cgroupsPath := "parent:scopeprefix:name" 410 411 spec := &specs.Spec{} 412 spec.Linux = &specs.Linux{ 413 CgroupsPath: cgroupsPath, 414 } 415 416 opts := &CreateOpts{ 417 UseSystemdCgroup: true, 418 Spec: spec, 419 } 420 421 cgroup, err := CreateCgroupConfig(opts, nil) 422 if err != nil { 423 t.Errorf("Couldn't create Cgroup config: %v", err) 424 } 425 426 expectedParent := "parent" 427 if cgroup.Parent != expectedParent { 428 t.Errorf("Expected to have %s as Parent instead of %s", expectedParent, cgroup.Parent) 429 } 430 431 expectedScopePrefix := "scopeprefix" 432 if cgroup.ScopePrefix != expectedScopePrefix { 433 t.Errorf("Expected to have %s as ScopePrefix instead of %s", expectedScopePrefix, cgroup.ScopePrefix) 434 } 435 436 expectedName := "name" 437 if cgroup.Name != expectedName { 438 t.Errorf("Expected to have %s as Name instead of %s", expectedName, cgroup.Name) 439 } 440 } 441 442 func TestLinuxCgroupSystemdWithEmptyPath(t *testing.T) { 443 cgroupsPath := "" 444 445 spec := &specs.Spec{} 446 spec.Linux = &specs.Linux{ 447 CgroupsPath: cgroupsPath, 448 } 449 450 opts := &CreateOpts{ 451 CgroupName: "ContainerID", 452 UseSystemdCgroup: true, 453 Spec: spec, 454 } 455 456 cgroup, err := CreateCgroupConfig(opts, nil) 457 if err != nil { 458 t.Errorf("Couldn't create Cgroup config: %v", err) 459 } 460 461 expectedParent := "" 462 if cgroup.Parent != expectedParent { 463 t.Errorf("Expected to have %s as Parent instead of %s", expectedParent, cgroup.Parent) 464 } 465 466 expectedScopePrefix := "runc" 467 if cgroup.ScopePrefix != expectedScopePrefix { 468 t.Errorf("Expected to have %s as ScopePrefix instead of %s", expectedScopePrefix, cgroup.ScopePrefix) 469 } 470 471 if cgroup.Name != opts.CgroupName { 472 t.Errorf("Expected to have %s as Name instead of %s", opts.CgroupName, cgroup.Name) 473 } 474 } 475 476 func TestLinuxCgroupSystemdWithInvalidPath(t *testing.T) { 477 cgroupsPath := "/user/cgroups/path/id" 478 479 spec := &specs.Spec{} 480 spec.Linux = &specs.Linux{ 481 CgroupsPath: cgroupsPath, 482 } 483 484 opts := &CreateOpts{ 485 CgroupName: "ContainerID", 486 UseSystemdCgroup: true, 487 Spec: spec, 488 } 489 490 _, err := CreateCgroupConfig(opts, nil) 491 if err == nil { 492 t.Error("Expected to produce an error if not using the correct format for cgroup paths belonging to systemd") 493 } 494 } 495 496 func TestLinuxCgroupsPathSpecified(t *testing.T) { 497 cgroupsPath := "/user/cgroups/path/id" 498 499 spec := &specs.Spec{} 500 spec.Linux = &specs.Linux{ 501 CgroupsPath: cgroupsPath, 502 } 503 504 opts := &CreateOpts{ 505 CgroupName: "ContainerID", 506 UseSystemdCgroup: false, 507 Spec: spec, 508 } 509 510 cgroup, err := CreateCgroupConfig(opts, nil) 511 if err != nil { 512 t.Errorf("Couldn't create Cgroup config: %v", err) 513 } 514 515 if cgroup.Path != cgroupsPath { 516 t.Errorf("Wrong cgroupsPath, expected '%s' got '%s'", cgroupsPath, cgroup.Path) 517 } 518 } 519 520 func TestLinuxCgroupsPathNotSpecified(t *testing.T) { 521 spec := &specs.Spec{} 522 opts := &CreateOpts{ 523 CgroupName: "ContainerID", 524 UseSystemdCgroup: false, 525 Spec: spec, 526 } 527 528 cgroup, err := CreateCgroupConfig(opts, nil) 529 if err != nil { 530 t.Errorf("Couldn't create Cgroup config: %v", err) 531 } 532 533 if cgroup.Path != "" { 534 t.Errorf("Wrong cgroupsPath, expected it to be empty string, got '%s'", cgroup.Path) 535 } 536 } 537 538 func TestSpecconvExampleValidate(t *testing.T) { 539 spec := Example() 540 spec.Root.Path = "/" 541 542 opts := &CreateOpts{ 543 CgroupName: "ContainerID", 544 UseSystemdCgroup: false, 545 Spec: spec, 546 } 547 548 config, err := CreateLibcontainerConfig(opts) 549 if err != nil { 550 t.Errorf("Couldn't create libcontainer config: %v", err) 551 } 552 553 if config.NoNewPrivileges != spec.Process.NoNewPrivileges { 554 t.Errorf("specconv NoNewPrivileges mismatch. Expected %v got %v", 555 spec.Process.NoNewPrivileges, config.NoNewPrivileges) 556 } 557 558 if err := validate.Validate(config); err != nil { 559 t.Errorf("Expected specconv to produce valid container config: %v", err) 560 } 561 } 562 563 func TestSpecconvNoLinuxSection(t *testing.T) { 564 spec := Example() 565 spec.Root.Path = "/" 566 spec.Linux = nil 567 spec.Hostname = "" 568 569 opts := &CreateOpts{ 570 CgroupName: "ContainerID", 571 Spec: spec, 572 } 573 574 config, err := CreateLibcontainerConfig(opts) 575 if err != nil { 576 t.Errorf("Couldn't create libcontainer config: %v", err) 577 } 578 579 if err := validate.Validate(config); err != nil { 580 t.Errorf("Expected specconv to produce valid container config: %v", err) 581 } 582 } 583 584 func TestDupNamespaces(t *testing.T) { 585 spec := &specs.Spec{ 586 Root: &specs.Root{ 587 Path: "rootfs", 588 }, 589 Linux: &specs.Linux{ 590 Namespaces: []specs.LinuxNamespace{ 591 { 592 Type: "pid", 593 }, 594 { 595 Type: "pid", 596 Path: "/proc/1/ns/pid", 597 }, 598 }, 599 }, 600 } 601 602 _, err := CreateLibcontainerConfig(&CreateOpts{ 603 Spec: spec, 604 }) 605 606 if !strings.Contains(err.Error(), "malformed spec file: duplicated ns") { 607 t.Errorf("Duplicated namespaces should be forbidden") 608 } 609 } 610 611 func TestUserNamespaceMappingAndPath(t *testing.T) { 612 if _, err := os.Stat("/proc/self/ns/user"); os.IsNotExist(err) { 613 t.Skip("Test requires userns.") 614 } 615 616 spec := &specs.Spec{ 617 Root: &specs.Root{ 618 Path: "rootfs", 619 }, 620 Linux: &specs.Linux{ 621 UIDMappings: []specs.LinuxIDMapping{ 622 {ContainerID: 0, HostID: 1000, Size: 1000}, 623 }, 624 GIDMappings: []specs.LinuxIDMapping{ 625 {ContainerID: 0, HostID: 2000, Size: 1000}, 626 }, 627 Namespaces: []specs.LinuxNamespace{ 628 { 629 Type: "user", 630 Path: "/proc/1/ns/user", 631 }, 632 }, 633 }, 634 } 635 636 _, err := CreateLibcontainerConfig(&CreateOpts{ 637 Spec: spec, 638 }) 639 640 if !strings.Contains(err.Error(), "both namespace path and non-matching mapping specified") { 641 t.Errorf("user namespace with path and non-matching mapping should be forbidden, got error %v", err) 642 } 643 } 644 645 func TestNonZeroEUIDCompatibleSpecconvValidate(t *testing.T) { 646 if _, err := os.Stat("/proc/self/ns/user"); os.IsNotExist(err) { 647 t.Skip("Test requires userns.") 648 } 649 650 spec := Example() 651 spec.Root.Path = "/" 652 ToRootless(spec) 653 654 opts := &CreateOpts{ 655 CgroupName: "ContainerID", 656 UseSystemdCgroup: false, 657 Spec: spec, 658 RootlessEUID: true, 659 RootlessCgroups: true, 660 } 661 662 config, err := CreateLibcontainerConfig(opts) 663 if err != nil { 664 t.Errorf("Couldn't create libcontainer config: %v", err) 665 } 666 667 if err := validate.Validate(config); err != nil { 668 t.Errorf("Expected specconv to produce valid rootless container config: %v", err) 669 } 670 } 671 672 func TestInitSystemdProps(t *testing.T) { 673 type inT struct { 674 name, value string 675 } 676 type expT struct { 677 isErr bool 678 name string 679 value interface{} 680 } 681 682 testCases := []struct { 683 desc string 684 in inT 685 exp expT 686 }{ 687 { 688 in: inT{"org.systemd.property.TimeoutStopUSec", "uint64 123456789"}, 689 exp: expT{false, "TimeoutStopUSec", uint64(123456789)}, 690 }, 691 { 692 desc: "convert USec to Sec (default numeric type)", 693 in: inT{"org.systemd.property.TimeoutStopSec", "456"}, 694 exp: expT{false, "TimeoutStopUSec", uint64(456000000)}, 695 }, 696 { 697 desc: "convert USec to Sec (byte)", 698 in: inT{"org.systemd.property.TimeoutStopSec", "byte 234"}, 699 exp: expT{false, "TimeoutStopUSec", uint64(234000000)}, 700 }, 701 { 702 desc: "convert USec to Sec (int16)", 703 in: inT{"org.systemd.property.TimeoutStopSec", "int16 234"}, 704 exp: expT{false, "TimeoutStopUSec", uint64(234000000)}, 705 }, 706 { 707 desc: "convert USec to Sec (uint16)", 708 in: inT{"org.systemd.property.TimeoutStopSec", "uint16 234"}, 709 exp: expT{false, "TimeoutStopUSec", uint64(234000000)}, 710 }, 711 { 712 desc: "convert USec to Sec (int32)", 713 in: inT{"org.systemd.property.TimeoutStopSec", "int32 234"}, 714 exp: expT{false, "TimeoutStopUSec", uint64(234000000)}, 715 }, 716 { 717 desc: "convert USec to Sec (uint32)", 718 in: inT{"org.systemd.property.TimeoutStopSec", "uint32 234"}, 719 exp: expT{false, "TimeoutStopUSec", uint64(234000000)}, 720 }, 721 { 722 desc: "convert USec to Sec (int64)", 723 in: inT{"org.systemd.property.TimeoutStopSec", "int64 234"}, 724 exp: expT{false, "TimeoutStopUSec", uint64(234000000)}, 725 }, 726 { 727 desc: "convert USec to Sec (uint64)", 728 in: inT{"org.systemd.property.TimeoutStopSec", "uint64 234"}, 729 exp: expT{false, "TimeoutStopUSec", uint64(234000000)}, 730 }, 731 { 732 desc: "convert USec to Sec (float)", 733 in: inT{"org.systemd.property.TimeoutStopSec", "234.789"}, 734 exp: expT{false, "TimeoutStopUSec", uint64(234789000)}, 735 }, 736 { 737 desc: "convert USec to Sec (bool -- invalid value)", 738 in: inT{"org.systemd.property.TimeoutStopSec", "false"}, 739 exp: expT{true, "", ""}, 740 }, 741 { 742 desc: "convert USec to Sec (string -- invalid value)", 743 in: inT{"org.systemd.property.TimeoutStopSec", "'covfefe'"}, 744 exp: expT{true, "", ""}, 745 }, 746 { 747 desc: "convert USec to Sec (bad variable name, no conversion)", 748 in: inT{"org.systemd.property.FOOSec", "123"}, 749 exp: expT{false, "FOOSec", 123}, 750 }, 751 { 752 in: inT{"org.systemd.property.CollectMode", "'inactive-or-failed'"}, 753 exp: expT{false, "CollectMode", "inactive-or-failed"}, 754 }, 755 { 756 desc: "unrelated property", 757 in: inT{"some.other.annotation", "0"}, 758 exp: expT{false, "", ""}, 759 }, 760 { 761 desc: "too short property name", 762 in: inT{"org.systemd.property.Xo", "1"}, 763 exp: expT{true, "", ""}, 764 }, 765 { 766 desc: "invalid character in property name", 767 in: inT{"org.systemd.property.Number1", "1"}, 768 exp: expT{true, "", ""}, 769 }, 770 { 771 desc: "invalid property value", 772 in: inT{"org.systemd.property.ValidName", "invalid-value"}, 773 exp: expT{true, "", ""}, 774 }, 775 } 776 777 spec := &specs.Spec{} 778 779 for _, tc := range testCases { 780 tc := tc 781 spec.Annotations = map[string]string{tc.in.name: tc.in.value} 782 783 outMap, err := initSystemdProps(spec) 784 // t.Logf("input %+v, expected %+v, got err:%v out:%+v", tc.in, tc.exp, err, outMap) 785 786 if tc.exp.isErr != (err != nil) { 787 t.Errorf("input %+v, expecting error: %v, got %v", tc.in, tc.exp.isErr, err) 788 } 789 expLen := 1 // expect a single item 790 if tc.exp.name == "" { 791 expLen = 0 // expect nothing 792 } 793 if len(outMap) != expLen { 794 t.Fatalf("input %+v, expected %d, got %d entries: %v", tc.in, expLen, len(outMap), outMap) 795 } 796 if expLen == 0 { 797 continue 798 } 799 800 out := outMap[0] 801 if tc.exp.name != out.Name { 802 t.Errorf("input %+v, expecting name: %q, got %q", tc.in, tc.exp.name, out.Name) 803 } 804 expValue := dbus.MakeVariant(tc.exp.value).String() 805 if expValue != out.Value.String() { 806 t.Errorf("input %+v, expecting value: %s, got %s", tc.in, expValue, out.Value) 807 } 808 } 809 } 810 811 func TestCheckPropertyName(t *testing.T) { 812 testCases := []struct { 813 in string 814 valid bool 815 }{ 816 {"", false}, // too short 817 {"xx", false}, // too short 818 {"xxx", true}, 819 {"someValidName", true}, 820 {"A name", false}, // space 821 {"3335", false}, // numbers 822 {"Name1", false}, // numbers 823 {"Кир", false}, // non-ascii 824 {"მადლობა", false}, // non-ascii 825 {"合い言葉", false}, // non-ascii 826 } 827 828 for _, tc := range testCases { 829 err := checkPropertyName(tc.in) 830 if (err == nil) != tc.valid { 831 t.Errorf("case %q: expected valid: %v, got error: %v", tc.in, tc.valid, err) 832 } 833 } 834 } 835 836 func BenchmarkCheckPropertyName(b *testing.B) { 837 for i := 0; i < b.N; i++ { 838 for _, s := range []string{"", "xx", "xxx", "someValidName", "A name", "Кир", "მადლობა", "合い言葉"} { 839 _ = checkPropertyName(s) 840 } 841 } 842 } 843 844 func TestNullProcess(t *testing.T) { 845 spec := Example() 846 spec.Process = nil 847 848 _, err := CreateLibcontainerConfig(&CreateOpts{ 849 Spec: spec, 850 }) 851 if err != nil { 852 t.Errorf("Null process should be forbidden") 853 } 854 } 855 856 func TestCreateDevices(t *testing.T) { 857 spec := Example() 858 859 // dummy uid/gid for /dev/tty; will enable the test to check if createDevices() 860 // preferred the spec's device over the redundant default device 861 ttyUid := uint32(1000) 862 ttyGid := uint32(1000) 863 fm := os.FileMode(0o666) 864 865 spec.Linux = &specs.Linux{ 866 Devices: []specs.LinuxDevice{ 867 { 868 // This is purposely redundant with one of runc's default devices 869 Path: "/dev/tty", 870 Type: "c", 871 Major: 5, 872 Minor: 0, 873 FileMode: &fm, 874 UID: &ttyUid, 875 GID: &ttyGid, 876 }, 877 { 878 // This is purposely not redundant with one of runc's default devices 879 Path: "/dev/ram0", 880 Type: "b", 881 Major: 1, 882 Minor: 0, 883 }, 884 }, 885 } 886 887 conf := &configs.Config{} 888 889 defaultDevs, err := createDevices(spec, conf) 890 if err != nil { 891 t.Errorf("failed to create devices: %v", err) 892 } 893 894 // Verify the returned default devices has the /dev/tty entry deduplicated 895 found := false 896 for _, d := range defaultDevs { 897 if d.Path == "/dev/tty" { 898 if found { 899 t.Errorf("createDevices failed: returned a duplicated device entry: %v", defaultDevs) 900 } 901 found = true 902 } 903 } 904 905 // Verify that createDevices() placed all default devices in the config 906 for _, allowedDev := range AllowedDevices { 907 if allowedDev.Path == "" { 908 continue 909 } 910 911 found := false 912 for _, configDev := range conf.Devices { 913 if configDev.Path == allowedDev.Path { 914 found = true 915 } 916 } 917 if !found { 918 configDevPaths := []string{} 919 for _, configDev := range conf.Devices { 920 configDevPaths = append(configDevPaths, configDev.Path) 921 } 922 t.Errorf("allowedDevice %s was not found in the config's devices: %v", allowedDev.Path, configDevPaths) 923 } 924 } 925 926 // Verify that createDevices() deduplicated the /dev/tty entry in the config 927 for _, configDev := range conf.Devices { 928 if configDev.Path == "/dev/tty" { 929 wantDev := &devices.Device{ 930 Path: "/dev/tty", 931 FileMode: 0o666, 932 Uid: 1000, 933 Gid: 1000, 934 Rule: devices.Rule{ 935 Type: devices.CharDevice, 936 Major: 5, 937 Minor: 0, 938 }, 939 } 940 941 if *configDev != *wantDev { 942 t.Errorf("redundant dev was not deduplicated correctly: want %v, got %v", wantDev, configDev) 943 } 944 } 945 } 946 947 // Verify that createDevices() added the entry for /dev/ram0 in the config 948 found = false 949 for _, configDev := range conf.Devices { 950 if configDev.Path == "/dev/ram0" { 951 found = true 952 break 953 } 954 } 955 if !found { 956 t.Errorf("device /dev/ram0 not found in config devices; got %v", conf.Devices) 957 } 958 }