diff --git a/bsc1149954-0001-sd-notify-do-not-hang-when-NOTIFY_SOCKET-is-used-wit.patch b/bsc1149954-0001-sd-notify-do-not-hang-when-NOTIFY_SOCKET-is-used-wit.patch
deleted file mode 100644
index e84eeed..0000000
--- a/bsc1149954-0001-sd-notify-do-not-hang-when-NOTIFY_SOCKET-is-used-wit.patch
+++ /dev/null
@@ -1,291 +0,0 @@
-From 5d13416879fe0f50c300d94c569ea77950cbee94 Mon Sep 17 00:00:00 2001
-From: Giuseppe Scrivano <gscrivan@redhat.com>
-Date: Fri, 25 May 2018 18:04:06 +0200
-Subject: [PATCH] sd-notify: do not hang when NOTIFY_SOCKET is used with create
-
-if NOTIFY_SOCKET is used, do not block the main runc process waiting
-for events on the notify socket.  Bind mount the parent directory of
-the notify socket, so that "start" can create the socket and it is
-still accessible from the container.
-
-Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
-(cherry picked from commit 25fd4a67571992b9121f77d2a4f0d89d4375f383)
----
- notify_socket.go | 132 +++++++++++++++++++++++++++++++++--------------
- signals.go       |   4 +-
- start.go         |  13 ++++-
- utils_linux.go   |  12 ++++-
- 4 files changed, 116 insertions(+), 45 deletions(-)
-
-diff --git a/notify_socket.go b/notify_socket.go
-index e7453c62..f313a7a6 100644
---- a/notify_socket.go
-+++ b/notify_socket.go
-@@ -7,11 +7,13 @@ import (
- 	"fmt"
- 	"net"
- 	"os"
-+	"path"
- 	"path/filepath"
-+	"strconv"
-+	"time"
- 
-+	"github.com/opencontainers/runc/libcontainer"
- 	"github.com/opencontainers/runtime-spec/specs-go"
--
--	"github.com/sirupsen/logrus"
- 	"github.com/urfave/cli"
- )
- 
-@@ -27,12 +29,12 @@ func newNotifySocket(context *cli.Context, notifySocketHost string, id string) *
- 	}
- 
- 	root := filepath.Join(context.GlobalString("root"), id)
--	path := filepath.Join(root, "notify.sock")
-+	socketPath := filepath.Join(root, "notify", "notify.sock")
- 
- 	notifySocket := &notifySocket{
- 		socket:     nil,
- 		host:       notifySocketHost,
--		socketPath: path,
-+		socketPath: socketPath,
- 	}
- 
- 	return notifySocket
-@@ -44,13 +46,19 @@ func (s *notifySocket) Close() error {
- 
- // If systemd is supporting sd_notify protocol, this function will add support
- // for sd_notify protocol from within the container.
--func (s *notifySocket) setupSpec(context *cli.Context, spec *specs.Spec) {
--	mount := specs.Mount{Destination: s.host, Source: s.socketPath, Options: []string{"bind"}}
-+func (s *notifySocket) setupSpec(context *cli.Context, spec *specs.Spec) error {
-+	pathInContainer := filepath.Join("/run/notify", path.Base(s.socketPath))
-+	mount := specs.Mount{
-+		Destination: path.Dir(pathInContainer),
-+		Source:      path.Dir(s.socketPath),
-+		Options:     []string{"bind", "nosuid", "noexec", "nodev", "ro"},
-+	}
- 	spec.Mounts = append(spec.Mounts, mount)
--	spec.Process.Env = append(spec.Process.Env, fmt.Sprintf("NOTIFY_SOCKET=%s", s.host))
-+	spec.Process.Env = append(spec.Process.Env, fmt.Sprintf("NOTIFY_SOCKET=%s", pathInContainer))
-+	return nil
- }
- 
--func (s *notifySocket) setupSocket() error {
-+func (s *notifySocket) bindSocket() error {
- 	addr := net.UnixAddr{
- 		Name: s.socketPath,
- 		Net:  "unixgram",
-@@ -71,46 +79,92 @@ func (s *notifySocket) setupSocket() error {
- 	return nil
- }
- 
--// pid1 must be set only with -d, as it is used to set the new process as the main process
--// for the service in systemd
--func (s *notifySocket) run(pid1 int) {
--	buf := make([]byte, 512)
--	notifySocketHostAddr := net.UnixAddr{Name: s.host, Net: "unixgram"}
-+func (s *notifySocket) setupSocketDirectory() error {
-+	return os.Mkdir(path.Dir(s.socketPath), 0755)
-+}
-+
-+func notifySocketStart(context *cli.Context, notifySocketHost, id string) (*notifySocket, error) {
-+	notifySocket := newNotifySocket(context, notifySocketHost, id)
-+	if notifySocket == nil {
-+		return nil, nil
-+	}
-+
-+	if err := notifySocket.bindSocket(); err != nil {
-+		return nil, err
-+	}
-+	return notifySocket, nil
-+}
-+
-+func (n *notifySocket) waitForContainer(container libcontainer.Container) error {
-+	s, err := container.State()
-+	if err != nil {
-+		return err
-+	}
-+	return n.run(s.InitProcessPid)
-+}
-+
-+func (n *notifySocket) run(pid1 int) error {
-+	if n.socket == nil {
-+		return nil
-+	}
-+	notifySocketHostAddr := net.UnixAddr{Name: n.host, Net: "unixgram"}
- 	client, err := net.DialUnix("unixgram", nil, &notifySocketHostAddr)
- 	if err != nil {
--		logrus.Error(err)
--		return
-+		return err
- 	}
--	for {
--		r, err := s.socket.Read(buf)
--		if err != nil {
--			break
--		}
--		var out bytes.Buffer
--		for _, line := range bytes.Split(buf[0:r], []byte{'\n'}) {
--			if bytes.HasPrefix(line, []byte("READY=")) {
--				_, err = out.Write(line)
--				if err != nil {
--					return
--				}
- 
--				_, err = out.Write([]byte{'\n'})
--				if err != nil {
--					return
--				}
-+	ticker := time.NewTicker(time.Millisecond * 100)
-+	defer ticker.Stop()
- 
--				_, err = client.Write(out.Bytes())
--				if err != nil {
-+	fileChan := make(chan []byte)
-+	go func() {
-+		for {
-+			buf := make([]byte, 4096)
-+			r, err := n.socket.Read(buf)
-+			if err != nil {
-+				return
-+			}
-+			got := buf[0:r]
-+			// systemd-ready sends a single datagram with the state string as payload,
-+			// so we don't need to worry about partial messages.
-+			for _, line := range bytes.Split(got, []byte{'\n'}) {
-+				if bytes.HasPrefix(got, []byte("READY=")) {
-+					fileChan <- line
- 					return
- 				}
-+			}
- 
--				// now we can inform systemd to use pid1 as the pid to monitor
--				if pid1 > 0 {
--					newPid := fmt.Sprintf("MAINPID=%d\n", pid1)
--					client.Write([]byte(newPid))
--				}
--				return
-+		}
-+	}()
-+
-+	for {
-+		select {
-+		case <-ticker.C:
-+			_, err := os.Stat(filepath.Join("/proc", strconv.Itoa(pid1)))
-+			if err != nil {
-+				return nil
- 			}
-+		case b := <-fileChan:
-+			var out bytes.Buffer
-+			_, err = out.Write(b)
-+			if err != nil {
-+				return err
-+			}
-+
-+			_, err = out.Write([]byte{'\n'})
-+			if err != nil {
-+				return err
-+			}
-+
-+			_, err = client.Write(out.Bytes())
-+			if err != nil {
-+				return err
-+			}
-+
-+			// now we can inform systemd to use pid1 as the pid to monitor
-+			newPid := fmt.Sprintf("MAINPID=%d\n", pid1)
-+			client.Write([]byte(newPid))
-+			return nil
- 		}
- 	}
- }
-diff --git a/signals.go b/signals.go
-index b67f65a0..dd25e094 100644
---- a/signals.go
-+++ b/signals.go
-@@ -70,6 +70,7 @@ func (h *signalHandler) forward(process *libcontainer.Process, tty *tty, detach
- 			h.notifySocket.run(pid1)
- 			return 0, nil
- 		}
-+		h.notifySocket.run(os.Getpid())
- 		go h.notifySocket.run(0)
- 	}
- 
-@@ -97,9 +98,6 @@ func (h *signalHandler) forward(process *libcontainer.Process, tty *tty, detach
- 					// status because we must ensure that any of the go specific process
- 					// fun such as flushing pipes are complete before we return.
- 					process.Wait()
--					if h.notifySocket != nil {
--						h.notifySocket.Close()
--					}
- 					return e.status, nil
- 				}
- 			}
-diff --git a/start.go b/start.go
-index 2bb698b2..3a1769a4 100644
---- a/start.go
-+++ b/start.go
-@@ -3,6 +3,7 @@ package main
- import (
- 	"errors"
- 	"fmt"
-+	"os"
- 
- 	"github.com/opencontainers/runc/libcontainer"
- 	"github.com/urfave/cli"
-@@ -31,7 +32,17 @@ your host.`,
- 		}
- 		switch status {
- 		case libcontainer.Created:
--			return container.Exec()
-+			notifySocket, err := notifySocketStart(context, os.Getenv("NOTIFY_SOCKET"), container.ID())
-+			if err != nil {
-+				return err
-+			}
-+			if err := container.Exec(); err != nil {
-+				return err
-+			}
-+			if notifySocket != nil {
-+				return notifySocket.waitForContainer(container)
-+			}
-+			return nil
- 		case libcontainer.Stopped:
- 			return errors.New("cannot start a container that has stopped")
- 		case libcontainer.Running:
-diff --git a/utils_linux.go b/utils_linux.go
-index 984e6b0f..46c26246 100644
---- a/utils_linux.go
-+++ b/utils_linux.go
-@@ -408,7 +408,9 @@ func startContainer(context *cli.Context, spec *specs.Spec, action CtAct, criuOp
- 
- 	notifySocket := newNotifySocket(context, os.Getenv("NOTIFY_SOCKET"), id)
- 	if notifySocket != nil {
--		notifySocket.setupSpec(context, spec)
-+		if err := notifySocket.setupSpec(context, spec); err != nil {
-+			return -1, err
-+		}
- 	}
- 
- 	container, err := createContainer(context, id, spec)
-@@ -417,10 +419,16 @@ func startContainer(context *cli.Context, spec *specs.Spec, action CtAct, criuOp
- 	}
- 
- 	if notifySocket != nil {
--		err := notifySocket.setupSocket()
-+		err := notifySocket.setupSocketDirectory()
- 		if err != nil {
- 			return -1, err
- 		}
-+		if action == CT_ACT_RUN {
-+			err := notifySocket.bindSocket()
-+			if err != nil {
-+				return -1, err
-+			}
-+		}
- 	}
- 
- 	// Support on-demand socket activation by passing file descriptors into the container init process.
--- 
-2.25.1
-
diff --git a/bsc1168481-0001-cgroup-devices-major-cleanups-and-minimal-transition.patch b/bsc1168481-0001-cgroup-devices-major-cleanups-and-minimal-transition.patch
deleted file mode 100644
index 85fea00..0000000
--- a/bsc1168481-0001-cgroup-devices-major-cleanups-and-minimal-transition.patch
+++ /dev/null
@@ -1,3832 +0,0 @@
-From 607e6ef7bf4f32652f5346e72b712b92ace98f58 Mon Sep 17 00:00:00 2001
-From: Aleksa Sarai <asarai@suse.de>
-Date: Sun, 10 May 2020 02:40:25 +1000
-Subject: [PATCH] cgroup: devices: major cleanups and minimal transition rules
-
-This is a backport of [1], which resolves many issues with the
-implementation of the runc devices cgroup code. Among other things, it
-removes some of the disruptive aspects of "runc update" which customers
-have run into. See [1] for more details.
-
-Rather than reproducing all of the commits, this is a squashed version
-of the following upstream commits:
-
- * 19a3e9bf8053 ("contrib: recvtty: add --no-stdin flag")
- * 11736767d2c5 ("cgroups: add GetFreezerState() helper to Manager")
- * 4d363ccbd9cc ("cgroup: devices: eradicate the Allow/Deny lists")
- * 5baa34c9134b ("specconv: remove default /dev/console access")
- * 8ed0c4c03641 ("configs: use different types for .Devices and .Resources.Devices")
- * 2ef5f417d923 ("cgroups: implement a devices cgroupv1 emulator")
- * b70aacc28f43 ("cgroupv1: devices: use minimal transition rules with devices.Emulator")
- * 410aef3ef858 ("cgroups: systemd: make use of Device*= properties")
-
-[1]: https://github.com/opencontainers/runc/pull/2391
-
-SUSE-Bugs: bsc#1168481
-Signed-off-by: Aleksa Sarai <asarai@suse.de>
----
- contrib/cmd/recvtty/recvtty.go                |   19 +-
- libcontainer/README.md                        |    5 +-
- libcontainer/cgroups/cgroups.go               |    3 +
- .../cgroups/devices/devices_emulator.go       |  355 ++++++
- .../cgroups/devices/devices_emulator_test.go  | 1096 +++++++++++++++++
- .../cgroups/ebpf/devicefilter/devicefilter.go |    4 +-
- .../ebpf/devicefilter/devicefilter_test.go    |   70 +-
- libcontainer/cgroups/fs/apply_raw.go          |   39 +-
- libcontainer/cgroups/fs/devices.go            |   87 +-
- libcontainer/cgroups/fs/devices_test.go       |   91 +-
- libcontainer/cgroups/fs/freezer.go            |   34 +-
- libcontainer/cgroups/fs2/devices.go           |   39 +-
- libcontainer/cgroups/fs2/freezer.go           |   62 +-
- libcontainer/cgroups/fs2/fs2.go               |    4 +
- libcontainer/cgroups/systemd/apply_systemd.go |  229 +++-
- .../cgroups/systemd/unified_hierarchy.go      |   14 +
- libcontainer/configs/cgroup_linux.go          |   11 +-
- libcontainer/configs/device.go                |  169 ++-
- libcontainer/configs/device_defaults.go       |  111 --
- libcontainer/container_linux.go               |   22 +-
- libcontainer/container_linux_test.go          |    5 +
- libcontainer/devices/devices.go               |   36 +-
- libcontainer/integration/template_test.go     |   11 +-
- libcontainer/rootfs_linux.go                  |   19 +-
- libcontainer/specconv/spec_linux.go           |  556 +++++----
- 25 files changed, 2416 insertions(+), 675 deletions(-)
- create mode 100644 libcontainer/cgroups/devices/devices_emulator.go
- create mode 100644 libcontainer/cgroups/devices/devices_emulator_test.go
- delete mode 100644 libcontainer/configs/device_defaults.go
-
-diff --git a/contrib/cmd/recvtty/recvtty.go b/contrib/cmd/recvtty/recvtty.go
-index a658b8d20202..00b30e1c39cd 100644
---- a/contrib/cmd/recvtty/recvtty.go
-+++ b/contrib/cmd/recvtty/recvtty.go
-@@ -65,7 +65,7 @@ func bail(err error) {
- 	os.Exit(1)
- }
- 
--func handleSingle(path string) error {
-+func handleSingle(path string, noStdin bool) error {
- 	// Open a socket.
- 	ln, err := net.Listen("unix", path)
- 	if err != nil {
-@@ -113,10 +113,12 @@ func handleSingle(path string) error {
- 		io.Copy(os.Stdout, c)
- 		quitChan <- struct{}{}
- 	}()
--	go func() {
--		io.Copy(c, os.Stdin)
--		quitChan <- struct{}{}
--	}()
-+	if !noStdin {
-+		go func() {
-+			io.Copy(c, os.Stdin)
-+			quitChan <- struct{}{}
-+		}()
-+	}
- 
- 	// Only close the master fd once we've stopped copying.
- 	<-quitChan
-@@ -201,6 +203,10 @@ func main() {
- 			Value: "",
- 			Usage: "Path to write daemon process ID to",
- 		},
-+		cli.BoolFlag{
-+			Name:  "no-stdin",
-+			Usage: "Disable stdin handling (no-op for null mode)",
-+		},
- 	}
- 
- 	app.Action = func(ctx *cli.Context) error {
-@@ -218,9 +224,10 @@ func main() {
- 			}
- 		}
- 
-+		noStdin := ctx.Bool("no-stdin")
- 		switch ctx.String("mode") {
- 		case "single":
--			if err := handleSingle(path); err != nil {
-+			if err := handleSingle(path, noStdin); err != nil {
- 				return err
- 			}
- 		case "null":
-diff --git a/libcontainer/README.md b/libcontainer/README.md
-index a791ca2d2494..6803ef56c5ba 100644
---- a/libcontainer/README.md
-+++ b/libcontainer/README.md
-@@ -155,8 +155,7 @@ config := &configs.Config{
- 		Parent: "system",
- 		Resources: &configs.Resources{
- 			MemorySwappiness: nil,
--			AllowAllDevices:  nil,
--			AllowedDevices:   configs.DefaultAllowedDevices,
-+			Devices:          specconv.AllowedDevices,
- 		},
- 	},
- 	MaskPaths: []string{
-@@ -166,7 +165,7 @@ config := &configs.Config{
- 	ReadonlyPaths: []string{
- 		"/proc/sys", "/proc/sysrq-trigger", "/proc/irq", "/proc/bus",
- 	},
--	Devices:  configs.DefaultAutoCreatedDevices,
-+	Devices:  specconv.AllowedDevices,
- 	Hostname: "testing",
- 	Mounts: []*configs.Mount{
- 		{
-diff --git a/libcontainer/cgroups/cgroups.go b/libcontainer/cgroups/cgroups.go
-index c0a965923f9b..b4bc05c15f96 100644
---- a/libcontainer/cgroups/cgroups.go
-+++ b/libcontainer/cgroups/cgroups.go
-@@ -49,6 +49,9 @@ type Manager interface {
- 
- 	// Gets the cgroup as configured.
- 	GetCgroups() (*configs.Cgroup, error)
-+
-+	// GetFreezerState retrieves the current FreezerState of the cgroup.
-+	GetFreezerState() (configs.FreezerState, error)
- }
- 
- type NotFoundError struct {
-diff --git a/libcontainer/cgroups/devices/devices_emulator.go b/libcontainer/cgroups/devices/devices_emulator.go
-new file mode 100644
-index 000000000000..cfb535bd4d8b
---- /dev/null
-+++ b/libcontainer/cgroups/devices/devices_emulator.go
-@@ -0,0 +1,355 @@
-+// +build linux
-+
-+package devices
-+
-+import (
-+	"bufio"
-+	"io"
-+	"regexp"
-+	"sort"
-+	"strconv"
-+
-+	"github.com/opencontainers/runc/libcontainer/configs"
-+
-+	"github.com/pkg/errors"
-+)
-+
-+// deviceMeta is a DeviceRule without the Allow or Permissions fields, and no
-+// wildcard-type support. It's effectively the "match" portion of a metadata
-+// rule, for the purposes of our emulation.
-+type deviceMeta struct {
-+	node  configs.DeviceType
-+	major int64
-+	minor int64
-+}
-+
-+// deviceRule is effectively the tuple (deviceMeta, DevicePermissions).
-+type deviceRule struct {
-+	meta  deviceMeta
-+	perms configs.DevicePermissions
-+}
-+
-+// deviceRules is a mapping of device metadata rules to the associated
-+// permissions in the ruleset.
-+type deviceRules map[deviceMeta]configs.DevicePermissions
-+
-+func (r deviceRules) orderedEntries() []deviceRule {
-+	var rules []deviceRule
-+	for meta, perms := range r {
-+		rules = append(rules, deviceRule{meta: meta, perms: perms})
-+	}
-+	sort.Slice(rules, func(i, j int) bool {
-+		// Sort by (major, minor, type).
-+		a, b := rules[i].meta, rules[j].meta
-+		return a.major < b.major ||
-+			(a.major == b.major && a.minor < b.minor) ||
-+			(a.major == b.major && a.minor == b.minor && a.node < b.node)
-+	})
-+	return rules
-+}
-+
-+type Emulator struct {
-+	defaultAllow bool
-+	rules        deviceRules
-+}
-+
-+func (e *Emulator) IsBlacklist() bool {
-+	return e.defaultAllow
-+}
-+
-+func (e *Emulator) IsAllowAll() bool {
-+	return e.IsBlacklist() && len(e.rules) == 0
-+}
-+
-+var devicesListRegexp = regexp.MustCompile(`^([abc])\s+(\d+|\*):(\d+|\*)\s+([rwm]+)$`)
-+
-+func parseLine(line string) (*deviceRule, error) {
-+	matches := devicesListRegexp.FindStringSubmatch(line)
-+	if matches == nil {
-+		return nil, errors.Errorf("line doesn't match devices.list format")
-+	}
-+	var (
-+		rule  deviceRule
-+		node  = matches[1]
-+		major = matches[2]
-+		minor = matches[3]
-+		perms = matches[4]
-+	)
-+
-+	// Parse the node type.
-+	switch node {
-+	case "a":
-+		// Super-special case -- "a" always means every device with every
-+		// access mode. In fact, for devices.list this actually indicates that
-+		// the cgroup is in black-list mode.
-+		// TODO: Double-check that the entire file is "a *:* rwm".
-+		return nil, nil
-+	case "b":
-+		rule.meta.node = configs.BlockDevice
-+	case "c":
-+		rule.meta.node = configs.CharDevice
-+	default:
-+		// Should never happen!
-+		return nil, errors.Errorf("unknown device type %q", node)
-+	}
-+
-+	// Parse the major number.
-+	if major == "*" {
-+		rule.meta.major = configs.Wildcard
-+	} else {
-+		val, err := strconv.ParseUint(major, 10, 32)
-+		if err != nil {
-+			return nil, errors.Wrap(err, "parse major number")
-+		}
-+		rule.meta.major = int64(val)
-+	}
-+
-+	// Parse the minor number.
-+	if minor == "*" {
-+		rule.meta.minor = configs.Wildcard
-+	} else {
-+		val, err := strconv.ParseUint(minor, 10, 32)
-+		if err != nil {
-+			return nil, errors.Wrap(err, "parse minor number")
-+		}
-+		rule.meta.minor = int64(val)
-+	}
-+
-+	// Parse the access permissions.
-+	rule.perms = configs.DevicePermissions(perms)
-+	if !rule.perms.IsValid() || rule.perms.IsEmpty() {
-+		// Should never happen!
-+		return nil, errors.Errorf("parse access mode: contained unknown modes or is empty: %q", perms)
-+	}
-+	return &rule, nil
-+}
-+
-+func (e *Emulator) addRule(rule deviceRule) error {
-+	if e.rules == nil {
-+		e.rules = make(map[deviceMeta]configs.DevicePermissions)
-+	}
-+
-+	// Merge with any pre-existing permissions.
-+	oldPerms := e.rules[rule.meta]
-+	newPerms := rule.perms.Union(oldPerms)
-+	e.rules[rule.meta] = newPerms
-+	return nil
-+}
-+
-+func (e *Emulator) rmRule(rule deviceRule) error {
-+	// Give an error if any of the permissions requested to be removed are
-+	// present in a partially-matching wildcard rule, because such rules will
-+	// be ignored by cgroupv1.
-+	//
-+	// This is a diversion from cgroupv1, but is necessary to avoid leading
-+	// users into a false sense of security. cgroupv1 will silently(!) ignore
-+	// requests to remove partial exceptions, but we really shouldn't do that.
-+	//
-+	// It may seem like we could just "split" wildcard rules which hit this
-+	// issue, but unfortunately there are 2^32 possible major and minor
-+	// numbers, which would exhaust kernel memory quickly if we did this. Not
-+	// to mention it'd be really slow (the kernel side is implemented as a
-+	// linked-list of exceptions).
-+	for _, partialMeta := range []deviceMeta{
-+		{node: rule.meta.node, major: configs.Wildcard, minor: rule.meta.minor},
-+		{node: rule.meta.node, major: rule.meta.major, minor: configs.Wildcard},
-+		{node: rule.meta.node, major: configs.Wildcard, minor: configs.Wildcard},
-+	} {
-+		// This wildcard rule is equivalent to the requested rule, so skip it.
-+		if rule.meta == partialMeta {
-+			continue
-+		}
-+		// Only give an error if the set of permissions overlap.
-+		partialPerms := e.rules[partialMeta]
-+		if !partialPerms.Intersection(rule.perms).IsEmpty() {
-+			return errors.Errorf("requested rule [%v %v] not supported by devices cgroupv1 (cannot punch hole in existing wildcard rule [%v %v])", rule.meta, rule.perms, partialMeta, partialPerms)
-+		}
-+	}
-+
-+	// Subtract all of the permissions listed from the full match rule. If the
-+	// rule didn't exist, all of this is a no-op.
-+	newPerms := e.rules[rule.meta].Difference(rule.perms)
-+	if newPerms.IsEmpty() {
-+		delete(e.rules, rule.meta)
-+	} else {
-+		e.rules[rule.meta] = newPerms
-+	}
-+	// TODO: The actual cgroup code doesn't care if an exception didn't exist
-+	//       during removal, so not erroring out here is /accurate/ but quite
-+	//       worrying. Maybe we should do additional validation, but again we
-+	//       have to worry about backwards-compatibility.
-+	return nil
-+}
-+
-+func (e *Emulator) allow(rule *deviceRule) error {
-+	// This cgroup is configured as a black-list. Reset the entire emulator,
-+	// and put is into black-list mode.
-+	if rule == nil || rule.meta.node == configs.WildcardDevice {
-+		*e = Emulator{
-+			defaultAllow: true,
-+			rules:        nil,
-+		}
-+		return nil
-+	}
-+
-+	var err error
-+	if e.defaultAllow {
-+		err = errors.Wrap(e.rmRule(*rule), "remove 'deny' exception")
-+	} else {
-+		err = errors.Wrap(e.addRule(*rule), "add 'allow' exception")
-+	}
-+	return err
-+}
-+
-+func (e *Emulator) deny(rule *deviceRule) error {
-+	// This cgroup is configured as a white-list. Reset the entire emulator,
-+	// and put is into white-list mode.
-+	if rule == nil || rule.meta.node == configs.WildcardDevice {
-+		*e = Emulator{
-+			defaultAllow: false,
-+			rules:        nil,
-+		}
-+		return nil
-+	}
-+
-+	var err error
-+	if e.defaultAllow {
-+		err = errors.Wrap(e.addRule(*rule), "add 'deny' exception")
-+	} else {
-+		err = errors.Wrap(e.rmRule(*rule), "remove 'allow' exception")
-+	}
-+	return err
-+}
-+
-+func (e *Emulator) Apply(rule configs.DeviceRule) error {
-+	if !rule.Type.CanCgroup() {
-+		return errors.Errorf("cannot add rule [%#v] with non-cgroup type %q", rule, rule.Type)
-+	}
-+
-+	innerRule := &deviceRule{
-+		meta: deviceMeta{
-+			node:  rule.Type,
-+			major: rule.Major,
-+			minor: rule.Minor,
-+		},
-+		perms: rule.Permissions,
-+	}
-+	if innerRule.meta.node == configs.WildcardDevice {
-+		innerRule = nil
-+	}
-+
-+	if rule.Allow {
-+		return e.allow(innerRule)
-+	} else {
-+		return e.deny(innerRule)
-+	}
-+}
-+
-+// EmulatorFromList takes a reader to a "devices.list"-like source, and returns
-+// a new Emulator that represents the state of the devices cgroup. Note that
-+// black-list devices cgroups cannot be fully reconstructed, due to limitations
-+// in the devices cgroup API. Instead, such cgroups are always treated as
-+// "allow all" cgroups.
-+func EmulatorFromList(list io.Reader) (*Emulator, error) {
-+	// Normally cgroups are in black-list mode by default, but the way we
-+	// figure out the current mode is whether or not devices.list has an
-+	// allow-all rule. So we default to a white-list, and the existence of an
-+	// "a *:* rwm" entry will tell us otherwise.
-+	e := &Emulator{
-+		defaultAllow: false,
-+	}
-+
-+	// Parse the "devices.list".
-+	s := bufio.NewScanner(list)
-+	for s.Scan() {
-+		line := s.Text()
-+		deviceRule, err := parseLine(line)
-+		if err != nil {
-+			return nil, errors.Wrapf(err, "parsing line %q", line)
-+		}
-+		// "devices.list" is an allow list. Note that this means that in
-+		// black-list mode, we have no idea what rules are in play. As a
-+		// result, we need to be very careful in Transition().
-+		if err := e.allow(deviceRule); err != nil {
-+			return nil, errors.Wrapf(err, "adding devices.list rule")
-+		}
-+	}
-+	if err := s.Err(); err != nil {
-+		return nil, errors.Wrap(err, "reading devices.list lines")
-+	}
-+	return e, nil
-+}
-+
-+// Transition calculates what is the minimally-disruptive set of rules need to
-+// be applied to a devices cgroup in order to transition to the given target.
-+// This means that any already-existing rules will not be applied, and
-+// disruptive rules (like denying all device access) will only be applied if
-+// necessary.
-+//
-+// This function is the sole reason for all of Emulator -- to allow us
-+// to figure out how to update a containers' cgroups without causing spurrious
-+// device errors (if possible).
-+func (source *Emulator) Transition(target *Emulator) ([]*configs.DeviceRule, error) {
-+	var transitionRules []*configs.DeviceRule
-+	oldRules := source.rules
-+
-+	// If the default policy doesn't match, we need to include a "disruptive"
-+	// rule (either allow-all or deny-all) in order to switch the cgroup to the
-+	// correct default policy.
-+	//
-+	// However, due to a limitation in "devices.list" we cannot be sure what
-+	// deny rules are in place in a black-list cgroup. Thus if the source is a
-+	// black-list we also have to include a disruptive rule.
-+	if source.IsBlacklist() || source.defaultAllow != target.defaultAllow {
-+		transitionRules = append(transitionRules, &configs.DeviceRule{
-+			Type:        'a',
-+			Major:       -1,
-+			Minor:       -1,
-+			Permissions: configs.DevicePermissions("rwm"),
-+			Allow:       target.defaultAllow,
-+		})
-+		// The old rules are only relevant if we aren't starting out with a
-+		// disruptive rule.
-+		oldRules = nil
-+	}
-+
-+	// NOTE: We traverse through the rules in a sorted order so we always write
-+	//       the same set of rules (this is to aid testing).
-+
-+	// First, we create inverse rules for any old rules not in the new set.
-+	// This includes partial-inverse rules for specific permissions. This is a
-+	// no-op if we added a disruptive rule, since oldRules will be empty.
-+	for _, rule := range oldRules.orderedEntries() {
-+		meta, oldPerms := rule.meta, rule.perms
-+		newPerms := target.rules[meta]
-+		droppedPerms := oldPerms.Difference(newPerms)
-+		if !droppedPerms.IsEmpty() {
-+			transitionRules = append(transitionRules, &configs.DeviceRule{
-+				Type:        meta.node,
-+				Major:       meta.major,
-+				Minor:       meta.minor,
-+				Permissions: droppedPerms,
-+				Allow:       target.defaultAllow,
-+			})
-+		}
-+	}
-+
-+	// Add any additional rules which weren't in the old set. We happen to
-+	// filter out rules which are present in both sets, though this isn't
-+	// strictly necessary.
-+	for _, rule := range target.rules.orderedEntries() {
-+		meta, newPerms := rule.meta, rule.perms
-+		oldPerms := oldRules[meta]
-+		gainedPerms := newPerms.Difference(oldPerms)
-+		if !gainedPerms.IsEmpty() {
-+			transitionRules = append(transitionRules, &configs.DeviceRule{
-+				Type:        meta.node,
-+				Major:       meta.major,
-+				Minor:       meta.minor,
-+				Permissions: gainedPerms,
-+				Allow:       !target.defaultAllow,
-+			})
-+		}
-+	}
-+	return transitionRules, nil
-+}
-diff --git a/libcontainer/cgroups/devices/devices_emulator_test.go b/libcontainer/cgroups/devices/devices_emulator_test.go
-new file mode 100644
-index 000000000000..f813accef239
---- /dev/null
-+++ b/libcontainer/cgroups/devices/devices_emulator_test.go
-@@ -0,0 +1,1096 @@
-+package devices
-+
-+import (
-+	"bytes"
-+	"reflect"
-+	"testing"
-+
-+	"github.com/opencontainers/runc/libcontainer/configs"
-+)
-+
-+func TestDeviceEmulatorLoad(t *testing.T) {
-+	tests := []struct {
-+		name, list string
-+		expected   *Emulator
-+	}{
-+		{
-+			name: "BlacklistMode",
-+			list: `a *:* rwm`,
-+			expected: &Emulator{
-+				defaultAllow: true,
-+			},
-+		},
-+		{
-+			name: "WhitelistBasic",
-+			list: `c 4:2 rw`,
-+			expected: &Emulator{
-+				defaultAllow: false,
-+				rules: deviceRules{
-+					{
-+						node:  configs.CharDevice,
-+						major: 4,
-+						minor: 2,
-+					}: configs.DevicePermissions("rw"),
-+				},
-+			},
-+		},
-+		{
-+			name: "WhitelistWildcard",
-+			list: `b 0:* m`,
-+			expected: &Emulator{
-+				defaultAllow: false,
-+				rules: deviceRules{
-+					{
-+						node:  configs.BlockDevice,
-+						major: 0,
-+						minor: configs.Wildcard,
-+					}: configs.DevicePermissions("m"),
-+				},
-+			},
-+		},
-+		{
-+			name: "WhitelistDuplicate",
-+			list: `c *:* rwm
-+c 1:1 r`,
-+			expected: &Emulator{
-+				defaultAllow: false,
-+				rules: deviceRules{
-+					{
-+						node:  configs.CharDevice,
-+						major: configs.Wildcard,
-+						minor: configs.Wildcard,
-+					}: configs.DevicePermissions("rwm"),
-+					// To match the kernel, we allow redundant rules.
-+					{
-+						node:  configs.CharDevice,
-+						major: 1,
-+						minor: 1,
-+					}: configs.DevicePermissions("r"),
-+				},
-+			},
-+		},
-+		{
-+			name: "WhitelistComplicated",
-+			list: `c *:* m
-+b *:* m
-+c 1:3 rwm
-+c 1:5 rwm
-+c 1:7 rwm
-+c 1:8 rwm
-+c 1:9 rwm
-+c 5:0 rwm
-+c 5:2 rwm
-+c 136:* rwm
-+c 10:200 rwm`,
-+			expected: &Emulator{
-+				defaultAllow: false,
-+				rules: deviceRules{
-+					{
-+						node:  configs.CharDevice,
-+						major: configs.Wildcard,
-+						minor: configs.Wildcard,
-+					}: configs.DevicePermissions("m"),
-+					{
-+						node:  configs.BlockDevice,
-+						major: configs.Wildcard,
-+						minor: configs.Wildcard,
-+					}: configs.DevicePermissions("m"),
-+					{
-+						node:  configs.CharDevice,
-+						major: 1,
-+						minor: 3,
-+					}: configs.DevicePermissions("rwm"),
-+					{
-+						node:  configs.CharDevice,
-+						major: 1,
-+						minor: 5,
-+					}: configs.DevicePermissions("rwm"),
-+					{
-+						node:  configs.CharDevice,
-+						major: 1,
-+						minor: 7,
-+					}: configs.DevicePermissions("rwm"),
-+					{
-+						node:  configs.CharDevice,
-+						major: 1,
-+						minor: 8,
-+					}: configs.DevicePermissions("rwm"),
-+					{
-+						node:  configs.CharDevice,
-+						major: 1,
-+						minor: 9,
-+					}: configs.DevicePermissions("rwm"),
-+					{
-+						node:  configs.CharDevice,
-+						major: 5,
-+						minor: 0,
-+					}: configs.DevicePermissions("rwm"),
-+					{
-+						node:  configs.CharDevice,
-+						major: 5,
-+						minor: 2,
-+					}: configs.DevicePermissions("rwm"),
-+					{
-+						node:  configs.CharDevice,
-+						major: 136,
-+						minor: configs.Wildcard,
-+					}: configs.DevicePermissions("rwm"),
-+					{
-+						node:  configs.CharDevice,
-+						major: 10,
-+						minor: 200,
-+					}: configs.DevicePermissions("rwm"),
-+				},
-+			},
-+		},
-+		// Some invalid lists.
-+		{
-+			name:     "InvalidFieldNumber",
-+			list:     `b 1:0`,
-+			expected: nil,
-+		},
-+		{
-+			name:     "InvalidDeviceType",
-+			list:     `p *:* rwm`,
-+			expected: nil,
-+		},
-+		{
-+			name:     "InvalidMajorNumber1",
-+			list:     `p -1:3 rwm`,
-+			expected: nil,
-+		},
-+		{
-+			name:     "InvalidMajorNumber2",
-+			list:     `c foo:27 rwm`,
-+			expected: nil,
-+		},
-+		{
-+			name:     "InvalidMinorNumber1",
-+			list:     `b 1:-4 rwm`,
-+			expected: nil,
-+		},
-+		{
-+			name:     "InvalidMinorNumber2",
-+			list:     `b 1:foo rwm`,
-+			expected: nil,
-+		},
-+		{
-+			name:     "InvalidPermissions",
-+			list:     `b 1:7 rwk`,
-+			expected: nil,
-+		},
-+	}
-+
-+	for _, test := range tests {
-+		test := test // capture range variable
-+		t.Run(test.name, func(t *testing.T) {
-+			list := bytes.NewBufferString(test.list)
-+			emu, err := EmulatorFromList(list)
-+			if err != nil && test.expected != nil {
-+				t.Fatalf("unexpected failure when creating emulator: %v", err)
-+			} else if err == nil && test.expected == nil {
-+				t.Fatalf("unexpected success when creating emulator: %#v", emu)
-+			}
-+
-+			if !reflect.DeepEqual(emu, test.expected) {
-+				t.Errorf("final emulator state mismatch: %#v != %#v", emu, test.expected)
-+			}
-+		})
-+	}
-+}
-+
-+func testDeviceEmulatorApply(t *testing.T, baseDefaultAllow bool) {
-+	tests := []struct {
-+		name           string
-+		rule           configs.DeviceRule
-+		base, expected *Emulator
-+	}{
-+		// Switch between default modes.
-+		{
-+			name: "SwitchToOtherMode",
-+			rule: configs.DeviceRule{
-+				Type:        configs.WildcardDevice,
-+				Major:       configs.Wildcard,
-+				Minor:       configs.Wildcard,
-+				Permissions: configs.DevicePermissions("rwm"),
-+				Allow:       !baseDefaultAllow,
-+			},
-+			base: &Emulator{
-+				defaultAllow: baseDefaultAllow,
-+				rules: deviceRules{
-+					{
-+						node:  configs.CharDevice,
-+						major: configs.Wildcard,
-+						minor: configs.Wildcard,
-+					}: configs.DevicePermissions("rwm"),
-+					{
-+						node:  configs.CharDevice,
-+						major: 1,
-+						minor: 1,
-+					}: configs.DevicePermissions("r"),
-+				},
-+			},
-+			expected: &Emulator{
-+				defaultAllow: !baseDefaultAllow,
-+				rules:        nil,
-+			},
-+		},
-+		{
-+			name: "SwitchToSameModeNoop",
-+			rule: configs.DeviceRule{
-+				Type:        configs.WildcardDevice,
-+				Major:       configs.Wildcard,
-+				Minor:       configs.Wildcard,
-+				Permissions: configs.DevicePermissions("rwm"),
-+				Allow:       baseDefaultAllow,
-+			},
-+			base: &Emulator{
-+				defaultAllow: baseDefaultAllow,
-+				rules:        nil,
-+			},
-+			expected: &Emulator{
-+				defaultAllow: baseDefaultAllow,
-+				rules:        nil,
-+			},
-+		},
-+		{
-+			name: "SwitchToSameMode",
-+			rule: configs.DeviceRule{
-+				Type:        configs.WildcardDevice,
-+				Major:       configs.Wildcard,
-+				Minor:       configs.Wildcard,
-+				Permissions: configs.DevicePermissions("rwm"),
-+				Allow:       baseDefaultAllow,
-+			},
-+			base: &Emulator{
-+				defaultAllow: baseDefaultAllow,
-+				rules: deviceRules{
-+					{
-+						node:  configs.CharDevice,
-+						major: configs.Wildcard,
-+						minor: configs.Wildcard,
-+					}: configs.DevicePermissions("rwm"),
-+					{
-+						node:  configs.CharDevice,
-+						major: 1,
-+						minor: 1,
-+					}: configs.DevicePermissions("r"),
-+				},
-+			},
-+			expected: &Emulator{
-+				defaultAllow: baseDefaultAllow,
-+				rules:        nil,
-+			},
-+		},
-+		// Rule addition logic.
-+		{
-+			name: "RuleAdditionBasic",
-+			rule: configs.DeviceRule{
-+				Type:        configs.CharDevice,
-+				Major:       42,
-+				Minor:       1337,
-+				Permissions: configs.DevicePermissions("rm"),
-+				Allow:       !baseDefaultAllow,
-+			},
-+			base: &Emulator{
-+				defaultAllow: baseDefaultAllow,
-+				rules: deviceRules{
-+					{
-+						node:  configs.CharDevice,
-+						major: 2,
-+						minor: 1,
-+					}: configs.DevicePermissions("rwm"),
-+					{
-+						node:  configs.BlockDevice,
-+						major: 1,
-+						minor: 5,
-+					}: configs.DevicePermissions("r"),
-+				},
-+			},
-+			expected: &Emulator{
-+				defaultAllow: baseDefaultAllow,
-+				rules: deviceRules{
-+					{
-+						node:  configs.CharDevice,
-+						major: 2,
-+						minor: 1,
-+					}: configs.DevicePermissions("rwm"),
-+					{
-+						node:  configs.BlockDevice,
-+						major: 1,
-+						minor: 5,
-+					}: configs.DevicePermissions("r"),
-+					{
-+						node:  configs.CharDevice,
-+						major: 42,
-+						minor: 1337,
-+					}: configs.DevicePermissions("rm"),
-+				},
-+			},
-+		},
-+		{
-+			name: "RuleAdditionBasicDuplicate",
-+			rule: configs.DeviceRule{
-+				Type:        configs.CharDevice,
-+				Major:       42,
-+				Minor:       1337,
-+				Permissions: configs.DevicePermissions("rm"),
-+				Allow:       !baseDefaultAllow,
-+			},
-+			base: &Emulator{
-+				defaultAllow: baseDefaultAllow,
-+				rules: deviceRules{
-+					{
-+						node:  configs.CharDevice,
-+						major: 42,
-+						minor: configs.Wildcard,
-+					}: configs.DevicePermissions("rwm"),
-+				},
-+			},
-+			expected: &Emulator{
-+				defaultAllow: baseDefaultAllow,
-+				rules: deviceRules{
-+					{
-+						node:  configs.CharDevice,
-+						major: 42,
-+						minor: configs.Wildcard,
-+					}: configs.DevicePermissions("rwm"),
-+					// To match the kernel, we allow redundant rules.
-+					{
-+						node:  configs.CharDevice,
-+						major: 42,
-+						minor: 1337,
-+					}: configs.DevicePermissions("rm"),
-+				},
-+			},
-+		},
-+		{
-+			name: "RuleAdditionBasicDuplicateNoop",
-+			rule: configs.DeviceRule{
-+				Type:        configs.CharDevice,
-+				Major:       42,
-+				Minor:       1337,
-+				Permissions: configs.DevicePermissions("rm"),
-+				Allow:       !baseDefaultAllow,
-+			},
-+			base: &Emulator{
-+				defaultAllow: baseDefaultAllow,
-+				rules: deviceRules{
-+					{
-+						node:  configs.CharDevice,
-+						major: 42,
-+						minor: 1337,
-+					}: configs.DevicePermissions("rm"),
-+				},
-+			},
-+			expected: &Emulator{
-+				defaultAllow: baseDefaultAllow,
-+				rules: deviceRules{
-+					{
-+						node:  configs.CharDevice,
-+						major: 42,
-+						minor: 1337,
-+					}: configs.DevicePermissions("rm"),
-+				},
-+			},
-+		},
-+		{
-+			name: "RuleAdditionMerge",
-+			rule: configs.DeviceRule{
-+				Type:        configs.BlockDevice,
-+				Major:       5,
-+				Minor:       12,
-+				Permissions: configs.DevicePermissions("rm"),
-+				Allow:       !baseDefaultAllow,
-+			},
-+			base: &Emulator{
-+				defaultAllow: baseDefaultAllow,
-+				rules: deviceRules{
-+					{
-+						node:  configs.CharDevice,
-+						major: 2,
-+						minor: 1,
-+					}: configs.DevicePermissions("rwm"),
-+					{
-+						node:  configs.BlockDevice,
-+						major: 5,
-+						minor: 12,
-+					}: configs.DevicePermissions("rw"),
-+				},
-+			},
-+			expected: &Emulator{
-+				defaultAllow: baseDefaultAllow,
-+				rules: deviceRules{
-+					{
-+						node:  configs.CharDevice,
-+						major: 2,
-+						minor: 1,
-+					}: configs.DevicePermissions("rwm"),
-+					{
-+						node:  configs.BlockDevice,
-+						major: 5,
-+						minor: 12,
-+					}: configs.DevicePermissions("rwm"),
-+				},
-+			},
-+		},
-+		{
-+			name: "RuleAdditionMergeWildcard",
-+			rule: configs.DeviceRule{
-+				Type:        configs.BlockDevice,
-+				Major:       5,
-+				Minor:       configs.Wildcard,
-+				Permissions: configs.DevicePermissions("rm"),
-+				Allow:       !baseDefaultAllow,
-+			},
-+			base: &Emulator{
-+				defaultAllow: baseDefaultAllow,
-+				rules: deviceRules{
-+					{
-+						node:  configs.CharDevice,
-+						major: 2,
-+						minor: 1,
-+					}: configs.DevicePermissions("rwm"),
-+					{
-+						node:  configs.BlockDevice,
-+						major: 5,
-+						minor: configs.Wildcard,
-+					}: configs.DevicePermissions("rw"),
-+				},
-+			},
-+			expected: &Emulator{
-+				defaultAllow: baseDefaultAllow,
-+				rules: deviceRules{
-+					{
-+						node:  configs.CharDevice,
-+						major: 2,
-+						minor: 1,
-+					}: configs.DevicePermissions("rwm"),
-+					{
-+						node:  configs.BlockDevice,
-+						major: 5,
-+						minor: configs.Wildcard,
-+					}: configs.DevicePermissions("rwm"),
-+				},
-+			},
-+		},
-+		{
-+			name: "RuleAdditionMergeNoop",
-+			rule: configs.DeviceRule{
-+				Type:        configs.BlockDevice,
-+				Major:       5,
-+				Minor:       12,
-+				Permissions: configs.DevicePermissions("r"),
-+				Allow:       !baseDefaultAllow,
-+			},
-+			base: &Emulator{
-+				defaultAllow: baseDefaultAllow,
-+				rules: deviceRules{
-+					{
-+						node:  configs.CharDevice,
-+						major: 2,
-+						minor: 1,
-+					}: configs.DevicePermissions("rwm"),
-+					{
-+						node:  configs.BlockDevice,
-+						major: 5,
-+						minor: 12,
-+					}: configs.DevicePermissions("rw"),
-+				},
-+			},
-+			expected: &Emulator{
-+				defaultAllow: baseDefaultAllow,
-+				rules: deviceRules{
-+					{
-+						node:  configs.CharDevice,
-+						major: 2,
-+						minor: 1,
-+					}: configs.DevicePermissions("rwm"),
-+					{
-+						node:  configs.BlockDevice,
-+						major: 5,
-+						minor: 12,
-+					}: configs.DevicePermissions("rw"),
-+				},
-+			},
-+		},
-+		// Rule removal logic.
-+		{
-+			name: "RuleRemovalBasic",
-+			rule: configs.DeviceRule{
-+				Type:        configs.CharDevice,
-+				Major:       42,
-+				Minor:       1337,
-+				Permissions: configs.DevicePermissions("rm"),
-+				Allow:       baseDefaultAllow,
-+			},
-+			base: &Emulator{
-+				defaultAllow: baseDefaultAllow,
-+				rules: deviceRules{
-+					{
-+						node:  configs.CharDevice,
-+						major: 42,
-+						minor: 1337,
-+					}: configs.DevicePermissions("rm"),
-+					{
-+						node:  configs.BlockDevice,
-+						major: 1,
-+						minor: 5,
-+					}: configs.DevicePermissions("r"),
-+				},
-+			},
-+			expected: &Emulator{
-+				defaultAllow: baseDefaultAllow,
-+				rules: deviceRules{
-+					{
-+						node:  configs.BlockDevice,
-+						major: 1,
-+						minor: 5,
-+					}: configs.DevicePermissions("r"),
-+				},
-+			},
-+		},
-+		{
-+			name: "RuleRemovalNonexistent",
-+			rule: configs.DeviceRule{
-+				Type:        configs.CharDevice,
-+				Major:       4,
-+				Minor:       1,
-+				Permissions: configs.DevicePermissions("rw"),
-+				Allow:       baseDefaultAllow,
-+			},
-+			base: &Emulator{
-+				defaultAllow: baseDefaultAllow,
-+				rules: deviceRules{
-+					{
-+						node:  configs.BlockDevice,
-+						major: 1,
-+						minor: 5,
-+					}: configs.DevicePermissions("r"),
-+				},
-+			},
-+			expected: &Emulator{
-+				defaultAllow: baseDefaultAllow,
-+				rules: deviceRules{
-+					{
-+						node:  configs.BlockDevice,
-+						major: 1,
-+						minor: 5,
-+					}: configs.DevicePermissions("r"),
-+				},
-+			},
-+		},
-+		{
-+			name: "RuleRemovalFull",
-+			rule: configs.DeviceRule{
-+				Type:        configs.CharDevice,
-+				Major:       42,
-+				Minor:       1337,
-+				Permissions: configs.DevicePermissions("rw"),
-+				Allow:       baseDefaultAllow,
-+			},
-+			base: &Emulator{
-+				defaultAllow: baseDefaultAllow,
-+				rules: deviceRules{
-+					{
-+						node:  configs.CharDevice,
-+						major: 42,
-+						minor: 1337,
-+					}: configs.DevicePermissions("w"),
-+					{
-+						node:  configs.BlockDevice,
-+						major: 1,
-+						minor: 5,
-+					}: configs.DevicePermissions("r"),
-+				},
-+			},
-+			expected: &Emulator{
-+				defaultAllow: baseDefaultAllow,
-+				rules: deviceRules{
-+					{
-+						node:  configs.BlockDevice,
-+						major: 1,
-+						minor: 5,
-+					}: configs.DevicePermissions("r"),
-+				},
-+			},
-+		},
-+		{
-+			name: "RuleRemovalPartial",
-+			rule: configs.DeviceRule{
-+				Type:        configs.CharDevice,
-+				Major:       42,
-+				Minor:       1337,
-+				Permissions: configs.DevicePermissions("r"),
-+				Allow:       baseDefaultAllow,
-+			},
-+			base: &Emulator{
-+				defaultAllow: baseDefaultAllow,
-+				rules: deviceRules{
-+					{
-+						node:  configs.CharDevice,
-+						major: 42,
-+						minor: 1337,
-+					}: configs.DevicePermissions("rm"),
-+					{
-+						node:  configs.BlockDevice,
-+						major: 1,
-+						minor: 5,
-+					}: configs.DevicePermissions("r"),
-+				},
-+			},
-+			expected: &Emulator{
-+				defaultAllow: baseDefaultAllow,
-+				rules: deviceRules{
-+					{
-+						node:  configs.CharDevice,
-+						major: 42,
-+						minor: 1337,
-+					}: configs.DevicePermissions("m"),
-+					{
-+						node:  configs.BlockDevice,
-+						major: 1,
-+						minor: 5,
-+					}: configs.DevicePermissions("r"),
-+				},
-+			},
-+		},
-+		// Check our non-canonical behaviour when it comes to try to "punch
-+		// out" holes in a wildcard rule.
-+		{
-+			name: "RuleRemovalWildcardPunchoutImpossible",
-+			rule: configs.DeviceRule{
-+				Type:        configs.CharDevice,
-+				Major:       42,
-+				Minor:       1337,
-+				Permissions: configs.DevicePermissions("r"),
-+				Allow:       baseDefaultAllow,
-+			},
-+			base: &Emulator{
-+				defaultAllow: baseDefaultAllow,
-+				rules: deviceRules{
-+					{
-+						node:  configs.CharDevice,
-+						major: 42,
-+						minor: configs.Wildcard,
-+					}: configs.DevicePermissions("rm"),
-+					{
-+						node:  configs.CharDevice,
-+						major: 42,
-+						minor: 1337,
-+					}: configs.DevicePermissions("r"),
-+				},
-+			},
-+			expected: nil,
-+		},
-+		{
-+			name: "RuleRemovalWildcardPunchoutPossible",
-+			rule: configs.DeviceRule{
-+				Type:        configs.CharDevice,
-+				Major:       42,
-+				Minor:       1337,
-+				Permissions: configs.DevicePermissions("r"),
-+				Allow:       baseDefaultAllow,
-+			},
-+			base: &Emulator{
-+				defaultAllow: baseDefaultAllow,
-+				rules: deviceRules{
-+					{
-+						node:  configs.CharDevice,
-+						major: 42,
-+						minor: configs.Wildcard,
-+					}: configs.DevicePermissions("wm"),
-+					{
-+						node:  configs.CharDevice,
-+						major: 42,
-+						minor: 1337,
-+					}: configs.DevicePermissions("r"),
-+				},
-+			},
-+			expected: &Emulator{
-+				defaultAllow: baseDefaultAllow,
-+				rules: deviceRules{
-+					{
-+						node:  configs.CharDevice,
-+						major: 42,
-+						minor: configs.Wildcard,
-+					}: configs.DevicePermissions("wm"),
-+				},
-+			},
-+		},
-+	}
-+
-+	for _, test := range tests {
-+		test := test
-+		t.Run(test.name, func(t *testing.T) {
-+			err := test.base.Apply(test.rule)
-+			if err != nil && test.expected != nil {
-+				t.Fatalf("unexpected failure when applying apply rule: %v", err)
-+			} else if err == nil && test.expected == nil {
-+				t.Fatalf("unexpected success when applying apply rule: %#v", test.base)
-+			}
-+
-+			if test.expected != nil && !reflect.DeepEqual(test.base, test.expected) {
-+				t.Errorf("final emulator state mismatch: %#v != %#v", test.base, test.expected)
-+			}
-+		})
-+	}
-+}
-+
-+func TestDeviceEmulatorWhitelistApply(t *testing.T) {
-+	testDeviceEmulatorApply(t, false)
-+}
-+
-+func TestDeviceEmulatorBlacklistApply(t *testing.T) {
-+	testDeviceEmulatorApply(t, true)
-+}
-+
-+func testDeviceEmulatorTransition(t *testing.T, sourceDefaultAllow bool) {
-+	tests := []struct {
-+		name           string
-+		source, target *Emulator
-+		expected       []*configs.DeviceRule
-+	}{
-+		// No-op changes.
-+		{
-+			name: "Noop",
-+			source: &Emulator{
-+				defaultAllow: sourceDefaultAllow,
-+				rules: deviceRules{
-+					{
-+						node:  configs.CharDevice,
-+						major: 42,
-+						minor: configs.Wildcard,
-+					}: configs.DevicePermissions("wm"),
-+				},
-+			},
-+			target: &Emulator{
-+				defaultAllow: sourceDefaultAllow,
-+				rules: deviceRules{
-+					{
-+						node:  configs.CharDevice,
-+						major: 42,
-+						minor: configs.Wildcard,
-+					}: configs.DevicePermissions("wm"),
-+				},
-+			},
-+			// Identical white-lists produce no extra rules.
-+			expected: nil,
-+		},
-+		// Switching modes.
-+		{
-+			name: "SwitchToOtherMode",
-+			source: &Emulator{
-+				defaultAllow: sourceDefaultAllow,
-+				rules: deviceRules{
-+					{
-+						node:  configs.CharDevice,
-+						major: 1,
-+						minor: 2,
-+					}: configs.DevicePermissions("rwm"),
-+				},
-+			},
-+			target: &Emulator{
-+				defaultAllow: !sourceDefaultAllow,
-+				rules: deviceRules{
-+					{
-+						node:  configs.BlockDevice,
-+						major: 42,
-+						minor: configs.Wildcard,
-+					}: configs.DevicePermissions("wm"),
-+				},
-+			},
-+			expected: []*configs.DeviceRule{
-+				// Clear-all rule.
-+				{
-+					Type:        configs.WildcardDevice,
-+					Major:       configs.Wildcard,
-+					Minor:       configs.Wildcard,
-+					Permissions: configs.DevicePermissions("rwm"),
-+					Allow:       !sourceDefaultAllow,
-+				},
-+				// The actual rule-set.
-+				{
-+					Type:        configs.BlockDevice,
-+					Major:       42,
-+					Minor:       configs.Wildcard,
-+					Permissions: configs.DevicePermissions("wm"),
-+					Allow:       sourceDefaultAllow,
-+				},
-+			},
-+		},
-+		// Rule changes.
-+		{
-+			name: "RuleAddition",
-+			source: &Emulator{
-+				defaultAllow: sourceDefaultAllow,
-+				rules: deviceRules{
-+					{
-+						node:  configs.CharDevice,
-+						major: 1,
-+						minor: 2,
-+					}: configs.DevicePermissions("rwm"),
-+				},
-+			},
-+			target: &Emulator{
-+				defaultAllow: sourceDefaultAllow,
-+				rules: deviceRules{
-+					{
-+						node:  configs.CharDevice,
-+						major: 1,
-+						minor: 2,
-+					}: configs.DevicePermissions("rwm"),
-+					{
-+						node:  configs.BlockDevice,
-+						major: 42,
-+						minor: 1337,
-+					}: configs.DevicePermissions("rwm"),
-+				},
-+			},
-+			expected: []*configs.DeviceRule{
-+				{
-+					Type:        configs.BlockDevice,
-+					Major:       42,
-+					Minor:       1337,
-+					Permissions: configs.DevicePermissions("rwm"),
-+					Allow:       !sourceDefaultAllow,
-+				},
-+			},
-+		},
-+		{
-+			name: "RuleRemoval",
-+			source: &Emulator{
-+				defaultAllow: sourceDefaultAllow,
-+				rules: deviceRules{
-+					{
-+						node:  configs.CharDevice,
-+						major: 1,
-+						minor: 2,
-+					}: configs.DevicePermissions("rwm"),
-+					{
-+						node:  configs.BlockDevice,
-+						major: 42,
-+						minor: 1337,
-+					}: configs.DevicePermissions("rwm"),
-+				},
-+			},
-+			target: &Emulator{
-+				defaultAllow: sourceDefaultAllow,
-+				rules: deviceRules{
-+					{
-+						node:  configs.CharDevice,
-+						major: 1,
-+						minor: 2,
-+					}: configs.DevicePermissions("rwm"),
-+				},
-+			},
-+			expected: []*configs.DeviceRule{
-+				{
-+					Type:        configs.BlockDevice,
-+					Major:       42,
-+					Minor:       1337,
-+					Permissions: configs.DevicePermissions("rwm"),
-+					Allow:       sourceDefaultAllow,
-+				},
-+			},
-+		},
-+		{
-+			name: "RuleMultipleAdditionRemoval",
-+			source: &Emulator{
-+				defaultAllow: sourceDefaultAllow,
-+				rules: deviceRules{
-+					{
-+						node:  configs.CharDevice,
-+						major: 1,
-+						minor: 2,
-+					}: configs.DevicePermissions("rwm"),
-+					{
-+						node:  configs.BlockDevice,
-+						major: 3,
-+						minor: 9,
-+					}: configs.DevicePermissions("rw"),
-+				},
-+			},
-+			target: &Emulator{
-+				defaultAllow: sourceDefaultAllow,
-+				rules: deviceRules{
-+					{
-+						node:  configs.CharDevice,
-+						major: 1,
-+						minor: 2,
-+					}: configs.DevicePermissions("rwm"),
-+				},
-+			},
-+			expected: []*configs.DeviceRule{
-+				{
-+					Type:        configs.BlockDevice,
-+					Major:       3,
-+					Minor:       9,
-+					Permissions: configs.DevicePermissions("rw"),
-+					Allow:       sourceDefaultAllow,
-+				},
-+			},
-+		},
-+		// Modifying the access permissions.
-+		{
-+			name: "RulePartialAddition",
-+			source: &Emulator{
-+				defaultAllow: sourceDefaultAllow,
-+				rules: deviceRules{
-+					{
-+						node:  configs.CharDevice,
-+						major: 1,
-+						minor: 2,
-+					}: configs.DevicePermissions("r"),
-+				},
-+			},
-+			target: &Emulator{
-+				defaultAllow: sourceDefaultAllow,
-+				rules: deviceRules{
-+					{
-+						node:  configs.CharDevice,
-+						major: 1,
-+						minor: 2,
-+					}: configs.DevicePermissions("rwm"),
-+				},
-+			},
-+			expected: []*configs.DeviceRule{
-+				{
-+					Type:        configs.CharDevice,
-+					Major:       1,
-+					Minor:       2,
-+					Permissions: configs.DevicePermissions("wm"),
-+					Allow:       !sourceDefaultAllow,
-+				},
-+			},
-+		},
-+		{
-+			name: "RulePartialRemoval",
-+			source: &Emulator{
-+				defaultAllow: sourceDefaultAllow,
-+				rules: deviceRules{
-+					{
-+						node:  configs.CharDevice,
-+						major: 1,
-+						minor: 2,
-+					}: configs.DevicePermissions("rw"),
-+				},
-+			},
-+			target: &Emulator{
-+				defaultAllow: sourceDefaultAllow,
-+				rules: deviceRules{
-+					{
-+						node:  configs.CharDevice,
-+						major: 1,
-+						minor: 2,
-+					}: configs.DevicePermissions("w"),
-+				},
-+			},
-+			expected: []*configs.DeviceRule{
-+				{
-+					Type:        configs.CharDevice,
-+					Major:       1,
-+					Minor:       2,
-+					Permissions: configs.DevicePermissions("r"),
-+					Allow:       sourceDefaultAllow,
-+				},
-+			},
-+		},
-+		{
-+			name: "RulePartialBoth",
-+			source: &Emulator{
-+				defaultAllow: sourceDefaultAllow,
-+				rules: deviceRules{
-+					{
-+						node:  configs.CharDevice,
-+						major: 1,
-+						minor: 2,
-+					}: configs.DevicePermissions("rw"),
-+				},
-+			},
-+			target: &Emulator{
-+				defaultAllow: sourceDefaultAllow,
-+				rules: deviceRules{
-+					{
-+						node:  configs.CharDevice,
-+						major: 1,
-+						minor: 2,
-+					}: configs.DevicePermissions("rm"),
-+				},
-+			},
-+			expected: []*configs.DeviceRule{
-+				{
-+					Type:        configs.CharDevice,
-+					Major:       1,
-+					Minor:       2,
-+					Permissions: configs.DevicePermissions("w"),
-+					Allow:       sourceDefaultAllow,
-+				},
-+				{
-+					Type:        configs.CharDevice,
-+					Major:       1,
-+					Minor:       2,
-+					Permissions: configs.DevicePermissions("m"),
-+					Allow:       !sourceDefaultAllow,
-+				},
-+			},
-+		},
-+	}
-+
-+	for _, test := range tests {
-+		test := test
-+		t.Run(test.name, func(t *testing.T) {
-+			// If we are in black-list mode, we need to prepend the relevant
-+			// clear-all rule (the expected rule lists are written with
-+			// white-list mode in mind), and then make a full copy of the
-+			// target rules.
-+			if sourceDefaultAllow && test.source.defaultAllow == test.target.defaultAllow {
-+				test.expected = []*configs.DeviceRule{{
-+					Type:        configs.WildcardDevice,
-+					Major:       configs.Wildcard,
-+					Minor:       configs.Wildcard,
-+					Permissions: configs.DevicePermissions("rwm"),
-+					Allow:       test.target.defaultAllow,
-+				}}
-+				for _, rule := range test.target.rules.orderedEntries() {
-+					test.expected = append(test.expected, &configs.DeviceRule{
-+						Type:        rule.meta.node,
-+						Major:       rule.meta.major,
-+						Minor:       rule.meta.minor,
-+						Permissions: rule.perms,
-+						Allow:       !test.target.defaultAllow,
-+					})
-+				}
-+			}
-+
-+			rules, err := test.source.Transition(test.target)
-+			if err != nil {
-+				t.Fatalf("unexpected error while calculating transition rules: %#v", err)
-+			}
-+
-+			if !reflect.DeepEqual(rules, test.expected) {
-+				t.Errorf("rules don't match expected set: %#v != %#v", rules, test.expected)
-+			}
-+
-+			// Apply the rules to the source to see if it actually transitions
-+			// correctly. This is all emulated but it's a good thing to
-+			// double-check.
-+			for _, rule := range rules {
-+				if err := test.source.Apply(*rule); err != nil {
-+					t.Fatalf("error while applying transition rule [%#v]: %v", rule, err)
-+				}
-+			}
-+			if !reflect.DeepEqual(test.source, test.target) {
-+				t.Errorf("transition incomplete after applying all rules: %#v != %#v", test.source, test.target)
-+			}
-+		})
-+	}
-+}
-+
-+func TestDeviceEmulatorTransitionFromBlacklist(t *testing.T) {
-+	testDeviceEmulatorTransition(t, true)
-+}
-+
-+func TestDeviceEmulatorTransitionFromWhitelist(t *testing.T) {
-+	testDeviceEmulatorTransition(t, false)
-+}
-diff --git a/libcontainer/cgroups/ebpf/devicefilter/devicefilter.go b/libcontainer/cgroups/ebpf/devicefilter/devicefilter.go
-index 847ce8ef1a0d..af9275451cd8 100644
---- a/libcontainer/cgroups/ebpf/devicefilter/devicefilter.go
-+++ b/libcontainer/cgroups/ebpf/devicefilter/devicefilter.go
-@@ -22,7 +22,7 @@ const (
- )
- 
- // DeviceFilter returns eBPF device filter program and its license string
--func DeviceFilter(devices []*configs.Device) (asm.Instructions, string, error) {
-+func DeviceFilter(devices []*configs.DeviceRule) (asm.Instructions, string, error) {
- 	p := &program{}
- 	p.init()
- 	for i := len(devices) - 1; i >= 0; i-- {
-@@ -67,7 +67,7 @@ func (p *program) init() {
- }
- 
- // appendDevice needs to be called from the last element of OCI linux.resources.devices to the head element.
--func (p *program) appendDevice(dev *configs.Device) error {
-+func (p *program) appendDevice(dev *configs.DeviceRule) error {
- 	if p.blockID < 0 {
- 		return errors.New("the program is finalized")
- 	}
-diff --git a/libcontainer/cgroups/ebpf/devicefilter/devicefilter_test.go b/libcontainer/cgroups/ebpf/devicefilter/devicefilter_test.go
-index 59ff4b49bd35..885bf4db4ef5 100644
---- a/libcontainer/cgroups/ebpf/devicefilter/devicefilter_test.go
-+++ b/libcontainer/cgroups/ebpf/devicefilter/devicefilter_test.go
-@@ -20,7 +20,7 @@ func hash(s, comm string) string {
- 	return strings.Join(res, "\n")
- }
- 
--func testDeviceFilter(t testing.TB, devices []*configs.Device, expectedStr string) {
-+func testDeviceFilter(t testing.TB, devices []*configs.DeviceRule, expectedStr string) {
- 	insts, _, err := DeviceFilter(devices)
- 	if err != nil {
- 		t.Fatalf("%s: %v (devices: %+v)", t.Name(), err, devices)
-@@ -81,71 +81,69 @@ block-2:
-         18: Exit
- block-3:
-         19: JNEImm dst: r2 off: -1 imm: 2 <block-4>
--        20: JNEImm dst: r4 off: -1 imm: 5 <block-4>
--        21: JNEImm dst: r5 off: -1 imm: 1 <block-4>
-+        20: JNEImm dst: r4 off: -1 imm: 1 <block-4>
-+        21: JNEImm dst: r5 off: -1 imm: 9 <block-4>
-         22: Mov32Imm dst: r0 imm: 1
-         23: Exit
- block-4:
-         24: JNEImm dst: r2 off: -1 imm: 2 <block-5>
-         25: JNEImm dst: r4 off: -1 imm: 1 <block-5>
--        26: JNEImm dst: r5 off: -1 imm: 9 <block-5>
-+        26: JNEImm dst: r5 off: -1 imm: 5 <block-5>
-         27: Mov32Imm dst: r0 imm: 1
-         28: Exit
- block-5:
-         29: JNEImm dst: r2 off: -1 imm: 2 <block-6>
--        30: JNEImm dst: r4 off: -1 imm: 1 <block-6>
--        31: JNEImm dst: r5 off: -1 imm: 5 <block-6>
-+        30: JNEImm dst: r4 off: -1 imm: 5 <block-6>
-+        31: JNEImm dst: r5 off: -1 imm: 0 <block-6>
-         32: Mov32Imm dst: r0 imm: 1
-         33: Exit
- block-6:
-         34: JNEImm dst: r2 off: -1 imm: 2 <block-7>
--        35: JNEImm dst: r4 off: -1 imm: 5 <block-7>
--        36: JNEImm dst: r5 off: -1 imm: 0 <block-7>
-+        35: JNEImm dst: r4 off: -1 imm: 1 <block-7>
-+        36: JNEImm dst: r5 off: -1 imm: 7 <block-7>
-         37: Mov32Imm dst: r0 imm: 1
-         38: Exit
- block-7:
-         39: JNEImm dst: r2 off: -1 imm: 2 <block-8>
-         40: JNEImm dst: r4 off: -1 imm: 1 <block-8>
--        41: JNEImm dst: r5 off: -1 imm: 7 <block-8>
-+        41: JNEImm dst: r5 off: -1 imm: 8 <block-8>
-         42: Mov32Imm dst: r0 imm: 1
-         43: Exit
- block-8:
-         44: JNEImm dst: r2 off: -1 imm: 2 <block-9>
-         45: JNEImm dst: r4 off: -1 imm: 1 <block-9>
--        46: JNEImm dst: r5 off: -1 imm: 8 <block-9>
-+        46: JNEImm dst: r5 off: -1 imm: 3 <block-9>
-         47: Mov32Imm dst: r0 imm: 1
-         48: Exit
- block-9:
--        49: JNEImm dst: r2 off: -1 imm: 2 <block-10>
--        50: JNEImm dst: r4 off: -1 imm: 1 <block-10>
--        51: JNEImm dst: r5 off: -1 imm: 3 <block-10>
--        52: Mov32Imm dst: r0 imm: 1
--        53: Exit
--block-10:
- // (b, wildcard, wildcard, m, true)
--        54: JNEImm dst: r2 off: -1 imm: 1 <block-11>
--        55: Mov32Reg dst: r1 src: r3
--        56: And32Imm dst: r1 imm: 1
--        57: JEqImm dst: r1 off: -1 imm: 0 <block-11>
--        58: Mov32Imm dst: r0 imm: 1
--        59: Exit
--block-11:
-+        49: JNEImm dst: r2 off: -1 imm: 1 <block-10>
-+        50: Mov32Reg dst: r1 src: r3
-+        51: And32Imm dst: r1 imm: 1
-+        52: JEqImm dst: r1 off: -1 imm: 0 <block-10>
-+        53: Mov32Imm dst: r0 imm: 1
-+        54: Exit
-+block-10:
- // (c, wildcard, wildcard, m, true)
--        60: JNEImm dst: r2 off: -1 imm: 2 <block-12>
--        61: Mov32Reg dst: r1 src: r3
--        62: And32Imm dst: r1 imm: 1
--        63: JEqImm dst: r1 off: -1 imm: 0 <block-12>
--        64: Mov32Imm dst: r0 imm: 1
--        65: Exit
--block-12:
--        66: Mov32Imm dst: r0 imm: 0
--        67: Exit
-+        55: JNEImm dst: r2 off: -1 imm: 2 <block-11>
-+        56: Mov32Reg dst: r1 src: r3
-+        57: And32Imm dst: r1 imm: 1
-+        58: JEqImm dst: r1 off: -1 imm: 0 <block-11>
-+        59: Mov32Imm dst: r0 imm: 1
-+        60: Exit
-+block-11:
-+        61: Mov32Imm dst: r0 imm: 0
-+        62: Exit
- `
--	testDeviceFilter(t, specconv.AllowedDevices, expected)
-+	var devices []*configs.DeviceRule
-+	for _, device := range specconv.AllowedDevices {
-+		devices = append(devices, &device.DeviceRule)
-+	}
-+	testDeviceFilter(t, devices, expected)
- }
- 
- func TestDeviceFilter_Privileged(t *testing.T) {
--	devices := []*configs.Device{
-+	devices := []*configs.DeviceRule{
- 		{
- 			Type:        'a',
- 			Major:       -1,
-@@ -171,7 +169,7 @@ block-0:
- }
- 
- func TestDeviceFilter_PrivilegedExceptSingleDevice(t *testing.T) {
--	devices := []*configs.Device{
-+	devices := []*configs.DeviceRule{
- 		{
- 			Type:        'a',
- 			Major:       -1,
-@@ -210,7 +208,7 @@ block-1:
- }
- 
- func TestDeviceFilter_Weird(t *testing.T) {
--	devices := []*configs.Device{
-+	devices := []*configs.DeviceRule{
- 		{
- 			Type:        'b',
- 			Major:       8,
-diff --git a/libcontainer/cgroups/fs/apply_raw.go b/libcontainer/cgroups/fs/apply_raw.go
-index ec148b489051..11a15660beb1 100644
---- a/libcontainer/cgroups/fs/apply_raw.go
-+++ b/libcontainer/cgroups/fs/apply_raw.go
-@@ -162,9 +162,6 @@ func (m *Manager) Apply(pid int) (err error) {
- 	}
- 
- 	for _, sys := range m.getSubsystems() {
--		// TODO: Apply should, ideally, be reentrant or be broken up into a separate
--		// create and join phase so that the cgroup hierarchy for a container can be
--		// created then join consists of writing the process pids to cgroup.procs
- 		p, err := d.path(sys.Name())
- 		if err != nil {
- 			// The non-presence of the devices subsystem is
-@@ -177,10 +174,10 @@ func (m *Manager) Apply(pid int) (err error) {
- 		m.Paths[sys.Name()] = p
- 
- 		if err := sys.Apply(d); err != nil {
--			// In the case of rootless (including euid=0 in userns), where an explicit cgroup path hasn't
--			// been set, we don't bail on error in case of permission problems.
--			// Cases where limits have been set (and we couldn't create our own
--			// cgroup) are handled by Set.
-+			// In the case of rootless (including euid=0 in userns), where an
-+			// explicit cgroup path hasn't been set, we don't bail on error in
-+			// case of permission problems. Cases where limits have been set
-+			// (and we couldn't create our own cgroup) are handled by Set.
- 			if isIgnorableError(m.Rootless, err) && m.Cgroups.Path == "" {
- 				delete(m.Paths, sys.Name())
- 				continue
-@@ -272,22 +269,26 @@ func (m *Manager) Set(container *configs.Config) error {
- 
- // Freeze toggles the container's freezer cgroup depending on the state
- // provided
--func (m *Manager) Freeze(state configs.FreezerState) error {
--	if m.Cgroups == nil {
-+func (m *Manager) Freeze(state configs.FreezerState) (Err error) {
-+	path := m.GetPaths()["freezer"]
-+	if m.Cgroups == nil || path == "" {
- 		return errors.New("cannot toggle freezer: cgroups not configured for container")
- 	}
- 
--	paths := m.GetPaths()
--	dir := paths["freezer"]
- 	prevState := m.Cgroups.Resources.Freezer
- 	m.Cgroups.Resources.Freezer = state
-+	defer func() {
-+		if Err != nil {
-+			m.Cgroups.Resources.Freezer = prevState
-+		}
-+	}()
-+
- 	freezer, err := m.getSubsystems().Get("freezer")
- 	if err != nil {
- 		return err
- 	}
--	err = freezer.Set(dir, m.Cgroups)
-+	err = freezer.Set(path, m.Cgroups)
- 	if err != nil {
--		m.Cgroups.Resources.Freezer = prevState
- 		return err
- 	}
- 	return nil
-@@ -409,3 +410,15 @@ func CheckCpushares(path string, c uint64) error {
- func (m *Manager) GetCgroups() (*configs.Cgroup, error) {
- 	return m.Cgroups, nil
- }
-+
-+func (m *Manager) GetFreezerState() (configs.FreezerState, error) {
-+	paths := m.GetPaths()
-+	dir := paths["freezer"]
-+	freezer, err := m.getSubsystems().Get("freezer")
-+
-+	// If the container doesn't have the freezer cgroup, say it's undefined.
-+	if err != nil || dir == "" {
-+		return configs.Undefined, nil
-+	}
-+	return freezer.(*FreezerGroup).GetState(dir)
-+}
-diff --git a/libcontainer/cgroups/fs/devices.go b/libcontainer/cgroups/fs/devices.go
-index 036c8db773e5..4fc3951de913 100644
---- a/libcontainer/cgroups/fs/devices.go
-+++ b/libcontainer/cgroups/fs/devices.go
-@@ -3,13 +3,19 @@
- package fs
- 
- import (
-+	"bytes"
-+	"fmt"
-+	"reflect"
-+
- 	"github.com/opencontainers/runc/libcontainer/cgroups"
-+	"github.com/opencontainers/runc/libcontainer/cgroups/devices"
- 	"github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
- 	"github.com/opencontainers/runc/libcontainer/configs"
- 	"github.com/opencontainers/runc/libcontainer/system"
- )
- 
- type DevicesGroup struct {
-+	testingSkipFinalCheck bool
- }
- 
- func (s *DevicesGroup) Name() string {
-@@ -26,49 +32,74 @@ func (s *DevicesGroup) Apply(d *cgroupData) error {
- 	return nil
- }
- 
-+func loadEmulator(path string) (*devices.Emulator, error) {
-+	list, err := fscommon.ReadFile(path, "devices.list")
-+	if err != nil {
-+		return nil, err
-+	}
-+	return devices.EmulatorFromList(bytes.NewBufferString(list))
-+}
-+
-+func buildEmulator(rules []*configs.DeviceRule) (*devices.Emulator, error) {
-+	// This defaults to a white-list -- which is what we want!
-+	emu := &devices.Emulator{}
-+	for _, rule := range rules {
-+		if err := emu.Apply(*rule); err != nil {
-+			return nil, err
-+		}
-+	}
-+	return emu, nil
-+}
-+
- func (s *DevicesGroup) Set(path string, cgroup *configs.Cgroup) error {
- 	if system.RunningInUserNS() {
- 		return nil
- 	}
- 
--	devices := cgroup.Resources.Devices
--	if len(devices) > 0 {
--		for _, dev := range devices {
--			file := "devices.deny"
--			if dev.Allow {
--				file = "devices.allow"
--			}
--			if err := fscommon.WriteFile(path, file, dev.CgroupString()); err != nil {
--				return err
--			}
--		}
--		return nil
-+	// Generate two emulators, one for the current state of the cgroup and one
-+	// for the requested state by the user.
-+	current, err := loadEmulator(path)
-+	if err != nil {
-+		return err
-+	}
-+	target, err := buildEmulator(cgroup.Resources.Devices)
-+	if err != nil {
-+		return err
- 	}
--	if cgroup.Resources.AllowAllDevices != nil {
--		if *cgroup.Resources.AllowAllDevices == false {
--			if err := fscommon.WriteFile(path, "devices.deny", "a"); err != nil {
--				return err
--			}
- 
--			for _, dev := range cgroup.Resources.AllowedDevices {
--				if err := fscommon.WriteFile(path, "devices.allow", dev.CgroupString()); err != nil {
--					return err
--				}
--			}
--			return nil
-+	// Compute the minimal set of transition rules needed to achieve the
-+	// requested state.
-+	transitionRules, err := current.Transition(target)
-+	if err != nil {
-+		return err
-+	}
-+	for _, rule := range transitionRules {
-+		file := "devices.deny"
-+		if rule.Allow {
-+			file = "devices.allow"
- 		}
--
--		if err := fscommon.WriteFile(path, "devices.allow", "a"); err != nil {
-+		if err := fscommon.WriteFile(path, file, rule.CgroupString()); err != nil {
- 			return err
- 		}
- 	}
- 
--	for _, dev := range cgroup.Resources.DeniedDevices {
--		if err := fscommon.WriteFile(path, "devices.deny", dev.CgroupString()); err != nil {
-+	// Final safety check -- ensure that the resulting state is what was
-+	// requested. This is only really correct for white-lists, but for
-+	// black-lists we can at least check that the cgroup is in the right mode.
-+	//
-+	// This safety-check is skipped for the unit tests because we cannot
-+	// currently mock devices.list correctly.
-+	if !s.testingSkipFinalCheck {
-+		currentAfter, err := loadEmulator(path)
-+		if err != nil {
- 			return err
- 		}
-+		if !target.IsBlacklist() && !reflect.DeepEqual(currentAfter, target) {
-+			return fmt.Errorf("resulting devices cgroup doesn't precisely match target")
-+		} else if target.IsBlacklist() != currentAfter.IsBlacklist() {
-+			return fmt.Errorf("resulting devices cgroup doesn't match target mode")
-+		}
- 	}
--
- 	return nil
- }
- 
-diff --git a/libcontainer/cgroups/fs/devices_test.go b/libcontainer/cgroups/fs/devices_test.go
-index 648f4a2ff47b..da7ccb01ecc1 100644
---- a/libcontainer/cgroups/fs/devices_test.go
-+++ b/libcontainer/cgroups/fs/devices_test.go
-@@ -9,91 +9,44 @@ import (
- 	"github.com/opencontainers/runc/libcontainer/configs"
- )
- 
--var (
--	allowedDevices = []*configs.Device{
--		{
--			Path:        "/dev/zero",
--			Type:        'c',
--			Major:       1,
--			Minor:       5,
--			Permissions: "rwm",
--			FileMode:    0666,
--		},
--	}
--	allowedList   = "c 1:5 rwm"
--	deniedDevices = []*configs.Device{
--		{
--			Path:        "/dev/null",
--			Type:        'c',
--			Major:       1,
--			Minor:       3,
--			Permissions: "rwm",
--			FileMode:    0666,
--		},
--	}
--	deniedList = "c 1:3 rwm"
--)
--
- func TestDevicesSetAllow(t *testing.T) {
- 	helper := NewCgroupTestUtil("devices", t)
- 	defer helper.cleanup()
- 
- 	helper.writeFileContents(map[string]string{
--		"devices.deny": "a",
-+		"devices.allow": "",
-+		"devices.deny":  "",
-+		"devices.list":  "a *:* rwm",
- 	})
--	allowAllDevices := false
--	helper.CgroupData.config.Resources.AllowAllDevices = &allowAllDevices
--	helper.CgroupData.config.Resources.AllowedDevices = allowedDevices
--	devices := &DevicesGroup{}
--	if err := devices.Set(helper.CgroupPath, helper.CgroupData.config); err != nil {
--		t.Fatal(err)
--	}
--
--	value, err := fscommon.GetCgroupParamString(helper.CgroupPath, "devices.allow")
--	if err != nil {
--		t.Fatalf("Failed to parse devices.allow - %s", err)
--	}
--
--	if value != allowedList {
--		t.Fatal("Got the wrong value, set devices.allow failed.")
--	}
- 
--	// When AllowAllDevices is nil, devices.allow file should not be modified.
--	helper.CgroupData.config.Resources.AllowAllDevices = nil
--	if err := devices.Set(helper.CgroupPath, helper.CgroupData.config); err != nil {
--		t.Fatal(err)
--	}
--	value, err = fscommon.GetCgroupParamString(helper.CgroupPath, "devices.allow")
--	if err != nil {
--		t.Fatalf("Failed to parse devices.allow - %s", err)
--	}
--	if value != allowedList {
--		t.Fatal("devices policy shouldn't have changed on AllowedAllDevices=nil.")
-+	helper.CgroupData.config.Resources.Devices = []*configs.DeviceRule{
-+		{
-+			Type:        configs.CharDevice,
-+			Major:       1,
-+			Minor:       5,
-+			Permissions: configs.DevicePermissions("rwm"),
-+			Allow:       true,
-+		},
- 	}
--}
--
--func TestDevicesSetDeny(t *testing.T) {
--	helper := NewCgroupTestUtil("devices", t)
--	defer helper.cleanup()
- 
--	helper.writeFileContents(map[string]string{
--		"devices.allow": "a",
--	})
--
--	allowAllDevices := true
--	helper.CgroupData.config.Resources.AllowAllDevices = &allowAllDevices
--	helper.CgroupData.config.Resources.DeniedDevices = deniedDevices
--	devices := &DevicesGroup{}
-+	devices := &DevicesGroup{testingSkipFinalCheck: true}
- 	if err := devices.Set(helper.CgroupPath, helper.CgroupData.config); err != nil {
- 		t.Fatal(err)
- 	}
- 
-+	// The default deny rule must be written.
- 	value, err := fscommon.GetCgroupParamString(helper.CgroupPath, "devices.deny")
- 	if err != nil {
--		t.Fatalf("Failed to parse devices.deny - %s", err)
-+		t.Fatalf("Failed to parse devices.deny: %s", err)
-+	}
-+	if value[0] != 'a' {
-+		t.Errorf("Got the wrong value (%q), set devices.deny failed.", value)
- 	}
- 
--	if value != deniedList {
--		t.Fatal("Got the wrong value, set devices.deny failed.")
-+	// Permitted rule must be written.
-+	if value, err := fscommon.GetCgroupParamString(helper.CgroupPath, "devices.allow"); err != nil {
-+		t.Fatalf("Failed to parse devices.allow: %s", err)
-+	} else if value != "c 1:5 rwm" {
-+		t.Errorf("Got the wrong value (%q), set devices.allow failed.", value)
- 	}
- }
-diff --git a/libcontainer/cgroups/fs/freezer.go b/libcontainer/cgroups/fs/freezer.go
-index 9dc81bdb84c3..a6c622958c8c 100644
---- a/libcontainer/cgroups/fs/freezer.go
-+++ b/libcontainer/cgroups/fs/freezer.go
-@@ -4,12 +4,15 @@ package fs
- 
- import (
- 	"fmt"
-+	"os"
- 	"strings"
- 	"time"
- 
- 	"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"
- )
- 
- type FreezerGroup struct {
-@@ -39,11 +42,11 @@ func (s *FreezerGroup) Set(path string, cgroup *configs.Cgroup) error {
- 				return err
- 			}
- 
--			state, err := fscommon.ReadFile(path, "freezer.state")
-+			state, err := s.GetState(path)
- 			if err != nil {
- 				return err
- 			}
--			if strings.TrimSpace(state) == string(cgroup.Resources.Freezer) {
-+			if state == cgroup.Resources.Freezer {
- 				break
- 			}
- 
-@@ -65,3 +68,30 @@ func (s *FreezerGroup) Remove(d *cgroupData) error {
- func (s *FreezerGroup) GetStats(path string, stats *cgroups.Stats) error {
- 	return nil
- }
-+
-+func (s *FreezerGroup) GetState(path string) (configs.FreezerState, error) {
-+	for {
-+		state, err := fscommon.ReadFile(path, "freezer.state")
-+		if err != nil {
-+			// If the kernel is too old, then we just treat the freezer as
-+			// being in an "undefined" state.
-+			if os.IsNotExist(err) || errors.Cause(err) == unix.ENODEV {
-+				err = nil
-+			}
-+			return configs.Undefined, err
-+		}
-+		switch strings.TrimSpace(state) {
-+		case "THAWED":
-+			return configs.Thawed, nil
-+		case "FROZEN":
-+			return configs.Frozen, nil
-+		case "FREEZING":
-+			// Make sure we get a stable freezer state, so retry if the cgroup
-+			// is still undergoing freezing. This should be a temporary delay.
-+			time.Sleep(1 * time.Millisecond)
-+			continue
-+		default:
-+			return configs.Undefined, fmt.Errorf("unknown freezer.state %q", state)
-+		}
-+	}
-+}
-diff --git a/libcontainer/cgroups/fs2/devices.go b/libcontainer/cgroups/fs2/devices.go
-index e0fd685402a2..ee180bdaf50b 100644
---- a/libcontainer/cgroups/fs2/devices.go
-+++ b/libcontainer/cgroups/fs2/devices.go
-@@ -10,12 +10,10 @@ import (
- 	"golang.org/x/sys/unix"
- )
- 
--func isRWM(cgroupPermissions string) bool {
--	r := false
--	w := false
--	m := false
--	for _, rn := range cgroupPermissions {
--		switch rn {
-+func isRWM(perms configs.DevicePermissions) bool {
-+	var r, w, m bool
-+	for _, perm := range perms {
-+		switch perm {
- 		case 'r':
- 			r = true
- 		case 'w':
-@@ -39,22 +37,10 @@ func canSkipEBPFError(cgroup *configs.Cgroup) bool {
- }
- 
- func setDevices(dirPath string, cgroup *configs.Cgroup) error {
-+	// 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
--	if allowAllDevices := cgroup.Resources.AllowAllDevices; allowAllDevices != nil {
--		// never set by OCI specconv, but *allowAllDevices=false is still used by the integration test
--		if *allowAllDevices == true {
--			return errors.New("libcontainer AllowAllDevices is not supported, use Devices")
--		}
--		for _, ad := range cgroup.Resources.AllowedDevices {
--			d := *ad
--			d.Allow = true
--			devices = append(devices, &d)
--		}
--	}
--	if len(cgroup.Resources.DeniedDevices) != 0 {
--		// never set by OCI specconv
--		return errors.New("libcontainer DeniedDevices is not supported, use Devices")
--	}
- 	insts, license, err := devicefilter.DeviceFilter(devices)
- 	if err != nil {
- 		return err
-@@ -64,6 +50,17 @@ func setDevices(dirPath string, cgroup *configs.Cgroup) error {
- 		return errors.Errorf("cannot get dir FD for %s", dirPath)
- 	}
- 	defer unix.Close(dirFD)
-+	// XXX: This code is currently incorrect when it comes to updating an
-+	//      existing cgroup with new rules (new rulesets are just appended to
-+	//      the program list because this uses BPF_F_ALLOW_MULTI). If we didn't
-+	//      use BPF_F_ALLOW_MULTI we could actually atomically swap the
-+	//      programs.
-+	//
-+	//      The real issue is that BPF_F_ALLOW_MULTI makes it hard to have a
-+	//      race-free blacklist because it acts as a whitelist by default, and
-+	//      having a deny-everything program cannot be overriden by other
-+	//      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) {
- 			return err
-diff --git a/libcontainer/cgroups/fs2/freezer.go b/libcontainer/cgroups/fs2/freezer.go
-index 130c63f3e2bf..3dd4a527bf0f 100644
---- a/libcontainer/cgroups/fs2/freezer.go
-+++ b/libcontainer/cgroups/fs2/freezer.go
-@@ -3,32 +3,48 @@
- package fs2
- 
- import (
--	"strconv"
-+	"os"
- 	"strings"
- 
- 	"github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
- 	"github.com/opencontainers/runc/libcontainer/configs"
- 	"github.com/pkg/errors"
-+	"golang.org/x/sys/unix"
- )
- 
- func setFreezer(dirPath string, state configs.FreezerState) error {
--	var desired int
-+	if err := supportsFreezer(dirPath); err != nil {
-+		// We can ignore this request as long as the user didn't ask us to
-+		// freeze the container (since without the freezer cgroup, that's a
-+		// no-op).
-+		if state == configs.Undefined || state == configs.Thawed {
-+			err = nil
-+		}
-+		return errors.Wrap(err, "freezer not supported")
-+	}
-+
-+	var stateStr string
- 	switch state {
- 	case configs.Undefined:
- 		return nil
- 	case configs.Frozen:
--		desired = 1
-+		stateStr = "1"
- 	case configs.Thawed:
--		desired = 0
-+		stateStr = "0"
- 	default:
--		return errors.Errorf("unknown freezer state %+v", state)
-+		return errors.Errorf("invalid freezer state %q requested", state)
-+	}
-+
-+	if err := fscommon.WriteFile(dirPath, "cgroup.freeze", stateStr); err != nil {
-+		return err
- 	}
--	supportedErr := supportsFreezer(dirPath)
--	if supportedErr != nil && desired != 0 {
--		// can ignore error if desired == 1
--		return errors.Wrap(supportedErr, "freezer not supported")
-+	// Confirm that the cgroup did actually change states.
-+	if actualState, err := getFreezer(dirPath); err != nil {
-+		return err
-+	} else if actualState != state {
-+		return errors.Errorf(`expected "cgroup.freeze" to be in state %q but was in %q`, state, actualState)
- 	}
--	return freezeWithInt(dirPath, desired)
-+	return nil
- }
- 
- func supportsFreezer(dirPath string) error {
-@@ -36,18 +52,22 @@ func supportsFreezer(dirPath string) error {
- 	return err
- }
- 
--// freeze writes desired int to "cgroup.freeze".
--func freezeWithInt(dirPath string, desired int) error {
--	desiredS := strconv.Itoa(desired)
--	if err := fscommon.WriteFile(dirPath, "cgroup.freeze", desiredS); err != nil {
--		return err
--	}
--	got, err := fscommon.ReadFile(dirPath, "cgroup.freeze")
-+func getFreezer(dirPath string) (configs.FreezerState, error) {
-+	state, err := fscommon.ReadFile(dirPath, "cgroup.freeze")
- 	if err != nil {
--		return err
-+		// If the kernel is too old, then we just treat the freezer as being in
-+		// an "undefined" state.
-+		if os.IsNotExist(err) || errors.Cause(err) == unix.ENODEV {
-+			err = nil
-+		}
-+		return configs.Undefined, err
- 	}
--	if gotS := strings.TrimSpace(string(got)); gotS != desiredS {
--		return errors.Errorf("expected \"cgroup.freeze\" in %q to be %q, got %q", dirPath, desiredS, gotS)
-+	switch strings.TrimSpace(state) {
-+	case "0":
-+		return configs.Thawed, nil
-+	case "1":
-+		return configs.Frozen, nil
-+	default:
-+		return configs.Undefined, errors.Errorf(`unknown "cgroup.freeze" state: %q`, state)
- 	}
--	return nil
- }
-diff --git a/libcontainer/cgroups/fs2/fs2.go b/libcontainer/cgroups/fs2/fs2.go
-index 4bb7091a1832..82d1d08430c7 100644
---- a/libcontainer/cgroups/fs2/fs2.go
-+++ b/libcontainer/cgroups/fs2/fs2.go
-@@ -212,3 +212,7 @@ func (m *manager) Set(container *configs.Config) error {
- func (m *manager) GetCgroups() (*configs.Cgroup, error) {
- 	return m.config, nil
- }
-+
-+func (m *manager) GetFreezerState() (configs.FreezerState, error) {
-+	return getFreezer(m.dirPath)
-+}
-diff --git a/libcontainer/cgroups/systemd/apply_systemd.go b/libcontainer/cgroups/systemd/apply_systemd.go
-index c4b19b3e6d4f..e2f94d1b1254 100644
---- a/libcontainer/cgroups/systemd/apply_systemd.go
-+++ b/libcontainer/cgroups/systemd/apply_systemd.go
-@@ -3,7 +3,8 @@
- package systemd
- 
- import (
--	"errors"
-+	"bufio"
-+	stdErrors "errors"
- 	"fmt"
- 	"io/ioutil"
- 	"math"
-@@ -16,8 +17,10 @@ import (
- 	systemdDbus "github.com/coreos/go-systemd/dbus"
- 	"github.com/godbus/dbus"
- 	"github.com/opencontainers/runc/libcontainer/cgroups"
-+	"github.com/opencontainers/runc/libcontainer/cgroups/devices"
- 	"github.com/opencontainers/runc/libcontainer/cgroups/fs"
- 	"github.com/opencontainers/runc/libcontainer/configs"
-+	"github.com/pkg/errors"
- 	"github.com/sirupsen/logrus"
- )
- 
-@@ -36,7 +39,7 @@ type subsystem interface {
- 	Set(path string, cgroup *configs.Cgroup) error
- }
- 
--var errSubsystemDoesNotExist = errors.New("cgroup: subsystem does not exist")
-+var errSubsystemDoesNotExist = stdErrors.New("cgroup: subsystem does not exist")
- 
- type subsystemSet []subsystem
- 
-@@ -70,6 +73,209 @@ const (
- 	testSliceWait = 4
- )
- 
-+func groupPrefix(ruleType configs.DeviceType) (string, error) {
-+	switch ruleType {
-+	case configs.BlockDevice:
-+		return "block-", nil
-+	case configs.CharDevice:
-+		return "char-", nil
-+	default:
-+		return "", errors.Errorf("device type %v has no group prefix", ruleType)
-+	}
-+}
-+
-+// findDeviceGroup tries to find the device group name (as listed in
-+// /proc/devices) with the type prefixed as requried for DeviceAllow, for a
-+// given (type, major) combination. If more than one device group exists, an
-+// arbitrary one is chosen.
-+func findDeviceGroup(ruleType configs.DeviceType, ruleMajor int64) (string, error) {
-+	fh, err := os.Open("/proc/devices")
-+	if err != nil {
-+		return "", err
-+	}
-+	defer fh.Close()
-+
-+	prefix, err := groupPrefix(ruleType)
-+	if err != nil {
-+		return "", err
-+	}
-+
-+	scanner := bufio.NewScanner(fh)
-+	var currentType configs.DeviceType
-+	for scanner.Scan() {
-+		// We need to strip spaces because the first number is column-aligned.
-+		line := strings.TrimSpace(scanner.Text())
-+
-+		// Handle the "header" lines.
-+		switch line {
-+		case "Block devices:":
-+			currentType = configs.BlockDevice
-+			continue
-+		case "Character devices:":
-+			currentType = configs.CharDevice
-+			continue
-+		case "":
-+			continue
-+		}
-+
-+		// Skip lines unrelated to our type.
-+		if currentType != ruleType {
-+			continue
-+		}
-+
-+		// Parse out the (major, name).
-+		var (
-+			currMajor int64
-+			currName  string
-+		)
-+		if n, err := fmt.Sscanf(line, "%d %s", &currMajor, &currName); err != nil || n != 2 {
-+			if err == nil {
-+				err = errors.Errorf("wrong number of fields")
-+			}
-+			return "", errors.Wrapf(err, "scan /proc/devices line %q", line)
-+		}
-+
-+		if currMajor == ruleMajor {
-+			return prefix + currName, nil
-+		}
-+	}
-+	if err := scanner.Err(); err != nil {
-+		return "", errors.Wrap(err, "reading /proc/devices")
-+	}
-+	// Couldn't find the device group.
-+	return "", nil
-+}
-+
-+// generateDeviceProperties takes the configured device rules and generates a
-+// corresponding set of systemd properties to configure the devices correctly.
-+func generateDeviceProperties(rules []*configs.DeviceRule) ([]systemdDbus.Property, error) {
-+	// DeviceAllow is the type "a(ss)" which means we need a temporary struct
-+	// to represent it in Go.
-+	type deviceAllowEntry struct {
-+		Path  string
-+		Perms string
-+	}
-+
-+	properties := []systemdDbus.Property{
-+		// Always run in the strictest white-list mode.
-+		newProp("DevicePolicy", "strict"),
-+		// Empty the DevicesAllow array before filling it.
-+		newProp("DeviceAllow", []deviceAllowEntry{}),
-+	}
-+
-+	// Figure out the set of rules.
-+	configEmu := &devices.Emulator{}
-+	for _, rule := range rules {
-+		if err := configEmu.Apply(*rule); err != nil {
-+			return nil, errors.Wrap(err, "apply rule for systemd")
-+		}
-+	}
-+	// systemd doesn't support blacklists. So we log a warning, and tell
-+	// systemd to act as a deny-all whitelist. This ruleset will be replaced
-+	// with our normal fallback code. This may result in spurrious errors, but
-+	// the only other option is to error out here.
-+	if configEmu.IsBlacklist() {
-+		// However, if we're dealing with an allow-all rule then we can do it.
-+		if configEmu.IsAllowAll() {
-+			return []systemdDbus.Property{
-+				// Run in white-list mode by setting to "auto" and removing all
-+				// DeviceAllow rules.
-+				newProp("DevicePolicy", "auto"),
-+				newProp("DeviceAllow", []deviceAllowEntry{}),
-+			}, nil
-+		}
-+		logrus.Warn("systemd doesn't support blacklist device rules -- applying temporary deny-all rule")
-+		return properties, nil
-+	}
-+
-+	// Now generate the set of rules we actually need to apply. Unlike the
-+	// normal devices cgroup, in "strict" mode systemd defaults to a deny-all
-+	// whitelist which is the default for devices.Emulator.
-+	baseEmu := &devices.Emulator{}
-+	finalRules, err := baseEmu.Transition(configEmu)
-+	if err != nil {
-+		return nil, errors.Wrap(err, "get simplified rules for systemd")
-+	}
-+	var deviceAllowList []deviceAllowEntry
-+	for _, rule := range finalRules {
-+		if !rule.Allow {
-+			// Should never happen.
-+			return nil, errors.Errorf("[internal error] cannot add deny rule to systemd DeviceAllow list: %v", *rule)
-+		}
-+		switch rule.Type {
-+		case configs.BlockDevice, configs.CharDevice:
-+		default:
-+			// Should never happen.
-+			return nil, errors.Errorf("invalid device type for DeviceAllow: %v", rule.Type)
-+		}
-+
-+		entry := deviceAllowEntry{
-+			Perms: string(rule.Permissions),
-+		}
-+
-+		// systemd has a fairly odd (though understandable) syntax here, and
-+		// because of the OCI configuration format we have to do quite a bit of
-+		// trickery to convert things:
-+		//
-+		//  * Concrete rules with non-wildcard major/minor numbers have to use
-+		//    /dev/{block,char} paths. This is slightly odd because it means
-+		//    that we cannot add whitelist rules for devices that don't exist,
-+		//    but there's not too much we can do about that.
-+		//
-+		//    However, path globbing is not support for path-based rules so we
-+		//    need to handle wildcards in some other manner.
-+		//
-+		//  * Wildcard-minor rules have to specify a "device group name" (the
-+		//    second column in /proc/devices).
-+		//
-+		//  * Wildcard (major and minor) rules can just specify a glob with the
-+		//    type ("char-*" or "block-*").
-+		//
-+		// The only type of rule we can't handle is wildcard-major rules, and
-+		// so we'll give a warning in that case (note that the fallback code
-+		// will insert any rules systemd couldn't handle). What amazing fun.
-+
-+		if rule.Major == configs.Wildcard {
-+			// "_ *:n _" rules aren't supported by systemd.
-+			if rule.Minor != configs.Wildcard {
-+				logrus.Warnf("systemd doesn't support '*:n' device rules -- temporarily ignoring rule: %v", *rule)
-+				continue
-+			}
-+
-+			// "_ *:* _" rules just wildcard everything.
-+			prefix, err := groupPrefix(rule.Type)
-+			if err != nil {
-+				return nil, err
-+			}
-+			entry.Path = prefix + "*"
-+		} else if rule.Minor == configs.Wildcard {
-+			// "_ n:* _" rules require a device group from /proc/devices.
-+			group, err := findDeviceGroup(rule.Type, rule.Major)
-+			if err != nil {
-+				return nil, errors.Wrapf(err, "find device '%v/%d'", rule.Type, rule.Major)
-+			}
-+			if group == "" {
-+				// Couldn't find a group.
-+				logrus.Warnf("could not find device group for '%v/%d' in /proc/devices -- temporarily ignoring rule: %v", rule.Type, rule.Major, *rule)
-+				continue
-+			}
-+			entry.Path = group
-+		} else {
-+			// "_ n:m _" rules are just a path in /dev/{block,char}/.
-+			switch rule.Type {
-+			case configs.BlockDevice:
-+				entry.Path = fmt.Sprintf("/dev/block/%d:%d", rule.Major, rule.Minor)
-+			case configs.CharDevice:
-+				entry.Path = fmt.Sprintf("/dev/char/%d:%d", rule.Major, rule.Minor)
-+			}
-+		}
-+		deviceAllowList = append(deviceAllowList, entry)
-+	}
-+
-+	properties = append(properties, newProp("DeviceAllow", deviceAllowList))
-+	return properties, nil
-+}
-+
- var (
- 	connLock sync.Mutex
- 	theConn  *systemdDbus.Conn
-@@ -196,6 +402,12 @@ func (m *LegacyManager) Apply(pid int) error {
- 	properties = append(properties,
- 		newProp("DefaultDependencies", false))
- 
-+	deviceProperties, err := generateDeviceProperties(c.Resources.Devices)
-+	if err != nil {
-+		return err
-+	}
-+	properties = append(properties, deviceProperties...)
-+
- 	if c.Resources.Memory != 0 {
- 		properties = append(properties,
- 			newProp("MemoryLimit", uint64(c.Resources.Memory)))
-@@ -476,7 +688,6 @@ func (m *LegacyManager) Set(container *configs.Config) error {
- 		if err != nil && !cgroups.IsNotFound(err) {
- 			return err
- 		}
--
- 		if err := sys.Set(path, container.Cgroups); err != nil {
- 			return err
- 		}
-@@ -532,3 +743,15 @@ func isUnitExists(err error) bool {
- func (m *LegacyManager) GetCgroups() (*configs.Cgroup, error) {
- 	return m.Cgroups, nil
- }
-+
-+func (m *LegacyManager) GetFreezerState() (configs.FreezerState, error) {
-+	path, err := getSubsystemPath(m.Cgroups, "freezer")
-+	if err != nil && !cgroups.IsNotFound(err) {
-+		return configs.Undefined, err
-+	}
-+	freezer, err := legacySubsystems.Get("freezer")
-+	if err != nil {
-+		return configs.Undefined, err
-+	}
-+	return freezer.(*fs.FreezerGroup).GetState(path)
-+}
-diff --git a/libcontainer/cgroups/systemd/unified_hierarchy.go b/libcontainer/cgroups/systemd/unified_hierarchy.go
-index 6605099190ca..2bfcccd130ac 100644
---- a/libcontainer/cgroups/systemd/unified_hierarchy.go
-+++ b/libcontainer/cgroups/systemd/unified_hierarchy.go
-@@ -87,6 +87,12 @@ func (m *UnifiedManager) Apply(pid int) error {
- 	properties = append(properties,
- 		newProp("DefaultDependencies", false))
- 
-+	deviceProperties, err := generateDeviceProperties(c.Resources.Devices)
-+	if err != nil {
-+		return err
-+	}
-+	properties = append(properties, deviceProperties...)
-+
- 	if c.Resources.Memory != 0 {
- 		properties = append(properties,
- 			newProp("MemoryLimit", uint64(c.Resources.Memory)))
-@@ -310,3 +316,11 @@ func (m *UnifiedManager) Set(container *configs.Config) error {
- func (m *UnifiedManager) GetCgroups() (*configs.Cgroup, error) {
- 	return m.Cgroups, nil
- }
-+
-+func (m *UnifiedManager) GetFreezerState() (configs.FreezerState, error) {
-+	fsMgr, err := m.fsManager()
-+	if err != nil {
-+		return configs.Undefined, err
-+	}
-+	return fsMgr.GetFreezerState()
-+}
-diff --git a/libcontainer/configs/cgroup_linux.go b/libcontainer/configs/cgroup_linux.go
-index 58ed19c9e78e..e83bb72dc334 100644
---- a/libcontainer/configs/cgroup_linux.go
-+++ b/libcontainer/configs/cgroup_linux.go
-@@ -32,15 +32,8 @@ type Cgroup struct {
- }
- 
- type Resources struct {
--	// If this is true allow access to any kind of device within the container.  If false, allow access only to devices explicitly listed in the allowed_devices list.
--	// Deprecated
--	AllowAllDevices *bool `json:"allow_all_devices,omitempty"`
--	// Deprecated
--	AllowedDevices []*Device `json:"allowed_devices,omitempty"`
--	// Deprecated
--	DeniedDevices []*Device `json:"denied_devices,omitempty"`
--
--	Devices []*Device `json:"devices"`
-+	// Devices is the set of access rules for devices in the container.
-+	Devices []*DeviceRule `json:"devices"`
- 
- 	// Memory limit (in bytes)
- 	Memory int64 `json:"memory"`
-diff --git a/libcontainer/configs/device.go b/libcontainer/configs/device.go
-index 8701bb212d7b..24c5bbfa6ae8 100644
---- a/libcontainer/configs/device.go
-+++ b/libcontainer/configs/device.go
-@@ -1,8 +1,12 @@
- package configs
- 
- import (
-+	"errors"
- 	"fmt"
- 	"os"
-+	"strconv"
-+
-+	"golang.org/x/sys/unix"
- )
- 
- const (
-@@ -12,21 +16,11 @@ const (
- // TODO Windows: This can be factored out in the future
- 
- type Device struct {
--	// Device type, block, char, etc.
--	Type rune `json:"type"`
-+	DeviceRule
- 
- 	// Path to the device.
- 	Path string `json:"path"`
- 
--	// Major is the device's major number.
--	Major int64 `json:"major"`
--
--	// Minor is the device's minor number.
--	Minor int64 `json:"minor"`
--
--	// Cgroup permissions format, rwm.
--	Permissions string `json:"permissions"`
--
- 	// FileMode permission bits for the device.
- 	FileMode os.FileMode `json:"file_mode"`
- 
-@@ -35,23 +29,154 @@ type Device struct {
- 
- 	// Gid of the device.
- 	Gid uint32 `json:"gid"`
-+}
- 
--	// Write the file to the allowed list
--	Allow bool `json:"allow"`
-+// DevicePermissions is a cgroupv1-style string to represent device access. It
-+// has to be a string for backward compatibility reasons, hence why it has
-+// methods to do set operations.
-+type DevicePermissions string
-+
-+const (
-+	deviceRead uint = (1 << iota)
-+	deviceWrite
-+	deviceMknod
-+)
-+
-+func (p DevicePermissions) toSet() uint {
-+	var set uint
-+	for _, perm := range p {
-+		switch perm {
-+		case 'r':
-+			set |= deviceRead
-+		case 'w':
-+			set |= deviceWrite
-+		case 'm':
-+			set |= deviceMknod
-+		}
-+	}
-+	return set
-+}
-+
-+func fromSet(set uint) DevicePermissions {
-+	var perm string
-+	if set&deviceRead == deviceRead {
-+		perm += "r"
-+	}
-+	if set&deviceWrite == deviceWrite {
-+		perm += "w"
-+	}
-+	if set&deviceMknod == deviceMknod {
-+		perm += "m"
-+	}
-+	return DevicePermissions(perm)
-+}
-+
-+// Union returns the union of the two sets of DevicePermissions.
-+func (p DevicePermissions) Union(o DevicePermissions) DevicePermissions {
-+	lhs := p.toSet()
-+	rhs := o.toSet()
-+	return fromSet(lhs | rhs)
-+}
-+
-+// Difference returns the set difference of the two sets of DevicePermissions.
-+// In set notation, A.Difference(B) gives you A\B.
-+func (p DevicePermissions) Difference(o DevicePermissions) DevicePermissions {
-+	lhs := p.toSet()
-+	rhs := o.toSet()
-+	return fromSet(lhs &^ rhs)
-+}
-+
-+// Intersection computes the intersection of the two sets of DevicePermissions.
-+func (p DevicePermissions) Intersection(o DevicePermissions) DevicePermissions {
-+	lhs := p.toSet()
-+	rhs := o.toSet()
-+	return fromSet(lhs & rhs)
-+}
-+
-+// IsEmpty returns whether the set of permissions in a DevicePermissions is
-+// empty.
-+func (p DevicePermissions) IsEmpty() bool {
-+	return p == DevicePermissions("")
-+}
-+
-+// IsValid returns whether the set of permissions is a subset of valid
-+// permissions (namely, {r,w,m}).
-+func (p DevicePermissions) IsValid() bool {
-+	return p == fromSet(p.toSet())
-+}
-+
-+type DeviceType rune
-+
-+const (
-+	WildcardDevice DeviceType = 'a'
-+	BlockDevice    DeviceType = 'b'
-+	CharDevice     DeviceType = 'c' // or 'u'
-+	FifoDevice     DeviceType = 'p'
-+)
-+
-+func (t DeviceType) IsValid() bool {
-+	switch t {
-+	case WildcardDevice, BlockDevice, CharDevice, FifoDevice:
-+		return true
-+	default:
-+		return false
-+	}
- }
- 
--func (d *Device) CgroupString() string {
--	return fmt.Sprintf("%c %s:%s %s", d.Type, deviceNumberString(d.Major), deviceNumberString(d.Minor), d.Permissions)
-+func (t DeviceType) CanMknod() bool {
-+	switch t {
-+	case BlockDevice, CharDevice, FifoDevice:
-+		return true
-+	default:
-+		return false
-+	}
-+}
-+
-+func (t DeviceType) CanCgroup() bool {
-+	switch t {
-+	case WildcardDevice, BlockDevice, CharDevice:
-+		return true
-+	default:
-+		return false
-+	}
- }
- 
--func (d *Device) Mkdev() int {
--	return int((d.Major << 8) | (d.Minor & 0xff) | ((d.Minor & 0xfff00) << 12))
-+type DeviceRule struct {
-+	// Type of device ('c' for char, 'b' for block). If set to 'a', this rule
-+	// acts as a wildcard and all fields other than Allow are ignored.
-+	Type DeviceType `json:"type"`
-+
-+	// Major is the device's major number.
-+	Major int64 `json:"major"`
-+
-+	// Minor is the device's minor number.
-+	Minor int64 `json:"minor"`
-+
-+	// Permissions is the set of permissions that this rule applies to (in the
-+	// cgroupv1 format -- any combination of "rwm").
-+	Permissions DevicePermissions `json:"permissions"`
-+
-+	// Allow specifies whether this rule is allowed.
-+	Allow bool `json:"allow"`
-+}
-+
-+func (d *DeviceRule) CgroupString() string {
-+	var (
-+		major = strconv.FormatInt(d.Major, 10)
-+		minor = strconv.FormatInt(d.Minor, 10)
-+	)
-+	if d.Major == Wildcard {
-+		major = "*"
-+	}
-+	if d.Minor == Wildcard {
-+		minor = "*"
-+	}
-+	return fmt.Sprintf("%c %s:%s %s", d.Type, major, minor, d.Permissions)
- }
- 
--// deviceNumberString converts the device number to a string return result.
--func deviceNumberString(number int64) string {
--	if number == Wildcard {
--		return "*"
-+func (d *DeviceRule) Mkdev() (uint64, error) {
-+	if d.Major == Wildcard || d.Minor == Wildcard {
-+		return 0, errors.New("cannot mkdev() device with wildcards")
- 	}
--	return fmt.Sprint(number)
-+	return unix.Mkdev(uint32(d.Major), uint32(d.Minor)), nil
- }
-diff --git a/libcontainer/configs/device_defaults.go b/libcontainer/configs/device_defaults.go
-deleted file mode 100644
-index e4f423c523ff..000000000000
---- a/libcontainer/configs/device_defaults.go
-+++ /dev/null
-@@ -1,111 +0,0 @@
--// +build linux
--
--package configs
--
--var (
--	// DefaultSimpleDevices are devices that are to be both allowed and created.
--	DefaultSimpleDevices = []*Device{
--		// /dev/null and zero
--		{
--			Path:        "/dev/null",
--			Type:        'c',
--			Major:       1,
--			Minor:       3,
--			Permissions: "rwm",
--			FileMode:    0666,
--		},
--		{
--			Path:        "/dev/zero",
--			Type:        'c',
--			Major:       1,
--			Minor:       5,
--			Permissions: "rwm",
--			FileMode:    0666,
--		},
--
--		{
--			Path:        "/dev/full",
--			Type:        'c',
--			Major:       1,
--			Minor:       7,
--			Permissions: "rwm",
--			FileMode:    0666,
--		},
--
--		// consoles and ttys
--		{
--			Path:        "/dev/tty",
--			Type:        'c',
--			Major:       5,
--			Minor:       0,
--			Permissions: "rwm",
--			FileMode:    0666,
--		},
--
--		// /dev/urandom,/dev/random
--		{
--			Path:        "/dev/urandom",
--			Type:        'c',
--			Major:       1,
--			Minor:       9,
--			Permissions: "rwm",
--			FileMode:    0666,
--		},
--		{
--			Path:        "/dev/random",
--			Type:        'c',
--			Major:       1,
--			Minor:       8,
--			Permissions: "rwm",
--			FileMode:    0666,
--		},
--	}
--	DefaultAllowedDevices = append([]*Device{
--		// allow mknod for any device
--		{
--			Type:        'c',
--			Major:       Wildcard,
--			Minor:       Wildcard,
--			Permissions: "m",
--		},
--		{
--			Type:        'b',
--			Major:       Wildcard,
--			Minor:       Wildcard,
--			Permissions: "m",
--		},
--
--		{
--			Path:        "/dev/console",
--			Type:        'c',
--			Major:       5,
--			Minor:       1,
--			Permissions: "rwm",
--		},
--		// /dev/pts/ - pts namespaces are "coming soon"
--		{
--			Path:        "",
--			Type:        'c',
--			Major:       136,
--			Minor:       Wildcard,
--			Permissions: "rwm",
--		},
--		{
--			Path:        "",
--			Type:        'c',
--			Major:       5,
--			Minor:       2,
--			Permissions: "rwm",
--		},
--
--		// tuntap
--		{
--			Path:        "",
--			Type:        'c',
--			Major:       10,
--			Minor:       200,
--			Permissions: "rwm",
--		},
--	}, DefaultSimpleDevices...)
--	DefaultAutoCreatedDevices = append([]*Device{}, DefaultSimpleDevices...)
--)
-diff --git a/libcontainer/container_linux.go b/libcontainer/container_linux.go
-index fe70c937aad9..e59f29588164 100644
---- a/libcontainer/container_linux.go
-+++ b/libcontainer/container_linux.go
-@@ -1812,27 +1812,11 @@ func (c *linuxContainer) runType() (Status, error) {
- }
- 
- func (c *linuxContainer) isPaused() (bool, error) {
--	fcg := c.cgroupManager.GetPaths()["freezer"]
--	if fcg == "" {
--		// A container doesn't have a freezer cgroup
--		return false, nil
--	}
--	pausedState := "FROZEN"
--	filename := "freezer.state"
--	if cgroups.IsCgroup2UnifiedMode() {
--		filename = "cgroup.freeze"
--		pausedState = "1"
--	}
--
--	data, err := ioutil.ReadFile(filepath.Join(fcg, filename))
-+	state, err := c.cgroupManager.GetFreezerState()
- 	if err != nil {
--		// If freezer cgroup is not mounted, the container would just be not paused.
--		if os.IsNotExist(err) || err == syscall.ENODEV {
--			return false, nil
--		}
--		return false, newSystemErrorWithCause(err, "checking if container is paused")
-+		return false, err
- 	}
--	return bytes.Equal(bytes.TrimSpace(data), []byte(pausedState)), nil
-+	return state == configs.Frozen, nil
- }
- 
- func (c *linuxContainer) currentState() (*State, error) {
-diff --git a/libcontainer/container_linux_test.go b/libcontainer/container_linux_test.go
-index f8af05d75fb0..2d972212ce02 100644
---- a/libcontainer/container_linux_test.go
-+++ b/libcontainer/container_linux_test.go
-@@ -61,10 +61,15 @@ func (m *mockCgroupManager) GetUnifiedPath() (string, error) {
- func (m *mockCgroupManager) Freeze(state configs.FreezerState) error {
- 	return nil
- }
-+
- func (m *mockCgroupManager) GetCgroups() (*configs.Cgroup, error) {
- 	return nil, nil
- }
- 
-+func (m *mockCgroupManager) GetFreezerState() (configs.FreezerState, error) {
-+	return configs.Thawed, nil
-+}
-+
- func (m *mockIntelRdtManager) Apply(pid int) error {
- 	return nil
- }
-diff --git a/libcontainer/devices/devices.go b/libcontainer/devices/devices.go
-index 5dabe06cef5b..702f913ec942 100644
---- a/libcontainer/devices/devices.go
-+++ b/libcontainer/devices/devices.go
-@@ -31,33 +31,33 @@ func DeviceFromPath(path, permissions string) (*configs.Device, error) {
- 	}
- 
- 	var (
-+		devType   configs.DeviceType
-+		mode      = stat.Mode
- 		devNumber = uint64(stat.Rdev)
- 		major     = unix.Major(devNumber)
- 		minor     = unix.Minor(devNumber)
- 	)
--	if major == 0 {
--		return nil, ErrNotADevice
--	}
--
--	var (
--		devType rune
--		mode    = stat.Mode
--	)
- 	switch {
- 	case mode&unix.S_IFBLK == unix.S_IFBLK:
--		devType = 'b'
-+		devType = configs.BlockDevice
- 	case mode&unix.S_IFCHR == unix.S_IFCHR:
--		devType = 'c'
-+		devType = configs.CharDevice
-+	case mode&unix.S_IFIFO == unix.S_IFIFO:
-+		devType = configs.FifoDevice
-+	default:
-+		return nil, ErrNotADevice
- 	}
- 	return &configs.Device{
--		Type:        devType,
--		Path:        path,
--		Major:       int64(major),
--		Minor:       int64(minor),
--		Permissions: permissions,
--		FileMode:    os.FileMode(mode),
--		Uid:         stat.Uid,
--		Gid:         stat.Gid,
-+		DeviceRule: configs.DeviceRule{
-+			Type:        devType,
-+			Major:       int64(major),
-+			Minor:       int64(minor),
-+			Permissions: configs.DevicePermissions(permissions),
-+		},
-+		Path:     path,
-+		FileMode: os.FileMode(mode),
-+		Uid:      stat.Uid,
-+		Gid:      stat.Gid,
- 	}, nil
- }
- 
-diff --git a/libcontainer/integration/template_test.go b/libcontainer/integration/template_test.go
-index 5f7cab53b67c..30503a98bb99 100644
---- a/libcontainer/integration/template_test.go
-+++ b/libcontainer/integration/template_test.go
-@@ -2,6 +2,7 @@ package integration
- 
- import (
- 	"github.com/opencontainers/runc/libcontainer/configs"
-+	"github.com/opencontainers/runc/libcontainer/specconv"
- 
- 	"golang.org/x/sys/unix"
- )
-@@ -20,7 +21,10 @@ const defaultMountFlags = unix.MS_NOEXEC | unix.MS_NOSUID | unix.MS_NODEV
- // it uses a network strategy of just setting a loopback interface
- // and the default setup for devices
- func newTemplateConfig(rootfs string) *configs.Config {
--	allowAllDevices := false
-+	var allowedDevices []*configs.DeviceRule
-+	for _, device := range specconv.AllowedDevices {
-+		allowedDevices = append(allowedDevices, &device.DeviceRule)
-+	}
- 	return &configs.Config{
- 		Rootfs: rootfs,
- 		Capabilities: &configs.Capabilities{
-@@ -116,8 +120,7 @@ func newTemplateConfig(rootfs string) *configs.Config {
- 			Path: "integration/test",
- 			Resources: &configs.Resources{
- 				MemorySwappiness: nil,
--				AllowAllDevices:  &allowAllDevices,
--				AllowedDevices:   configs.DefaultAllowedDevices,
-+				Devices:          allowedDevices,
- 			},
- 		},
- 		MaskPaths: []string{
-@@ -127,7 +130,7 @@ func newTemplateConfig(rootfs string) *configs.Config {
- 		ReadonlyPaths: []string{
- 			"/proc/sys", "/proc/sysrq-trigger", "/proc/irq", "/proc/bus",
- 		},
--		Devices:  configs.DefaultAutoCreatedDevices,
-+		Devices:  specconv.AllowedDevices,
- 		Hostname: "integration",
- 		Mounts: []*configs.Mount{
- 			{
-diff --git a/libcontainer/rootfs_linux.go b/libcontainer/rootfs_linux.go
-index 106c4c2b98bf..4285eb8acaaa 100644
---- a/libcontainer/rootfs_linux.go
-+++ b/libcontainer/rootfs_linux.go
-@@ -624,11 +624,14 @@ func bindMountDeviceNode(dest string, node *configs.Device) error {
- 
- // Creates the device node in the rootfs of the container.
- func createDeviceNode(rootfs string, node *configs.Device, bind bool) error {
-+	if node.Path == "" {
-+		// The node only exists for cgroup reasons, ignore it here.
-+		return nil
-+	}
- 	dest := filepath.Join(rootfs, node.Path)
- 	if err := os.MkdirAll(filepath.Dir(dest), 0755); err != nil {
- 		return err
- 	}
--
- 	if bind {
- 		return bindMountDeviceNode(dest, node)
- 	}
-@@ -646,16 +649,20 @@ func createDeviceNode(rootfs string, node *configs.Device, bind bool) error {
- func mknodDevice(dest string, node *configs.Device) error {
- 	fileMode := node.FileMode
- 	switch node.Type {
--	case 'c', 'u':
--		fileMode |= unix.S_IFCHR
--	case 'b':
-+	case configs.BlockDevice:
- 		fileMode |= unix.S_IFBLK
--	case 'p':
-+	case configs.CharDevice:
-+		fileMode |= unix.S_IFCHR
-+	case configs.FifoDevice:
- 		fileMode |= unix.S_IFIFO
- 	default:
- 		return fmt.Errorf("%c is not a valid device type for device %s", node.Type, node.Path)
- 	}
--	if err := unix.Mknod(dest, uint32(fileMode), node.Mkdev()); err != nil {
-+	dev, err := node.Mkdev()
-+	if err != nil {
-+		return err
-+	}
-+	if err := unix.Mknod(dest, uint32(fileMode), int(dev)); err != nil {
- 		return err
- 	}
- 	return unix.Chown(dest, int(node.Uid), int(node.Gid))
-diff --git a/libcontainer/specconv/spec_linux.go b/libcontainer/specconv/spec_linux.go
-index d9e73c46be10..69bbc7c0b889 100644
---- a/libcontainer/specconv/spec_linux.go
-+++ b/libcontainer/specconv/spec_linux.go
-@@ -43,104 +43,147 @@ var mountPropagationMapping = map[string]int{
- 	"":            0,
- }
- 
--// AllowedDevices is exposed for devicefilter_test.go
-+// AllowedDevices is the set of devices which are automatically included for
-+// all containers.
-+//
-+// XXX (cyphar)
-+//    This behaviour is at the very least "questionable" (if not outright
-+//    wrong) according to the runtime-spec.
-+//
-+//    Yes, we have to include certain devices other than the ones the user
-+//    specifies, but several devices listed here are not part of the spec
-+//    (including "mknod for any device"?!). In addition, these rules are
-+//    appended to the user-provided set which means that users *cannot disable
-+//    this behaviour*.
-+//
-+//    ... unfortunately I'm too scared to change this now because who knows how
-+//    many people depend on this (incorrect and arguably insecure) behaviour.
- var AllowedDevices = []*configs.Device{
- 	// allow mknod for any device
- 	{
--		Type:        'c',
--		Major:       wildcard,
--		Minor:       wildcard,
--		Permissions: "m",
--		Allow:       true,
--	},
--	{
--		Type:        'b',
--		Major:       wildcard,
--		Minor:       wildcard,
--		Permissions: "m",
--		Allow:       true,
-+		DeviceRule: configs.DeviceRule{
-+			Type:        configs.CharDevice,
-+			Major:       configs.Wildcard,
-+			Minor:       configs.Wildcard,
-+			Permissions: "m",
-+			Allow:       true,
-+		},
- 	},
- 	{
--		Type:        'c',
--		Path:        "/dev/null",
--		Major:       1,
--		Minor:       3,
--		Permissions: "rwm",
--		Allow:       true,
-+		DeviceRule: configs.DeviceRule{
-+			Type:        configs.BlockDevice,
-+			Major:       configs.Wildcard,
-+			Minor:       configs.Wildcard,
-+			Permissions: "m",
-+			Allow:       true,
-+		},
- 	},
- 	{
--		Type:        'c',
--		Path:        "/dev/random",
--		Major:       1,
--		Minor:       8,
--		Permissions: "rwm",
--		Allow:       true,
-+		Path:     "/dev/null",
-+		FileMode: 0666,
-+		Uid:      0,
-+		Gid:      0,
-+		DeviceRule: configs.DeviceRule{
-+			Type:        configs.CharDevice,
-+			Major:       1,
-+			Minor:       3,
-+			Permissions: "rwm",
-+			Allow:       true,
-+		},
- 	},
- 	{
--		Type:        'c',
--		Path:        "/dev/full",
--		Major:       1,
--		Minor:       7,
--		Permissions: "rwm",
--		Allow:       true,
-+		Path:     "/dev/random",
-+		FileMode: 0666,
-+		Uid:      0,
-+		Gid:      0,
-+		DeviceRule: configs.DeviceRule{
-+			Type:        configs.CharDevice,
-+			Major:       1,
-+			Minor:       8,
-+			Permissions: "rwm",
-+			Allow:       true,
-+		},
- 	},
- 	{
--		Type:        'c',
--		Path:        "/dev/tty",
--		Major:       5,
--		Minor:       0,
--		Permissions: "rwm",
--		Allow:       true,
-+		Path:     "/dev/full",
-+		FileMode: 0666,
-+		Uid:      0,
-+		Gid:      0,
-+		DeviceRule: configs.DeviceRule{
-+			Type:        configs.CharDevice,
-+			Major:       1,
-+			Minor:       7,
-+			Permissions: "rwm",
-+			Allow:       true,
-+		},
- 	},
- 	{
--		Type:        'c',
--		Path:        "/dev/zero",
--		Major:       1,
--		Minor:       5,
--		Permissions: "rwm",
--		Allow:       true,
-+		Path:     "/dev/tty",
-+		FileMode: 0666,
-+		Uid:      0,
-+		Gid:      0,
-+		DeviceRule: configs.DeviceRule{
-+			Type:        configs.CharDevice,
-+			Major:       5,
-+			Minor:       0,
-+			Permissions: "rwm",
-+			Allow:       true,
-+		},
- 	},
- 	{
--		Type:        'c',
--		Path:        "/dev/urandom",
--		Major:       1,
--		Minor:       9,
--		Permissions: "rwm",
--		Allow:       true,
-+		Path:     "/dev/zero",
-+		FileMode: 0666,
-+		Uid:      0,
-+		Gid:      0,
-+		DeviceRule: configs.DeviceRule{
-+			Type:        configs.CharDevice,
-+			Major:       1,
-+			Minor:       5,
-+			Permissions: "rwm",
-+			Allow:       true,
-+		},
- 	},
- 	{
--		Path:        "/dev/console",
--		Type:        'c',
--		Major:       5,
--		Minor:       1,
--		Permissions: "rwm",
--		Allow:       true,
-+		Path:     "/dev/urandom",
-+		FileMode: 0666,
-+		Uid:      0,
-+		Gid:      0,
-+		DeviceRule: configs.DeviceRule{
-+			Type:        configs.CharDevice,
-+			Major:       1,
-+			Minor:       9,
-+			Permissions: "rwm",
-+			Allow:       true,
-+		},
- 	},
- 	// /dev/pts/ - pts namespaces are "coming soon"
- 	{
--		Path:        "",
--		Type:        'c',
--		Major:       136,
--		Minor:       wildcard,
--		Permissions: "rwm",
--		Allow:       true,
-+		DeviceRule: configs.DeviceRule{
-+			Type:        configs.CharDevice,
-+			Major:       136,
-+			Minor:       configs.Wildcard,
-+			Permissions: "rwm",
-+			Allow:       true,
-+		},
- 	},
- 	{
--		Path:        "",
--		Type:        'c',
--		Major:       5,
--		Minor:       2,
--		Permissions: "rwm",
--		Allow:       true,
-+		DeviceRule: configs.DeviceRule{
-+			Type:        configs.CharDevice,
-+			Major:       5,
-+			Minor:       2,
-+			Permissions: "rwm",
-+			Allow:       true,
-+		},
- 	},
- 	// tuntap
- 	{
--		Path:        "",
--		Type:        'c',
--		Major:       10,
--		Minor:       200,
--		Permissions: "rwm",
--		Allow:       true,
-+		DeviceRule: configs.DeviceRule{
-+			Type:        configs.CharDevice,
-+			Major:       10,
-+			Minor:       200,
-+			Permissions: "rwm",
-+			Allow:       true,
-+		},
- 	},
- }
- 
-@@ -342,251 +385,198 @@ func CreateCgroupConfig(opts *CreateOpts) (*configs.Cgroup, error) {
- 
- 	// In rootless containers, any attempt to make cgroup changes is likely to fail.
- 	// libcontainer will validate this but ignores the error.
--	c.Resources.AllowedDevices = AllowedDevices
- 	if spec.Linux != nil {
- 		r := spec.Linux.Resources
--		if r == nil {
--			return c, nil
--		}
--		for i, d := range spec.Linux.Resources.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
--			}
--			dd := &configs.Device{
--				Type:        dt,
--				Major:       major,
--				Minor:       minor,
--				Permissions: d.Access,
--				Allow:       d.Allow,
--			}
--			c.Resources.Devices = append(c.Resources.Devices, dd)
--		}
--		if r.Memory != nil {
--			if r.Memory.Limit != nil {
--				c.Resources.Memory = *r.Memory.Limit
--			}
--			if r.Memory.Reservation != nil {
--				c.Resources.MemoryReservation = *r.Memory.Reservation
--			}
--			if r.Memory.Swap != nil {
--				c.Resources.MemorySwap = *r.Memory.Swap
--			}
--			if r.Memory.Kernel != nil {
--				c.Resources.KernelMemory = *r.Memory.Kernel
--			}
--			if r.Memory.KernelTCP != nil {
--				c.Resources.KernelMemoryTCP = *r.Memory.KernelTCP
--			}
--			if r.Memory.Swappiness != nil {
--				c.Resources.MemorySwappiness = r.Memory.Swappiness
--			}
--			if r.Memory.DisableOOMKiller != nil {
--				c.Resources.OomKillDisable = *r.Memory.DisableOOMKiller
--			}
--		}
--		if r.CPU != nil {
--			if r.CPU.Shares != nil {
--				c.Resources.CpuShares = *r.CPU.Shares
--			}
--			if r.CPU.Quota != nil {
--				c.Resources.CpuQuota = *r.CPU.Quota
--			}
--			if r.CPU.Period != nil {
--				c.Resources.CpuPeriod = *r.CPU.Period
--			}
--			if r.CPU.RealtimeRuntime != nil {
--				c.Resources.CpuRtRuntime = *r.CPU.RealtimeRuntime
--			}
--			if r.CPU.RealtimePeriod != nil {
--				c.Resources.CpuRtPeriod = *r.CPU.RealtimePeriod
--			}
--			if r.CPU.Cpus != "" {
--				c.Resources.CpusetCpus = r.CPU.Cpus
-+		if r != nil {
-+			for i, d := range spec.Linux.Resources.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, &configs.DeviceRule{
-+					Type:        dt,
-+					Major:       major,
-+					Minor:       minor,
-+					Permissions: configs.DevicePermissions(d.Access),
-+					Allow:       d.Allow,
-+				})
- 			}
--			if r.CPU.Mems != "" {
--				c.Resources.CpusetMems = r.CPU.Mems
-+			if r.Memory != nil {
-+				if r.Memory.Limit != nil {
-+					c.Resources.Memory = *r.Memory.Limit
-+				}
-+				if r.Memory.Reservation != nil {
-+					c.Resources.MemoryReservation = *r.Memory.Reservation
-+				}
-+				if r.Memory.Swap != nil {
-+					c.Resources.MemorySwap = *r.Memory.Swap
-+				}
-+				if r.Memory.Kernel != nil {
-+					c.Resources.KernelMemory = *r.Memory.Kernel
-+				}
-+				if r.Memory.KernelTCP != nil {
-+					c.Resources.KernelMemoryTCP = *r.Memory.KernelTCP
-+				}
-+				if r.Memory.Swappiness != nil {
-+					c.Resources.MemorySwappiness = r.Memory.Swappiness
-+				}
-+				if r.Memory.DisableOOMKiller != nil {
-+					c.Resources.OomKillDisable = *r.Memory.DisableOOMKiller
-+				}
- 			}
--		}
--		if r.Pids != nil {
--			c.Resources.PidsLimit = r.Pids.Limit
--		}
--		if r.BlockIO != nil {
--			if r.BlockIO.Weight != nil {
--				c.Resources.BlkioWeight = *r.BlockIO.Weight
-+			if r.CPU != nil {
-+				if r.CPU.Shares != nil {
-+					c.Resources.CpuShares = *r.CPU.Shares
-+				}
-+				if r.CPU.Quota != nil {
-+					c.Resources.CpuQuota = *r.CPU.Quota
-+				}
-+				if r.CPU.Period != nil {
-+					c.Resources.CpuPeriod = *r.CPU.Period
-+				}
-+				if r.CPU.RealtimeRuntime != nil {
-+					c.Resources.CpuRtRuntime = *r.CPU.RealtimeRuntime
-+				}
-+				if r.CPU.RealtimePeriod != nil {
-+					c.Resources.CpuRtPeriod = *r.CPU.RealtimePeriod
-+				}
-+				if r.CPU.Cpus != "" {
-+					c.Resources.CpusetCpus = r.CPU.Cpus
-+				}
-+				if r.CPU.Mems != "" {
-+					c.Resources.CpusetMems = r.CPU.Mems
-+				}
- 			}
--			if r.BlockIO.LeafWeight != nil {
--				c.Resources.BlkioLeafWeight = *r.BlockIO.LeafWeight
-+			if r.Pids != nil {
-+				c.Resources.PidsLimit = r.Pids.Limit
- 			}
--			if r.BlockIO.WeightDevice != nil {
--				for _, wd := range r.BlockIO.WeightDevice {
--					var weight, leafWeight uint16
--					if wd.Weight != nil {
--						weight = *wd.Weight
--					}
--					if wd.LeafWeight != nil {
--						leafWeight = *wd.LeafWeight
-+			if r.BlockIO != nil {
-+				if r.BlockIO.Weight != nil {
-+					c.Resources.BlkioWeight = *r.BlockIO.Weight
-+				}
-+				if r.BlockIO.LeafWeight != nil {
-+					c.Resources.BlkioLeafWeight = *r.BlockIO.LeafWeight
-+				}
-+				if r.BlockIO.WeightDevice != nil {
-+					for _, wd := range r.BlockIO.WeightDevice {
-+						var weight, leafWeight uint16
-+						if wd.Weight != nil {
-+							weight = *wd.Weight
-+						}
-+						if wd.LeafWeight != nil {
-+							leafWeight = *wd.LeafWeight
-+						}
-+						weightDevice := configs.NewWeightDevice(wd.Major, wd.Minor, weight, leafWeight)
-+						c.Resources.BlkioWeightDevice = append(c.Resources.BlkioWeightDevice, weightDevice)
- 					}
--					weightDevice := configs.NewWeightDevice(wd.Major, wd.Minor, weight, leafWeight)
--					c.Resources.BlkioWeightDevice = append(c.Resources.BlkioWeightDevice, weightDevice)
- 				}
--			}
--			if r.BlockIO.ThrottleReadBpsDevice != nil {
--				for _, td := range r.BlockIO.ThrottleReadBpsDevice {
--					rate := td.Rate
--					throttleDevice := configs.NewThrottleDevice(td.Major, td.Minor, rate)
--					c.Resources.BlkioThrottleReadBpsDevice = append(c.Resources.BlkioThrottleReadBpsDevice, throttleDevice)
-+				if r.BlockIO.ThrottleReadBpsDevice != nil {
-+					for _, td := range r.BlockIO.ThrottleReadBpsDevice {
-+						rate := td.Rate
-+						throttleDevice := configs.NewThrottleDevice(td.Major, td.Minor, rate)
-+						c.Resources.BlkioThrottleReadBpsDevice = append(c.Resources.BlkioThrottleReadBpsDevice, throttleDevice)
-+					}
- 				}
--			}
--			if r.BlockIO.ThrottleWriteBpsDevice != nil {
--				for _, td := range r.BlockIO.ThrottleWriteBpsDevice {
--					rate := td.Rate
--					throttleDevice := configs.NewThrottleDevice(td.Major, td.Minor, rate)
--					c.Resources.BlkioThrottleWriteBpsDevice = append(c.Resources.BlkioThrottleWriteBpsDevice, throttleDevice)
-+				if r.BlockIO.ThrottleWriteBpsDevice != nil {
-+					for _, td := range r.BlockIO.ThrottleWriteBpsDevice {
-+						rate := td.Rate
-+						throttleDevice := configs.NewThrottleDevice(td.Major, td.Minor, rate)
-+						c.Resources.BlkioThrottleWriteBpsDevice = append(c.Resources.BlkioThrottleWriteBpsDevice, throttleDevice)
-+					}
- 				}
--			}
--			if r.BlockIO.ThrottleReadIOPSDevice != nil {
--				for _, td := range r.BlockIO.ThrottleReadIOPSDevice {
--					rate := td.Rate
--					throttleDevice := configs.NewThrottleDevice(td.Major, td.Minor, rate)
--					c.Resources.BlkioThrottleReadIOPSDevice = append(c.Resources.BlkioThrottleReadIOPSDevice, throttleDevice)
-+				if r.BlockIO.ThrottleReadIOPSDevice != nil {
-+					for _, td := range r.BlockIO.ThrottleReadIOPSDevice {
-+						rate := td.Rate
-+						throttleDevice := configs.NewThrottleDevice(td.Major, td.Minor, rate)
-+						c.Resources.BlkioThrottleReadIOPSDevice = append(c.Resources.BlkioThrottleReadIOPSDevice, throttleDevice)
-+					}
- 				}
--			}
--			if r.BlockIO.ThrottleWriteIOPSDevice != nil {
--				for _, td := range r.BlockIO.ThrottleWriteIOPSDevice {
--					rate := td.Rate
--					throttleDevice := configs.NewThrottleDevice(td.Major, td.Minor, rate)
--					c.Resources.BlkioThrottleWriteIOPSDevice = append(c.Resources.BlkioThrottleWriteIOPSDevice, throttleDevice)
-+				if r.BlockIO.ThrottleWriteIOPSDevice != nil {
-+					for _, td := range r.BlockIO.ThrottleWriteIOPSDevice {
-+						rate := td.Rate
-+						throttleDevice := configs.NewThrottleDevice(td.Major, td.Minor, rate)
-+						c.Resources.BlkioThrottleWriteIOPSDevice = append(c.Resources.BlkioThrottleWriteIOPSDevice, throttleDevice)
-+					}
- 				}
- 			}
--		}
--		for _, l := range r.HugepageLimits {
--			c.Resources.HugetlbLimit = append(c.Resources.HugetlbLimit, &configs.HugepageLimit{
--				Pagesize: l.Pagesize,
--				Limit:    l.Limit,
--			})
--		}
--		if r.Network != nil {
--			if r.Network.ClassID != nil {
--				c.Resources.NetClsClassid = *r.Network.ClassID
--			}
--			for _, m := range r.Network.Priorities {
--				c.Resources.NetPrioIfpriomap = append(c.Resources.NetPrioIfpriomap, &configs.IfPrioMap{
--					Interface: m.Name,
--					Priority:  int64(m.Priority),
-+			for _, l := range r.HugepageLimits {
-+				c.Resources.HugetlbLimit = append(c.Resources.HugetlbLimit, &configs.HugepageLimit{
-+					Pagesize: l.Pagesize,
-+					Limit:    l.Limit,
- 				})
- 			}
-+			if r.Network != nil {
-+				if r.Network.ClassID != nil {
-+					c.Resources.NetClsClassid = *r.Network.ClassID
-+				}
-+				for _, m := range r.Network.Priorities {
-+					c.Resources.NetPrioIfpriomap = append(c.Resources.NetPrioIfpriomap, &configs.IfPrioMap{
-+						Interface: m.Name,
-+						Priority:  int64(m.Priority),
-+					})
-+				}
-+			}
- 		}
- 	}
--	// append the default allowed devices to the end of the list
--	c.Resources.Devices = append(c.Resources.Devices, AllowedDevices...)
-+	// Append the default allowed devices to the end of the list.
-+	// XXX: Really this should be prefixed...
-+	for _, device := range AllowedDevices {
-+		c.Resources.Devices = append(c.Resources.Devices, &device.DeviceRule)
-+	}
- 	return c, nil
- }
- 
--func stringToCgroupDeviceRune(s string) (rune, error) {
-+func stringToCgroupDeviceRune(s string) (configs.DeviceType, error) {
- 	switch s {
- 	case "a":
--		return 'a', nil
-+		return configs.WildcardDevice, nil
- 	case "b":
--		return 'b', nil
-+		return configs.BlockDevice, nil
- 	case "c":
--		return 'c', nil
-+		return configs.CharDevice, nil
- 	default:
- 		return 0, fmt.Errorf("invalid cgroup device type %q", s)
- 	}
- }
- 
--func stringToDeviceRune(s string) (rune, error) {
-+func stringToDeviceRune(s string) (configs.DeviceType, error) {
- 	switch s {
- 	case "p":
--		return 'p', nil
--	case "u":
--		return 'u', nil
-+		return configs.FifoDevice, nil
-+	case "u", "c":
-+		return configs.CharDevice, nil
- 	case "b":
--		return 'b', nil
--	case "c":
--		return 'c', nil
-+		return configs.BlockDevice, nil
- 	default:
- 		return 0, fmt.Errorf("invalid device type %q", s)
- 	}
- }
- 
- func createDevices(spec *specs.Spec, config *configs.Config) error {
--	// add whitelisted devices
--	config.Devices = []*configs.Device{
--		{
--			Type:     'c',
--			Path:     "/dev/null",
--			Major:    1,
--			Minor:    3,
--			FileMode: 0666,
--			Uid:      0,
--			Gid:      0,
--		},
--		{
--			Type:     'c',
--			Path:     "/dev/random",
--			Major:    1,
--			Minor:    8,
--			FileMode: 0666,
--			Uid:      0,
--			Gid:      0,
--		},
--		{
--			Type:     'c',
--			Path:     "/dev/full",
--			Major:    1,
--			Minor:    7,
--			FileMode: 0666,
--			Uid:      0,
--			Gid:      0,
--		},
--		{
--			Type:     'c',
--			Path:     "/dev/tty",
--			Major:    5,
--			Minor:    0,
--			FileMode: 0666,
--			Uid:      0,
--			Gid:      0,
--		},
--		{
--			Type:     'c',
--			Path:     "/dev/zero",
--			Major:    1,
--			Minor:    5,
--			FileMode: 0666,
--			Uid:      0,
--			Gid:      0,
--		},
--		{
--			Type:     'c',
--			Path:     "/dev/urandom",
--			Major:    1,
--			Minor:    9,
--			FileMode: 0666,
--			Uid:      0,
--			Gid:      0,
--		},
-+	// Add default set of devices.
-+	for _, device := range AllowedDevices {
-+		if device.Path != "" {
-+			config.Devices = append(config.Devices, device)
-+		}
- 	}
--	// merge in additional devices from the spec
-+	// Merge in additional devices from the spec.
- 	if spec.Linux != nil {
- 		for _, d := range spec.Linux.Devices {
- 			var uid, gid uint32
-@@ -606,10 +596,12 @@ func createDevices(spec *specs.Spec, config *configs.Config) error {
- 				filemode = *d.FileMode
- 			}
- 			device := &configs.Device{
--				Type:     dt,
-+				DeviceRule: configs.DeviceRule{
-+					Type:  dt,
-+					Major: d.Major,
-+					Minor: d.Minor,
-+				},
- 				Path:     d.Path,
--				Major:    d.Major,
--				Minor:    d.Minor,
- 				FileMode: filemode,
- 				Uid:      uid,
- 				Gid:      gid,
--- 
-2.26.2
-
diff --git a/runc-1.0.0-rc10.tar.xz b/runc-1.0.0-rc10.tar.xz
deleted file mode 100644
index 43fb692..0000000
--- a/runc-1.0.0-rc10.tar.xz
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:c823307ce8695af05381c5c25a92daacd6219c674d8bebaa0e1bff801c2b1f24
-size 743480
diff --git a/runc-1.0.0-rc10.tar.xz.asc b/runc-1.0.0-rc10.tar.xz.asc
deleted file mode 100644
index d238a72..0000000
--- a/runc-1.0.0-rc10.tar.xz.asc
+++ /dev/null
@@ -1,17 +0,0 @@
------BEGIN PGP SIGNATURE-----
-
-iQJDBAABCAAtFiEEXzbGxhtUYBJKdfWmnhiqJn3bjbQFAl4pJHYPHGFzYXJhaUBz
-dXNlLmRlAAoJEJ4YqiZ92420DgkP/2zEWRsjx/qNMFnwOo6yYy+sGeQn8KMmEQRD
-9OpX+mF6BtIMICQ9ocenYjTkwzd7UVq4SC1VXFKBZQgchlH4X+5RT89AUiFePRsi
-jsEP5Cwr/5xDBhW6gvwh5V1Z+XzdxTb4lNuodoFm0OucNoFGp7vCbKmfgxqY/m66
-9R23tKouot0y6q1sSBG2lLwwmj7JAQboxtVA+dTEzmhnEyfh0UzBlJKDW1I79mbE
-NeD1r/Rh/NVrJL6HaKfXhdVvITVyeabQLBgj0Y/JYVCWMfyXnz7sBJQ7lrwtVV+P
-9qROtwSu15vOLi9d2u+U8SwjAmDz01kLH3rYji/Xtie3xk8i/8yD9q8lFjA3fbu5
-IOs1vogsWt6yMWRnXHWbBxMmtOw+RQJ/gyUpCJE5MeMQNHPsZ6wYbMXTfzdqFnse
-bTkOHPPIRqnK6mDT0b3CoS7Ugi8qZs2lZ8CvVoOeTqaCMb5SIWehF7jIbo0ECzGf
-TQ0cZ982M03Rm4NjI7G5SCKIpJPEtOYS0NauOn6oqdDON9qCJVZdecCfdWYdEO1n
-ikpea7Ahc5x0g9p0WF+HsewvqpMpWUdCsVmLPiwJrBG2GFOC3oPvB2vjKUk28ix1
-3B7v3JS/XHlokRPMEkJn+zR7CVWchaT7Ov+3AHM9VCjk5dgNdADF3y7DYG8Q/ccV
-TZGdkemL
-=UoY/
------END PGP SIGNATURE-----
diff --git a/runc-1.0.0-rc91.tar.xz b/runc-1.0.0-rc91.tar.xz
new file mode 100644
index 0000000..677fdbc
--- /dev/null
+++ b/runc-1.0.0-rc91.tar.xz
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:218bfeb626b729a686e5df1216410a19d3147185109a481ea68a0c0cb872074a
+size 1123984
diff --git a/runc-1.0.0-rc91.tar.xz.asc b/runc-1.0.0-rc91.tar.xz.asc
new file mode 100644
index 0000000..ef72901
--- /dev/null
+++ b/runc-1.0.0-rc91.tar.xz.asc
@@ -0,0 +1,16 @@
+-----BEGIN PGP SIGNATURE-----
+
+iQIzBAABCAAdFiEEXzbGxhtUYBJKdfWmnhiqJn3bjbQFAl79NOcACgkQnhiqJn3b
+jbR4xBAAtu5FwyYuHBiy2g7piPnnHjD3LNJ10DTvJXnNzxncipZjv6m5EERuiaiH
+ugtl06dNfMB1A7e+faI5JMsHcjqnOlzcDTs5RHjNV/lvCfCjvAIYsjn56HmSF56U
+wrnOa7buXrGykUBPPJwRe6oJM/Rq8E+L1FbO4yxzAU94H6iFZTJUY6YgAMndXy91
++lAxu47ehohQ27rbdhMUgm/HELsskN401+bYFGMMTq24vEHlJ56pNeZHKisaLOrI
+6iVJhT0VpEjGJJhChHDLOx5b22Dfq3SzAd2189SymJTas8CaRWhADeHSSHYGY9UC
+VDtDpwUtlLJIksQSTdcPuBuKbfIy0zHYCn+sowklRlMQz8tUbO+xXuOhX5k3dLe8
+F+k/+NU9lxBZsp/MKkBzC1YaBDOHD95wI7Lc0iT0LIZCYXg8pWUbVfuPSFBx8cCA
+/uyp3WnrK5gsZHMFEMCrFkBL+y0XB45Fo5Y6cKpPRuOGOjvSIAP2PvHHM78oE8Ax
+LJxEmLp7thJTNFoVVlp8pPfnkZqsj+/8a9j5jjZra9c0WIC6gYkXgida70Oq9yxj
+7AGiK+o7XS3dqzynHHSODsQu89i1zwSrkFbqIgsfem2JTc2NRJV/ouCBhKatjuSW
+YbGcXH7NNeXSWb67TmKhUgQihBainYU5L5W0Y6M4/9mu73R5tG4=
+=Af6r
+-----END PGP SIGNATURE-----
diff --git a/runc.changes b/runc.changes
index 5ebb3e5..ec160f7 100644
--- a/runc.changes
+++ b/runc.changes
@@ -1,3 +1,20 @@
+-------------------------------------------------------------------
+Thu Jul  2 01:24:49 UTC 2020 - Aleksa Sarai <asarai@suse.com>
+
+- Upgrade to runc v1.0.0~rc91. Upstream changelog is available from
+  https://github.com/opencontainers/runc/releases/tag/v1.0.0-rc91
+
+  * This release of runc has experimental support for cgroupv2-only systems.
+
+- Remove upstreamed patches:
+  - bsc1149954-0001-sd-notify-do-not-hang-when-NOTIFY_SOCKET-is-used-wit.patch
+  - bsc1168481-0001-cgroup-devices-major-cleanups-and-minimal-transition.patch
+
+-------------------------------------------------------------------
+Thu Jun 25 22:34:03 UTC 2020 - Aleksa Sarai <asarai@suse.com>
+
+- Switch to Go 1.13 for build.
+
 -------------------------------------------------------------------
 Wed May 13 06:49:44 UTC 2020 - Aleksa Sarai <asarai@suse.com>
 
diff --git a/runc.spec b/runc.spec
index d5cb9fa..0cc2c57 100644
--- a/runc.spec
+++ b/runc.spec
@@ -22,9 +22,9 @@
 %define git_version %{nil}
 
 # Package-wide golang version
-%define go_version 1.10
+%define go_version 1.13
 %define go_tool go
-%define _version 1.0.0-rc10
+%define _version 1.0.0-rc91
 %define project github.com/opencontainers/runc
 
 # enable libseccomp for sle >= sle12sp2
@@ -41,7 +41,7 @@
 %endif
 
 Name:           runc
-Version:        1.0.0~rc10
+Version:        1.0.0~rc91
 Release:        0
 Summary:        Tool for spawning and running OCI containers
 License:        Apache-2.0
@@ -51,13 +51,11 @@ Source0:        https://github.com/opencontainers/runc/releases/download/v%{_ver
 Source1:        https://github.com/opencontainers/runc/releases/download/v%{_version}/runc.tar.xz.asc#/runc-%{_version}.tar.xz.asc
 Source2:        runc.keyring
 Source3:        runc-rpmlintrc
-# FIX-UPSTREAM: Backport of https://github.com/opencontainers/runc/pull/1807. bsc#1149954
-Patch0:         bsc1149954-0001-sd-notify-do-not-hang-when-NOTIFY_SOCKET-is-used-wit.patch
-# FIX-UPSTREAM: Backport of https://github.com/opencontainers/runc/pull/2391. bsc#1168481
-Patch1:         bsc1168481-0001-cgroup-devices-major-cleanups-and-minimal-transition.patch
 BuildRequires:  fdupes
 BuildRequires:  go-go-md2man
-BuildRequires:  golang(API) >= %{go_version}
+# Due to a limitation in openSUSE's Go packaging we cannot have a BuildRequires
+# for 'golang(API) >= 1.x' here, so just require 1.x exactly. bsc#1172608
+BuildRequires:  go%{go_version}
 %if 0%{?with_libseccomp}
 BuildRequires:  libseccomp-devel
 %endif
@@ -73,7 +71,7 @@ and has grown to become a separate project entirely.
 %package test
 Summary:        Test package for runc
 Group:          System/Management
-BuildRequires:  golang(API) >= %{go_version}
+BuildRequires:  go%{go_version}
 %if 0%{?with_libseccomp}
 BuildRequires:  libseccomp-devel
 %endif
@@ -88,10 +86,6 @@ Test package for runc. It contains the source code and the tests.
 
 %prep
 %setup -q -n %{name}-%{_version}
-# bsc#1149954
-%patch0 -p1
-# bsc#1168481
-%patch1 -p1
 
 %build
 # Do not use symlinks. If you want to run the unit tests for this package at