Repository: opencontainers/runc
Pull request #3017 (2021-06-10 05:58:36)

Returns clearer error message for Setenv
> container_linux.go:349: starting container process caused "process_linux.go:449: container init caused \"setenv: invalid argument\"": unknown

xref kubernetes/kubernetes#102732

Pick the judgment logic from `os.Setenv`, and added clear error message 
https://github.com/golang/go/blob/27f83723e98d8e3795a07bdca2b3a8155b0d72b3/src/syscall/env_unix.go#L97-L130


Patch:
diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml
index b7a858f0..f54cc6e9 100644
--- a/.github/workflows/validate.yml
+++ b/.github/workflows/validate.yml
@@ -91,7 +91,7 @@ jobs:
     steps:
       - name: get pr commits
         id: 'get-pr-commits'
-        uses: tim-actions/get-pr-commits@v1.1.0
+        uses: tim-actions/get-pr-commits@v1.0.0
         with:
           token: ${{ secrets.GITHUB_TOKEN }}
 
diff --git a/go.mod b/go.mod
index 6262a121..2b698671 100644
--- a/go.mod
+++ b/go.mod
@@ -5,7 +5,7 @@ go 1.13
 require (
 	github.com/bits-and-blooms/bitset v1.2.0
 	github.com/checkpoint-restore/go-criu/v5 v5.0.0
-	github.com/cilium/ebpf v0.6.1
+	github.com/cilium/ebpf v0.6.0
 	github.com/containerd/console v1.0.2
 	github.com/coreos/go-systemd/v22 v22.3.2
 	github.com/cyphar/filepath-securejoin v0.2.2
diff --git a/go.sum b/go.sum
index 0bc7fd05..997c1c79 100644
--- a/go.sum
+++ b/go.sum
@@ -3,8 +3,8 @@ github.com/bits-and-blooms/bitset v1.2.0 h1:Kn4yilvwNtMACtf1eYDlG8H77R07mZSPbMjL
 github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
 github.com/checkpoint-restore/go-criu/v5 v5.0.0 h1:TW8f/UvntYoVDMN1K2HlT82qH1rb0sOjpGw3m6Ym+i4=
 github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M=
-github.com/cilium/ebpf v0.6.1 h1:n6ZUOkSFi6OwcMeTCFaDQx2Onx2rEikQo69315MNbdc=
-github.com/cilium/ebpf v0.6.1/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=
+github.com/cilium/ebpf v0.6.0 h1:hOQqNhQdMIi0zmjii4jKUnI0i+NB7ApvTXs2MstI5S0=
+github.com/cilium/ebpf v0.6.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=
 github.com/containerd/console v1.0.2 h1:Pi6D+aZXM+oUw1czuKgH5IJ+y0jhYcwBJfx5/Ghn9dE=
 github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ=
 github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI=
diff --git a/libcontainer/cgroups/ebpf/ebpf_linux.go b/libcontainer/cgroups/ebpf/ebpf_linux.go
index fccf3931..e3e8efa5 100644
--- a/libcontainer/cgroups/ebpf/ebpf_linux.go
+++ b/libcontainer/cgroups/ebpf/ebpf_linux.go
@@ -81,8 +81,6 @@ var (
 
 // Loosely based on the BPF_F_REPLACE support check in
 //   <https://github.com/cilium/ebpf/blob/v0.6.0/link/syscalls.go>.
-//
-// TODO: move this logic to cilium/ebpf
 func haveBpfProgReplace() bool {
 	haveBpfProgReplaceOnce.Do(func() {
 		prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{
diff --git a/libcontainer/cgroups/fs/blkio.go b/libcontainer/cgroups/fs/blkio.go
index e5240a17..99407474 100644
--- a/libcontainer/cgroups/fs/blkio.go
+++ b/libcontainer/cgroups/fs/blkio.go
@@ -6,7 +6,6 @@ import (
 	"bufio"
 	"fmt"
 	"os"
-	"path/filepath"
 	"strconv"
 	"strings"
 
@@ -15,10 +14,7 @@ import (
 	"github.com/opencontainers/runc/libcontainer/configs"
 )
 
-type BlkioGroup struct {
-	weightFilename       string
-	weightDeviceFilename string
-}
+type BlkioGroup struct{}
 
 func (s *BlkioGroup) Name() string {
 	return "blkio"
@@ -29,9 +25,8 @@ func (s *BlkioGroup) Apply(path string, d *cgroupData) error {
 }
 
 func (s *BlkioGroup) Set(path string, r *configs.Resources) error {
-	s.detectWeightFilenames(path)
 	if r.BlkioWeight != 0 {
-		if err := fscommon.WriteFile(path, s.weightFilename, strconv.FormatUint(uint64(r.BlkioWeight), 10)); err != nil {
+		if err := fscommon.WriteFile(path, "blkio.weight", strconv.FormatUint(uint64(r.BlkioWeight), 10)); err != nil {
 			return err
 		}
 	}
@@ -42,7 +37,7 @@ func (s *BlkioGroup) Set(path string, r *configs.Resources) error {
 		}
 	}
 	for _, wd := range r.BlkioWeightDevice {
-		if err := fscommon.WriteFile(path, s.weightDeviceFilename, wd.WeightString()); err != nil {
+		if err := fscommon.WriteFile(path, "blkio.weight_device", wd.WeightString()); err != nil {
 			return err
 		}
 		if err := fscommon.WriteFile(path, "blkio.leaf_weight_device", wd.LeafWeightString()); err != nil {
@@ -292,17 +287,3 @@ func (s *BlkioGroup) GetStats(path string, stats *cgroups.Stats) error {
 	}
 	return nil
 }
-
-func (s *BlkioGroup) detectWeightFilenames(path string) {
-	if s.weightFilename != "" {
-		// Already detected.
-		return
-	}
-	if cgroups.PathExists(filepath.Join(path, "blkio.weight")) {
-		s.weightFilename = "blkio.weight"
-		s.weightDeviceFilename = "blkio.weight_device"
-	} else {
-		s.weightFilename = "blkio.bfq.weight"
-		s.weightDeviceFilename = "blkio.bfq.weight_device"
-	}
-}
diff --git a/libcontainer/cgroups/systemd/common.go b/libcontainer/cgroups/systemd/common.go
index c9995bfe..67c61278 100644
--- a/libcontainer/cgroups/systemd/common.go
+++ b/libcontainer/cgroups/systemd/common.go
@@ -178,7 +178,7 @@ func allowAllDevices() []systemdDbus.Property {
 // corresponding set of systemd properties to configure the devices correctly.
 func generateDeviceProperties(r *configs.Resources) ([]systemdDbus.Property, error) {
 	if r.SkipDevices {
-		return nil, nil
+		return allowAllDevices(), nil
 	}
 
 	properties := []systemdDbus.Property{
diff --git a/libcontainer/cgroups/systemd/v1.go b/libcontainer/cgroups/systemd/v1.go
index 1f533c81..06526c54 100644
--- a/libcontainer/cgroups/systemd/v1.go
+++ b/libcontainer/cgroups/systemd/v1.go
@@ -339,24 +339,27 @@ func (m *legacyManager) Set(r *configs.Resources) error {
 		return err
 	}
 
-	// Figure out the current freezer state, so we can revert to it after we
-	// temporarily freeze the container.
-	targetFreezerState, err := m.GetFreezerState()
-	if err != nil {
-		return err
-	}
-	if targetFreezerState == configs.Undefined {
-		targetFreezerState = configs.Thawed
-	}
-
 	// We have to freeze the container while systemd sets the cgroup settings.
 	// The reason for this is that systemd's application of DeviceAllow rules
 	// is done disruptively, resulting in spurrious errors to common devices
 	// (unlike our fs driver, they will happily write deny-all rules to running
 	// containers). So we freeze the container to avoid them hitting the cgroup
 	// error. But if the freezer cgroup isn't supported, we just warn about it.
-	if err := m.Freeze(configs.Frozen); err != nil {
-		logrus.Infof("freeze container before SetUnitProperties failed: %v", err)
+	targetFreezerState := configs.Undefined
+	if !m.cgroups.SkipDevices {
+		// Figure out the current freezer state, so we can revert to it after we
+		// temporarily freeze the container.
+		targetFreezerState, err = m.GetFreezerState()
+		if err != nil {
+			return err
+		}
+		if targetFreezerState == configs.Undefined {
+			targetFreezerState = configs.Thawed
+		}
+
+		if err := m.Freeze(configs.Frozen); err != nil {
+			logrus.Infof("freeze container before SetUnitProperties failed: %v", err)
+		}
 	}
 
 	if err := setUnitProperties(m.dbus, getUnitName(m.cgroups), properties...); err != nil {
diff --git a/libcontainer/cgroups/systemd/v2.go b/libcontainer/cgroups/systemd/v2.go
index 262aaca8..cbd3a308 100644
--- a/libcontainer/cgroups/systemd/v2.go
+++ b/libcontainer/cgroups/systemd/v2.go
@@ -418,24 +418,27 @@ func (m *unifiedManager) Set(r *configs.Resources) error {
 		return err
 	}
 
-	// Figure out the current freezer state, so we can revert to it after we
-	// temporarily freeze the container.
-	targetFreezerState, err := m.GetFreezerState()
-	if err != nil {
-		return err
-	}
-	if targetFreezerState == configs.Undefined {
-		targetFreezerState = configs.Thawed
-	}
-
 	// We have to freeze the container while systemd sets the cgroup settings.
 	// The reason for this is that systemd's application of DeviceAllow rules
 	// is done disruptively, resulting in spurrious errors to common devices
 	// (unlike our fs driver, they will happily write deny-all rules to running
 	// containers). So we freeze the container to avoid them hitting the cgroup
 	// error. But if the freezer cgroup isn't supported, we just warn about it.
-	if err := m.Freeze(configs.Frozen); err != nil {
-		logrus.Infof("freeze container before SetUnitProperties failed: %v", err)
+	targetFreezerState := configs.Undefined
+	if !m.cgroups.SkipDevices {
+		// Figure out the current freezer state, so we can revert to it after we
+		// temporarily freeze the container.
+		targetFreezerState, err = m.GetFreezerState()
+		if err != nil {
+			return err
+		}
+		if targetFreezerState == configs.Undefined {
+			targetFreezerState = configs.Thawed
+		}
+
+		if err := m.Freeze(configs.Frozen); err != nil {
+			logrus.Infof("freeze container before SetUnitProperties failed: %v", err)
+		}
 	}
 
 	if err := setUnitProperties(m.dbus, getUnitName(m.cgroups), properties...); err != nil {
diff --git a/libcontainer/configs/validate/validator.go b/libcontainer/configs/validate/validator.go
index b0254600..0b38b8bd 100644
--- a/libcontainer/configs/validate/validator.go
+++ b/libcontainer/configs/validate/validator.go
@@ -12,7 +12,6 @@ import (
 	"github.com/opencontainers/runc/libcontainer/configs"
 	"github.com/opencontainers/runc/libcontainer/intelrdt"
 	selinux "github.com/opencontainers/selinux/go-selinux"
-	"github.com/sirupsen/logrus"
 	"golang.org/x/sys/unix"
 )
 
@@ -30,7 +29,6 @@ type check func(config *configs.Config) error
 
 func (v *ConfigValidator) Validate(config *configs.Config) error {
 	checks := []check{
-		v.cgroups,
 		v.rootfs,
 		v.network,
 		v.hostname,
@@ -40,21 +38,17 @@ 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 {
 			return err
 		}
 	}
-	// Relaxed validation rules for backward compatibility
-	warns := []check{
-		v.mounts, // TODO (runc v1.x.x): make this an error instead of a warning
-	}
-	for _, c := range warns {
-		if err := c(config); err != nil {
-			logrus.WithError(err).Warnf("invalid configuration")
-		}
+	if err := v.cgroups(config); err != nil {
+		return err
 	}
+
 	return nil
 }
 
diff --git a/libcontainer/init_linux.go b/libcontainer/init_linux.go
index 798e7a84..c456cbe7 100644
--- a/libcontainer/init_linux.go
+++ b/libcontainer/init_linux.go
@@ -112,9 +112,19 @@ func populateProcessEnvironment(env []string) error {
 	for _, pair := range env {
 		p := strings.SplitN(pair, "=", 2)
 		if len(p) < 2 {
-			return fmt.Errorf("invalid environment '%v'", pair)
+			return fmt.Errorf("invalid environment variable: %q", pair)
 		}
-		if err := os.Setenv(p[0], p[1]); err != nil {
+		name, val := p[0], p[1]
+		if name == "" {
+			return fmt.Errorf("environment variable name can't be empty: %q", pair)
+		}
+		if strings.IndexByte(name, 0) >= 0 {
+			return fmt.Errorf("environment variable name can't contain null(\\x00): %q", pair)
+		}
+		if strings.IndexByte(val, 0) >= 0 {
+			return fmt.Errorf("environment variable value can't contain null(\\x00): %q", pair)
+		}
+		if err := os.Setenv(name, val); err != nil {
 			return err
 		}
 	}
diff --git a/libcontainer/specconv/spec_linux.go b/libcontainer/specconv/spec_linux.go
index 8474769c..ac83123d 100644
--- a/libcontainer/specconv/spec_linux.go
+++ b/libcontainer/specconv/spec_linux.go
@@ -333,10 +333,7 @@ func CreateLibcontainerConfig(opts *CreateOpts) (*configs.Config, error) {
 
 func createLibcontainerMount(cwd string, m specs.Mount) (*configs.Mount, error) {
 	if !filepath.IsAbs(m.Destination) {
-		// Relax validation for backward compatibility
-		// TODO (runc v1.x.x): change warning to an error
-		// return nil, fmt.Errorf("mount destination %s is not absolute", m.Destination)
-		logrus.Warnf("mount destination %s is not absolute. Support for non-absolute mount destinations will be removed in a future release.", m.Destination)
+		return nil, fmt.Errorf("mount destination %s not absolute", m.Destination)
 	}
 	flags, pgflags, data, ext := parseMountOptions(m.Options)
 	source := m.Source
diff --git a/vendor/github.com/cilium/ebpf/CONTRIBUTING.md b/vendor/github.com/cilium/ebpf/CONTRIBUTING.md
index 72ceb437..97c794f3 100644
--- a/vendor/github.com/cilium/ebpf/CONTRIBUTING.md
+++ b/vendor/github.com/cilium/ebpf/CONTRIBUTING.md
@@ -18,23 +18,6 @@ reason about the proposed changes.
 ## Running the tests
 
 Many of the tests require privileges to set resource limits and load eBPF code.
-The easiest way to obtain these is to run the tests with `sudo`.
-
-To test the current package with your local kernel you can simply run:
-```
-go test -exec sudo  ./...
-```
-
-To test the current package with a different kernel version you can use the [run-tests.sh](run-tests.sh) script.
-It requires [virtme](https://github.com/amluto/virtme) and qemu to be installed.
-
-Examples:
-
-```bash
-# Run all tests on a 5.4 kernel
-./run-tests.sh 5.4
-
-# Run a subset of tests:
-./run-tests.sh 5.4 go test ./link
-```
+The easiest way to obtain these is to run the tests with `sudo`:
 
+    sudo go test ./...
\ No newline at end of file
diff --git a/vendor/github.com/cilium/ebpf/link/kprobe.go b/vendor/github.com/cilium/ebpf/link/kprobe.go
index ea71d6d6..fe481e6a 100644
--- a/vendor/github.com/cilium/ebpf/link/kprobe.go
+++ b/vendor/github.com/cilium/ebpf/link/kprobe.go
@@ -131,10 +131,7 @@ func kprobe(symbol string, prog *ebpf.Program, ret bool) (*perfEvent, error) {
 	}
 
 	// Use kprobe PMU if the kernel has it available.
-	tp, err := pmuKprobe(platformPrefix(symbol), ret)
-	if errors.Is(err, os.ErrNotExist) {
-		tp, err = pmuKprobe(symbol, ret)
-	}
+	tp, err := pmuKprobe(symbol, ret)
 	if err == nil {
 		return tp, nil
 	}
@@ -143,10 +140,7 @@ func kprobe(symbol string, prog *ebpf.Program, ret bool) (*perfEvent, error) {
 	}
 
 	// Use tracefs if kprobe PMU is missing.
-	tp, err = tracefsKprobe(platformPrefix(symbol), ret)
-	if errors.Is(err, os.ErrNotExist) {
-		tp, err = tracefsKprobe(symbol, ret)
-	}
+	tp, err = tracefsKprobe(symbol, ret)
 	if err != nil {
 		return nil, fmt.Errorf("creating trace event '%s' in tracefs: %w", symbol, err)
 	}
diff --git a/vendor/github.com/cilium/ebpf/link/platform.go b/vendor/github.com/cilium/ebpf/link/platform.go
deleted file mode 100644
index eb6f7b7a..00000000
--- a/vendor/github.com/cilium/ebpf/link/platform.go
+++ /dev/null
@@ -1,25 +0,0 @@
-package link
-
-import (
-	"fmt"
-	"runtime"
-)
-
-func platformPrefix(symbol string) string {
-
-	prefix := runtime.GOARCH
-
-	// per https://github.com/golang/go/blob/master/src/go/build/syslist.go
-	switch prefix {
-	case "386":
-		prefix = "ia32"
-	case "amd64", "amd64p32":
-		prefix = "x64"
-	case "arm64", "arm64be":
-		prefix = "arm64"
-	default:
-		return symbol
-	}
-
-	return fmt.Sprintf("__%s_%s", prefix, symbol)
-}
diff --git a/vendor/github.com/cilium/ebpf/link/program.go b/vendor/github.com/cilium/ebpf/link/program.go
index b90c4574..0fe9d37c 100644
--- a/vendor/github.com/cilium/ebpf/link/program.go
+++ b/vendor/github.com/cilium/ebpf/link/program.go
@@ -43,7 +43,7 @@ func RawAttachProgram(opts RawAttachProgramOptions) error {
 	}
 
 	if err := internal.BPFProgAttach(&attr); err != nil {
-		return fmt.Errorf("can't attach program: %w", err)
+		return fmt.Errorf("can't attach program: %s", err)
 	}
 	return nil
 }
@@ -69,7 +69,7 @@ func RawDetachProgram(opts RawDetachProgramOptions) error {
 		AttachType:  uint32(opts.Attach),
 	}
 	if err := internal.BPFProgDetach(&attr); err != nil {
-		return fmt.Errorf("can't detach program: %w", err)
+		return fmt.Errorf("can't detach program: %s", err)
 	}
 
 	return nil
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 6878ffcf..495cc586 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -3,7 +3,7 @@ github.com/bits-and-blooms/bitset
 # github.com/checkpoint-restore/go-criu/v5 v5.0.0
 github.com/checkpoint-restore/go-criu/v5
 github.com/checkpoint-restore/go-criu/v5/rpc
-# github.com/cilium/ebpf v0.6.1
+# github.com/cilium/ebpf v0.6.0
 github.com/cilium/ebpf
 github.com/cilium/ebpf/asm
 github.com/cilium/ebpf/internal


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 47be5804..cc46afe6 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -19,7 +19,7 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        # Docker/Moby still builds runc with Go 1.13, so we should still support Go 1.13.
+        # Dockre/Moby still builds runc with Go 1.13, so we should still support Go 1.13.
         go-version: [1.13.x, 1.15.x, 1.16.x]
         rootless: ["rootless", ""]
         race: ["-race", ""]
diff --git a/libcontainer/cgroups/fs/blkio_test.go b/libcontainer/cgroups/fs/blkio_test.go
index 793c0432..4dbb48ad 100644
--- a/libcontainer/cgroups/fs/blkio_test.go
+++ b/libcontainer/cgroups/fs/blkio_test.go
@@ -171,80 +171,62 @@ func appendBlkioStatEntry(blkioStatEntries *[]cgroups.BlkioStatEntry, major, min
 }
 
 func TestBlkioSetWeight(t *testing.T) {
+	helper := NewCgroupTestUtil("blkio", t)
+	defer helper.cleanup()
+
 	const (
 		weightBefore = 100
 		weightAfter  = 200
 	)
 
-	for _, legacyIOScheduler := range []bool{false, true} {
-		// Populate cgroup
-		helper := NewCgroupTestUtil("blkio", t)
-		defer helper.cleanup()
-		weightFilename := "blkio.bfq.weight"
-		if legacyIOScheduler {
-			weightFilename = "blkio.weight"
-		}
-		helper.writeFileContents(map[string]string{
-			weightFilename: strconv.Itoa(weightBefore),
-		})
-		// Apply new configuration
-		helper.CgroupData.config.Resources.BlkioWeight = weightAfter
-		blkio := &BlkioGroup{}
-		if err := blkio.Set(helper.CgroupPath, helper.CgroupData.config.Resources); err != nil {
-			t.Fatal(err)
-		}
-		// Verify results
-		if weightFilename != blkio.weightFilename {
-			t.Fatalf("weight filename detection failed: expected %q, detected %q", weightFilename, blkio.weightFilename)
-		}
-		value, err := fscommon.GetCgroupParamUint(helper.CgroupPath, weightFilename)
-		if err != nil {
-			t.Fatal(err)
-		}
-		if value != weightAfter {
-			t.Fatalf("Got the wrong value, set %s failed.", weightFilename)
-		}
+	helper.writeFileContents(map[string]string{
+		"blkio.weight": strconv.Itoa(weightBefore),
+	})
+
+	helper.CgroupData.config.Resources.BlkioWeight = weightAfter
+	blkio := &BlkioGroup{}
+	if err := blkio.Set(helper.CgroupPath, helper.CgroupData.config.Resources); err != nil {
+		t.Fatal(err)
+	}
+
+	value, err := fscommon.GetCgroupParamUint(helper.CgroupPath, "blkio.weight")
+	if err != nil {
+		t.Fatalf("Failed to parse blkio.weight - %s", err)
+	}
+
+	if value != weightAfter {
+		t.Fatal("Got the wrong value, set blkio.weight failed.")
 	}
 }
 
 func TestBlkioSetWeightDevice(t *testing.T) {
+	helper := NewCgroupTestUtil("blkio", t)
+	defer helper.cleanup()
+
 	const (
 		weightDeviceBefore = "8:0 400"
 	)
 
-	for _, legacyIOScheduler := range []bool{false, true} {
-		// Populate cgroup
-		helper := NewCgroupTestUtil("blkio", t)
-		defer helper.cleanup()
-		weightFilename := "blkio.bfq.weight"
-		weightDeviceFilename := "blkio.bfq.weight_device"
-		if legacyIOScheduler {
-			weightFilename = "blkio.weight"
-			weightDeviceFilename = "blkio.weight_device"
-		}
-		helper.writeFileContents(map[string]string{
-			weightFilename:       "",
-			weightDeviceFilename: weightDeviceBefore,
-		})
-		// Apply new configuration
-		wd := configs.NewWeightDevice(8, 0, 500, 0)
-		weightDeviceAfter := wd.WeightString()
-		helper.CgroupData.config.Resources.BlkioWeightDevice = []*configs.WeightDevice{wd}
-		blkio := &BlkioGroup{}
-		if err := blkio.Set(helper.CgroupPath, helper.CgroupData.config.Resources); err != nil {
-			t.Fatal(err)
-		}
-		// Verify results
-		if weightDeviceFilename != blkio.weightDeviceFilename {
-			t.Fatalf("weight_device filename detection failed: expected %q, detected %q", weightDeviceFilename, blkio.weightDeviceFilename)
-		}
-		value, err := fscommon.GetCgroupParamString(helper.CgroupPath, weightDeviceFilename)
-		if err != nil {
-			t.Fatal(err)
-		}
-		if value != weightDeviceAfter {
-			t.Fatalf("Got the wrong value, set %s failed.", weightDeviceFilename)
-		}
+	wd := configs.NewWeightDevice(8, 0, 500, 0)
+	weightDeviceAfter := wd.WeightString()
+
+	helper.writeFileContents(map[string]string{
+		"blkio.weight_device": weightDeviceBefore,
+	})
+
+	helper.CgroupData.config.Resources.BlkioWeightDevice = []*configs.WeightDevice{wd}
+	blkio := &BlkioGroup{}
+	if err := blkio.Set(helper.CgroupPath, helper.CgroupData.config.Resources); err != nil {
+		t.Fatal(err)
+	}
+
+	value, err := fscommon.GetCgroupParamString(helper.CgroupPath, "blkio.weight_device")
+	if err != nil {
+		t.Fatalf("Failed to parse blkio.weight_device - %s", err)
+	}
+
+	if value != weightDeviceAfter {
+		t.Fatal("Got the wrong value, set blkio.weight_device failed.")
 	}
 }
 
@@ -265,27 +247,23 @@ func TestBlkioSetMultipleWeightDevice(t *testing.T) {
 	// is present will suffice for the test to ensure multiple writes are done.
 	weightDeviceAfter := wd2.WeightString()
 
-	blkio := &BlkioGroup{}
-	blkio.detectWeightFilenames(helper.CgroupPath)
-	if blkio.weightDeviceFilename != "blkio.bfq.weight_device" {
-		t.Fatalf("when blkio controller is unavailable, expected to use \"blkio.bfq.weight_device\", tried to use %q", blkio.weightDeviceFilename)
-	}
 	helper.writeFileContents(map[string]string{
-		blkio.weightDeviceFilename: weightDeviceBefore,
+		"blkio.weight_device": weightDeviceBefore,
 	})
 
 	helper.CgroupData.config.Resources.BlkioWeightDevice = []*configs.WeightDevice{wd1, wd2}
+	blkio := &BlkioGroup{}
 	if err := blkio.Set(helper.CgroupPath, helper.CgroupData.config.Resources); err != nil {
 		t.Fatal(err)
 	}
 
-	value, err := fscommon.GetCgroupParamString(helper.CgroupPath, blkio.weightDeviceFilename)
+	value, err := fscommon.GetCgroupParamString(helper.CgroupPath, "blkio.weight_device")
 	if err != nil {
-		t.Fatal(err)
+		t.Fatalf("Failed to parse blkio.weight_device - %s", err)
 	}
 
 	if value != weightDeviceAfter {
-		t.Fatalf("Got the wrong value, set %s failed.", blkio.weightDeviceFilename)
+		t.Fatal("Got the wrong value, set blkio.weight_device failed.")
 	}
 }
 
diff --git a/libcontainer/configs/validate/validator_test.go b/libcontainer/configs/validate/validator_test.go
index 687ce1b0..7d1ef0f6 100644
--- a/libcontainer/configs/validate/validator_test.go
+++ b/libcontainer/configs/validate/validator_test.go
@@ -323,12 +323,10 @@ func TestValidateMounts(t *testing.T) {
 		isErr bool
 		dest  string
 	}{
-		// TODO (runc v1.x.x): make these relative paths an error. See https://github.com/opencontainers/runc/pull/3004
-		{isErr: false, dest: "not/an/abs/path"},
-		{isErr: false, dest: "./rel/path"},
-		{isErr: false, dest: "./rel/path"},
-		{isErr: false, dest: "../../path"},
-
+		{isErr: true, dest: "not/an/abs/path"},
+		{isErr: true, dest: "./rel/path"},
+		{isErr: true, dest: "./rel/path"},
+		{isErr: true, dest: "../../path"},
 		{isErr: false, dest: "/abs/path"},
 		{isErr: false, dest: "/abs/but/../unclean"},
 	}
diff --git a/libcontainer/integration/update_test.go b/libcontainer/integration/update_test.go
deleted file mode 100644
index 4222b7e2..00000000
--- a/libcontainer/integration/update_test.go
+++ /dev/null
@@ -1,104 +0,0 @@
-package integration
-
-import (
-	"bytes"
-	"os"
-	"strings"
-	"testing"
-
-	"github.com/opencontainers/runc/libcontainer"
-	"github.com/opencontainers/runc/libcontainer/cgroups/systemd"
-	"github.com/opencontainers/runc/libcontainer/devices"
-)
-
-func testUpdateDevices(t *testing.T, systemd bool) {
-	if testing.Short() {
-		return
-	}
-	rootfs, err := newRootfs()
-	ok(t, err)
-	defer remove(rootfs)
-	config := newTemplateConfig(t, &tParam{
-		rootfs:  rootfs,
-		systemd: systemd,
-	})
-	container, err := newContainer(t, config)
-	ok(t, err)
-	defer destroyContainer(container)
-
-	// Execute a first process in the container
-	stdinR, stdinW, err := os.Pipe()
-	ok(t, err)
-	process := &libcontainer.Process{
-		Cwd:   "/",
-		Args:  []string{"cat"},
-		Env:   standardEnvironment,
-		Stdin: stdinR,
-		Init:  true,
-	}
-	err = container.Run(process)
-	_ = stdinR.Close()
-	defer func() {
-		_ = stdinW.Close()
-		if _, err := process.Wait(); err != nil {
-			t.Log(err)
-		}
-	}()
-	ok(t, err)
-
-	var buf bytes.Buffer
-	devCheck := &libcontainer.Process{
-		Cwd:    "/",
-		Args:   []string{"/bin/sh", "-c", "echo > /dev/full; cat /dev/null; true"},
-		Env:    standardEnvironment,
-		Stderr: &buf,
-	}
-	isAllowed := true
-	expected := map[bool][]string{
-		true: {
-			"write error: No space left on device", // from write to /dev/full
-			// no error from cat /dev/null
-		},
-		false: {
-			"/dev/full: Operation not permitted",
-			`cat: can't open '/dev/null': Operation not permitted`,
-		},
-	}
-	defaultDevices := config.Cgroups.Resources.Devices
-
-	for i := 0; i < 300; i++ {
-		// Check the access
-		buf.Reset()
-		err = container.Run(devCheck)
-		ok(t, err)
-		waitProcess(devCheck, t)
-
-		for _, exp := range expected[isAllowed] {
-			if !strings.Contains(buf.String(), exp) {
-				t.Fatalf("[%d] expected %q, got %q", i, exp, buf.String())
-			}
-		}
-
-		// Now flip the access permission
-		isAllowed = !isAllowed
-		if isAllowed {
-			config.Cgroups.Resources.Devices = defaultDevices
-		} else {
-			config.Cgroups.Resources.Devices = []*devices.Rule{}
-		}
-		if err := container.Set(*config); err != nil {
-			t.Fatal(err)
-		}
-	}
-}
-
-func TestUpdateDevices(t *testing.T) {
-	testUpdateDevices(t, false)
-}
-
-func TestUpdateDevicesSystemd(t *testing.T) {
-	if !systemd.IsRunningSystemd() {
-		t.Skip("Test requires systemd.")
-	}
-	testUpdateDevices(t, true)
-}
diff --git a/tests/integration/dev.bats b/tests/integration/dev.bats
index 35a9ac06..80ceb0a3 100644
--- a/tests/integration/dev.bats
+++ b/tests/integration/dev.bats
@@ -33,7 +33,7 @@ function teardown() {
 	[[ "${lines[0]}" =~ "crw-rw-rw".+"1".+"0".+"0".+"5,".+"2".+"/dev/ptmx" ]]
 }
 
-@test "runc run/update [device cgroup deny]" {
+@test "runc run [device cgroup deny]" {
 	requires root
 
 	update_config ' .linux.resources.devices = [{"allow": false, "access": "rwm"}]
@@ -52,18 +52,6 @@ function teardown() {
 	runc exec test_deny sh -c 'head -n 1 /dev/kmsg'
 	[ "$status" -eq 1 ]
 	[[ "${output}" == *'Operation not permitted'* ]]
-
-	runc update test_deny --pids-limit 42
-
-	# test write
-	runc exec test_deny sh -c 'hostname | tee /dev/kmsg'
-	[ "$status" -eq 1 ]
-	[[ "${output}" == *'Operation not permitted'* ]]
-
-	# test read
-	runc exec test_deny sh -c 'head -n 1 /dev/kmsg'
-	[ "$status" -eq 1 ]
-	[[ "${output}" == *'Operation not permitted'* ]]
 }
 
 @test "runc run [device cgroup allow rw char device]" {
diff --git a/tests/integration/update.bats b/tests/integration/update.bats
index e204ea49..41344a9d 100644
--- a/tests/integration/update.bats
+++ b/tests/integration/update.bats
@@ -648,3 +648,29 @@ EOF
 	runc resume test_update
 	[ "$status" -eq 0 ]
 }
+
+@test "runc update replaces devices cgroup program" {
+	[[ "$ROOTLESS" -ne 0 ]] && requires rootless_cgroup
+
+	# Unfortunately we can't update device rules directly with runc ("runc
+	# update" doesn't support it, and adding support would require ironing out
+	# some long-standing design issues with device configuration). So instead
+	# we just run "runc update" many times, relying on the fact that runc will
+	# re-apply devices cgroup rules on each runc update.
+	#
+	# In the past runc would not delete old cgroupv2 eBPF programs, so this
+	# test ensures that once we go past the program limit (64 stacked programs
+	# at time of writing) you can still run "runc" update.
+
+	# Run the container in the background.
+	runc run -d --console-socket "$CONSOLE_SOCKET" test_update
+	[ "$status" -eq 0 ]
+
+	for new_limit in $(seq 300); do
+		runc update --pids-limit "$((2 * new_limit))" test_update
+		[ "$status" -eq 0 ]
+	done
+
+	# The container should still be running.
+	testcontainer test_update running
+}