mirror of
https://github.com/opencontainers/runc.git
synced 2026-07-10 21:53:57 +08:00
8e8b136c49
With the idmap work, we will have a tainted Go thread in our thread-group that has a different mount namespace to the other threads. It seems that (due to some bad luck) the Go scheduler tends to make this thread the thread-group leader in our tests, which results in very baffling failures where /proc/self/mountinfo produces gibberish results. In order to avoid this, switch to using /proc/thread-self for everything that is thread-local. This primarily includes switching all file descriptor paths (CLONE_FS), all of the places that check the current cgroup (technically we never will run a single runc thread in a separate cgroup, but better to be safe than sorry), and the aforementioned mountinfo code. We don't need to do anything for the following because the results we need aren't thread-local: * Checks that certain namespaces are supported by stat(2)ing /proc/self/ns/... * /proc/self/exe and /proc/self/cmdline are not thread-local. * While threads can be in different cgroups, we do not do this for the runc binary (or libcontainer) and thus we do not need to switch to the thread-local version of /proc/self/cgroups. * All of the CLONE_NEWUSER files are not thread-local because you cannot set the usernamespace of a single thread (setns(CLONE_NEWUSER) is blocked for multi-threaded programs). Note that we have to use runtime.LockOSThread when we have an open handle to a tid-specific procfs file that we are operating on multiple times. Go can reschedule us such that we are running on a different thread and then kill the original thread (causing -ENOENT or similarly confusing errors). This is not strictly necessary for most usages of /proc/thread-self (such as using /proc/thread-self/fd/$n directly) since only operating on the actual inodes associated with the tid requires this locking, but because of the pre-3.17 fallback for CentOS, we have to do this in most cases. In addition, CentOS's kernel is too old for /proc/thread-self, which requires us to emulate it -- however in rootfs_linux.go, we are in the container pid namespace but /proc is the host's procfs. This leads to the incredibly frustrating situation where there is no way (on pre-4.1 Linux) to figure out which /proc/self/task/... entry refers to the current tid. We can just use /proc/self in this case. Yes this is all pretty ugly. I also wish it wasn't necessary. Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
103 lines
2.9 KiB
Go
103 lines
2.9 KiB
Go
/*
|
|
Copyright The containerd 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 fs2
|
|
|
|
import (
|
|
"bufio"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/opencontainers/runc/libcontainer/configs"
|
|
"github.com/opencontainers/runc/libcontainer/utils"
|
|
)
|
|
|
|
const UnifiedMountpoint = "/sys/fs/cgroup"
|
|
|
|
func defaultDirPath(c *configs.Cgroup) (string, error) {
|
|
if (c.Name != "" || c.Parent != "") && c.Path != "" {
|
|
return "", fmt.Errorf("cgroup: either Path or Name and Parent should be used, got %+v", c)
|
|
}
|
|
|
|
return _defaultDirPath(UnifiedMountpoint, c.Path, c.Parent, c.Name)
|
|
}
|
|
|
|
func _defaultDirPath(root, cgPath, cgParent, cgName string) (string, error) {
|
|
if (cgName != "" || cgParent != "") && cgPath != "" {
|
|
return "", errors.New("cgroup: either Path or Name and Parent should be used")
|
|
}
|
|
|
|
// XXX: Do not remove CleanPath. Path safety is important! -- cyphar
|
|
innerPath := utils.CleanPath(cgPath)
|
|
if innerPath == "" {
|
|
cgParent := utils.CleanPath(cgParent)
|
|
cgName := utils.CleanPath(cgName)
|
|
innerPath = filepath.Join(cgParent, cgName)
|
|
}
|
|
if filepath.IsAbs(innerPath) {
|
|
return filepath.Join(root, innerPath), nil
|
|
}
|
|
|
|
// we don't need to use /proc/thread-self here because runc always runs
|
|
// with every thread in the same cgroup. This lets us avoid having to do
|
|
// runtime.LockOSThread.
|
|
ownCgroup, err := parseCgroupFile("/proc/self/cgroup")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
// The current user scope most probably has tasks in it already,
|
|
// making it impossible to enable controllers for its sub-cgroup.
|
|
// A parent cgroup (with no tasks in it) is what we need.
|
|
ownCgroup = filepath.Dir(ownCgroup)
|
|
|
|
return filepath.Join(root, ownCgroup, innerPath), nil
|
|
}
|
|
|
|
// parseCgroupFile parses /proc/PID/cgroup file and return string
|
|
func parseCgroupFile(path string) (string, error) {
|
|
f, err := os.Open(path)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer f.Close()
|
|
return parseCgroupFromReader(f)
|
|
}
|
|
|
|
func parseCgroupFromReader(r io.Reader) (string, error) {
|
|
s := bufio.NewScanner(r)
|
|
for s.Scan() {
|
|
var (
|
|
text = s.Text()
|
|
parts = strings.SplitN(text, ":", 3)
|
|
)
|
|
if len(parts) < 3 {
|
|
return "", fmt.Errorf("invalid cgroup entry: %q", text)
|
|
}
|
|
// text is like "0::/user.slice/user-1001.slice/session-1.scope"
|
|
if parts[0] == "0" && parts[1] == "" {
|
|
return parts[2], nil
|
|
}
|
|
}
|
|
if err := s.Err(); err != nil {
|
|
return "", err
|
|
}
|
|
return "", errors.New("cgroup path not found")
|
|
}
|