...

Source file src/github.com/opencontainers/runc/libcontainer/specconv/spec_linux_test.go

Documentation: github.com/opencontainers/runc/libcontainer/specconv

     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  	validator := validate.New()
   559  	if err := validator.Validate(config); err != nil {
   560  		t.Errorf("Expected specconv to produce valid container config: %v", err)
   561  	}
   562  }
   563  
   564  func TestSpecconvNoLinuxSection(t *testing.T) {
   565  	spec := Example()
   566  	spec.Root.Path = "/"
   567  	spec.Linux = nil
   568  	spec.Hostname = ""
   569  
   570  	opts := &CreateOpts{
   571  		CgroupName: "ContainerID",
   572  		Spec:       spec,
   573  	}
   574  
   575  	config, err := CreateLibcontainerConfig(opts)
   576  	if err != nil {
   577  		t.Errorf("Couldn't create libcontainer config: %v", err)
   578  	}
   579  
   580  	validator := validate.New()
   581  	if err := validator.Validate(config); err != nil {
   582  		t.Errorf("Expected specconv to produce valid container config: %v", err)
   583  	}
   584  }
   585  
   586  func TestDupNamespaces(t *testing.T) {
   587  	spec := &specs.Spec{
   588  		Root: &specs.Root{
   589  			Path: "rootfs",
   590  		},
   591  		Linux: &specs.Linux{
   592  			Namespaces: []specs.LinuxNamespace{
   593  				{
   594  					Type: "pid",
   595  				},
   596  				{
   597  					Type: "pid",
   598  					Path: "/proc/1/ns/pid",
   599  				},
   600  			},
   601  		},
   602  	}
   603  
   604  	_, err := CreateLibcontainerConfig(&CreateOpts{
   605  		Spec: spec,
   606  	})
   607  
   608  	if !strings.Contains(err.Error(), "malformed spec file: duplicated ns") {
   609  		t.Errorf("Duplicated namespaces should be forbidden")
   610  	}
   611  }
   612  
   613  func TestUserNamespaceMappingAndPath(t *testing.T) {
   614  	if _, err := os.Stat("/proc/self/ns/user"); os.IsNotExist(err) {
   615  		t.Skip("Test requires userns.")
   616  	}
   617  
   618  	spec := &specs.Spec{
   619  		Root: &specs.Root{
   620  			Path: "rootfs",
   621  		},
   622  		Linux: &specs.Linux{
   623  			UIDMappings: []specs.LinuxIDMapping{
   624  				{ContainerID: 0, HostID: 1000, Size: 1000},
   625  			},
   626  			GIDMappings: []specs.LinuxIDMapping{
   627  				{ContainerID: 0, HostID: 2000, Size: 1000},
   628  			},
   629  			Namespaces: []specs.LinuxNamespace{
   630  				{
   631  					Type: "user",
   632  					Path: "/proc/1/ns/user",
   633  				},
   634  			},
   635  		},
   636  	}
   637  
   638  	_, err := CreateLibcontainerConfig(&CreateOpts{
   639  		Spec: spec,
   640  	})
   641  
   642  	if !strings.Contains(err.Error(), "both namespace path and non-matching mapping specified") {
   643  		t.Errorf("user namespace with path and non-matching mapping should be forbidden, got error %v", err)
   644  	}
   645  }
   646  
   647  func TestNonZeroEUIDCompatibleSpecconvValidate(t *testing.T) {
   648  	if _, err := os.Stat("/proc/self/ns/user"); os.IsNotExist(err) {
   649  		t.Skip("Test requires userns.")
   650  	}
   651  
   652  	spec := Example()
   653  	spec.Root.Path = "/"
   654  	ToRootless(spec)
   655  
   656  	opts := &CreateOpts{
   657  		CgroupName:       "ContainerID",
   658  		UseSystemdCgroup: false,
   659  		Spec:             spec,
   660  		RootlessEUID:     true,
   661  		RootlessCgroups:  true,
   662  	}
   663  
   664  	config, err := CreateLibcontainerConfig(opts)
   665  	if err != nil {
   666  		t.Errorf("Couldn't create libcontainer config: %v", err)
   667  	}
   668  
   669  	validator := validate.New()
   670  	if err := validator.Validate(config); err != nil {
   671  		t.Errorf("Expected specconv to produce valid rootless container config: %v", err)
   672  	}
   673  }
   674  
   675  func TestInitSystemdProps(t *testing.T) {
   676  	type inT struct {
   677  		name, value string
   678  	}
   679  	type expT struct {
   680  		isErr bool
   681  		name  string
   682  		value interface{}
   683  	}
   684  
   685  	testCases := []struct {
   686  		desc string
   687  		in   inT
   688  		exp  expT
   689  	}{
   690  		{
   691  			in:  inT{"org.systemd.property.TimeoutStopUSec", "uint64 123456789"},
   692  			exp: expT{false, "TimeoutStopUSec", uint64(123456789)},
   693  		},
   694  		{
   695  			desc: "convert USec to Sec (default numeric type)",
   696  			in:   inT{"org.systemd.property.TimeoutStopSec", "456"},
   697  			exp:  expT{false, "TimeoutStopUSec", uint64(456000000)},
   698  		},
   699  		{
   700  			desc: "convert USec to Sec (byte)",
   701  			in:   inT{"org.systemd.property.TimeoutStopSec", "byte 234"},
   702  			exp:  expT{false, "TimeoutStopUSec", uint64(234000000)},
   703  		},
   704  		{
   705  			desc: "convert USec to Sec (int16)",
   706  			in:   inT{"org.systemd.property.TimeoutStopSec", "int16 234"},
   707  			exp:  expT{false, "TimeoutStopUSec", uint64(234000000)},
   708  		},
   709  		{
   710  			desc: "convert USec to Sec (uint16)",
   711  			in:   inT{"org.systemd.property.TimeoutStopSec", "uint16 234"},
   712  			exp:  expT{false, "TimeoutStopUSec", uint64(234000000)},
   713  		},
   714  		{
   715  			desc: "convert USec to Sec (int32)",
   716  			in:   inT{"org.systemd.property.TimeoutStopSec", "int32 234"},
   717  			exp:  expT{false, "TimeoutStopUSec", uint64(234000000)},
   718  		},
   719  		{
   720  			desc: "convert USec to Sec (uint32)",
   721  			in:   inT{"org.systemd.property.TimeoutStopSec", "uint32 234"},
   722  			exp:  expT{false, "TimeoutStopUSec", uint64(234000000)},
   723  		},
   724  		{
   725  			desc: "convert USec to Sec (int64)",
   726  			in:   inT{"org.systemd.property.TimeoutStopSec", "int64 234"},
   727  			exp:  expT{false, "TimeoutStopUSec", uint64(234000000)},
   728  		},
   729  		{
   730  			desc: "convert USec to Sec (uint64)",
   731  			in:   inT{"org.systemd.property.TimeoutStopSec", "uint64 234"},
   732  			exp:  expT{false, "TimeoutStopUSec", uint64(234000000)},
   733  		},
   734  		{
   735  			desc: "convert USec to Sec (float)",
   736  			in:   inT{"org.systemd.property.TimeoutStopSec", "234.789"},
   737  			exp:  expT{false, "TimeoutStopUSec", uint64(234789000)},
   738  		},
   739  		{
   740  			desc: "convert USec to Sec (bool -- invalid value)",
   741  			in:   inT{"org.systemd.property.TimeoutStopSec", "false"},
   742  			exp:  expT{true, "", ""},
   743  		},
   744  		{
   745  			desc: "convert USec to Sec (string -- invalid value)",
   746  			in:   inT{"org.systemd.property.TimeoutStopSec", "'covfefe'"},
   747  			exp:  expT{true, "", ""},
   748  		},
   749  		{
   750  			desc: "convert USec to Sec (bad variable name, no conversion)",
   751  			in:   inT{"org.systemd.property.FOOSec", "123"},
   752  			exp:  expT{false, "FOOSec", 123},
   753  		},
   754  		{
   755  			in:  inT{"org.systemd.property.CollectMode", "'inactive-or-failed'"},
   756  			exp: expT{false, "CollectMode", "inactive-or-failed"},
   757  		},
   758  		{
   759  			desc: "unrelated property",
   760  			in:   inT{"some.other.annotation", "0"},
   761  			exp:  expT{false, "", ""},
   762  		},
   763  		{
   764  			desc: "too short property name",
   765  			in:   inT{"org.systemd.property.Xo", "1"},
   766  			exp:  expT{true, "", ""},
   767  		},
   768  		{
   769  			desc: "invalid character in property name",
   770  			in:   inT{"org.systemd.property.Number1", "1"},
   771  			exp:  expT{true, "", ""},
   772  		},
   773  		{
   774  			desc: "invalid property value",
   775  			in:   inT{"org.systemd.property.ValidName", "invalid-value"},
   776  			exp:  expT{true, "", ""},
   777  		},
   778  	}
   779  
   780  	spec := &specs.Spec{}
   781  
   782  	for _, tc := range testCases {
   783  		tc := tc
   784  		spec.Annotations = map[string]string{tc.in.name: tc.in.value}
   785  
   786  		outMap, err := initSystemdProps(spec)
   787  		// t.Logf("input %+v, expected %+v, got err:%v out:%+v", tc.in, tc.exp, err, outMap)
   788  
   789  		if tc.exp.isErr != (err != nil) {
   790  			t.Errorf("input %+v, expecting error: %v, got %v", tc.in, tc.exp.isErr, err)
   791  		}
   792  		expLen := 1 // expect a single item
   793  		if tc.exp.name == "" {
   794  			expLen = 0 // expect nothing
   795  		}
   796  		if len(outMap) != expLen {
   797  			t.Fatalf("input %+v, expected %d, got %d entries: %v", tc.in, expLen, len(outMap), outMap)
   798  		}
   799  		if expLen == 0 {
   800  			continue
   801  		}
   802  
   803  		out := outMap[0]
   804  		if tc.exp.name != out.Name {
   805  			t.Errorf("input %+v, expecting name: %q, got %q", tc.in, tc.exp.name, out.Name)
   806  		}
   807  		expValue := dbus.MakeVariant(tc.exp.value).String()
   808  		if expValue != out.Value.String() {
   809  			t.Errorf("input %+v, expecting value: %s, got %s", tc.in, expValue, out.Value)
   810  		}
   811  	}
   812  }
   813  
   814  func TestIsValidName(t *testing.T) {
   815  	testCases := []struct {
   816  		in    string
   817  		valid bool
   818  	}{
   819  		{"", false},   // too short
   820  		{"xx", false}, // too short
   821  		{"xxx", true},
   822  		{"someValidName", true},
   823  		{"A name", false},  // space
   824  		{"3335", false},    // numbers
   825  		{"Name1", false},   // numbers
   826  		{"Кир", false},     // non-ascii
   827  		{"მადლობა", false}, // non-ascii
   828  		{"合い言葉", false},    // non-ascii
   829  	}
   830  
   831  	for _, tc := range testCases {
   832  		err := checkPropertyName(tc.in)
   833  		if (err == nil) != tc.valid {
   834  			t.Errorf("case %q: expected valid: %v, got error: %v", tc.in, tc.valid, err)
   835  		}
   836  	}
   837  }
   838  
   839  func BenchmarkIsValidName(b *testing.B) {
   840  	for i := 0; i < b.N; i++ {
   841  		for _, s := range []string{"", "xx", "xxx", "someValidName", "A name", "Кир", "მადლობა", "合い言葉"} {
   842  			_ = checkPropertyName(s)
   843  		}
   844  	}
   845  }
   846  
   847  func TestNullProcess(t *testing.T) {
   848  	spec := Example()
   849  	spec.Process = nil
   850  
   851  	_, err := CreateLibcontainerConfig(&CreateOpts{
   852  		Spec: spec,
   853  	})
   854  	if err != nil {
   855  		t.Errorf("Null process should be forbidden")
   856  	}
   857  }
   858  
   859  func TestCreateDevices(t *testing.T) {
   860  	spec := Example()
   861  
   862  	// dummy uid/gid for /dev/tty; will enable the test to check if createDevices()
   863  	// preferred the spec's device over the redundant default device
   864  	ttyUid := uint32(1000)
   865  	ttyGid := uint32(1000)
   866  	fm := os.FileMode(0o666)
   867  
   868  	spec.Linux = &specs.Linux{
   869  		Devices: []specs.LinuxDevice{
   870  			{
   871  				// This is purposely redundant with one of runc's default devices
   872  				Path:     "/dev/tty",
   873  				Type:     "c",
   874  				Major:    5,
   875  				Minor:    0,
   876  				FileMode: &fm,
   877  				UID:      &ttyUid,
   878  				GID:      &ttyGid,
   879  			},
   880  			{
   881  				// This is purposely not redundant with one of runc's default devices
   882  				Path:  "/dev/ram0",
   883  				Type:  "b",
   884  				Major: 1,
   885  				Minor: 0,
   886  			},
   887  		},
   888  	}
   889  
   890  	conf := &configs.Config{}
   891  
   892  	defaultDevs, err := createDevices(spec, conf)
   893  	if err != nil {
   894  		t.Errorf("failed to create devices: %v", err)
   895  	}
   896  
   897  	// Verify the returned default devices has the /dev/tty entry deduplicated
   898  	found := false
   899  	for _, d := range defaultDevs {
   900  		if d.Path == "/dev/tty" {
   901  			if found {
   902  				t.Errorf("createDevices failed: returned a duplicated device entry: %v", defaultDevs)
   903  			}
   904  			found = true
   905  		}
   906  	}
   907  
   908  	// Verify that createDevices() placed all default devices in the config
   909  	for _, allowedDev := range AllowedDevices {
   910  		if allowedDev.Path == "" {
   911  			continue
   912  		}
   913  
   914  		found := false
   915  		for _, configDev := range conf.Devices {
   916  			if configDev.Path == allowedDev.Path {
   917  				found = true
   918  			}
   919  		}
   920  		if !found {
   921  			configDevPaths := []string{}
   922  			for _, configDev := range conf.Devices {
   923  				configDevPaths = append(configDevPaths, configDev.Path)
   924  			}
   925  			t.Errorf("allowedDevice %s was not found in the config's devices: %v", allowedDev.Path, configDevPaths)
   926  		}
   927  	}
   928  
   929  	// Verify that createDevices() deduplicated the /dev/tty entry in the config
   930  	for _, configDev := range conf.Devices {
   931  		if configDev.Path == "/dev/tty" {
   932  			wantDev := &devices.Device{
   933  				Path:     "/dev/tty",
   934  				FileMode: 0o666,
   935  				Uid:      1000,
   936  				Gid:      1000,
   937  				Rule: devices.Rule{
   938  					Type:  devices.CharDevice,
   939  					Major: 5,
   940  					Minor: 0,
   941  				},
   942  			}
   943  
   944  			if *configDev != *wantDev {
   945  				t.Errorf("redundant dev was not deduplicated correctly: want %v, got %v", wantDev, configDev)
   946  			}
   947  		}
   948  	}
   949  
   950  	// Verify that createDevices() added the entry for /dev/ram0 in the config
   951  	found = false
   952  	for _, configDev := range conf.Devices {
   953  		if configDev.Path == "/dev/ram0" {
   954  			found = true
   955  			break
   956  		}
   957  	}
   958  	if !found {
   959  		t.Errorf("device /dev/ram0 not found in config devices; got %v", conf.Devices)
   960  	}
   961  }
   962  

View as plain text