c0cf07af42
Fix CVE patch. OBS-URL: https://build.opensuse.org/request/show/733530 OBS-URL: https://build.opensuse.org/package/show/Virtualization:containers/runc?expand=0&rev=77
287 lines
9.6 KiB
Diff
287 lines
9.6 KiB
Diff
From 6ce3791ce27128f8b4ae45323effa10953fa9904 Mon Sep 17 00:00:00 2001
|
|
From: Aleksa Sarai <asarai@suse.de>
|
|
Date: Mon, 23 Sep 2019 16:45:45 -0400
|
|
Subject: [PATCH] CVE-2019-16884
|
|
|
|
This patch includes a squash of the following upstream patches:
|
|
|
|
* 331692baa7af ("Only allow proc mount if it is procfs")
|
|
|
|
As well as the following still-in-review patches:
|
|
|
|
* ("apparmor: verify that writes to /proc/... are on procfs")
|
|
* ("selinux: verify that writes to /proc/... are on procfs")
|
|
|
|
Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
|
|
Signed-off-by: Aleksa Sarai <asarai@suse.de>
|
|
---
|
|
libcontainer/apparmor/apparmor.go | 18 ++++++-
|
|
libcontainer/container_linux.go | 4 +-
|
|
libcontainer/rootfs_linux.go | 50 ++++++++++++++-----
|
|
libcontainer/rootfs_linux_test.go | 8 +--
|
|
.../selinux/go-selinux/selinux_linux.go | 20 ++++++++
|
|
5 files changed, 79 insertions(+), 21 deletions(-)
|
|
|
|
diff --git a/libcontainer/apparmor/apparmor.go b/libcontainer/apparmor/apparmor.go
|
|
index 7fff0627fa1b..3504b80d8643 100644
|
|
--- a/libcontainer/apparmor/apparmor.go
|
|
+++ b/libcontainer/apparmor/apparmor.go
|
|
@@ -6,6 +6,8 @@ import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
+
|
|
+ "golang.org/x/sys/unix"
|
|
)
|
|
|
|
// IsEnabled returns true if apparmor is enabled for the host.
|
|
@@ -19,7 +21,13 @@ func IsEnabled() bool {
|
|
return false
|
|
}
|
|
|
|
-func setprocattr(attr, value string) error {
|
|
+func isProcHandle(fh *os.File) (bool, error) {
|
|
+ var buf unix.Statfs_t
|
|
+ err := unix.Fstatfs(int(fh.Fd()), &buf)
|
|
+ return buf.Type == unix.PROC_SUPER_MAGIC, err
|
|
+}
|
|
+
|
|
+func setProcAttr(attr, value string) error {
|
|
// Under AppArmor you can only change your own attr, so use /proc/self/
|
|
// instead of /proc/<tid>/ like libapparmor does
|
|
path := fmt.Sprintf("/proc/self/attr/%s", attr)
|
|
@@ -30,6 +38,12 @@ func setprocattr(attr, value string) error {
|
|
}
|
|
defer f.Close()
|
|
|
|
+ if ok, err := isProcHandle(f); err != nil {
|
|
+ return err
|
|
+ } else if !ok {
|
|
+ return fmt.Errorf("/proc path not on procfs: %s", path)
|
|
+ }
|
|
+
|
|
_, err = fmt.Fprintf(f, "%s", value)
|
|
return err
|
|
}
|
|
@@ -37,7 +51,7 @@ func setprocattr(attr, value string) error {
|
|
// changeOnExec reimplements aa_change_onexec from libapparmor in Go
|
|
func changeOnExec(name string) error {
|
|
value := "exec " + name
|
|
- if err := setprocattr("exec", value); err != nil {
|
|
+ if err := setProcAttr("exec", value); err != nil {
|
|
return fmt.Errorf("apparmor failed to apply profile: %s", err)
|
|
}
|
|
return nil
|
|
diff --git a/libcontainer/container_linux.go b/libcontainer/container_linux.go
|
|
index 7e58e5e00824..d51e35dffb93 100644
|
|
--- a/libcontainer/container_linux.go
|
|
+++ b/libcontainer/container_linux.go
|
|
@@ -19,7 +19,7 @@ import (
|
|
"syscall" // only for SysProcAttr and Signal
|
|
"time"
|
|
|
|
- "github.com/cyphar/filepath-securejoin"
|
|
+ securejoin "github.com/cyphar/filepath-securejoin"
|
|
"github.com/opencontainers/runc/libcontainer/cgroups"
|
|
"github.com/opencontainers/runc/libcontainer/configs"
|
|
"github.com/opencontainers/runc/libcontainer/intelrdt"
|
|
@@ -1160,7 +1160,7 @@ func (c *linuxContainer) makeCriuRestoreMountpoints(m *configs.Mount) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
- if err := checkMountDestination(c.config.Rootfs, dest); err != nil {
|
|
+ if err := checkProcMount(c.config.Rootfs, dest, ""); err != nil {
|
|
return err
|
|
}
|
|
m.Destination = dest
|
|
diff --git a/libcontainer/rootfs_linux.go b/libcontainer/rootfs_linux.go
|
|
index f13b226e444e..5650b0acbca8 100644
|
|
--- a/libcontainer/rootfs_linux.go
|
|
+++ b/libcontainer/rootfs_linux.go
|
|
@@ -13,7 +13,7 @@ import (
|
|
"strings"
|
|
"time"
|
|
|
|
- "github.com/cyphar/filepath-securejoin"
|
|
+ securejoin "github.com/cyphar/filepath-securejoin"
|
|
"github.com/mrunalp/fileutils"
|
|
"github.com/opencontainers/runc/libcontainer/cgroups"
|
|
"github.com/opencontainers/runc/libcontainer/configs"
|
|
@@ -197,7 +197,7 @@ func prepareBindMount(m *configs.Mount, rootfs string) error {
|
|
if dest, err = securejoin.SecureJoin(rootfs, m.Destination); err != nil {
|
|
return err
|
|
}
|
|
- if err := checkMountDestination(rootfs, dest); err != nil {
|
|
+ if err := checkProcMount(rootfs, dest, m.Source); err != nil {
|
|
return err
|
|
}
|
|
// update the mount with the correct dest after symlinks are resolved.
|
|
@@ -388,7 +388,7 @@ func mountToRootfs(m *configs.Mount, rootfs, mountLabel string, enableCgroupns b
|
|
if dest, err = securejoin.SecureJoin(rootfs, m.Destination); err != nil {
|
|
return err
|
|
}
|
|
- if err := checkMountDestination(rootfs, dest); err != nil {
|
|
+ if err := checkProcMount(rootfs, dest, m.Source); err != nil {
|
|
return err
|
|
}
|
|
// update the mount with the correct dest after symlinks are resolved.
|
|
@@ -435,12 +435,12 @@ func getCgroupMounts(m *configs.Mount) ([]*configs.Mount, error) {
|
|
return binds, nil
|
|
}
|
|
|
|
-// checkMountDestination checks to ensure that the mount destination is not over the top of /proc.
|
|
+// checkProcMount checks to ensure that the mount destination is not over the top of /proc.
|
|
// dest is required to be an abs path and have any symlinks resolved before calling this function.
|
|
-func checkMountDestination(rootfs, dest string) error {
|
|
- invalidDestinations := []string{
|
|
- "/proc",
|
|
- }
|
|
+//
|
|
+// if source is nil, don't stat the filesystem. This is used for restore of a checkpoint.
|
|
+func checkProcMount(rootfs, dest, source string) error {
|
|
+ const procPath = "/proc"
|
|
// White list, it should be sub directories of invalid destinations
|
|
validDestinations := []string{
|
|
// These entries can be bind mounted by files emulated by fuse,
|
|
@@ -463,16 +463,40 @@ func checkMountDestination(rootfs, dest string) error {
|
|
return nil
|
|
}
|
|
}
|
|
- for _, invalid := range invalidDestinations {
|
|
- path, err := filepath.Rel(filepath.Join(rootfs, invalid), dest)
|
|
+ path, err := filepath.Rel(filepath.Join(rootfs, procPath), dest)
|
|
+ if err != nil {
|
|
+ return err
|
|
+ }
|
|
+ // pass if the mount path is located outside of /proc
|
|
+ if strings.HasPrefix(path, "..") {
|
|
+ return nil
|
|
+ }
|
|
+ if path == "." {
|
|
+ // an empty source is pasted on restore
|
|
+ if source == "" {
|
|
+ return nil
|
|
+ }
|
|
+ // only allow a mount on-top of proc if it's source is "proc"
|
|
+ isproc, err := isProc(source)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
- if path != "." && !strings.HasPrefix(path, "..") {
|
|
- return fmt.Errorf("%q cannot be mounted because it is located inside %q", dest, invalid)
|
|
+ // pass if the mount is happening on top of /proc and the source of
|
|
+ // the mount is a proc filesystem
|
|
+ if isproc {
|
|
+ return nil
|
|
}
|
|
+ return fmt.Errorf("%q cannot be mounted because it is not of type proc", dest)
|
|
}
|
|
- return nil
|
|
+ return fmt.Errorf("%q cannot be mounted because it is inside /proc", dest)
|
|
+}
|
|
+
|
|
+func isProc(path string) (bool, error) {
|
|
+ var s unix.Statfs_t
|
|
+ if err := unix.Statfs(path, &s); err != nil {
|
|
+ return false, err
|
|
+ }
|
|
+ return s.Type == unix.PROC_SUPER_MAGIC, nil
|
|
}
|
|
|
|
func setupDevSymlinks(rootfs string) error {
|
|
diff --git a/libcontainer/rootfs_linux_test.go b/libcontainer/rootfs_linux_test.go
|
|
index d755984bc0f9..1bfe7c663225 100644
|
|
--- a/libcontainer/rootfs_linux_test.go
|
|
+++ b/libcontainer/rootfs_linux_test.go
|
|
@@ -10,7 +10,7 @@ import (
|
|
|
|
func TestCheckMountDestOnProc(t *testing.T) {
|
|
dest := "/rootfs/proc/sys"
|
|
- err := checkMountDestination("/rootfs", dest)
|
|
+ err := checkProcMount("/rootfs", dest, "")
|
|
if err == nil {
|
|
t.Fatal("destination inside proc should return an error")
|
|
}
|
|
@@ -18,7 +18,7 @@ func TestCheckMountDestOnProc(t *testing.T) {
|
|
|
|
func TestCheckMountDestOnProcChroot(t *testing.T) {
|
|
dest := "/rootfs/proc/"
|
|
- err := checkMountDestination("/rootfs", dest)
|
|
+ err := checkProcMount("/rootfs", dest, "/proc")
|
|
if err != nil {
|
|
t.Fatal("destination inside proc when using chroot should not return an error")
|
|
}
|
|
@@ -26,7 +26,7 @@ func TestCheckMountDestOnProcChroot(t *testing.T) {
|
|
|
|
func TestCheckMountDestInSys(t *testing.T) {
|
|
dest := "/rootfs//sys/fs/cgroup"
|
|
- err := checkMountDestination("/rootfs", dest)
|
|
+ err := checkProcMount("/rootfs", dest, "")
|
|
if err != nil {
|
|
t.Fatal("destination inside /sys should not return an error")
|
|
}
|
|
@@ -34,7 +34,7 @@ func TestCheckMountDestInSys(t *testing.T) {
|
|
|
|
func TestCheckMountDestFalsePositive(t *testing.T) {
|
|
dest := "/rootfs/sysfiles/fs/cgroup"
|
|
- err := checkMountDestination("/rootfs", dest)
|
|
+ err := checkProcMount("/rootfs", dest, "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
diff --git a/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go b/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go
|
|
index d7786c33c197..611df8f9b3b0 100644
|
|
--- a/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go
|
|
+++ b/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go
|
|
@@ -18,6 +18,8 @@ import (
|
|
"strings"
|
|
"sync"
|
|
"syscall"
|
|
+
|
|
+ "golang.org/x/sys/unix"
|
|
)
|
|
|
|
const (
|
|
@@ -252,6 +254,12 @@ func getSELinuxPolicyRoot() string {
|
|
return filepath.Join(selinuxDir, readConfig(selinuxTypeTag))
|
|
}
|
|
|
|
+func isProcHandle(fh *os.File) (bool, error) {
|
|
+ var buf unix.Statfs_t
|
|
+ err := unix.Fstatfs(int(fh.Fd()), &buf)
|
|
+ return buf.Type == unix.PROC_SUPER_MAGIC, err
|
|
+}
|
|
+
|
|
func readCon(fpath string) (string, error) {
|
|
if fpath == "" {
|
|
return "", ErrEmptyPath
|
|
@@ -263,6 +271,12 @@ func readCon(fpath string) (string, error) {
|
|
}
|
|
defer in.Close()
|
|
|
|
+ if ok, err := isProcHandle(in); err != nil {
|
|
+ return "", err
|
|
+ } else if !ok {
|
|
+ return "", fmt.Errorf("/proc path not on procfs: %s", fpath)
|
|
+ }
|
|
+
|
|
var retval string
|
|
if _, err := fmt.Fscanf(in, "%s", &retval); err != nil {
|
|
return "", err
|
|
@@ -345,6 +359,12 @@ func writeCon(fpath string, val string) error {
|
|
}
|
|
defer out.Close()
|
|
|
|
+ if ok, err := isProcHandle(out); err != nil {
|
|
+ return err
|
|
+ } else if !ok {
|
|
+ return fmt.Errorf("/proc path not on procfs: %s", fpath)
|
|
+ }
|
|
+
|
|
if val != "" {
|
|
_, err = out.Write([]byte(val))
|
|
} else {
|
|
--
|
|
2.23.0
|
|
|