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  }