From 7b244a9844a9581be324a426c5b9d8f1559335873b6e5de4c3a2a8a74d30f727 Mon Sep 17 00:00:00 2001 From: Aleksa Sarai Date: Thu, 2 Jul 2020 01:50:30 +0000 Subject: [PATCH] Accepting request 818188 from home:cyphar:docker - 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 OBS-URL: https://build.opensuse.org/request/show/818188 OBS-URL: https://build.opensuse.org/package/show/Virtualization:containers/runc?expand=0&rev=96 --- ...-hang-when-NOTIFY_SOCKET-is-used-wit.patch | 291 -- ...ajor-cleanups-and-minimal-transition.patch | 3832 ----------------- runc-1.0.0-rc10.tar.xz | 3 - runc-1.0.0-rc10.tar.xz.asc | 17 - runc-1.0.0-rc91.tar.xz | 3 + runc-1.0.0-rc91.tar.xz.asc | 16 + runc.changes | 17 + runc.spec | 20 +- 8 files changed, 43 insertions(+), 4156 deletions(-) delete mode 100644 bsc1149954-0001-sd-notify-do-not-hang-when-NOTIFY_SOCKET-is-used-wit.patch delete mode 100644 bsc1168481-0001-cgroup-devices-major-cleanups-and-minimal-transition.patch delete mode 100644 runc-1.0.0-rc10.tar.xz delete mode 100644 runc-1.0.0-rc10.tar.xz.asc create mode 100644 runc-1.0.0-rc91.tar.xz create mode 100644 runc-1.0.0-rc91.tar.xz.asc 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 -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 -(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 := ¬ifySocket{ - 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, ¬ifySocketHostAddr) - 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 -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 ---- - 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 -- 20: JNEImm dst: r4 off: -1 imm: 5 -- 21: JNEImm dst: r5 off: -1 imm: 1 -+ 20: JNEImm dst: r4 off: -1 imm: 1 -+ 21: JNEImm dst: r5 off: -1 imm: 9 - 22: Mov32Imm dst: r0 imm: 1 - 23: Exit - block-4: - 24: JNEImm dst: r2 off: -1 imm: 2 - 25: JNEImm dst: r4 off: -1 imm: 1 -- 26: JNEImm dst: r5 off: -1 imm: 9 -+ 26: JNEImm dst: r5 off: -1 imm: 5 - 27: Mov32Imm dst: r0 imm: 1 - 28: Exit - block-5: - 29: JNEImm dst: r2 off: -1 imm: 2 -- 30: JNEImm dst: r4 off: -1 imm: 1 -- 31: JNEImm dst: r5 off: -1 imm: 5 -+ 30: JNEImm dst: r4 off: -1 imm: 5 -+ 31: JNEImm dst: r5 off: -1 imm: 0 - 32: Mov32Imm dst: r0 imm: 1 - 33: Exit - block-6: - 34: JNEImm dst: r2 off: -1 imm: 2 -- 35: JNEImm dst: r4 off: -1 imm: 5 -- 36: JNEImm dst: r5 off: -1 imm: 0 -+ 35: JNEImm dst: r4 off: -1 imm: 1 -+ 36: JNEImm dst: r5 off: -1 imm: 7 - 37: Mov32Imm dst: r0 imm: 1 - 38: Exit - block-7: - 39: JNEImm dst: r2 off: -1 imm: 2 - 40: JNEImm dst: r4 off: -1 imm: 1 -- 41: JNEImm dst: r5 off: -1 imm: 7 -+ 41: JNEImm dst: r5 off: -1 imm: 8 - 42: Mov32Imm dst: r0 imm: 1 - 43: Exit - block-8: - 44: JNEImm dst: r2 off: -1 imm: 2 - 45: JNEImm dst: r4 off: -1 imm: 1 -- 46: JNEImm dst: r5 off: -1 imm: 8 -+ 46: JNEImm dst: r5 off: -1 imm: 3 - 47: Mov32Imm dst: r0 imm: 1 - 48: Exit - block-9: -- 49: JNEImm dst: r2 off: -1 imm: 2 -- 50: JNEImm dst: r4 off: -1 imm: 1 -- 51: JNEImm dst: r5 off: -1 imm: 3 -- 52: Mov32Imm dst: r0 imm: 1 -- 53: Exit --block-10: - // (b, wildcard, wildcard, m, true) -- 54: JNEImm dst: r2 off: -1 imm: 1 -- 55: Mov32Reg dst: r1 src: r3 -- 56: And32Imm dst: r1 imm: 1 -- 57: JEqImm dst: r1 off: -1 imm: 0 -- 58: Mov32Imm dst: r0 imm: 1 -- 59: Exit --block-11: -+ 49: JNEImm dst: r2 off: -1 imm: 1 -+ 50: Mov32Reg dst: r1 src: r3 -+ 51: And32Imm dst: r1 imm: 1 -+ 52: JEqImm dst: r1 off: -1 imm: 0 -+ 53: Mov32Imm dst: r0 imm: 1 -+ 54: Exit -+block-10: - // (c, wildcard, wildcard, m, true) -- 60: JNEImm dst: r2 off: -1 imm: 2 -- 61: Mov32Reg dst: r1 src: r3 -- 62: And32Imm dst: r1 imm: 1 -- 63: JEqImm dst: r1 off: -1 imm: 0 -- 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 -+ 56: Mov32Reg dst: r1 src: r3 -+ 57: And32Imm dst: r1 imm: 1 -+ 58: JEqImm dst: r1 off: -1 imm: 0 -+ 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 + +- 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 + +- Switch to Go 1.13 for build. + ------------------------------------------------------------------- Wed May 13 06:49:44 UTC 2020 - Aleksa Sarai 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