diff --git a/src/runtime/virtcontainers/nsenter.go b/src/runtime/virtcontainers/nsenter.go deleted file mode 100644 index d89b55ac92..0000000000 --- a/src/runtime/virtcontainers/nsenter.go +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) 2016 Intel Corporation -// -// SPDX-License-Identifier: Apache-2.0 -// - -package virtcontainers - -// nsenter is a spawner implementation for the nsenter util-linux command. -type nsenter struct { - ContConfig ContainerConfig -} - -const ( - // NsenterCmd is the command used to start nsenter. - nsenterCmd = "nsenter" -) - -// formatArgs is the spawner command formatting implementation for nsenter. -func (n *nsenter) formatArgs(args []string) ([]string, error) { - var newArgs []string - pid := "-1" - - // TODO: Retrieve container PID from container ID - - newArgs = append(newArgs, nsenterCmd+" --target "+pid+" --mount --uts --ipc --net --pid") - newArgs = append(newArgs, args...) - - return newArgs, nil -} diff --git a/src/runtime/virtcontainers/nsenter_test.go b/src/runtime/virtcontainers/nsenter_test.go deleted file mode 100644 index 5f58c627e6..0000000000 --- a/src/runtime/virtcontainers/nsenter_test.go +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) 2016 Intel Corporation -// -// SPDX-License-Identifier: Apache-2.0 -// - -package virtcontainers - -import ( - "strings" - "testing" - - "github.com/stretchr/testify/assert" -) - -func testNsEnterFormatArgs(t *testing.T, args []string, expected string) { - nsenter := &nsenter{} - - cmd, err := nsenter.formatArgs(args) - assert.NoError(t, err) - assert.Equal(t, strings.Join(cmd, " "), expected) -} - -func TestNsEnterFormatArgsHello(t *testing.T) { - expectedCmd := "nsenter --target -1 --mount --uts --ipc --net --pid echo hello" - - args := []string{"echo", "hello"} - - testNsEnterFormatArgs(t, args, expectedCmd) -} diff --git a/src/runtime/virtcontainers/pkg/nsenter/nsenter.go b/src/runtime/virtcontainers/pkg/nsenter/nsenter.go index 8ae666408b..1a9d10d3e0 100644 --- a/src/runtime/virtcontainers/pkg/nsenter/nsenter.go +++ b/src/runtime/virtcontainers/pkg/nsenter/nsenter.go @@ -6,29 +6,6 @@ package nsenter -import ( - "fmt" - "os" - "path/filepath" - "runtime" - "strconv" - "sync" - "syscall" - - "golang.org/x/sys/unix" -) - -// Filesystems constants. -const ( - // https://github.com/torvalds/linux/blob/master/include/uapi/linux/magic.h - nsFSMagic = 0x6e736673 - procFSMagic = 0x9fa0 - - procRootPath = "/proc" - nsDirPath = "ns" - taskDirPath = "task" -) - // NSType defines a namespace type. type NSType string @@ -47,147 +24,8 @@ const ( NSTypeUTS NSType = "uts" ) -// CloneFlagsTable is exported so that consumers of this package don't need -// to define this same table again. -var CloneFlagsTable = make(map[NSType]int) - -// Namespace describes a namespace that will be entered. type Namespace struct { Path string PID int Type NSType } - -type nsPair struct { - targetNS *os.File - threadNS *os.File -} - -func init() { - var ns = map[NSType]int{ - NSTypeCGroup: unix.CLONE_NEWCGROUP, - NSTypeIPC: unix.CLONE_NEWIPC, - NSTypeNet: unix.CLONE_NEWNET, - NSTypePID: unix.CLONE_NEWPID, - NSTypeUTS: unix.CLONE_NEWUTS, - } - - for k, v := range ns { - if _, err := os.Stat(fmt.Sprint("/proc/self/ns/", string(k))); err == nil { - CloneFlagsTable[k] = v - } - } -} - -func getNSPathFromPID(pid int, nsType NSType) string { - return filepath.Join(procRootPath, strconv.Itoa(pid), nsDirPath, string(nsType)) -} - -func getCurrentThreadNSPath(nsType NSType) string { - return filepath.Join(procRootPath, strconv.Itoa(os.Getpid()), - taskDirPath, strconv.Itoa(unix.Gettid()), nsDirPath, string(nsType)) -} - -func setNS(nsFile *os.File, nsType NSType) error { - if nsFile == nil { - return fmt.Errorf("File handler cannot be nil") - } - - nsFlag, exist := CloneFlagsTable[nsType] - if !exist { - return fmt.Errorf("Unknown namespace type %q", nsType) - } - - if err := unix.Setns(int(nsFile.Fd()), nsFlag); err != nil { - return fmt.Errorf("Error switching to ns %v: %v", nsFile.Name(), err) - } - - return nil -} - -// getFileFromNS checks the provided file path actually matches a real -// namespace filesystem, and then opens it to return a handler to this -// file. This is needed since the system call setns() expects a file -// descriptor to enter the given namespace. -func getFileFromNS(nsPath string) (*os.File, error) { - stat := syscall.Statfs_t{} - if err := syscall.Statfs(nsPath, &stat); err != nil { - return nil, fmt.Errorf("failed to Statfs %q: %v", nsPath, err) - } - - switch stat.Type { - case nsFSMagic, procFSMagic: - break - default: - return nil, fmt.Errorf("unknown FS magic on %q: %x", nsPath, stat.Type) - } - - file, err := os.Open(nsPath) - if err != nil { - return nil, err - } - - return file, nil -} - -// NsEnter executes the passed closure under the given namespace, -// restoring the original namespace afterwards. -func NsEnter(nsList []Namespace, toRun func() error) error { - targetNSList := make(map[NSType]*nsPair) - - // Open all targeted namespaces. - for _, ns := range nsList { - targetNSPath := ns.Path - if targetNSPath == "" { - targetNSPath = getNSPathFromPID(ns.PID, ns.Type) - } - - targetNS, err := getFileFromNS(targetNSPath) - if err != nil { - return fmt.Errorf("failed to open target ns: %v", err) - } - defer targetNS.Close() - - targetNSList[ns.Type] = &nsPair{ - targetNS: targetNS, - } - } - - containedCall := func() error { - for nsType := range targetNSList { - threadNS, err := getFileFromNS(getCurrentThreadNSPath(nsType)) - if err != nil { - return fmt.Errorf("failed to open current ns: %v", err) - } - defer threadNS.Close() - - targetNSList[nsType].threadNS = threadNS - } - - // Switch to namespaces all at once. - for nsType, pair := range targetNSList { - // Switch to targeted namespace. - if err := setNS(pair.targetNS, nsType); err != nil { - return fmt.Errorf("error switching to ns %v: %v", pair.targetNS.Name(), err) - } - // Switch back to initial namespace after closure return. - defer setNS(pair.threadNS, nsType) - } - - return toRun() - } - - var wg sync.WaitGroup - wg.Add(1) - - var innerError error - go func() { - defer wg.Done() - runtime.LockOSThread() - defer runtime.UnlockOSThread() - innerError = containedCall() - }() - wg.Wait() - - return innerError -} diff --git a/src/runtime/virtcontainers/pkg/nsenter/nsenter_test.go b/src/runtime/virtcontainers/pkg/nsenter/nsenter_test.go deleted file mode 100644 index 672d744c71..0000000000 --- a/src/runtime/virtcontainers/pkg/nsenter/nsenter_test.go +++ /dev/null @@ -1,240 +0,0 @@ -// Copyright (c) 2018 Intel Corporation -// -// SPDX-License-Identifier: Apache-2.0 -// - -package nsenter - -import ( - "fmt" - "io/ioutil" - "os" - "os/exec" - "path/filepath" - "runtime" - "strconv" - "syscall" - "testing" - - "github.com/stretchr/testify/assert" - "golang.org/x/sys/unix" - - ktu "github.com/kata-containers/kata-containers/src/runtime/pkg/katatestutils" -) - -const testPID = 12345 - -var tu = ktu.NewTestConstraint(true) - -func TestGetNSPathFromPID(t *testing.T) { - for nsType := range CloneFlagsTable { - expectedPath := fmt.Sprintf("/proc/%d/ns/%s", testPID, nsType) - path := getNSPathFromPID(testPID, nsType) - assert.Equal(t, path, expectedPath) - } -} - -func TestGetCurrentThreadNSPath(t *testing.T) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - - currentPID := os.Getpid() - currentTID := unix.Gettid() - for nsType := range CloneFlagsTable { - expectedPath := fmt.Sprintf("/proc/%d/task/%d/ns/%s", currentPID, currentTID, nsType) - path := getCurrentThreadNSPath(nsType) - assert.Equal(t, path, expectedPath) - } -} - -func TestGetFileFromNSEmptyNSPathFailure(t *testing.T) { - nsFile, err := getFileFromNS("") - assert.NotNil(t, err, "Empty path should result as a failure") - assert.Nil(t, nsFile, "The file handler returned should be nil") -} - -func TestGetFileFromNSNotExistingNSPathFailure(t *testing.T) { - nsFile, err := ioutil.TempFile("", "not-existing-ns-path") - assert.NoError(t, err) - nsFilePath := nsFile.Name() - nsFile.Close() - - assert.NoError(t, os.Remove(nsFilePath)) - - nsFile, err = getFileFromNS(nsFilePath) - assert.NotNil(t, err, "Not existing path should result as a failure") - assert.Nil(t, nsFile, "The file handler returned should be nil") -} - -func TestGetFileFromNSWrongNSPathFailure(t *testing.T) { - nsFile, err := ioutil.TempFile("", "wrong-ns-path") - assert.NoError(t, err) - nsFilePath := nsFile.Name() - nsFile.Close() - - defer os.Remove(nsFilePath) - - nsFile, err = getFileFromNS(nsFilePath) - assert.NotNil(t, err, "Should fail because wrong filesystem") - assert.Nil(t, nsFile, "The file handler returned should be nil") -} - -func TestGetFileFromNSSuccessful(t *testing.T) { - for nsType := range CloneFlagsTable { - nsFilePath := fmt.Sprintf("/proc/self/ns/%s", string(nsType)) - nsFile, err := getFileFromNS(nsFilePath) - assert.Nil(t, err, "Should have succeeded: %v", err) - assert.NotNil(t, nsFile, "The file handler should not be nil") - if nsFile != nil { - nsFile.Close() - } - } -} - -func startSleepBinary(duration int, cloneFlags int) (int, error) { - sleepBinName := "sleep" - sleepPath, err := exec.LookPath(sleepBinName) - if err != nil { - return -1, fmt.Errorf("Could not find %q: %v", sleepBinName, err) - } - - cmd := exec.Command(sleepPath, strconv.Itoa(duration)) - cmd.SysProcAttr = &syscall.SysProcAttr{ - Cloneflags: uintptr(cloneFlags), - } - - if err := cmd.Start(); err != nil { - return -1, err - } - - return cmd.Process.Pid, nil -} - -func TestSetNSNilFileHandlerFailure(t *testing.T) { - err := setNS(nil, "") - assert.NotNil(t, err, "Should fail because file handler is nil") -} - -func TestSetNSUnknownNSTypeFailure(t *testing.T) { - file := &os.File{} - err := setNS(file, "") - assert.NotNil(t, err, "Should fail because unknown ns type") -} - -func TestSetNSWrongFileFailure(t *testing.T) { - nsFile, err := ioutil.TempFile("", "wrong-ns-path") - assert.NoError(t, err) - defer func() { - nsFilePath := nsFile.Name() - nsFile.Close() - os.Remove(nsFilePath) - }() - - err = setNS(nsFile, NSTypeIPC) - assert.NotNil(t, err, "Should fail because file is not a namespace") -} - -func supportedNamespaces() []Namespace { - var list []Namespace - var ns = []Namespace{ - {Type: NSTypeCGroup}, - {Type: NSTypeIPC}, - {Type: NSTypeNet}, - {Type: NSTypePID}, - {Type: NSTypeUTS}, - } - - for _, n := range ns { - if _, err := os.Stat(fmt.Sprint("/proc/self/ns/", string(n.Type))); err == nil { - list = append(list, n) - } - } - - return list -} - -func testToRunNil() error { - return nil -} - -func TestNsEnterEmptyPathAndPIDFromNSListFailure(t *testing.T) { - err := NsEnter(supportedNamespaces(), testToRunNil) - assert.NotNil(t, err, "Should fail because neither a path nor a PID"+ - " has been provided by every namespace of the list") -} - -func TestNsEnterEmptyNamespaceListSuccess(t *testing.T) { - err := NsEnter([]Namespace{}, testToRunNil) - assert.Nil(t, err, "Should not fail since closure should return nil: %v", err) -} - -func TestNsEnterSuccessful(t *testing.T) { - if tu.NotValid(ktu.NeedRoot()) { - t.Skip(ktu.TestDisabledNeedRoot) - } - nsList := supportedNamespaces() - sleepDuration := 60 - - cloneFlags := 0 - for _, ns := range nsList { - cloneFlags |= CloneFlagsTable[ns.Type] - } - - sleepPID, err := startSleepBinary(sleepDuration, cloneFlags) - assert.NoError(t, err) - defer func() { - if sleepPID > 1 { - unix.Kill(sleepPID, syscall.SIGKILL) - } - }() - - for idx := range nsList { - nsList[idx].Path = getNSPathFromPID(sleepPID, nsList[idx].Type) - nsList[idx].PID = sleepPID - } - - var sleepPIDFromNsEnter int - - testToRun := func() error { - sleepPIDFromNsEnter, err = startSleepBinary(sleepDuration, 0) - if err != nil { - return err - } - - return nil - } - - err = NsEnter(nsList, testToRun) - assert.Nil(t, err, "%v", err) - - defer func() { - if sleepPIDFromNsEnter > 1 { - unix.Kill(sleepPIDFromNsEnter, syscall.SIGKILL) - } - }() - - for _, ns := range nsList { - nsPathEntered := getNSPathFromPID(sleepPIDFromNsEnter, ns.Type) - - // Here we are trying to resolve the path but it fails because - // namespaces links don't really exist. For this reason, the - // call to EvalSymlinks will fail when it will try to stat the - // resolved path found. As we only care about the path, we can - // retrieve it from the PathError structure. - evalExpectedNSPath, err := filepath.EvalSymlinks(ns.Path) - if err != nil { - evalExpectedNSPath = err.(*os.PathError).Path - } - - // Same thing here, resolving the namespace path. - evalNSEnteredPath, err := filepath.EvalSymlinks(nsPathEntered) - if err != nil { - evalNSEnteredPath = err.(*os.PathError).Path - } - - _, evalExpectedNS := filepath.Split(evalExpectedNSPath) - _, evalNSEntered := filepath.Split(evalNSEnteredPath) - - assert.Equal(t, evalExpectedNS, evalNSEntered) - } -}