From b96c7e5abea545d19c39e0eeff3af24b9cb77585 Mon Sep 17 00:00:00 2001 From: Archana Shinde Date: Thu, 5 Dec 2019 01:05:58 +0000 Subject: [PATCH] rootless: fix rootless for case net=none When kata-runtime was invoked as rootless by podman with net=none, an empty net namespace path is provided. kata-runtime was then trying to create a new network namespace and bind-mounting it under /var/run/netns, resulting in a permission error. Instead, with this commit, the runtime checks if it is running rootless and instead creates network namespace bind mount under rootless directory instead. Fixes #2319 Signed-off-by: Archana Shinde --- pkg/katautils/network.go | 17 +++++- pkg/rootless/rootless.go | 129 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 143 insertions(+), 3 deletions(-) diff --git a/pkg/katautils/network.go b/pkg/katautils/network.go index 7c9f0418e9..b01d13cee0 100644 --- a/pkg/katautils/network.go +++ b/pkg/katautils/network.go @@ -16,6 +16,7 @@ import ( "github.com/containernetworking/plugins/pkg/ns" "github.com/containernetworking/plugins/pkg/testutils" + "github.com/kata-containers/runtime/pkg/rootless" vc "github.com/kata-containers/runtime/virtcontainers" "golang.org/x/sys/unix" ) @@ -59,10 +60,20 @@ func SetupNetworkNamespace(config *vc.NetworkConfig) error { return nil } + var err error + var n ns.NetNS + if config.NetNSPath == "" { - n, err := testutils.NewNS() - if err != nil { - return err + if rootless.IsRootless() { + n, err = rootless.NewNS() + if err != nil { + return err + } + } else { + n, err = testutils.NewNS() + if err != nil { + return err + } } config.NetNSPath = n.Path() diff --git a/pkg/rootless/rootless.go b/pkg/rootless/rootless.go index 495fdf19a9..2bb702ab18 100644 --- a/pkg/rootless/rootless.go +++ b/pkg/rootless/rootless.go @@ -3,19 +3,39 @@ // SPDX-License-Identifier: Apache-2.0 // +// Copyright 2015-2019 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package rootless import ( "bufio" "context" + "crypto/rand" + "fmt" "io" "os" + "path/filepath" + "runtime" "strconv" "strings" "sync" + "github.com/containernetworking/plugins/pkg/ns" "github.com/pkg/errors" "github.com/sirupsen/logrus" + "golang.org/x/sys/unix" ) var ( @@ -121,3 +141,112 @@ func IsRootless() bool { func GetRootlessDir() string { return rootlessDir } + +// Creates a new persistent network namespace and returns an object +// representing that namespace, without switching to it +func NewNS() (ns.NetNS, error) { + nsRunDir := filepath.Join(GetRootlessDir(), "netns") + + b := make([]byte, 16) + _, err := rand.Reader.Read(b) + if err != nil { + return nil, fmt.Errorf("failed to generate random netns name: %v", err) + } + + // Create the directory for mounting network namespaces + // This needs to be a shared mountpoint in case it is mounted in to + // other namespaces (containers) + err = os.MkdirAll(nsRunDir, 0755) + if err != nil { + return nil, err + } + + nsName := fmt.Sprintf("net-%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:]) + + // create an empty file at the mount point + nsPath := filepath.Join(nsRunDir, nsName) + mountPointFd, err := os.Create(nsPath) + if err != nil { + return nil, err + } + if err := mountPointFd.Close(); err != nil { + return nil, err + } + + // Ensure the mount point is cleaned up on errors; if the namespace + // was successfully mounted this will have no effect because the file + // is in-use + defer func() { + _ = os.RemoveAll(nsPath) + }() + + var wg sync.WaitGroup + wg.Add(1) + + // do namespace work in a dedicated goroutine, so that we can safely + // Lock/Unlock OSThread without upsetting the lock/unlock state of + // the caller of this function + go (func() { + defer wg.Done() + runtime.LockOSThread() + // Don't unlock. By not unlocking, golang will kill the OS thread when the + // goroutine is done (for go1.10+) + + threadNsPath := getCurrentThreadNetNSPath() + + var origNS ns.NetNS + origNS, err = ns.GetNS(threadNsPath) + if err != nil { + rootlessLog.Warnf("cannot open current network namespace %s: %q", threadNsPath, err) + return + } + defer func() { + if err := origNS.Close(); err != nil { + rootlessLog.Errorf("unable to close namespace: %q", err) + } + }() + + // create a new netns on the current thread + err = unix.Unshare(unix.CLONE_NEWNET) + if err != nil { + rootlessLog.Warnf("cannot create a new network namespace: %q", err) + return + } + + // Put this thread back to the orig ns, since it might get reused (pre go1.10) + defer func() { + if err := origNS.Set(); err != nil { + if IsRootless() && strings.Contains(err.Error(), "operation not permitted") { + // When running in rootless mode it will fail to re-join + // the network namespace owned by root on the host. + return + } + rootlessLog.Warnf("unable to reset namespace: %q", err) + } + }() + + // bind mount the netns from the current thread (from /proc) onto the + // mount point. This causes the namespace to persist, even when there + // are no threads in the ns. + err = unix.Mount(threadNsPath, nsPath, "none", unix.MS_BIND, "") + if err != nil { + err = fmt.Errorf("failed to bind mount ns at %s: %v", nsPath, err) + } + })() + wg.Wait() + + if err != nil { + unix.Unmount(nsPath, unix.MNT_DETACH) + return nil, fmt.Errorf("failed to create namespace: %v", err) + } + + return ns.GetNS(nsPath) +} + +// getCurrentThreadNetNSPath copied from pkg/ns +func getCurrentThreadNetNSPath() string { + // /proc/self/ns/net returns the namespace of the main thread, not + // of whatever thread this goroutine is running on. Make sure we + // use the thread's net namespace since the thread is switching around + return fmt.Sprintf("/proc/%d/task/%d/ns/net", os.Getpid(), unix.Gettid()) +}