diff --git a/patch/0118-runc-don-t-deny-all-devices-when-update-cgroup-resou.patch b/patch/0118-runc-don-t-deny-all-devices-when-update-cgroup-resou.patch new file mode 100644 index 0000000000000000000000000000000000000000..bfb3e07ee66c0d6d9e8b86a7aeced74aa35bfc48 --- /dev/null +++ b/patch/0118-runc-don-t-deny-all-devices-when-update-cgroup-resou.patch @@ -0,0 +1,180 @@ +From eb576e28687555ba9253a2679706bf5491be0c11 Mon Sep 17 00:00:00 2001 +From: panwenxiang +Date: Fri, 24 Apr 2020 23:05:31 +0800 +Subject: [PATCH] runc: don't deny all devices when update cgroup resource + +reason: runc Update command causes 'Operation not permitted + +cherry-pick from https://github.com/opencontainers/runc/pull/2204 +cherry-pick from https://github.com/opencontainers/runc/pull/2205 + +It's first seen in a kubernetes cluster with docker as container runtime. +Our users reported that in some situation their bash script failed with message +can't create /dev/null: Operation not permitted. +But /dev/null is default device with permission rwm, +After digging some logs, we found that it can be reproduced in runc by following steps. + +- Run a runc container like "busybox". Suppose this container is called A +- run while true;do echo >/dev/null;done in container +- runc update --cpu-share 1024 A +- You will see sh: can't create /dev/null: Operation not permitted + +Change-Id: I4f374eed5033b9f3eb47c31b622c408d24142473 +Signed-off-by: panwenxiang +--- + libcontainer/cgroups/fs/devices.go | 72 +++++++++++++++++++++---- + libcontainer/cgroups/fs/devices_test.go | 8 +++ + 2 files changed, 71 insertions(+), 9 deletions(-) + +diff --git a/libcontainer/cgroups/fs/devices.go b/libcontainer/cgroups/fs/devices.go +index 478b5db7..c5ca11f5 100644 +--- a/libcontainer/cgroups/fs/devices.go ++++ b/libcontainer/cgroups/fs/devices.go +@@ -10,11 +10,18 @@ import ( + "io" + "os" + "path/filepath" ++ "strings" + ) + + type DevicesGroup struct { + } + ++type Empty struct{} ++ ++var ( ++ defaultDevice = &configs.Device{Type: 'a', Major: -1, Minor: -1, Permissions: "rwm"} ++) ++ + func (s *DevicesGroup) Name() string { + return "devices" + } +@@ -61,6 +68,10 @@ func (s *DevicesGroup) Set(path string, cgroup *configs.Cgroup) error { + return err + } + ++ oldAllowedDevices, err := readDevicesExceptDefault(path) ++ if err != nil { ++ return err ++ } + devices := cgroup.Resources.Devices + if len(devices) > 0 { + for _, dev := range devices { +@@ -68,10 +79,17 @@ func (s *DevicesGroup) Set(path string, cgroup *configs.Cgroup) error { + if dev.Allow { + file = "devices.allow" + } +- if err := writeFile(path, file, dev.CgroupString()); err != nil { +- return err ++ // For the second time set, we don't deny all devices, skip ++ if dev.Type == defaultDevice.Type && len(oldAllowedDevices) != 0 { ++ file = "" ++ } ++ ++ if len(file) > 0 { ++ if err := writeFile(path, file, dev.CgroupString()); err != nil { ++ return err ++ } ++ delete(deviceMap, dev.CgroupString()) + } +- delete(deviceMap, dev.CgroupString()) + } + for item, _ := range deviceMap { + if item[0] == 'b' || item[0] == 'c' { +@@ -84,13 +102,31 @@ func (s *DevicesGroup) Set(path string, cgroup *configs.Cgroup) error { + } + if cgroup.Resources.AllowAllDevices != nil { + if *cgroup.Resources.AllowAllDevices == false { +- if err := writeFile(path, "devices.deny", "a"); err != nil { +- return err ++ if len(oldAllowedDevices) == 0 { ++ if err := writeFile(path, "devices.deny", "a"); err != nil { ++ return err ++ } + } + +- for _, dev := range cgroup.Resources.AllowedDevices { +- if err := writeFile(path, "devices.allow", dev.CgroupString()); err != nil { +- return err ++ newAllowedDevices := make(map[string]Empty) ++ for _, dev := range cgroup.AllowedDevices { ++ newAllowedDevices[dev.CgroupString()] = Empty{} ++ } ++ // Deny no longer allowed devices ++ for cgroupString := range oldAllowedDevices { ++ if _, found := newAllowedDevices[cgroupString]; !found { ++ if err := writeFile(path, "devices.deny", cgroupString); err != nil { ++ return err ++ } ++ } ++ } ++ ++ // Allow new devices ++ for cgroupString := range newAllowedDevices { ++ if _, found := oldAllowedDevices[cgroupString]; !found { ++ if err := writeFile(path, "devices.allow", cgroupString); err != nil { ++ return err ++ } + } + } + return nil +@@ -100,7 +136,6 @@ func (s *DevicesGroup) Set(path string, cgroup *configs.Cgroup) error { + return err + } + } +- + for _, dev := range cgroup.Resources.DeniedDevices { + if err := writeFile(path, "devices.deny", dev.CgroupString()); err != nil { + return err +@@ -117,3 +152,22 @@ func (s *DevicesGroup) Remove(d *cgroupData) error { + func (s *DevicesGroup) GetStats(path string, stats *cgroups.Stats) error { + return nil + } ++ ++func readDevicesExceptDefault(path string) (allowed map[string]Empty, err error) { ++ cgroupData, err := readFile(path, "devices.list") ++ if err != nil { ++ return nil, err ++ } ++ ++ allowedDevices := make(map[string]Empty) ++ defaultDeviceString := defaultDevice.CgroupString() ++ for _, data := range strings.Split(cgroupData, "\n") { ++ // skip allow all devices ++ if len(data) == 0 || data == defaultDeviceString { ++ continue ++ } ++ allowedDevices[data] = Empty{} ++ } ++ ++ return allowedDevices, nil ++} +diff --git a/libcontainer/cgroups/fs/devices_test.go b/libcontainer/cgroups/fs/devices_test.go +index fc635b99..1184c459 100644 +--- a/libcontainer/cgroups/fs/devices_test.go ++++ b/libcontainer/cgroups/fs/devices_test.go +@@ -37,6 +37,10 @@ func TestDevicesSetAllow(t *testing.T) { + helper := NewCgroupTestUtil("devices", t) + defer helper.cleanup() + ++ helper.writeFileContents(map[string]string{ ++ "devices.list": "a *:* rwm", ++ }) ++ + helper.writeFileContents(map[string]string{ + "devices.deny": "a", + }) +@@ -75,6 +79,10 @@ func TestDevicesSetDeny(t *testing.T) { + helper := NewCgroupTestUtil("devices", t) + defer helper.cleanup() + ++ helper.writeFileContents(map[string]string{ ++ "devices.list": "a *:* rwm", ++ }) ++ + helper.writeFileContents(map[string]string{ + "devices.allow": "a", + }) +-- +2.21.0 + diff --git a/patch/0119-runc-rootfs-do-not-permit-proc-mounts-to-no.patch b/patch/0119-runc-rootfs-do-not-permit-proc-mounts-to-no.patch new file mode 100644 index 0000000000000000000000000000000000000000..b203ba4643c24dbca7d1fbb2ff3ccc7793c0beea --- /dev/null +++ b/patch/0119-runc-rootfs-do-not-permit-proc-mounts-to-no.patch @@ -0,0 +1,49 @@ +From 78b4cdf24a75950da64dab9146984b448497cc28 Mon Sep 17 00:00:00 2001 +From: xiadanni1 +Date: Wed, 15 Apr 2020 16:58:02 +0800 +Subject: [PATCH] rootfs: do not permit /proc mounts to non-directories + +mount(2) will blindly follow symlinks, which is a problem because it +allows a malicious container to trick runc into mounting /proc to an +entirely different location (and thus within the attacker's control for +a rename-exchange attack). + +This is just a hotfix (to "stop the bleeding"), and the more complete +fix would be finish libpathrs and port runc to it (to avoid these types +of attacks entirely, and defend against a variety of other /proc-related +attacks). It can be bypased by someone having "/" be a volume controlled +by another container. + +Fixes: CVE-2019-19921 +Signed-off-by: Aleksa Sarai +Signed-off-by: xiadanni1 +--- + libcontainer/rootfs_linux.go | 12 ++++++++++++ + 1 file changed, 12 insertions(+) + +diff --git a/libcontainer/rootfs_linux.go b/libcontainer/rootfs_linux.go +index 4c18482..67cf0bf 100644 +--- a/libcontainer/rootfs_linux.go ++++ b/libcontainer/rootfs_linux.go +@@ -168,6 +168,18 @@ func mountToRootfs(m *configs.Mount, rootfs, mountLabel string) error { + + switch m.Device { + case "proc", "sysfs": ++ // If the destination already exists and is not a directory, we bail ++ // out This is to avoid mounting through a symlink or similar -- which ++ // has been a "fun" attack scenario in the past. ++ // TODO: This won't be necessary once we switch to libpathrs and we can ++ // stop all of these symlink-exchange attacks. ++ if fi, err := os.Lstat(dest); err != nil { ++ if !os.IsNotExist(err) { ++ return err ++ } ++ } else if fi.Mode()&os.ModeDir == 0 { ++ return fmt.Errorf("filesystem %q must be mounted on ordinary directory", m.Device) ++ } + if strings.HasPrefix(m.Destination, "/proc/sys/") { + return nil + } +-- +1.8.3.1 + diff --git a/patch/0120-runc-fix-permission-denied.patch b/patch/0120-runc-fix-permission-denied.patch new file mode 100644 index 0000000000000000000000000000000000000000..0c304b8b338e1e9b66d7a4a09781656dde2a2cd1 --- /dev/null +++ b/patch/0120-runc-fix-permission-denied.patch @@ -0,0 +1,45 @@ +From 6594d5c042a2253386820a640b3a7087e07d0df2 Mon Sep 17 00:00:00 2001 +From: xiadanni +Date: Thu, 9 Jul 2020 15:56:54 +0800 +Subject: [PATCH] runc: fix permission denied + +reason: when exec as root and config.Cwd is not owned by root, +exec will fail because root doesn't have the caps. + +Signed-off-by: Kurnia D Win +Signed-off-by: xiadanni +--- + libcontainer/init_linux.go | 10 +++++----- + 1 file changed, 5 insertions(+), 5 deletions(-) + +diff --git a/libcontainer/init_linux.go b/libcontainer/init_linux.go +index 2a93431..73505ef 100644 +--- a/libcontainer/init_linux.go ++++ b/libcontainer/init_linux.go +@@ -118,6 +118,11 @@ func finalizeNamespace(config *initConfig) error { + if err := utils.CloseExecFrom(config.PassedFilesCount + 3); err != nil { + return err + } ++ if config.Cwd != "" { ++ if err := syscall.Chdir(config.Cwd); err != nil { ++ return fmt.Errorf("chdir to cwd (%q) set in config.json failed: %v", config.Cwd, err) ++ } ++ } + + capabilities := &configs.Capabilities{} + if config.Capabilities != nil { +@@ -146,11 +151,6 @@ func finalizeNamespace(config *initConfig) error { + if err := w.ApplyCaps(); err != nil { + return err + } +- if config.Cwd != "" { +- if err := syscall.Chdir(config.Cwd); err != nil { +- return fmt.Errorf("chdir to cwd (%q) set in config.json failed: %v", config.Cwd, err) +- } +- } + return nil + } + +-- +1.8.3.1 + diff --git a/runc-openeuler.spec b/runc-openeuler.spec index df0c4fa12d82b4c206da6aebc0c7c423d018104c..29be670b25710fc5ed457fffc2493d3013de6b62 100644 --- a/runc-openeuler.spec +++ b/runc-openeuler.spec @@ -2,7 +2,7 @@ Name: docker-runc Version: 1.0.0.rc3 -Release: 103 +Release: 104 Summary: runc is a CLI tool for spawning and running containers according to the OCI specification. License: ASL 2.0 @@ -40,3 +40,32 @@ install -p -m 755 runc $RPM_BUILD_ROOT/%{_bindir}/runc %{_bindir}/runc %changelog +* Wed Nov 25 2020 xiadanni - 1.0.0.rc3-104 +- Type:bugfix +- ID:NA +- SUG:NA +- DESC:don't deny all devices when update cgroup resource + do not permit /proc mounts to non-directories + fix permission denied + +* Fri Mar 20 2020 xiadanni - 1.0.0.rc3-103 +- Type:bugfix +- ID:NA +- SUG:NA +- DESC:pass back the pid of runc:[1:CHILD] so we can wait on it + +* Thu Mar 5 2020 xiadanni - 1.0.0.rc3-102 +- Type:bugfix +- ID:NA +- SUG:NA +- DESC:fixes config.Namespaces is empty when accessed + write freezer state after every state check + may kill other process when container has been stopped + fix cgroup hugetlb size prefix for kB + check nil pointers in cgroup manager + +* Wed Jan 1 2020 xiadanni - 1.0.0.rc3-101 +- Type:requirement +- ID:NA +- SUG:NA +- DESC:package init diff --git a/series.conf b/series.conf index 376f1a2781eb5b00b92f787a322eb1f3881bf83d..d87ecaba38349396267e9e533d27c69071abef4f 100644 --- a/series.conf +++ b/series.conf @@ -114,3 +114,6 @@ 0115-runc-Fix-cgroup-hugetlb-size-prefix-for-kB.patch 0116-runc-check-nil-pointers-in-cgroup-manager.patch 0117-runc-Pass-back-the-pid-of-runc-1-CHILD-so-w.patch +0118-runc-don-t-deny-all-devices-when-update-cgroup-resou.patch +0119-runc-rootfs-do-not-permit-proc-mounts-to-no.patch +0120-runc-fix-permission-denied.patch