File 3402.patch of Package runc
From a5bf5cfde84381ce99bb8faead0ef6f122843a2a Mon Sep 17 00:00:00 2001
From: Stefan Agner <stefan@agner.ch>
Date: Thu, 3 Mar 2022 14:24:37 +0100
Subject: [PATCH 1/4] Separate Device handling for default AllowDevices
Signed-off-by: Stefan Agner <stefan@agner.ch>
---
libcontainer/devices/device.go | 3 +
libcontainer/specconv/spec_linux.go | 96 ++++++++++++++++--------
libcontainer/specconv/spec_linux_test.go | 8 +-
3 files changed, 70 insertions(+), 37 deletions(-)
diff --git a/libcontainer/devices/device.go b/libcontainer/devices/device.go
index c2c2b3bb7c0..9540a89c55b 100644
--- a/libcontainer/devices/device.go
+++ b/libcontainer/devices/device.go
@@ -24,6 +24,9 @@ type Device struct {
// Gid of the device.
Gid uint32 `json:"gid"`
+
+ // Is Default Device
+ IsDefault bool `json:"is_default"`
}
// Permissions is a cgroupv1-style string to represent device access. It
diff --git a/libcontainer/specconv/spec_linux.go b/libcontainer/specconv/spec_linux.go
index fbb68c24d5d..c8d3e3c6c59 100644
--- a/libcontainer/specconv/spec_linux.go
+++ b/libcontainer/specconv/spec_linux.go
@@ -201,6 +201,7 @@ func KnownMountOptions() []string {
var AllowedDevices = []*devices.Device{
// allow mknod for any device
{
+ IsDefault: true,
Rule: devices.Rule{
Type: devices.CharDevice,
Major: devices.Wildcard,
@@ -210,6 +211,7 @@ var AllowedDevices = []*devices.Device{
},
},
{
+ IsDefault: true,
Rule: devices.Rule{
Type: devices.BlockDevice,
Major: devices.Wildcard,
@@ -219,10 +221,11 @@ var AllowedDevices = []*devices.Device{
},
},
{
- Path: "/dev/null",
- FileMode: 0o666,
- Uid: 0,
- Gid: 0,
+ Path: "/dev/null",
+ FileMode: 0o666,
+ Uid: 0,
+ Gid: 0,
+ IsDefault: true,
Rule: devices.Rule{
Type: devices.CharDevice,
Major: 1,
@@ -232,10 +235,11 @@ var AllowedDevices = []*devices.Device{
},
},
{
- Path: "/dev/random",
- FileMode: 0o666,
- Uid: 0,
- Gid: 0,
+ Path: "/dev/random",
+ FileMode: 0o666,
+ Uid: 0,
+ Gid: 0,
+ IsDefault: true,
Rule: devices.Rule{
Type: devices.CharDevice,
Major: 1,
@@ -245,10 +249,11 @@ var AllowedDevices = []*devices.Device{
},
},
{
- Path: "/dev/full",
- FileMode: 0o666,
- Uid: 0,
- Gid: 0,
+ Path: "/dev/full",
+ FileMode: 0o666,
+ Uid: 0,
+ Gid: 0,
+ IsDefault: true,
Rule: devices.Rule{
Type: devices.CharDevice,
Major: 1,
@@ -258,10 +263,11 @@ var AllowedDevices = []*devices.Device{
},
},
{
- Path: "/dev/tty",
- FileMode: 0o666,
- Uid: 0,
- Gid: 0,
+ Path: "/dev/tty",
+ FileMode: 0o666,
+ Uid: 0,
+ Gid: 0,
+ IsDefault: true,
Rule: devices.Rule{
Type: devices.CharDevice,
Major: 5,
@@ -271,10 +277,11 @@ var AllowedDevices = []*devices.Device{
},
},
{
- Path: "/dev/zero",
- FileMode: 0o666,
- Uid: 0,
- Gid: 0,
+ Path: "/dev/zero",
+ FileMode: 0o666,
+ Uid: 0,
+ Gid: 0,
+ IsDefault: true,
Rule: devices.Rule{
Type: devices.CharDevice,
Major: 1,
@@ -284,10 +291,11 @@ var AllowedDevices = []*devices.Device{
},
},
{
- Path: "/dev/urandom",
- FileMode: 0o666,
- Uid: 0,
- Gid: 0,
+ Path: "/dev/urandom",
+ FileMode: 0o666,
+ Uid: 0,
+ Gid: 0,
+ IsDefault: true,
Rule: devices.Rule{
Type: devices.CharDevice,
Major: 1,
@@ -298,6 +306,7 @@ var AllowedDevices = []*devices.Device{
},
// /dev/pts/ - pts namespaces are "coming soon"
{
+ IsDefault: true,
Rule: devices.Rule{
Type: devices.CharDevice,
Major: 136,
@@ -307,6 +316,7 @@ var AllowedDevices = []*devices.Device{
},
},
{
+ IsDefault: true,
Rule: devices.Rule{
Type: devices.CharDevice,
Major: 5,
@@ -380,12 +390,14 @@ func CreateLibcontainerConfig(opts *CreateOpts) (*configs.Config, error) {
config.Mounts = append(config.Mounts, cm)
}
- defaultDevs, err := createDevices(spec, config)
+ err = createDevices(spec, config)
if err != nil {
return nil, err
}
- c, err := CreateCgroupConfig(opts, defaultDevs)
+ defaultAllowedDevices := createDefaultDevicesCgroups(config)
+
+ c, err := CreateCgroupConfig(opts, defaultAllowedDevices)
if err != nil {
return nil, err
}
@@ -941,21 +953,22 @@ func stringToDeviceRune(s string) (devices.Type, error) {
}
}
-func createDevices(spec *specs.Spec, config *configs.Config) ([]*devices.Device, error) {
+func createDevices(spec *specs.Spec, config *configs.Config) error {
// If a spec device is redundant with a default device, remove that default
// device (the spec one takes priority).
- dedupedAllowDevs := []*devices.Device{}
-
next:
for _, ad := range AllowedDevices {
- if ad.Path != "" && spec.Linux != nil {
+ if ad.Path == "" {
+ continue next
+ }
+
+ if spec.Linux != nil {
for _, sd := range spec.Linux.Devices {
if sd.Path == ad.Path {
continue next
}
}
}
- dedupedAllowDevs = append(dedupedAllowDevs, ad)
if ad.Path != "" {
config.Devices = append(config.Devices, ad)
}
@@ -975,7 +988,7 @@ next:
}
dt, err := stringToDeviceRune(d.Type)
if err != nil {
- return nil, err
+ return err
}
if d.FileMode != nil {
filemode = *d.FileMode &^ unix.S_IFMT
@@ -995,7 +1008,24 @@ next:
}
}
- return dedupedAllowDevs, nil
+ return nil
+}
+
+func createDefaultDevicesCgroups(config *configs.Config) []*devices.Device {
+ defaultAllowedDevices := []*devices.Device{}
+next:
+ for _, ad := range AllowedDevices {
+ if ad.Path != "" {
+ for _, device := range config.Devices {
+ if ad.Path == device.Path && !device.IsDefault {
+ continue next
+ }
+ }
+ }
+ defaultAllowedDevices = append(defaultAllowedDevices, ad)
+ }
+
+ return defaultAllowedDevices
}
func setupUserNamespace(spec *specs.Spec, config *configs.Config) error {
diff --git a/libcontainer/specconv/spec_linux_test.go b/libcontainer/specconv/spec_linux_test.go
index 8c7fb774f97..965ec4ffbb4 100644
--- a/libcontainer/specconv/spec_linux_test.go
+++ b/libcontainer/specconv/spec_linux_test.go
@@ -886,17 +886,17 @@ func TestCreateDevices(t *testing.T) {
conf := &configs.Config{}
- defaultDevs, err := createDevices(spec, conf)
+ err := createDevices(spec, conf)
if err != nil {
t.Errorf("failed to create devices: %v", err)
}
- // Verify the returned default devices has the /dev/tty entry deduplicated
+ // Verify the returned devices has the /dev/tty entry deduplicated
found := false
- for _, d := range defaultDevs {
+ for _, d := range conf.Devices {
if d.Path == "/dev/tty" {
if found {
- t.Errorf("createDevices failed: returned a duplicated device entry: %v", defaultDevs)
+ t.Errorf("createDevices failed: returned a duplicated device entry: %v", conf.Devices)
}
found = true
}
From 53506ab025f3e684039ec1142de541374b991172 Mon Sep 17 00:00:00 2001
From: Stefan Agner <stefan@agner.ch>
Date: Thu, 3 Mar 2022 14:55:53 +0100
Subject: [PATCH 2/4] Implement common function to create DeviceCgroup rules
Signed-off-by: Stefan Agner <stefan@agner.ch>
---
libcontainer/specconv/spec_linux.go | 84 +++++++++++++++++------------
1 file changed, 49 insertions(+), 35 deletions(-)
diff --git a/libcontainer/specconv/spec_linux.go b/libcontainer/specconv/spec_linux.go
index c8d3e3c6c59..73623e9eccc 100644
--- a/libcontainer/specconv/spec_linux.go
+++ b/libcontainer/specconv/spec_linux.go
@@ -704,6 +704,48 @@ func initSystemdProps(spec *specs.Spec) ([]systemdDbus.Property, error) {
return sp, nil
}
+func CreateCgroupDeviceConfig(r *configs.Resources, specr *specs.LinuxResources, defaultDevs []*devices.Device) error {
+ if specr != nil {
+ for i, d := range specr.Devices {
+ var (
+ t = "a"
+ major = int64(-1)
+ minor = int64(-1)
+ )
+ if d.Type != "" {
+ t = d.Type
+ }
+ if d.Major != nil {
+ major = *d.Major
+ }
+ if d.Minor != nil {
+ minor = *d.Minor
+ }
+ if d.Access == "" {
+ return fmt.Errorf("device access at %d field cannot be empty", i)
+ }
+ dt, err := stringToCgroupDeviceRune(t)
+ if err != nil {
+ return err
+ }
+ r.Devices = append(r.Devices, &devices.Rule{
+ Type: dt,
+ Major: major,
+ Minor: minor,
+ Permissions: devices.Permissions(d.Access),
+ Allow: d.Allow,
+ })
+ }
+ }
+
+ // Append the default allowed devices to the end of the list.
+ for _, device := range defaultDevs {
+ r.Devices = append(r.Devices, &device.Rule)
+ }
+
+ return nil
+}
+
func CreateCgroupConfig(opts *CreateOpts, defaultDevs []*devices.Device) (*configs.Cgroup, error) {
var (
myCgroupPath string
@@ -760,39 +802,10 @@ func CreateCgroupConfig(opts *CreateOpts, defaultDevs []*devices.Device) (*confi
// In rootless containers, any attempt to make cgroup changes is likely to fail.
// libcontainer will validate this but ignores the error.
+ var r *specs.LinuxResources
if spec.Linux != nil {
- r := spec.Linux.Resources
+ r = spec.Linux.Resources
if r != nil {
- for i, d := range r.Devices {
- var (
- t = "a"
- major = int64(-1)
- minor = int64(-1)
- )
- if d.Type != "" {
- t = d.Type
- }
- if d.Major != nil {
- major = *d.Major
- }
- if d.Minor != nil {
- minor = *d.Minor
- }
- if d.Access == "" {
- return nil, fmt.Errorf("device access at %d field cannot be empty", i)
- }
- dt, err := stringToCgroupDeviceRune(t)
- if err != nil {
- return nil, err
- }
- c.Resources.Devices = append(c.Resources.Devices, &devices.Rule{
- Type: dt,
- Major: major,
- Minor: minor,
- Permissions: devices.Permissions(d.Access),
- Allow: d.Allow,
- })
- }
if r.Memory != nil {
if r.Memory.Limit != nil {
c.Resources.Memory = *r.Memory.Limit
@@ -918,12 +931,13 @@ func CreateCgroupConfig(opts *CreateOpts, defaultDevs []*devices.Device) (*confi
}
}
}
- }
- // Append the default allowed devices to the end of the list.
- for _, device := range defaultDevs {
- c.Resources.Devices = append(c.Resources.Devices, &device.Rule)
+ err := CreateCgroupDeviceConfig(c.Resources, r, defaultDevs)
+ if err != nil {
+ return nil, err
+ }
}
+
return c, nil
}
From 2958e81addf2081a72631149b12af448eb569e54 Mon Sep 17 00:00:00 2001
From: Stefan Agner <stefan@agner.ch>
Date: Thu, 3 Mar 2022 15:43:10 +0100
Subject: [PATCH 3/4] Implement Device Resources updates
Add support to update Device Resources with runc update.
Signed-off-by: Stefan Agner <stefan@agner.ch>
---
libcontainer/specconv/spec_linux.go | 4 ++--
update.go | 26 +++++++++++++++++++-------
2 files changed, 21 insertions(+), 9 deletions(-)
diff --git a/libcontainer/specconv/spec_linux.go b/libcontainer/specconv/spec_linux.go
index 73623e9eccc..ba9468d802d 100644
--- a/libcontainer/specconv/spec_linux.go
+++ b/libcontainer/specconv/spec_linux.go
@@ -395,7 +395,7 @@ func CreateLibcontainerConfig(opts *CreateOpts) (*configs.Config, error) {
return nil, err
}
- defaultAllowedDevices := createDefaultDevicesCgroups(config)
+ defaultAllowedDevices := CreateDefaultDevicesCgroups(config)
c, err := CreateCgroupConfig(opts, defaultAllowedDevices)
if err != nil {
@@ -1025,7 +1025,7 @@ next:
return nil
}
-func createDefaultDevicesCgroups(config *configs.Config) []*devices.Device {
+func CreateDefaultDevicesCgroups(config *configs.Config) []*devices.Device {
defaultAllowedDevices := []*devices.Device{}
next:
for _, ad := range AllowedDevices {
diff --git a/update.go b/update.go
index fc2d656abbf..12b377793b3 100644
--- a/update.go
+++ b/update.go
@@ -8,6 +8,7 @@ import (
"strconv"
"github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/specconv"
"github.com/sirupsen/logrus"
"github.com/docker/go-units"
@@ -322,6 +323,24 @@ other options are ignored.
config.Cgroups.Resources.PidsLimit = r.Pids.Limit
config.Cgroups.Resources.Unified = r.Unified
+ if len(r.Devices) > 0 {
+ config.Cgroups.Resources.Devices = nil
+ defaultAllowedDevices := specconv.CreateDefaultDevicesCgroups(&config)
+
+ err = specconv.CreateCgroupDeviceConfig(config.Cgroups.Resources, &r, defaultAllowedDevices)
+ if err != nil {
+ return err
+ }
+ config.Cgroups.SkipDevices = false
+ } else {
+ // If "runc update" is not changing device configuration, add
+ // this to skip device update.
+ // This helps in case an extra plugin (nvidia GPU) applies some
+ // configuration on top of what runc does.
+ // Note this field is not saved into container's state.json.
+ config.Cgroups.SkipDevices = true
+ }
+
// Update Intel RDT
l3CacheSchema := context.String("l3-cache-schema")
memBwSchema := context.String("mem-bw-schema")
@@ -353,13 +372,6 @@ other options are ignored.
config.IntelRdt.MemBwSchema = memBwSchema
}
- // XXX(kolyshkin@): currently "runc update" is unable to change
- // device configuration, so add this to skip device update.
- // This helps in case an extra plugin (nvidia GPU) applies some
- // configuration on top of what runc does.
- // Note this field is not saved into container's state.json.
- config.Cgroups.SkipDevices = true
-
return container.Set(config)
},
}
From f17333d7c0ffc0cf5964622bcd84a9de3705e7f0 Mon Sep 17 00:00:00 2001
From: Stefan Agner <stefan@agner.ch>
Date: Fri, 5 Aug 2022 13:03:21 +0200
Subject: [PATCH 4/4] Add integration tests for device updates
Signed-off-by: Stefan Agner <stefan@agner.ch>
---
tests/integration/update.bats | 62 +++++++++++++++++++++++++++++++++++
1 file changed, 62 insertions(+)
diff --git a/tests/integration/update.bats b/tests/integration/update.bats
index 5a3dc7f0563..ef5d4cfd1e2 100644
--- a/tests/integration/update.bats
+++ b/tests/integration/update.bats
@@ -810,6 +810,68 @@ EOF
[ -z "$(<"$CONTAINER_OUTPUT")" ]
}
+@test "update devices" {
+ requires root
+
+ # Create a container without access to /dev/kmsg. Verify that accessing fails
+ # and update the container to add access from it.
+ #
+ # Finally, remove access again by passing an empty array of devices.
+ update_config ' .linux.resources.devices = [{"allow": false, "access": "rwm"}]
+ | .linux.devices = [{"path": "/dev/kmsg", "type": "c", "major": 1, "minor": 11}]
+ | .process.capabilities.bounding += ["CAP_SYSLOG"]
+ | .process.capabilities.effective += ["CAP_SYSLOG"]
+ | .process.capabilities.inheritable += ["CAP_SYSLOG"]
+ | .process.capabilities.permitted += ["CAP_SYSLOG"]'
+
+ # Run the container in the background.
+ runc run -d --console-socket "$CONSOLE_SOCKET" test_update_devices
+ [ "$status" -eq 0 ]
+
+ runc exec test_update_devices head -c 100 /dev/kmsg
+ [ "$status" -eq 1 ]
+
+ # Make sure default devices still work
+ runc exec test_update_devices cat /dev/null
+ [ "$status" -eq 0 ]
+
+ runc update --resources - test_update_devices <<EOF
+ {
+ "devices": [
+ {"allow": true, "type": "c", "major": 1, "minor": 11, "access": "rwa"}
+ ]
+ }
+EOF
+
+ runc exec test_update_devices head -c 100 /dev/kmsg
+ [ "$status" -eq 0 ]
+
+ # Make sure default devices still work
+ runc exec test_update_devices cat /dev/null
+ [ "$status" -eq 0 ]
+
+ # Not updating devices should keep device access
+ # For backwards compatibility
+ runc update --resources - test_update_devices <<EOF
+ { }
+EOF
+
+ runc exec test_update_devices head -c 100 /dev/kmsg
+ [ "$status" -eq 0 ]
+
+ # Explicitly removing access should remove access
+ runc update --resources - test_update_devices <<EOF
+ {
+ "devices": [
+ {"allow": false, "type": "c", "major": 1, "minor": 11, "access": "rwa"}
+ ]
+ }
+EOF
+
+ runc exec test_update_devices head -c 100 /dev/kmsg
+ [ "$status" -eq 1 ]
+}
+
@test "update paused container" {
requires cgroups_freezer
[ $EUID -ne 0 ] && requires rootless_cgroup