Update to v1.21.2 (#3479)

* Update to v1.21.2
Signed-off-by: dereknola <derek.nola@suse.com>
This commit is contained in:
Derek Nola
2021-06-18 13:46:09 -07:00
committed by GitHub
parent c35761d169
commit 5a67e8dc47
90 changed files with 1407 additions and 953 deletions

View File

@@ -1,5 +1,5 @@
ARG GO_VERSION=1.15
ARG BATS_VERSION=v1.2.1
ARG GO_VERSION=1.16
ARG BATS_VERSION=v1.3.0
FROM golang:${GO_VERSION}-buster
ARG DEBIAN_FRONTEND=noninteractive

11
vendor/github.com/opencontainers/runc/EMERITUS.md generated vendored Normal file
View File

@@ -0,0 +1,11 @@
## Emeritus ##
We would like to acknowledge previous runc maintainers and their huge
contributions to our collective success:
* Alexander Morozov (@lk4d4)
* Andrei Vagin (@avagin)
* Rohit Jnagal (@rjnagal)
* Victor Marmol (@vmarmol)
We thank these members for their service to the OCI community.

View File

@@ -1,5 +1,5 @@
CONTAINER_ENGINE := docker
GO := go
GO ?= go
PREFIX ?= /usr/local
BINDIR := $(PREFIX)/sbin
@@ -14,6 +14,10 @@ COMMIT_NO := $(shell git rev-parse HEAD 2> /dev/null || true)
COMMIT ?= $(if $(shell git status --porcelain --untracked-files=no),"$(COMMIT_NO)-dirty","$(COMMIT_NO)")
VERSION := $(shell cat ./VERSION)
# TODO: rm -mod=vendor once go 1.13 is unsupported
ifneq ($(GO111MODULE),off)
MOD_VENDOR := "-mod=vendor"
endif
ifeq ($(shell $(GO) env GOOS),linux)
ifeq (,$(filter $(shell $(GO) env GOARCH),mips mipsle mips64 mips64le ppc64))
ifeq (,$(findstring -race,$(EXTRA_FLAGS)))
@@ -21,9 +25,9 @@ ifeq ($(shell $(GO) env GOOS),linux)
endif
endif
endif
GO_BUILD := $(GO) build -trimpath $(GO_BUILDMODE) $(EXTRA_FLAGS) -tags "$(BUILDTAGS)" \
GO_BUILD := $(GO) build -trimpath $(MOD_VENDOR) $(GO_BUILDMODE) $(EXTRA_FLAGS) -tags "$(BUILDTAGS)" \
-ldflags "-X main.gitCommit=$(COMMIT) -X main.version=$(VERSION) $(EXTRA_LDFLAGS)"
GO_BUILD_STATIC := CGO_ENABLED=1 $(GO) build -trimpath $(EXTRA_FLAGS) -tags "$(BUILDTAGS) netgo osusergo" \
GO_BUILD_STATIC := CGO_ENABLED=1 $(GO) build -trimpath $(MOD_VENDOR) $(EXTRA_FLAGS) -tags "$(BUILDTAGS) netgo osusergo" \
-ldflags "-w -extldflags -static -X main.gitCommit=$(COMMIT) -X main.version=$(VERSION) $(EXTRA_LDFLAGS)"
.DEFAULT: runc
@@ -70,7 +74,7 @@ unittest: runcimage
$(RUNC_IMAGE) make localunittest TESTFLAGS=$(TESTFLAGS)
localunittest: all
$(GO) test -timeout 3m -tags "$(BUILDTAGS)" $(TESTFLAGS) -v ./...
$(GO) test $(MOD_VENDOR) -timeout 3m -tags "$(BUILDTAGS)" $(TESTFLAGS) -v ./...
integration: runcimage
$(CONTAINER_ENGINE) run $(CONTAINER_ENGINE_RUN_FLAGS) \

View File

@@ -28,7 +28,7 @@ A third party security audit was performed by Cure53, you can see the full repor
## Building
`runc` currently supports the Linux platform with various architecture support.
It must be built with Go version 1.14 or higher.
It must be built with Go version 1.13 or higher.
In order to enable seccomp support you will need to install `libseccomp` on your platform.
> e.g. `libseccomp-devel` for CentOS, or `libseccomp-dev` for Ubuntu

View File

@@ -1 +1 @@
1.0.0-rc93+dev
1.0.0-rc95

View File

@@ -15,8 +15,8 @@ Vagrant.configure("2") do |config|
set -e -u -o pipefail
# configuration
GO_VERSION="1.15"
BATS_VERSION="v1.2.1"
GO_VERSION="1.16.4"
BATS_VERSION="v1.3.0"
# install yum packages
yum install -y -q epel-release

View File

@@ -3,7 +3,7 @@
Vagrant.configure("2") do |config|
# Fedora box is used for testing cgroup v2 support
config.vm.box = "fedora/33-cloud-base"
config.vm.box = "fedora/34-cloud-base"
config.vm.provider :virtualbox do |v|
v.memory = 2048
v.cpus = 2
@@ -41,8 +41,8 @@ EOF
cat > /etc/systemd/system/user@.service.d/delegate.conf << EOF
[Service]
# default: Delegate=pids memory
# NOTE: delegation of cpuset requires systemd >= 244 (Fedora >= 32, Ubuntu >= 20.04). cpuset is ignored on Fedora 31.
Delegate=cpu cpuset io memory pids
# NOTE: delegation of cpuset requires systemd >= 244 (Fedora >= 32, Ubuntu >= 20.04).
Delegate=yes
EOF
systemctl daemon-reload
SHELL

View File

@@ -7,6 +7,7 @@ import (
"fmt"
"net"
"os"
"path/filepath"
"strconv"
criu "github.com/checkpoint-restore/go-criu/v5/rpc"
@@ -78,12 +79,37 @@ checkpointed.`,
},
}
func getCheckpointImagePath(context *cli.Context) string {
func prepareImagePaths(context *cli.Context) (string, string, error) {
imagePath := context.String("image-path")
if imagePath == "" {
imagePath = getDefaultImagePath(context)
}
return imagePath
if err := os.MkdirAll(imagePath, 0600); err != nil {
return "", "", err
}
parentPath := context.String("parent-path")
if parentPath == "" {
return imagePath, parentPath, nil
}
if filepath.IsAbs(parentPath) {
return "", "", errors.New("--parent-path must be relative")
}
realParent := filepath.Join(imagePath, parentPath)
fi, err := os.Stat(realParent)
if err == nil && !fi.IsDir() {
err = &os.PathError{Path: realParent, Err: unix.ENOTDIR}
}
if err != nil {
return "", "", fmt.Errorf("invalid --parent-path: %w", err)
}
return imagePath, parentPath, nil
}
func setPageServer(context *cli.Context, options *libcontainer.CriuOpts) {

View File

@@ -1,6 +1,6 @@
module github.com/opencontainers/runc
go 1.14
go 1.13
require (
github.com/checkpoint-restore/go-criu/v5 v5.0.0
@@ -23,6 +23,6 @@ require (
github.com/vishvananda/netlink v1.1.0
github.com/willf/bitset v1.1.11
golang.org/x/net v0.0.0-20201224014010-6772e930b67b
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c
golang.org/x/sys v0.0.0-20210426230700-d19ff857e887
google.golang.org/protobuf v1.25.0
)

View File

@@ -105,6 +105,8 @@ golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210426230700-d19ff857e887 h1:dXfMednGJh/SUUFjTLsWJz3P+TQt9qnR11GgeI3vWKs=
golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=

View File

@@ -7,37 +7,44 @@ import (
)
type Manager interface {
// Applies cgroup configuration to the process with the specified pid
// Apply creates a cgroup, if not yet created, and adds a process
// with the specified pid into that cgroup. A special value of -1
// can be used to merely create a cgroup.
Apply(pid int) error
// Returns the PIDs inside the cgroup set
// GetPids returns the PIDs of all processes inside the cgroup.
GetPids() ([]int, error)
// Returns the PIDs inside the cgroup set & all sub-cgroups
// GetAllPids returns the PIDs of all processes inside the cgroup
// any all its sub-cgroups.
GetAllPids() ([]int, error)
// Returns statistics for the cgroup set
// GetStats returns cgroups statistics.
GetStats() (*Stats, error)
// Toggles the freezer cgroup according with specified state
// Freeze sets the freezer cgroup to the specified state.
Freeze(state configs.FreezerState) error
// Destroys the cgroup set
// Destroy removes cgroup.
Destroy() error
// Path returns a cgroup path to the specified controller/subsystem.
// For cgroupv2, the argument is unused and can be empty.
Path(string) string
// Sets the cgroup as configured.
Set(container *configs.Config) error
// Set sets cgroup resources parameters/limits. If the argument is nil,
// the resources specified during Manager creation (or the previous call
// to Set) are used.
Set(r *configs.Resources) error
// GetPaths returns cgroup path(s) to save in a state file in order to restore later.
// GetPaths returns cgroup path(s) to save in a state file in order to
// restore later.
//
// For cgroup v1, a key is cgroup subsystem name, and the value is the path
// to the cgroup for this subsystem.
// For cgroup v1, a key is cgroup subsystem name, and the value is the
// path to the cgroup for this subsystem.
//
// For cgroup v2 unified hierarchy, a key is "", and the value is the unified path.
// For cgroup v2 unified hierarchy, a key is "", and the value is the
// unified path.
GetPaths() map[string]string
// GetCgroups returns the cgroup data as configured.
@@ -46,7 +53,7 @@ type Manager interface {
// GetFreezerState retrieves the current FreezerState of the cgroup.
GetFreezerState() (configs.FreezerState, error)
// Whether the cgroup path exists or not
// Exists returns whether the cgroup path exists or not.
Exists() bool
// OOMKillCount reports OOM kill count for the cgroup.

View File

@@ -25,19 +25,19 @@ func (s *BlkioGroup) Apply(path string, d *cgroupData) error {
return join(path, d.pid)
}
func (s *BlkioGroup) Set(path string, cgroup *configs.Cgroup) error {
if cgroup.Resources.BlkioWeight != 0 {
if err := fscommon.WriteFile(path, "blkio.weight", strconv.FormatUint(uint64(cgroup.Resources.BlkioWeight), 10)); err != nil {
func (s *BlkioGroup) Set(path string, r *configs.Resources) error {
if r.BlkioWeight != 0 {
if err := fscommon.WriteFile(path, "blkio.weight", strconv.FormatUint(uint64(r.BlkioWeight), 10)); err != nil {
return err
}
}
if cgroup.Resources.BlkioLeafWeight != 0 {
if err := fscommon.WriteFile(path, "blkio.leaf_weight", strconv.FormatUint(uint64(cgroup.Resources.BlkioLeafWeight), 10)); err != nil {
if r.BlkioLeafWeight != 0 {
if err := fscommon.WriteFile(path, "blkio.leaf_weight", strconv.FormatUint(uint64(r.BlkioLeafWeight), 10)); err != nil {
return err
}
}
for _, wd := range cgroup.Resources.BlkioWeightDevice {
for _, wd := range r.BlkioWeightDevice {
if err := fscommon.WriteFile(path, "blkio.weight_device", wd.WeightString()); err != nil {
return err
}
@@ -45,22 +45,22 @@ func (s *BlkioGroup) Set(path string, cgroup *configs.Cgroup) error {
return err
}
}
for _, td := range cgroup.Resources.BlkioThrottleReadBpsDevice {
for _, td := range r.BlkioThrottleReadBpsDevice {
if err := fscommon.WriteFile(path, "blkio.throttle.read_bps_device", td.String()); err != nil {
return err
}
}
for _, td := range cgroup.Resources.BlkioThrottleWriteBpsDevice {
for _, td := range r.BlkioThrottleWriteBpsDevice {
if err := fscommon.WriteFile(path, "blkio.throttle.write_bps_device", td.String()); err != nil {
return err
}
}
for _, td := range cgroup.Resources.BlkioThrottleReadIOPSDevice {
for _, td := range r.BlkioThrottleReadIOPSDevice {
if err := fscommon.WriteFile(path, "blkio.throttle.read_iops_device", td.String()); err != nil {
return err
}
}
for _, td := range cgroup.Resources.BlkioThrottleWriteIOPSDevice {
for _, td := range r.BlkioThrottleWriteIOPSDevice {
if err := fscommon.WriteFile(path, "blkio.throttle.write_iops_device", td.String()); err != nil {
return err
}

View File

@@ -32,7 +32,7 @@ func (s *CpuGroup) Apply(path string, d *cgroupData) error {
// We should set the real-Time group scheduling settings before moving
// in the process because if the process is already in SCHED_RR mode
// and no RT bandwidth is set, adding it will fail.
if err := s.SetRtSched(path, d.config); err != nil {
if err := s.SetRtSched(path, d.config.Resources); err != nil {
return err
}
// Since we are not using join(), we need to place the pid
@@ -40,23 +40,23 @@ func (s *CpuGroup) Apply(path string, d *cgroupData) error {
return cgroups.WriteCgroupProc(path, d.pid)
}
func (s *CpuGroup) SetRtSched(path string, cgroup *configs.Cgroup) error {
if cgroup.Resources.CpuRtPeriod != 0 {
if err := fscommon.WriteFile(path, "cpu.rt_period_us", strconv.FormatUint(cgroup.Resources.CpuRtPeriod, 10)); err != nil {
func (s *CpuGroup) SetRtSched(path string, r *configs.Resources) error {
if r.CpuRtPeriod != 0 {
if err := fscommon.WriteFile(path, "cpu.rt_period_us", strconv.FormatUint(r.CpuRtPeriod, 10)); err != nil {
return err
}
}
if cgroup.Resources.CpuRtRuntime != 0 {
if err := fscommon.WriteFile(path, "cpu.rt_runtime_us", strconv.FormatInt(cgroup.Resources.CpuRtRuntime, 10)); err != nil {
if r.CpuRtRuntime != 0 {
if err := fscommon.WriteFile(path, "cpu.rt_runtime_us", strconv.FormatInt(r.CpuRtRuntime, 10)); err != nil {
return err
}
}
return nil
}
func (s *CpuGroup) Set(path string, cgroup *configs.Cgroup) error {
if cgroup.Resources.CpuShares != 0 {
shares := cgroup.Resources.CpuShares
func (s *CpuGroup) Set(path string, r *configs.Resources) error {
if r.CpuShares != 0 {
shares := r.CpuShares
if err := fscommon.WriteFile(path, "cpu.shares", strconv.FormatUint(shares, 10)); err != nil {
return err
}
@@ -72,17 +72,17 @@ func (s *CpuGroup) Set(path string, cgroup *configs.Cgroup) error {
return fmt.Errorf("the minimum allowed cpu-shares is %d", sharesRead)
}
}
if cgroup.Resources.CpuPeriod != 0 {
if err := fscommon.WriteFile(path, "cpu.cfs_period_us", strconv.FormatUint(cgroup.Resources.CpuPeriod, 10)); err != nil {
if r.CpuPeriod != 0 {
if err := fscommon.WriteFile(path, "cpu.cfs_period_us", strconv.FormatUint(r.CpuPeriod, 10)); err != nil {
return err
}
}
if cgroup.Resources.CpuQuota != 0 {
if err := fscommon.WriteFile(path, "cpu.cfs_quota_us", strconv.FormatInt(cgroup.Resources.CpuQuota, 10)); err != nil {
if r.CpuQuota != 0 {
if err := fscommon.WriteFile(path, "cpu.cfs_quota_us", strconv.FormatInt(r.CpuQuota, 10)); err != nil {
return err
}
}
return s.SetRtSched(path, cgroup)
return s.SetRtSched(path, r)
}
func (s *CpuGroup) GetStats(path string, stats *cgroups.Stats) error {

View File

@@ -43,7 +43,7 @@ func (s *CpuacctGroup) Apply(path string, d *cgroupData) error {
return join(path, d.pid)
}
func (s *CpuacctGroup) Set(path string, cgroup *configs.Cgroup) error {
func (s *CpuacctGroup) Set(_ string, _ *configs.Resources) error {
return nil
}

View File

@@ -24,17 +24,17 @@ func (s *CpusetGroup) Name() string {
}
func (s *CpusetGroup) Apply(path string, d *cgroupData) error {
return s.ApplyDir(path, d.config, d.pid)
return s.ApplyDir(path, d.config.Resources, d.pid)
}
func (s *CpusetGroup) Set(path string, cgroup *configs.Cgroup) error {
if cgroup.Resources.CpusetCpus != "" {
if err := fscommon.WriteFile(path, "cpuset.cpus", cgroup.Resources.CpusetCpus); err != nil {
func (s *CpusetGroup) Set(path string, r *configs.Resources) error {
if r.CpusetCpus != "" {
if err := fscommon.WriteFile(path, "cpuset.cpus", r.CpusetCpus); err != nil {
return err
}
}
if cgroup.Resources.CpusetMems != "" {
if err := fscommon.WriteFile(path, "cpuset.mems", cgroup.Resources.CpusetMems); err != nil {
if r.CpusetMems != "" {
if err := fscommon.WriteFile(path, "cpuset.mems", r.CpusetMems); err != nil {
return err
}
}
@@ -144,7 +144,7 @@ func (s *CpusetGroup) GetStats(path string, stats *cgroups.Stats) error {
return nil
}
func (s *CpusetGroup) ApplyDir(dir string, cgroup *configs.Cgroup, pid int) error {
func (s *CpusetGroup) ApplyDir(dir string, r *configs.Resources, pid int) error {
// This might happen if we have no cpuset cgroup mounted.
// Just do nothing and don't fail.
if dir == "" {
@@ -166,7 +166,7 @@ func (s *CpusetGroup) ApplyDir(dir string, cgroup *configs.Cgroup, pid int) erro
// specified configs, otherwise, inherit from parent. This makes
// cpuset configs work correctly with 'cpuset.cpu_exclusive', and
// keep backward compatibility.
if err := s.ensureCpusAndMems(dir, cgroup); err != nil {
if err := s.ensureCpusAndMems(dir, r); err != nil {
return err
}
@@ -241,8 +241,8 @@ func isEmptyCpuset(str string) bool {
return str == "" || str == "\n"
}
func (s *CpusetGroup) ensureCpusAndMems(path string, cgroup *configs.Cgroup) error {
if err := s.Set(path, cgroup); err != nil {
func (s *CpusetGroup) ensureCpusAndMems(path string, r *configs.Resources) error {
if err := s.Set(path, r); err != nil {
return err
}
return cpusetCopyIfNeeded(path, filepath.Dir(path))

View File

@@ -54,8 +54,8 @@ func buildEmulator(rules []*devices.Rule) (*cgroupdevices.Emulator, error) {
return emu, nil
}
func (s *DevicesGroup) Set(path string, cgroup *configs.Cgroup) error {
if userns.RunningInUserNS() || cgroup.SkipDevices {
func (s *DevicesGroup) Set(path string, r *configs.Resources) error {
if userns.RunningInUserNS() || r.SkipDevices {
return nil
}
@@ -65,7 +65,7 @@ func (s *DevicesGroup) Set(path string, cgroup *configs.Cgroup) error {
if err != nil {
return err
}
target, err := buildEmulator(cgroup.Resources.Devices)
target, err := buildEmulator(r.Devices)
if err != nil {
return err
}

View File

@@ -27,27 +27,47 @@ func (s *FreezerGroup) Apply(path string, d *cgroupData) error {
return join(path, d.pid)
}
func (s *FreezerGroup) Set(path string, cgroup *configs.Cgroup) error {
switch cgroup.Resources.Freezer {
func (s *FreezerGroup) Set(path string, r *configs.Resources) (Err error) {
switch r.Freezer {
case configs.Frozen:
defer func() {
if Err != nil {
// Freezing failed, and it is bad and dangerous
// to leave the cgroup in FROZEN or FREEZING
// state, so (try to) thaw it back.
_ = fscommon.WriteFile(path, "freezer.state", string(configs.Thawed))
}
}()
// As per older kernel docs (freezer-subsystem.txt before
// kernel commit ef9fe980c6fcc1821), if FREEZING is seen,
// userspace should either retry or thaw. While current
// kernel cgroup v1 docs no longer mention a need to retry,
// the kernel (tested on v5.4, Ubuntu 20.04) can't reliably
// freeze a cgroup while new processes keep appearing in it
// even a recent kernel (v5.4, Ubuntu 20.04) can't reliably
// freeze a cgroup v1 while new processes keep appearing in it
// (either via fork/clone or by writing new PIDs to
// cgroup.procs).
//
// The numbers below are chosen to have a decent chance to
// succeed even in the worst case scenario (runc pause/unpause
// with parallel runc exec).
// The numbers below are empirically chosen to have a decent
// chance to succeed in various scenarios ("runc pause/unpause
// with parallel runc exec" and "bare freeze/unfreeze on a very
// slow system"), tested on RHEL7 and Ubuntu 20.04 kernels.
//
// Adding any amount of sleep in between retries did not
// increase the chances of successful freeze.
// increase the chances of successful freeze in "pause/unpause
// with parallel exec" reproducer. OTOH, adding an occasional
// sleep helped for the case where the system is extremely slow
// (CentOS 7 VM on GHA CI).
//
// Alas, this is still a game of chances, since the real fix
// belong to the kernel (cgroup v2 do not have this bug).
for i := 0; i < 1000; i++ {
if i%50 == 49 {
// Briefly thawing the cgroup also helps.
// Occasional thaw and sleep improves
// the chances to succeed in freezing
// in case new processes keep appearing
// in the cgroup.
_ = fscommon.WriteFile(path, "freezer.state", string(configs.Thawed))
time.Sleep(10 * time.Millisecond)
}
@@ -56,6 +76,13 @@ func (s *FreezerGroup) Set(path string, cgroup *configs.Cgroup) error {
return err
}
if i%25 == 24 {
// Occasional short sleep before reading
// the state back also improves the chances to
// succeed in freezing in case of a very slow
// system.
time.Sleep(10 * time.Microsecond)
}
state, err := fscommon.ReadFile(path, "freezer.state")
if err != nil {
return err
@@ -75,16 +102,13 @@ func (s *FreezerGroup) Set(path string, cgroup *configs.Cgroup) error {
}
}
// Despite our best efforts, it got stuck in FREEZING.
// Leaving it in this state is bad and dangerous, so
// let's (try to) thaw it back and error out.
_ = fscommon.WriteFile(path, "freezer.state", string(configs.Thawed))
return errors.New("unable to freeze")
case configs.Thawed:
return fscommon.WriteFile(path, "freezer.state", string(configs.Thawed))
case configs.Undefined:
return nil
default:
return fmt.Errorf("Invalid argument '%s' to freezer.state", string(cgroup.Resources.Freezer))
return fmt.Errorf("Invalid argument '%s' to freezer.state", string(r.Freezer))
}
}

View File

@@ -44,8 +44,8 @@ type subsystem interface {
GetStats(path string, stats *cgroups.Stats) error
// Creates and joins the cgroup represented by 'cgroupData'.
Apply(path string, c *cgroupData) error
// Set the cgroup represented by cgroup.
Set(path string, cgroup *configs.Cgroup) error
// Set sets the cgroup resources.
Set(path string, r *configs.Resources) error
}
type manager struct {
@@ -274,8 +274,8 @@ func (m *manager) GetStats() (*cgroups.Stats, error) {
return stats, nil
}
func (m *manager) Set(container *configs.Config) error {
if container.Cgroups == nil {
func (m *manager) Set(r *configs.Resources) error {
if r == nil {
return nil
}
@@ -284,7 +284,7 @@ func (m *manager) Set(container *configs.Config) error {
if m.cgroups != nil && m.cgroups.Paths != nil {
return nil
}
if container.Cgroups.Resources.Unified != nil {
if r.Unified != nil {
return cgroups.ErrV1NoUnified
}
@@ -292,11 +292,11 @@ func (m *manager) Set(container *configs.Config) error {
defer m.mu.Unlock()
for _, sys := range subsystems {
path := m.paths[sys.Name()]
if err := sys.Set(path, container.Cgroups); err != nil {
if err := sys.Set(path, r); err != nil {
if m.rootless && sys.Name() == "devices" {
continue
}
// When m.Rootless is true, errors from the device subsystem are ignored because it is really not expected to work.
// When m.rootless is true, errors from the device subsystem are ignored because it is really not expected to work.
// However, errors from other subsystems are not ignored.
// see @test "runc create (rootless + limits + no cgrouppath + no permission) fails with informative error"
if path == "" {
@@ -322,7 +322,7 @@ func (m *manager) Freeze(state configs.FreezerState) error {
prevState := m.cgroups.Resources.Freezer
m.cgroups.Resources.Freezer = state
freezer := &FreezerGroup{}
if err := freezer.Set(path, m.cgroups); err != nil {
if err := freezer.Set(path, m.cgroups.Resources); err != nil {
m.cgroups.Resources.Freezer = prevState
return err
}
@@ -428,5 +428,11 @@ func OOMKillCount(path string) (uint64, error) {
}
func (m *manager) OOMKillCount() (uint64, error) {
return OOMKillCount(m.Path("memory"))
c, err := OOMKillCount(m.Path("memory"))
// Ignore ENOENT when rootless as it couldn't create cgroup.
if err != nil && m.rootless && os.IsNotExist(err) {
err = nil
}
return c, err
}

View File

@@ -22,8 +22,8 @@ func (s *HugetlbGroup) Apply(path string, d *cgroupData) error {
return join(path, d.pid)
}
func (s *HugetlbGroup) Set(path string, cgroup *configs.Cgroup) error {
for _, hugetlb := range cgroup.Resources.HugetlbLimit {
func (s *HugetlbGroup) Set(path string, r *configs.Resources) error {
for _, hugetlb := range r.HugetlbLimit {
if err := fscommon.WriteFile(path, "hugetlb."+hugetlb.Pagesize+".limit_in_bytes", strconv.FormatUint(hugetlb.Limit, 10)); err != nil {
return err
}

View File

@@ -110,32 +110,32 @@ func setMemoryAndSwap(path string, r *configs.Resources) error {
return nil
}
func (s *MemoryGroup) Set(path string, cgroup *configs.Cgroup) error {
if err := setMemoryAndSwap(path, cgroup.Resources); err != nil {
func (s *MemoryGroup) Set(path string, r *configs.Resources) error {
if err := setMemoryAndSwap(path, r); err != nil {
return err
}
// ignore KernelMemory and KernelMemoryTCP
if cgroup.Resources.MemoryReservation != 0 {
if err := fscommon.WriteFile(path, "memory.soft_limit_in_bytes", strconv.FormatInt(cgroup.Resources.MemoryReservation, 10)); err != nil {
if r.MemoryReservation != 0 {
if err := fscommon.WriteFile(path, "memory.soft_limit_in_bytes", strconv.FormatInt(r.MemoryReservation, 10)); err != nil {
return err
}
}
if cgroup.Resources.OomKillDisable {
if r.OomKillDisable {
if err := fscommon.WriteFile(path, "memory.oom_control", "1"); err != nil {
return err
}
}
if cgroup.Resources.MemorySwappiness == nil || int64(*cgroup.Resources.MemorySwappiness) == -1 {
if r.MemorySwappiness == nil || int64(*r.MemorySwappiness) == -1 {
return nil
} else if *cgroup.Resources.MemorySwappiness <= 100 {
if err := fscommon.WriteFile(path, "memory.swappiness", strconv.FormatUint(*cgroup.Resources.MemorySwappiness, 10)); err != nil {
} else if *r.MemorySwappiness <= 100 {
if err := fscommon.WriteFile(path, "memory.swappiness", strconv.FormatUint(*r.MemorySwappiness, 10)); err != nil {
return err
}
} else {
return fmt.Errorf("invalid value:%d. valid memory swappiness range is 0-100", *cgroup.Resources.MemorySwappiness)
return fmt.Errorf("invalid value:%d. valid memory swappiness range is 0-100", *r.MemorySwappiness)
}
return nil
@@ -224,7 +224,9 @@ func getMemoryData(path, name string) (cgroups.MemoryData, error) {
value, err := fscommon.GetCgroupParamUint(path, usage)
if err != nil {
if moduleName != "memory" && os.IsNotExist(err) {
if name != "" && os.IsNotExist(err) {
// Ignore ENOENT as swap and kmem controllers
// are optional in the kernel.
return cgroups.MemoryData{}, nil
}
return cgroups.MemoryData{}, fmt.Errorf("failed to parse %s - %v", usage, err)
@@ -232,25 +234,16 @@ func getMemoryData(path, name string) (cgroups.MemoryData, error) {
memoryData.Usage = value
value, err = fscommon.GetCgroupParamUint(path, maxUsage)
if err != nil {
if moduleName != "memory" && os.IsNotExist(err) {
return cgroups.MemoryData{}, nil
}
return cgroups.MemoryData{}, fmt.Errorf("failed to parse %s - %v", maxUsage, err)
}
memoryData.MaxUsage = value
value, err = fscommon.GetCgroupParamUint(path, failcnt)
if err != nil {
if moduleName != "memory" && os.IsNotExist(err) {
return cgroups.MemoryData{}, nil
}
return cgroups.MemoryData{}, fmt.Errorf("failed to parse %s - %v", failcnt, err)
}
memoryData.Failcnt = value
value, err = fscommon.GetCgroupParamUint(path, limit)
if err != nil {
if moduleName != "memory" && os.IsNotExist(err) {
return cgroups.MemoryData{}, nil
}
return cgroups.MemoryData{}, fmt.Errorf("failed to parse %s - %v", limit, err)
}
memoryData.Limit = value

View File

@@ -24,7 +24,7 @@ func (s *NameGroup) Apply(path string, d *cgroupData) error {
return nil
}
func (s *NameGroup) Set(path string, cgroup *configs.Cgroup) error {
func (s *NameGroup) Set(_ string, _ *configs.Resources) error {
return nil
}

View File

@@ -21,9 +21,9 @@ func (s *NetClsGroup) Apply(path string, d *cgroupData) error {
return join(path, d.pid)
}
func (s *NetClsGroup) Set(path string, cgroup *configs.Cgroup) error {
if cgroup.Resources.NetClsClassid != 0 {
if err := fscommon.WriteFile(path, "net_cls.classid", strconv.FormatUint(uint64(cgroup.Resources.NetClsClassid), 10)); err != nil {
func (s *NetClsGroup) Set(path string, r *configs.Resources) error {
if r.NetClsClassid != 0 {
if err := fscommon.WriteFile(path, "net_cls.classid", strconv.FormatUint(uint64(r.NetClsClassid), 10)); err != nil {
return err
}
}

View File

@@ -19,8 +19,8 @@ func (s *NetPrioGroup) Apply(path string, d *cgroupData) error {
return join(path, d.pid)
}
func (s *NetPrioGroup) Set(path string, cgroup *configs.Cgroup) error {
for _, prioMap := range cgroup.Resources.NetPrioIfpriomap {
func (s *NetPrioGroup) Set(path string, r *configs.Resources) error {
for _, prioMap := range r.NetPrioIfpriomap {
if err := fscommon.WriteFile(path, "net_prio.ifpriomap", prioMap.CgroupString()); err != nil {
return err
}

View File

@@ -18,7 +18,7 @@ func (s *PerfEventGroup) Apply(path string, d *cgroupData) error {
return join(path, d.pid)
}
func (s *PerfEventGroup) Set(path string, cgroup *configs.Cgroup) error {
func (s *PerfEventGroup) Set(_ string, _ *configs.Resources) error {
return nil
}

View File

@@ -23,13 +23,13 @@ func (s *PidsGroup) Apply(path string, d *cgroupData) error {
return join(path, d.pid)
}
func (s *PidsGroup) Set(path string, cgroup *configs.Cgroup) error {
if cgroup.Resources.PidsLimit != 0 {
func (s *PidsGroup) Set(path string, r *configs.Resources) error {
if r.PidsLimit != 0 {
// "max" is the fallback value.
limit := "max"
if cgroup.Resources.PidsLimit > 0 {
limit = strconv.FormatInt(cgroup.Resources.PidsLimit, 10)
if r.PidsLimit > 0 {
limit = strconv.FormatInt(r.PidsLimit, 10)
}
if err := fscommon.WriteFile(path, "pids.max", limit); err != nil {

View File

@@ -12,15 +12,14 @@ import (
"github.com/opencontainers/runc/libcontainer/configs"
)
func isCpuSet(cgroup *configs.Cgroup) bool {
return cgroup.Resources.CpuWeight != 0 || cgroup.Resources.CpuQuota != 0 || cgroup.Resources.CpuPeriod != 0
func isCpuSet(r *configs.Resources) bool {
return r.CpuWeight != 0 || r.CpuQuota != 0 || r.CpuPeriod != 0
}
func setCpu(dirPath string, cgroup *configs.Cgroup) error {
if !isCpuSet(cgroup) {
func setCpu(dirPath string, r *configs.Resources) error {
if !isCpuSet(r) {
return nil
}
r := cgroup.Resources
// NOTE: .CpuShares is not used here. Conversion is the caller's responsibility.
if r.CpuWeight != 0 {
@@ -70,6 +69,15 @@ func statCpu(dirPath string, stats *cgroups.Stats) error {
case "system_usec":
stats.CpuStats.CpuUsage.UsageInKernelmode = v * 1000
case "nr_periods":
stats.CpuStats.ThrottlingData.Periods = v
case "nr_throttled":
stats.CpuStats.ThrottlingData.ThrottledPeriods = v
case "throttled_usec":
stats.CpuStats.ThrottlingData.ThrottledTime = v * 1000
}
}
return nil

View File

@@ -7,22 +7,22 @@ import (
"github.com/opencontainers/runc/libcontainer/configs"
)
func isCpusetSet(cgroup *configs.Cgroup) bool {
return cgroup.Resources.CpusetCpus != "" || cgroup.Resources.CpusetMems != ""
func isCpusetSet(r *configs.Resources) bool {
return r.CpusetCpus != "" || r.CpusetMems != ""
}
func setCpuset(dirPath string, cgroup *configs.Cgroup) error {
if !isCpusetSet(cgroup) {
func setCpuset(dirPath string, r *configs.Resources) error {
if !isCpusetSet(r) {
return nil
}
if cgroup.Resources.CpusetCpus != "" {
if err := fscommon.WriteFile(dirPath, "cpuset.cpus", cgroup.Resources.CpusetCpus); err != nil {
if r.CpusetCpus != "" {
if err := fscommon.WriteFile(dirPath, "cpuset.cpus", r.CpusetCpus); err != nil {
return err
}
}
if cgroup.Resources.CpusetMems != "" {
if err := fscommon.WriteFile(dirPath, "cpuset.mems", cgroup.Resources.CpusetMems); err != nil {
if r.CpusetMems != "" {
if err := fscommon.WriteFile(dirPath, "cpuset.mems", r.CpusetMems); err != nil {
return err
}
}

View File

@@ -10,7 +10,7 @@ import (
"github.com/opencontainers/runc/libcontainer/configs"
)
func supportedControllers(cgroup *configs.Cgroup) (string, error) {
func supportedControllers() (string, error) {
return fscommon.ReadFile(UnifiedMountpoint, "/cgroup.controllers")
}
@@ -18,13 +18,13 @@ func supportedControllers(cgroup *configs.Cgroup) (string, error) {
// based on (1) controllers available and (2) resources that are being set.
// We don't check "pseudo" controllers such as
// "freezer" and "devices".
func needAnyControllers(cgroup *configs.Cgroup) (bool, error) {
if cgroup == nil {
func needAnyControllers(r *configs.Resources) (bool, error) {
if r == nil {
return false, nil
}
// list of all available controllers
content, err := supportedControllers(cgroup)
content, err := supportedControllers()
if err != nil {
return false, err
}
@@ -39,22 +39,22 @@ func needAnyControllers(cgroup *configs.Cgroup) (bool, error) {
return ok
}
if isPidsSet(cgroup) && have("pids") {
if isPidsSet(r) && have("pids") {
return true, nil
}
if isMemorySet(cgroup) && have("memory") {
if isMemorySet(r) && have("memory") {
return true, nil
}
if isIoSet(cgroup) && have("io") {
if isIoSet(r) && have("io") {
return true, nil
}
if isCpuSet(cgroup) && have("cpu") {
if isCpuSet(r) && have("cpu") {
return true, nil
}
if isCpusetSet(cgroup) && have("cpuset") {
if isCpusetSet(r) && have("cpuset") {
return true, nil
}
if isHugeTlbSet(cgroup) && have("hugetlb") {
if isHugeTlbSet(r) && have("hugetlb") {
return true, nil
}
@@ -64,8 +64,8 @@ func needAnyControllers(cgroup *configs.Cgroup) (bool, error) {
// containsDomainController returns whether the current config contains domain controller or not.
// Refer to: http://man7.org/linux/man-pages/man7/cgroups.7.html
// As at Linux 4.19, the following controllers are threaded: cpu, perf_event, and pids.
func containsDomainController(cg *configs.Cgroup) bool {
return isMemorySet(cg) || isIoSet(cg) || isCpuSet(cg) || isHugeTlbSet(cg)
func containsDomainController(r *configs.Resources) bool {
return isMemorySet(r) || isIoSet(r) || isCpuSet(r) || isHugeTlbSet(r)
}
// CreateCgroupPath creates cgroupv2 path, enabling all the supported controllers.
@@ -74,7 +74,7 @@ func CreateCgroupPath(path string, c *configs.Cgroup) (Err error) {
return fmt.Errorf("invalid cgroup path %s", path)
}
content, err := supportedControllers(c)
content, err := supportedControllers()
if err != nil {
return err
}
@@ -115,7 +115,7 @@ func CreateCgroupPath(path string, c *configs.Cgroup) (Err error) {
// the controllers requested are thread-aware we can simply put the cgroup into
// threaded mode.
case "domain invalid":
if containsDomainController(c) {
if containsDomainController(c.Resources) {
return fmt.Errorf("cannot enter cgroupv2 %q with domain controllers -- it is in an invalid state", current)
} else {
// Not entirely correct (in theory we'd always want to be a domain --
@@ -129,7 +129,7 @@ func CreateCgroupPath(path string, c *configs.Cgroup) (Err error) {
case "domain threaded":
fallthrough
case "threaded":
if containsDomainController(c) {
if containsDomainController(c.Resources) {
return fmt.Errorf("cannot enter cgroupv2 %q with domain controllers -- it is in %s mode", current, cgType)
}
}

View File

@@ -30,7 +30,7 @@ func isRWM(perms devices.Permissions) bool {
// This is similar to the logic applied in crun for handling errors from bpf(2)
// <https://github.com/containers/crun/blob/0.17/src/libcrun/cgroup.c#L2438-L2470>.
func canSkipEBPFError(cgroup *configs.Cgroup) bool {
func canSkipEBPFError(r *configs.Resources) bool {
// If we're running in a user namespace we can ignore eBPF rules because we
// usually cannot use bpf(2), as well as rootless containers usually don't
// have the necessary privileges to mknod(2) device inodes or access
@@ -46,7 +46,7 @@ func canSkipEBPFError(cgroup *configs.Cgroup) bool {
// NOTE: This will sometimes trigger in cases where access modes are split
// between different rules but to handle this correctly would require
// using ".../libcontainer/cgroup/devices".Emulator.
for _, dev := range cgroup.Resources.Devices {
for _, dev := range r.Devices {
if !dev.Allow || !isRWM(dev.Permissions) {
return false
}
@@ -54,15 +54,14 @@ func canSkipEBPFError(cgroup *configs.Cgroup) bool {
return true
}
func setDevices(dirPath string, cgroup *configs.Cgroup) error {
if cgroup.SkipDevices {
func setDevices(dirPath string, r *configs.Resources) error {
if r.SkipDevices {
return nil
}
// XXX: This is currently a white-list (but all callers pass a blacklist of
// devices). This is bad for a whole variety of reasons, but will need
// to be fixed with co-ordinated effort with downstreams.
devices := cgroup.Devices
insts, license, err := devicefilter.DeviceFilter(devices)
insts, license, err := devicefilter.DeviceFilter(r.Devices)
if err != nil {
return err
}
@@ -83,7 +82,7 @@ func setDevices(dirPath string, cgroup *configs.Cgroup) error {
// programs. You could temporarily insert a deny-everything program
// but that would result in spurrious failures during updates.
if _, err := ebpf.LoadAttachCgroupDeviceFilter(insts, license, dirFD); err != nil {
if !canSkipEBPFError(cgroup) {
if !canSkipEBPFError(r) {
return err
}
}

View File

@@ -75,7 +75,7 @@ func (m *manager) Apply(pid int) error {
// - "runc create (rootless + limits + no cgrouppath + no permission) fails with informative error"
if m.rootless {
if m.config.Path == "" {
if blNeed, nErr := needAnyControllers(m.config); nErr == nil && !blNeed {
if blNeed, nErr := needAnyControllers(m.config.Resources); nErr == nil && !blNeed {
return nil
}
return errors.Wrap(err, "rootless needs no limits + no cgrouppath when no permission is granted for cgroups")
@@ -103,43 +103,27 @@ func (m *manager) GetStats() (*cgroups.Stats, error) {
)
st := cgroups.NewStats()
if err := m.getControllers(); err != nil {
return st, err
}
// pids (since kernel 4.5)
if _, ok := m.controllers["pids"]; ok {
if err := statPids(m.dirPath, st); err != nil {
errs = append(errs, err)
}
} else {
if err := statPidsWithoutController(m.dirPath, st); err != nil {
errs = append(errs, err)
}
if err := statPids(m.dirPath, st); err != nil {
errs = append(errs, err)
}
// memory (since kernel 4.5)
if _, ok := m.controllers["memory"]; ok {
if err := statMemory(m.dirPath, st); err != nil {
errs = append(errs, err)
}
if err := statMemory(m.dirPath, st); err != nil && !os.IsNotExist(err) {
errs = append(errs, err)
}
// io (since kernel 4.5)
if _, ok := m.controllers["io"]; ok {
if err := statIo(m.dirPath, st); err != nil {
errs = append(errs, err)
}
if err := statIo(m.dirPath, st); err != nil && !os.IsNotExist(err) {
errs = append(errs, err)
}
// cpu (since kernel 4.15)
if _, ok := m.controllers["cpu"]; ok {
if err := statCpu(m.dirPath, st); err != nil {
errs = append(errs, err)
}
// Note cpu.stat is available even if the controller is not enabled.
if err := statCpu(m.dirPath, st); err != nil && !os.IsNotExist(err) {
errs = append(errs, err)
}
// hugetlb (since kernel 5.6)
if _, ok := m.controllers["hugetlb"]; ok {
if err := statHugeTlb(m.dirPath, st); err != nil {
errs = append(errs, err)
}
if err := statHugeTlb(m.dirPath, st); err != nil && !os.IsNotExist(err) {
errs = append(errs, err)
}
if len(errs) > 0 && !m.rootless {
return st, errors.Errorf("error while statting cgroup v2: %+v", errs)
@@ -163,53 +147,50 @@ func (m *manager) Path(_ string) string {
return m.dirPath
}
func (m *manager) Set(container *configs.Config) error {
if container == nil || container.Cgroups == nil {
return nil
}
func (m *manager) Set(r *configs.Resources) error {
if err := m.getControllers(); err != nil {
return err
}
// pids (since kernel 4.5)
if err := setPids(m.dirPath, container.Cgroups); err != nil {
if err := setPids(m.dirPath, r); err != nil {
return err
}
// memory (since kernel 4.5)
if err := setMemory(m.dirPath, container.Cgroups); err != nil {
if err := setMemory(m.dirPath, r); err != nil {
return err
}
// io (since kernel 4.5)
if err := setIo(m.dirPath, container.Cgroups); err != nil {
if err := setIo(m.dirPath, r); err != nil {
return err
}
// cpu (since kernel 4.15)
if err := setCpu(m.dirPath, container.Cgroups); err != nil {
if err := setCpu(m.dirPath, r); err != nil {
return err
}
// devices (since kernel 4.15, pseudo-controller)
//
// When m.Rootless is true, errors from the device subsystem are ignored because it is really not expected to work.
// When m.rootless is true, errors from the device subsystem are ignored because it is really not expected to work.
// However, errors from other subsystems are not ignored.
// see @test "runc create (rootless + limits + no cgrouppath + no permission) fails with informative error"
if err := setDevices(m.dirPath, container.Cgroups); err != nil && !m.rootless {
if err := setDevices(m.dirPath, r); err != nil && !m.rootless {
return err
}
// cpuset (since kernel 5.0)
if err := setCpuset(m.dirPath, container.Cgroups); err != nil {
if err := setCpuset(m.dirPath, r); err != nil {
return err
}
// hugetlb (since kernel 5.6)
if err := setHugeTlb(m.dirPath, container.Cgroups); err != nil {
if err := setHugeTlb(m.dirPath, r); err != nil {
return err
}
// freezer (since kernel 5.2, pseudo-controller)
if err := setFreezer(m.dirPath, container.Cgroups.Freezer); err != nil {
if err := setFreezer(m.dirPath, r.Freezer); err != nil {
return err
}
if err := m.setUnified(container.Cgroups.Unified); err != nil {
if err := m.setUnified(r.Unified); err != nil {
return err
}
m.config = container.Cgroups
m.config.Resources = r
return nil
}
@@ -263,5 +244,10 @@ func OOMKillCount(path string) (uint64, error) {
}
func (m *manager) OOMKillCount() (uint64, error) {
return OOMKillCount(m.dirPath)
c, err := OOMKillCount(m.dirPath)
if err != nil && m.rootless && os.IsNotExist(err) {
err = nil
}
return c, err
}

View File

@@ -12,15 +12,15 @@ import (
"github.com/opencontainers/runc/libcontainer/configs"
)
func isHugeTlbSet(cgroup *configs.Cgroup) bool {
return len(cgroup.Resources.HugetlbLimit) > 0
func isHugeTlbSet(r *configs.Resources) bool {
return len(r.HugetlbLimit) > 0
}
func setHugeTlb(dirPath string, cgroup *configs.Cgroup) error {
if !isHugeTlbSet(cgroup) {
func setHugeTlb(dirPath string, r *configs.Resources) error {
if !isHugeTlbSet(r) {
return nil
}
for _, hugetlb := range cgroup.Resources.HugetlbLimit {
for _, hugetlb := range r.HugetlbLimit {
if err := fscommon.WriteFile(dirPath, "hugetlb."+hugetlb.Pagesize+".max", strconv.FormatUint(hugetlb.Limit, 10)); err != nil {
return err
}

View File

@@ -13,50 +13,50 @@ import (
"github.com/opencontainers/runc/libcontainer/configs"
)
func isIoSet(cgroup *configs.Cgroup) bool {
return cgroup.Resources.BlkioWeight != 0 ||
len(cgroup.Resources.BlkioThrottleReadBpsDevice) > 0 ||
len(cgroup.Resources.BlkioThrottleWriteBpsDevice) > 0 ||
len(cgroup.Resources.BlkioThrottleReadIOPSDevice) > 0 ||
len(cgroup.Resources.BlkioThrottleWriteIOPSDevice) > 0
func isIoSet(r *configs.Resources) bool {
return r.BlkioWeight != 0 ||
len(r.BlkioThrottleReadBpsDevice) > 0 ||
len(r.BlkioThrottleWriteBpsDevice) > 0 ||
len(r.BlkioThrottleReadIOPSDevice) > 0 ||
len(r.BlkioThrottleWriteIOPSDevice) > 0
}
func setIo(dirPath string, cgroup *configs.Cgroup) error {
if !isIoSet(cgroup) {
func setIo(dirPath string, r *configs.Resources) error {
if !isIoSet(r) {
return nil
}
if cgroup.Resources.BlkioWeight != 0 {
if r.BlkioWeight != 0 {
filename := "io.bfq.weight"
if err := fscommon.WriteFile(dirPath, filename,
strconv.FormatUint(uint64(cgroup.Resources.BlkioWeight), 10)); err != nil {
strconv.FormatUint(uint64(r.BlkioWeight), 10)); err != nil {
// if io.bfq.weight does not exist, then bfq module is not loaded.
// Fallback to use io.weight with a conversion scheme
if !os.IsNotExist(err) {
return err
}
v := cgroups.ConvertBlkIOToIOWeightValue(cgroup.Resources.BlkioWeight)
v := cgroups.ConvertBlkIOToIOWeightValue(r.BlkioWeight)
if err := fscommon.WriteFile(dirPath, "io.weight", strconv.FormatUint(v, 10)); err != nil {
return err
}
}
}
for _, td := range cgroup.Resources.BlkioThrottleReadBpsDevice {
for _, td := range r.BlkioThrottleReadBpsDevice {
if err := fscommon.WriteFile(dirPath, "io.max", td.StringName("rbps")); err != nil {
return err
}
}
for _, td := range cgroup.Resources.BlkioThrottleWriteBpsDevice {
for _, td := range r.BlkioThrottleWriteBpsDevice {
if err := fscommon.WriteFile(dirPath, "io.max", td.StringName("wbps")); err != nil {
return err
}
}
for _, td := range cgroup.Resources.BlkioThrottleReadIOPSDevice {
for _, td := range r.BlkioThrottleReadIOPSDevice {
if err := fscommon.WriteFile(dirPath, "io.max", td.StringName("riops")); err != nil {
return err
}
}
for _, td := range cgroup.Resources.BlkioThrottleWriteIOPSDevice {
for _, td := range r.BlkioThrottleWriteIOPSDevice {
if err := fscommon.WriteFile(dirPath, "io.max", td.StringName("wiops")); err != nil {
return err
}

View File

@@ -4,13 +4,16 @@ package fs2
import (
"bufio"
"math"
"os"
"strconv"
"strings"
"github.com/opencontainers/runc/libcontainer/cgroups"
"github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
"github.com/opencontainers/runc/libcontainer/configs"
"github.com/pkg/errors"
"golang.org/x/sys/unix"
)
// numToStr converts an int64 value to a string for writing to a
@@ -30,21 +33,20 @@ func numToStr(value int64) (ret string) {
return ret
}
func isMemorySet(cgroup *configs.Cgroup) bool {
return cgroup.Resources.MemoryReservation != 0 ||
cgroup.Resources.Memory != 0 || cgroup.Resources.MemorySwap != 0
func isMemorySet(r *configs.Resources) bool {
return r.MemoryReservation != 0 || r.Memory != 0 || r.MemorySwap != 0
}
func setMemory(dirPath string, cgroup *configs.Cgroup) error {
if !isMemorySet(cgroup) {
func setMemory(dirPath string, r *configs.Resources) error {
if !isMemorySet(r) {
return nil
}
swap, err := cgroups.ConvertMemorySwapToCgroupV2Value(cgroup.Resources.MemorySwap, cgroup.Resources.Memory)
swap, err := cgroups.ConvertMemorySwapToCgroupV2Value(r.MemorySwap, r.Memory)
if err != nil {
return err
}
swapStr := numToStr(swap)
if swapStr == "" && swap == 0 && cgroup.Resources.MemorySwap > 0 {
if swapStr == "" && swap == 0 && r.MemorySwap > 0 {
// memory and memorySwap set to the same value -- disable swap
swapStr = "0"
}
@@ -55,7 +57,7 @@ func setMemory(dirPath string, cgroup *configs.Cgroup) error {
}
}
if val := numToStr(cgroup.Resources.Memory); val != "" {
if val := numToStr(r.Memory); val != "" {
if err := fscommon.WriteFile(dirPath, "memory.max", val); err != nil {
return err
}
@@ -63,7 +65,7 @@ func setMemory(dirPath string, cgroup *configs.Cgroup) error {
// cgroup.Resources.KernelMemory is ignored
if val := numToStr(cgroup.Resources.MemoryReservation); val != "" {
if val := numToStr(r.MemoryReservation); val != "" {
if err := fscommon.WriteFile(dirPath, "memory.low", val); err != nil {
return err
}
@@ -89,9 +91,17 @@ func statMemory(dirPath string, stats *cgroups.Stats) error {
stats.MemoryStats.Stats[t] = v
}
stats.MemoryStats.Cache = stats.MemoryStats.Stats["file"]
// Unlike cgroup v1 which has memory.use_hierarchy binary knob,
// cgroup v2 is always hierarchical.
stats.MemoryStats.UseHierarchy = true
memoryUsage, err := getMemoryDataV2(dirPath, "")
if err != nil {
if errors.Is(err, unix.ENOENT) && dirPath == UnifiedMountpoint {
// The root cgroup does not have memory.{current,max}
// so emulate those using data from /proc/meminfo.
return statsFromMeminfo(stats)
}
return err
}
stats.MemoryStats.Usage = memoryUsage
@@ -99,9 +109,15 @@ func statMemory(dirPath string, stats *cgroups.Stats) error {
if err != nil {
return err
}
// As cgroup v1 reports SwapUsage values as mem+swap combined,
// while in cgroup v2 swap values do not include memory,
// report combined mem+swap for v1 compatibility.
swapUsage.Usage += memoryUsage.Usage
if swapUsage.Limit != math.MaxUint64 {
swapUsage.Limit += memoryUsage.Limit
}
stats.MemoryStats.SwapUsage = swapUsage
stats.MemoryStats.UseHierarchy = true
return nil
}
@@ -117,7 +133,10 @@ func getMemoryDataV2(path, name string) (cgroups.MemoryData, error) {
value, err := fscommon.GetCgroupParamUint(path, usage)
if err != nil {
if moduleName != "memory" && os.IsNotExist(err) {
if name != "" && os.IsNotExist(err) {
// Ignore EEXIST as there's no swap accounting
// if kernel CONFIG_MEMCG_SWAP is not set or
// swapaccount=0 kernel boot parameter is given.
return cgroups.MemoryData{}, nil
}
return cgroups.MemoryData{}, errors.Wrapf(err, "failed to parse %s", usage)
@@ -126,12 +145,69 @@ func getMemoryDataV2(path, name string) (cgroups.MemoryData, error) {
value, err = fscommon.GetCgroupParamUint(path, limit)
if err != nil {
if moduleName != "memory" && os.IsNotExist(err) {
return cgroups.MemoryData{}, nil
}
return cgroups.MemoryData{}, errors.Wrapf(err, "failed to parse %s", limit)
}
memoryData.Limit = value
return memoryData, nil
}
func statsFromMeminfo(stats *cgroups.Stats) error {
f, err := os.Open("/proc/meminfo")
if err != nil {
return err
}
defer f.Close()
// Fields we are interested in.
var (
swap_free uint64
swap_total uint64
main_total uint64
main_free uint64
)
mem := map[string]*uint64{
"SwapFree": &swap_free,
"SwapTotal": &swap_total,
"MemTotal": &main_total,
"MemFree": &main_free,
}
found := 0
sc := bufio.NewScanner(f)
for sc.Scan() {
parts := strings.SplitN(sc.Text(), ":", 3)
if len(parts) != 2 {
// Should not happen.
continue
}
k := parts[0]
p, ok := mem[k]
if !ok {
// Unknown field -- not interested.
continue
}
vStr := strings.TrimSpace(strings.TrimSuffix(parts[1], " kB"))
*p, err = strconv.ParseUint(vStr, 10, 64)
if err != nil {
return errors.Wrap(err, "parsing /proc/meminfo "+k)
}
found++
if found == len(mem) {
// Got everything we need -- skip the rest.
break
}
}
if sc.Err() != nil {
return sc.Err()
}
stats.MemoryStats.SwapUsage.Usage = (swap_total - swap_free) * 1024
stats.MemoryStats.SwapUsage.Limit = math.MaxUint64
stats.MemoryStats.Usage.Usage = (main_total - main_free) * 1024
stats.MemoryStats.Usage.Limit = math.MaxUint64
return nil
}

View File

@@ -3,6 +3,7 @@
package fs2
import (
"os"
"path/filepath"
"strings"
@@ -13,15 +14,15 @@ import (
"golang.org/x/sys/unix"
)
func isPidsSet(cgroup *configs.Cgroup) bool {
return cgroup.Resources.PidsLimit != 0
func isPidsSet(r *configs.Resources) bool {
return r.PidsLimit != 0
}
func setPids(dirPath string, cgroup *configs.Cgroup) error {
if !isPidsSet(cgroup) {
func setPids(dirPath string, r *configs.Resources) error {
if !isPidsSet(r) {
return nil
}
if val := numToStr(cgroup.Resources.PidsLimit); val != "" {
if val := numToStr(r.PidsLimit); val != "" {
if err := fscommon.WriteFile(dirPath, "pids.max", val); err != nil {
return err
}
@@ -30,7 +31,7 @@ func setPids(dirPath string, cgroup *configs.Cgroup) error {
return nil
}
func statPidsWithoutController(dirPath string, stats *cgroups.Stats) error {
func statPidsFromCgroupProcs(dirPath string, stats *cgroups.Stats) error {
// if the controller is not enabled, let's read PIDS from cgroups.procs
// (or threads if cgroup.threads is enabled)
contents, err := fscommon.ReadFile(dirPath, "cgroup.procs")
@@ -40,13 +41,8 @@ func statPidsWithoutController(dirPath string, stats *cgroups.Stats) error {
if err != nil {
return err
}
pids := make(map[string]string)
for _, i := range strings.Split(contents, "\n") {
if i != "" {
pids[i] = i
}
}
stats.PidsStats.Current = uint64(len(pids))
pids := strings.Count(contents, "\n")
stats.PidsStats.Current = uint64(pids)
stats.PidsStats.Limit = 0
return nil
}
@@ -54,6 +50,9 @@ func statPidsWithoutController(dirPath string, stats *cgroups.Stats) error {
func statPids(dirPath string, stats *cgroups.Stats) error {
current, err := fscommon.GetCgroupParamUint(dirPath, "pids.current")
if err != nil {
if os.IsNotExist(err) {
return statPidsFromCgroupProcs(dirPath, stats)
}
return errors.Wrap(err, "failed to parse pids.current")
}

View File

@@ -5,7 +5,6 @@ import (
"strings"
"sync"
securejoin "github.com/cyphar/filepath-securejoin"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
@@ -17,7 +16,7 @@ const (
)
var (
// Set to true by fs unit tests
// TestMode is set to true by unit tests that need "fake" cgroupfs.
TestMode bool
cgroupFd int = -1
@@ -71,12 +70,12 @@ func OpenFile(dir, file string, flags int) (*os.File, error) {
flags |= os.O_TRUNC | os.O_CREATE
mode = 0o600
}
if prepareOpenat2() != nil {
return openFallback(dir, file, flags, mode)
}
reldir := strings.TrimPrefix(dir, cgroupfsPrefix)
if len(reldir) == len(dir) { // non-standard path, old system?
return openWithSecureJoin(dir, file, flags, mode)
}
if prepareOpenat2() != nil {
return openWithSecureJoin(dir, file, flags, mode)
return openFallback(dir, file, flags, mode)
}
relname := reldir + "/" + file
@@ -93,11 +92,29 @@ func OpenFile(dir, file string, flags int) (*os.File, error) {
return os.NewFile(uintptr(fd), cgroupfsPrefix+relname), nil
}
func openWithSecureJoin(dir, file string, flags int, mode os.FileMode) (*os.File, error) {
path, err := securejoin.SecureJoin(dir, file)
var errNotCgroupfs = errors.New("not a cgroup file")
// openFallback is used when openat2(2) is not available. It checks the opened
// file is on cgroupfs, returning an error otherwise.
func openFallback(dir, file string, flags int, mode os.FileMode) (*os.File, error) {
path := dir + "/" + file
fd, err := os.OpenFile(path, flags, mode)
if err != nil {
return nil, err
}
if TestMode {
return fd, nil
}
// Check this is a cgroupfs file.
var st unix.Statfs_t
if err := unix.Fstatfs(int(fd.Fd()), &st); err != nil {
_ = fd.Close()
return nil, &os.PathError{Op: "statfs", Path: path, Err: err}
}
if st.Type != unix.CGROUP_SUPER_MAGIC && st.Type != unix.CGROUP2_SUPER_MAGIC {
_ = fd.Close()
return nil, &os.PathError{Op: "open", Path: path, Err: errNotCgroupfs}
}
return os.OpenFile(path, flags, mode)
return fd, nil
}

View File

@@ -2,6 +2,7 @@ package systemd
import (
"bufio"
"context"
"fmt"
"math"
"os"
@@ -28,10 +29,6 @@ const (
)
var (
connOnce sync.Once
connDbus *systemdDbus.Conn
connErr error
versionOnce sync.Once
version int
@@ -291,19 +288,6 @@ func generateDeviceProperties(rules []*devices.Rule) ([]systemdDbus.Property, er
return properties, nil
}
// getDbusConnection lazy initializes systemd dbus connection
// and returns it
func getDbusConnection(rootless bool) (*systemdDbus.Conn, error) {
connOnce.Do(func() {
if rootless {
connDbus, connErr = NewUserSystemdDbus()
} else {
connDbus, connErr = systemdDbus.New()
}
})
return connDbus, connErr
}
func newProp(name string, units interface{}) systemdDbus.Property {
return systemdDbus.Property{
Name: name,
@@ -319,32 +303,42 @@ func getUnitName(c *configs.Cgroup) string {
return c.Name
}
// isUnitExists returns true if the error is that a systemd unit already exists.
func isUnitExists(err error) bool {
// isDbusError returns true if the error is a specific dbus error.
func isDbusError(err error, name string) bool {
if err != nil {
if dbusError, ok := err.(dbus.Error); ok {
return strings.Contains(dbusError.Name, "org.freedesktop.systemd1.UnitExists")
var derr *dbus.Error
if errors.As(err, &derr) {
return strings.Contains(derr.Name, name)
}
}
return false
}
func startUnit(dbusConnection *systemdDbus.Conn, unitName string, properties []systemdDbus.Property) error {
// isUnitExists returns true if the error is that a systemd unit already exists.
func isUnitExists(err error) bool {
return isDbusError(err, "org.freedesktop.systemd1.UnitExists")
}
func startUnit(cm *dbusConnManager, unitName string, properties []systemdDbus.Property) error {
statusChan := make(chan string, 1)
if _, err := dbusConnection.StartTransientUnit(unitName, "replace", properties, statusChan); err == nil {
err := cm.retryOnDisconnect(func(c *systemdDbus.Conn) error {
_, err := c.StartTransientUnitContext(context.TODO(), unitName, "replace", properties, statusChan)
return err
})
if err == nil {
timeout := time.NewTimer(30 * time.Second)
defer timeout.Stop()
select {
case s := <-statusChan:
close(statusChan)
// Please refer to https://godoc.org/github.com/coreos/go-systemd/dbus#Conn.StartUnit
// Please refer to https://pkg.go.dev/github.com/coreos/go-systemd/v22/dbus#Conn.StartUnit
if s != "done" {
dbusConnection.ResetFailedUnit(unitName)
resetFailedUnit(cm, unitName)
return errors.Errorf("error creating systemd unit `%s`: got `%s`", unitName, s)
}
case <-timeout.C:
dbusConnection.ResetFailedUnit(unitName)
resetFailedUnit(cm, unitName)
return errors.New("Timeout waiting for systemd to create " + unitName)
}
} else if !isUnitExists(err) {
@@ -354,13 +348,17 @@ func startUnit(dbusConnection *systemdDbus.Conn, unitName string, properties []s
return nil
}
func stopUnit(dbusConnection *systemdDbus.Conn, unitName string) error {
func stopUnit(cm *dbusConnManager, unitName string) error {
statusChan := make(chan string, 1)
if _, err := dbusConnection.StopUnit(unitName, "replace", statusChan); err == nil {
err := cm.retryOnDisconnect(func(c *systemdDbus.Conn) error {
_, err := c.StopUnitContext(context.TODO(), unitName, "replace", statusChan)
return err
})
if err == nil {
select {
case s := <-statusChan:
close(statusChan)
// Please refer to https://godoc.org/github.com/coreos/go-systemd/dbus#Conn.StartUnit
// Please refer to https://godoc.org/github.com/coreos/go-systemd/v22/dbus#Conn.StartUnit
if s != "done" {
logrus.Warnf("error removing unit `%s`: got `%s`. Continuing...", unitName, s)
}
@@ -371,10 +369,38 @@ func stopUnit(dbusConnection *systemdDbus.Conn, unitName string) error {
return nil
}
func systemdVersion(conn *systemdDbus.Conn) int {
func resetFailedUnit(cm *dbusConnManager, name string) {
err := cm.retryOnDisconnect(func(c *systemdDbus.Conn) error {
return c.ResetFailedUnitContext(context.TODO(), name)
})
if err != nil {
logrus.Warnf("unable to reset failed unit: %v", err)
}
}
func setUnitProperties(cm *dbusConnManager, name string, properties ...systemdDbus.Property) error {
return cm.retryOnDisconnect(func(c *systemdDbus.Conn) error {
return c.SetUnitPropertiesContext(context.TODO(), name, true, properties...)
})
}
func getManagerProperty(cm *dbusConnManager, name string) (string, error) {
str := ""
err := cm.retryOnDisconnect(func(c *systemdDbus.Conn) error {
var err error
str, err = c.GetManagerProperty(name)
return err
})
if err != nil {
return "", err
}
return strconv.Unquote(str)
}
func systemdVersion(cm *dbusConnManager) int {
versionOnce.Do(func() {
version = -1
verStr, err := conn.GetManagerProperty("Version")
verStr, err := getManagerProperty(cm, "Version")
if err == nil {
version, err = systemdVersionAtoi(verStr)
}
@@ -389,11 +415,11 @@ func systemdVersion(conn *systemdDbus.Conn) int {
func systemdVersionAtoi(verStr string) (int, error) {
// verStr should be of the form:
// "v245.4-1.fc32", "245", "v245-1.fc32", "245-1.fc32"
// all the input strings include quotes, and the output int should be 245
// thus, we unconditionally remove the `"v`
// and then match on the first integer we can grab
re := regexp.MustCompile(`"?v?([0-9]+)`)
// "v245.4-1.fc32", "245", "v245-1.fc32", "245-1.fc32" (without quotes).
// The result for all of the above should be 245.
// Thus, we unconditionally remove the "v" prefix
// and then match on the first integer we can grab.
re := regexp.MustCompile(`v?([0-9]+)`)
matches := re.FindStringSubmatch(verStr)
if len(matches) < 2 {
return 0, errors.Errorf("can't parse version %s: incorrect number of matches %v", verStr, matches)
@@ -402,10 +428,10 @@ func systemdVersionAtoi(verStr string) (int, error) {
return ver, errors.Wrapf(err, "can't parse version %s", verStr)
}
func addCpuQuota(conn *systemdDbus.Conn, properties *[]systemdDbus.Property, quota int64, period uint64) {
func addCpuQuota(cm *dbusConnManager, properties *[]systemdDbus.Property, quota int64, period uint64) {
if period != 0 {
// systemd only supports CPUQuotaPeriodUSec since v242
sdVer := systemdVersion(conn)
sdVer := systemdVersion(cm)
if sdVer >= 242 {
*properties = append(*properties,
newProp("CPUQuotaPeriodUSec", period))
@@ -436,13 +462,13 @@ func addCpuQuota(conn *systemdDbus.Conn, properties *[]systemdDbus.Property, quo
}
}
func addCpuset(conn *systemdDbus.Conn, props *[]systemdDbus.Property, cpus, mems string) error {
func addCpuset(cm *dbusConnManager, props *[]systemdDbus.Property, cpus, mems string) error {
if cpus == "" && mems == "" {
return nil
}
// systemd only supports AllowedCPUs/AllowedMemoryNodes since v244
sdVer := systemdVersion(conn)
sdVer := systemdVersion(cm)
if sdVer < 244 {
logrus.Debugf("systemd v%d is too old to support AllowedCPUs/AllowedMemoryNodes"+
" (settings will still be applied to cgroupfs)", sdVer)

View File

@@ -0,0 +1,96 @@
// +build linux
package systemd
import (
"context"
"sync"
systemdDbus "github.com/coreos/go-systemd/v22/dbus"
dbus "github.com/godbus/dbus/v5"
)
var (
dbusC *systemdDbus.Conn
dbusMu sync.RWMutex
dbusInited bool
dbusRootless bool
)
type dbusConnManager struct {
}
// newDbusConnManager initializes systemd dbus connection manager.
func newDbusConnManager(rootless bool) *dbusConnManager {
if dbusInited && rootless != dbusRootless {
panic("can't have both root and rootless dbus")
}
dbusRootless = rootless
return &dbusConnManager{}
}
// getConnection lazily initializes and returns systemd dbus connection.
func (d *dbusConnManager) getConnection() (*systemdDbus.Conn, error) {
// In the case where dbusC != nil
// Use the read lock the first time to ensure
// that Conn can be acquired at the same time.
dbusMu.RLock()
if conn := dbusC; conn != nil {
dbusMu.RUnlock()
return conn, nil
}
dbusMu.RUnlock()
// In the case where dbusC == nil
// Use write lock to ensure that only one
// will be created
dbusMu.Lock()
defer dbusMu.Unlock()
if conn := dbusC; conn != nil {
return conn, nil
}
conn, err := d.newConnection()
if err != nil {
return nil, err
}
dbusC = conn
return conn, nil
}
func (d *dbusConnManager) newConnection() (*systemdDbus.Conn, error) {
if dbusRootless {
return newUserSystemdDbus()
}
return systemdDbus.NewWithContext(context.TODO())
}
// resetConnection resets the connection to its initial state
// (so it can be reconnected if necessary).
func (d *dbusConnManager) resetConnection(conn *systemdDbus.Conn) {
dbusMu.Lock()
defer dbusMu.Unlock()
if dbusC != nil && dbusC == conn {
dbusC.Close()
dbusC = nil
}
}
var errDbusConnClosed = dbus.ErrClosed.Error()
// retryOnDisconnect calls op, and if the error it returns is about closed dbus
// connection, the connection is re-established and the op is retried. This helps
// with the situation when dbus is restarted and we have a stale connection.
func (d *dbusConnManager) retryOnDisconnect(op func(*systemdDbus.Conn) error) error {
for {
conn, err := d.getConnection()
if err != nil {
return err
}
err = op(conn)
if !isDbusError(err, errDbusConnClosed) {
return err
}
d.resetConnection(conn)
}
}

View File

@@ -17,8 +17,8 @@ import (
"github.com/pkg/errors"
)
// NewUserSystemdDbus creates a connection for systemd user-instance.
func NewUserSystemdDbus() (*systemdDbus.Conn, error) {
// newUserSystemdDbus creates a connection for systemd user-instance.
func newUserSystemdDbus() (*systemdDbus.Conn, error) {
addr, err := DetectUserDbusSessionBusAddress()
if err != nil {
return nil, err

View File

@@ -20,12 +20,14 @@ type legacyManager struct {
mu sync.Mutex
cgroups *configs.Cgroup
paths map[string]string
dbus *dbusConnManager
}
func NewLegacyManager(cg *configs.Cgroup, paths map[string]string) cgroups.Manager {
return &legacyManager{
cgroups: cg,
paths: paths,
dbus: newDbusConnManager(false),
}
}
@@ -34,8 +36,8 @@ type subsystem interface {
Name() string
// Returns the stats, as 'stats', corresponding to the cgroup under 'path'.
GetStats(path string, stats *cgroups.Stats) error
// Set the cgroup represented by cgroup.
Set(path string, cgroup *configs.Cgroup) error
// Set sets cgroup resource limits.
Set(path string, r *configs.Resources) error
}
var errSubsystemDoesNotExist = errors.New("cgroup: subsystem does not exist")
@@ -56,9 +58,8 @@ var legacySubsystems = []subsystem{
&fs.NameGroup{GroupName: "name=systemd"},
}
func genV1ResourcesProperties(c *configs.Cgroup, conn *systemdDbus.Conn) ([]systemdDbus.Property, error) {
func genV1ResourcesProperties(r *configs.Resources, cm *dbusConnManager) ([]systemdDbus.Property, error) {
var properties []systemdDbus.Property
r := c.Resources
deviceProperties, err := generateDeviceProperties(r.Devices)
if err != nil {
@@ -76,7 +77,7 @@ func genV1ResourcesProperties(c *configs.Cgroup, conn *systemdDbus.Conn) ([]syst
newProp("CPUShares", r.CpuShares))
}
addCpuQuota(conn, &properties, r.CpuQuota, r.CpuPeriod)
addCpuQuota(cm, &properties, r.CpuQuota, r.CpuPeriod)
if r.BlkioWeight != 0 {
properties = append(properties,
@@ -88,7 +89,7 @@ func genV1ResourcesProperties(c *configs.Cgroup, conn *systemdDbus.Conn) ([]syst
newProp("TasksMax", uint64(r.PidsLimit)))
}
err = addCpuset(conn, &properties, r.CpusetCpus, r.CpusetMems)
err = addCpuset(cm, &properties, r.CpusetCpus, r.CpusetMems)
if err != nil {
return nil, err
}
@@ -164,13 +165,9 @@ func (m *legacyManager) Apply(pid int) error {
properties = append(properties,
newProp("DefaultDependencies", false))
dbusConnection, err := getDbusConnection(false)
if err != nil {
return err
}
properties = append(properties, c.SystemdProps...)
if err := startUnit(dbusConnection, unitName, properties); err != nil {
if err := startUnit(m.dbus, unitName, properties); err != nil {
return err
}
@@ -208,13 +205,8 @@ func (m *legacyManager) Destroy() error {
m.mu.Lock()
defer m.mu.Unlock()
dbusConnection, err := getDbusConnection(false)
if err != nil {
return err
}
unitName := getUnitName(m.cgroups)
stopErr := stopUnit(m.dbus, getUnitName(m.cgroups))
stopErr := stopUnit(dbusConnection, unitName)
// Both on success and on error, cleanup all the cgroups we are aware of.
// Some of them were created directly by Apply() and are not managed by systemd.
if err := cgroups.RemovePaths(m.paths); err != nil {
@@ -239,7 +231,7 @@ func (m *legacyManager) joinCgroups(pid int) error {
case "cpuset":
if path, ok := m.paths[name]; ok {
s := &fs.CpusetGroup{}
if err := s.ApplyDir(path, m.cgroups, pid); err != nil {
if err := s.ApplyDir(path, m.cgroups.Resources, pid); err != nil {
return err
}
}
@@ -292,7 +284,7 @@ func (m *legacyManager) Freeze(state configs.FreezerState) error {
prevState := m.cgroups.Resources.Freezer
m.cgroups.Resources.Freezer = state
freezer := &fs.FreezerGroup{}
if err := freezer.Set(path, m.cgroups); err != nil {
if err := freezer.Set(path, m.cgroups.Resources); err != nil {
m.cgroups.Resources.Freezer = prevState
return err
}
@@ -332,20 +324,16 @@ func (m *legacyManager) GetStats() (*cgroups.Stats, error) {
return stats, nil
}
func (m *legacyManager) Set(container *configs.Config) error {
func (m *legacyManager) Set(r *configs.Resources) error {
// If Paths are set, then we are just joining cgroups paths
// and there is no need to set any values.
if m.cgroups.Paths != nil {
return nil
}
if container.Cgroups.Resources.Unified != nil {
if r.Unified != nil {
return cgroups.ErrV1NoUnified
}
dbusConnection, err := getDbusConnection(false)
if err != nil {
return err
}
properties, err := genV1ResourcesProperties(container.Cgroups, dbusConnection)
properties, err := genV1ResourcesProperties(r, m.dbus)
if err != nil {
return err
}
@@ -373,7 +361,7 @@ func (m *legacyManager) Set(container *configs.Config) error {
}
}
if err := dbusConnection.SetUnitProperties(getUnitName(container.Cgroups), true, properties...); err != nil {
if err := setUnitProperties(m.dbus, getUnitName(m.cgroups), properties...); err != nil {
_ = m.Freeze(targetFreezerState)
return err
}
@@ -388,7 +376,7 @@ func (m *legacyManager) Set(container *configs.Config) error {
if !ok {
continue
}
if err := sys.Set(path, container.Cgroups); err != nil {
if err := sys.Set(path, r); err != nil {
return err
}
}

View File

@@ -26,6 +26,7 @@ type unifiedManager struct {
// path is like "/sys/fs/cgroup/user.slice/user-1001.slice/session-1.scope"
path string
rootless bool
dbus *dbusConnManager
}
func NewUnifiedManager(config *configs.Cgroup, path string, rootless bool) cgroups.Manager {
@@ -33,6 +34,7 @@ func NewUnifiedManager(config *configs.Cgroup, path string, rootless bool) cgrou
cgroups: config,
path: path,
rootless: rootless,
dbus: newDbusConnManager(rootless),
}
}
@@ -45,7 +47,7 @@ func NewUnifiedManager(config *configs.Cgroup, path string, rootless bool) cgrou
// For the list of keys, see https://www.kernel.org/doc/Documentation/cgroup-v2.txt
//
// For the list of systemd unit properties, see systemd.resource-control(5).
func unifiedResToSystemdProps(conn *systemdDbus.Conn, res map[string]string) (props []systemdDbus.Property, _ error) {
func unifiedResToSystemdProps(cm *dbusConnManager, res map[string]string) (props []systemdDbus.Property, _ error) {
var err error
for k, v := range res {
@@ -83,7 +85,7 @@ func unifiedResToSystemdProps(conn *systemdDbus.Conn, res map[string]string) (pr
return nil, fmt.Errorf("unified resource %q quota value conversion error: %w", k, err)
}
}
addCpuQuota(conn, &props, quota, period)
addCpuQuota(cm, &props, quota, period)
case "cpu.weight":
num, err := strconv.ParseUint(v, 10, 64)
@@ -103,7 +105,7 @@ func unifiedResToSystemdProps(conn *systemdDbus.Conn, res map[string]string) (pr
"cpuset.mems": "AllowedMemoryNodes",
}
// systemd only supports these properties since v244
sdVer := systemdVersion(conn)
sdVer := systemdVersion(cm)
if sdVer >= 244 {
props = append(props,
newProp(m[k], bits))
@@ -162,9 +164,8 @@ func unifiedResToSystemdProps(conn *systemdDbus.Conn, res map[string]string) (pr
return props, nil
}
func genV2ResourcesProperties(c *configs.Cgroup, conn *systemdDbus.Conn) ([]systemdDbus.Property, error) {
func genV2ResourcesProperties(r *configs.Resources, cm *dbusConnManager) ([]systemdDbus.Property, error) {
var properties []systemdDbus.Property
r := c.Resources
// NOTE: This is of questionable correctness because we insert our own
// devices eBPF program later. Two programs with identical rules
@@ -200,14 +201,14 @@ func genV2ResourcesProperties(c *configs.Cgroup, conn *systemdDbus.Conn) ([]syst
newProp("CPUWeight", r.CpuWeight))
}
addCpuQuota(conn, &properties, r.CpuQuota, r.CpuPeriod)
addCpuQuota(cm, &properties, r.CpuQuota, r.CpuPeriod)
if r.PidsLimit > 0 || r.PidsLimit == -1 {
properties = append(properties,
newProp("TasksMax", uint64(r.PidsLimit)))
}
err = addCpuset(conn, &properties, r.CpusetCpus, r.CpusetMems)
err = addCpuset(cm, &properties, r.CpusetCpus, r.CpusetMems)
if err != nil {
return nil, err
}
@@ -216,7 +217,7 @@ func genV2ResourcesProperties(c *configs.Cgroup, conn *systemdDbus.Conn) ([]syst
// convert Resources.Unified map to systemd properties
if r.Unified != nil {
unifiedProps, err := unifiedResToSystemdProps(conn, r.Unified)
unifiedProps, err := unifiedResToSystemdProps(cm, r.Unified)
if err != nil {
return nil, err
}
@@ -279,17 +280,13 @@ func (m *unifiedManager) Apply(pid int) error {
properties = append(properties,
newProp("DefaultDependencies", false))
dbusConnection, err := getDbusConnection(m.rootless)
if err != nil {
return err
}
properties = append(properties, c.SystemdProps...)
if err := startUnit(dbusConnection, unitName, properties); err != nil {
if err := startUnit(m.dbus, unitName, properties); err != nil {
return errors.Wrapf(err, "error while starting unit %q with properties %+v", unitName, properties)
}
if err = m.initPath(); err != nil {
if err := m.initPath(); err != nil {
return err
}
if err := fs2.CreateCgroupPath(m.path, m.cgroups); err != nil {
@@ -305,17 +302,13 @@ func (m *unifiedManager) Destroy() error {
m.mu.Lock()
defer m.mu.Unlock()
dbusConnection, err := getDbusConnection(m.rootless)
if err != nil {
return err
}
unitName := getUnitName(m.cgroups)
if err := stopUnit(dbusConnection, unitName); err != nil {
if err := stopUnit(m.dbus, unitName); err != nil {
return err
}
// XXX this is probably not needed, systemd should handle it
err = os.Remove(m.path)
err := os.Remove(m.path)
if err != nil && !os.IsNotExist(err) {
return err
}
@@ -324,6 +317,7 @@ func (m *unifiedManager) Destroy() error {
}
func (m *unifiedManager) Path(_ string) string {
_ = m.initPath()
return m.path
}
@@ -344,16 +338,8 @@ func (m *unifiedManager) getSliceFull() (string, error) {
}
if m.rootless {
dbusConnection, err := getDbusConnection(m.rootless)
if err != nil {
return "", err
}
// managerCGQuoted is typically "/user.slice/user-${uid}.slice/user@${uid}.service" including the quote symbols
managerCGQuoted, err := dbusConnection.GetManagerProperty("ControlGroup")
if err != nil {
return "", err
}
managerCG, err := strconv.Unquote(managerCGQuoted)
// managerCG is typically "/user.slice/user-${uid}.slice/user@${uid}.service".
managerCG, err := getManagerProperty(m.dbus, "ControlGroup")
if err != nil {
return "", err
}
@@ -426,12 +412,8 @@ func (m *unifiedManager) GetStats() (*cgroups.Stats, error) {
return fsMgr.GetStats()
}
func (m *unifiedManager) Set(container *configs.Config) error {
dbusConnection, err := getDbusConnection(m.rootless)
if err != nil {
return err
}
properties, err := genV2ResourcesProperties(m.cgroups, dbusConnection)
func (m *unifiedManager) Set(r *configs.Resources) error {
properties, err := genV2ResourcesProperties(r, m.dbus)
if err != nil {
return err
}
@@ -459,7 +441,7 @@ func (m *unifiedManager) Set(container *configs.Config) error {
}
}
if err := dbusConnection.SetUnitProperties(getUnitName(m.cgroups), true, properties...); err != nil {
if err := setUnitProperties(m.dbus, getUnitName(m.cgroups), properties...); err != nil {
_ = m.Freeze(targetFreezerState)
return errors.Wrap(err, "error while setting unit properties")
}
@@ -472,7 +454,7 @@ func (m *unifiedManager) Set(container *configs.Config) error {
if err != nil {
return err
}
return fsMgr.Set(container)
return fsMgr.Set(r)
}
func (m *unifiedManager) GetPaths() map[string]string {
@@ -498,5 +480,9 @@ func (m *unifiedManager) Exists() bool {
}
func (m *unifiedManager) OOMKillCount() (uint64, error) {
return fs2.OOMKillCount(m.path)
fsMgr, err := m.fsManager()
if err != nil {
return 0, err
}
return fsMgr.OOMKillCount()
}

View File

@@ -31,9 +31,10 @@ type IDMap struct {
// for syscalls. Additional architectures can be added by specifying them in
// Architectures.
type Seccomp struct {
DefaultAction Action `json:"default_action"`
Architectures []string `json:"architectures"`
Syscalls []*Syscall `json:"syscalls"`
DefaultAction Action `json:"default_action"`
Architectures []string `json:"architectures"`
Syscalls []*Syscall `json:"syscalls"`
DefaultErrnoRet *uint `json:"default_errno_ret"`
}
// Action is taken upon rule match in Seccomp

View File

@@ -39,6 +39,7 @@ func (v *ConfigValidator) Validate(config *configs.Config) error {
v.sysctl,
v.intelrdt,
v.rootlessEUID,
v.mounts,
}
for _, c := range checks {
if err := c(config); err != nil {
@@ -246,6 +247,16 @@ func (v *ConfigValidator) cgroups(config *configs.Config) error {
return nil
}
func (v *ConfigValidator) mounts(config *configs.Config) error {
for _, m := range config.Mounts {
if !filepath.IsAbs(m.Destination) {
return fmt.Errorf("invalid mount %+v: mount destination not absolute", m)
}
}
return nil
}
func isHostNetNS(path string) (bool, error) {
const currentProcessNetns = "/proc/self/ns/net"

View File

@@ -225,9 +225,9 @@ func (c *linuxContainer) Set(config configs.Config) error {
if status == Stopped {
return newGenericError(errors.New("container not running"), ContainerNotRunning)
}
if err := c.cgroupManager.Set(&config); err != nil {
if err := c.cgroupManager.Set(config.Cgroups.Resources); err != nil {
// Set configs back
if err2 := c.cgroupManager.Set(c.config); err2 != nil {
if err2 := c.cgroupManager.Set(c.config.Cgroups.Resources); err2 != nil {
logrus.Warnf("Setting back cgroup configs failed due to error: %v, your state.json and actual configs might be inconsistent.", err2)
}
return err
@@ -235,7 +235,7 @@ func (c *linuxContainer) Set(config configs.Config) error {
if c.intelRdtManager != nil {
if err := c.intelRdtManager.Set(&config); err != nil {
// Set configs back
if err2 := c.cgroupManager.Set(c.config); err2 != nil {
if err2 := c.cgroupManager.Set(c.config.Cgroups.Resources); err2 != nil {
logrus.Warnf("Setting back cgroup configs failed due to error: %v, your state.json and actual configs might be inconsistent.", err2)
}
if err2 := c.intelRdtManager.Set(c.config); err2 != nil {
@@ -610,6 +610,9 @@ func (c *linuxContainer) newInitConfig(process *Process) *initConfig {
AppArmorProfile: c.config.AppArmorProfile,
ProcessLabel: c.config.ProcessLabel,
Rlimits: c.config.Rlimits,
CreateConsole: process.ConsoleSocket != nil,
ConsoleWidth: process.ConsoleWidth,
ConsoleHeight: process.ConsoleHeight,
}
if process.NoNewPrivileges != nil {
cfg.NoNewPrivileges = *process.NoNewPrivileges
@@ -623,9 +626,10 @@ func (c *linuxContainer) newInitConfig(process *Process) *initConfig {
if len(process.Rlimits) > 0 {
cfg.Rlimits = process.Rlimits
}
cfg.CreateConsole = process.ConsoleSocket != nil
cfg.ConsoleWidth = process.ConsoleWidth
cfg.ConsoleHeight = process.ConsoleHeight
if cgroups.IsCgroup2UnifiedMode() {
cfg.Cgroup2Path = c.cgroupManager.Path("")
}
return cfg
}
@@ -1213,7 +1217,6 @@ func (c *linuxContainer) makeCriuRestoreMountpoints(m *configs.Mount) error {
if err := checkProcMount(c.config.Rootfs, dest, ""); err != nil {
return err
}
m.Destination = dest
if err := os.MkdirAll(dest, 0755); err != nil {
return err
}
@@ -1253,13 +1256,16 @@ func (c *linuxContainer) prepareCriuRestoreMounts(mounts []*configs.Mount) error
umounts := []string{}
defer func() {
for _, u := range umounts {
if e := unix.Unmount(u, unix.MNT_DETACH); e != nil {
if e != unix.EINVAL {
// Ignore EINVAL as it means 'target is not a mount point.'
// It probably has already been unmounted.
logrus.Warnf("Error during cleanup unmounting of %q (%v)", u, e)
_ = utils.WithProcfd(c.config.Rootfs, u, func(procfd string) error {
if e := unix.Unmount(procfd, unix.MNT_DETACH); e != nil {
if e != unix.EINVAL {
// Ignore EINVAL as it means 'target is not a mount point.'
// It probably has already been unmounted.
logrus.Warnf("Error during cleanup unmounting of %s (%s): %v", procfd, u, e)
}
}
}
return nil
})
}
}()
for _, m := range mounts {
@@ -1277,8 +1283,13 @@ func (c *linuxContainer) prepareCriuRestoreMounts(mounts []*configs.Mount) error
// because during initial container creation mounts are
// set up in the order they are configured.
if m.Device == "bind" {
if err := unix.Mount(m.Source, m.Destination, "", unix.MS_BIND|unix.MS_REC, ""); err != nil {
return errorsf.Wrapf(err, "unable to bind mount %q to %q", m.Source, m.Destination)
if err := utils.WithProcfd(c.config.Rootfs, m.Destination, func(procfd string) error {
if err := unix.Mount(m.Source, procfd, "", unix.MS_BIND|unix.MS_REC, ""); err != nil {
return errorsf.Wrapf(err, "unable to bind mount %q to %q (through %q)", m.Source, m.Destination, procfd)
}
return nil
}); err != nil {
return err
}
umounts = append(umounts, m.Destination)
}
@@ -1458,7 +1469,7 @@ func (c *linuxContainer) criuApplyCgroups(pid int, req *criurpc.CriuReq) error {
return err
}
if err := c.cgroupManager.Set(c.config); err != nil {
if err := c.cgroupManager.Set(c.config.Cgroups.Resources); err != nil {
return newSystemError(err)
}

View File

@@ -70,6 +70,7 @@ type initConfig struct {
RootlessEUID bool `json:"rootless_euid,omitempty"`
RootlessCgroups bool `json:"rootless_cgroups,omitempty"`
SpecState *specs.State `json:"spec_state,omitempty"`
Cgroup2Path string `json:"cgroup2_path,omitempty"`
}
type initer interface {

View File

@@ -24,6 +24,7 @@ type Config struct {
LogFormat string
LogFilePath string
LogPipeFd int
LogCaller bool
}
func ForwardLogs(logPipe io.ReadCloser) chan error {
@@ -77,6 +78,7 @@ func ConfigureLogging(config Config) error {
}
logrus.SetLevel(config.LogLevel)
logrus.SetReportCaller(config.LogCaller)
// XXX: while 0 is a valid fd (usually stdin), here we assume
// that we never deliberately set LogPipeFd to 0.

View File

@@ -75,12 +75,12 @@
# define SYS_memfd_create 385
# elif defined(__aarch64__)
# define SYS_memfd_create 279
# elif defined(__ppc__) || defined(__ppc64__)
# elif defined(__ppc__) || defined(__PPC64__) || defined(__powerpc64__)
# define SYS_memfd_create 360
# elif defined(__s390__) || defined(__s390x__)
# define SYS_memfd_create 350
# else
# error "unknown architecture -- cannot hard-code SYS_memfd_create"
# warning "unknown architecture -- cannot hard-code SYS_memfd_create"
# endif
# endif
#endif

View File

@@ -448,7 +448,7 @@ func (p *initProcess) start() (retErr error) {
// call prestart and CreateRuntime hooks
if !p.config.Config.Namespaces.Contains(configs.NEWNS) {
// Setup cgroup before the hook, so that the prestart and CreateRuntime hook could apply cgroup permissions.
if err := p.manager.Set(p.config.Config); err != nil {
if err := p.manager.Set(p.config.Config.Cgroups.Resources); err != nil {
return newSystemErrorWithCause(err, "setting cgroup config for ready process")
}
if p.intelRdtManager != nil {
@@ -504,7 +504,7 @@ func (p *initProcess) start() (retErr error) {
sentRun = true
case procHooks:
// Setup cgroup before prestart hook, so that the prestart hook could apply cgroup permissions.
if err := p.manager.Set(p.config.Config); err != nil {
if err := p.manager.Set(p.config.Config.Cgroups.Resources); err != nil {
return newSystemErrorWithCause(err, "setting cgroup config for procHooks process")
}
if p.intelRdtManager != nil {

View File

@@ -17,6 +17,7 @@ import (
"github.com/moby/sys/mountinfo"
"github.com/mrunalp/fileutils"
"github.com/opencontainers/runc/libcontainer/cgroups"
"github.com/opencontainers/runc/libcontainer/cgroups/fs2"
"github.com/opencontainers/runc/libcontainer/configs"
"github.com/opencontainers/runc/libcontainer/devices"
"github.com/opencontainers/runc/libcontainer/userns"
@@ -24,11 +25,20 @@ import (
libcontainerUtils "github.com/opencontainers/runc/libcontainer/utils"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/selinux/go-selinux/label"
"github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
)
const defaultMountFlags = unix.MS_NOEXEC | unix.MS_NOSUID | unix.MS_NODEV
type mountConfig struct {
root string
label string
cgroup2Path string
rootlessCgroups bool
cgroupns bool
}
// needsSetupDev returns true if /dev needs to be set up.
func needsSetupDev(config *configs.Config) bool {
for _, m := range config.Mounts {
@@ -48,7 +58,13 @@ func prepareRootfs(pipe io.ReadWriter, iConfig *initConfig) (err error) {
return newSystemErrorWithCause(err, "preparing rootfs")
}
hasCgroupns := config.Namespaces.Contains(configs.NEWCGROUP)
mountConfig := &mountConfig{
root: config.Rootfs,
label: config.MountLabel,
cgroup2Path: iConfig.Cgroup2Path,
rootlessCgroups: iConfig.RootlessCgroups,
cgroupns: config.Namespaces.Contains(configs.NEWCGROUP),
}
setupDev := needsSetupDev(config)
for _, m := range config.Mounts {
for _, precmd := range m.PremountCmds {
@@ -56,7 +72,7 @@ func prepareRootfs(pipe io.ReadWriter, iConfig *initConfig) (err error) {
return newSystemErrorWithCause(err, "running premount command")
}
}
if err := mountToRootfs(m, config.Rootfs, config.MountLabel, hasCgroupns); err != nil {
if err := mountToRootfs(m, mountConfig); err != nil {
return newSystemErrorWithCausef(err, "mounting %q to rootfs at %q", m.Source, m.Destination)
}
@@ -213,8 +229,6 @@ func prepareBindMount(m *configs.Mount, rootfs string) error {
if err := checkProcMount(rootfs, dest, m.Source); err != nil {
return err
}
// update the mount with the correct dest after symlinks are resolved.
m.Destination = dest
if err := createIfNotExists(dest, stat.IsDir()); err != nil {
return err
}
@@ -222,7 +236,7 @@ func prepareBindMount(m *configs.Mount, rootfs string) error {
return nil
}
func mountCgroupV1(m *configs.Mount, rootfs, mountLabel string, enableCgroupns bool) error {
func mountCgroupV1(m *configs.Mount, c *mountConfig) error {
binds, err := getCgroupMounts(m)
if err != nil {
return err
@@ -242,31 +256,34 @@ func mountCgroupV1(m *configs.Mount, rootfs, mountLabel string, enableCgroupns b
Data: "mode=755",
PropagationFlags: m.PropagationFlags,
}
if err := mountToRootfs(tmpfs, rootfs, mountLabel, enableCgroupns); err != nil {
if err := mountToRootfs(tmpfs, c); err != nil {
return err
}
for _, b := range binds {
if enableCgroupns {
subsystemPath := filepath.Join(rootfs, b.Destination)
if c.cgroupns {
subsystemPath := filepath.Join(c.root, b.Destination)
if err := os.MkdirAll(subsystemPath, 0755); err != nil {
return err
}
flags := defaultMountFlags
if m.Flags&unix.MS_RDONLY != 0 {
flags = flags | unix.MS_RDONLY
}
cgroupmount := &configs.Mount{
Source: "cgroup",
Device: "cgroup", // this is actually fstype
Destination: subsystemPath,
Flags: flags,
Data: filepath.Base(subsystemPath),
}
if err := mountNewCgroup(cgroupmount); err != nil {
if err := utils.WithProcfd(c.root, b.Destination, func(procfd string) error {
flags := defaultMountFlags
if m.Flags&unix.MS_RDONLY != 0 {
flags = flags | unix.MS_RDONLY
}
var (
source = "cgroup"
data = filepath.Base(subsystemPath)
)
if data == "systemd" {
data = cgroups.CgroupNamePrefix + data
source = "systemd"
}
return unix.Mount(source, procfd, "cgroup", uintptr(flags), data)
}); err != nil {
return err
}
} else {
if err := mountToRootfs(b, rootfs, mountLabel, enableCgroupns); err != nil {
if err := mountToRootfs(b, c); err != nil {
return err
}
}
@@ -276,7 +293,7 @@ func mountCgroupV1(m *configs.Mount, rootfs, mountLabel string, enableCgroupns b
// symlink(2) is very dumb, it will just shove the path into
// the link and doesn't do any checks or relative path
// conversion. Also, don't error out if the cgroup already exists.
if err := os.Symlink(mc, filepath.Join(rootfs, m.Destination, ss)); err != nil && !os.IsExist(err) {
if err := os.Symlink(mc, filepath.Join(c.root, m.Destination, ss)); err != nil && !os.IsExist(err) {
return err
}
}
@@ -284,30 +301,87 @@ func mountCgroupV1(m *configs.Mount, rootfs, mountLabel string, enableCgroupns b
return nil
}
func mountCgroupV2(m *configs.Mount, rootfs, mountLabel string, enableCgroupns bool) error {
cgroupPath, err := securejoin.SecureJoin(rootfs, m.Destination)
func mountCgroupV2(m *configs.Mount, c *mountConfig) error {
dest, err := securejoin.SecureJoin(c.root, m.Destination)
if err != nil {
return err
}
if err := os.MkdirAll(cgroupPath, 0755); err != nil {
if err := os.MkdirAll(dest, 0755); err != nil {
return err
}
if err := unix.Mount(m.Source, cgroupPath, "cgroup2", uintptr(m.Flags), m.Data); err != nil {
// when we are in UserNS but CgroupNS is not unshared, we cannot mount cgroup2 (#2158)
if err == unix.EPERM || err == unix.EBUSY {
return unix.Mount("/sys/fs/cgroup", cgroupPath, "", uintptr(m.Flags)|unix.MS_BIND, "")
return utils.WithProcfd(c.root, m.Destination, func(procfd string) error {
if err := unix.Mount(m.Source, procfd, "cgroup2", uintptr(m.Flags), m.Data); err != nil {
// when we are in UserNS but CgroupNS is not unshared, we cannot mount cgroup2 (#2158)
if err == unix.EPERM || err == unix.EBUSY {
src := fs2.UnifiedMountpoint
if c.cgroupns && c.cgroup2Path != "" {
// Emulate cgroupns by bind-mounting
// the container cgroup path rather than
// the whole /sys/fs/cgroup.
src = c.cgroup2Path
}
err = unix.Mount(src, procfd, "", uintptr(m.Flags)|unix.MS_BIND, "")
if err == unix.ENOENT && c.rootlessCgroups {
err = nil
}
}
return err
}
return err
}
return nil
return nil
})
}
func mountToRootfs(m *configs.Mount, rootfs, mountLabel string, enableCgroupns bool) error {
var (
dest = m.Destination
)
if !strings.HasPrefix(dest, rootfs) {
dest = filepath.Join(rootfs, dest)
func doTmpfsCopyUp(m *configs.Mount, rootfs, mountLabel string) (Err error) {
// Set up a scratch dir for the tmpfs on the host.
tmpdir, err := prepareTmp("/tmp")
if err != nil {
return newSystemErrorWithCause(err, "tmpcopyup: failed to setup tmpdir")
}
defer cleanupTmp(tmpdir)
tmpDir, err := ioutil.TempDir(tmpdir, "runctmpdir")
if err != nil {
return newSystemErrorWithCause(err, "tmpcopyup: failed to create tmpdir")
}
defer os.RemoveAll(tmpDir)
// Configure the *host* tmpdir as if it's the container mount. We change
// m.Destination since we are going to mount *on the host*.
oldDest := m.Destination
m.Destination = tmpDir
err = mountPropagate(m, "/", mountLabel)
m.Destination = oldDest
if err != nil {
return err
}
defer func() {
if Err != nil {
if err := unix.Unmount(tmpDir, unix.MNT_DETACH); err != nil {
logrus.Warnf("tmpcopyup: failed to unmount tmpdir on error: %v", err)
}
}
}()
return utils.WithProcfd(rootfs, m.Destination, func(procfd string) (Err error) {
// Copy the container data to the host tmpdir. We append "/" to force
// CopyDirectory to resolve the symlink rather than trying to copy the
// symlink itself.
if err := fileutils.CopyDirectory(procfd+"/", tmpDir); err != nil {
return fmt.Errorf("tmpcopyup: failed to copy %s to %s (%s): %w", m.Destination, procfd, tmpDir, err)
}
// Now move the mount into the container.
if err := unix.Mount(tmpDir, procfd, "", unix.MS_MOVE, ""); err != nil {
return fmt.Errorf("tmpcopyup: failed to move mount %s to %s (%s): %w", tmpDir, procfd, m.Destination, err)
}
return nil
})
}
func mountToRootfs(m *configs.Mount, c *mountConfig) error {
rootfs := c.root
mountLabel := c.label
dest, err := securejoin.SecureJoin(rootfs, m.Destination)
if err != nil {
return err
}
switch m.Device {
@@ -338,53 +412,21 @@ func mountToRootfs(m *configs.Mount, rootfs, mountLabel string, enableCgroupns b
}
return label.SetFileLabel(dest, mountLabel)
case "tmpfs":
copyUp := m.Extensions&configs.EXT_COPYUP == configs.EXT_COPYUP
tmpDir := ""
// dest might be an absolute symlink, so it needs
// to be resolved under rootfs.
dest, err := securejoin.SecureJoin(rootfs, m.Destination)
if err != nil {
return err
}
m.Destination = dest
stat, err := os.Stat(dest)
if err != nil {
if err := os.MkdirAll(dest, 0755); err != nil {
return err
}
}
if copyUp {
tmpdir, err := prepareTmp("/tmp")
if err != nil {
return newSystemErrorWithCause(err, "tmpcopyup: failed to setup tmpdir")
}
defer cleanupTmp(tmpdir)
tmpDir, err = ioutil.TempDir(tmpdir, "runctmpdir")
if err != nil {
return newSystemErrorWithCause(err, "tmpcopyup: failed to create tmpdir")
}
defer os.RemoveAll(tmpDir)
m.Destination = tmpDir
if m.Extensions&configs.EXT_COPYUP == configs.EXT_COPYUP {
err = doTmpfsCopyUp(m, rootfs, mountLabel)
} else {
err = mountPropagate(m, rootfs, mountLabel)
}
if err := mountPropagate(m, rootfs, mountLabel); err != nil {
if err != nil {
return err
}
if copyUp {
if err := fileutils.CopyDirectory(dest, tmpDir); err != nil {
errMsg := fmt.Errorf("tmpcopyup: failed to copy %s to %s: %v", dest, tmpDir, err)
if err1 := unix.Unmount(tmpDir, unix.MNT_DETACH); err1 != nil {
return newSystemErrorWithCausef(err1, "tmpcopyup: %v: failed to unmount", errMsg)
}
return errMsg
}
if err := unix.Mount(tmpDir, dest, "", unix.MS_MOVE, ""); err != nil {
errMsg := fmt.Errorf("tmpcopyup: failed to move mount %s to %s: %v", tmpDir, dest, err)
if err1 := unix.Unmount(tmpDir, unix.MNT_DETACH); err1 != nil {
return newSystemErrorWithCausef(err1, "tmpcopyup: %v: failed to unmount", errMsg)
}
return errMsg
}
}
if stat != nil {
if err = os.Chmod(dest, stat.Mode()); err != nil {
return err
@@ -424,23 +466,13 @@ func mountToRootfs(m *configs.Mount, rootfs, mountLabel string, enableCgroupns b
}
case "cgroup":
if cgroups.IsCgroup2UnifiedMode() {
return mountCgroupV2(m, rootfs, mountLabel, enableCgroupns)
return mountCgroupV2(m, c)
}
return mountCgroupV1(m, rootfs, mountLabel, enableCgroupns)
return mountCgroupV1(m, c)
default:
// ensure that the destination of the mount is resolved of symlinks at mount time because
// any previous mounts can invalidate the next mount's destination.
// this can happen when a user specifies mounts within other mounts to cause breakouts or other
// evil stuff to try to escape the container's rootfs.
var err error
if dest, err = securejoin.SecureJoin(rootfs, m.Destination); err != nil {
return err
}
if err := checkProcMount(rootfs, dest, m.Source); err != nil {
return err
}
// update the mount with the correct dest after symlinks are resolved.
m.Destination = dest
if err := os.MkdirAll(dest, 0755); err != nil {
return err
}
@@ -623,7 +655,7 @@ func createDevices(config *configs.Config) error {
return nil
}
func bindMountDeviceNode(dest string, node *devices.Device) error {
func bindMountDeviceNode(rootfs, dest string, node *devices.Device) error {
f, err := os.Create(dest)
if err != nil && !os.IsExist(err) {
return err
@@ -631,7 +663,9 @@ func bindMountDeviceNode(dest string, node *devices.Device) error {
if f != nil {
f.Close()
}
return unix.Mount(node.Path, dest, "bind", unix.MS_BIND, "")
return utils.WithProcfd(rootfs, dest, func(procfd string) error {
return unix.Mount(node.Path, procfd, "bind", unix.MS_BIND, "")
})
}
// Creates the device node in the rootfs of the container.
@@ -640,18 +674,21 @@ func createDeviceNode(rootfs string, node *devices.Device, bind bool) error {
// The node only exists for cgroup reasons, ignore it here.
return nil
}
dest := filepath.Join(rootfs, node.Path)
dest, err := securejoin.SecureJoin(rootfs, node.Path)
if err != nil {
return err
}
if err := os.MkdirAll(filepath.Dir(dest), 0755); err != nil {
return err
}
if bind {
return bindMountDeviceNode(dest, node)
return bindMountDeviceNode(rootfs, dest, node)
}
if err := mknodDevice(dest, node); err != nil {
if os.IsExist(err) {
return nil
} else if os.IsPermission(err) {
return bindMountDeviceNode(dest, node)
return bindMountDeviceNode(rootfs, dest, node)
}
return err
}
@@ -931,9 +968,20 @@ func readonlyPath(path string) error {
if os.IsNotExist(err) {
return nil
}
return err
return &os.PathError{Op: "bind-mount", Path: path, Err: err}
}
return unix.Mount(path, path, "", unix.MS_BIND|unix.MS_REMOUNT|unix.MS_RDONLY|unix.MS_REC, "")
var s unix.Statfs_t
if err := unix.Statfs(path, &s); err != nil {
return &os.PathError{Op: "statfs", Path: path, Err: err}
}
flags := uintptr(s.Flags) & (unix.MS_NOSUID | unix.MS_NODEV | unix.MS_NOEXEC)
if err := unix.Mount(path, path, "", flags|unix.MS_BIND|unix.MS_REMOUNT|unix.MS_RDONLY, ""); err != nil {
return &os.PathError{Op: "bind-mount-ro", Path: path, Err: err}
}
return nil
}
// remountReadonly will remount an existing mount point and ensure that it is read-only.
@@ -987,61 +1035,47 @@ func writeSystemProperty(key, value string) error {
}
func remount(m *configs.Mount, rootfs string) error {
var (
dest = m.Destination
)
if !strings.HasPrefix(dest, rootfs) {
dest = filepath.Join(rootfs, dest)
}
return unix.Mount(m.Source, dest, m.Device, uintptr(m.Flags|unix.MS_REMOUNT), "")
return utils.WithProcfd(rootfs, m.Destination, func(procfd string) error {
return unix.Mount(m.Source, procfd, m.Device, uintptr(m.Flags|unix.MS_REMOUNT), "")
})
}
// Do the mount operation followed by additional mounts required to take care
// of propagation flags.
// of propagation flags. This will always be scoped inside the container rootfs.
func mountPropagate(m *configs.Mount, rootfs string, mountLabel string) error {
var (
dest = m.Destination
data = label.FormatMountLabel(m.Data, mountLabel)
flags = m.Flags
)
if libcontainerUtils.CleanPath(dest) == "/dev" {
// Delay mounting the filesystem read-only if we need to do further
// operations on it. We need to set up files in "/dev" and tmpfs mounts may
// need to be chmod-ed after mounting. The mount will be remounted ro later
// in finalizeRootfs() if necessary.
if libcontainerUtils.CleanPath(m.Destination) == "/dev" || m.Device == "tmpfs" {
flags &= ^unix.MS_RDONLY
}
// Mount it rw to allow chmod operation. A remount will be performed
// later to make it ro if set.
if m.Device == "tmpfs" {
flags &= ^unix.MS_RDONLY
// Because the destination is inside a container path which might be
// mutating underneath us, we verify that we are actually going to mount
// inside the container with WithProcfd() -- mounting through a procfd
// mounts on the target.
if err := utils.WithProcfd(rootfs, m.Destination, func(procfd string) error {
return unix.Mount(m.Source, procfd, m.Device, uintptr(flags), data)
}); err != nil {
return fmt.Errorf("mount through procfd: %w", err)
}
copyUp := m.Extensions&configs.EXT_COPYUP == configs.EXT_COPYUP
if !(copyUp || strings.HasPrefix(dest, rootfs)) {
dest = filepath.Join(rootfs, dest)
}
if err := unix.Mount(m.Source, dest, m.Device, uintptr(flags), data); err != nil {
return err
}
for _, pflag := range m.PropagationFlags {
if err := unix.Mount("", dest, "", uintptr(pflag), ""); err != nil {
return err
// We have to apply mount propagation flags in a separate WithProcfd() call
// because the previous call invalidates the passed procfd -- the mount
// target needs to be re-opened.
if err := utils.WithProcfd(rootfs, m.Destination, func(procfd string) error {
for _, pflag := range m.PropagationFlags {
if err := unix.Mount("", procfd, "", uintptr(pflag), ""); err != nil {
return err
}
}
}
return nil
}
func mountNewCgroup(m *configs.Mount) error {
var (
data = m.Data
source = m.Source
)
if data == "systemd" {
data = cgroups.CgroupNamePrefix + data
source = "systemd"
}
if err := unix.Mount(source, m.Destination, m.Device, uintptr(m.Flags), data); err != nil {
return err
return nil
}); err != nil {
return fmt.Errorf("change mount propagation through procfd: %w", err)
}
return nil
}

View File

@@ -523,6 +523,11 @@ func assemble(program []bpf.Instruction) ([]unix.SockFilter, error) {
}
func generatePatch(config *configs.Seccomp) ([]bpf.Instruction, error) {
// Patch the generated cBPF only when there is not a defaultErrnoRet set
// and it is different from ENOSYS
if config.DefaultErrnoRet != nil && *config.DefaultErrnoRet == uint(retErrnoEnosys) {
return nil, nil
}
// We only add the stub if the default action is not permissive.
if isAllowAction(config.DefaultAction) {
logrus.Debugf("seccomp: skipping -ENOSYS stub filter generation")

View File

@@ -36,7 +36,7 @@ func InitSeccomp(config *configs.Seccomp) error {
return errors.New("cannot initialize Seccomp - nil config passed")
}
defaultAction, err := getAction(config.DefaultAction, nil)
defaultAction, err := getAction(config.DefaultAction, config.DefaultErrnoRet)
if err != nil {
return errors.New("error initializing seccomp - invalid default action")
}

View File

@@ -2,6 +2,7 @@ package specconv
import (
"os"
"path/filepath"
"strings"
"github.com/opencontainers/runc/libcontainer/cgroups"
@@ -200,8 +201,14 @@ func ToRootless(spec *specs.Spec) {
// Fix up mounts.
var mounts []specs.Mount
for _, mount := range spec.Mounts {
// Ignore all mounts that are under /sys.
if strings.HasPrefix(mount.Destination, "/sys") {
// Replace the /sys mount with an rbind.
if filepath.Clean(mount.Destination) == "/sys" {
mounts = append(mounts, specs.Mount{
Source: "/sys",
Destination: "/sys",
Type: "none",
Options: []string{"rbind", "nosuid", "noexec", "nodev", "ro"},
})
continue
}
@@ -216,13 +223,6 @@ func ToRootless(spec *specs.Spec) {
mount.Options = options
mounts = append(mounts, mount)
}
// Add the sysfs mount as an rbind.
mounts = append(mounts, specs.Mount{
Source: "/sys",
Destination: "/sys",
Type: "none",
Options: []string{"rbind", "nosuid", "noexec", "nodev", "ro"},
})
spec.Mounts = mounts
// Remove cgroup settings.

View File

@@ -238,7 +238,11 @@ func CreateLibcontainerConfig(opts *CreateOpts) (*configs.Config, error) {
}
for _, m := range spec.Mounts {
config.Mounts = append(config.Mounts, createLibcontainerMount(cwd, m))
cm, err := createLibcontainerMount(cwd, m)
if err != nil {
return nil, fmt.Errorf("invalid mount %+v: %w", m, err)
}
config.Mounts = append(config.Mounts, cm)
}
defaultDevs, err := createDevices(spec, config)
@@ -327,7 +331,10 @@ func CreateLibcontainerConfig(opts *CreateOpts) (*configs.Config, error) {
return config, nil
}
func createLibcontainerMount(cwd string, m specs.Mount) *configs.Mount {
func createLibcontainerMount(cwd string, m specs.Mount) (*configs.Mount, error) {
if !filepath.IsAbs(m.Destination) {
return nil, fmt.Errorf("mount destination %s not absolute", m.Destination)
}
flags, pgflags, data, ext := parseMountOptions(m.Options)
source := m.Source
device := m.Type
@@ -348,7 +355,7 @@ func createLibcontainerMount(cwd string, m specs.Mount) *configs.Mount {
Flags: flags,
PropagationFlags: pgflags,
Extensions: ext,
}
}, nil
}
// systemd property name check: latin letters only, at least 3 of them
@@ -871,6 +878,7 @@ func SetupSeccomp(config *specs.LinuxSeccomp) (*configs.Seccomp, error) {
return nil, err
}
newConfig.DefaultAction = newDefaultAction
newConfig.DefaultErrnoRet = config.DefaultErrnoRet
// Loop through all syscall blocks and convert them to libcontainer format
for _, call := range config.Syscalls {

View File

@@ -3,12 +3,15 @@ package utils
import (
"encoding/binary"
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
"strconv"
"strings"
"unsafe"
"github.com/cyphar/filepath-securejoin"
"golang.org/x/sys/unix"
)
@@ -88,6 +91,57 @@ func CleanPath(path string) string {
return filepath.Clean(path)
}
// stripRoot returns the passed path, stripping the root path if it was
// (lexicially) inside it. Note that both passed paths will always be treated
// as absolute, and the returned path will also always be absolute. In
// addition, the paths are cleaned before stripping the root.
func stripRoot(root, path string) string {
// Make the paths clean and absolute.
root, path = CleanPath("/"+root), CleanPath("/"+path)
switch {
case path == root:
path = "/"
case root == "/":
// do nothing
case strings.HasPrefix(path, root+"/"):
path = strings.TrimPrefix(path, root+"/")
}
return CleanPath("/" + path)
}
// WithProcfd runs the passed closure with a procfd path (/proc/self/fd/...)
// corresponding to the unsafePath resolved within the root. Before passing the
// fd, this path is verified to have been inside the root -- so operating on it
// through the passed fdpath should be safe. Do not access this path through
// the original path strings, and do not attempt to use the pathname outside of
// the passed closure (the file handle will be freed once the closure returns).
func WithProcfd(root, unsafePath string, fn func(procfd string) error) error {
// Remove the root then forcefully resolve inside the root.
unsafePath = stripRoot(root, unsafePath)
path, err := securejoin.SecureJoin(root, unsafePath)
if err != nil {
return fmt.Errorf("resolving path inside rootfs failed: %v", err)
}
// Open the target path.
fh, err := os.OpenFile(path, unix.O_PATH|unix.O_CLOEXEC, 0)
if err != nil {
return fmt.Errorf("open o_path procfd: %w", err)
}
defer fh.Close()
// Double-check the path is the one we expected.
procfd := "/proc/self/fd/" + strconv.Itoa(int(fh.Fd()))
if realpath, err := os.Readlink(procfd); err != nil {
return fmt.Errorf("procfd verification failed: %w", err)
} else if realpath != path {
return fmt.Errorf("possibly malicious path detected -- refusing to operate on %s", realpath)
}
// Run the closure.
return fn(procfd)
}
// SearchLabels searches a list of key-value pairs for the provided key and
// returns the corresponding value. The pairs must be separated with '='.
func SearchLabels(labels []string, query string) string {

View File

@@ -190,6 +190,7 @@ func createLogConfig(context *cli.Context) logs.Config {
LogLevel: logrus.InfoLevel,
LogFilePath: logFilePath,
LogFormat: context.GlobalString("log-format"),
LogCaller: context.GlobalBool("debug"),
}
if context.GlobalBool("debug") {
config.LogLevel = logrus.DebugLevel

View File

@@ -121,14 +121,15 @@ using the runc checkpoint command.`,
}
func criuOptions(context *cli.Context) *libcontainer.CriuOpts {
imagePath := getCheckpointImagePath(context)
if err := os.MkdirAll(imagePath, 0755); err != nil {
imagePath, parentPath, err := prepareImagePaths(context)
if err != nil {
fatal(err)
}
return &libcontainer.CriuOpts{
ImagesDirectory: imagePath,
WorkDirectory: context.String("work-path"),
ParentImage: context.String("parent-path"),
ParentImage: parentPath,
LeaveRunning: context.Bool("leave-running"),
TcpEstablished: context.Bool("tcp-established"),
ExternalUnixConnections: context.Bool("ext-unix-sk"),