From cfbeae219224f99be7c05c4a0e248a9f6931f941 Mon Sep 17 00:00:00 2001
From: Kir Kolyshkin <kolyshkin@gmail.com>
Date: Thu, 16 Mar 2023 14:35:50 -0700
Subject: [PATCH] Prohibit /proc and /sys to be symlinks

Commit 3291d66b9844 introduced a check for /proc and /sys, making sure
the destination (dest) is a directory (and not e.g. a symlink).

Later, a hunk from commit 0ca91f44f switched from using filepath.Join
to SecureJoin for dest. As SecureJoin follows and resolves symlinks,
the check whether dest is a symlink no longer works.

To fix, do the check without/before using SecureJoin.

Add integration tests to make sure we won't regress.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
(cherry picked from commit 0d72adf96dda1b687815bf89bb245b937a2f603c)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
---
 libcontainer/rootfs_linux.go | 33 ++++++++++++++++++++-------------
 tests/integration/mask.bats  | 19 +++++++++++++++++++
 2 files changed, 39 insertions(+), 13 deletions(-)

diff --git a/libcontainer/rootfs_linux.go b/libcontainer/rootfs_linux.go
index dc66d8a9..855bcdb0 100644
--- a/libcontainer/rootfs_linux.go
+++ b/libcontainer/rootfs_linux.go
@@ -160,29 +160,25 @@ func mountCmd(cmd configs.Command) error {
 }
 
 func mountToRootfs(m *configs.Mount, rootfs, mountLabel string) error {
-	var (
-		dest = m.Destination
-		err  error
-	)
-	if !strings.HasPrefix(dest, rootfs) {
-		dest, err = securejoin.SecureJoin(rootfs, m.Destination)
-		if err != nil {
-			return err
-		}
-	}
+	var err  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
+		// 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.
+		dest := filepath.Clean(m.Destination)
+		if !strings.HasPrefix(dest, rootfs) {
+			// Do not use securejoin as it resolves symlinks.
+			dest = filepath.Join(rootfs, dest)
+		}
 		if fi, err := os.Lstat(dest); err != nil {
 			if !os.IsNotExist(err) {
 				return err
 			}
-		} else if fi.Mode()&os.ModeDir == 0 {
+		} else if !fi.IsDir() {
 			return fmt.Errorf("filesystem %q must be mounted on ordinary directory", m.Device)
 		}
 		if strings.HasPrefix(m.Destination, "/proc/sys/") {
@@ -191,8 +187,19 @@ func mountToRootfs(m *configs.Mount, rootfs, mountLabel string) error {
 		if err := os.MkdirAll(dest, 0755); err != nil {
 			return err
 		}
-		// Selinux kernels do not support labeling of /proc or /sys
+		// Selinux kernels do not support labeling of /proc or /sys.
 		return mountPropagate(m, rootfs, "")
+	}
+
+	var dest = m.Destination
+        if !strings.HasPrefix(dest, rootfs) {
+                dest, err = securejoin.SecureJoin(rootfs, m.Destination)
+                if err != nil {
+                        return err
+                }
+        }
+
+	switch m.Device {
 	case "mqueue":
 		if err := os.MkdirAll(dest, 0755); err != nil {
 			return err
diff --git a/tests/integration/mask.bats b/tests/integration/mask.bats
index 074b0f2e..64d53954 100644
--- a/tests/integration/mask.bats
+++ b/tests/integration/mask.bats
@@ -61,3 +61,22 @@ function teardown() {
 	[ "$status" -eq 1 ]
 	[[ "${output}" == *"Operation not permitted"* ]]
 }
+
+@test "mask paths [prohibit symlink /proc]" {
+	ln -s /symlink rootfs/proc
+	runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
+	[ "$status" -eq 1 ]
+	[[ "${output}" == *"must be mounted on ordinary directory"* ]]
+}
+
+@test "mask paths [prohibit symlink /sys]" {
+	# In rootless containers, /sys is a bind mount not a real sysfs.
+	requires root
+
+	ln -s /symlink rootfs/sys
+	runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
+	[ "$status" -eq 1 ]
+	# On cgroup v1, this may fail before checking if /sys is a symlink,
+	# so we merely check that it fails, and do not check the exact error
+	# message like for /proc above.
+}
-- 
2.33.0

