runc: fix CVE-2024-45310

Signed-off-by: Song Zhang <zhangsong34@huawei.com>
(cherry picked from commit ab4cc0729c31453147018b290e97d51db51f3c13)
This commit is contained in:
Song Zhang 2024-09-10 15:54:14 +08:00 committed by openeuler-sync-bot
parent 4172c93f2e
commit db968d3370
5 changed files with 710 additions and 2 deletions

View File

@ -1 +1 @@
b5df7029488e0b42b65b5df8e23c7bd9e8884099 1251c89d252bb9f8136d47c5892497829e78683f

View File

@ -0,0 +1,344 @@
From 161ddbfb05c69e010d50d788533a19d51607b937 Mon Sep 17 00:00:00 2001
From: Aleksa Sarai <cyphar@cyphar.com>
Date: Mon, 1 Jul 2024 15:12:01 +1000
Subject: [PATCH 1/2] rootfs: consolidate mountpoint creation logic
The logic for how we create mountpoints is spread over each mountpoint
preparation function, when in reality the behaviour is pretty uniform
with only a handful of exceptions. So just move it all to one function
that is easier to understand.
Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
---
libcontainer/container_linux.go | 28 ++----
libcontainer/rootfs_linux.go | 160 ++++++++++++++-----------------
libcontainer/utils/utils_unix.go | 15 +++
3 files changed, 94 insertions(+), 109 deletions(-)
diff --git a/libcontainer/container_linux.go b/libcontainer/container_linux.go
index 5086d50..e8b00c8 100644
--- a/libcontainer/container_linux.go
+++ b/libcontainer/container_linux.go
@@ -1278,8 +1278,7 @@ func (c *linuxContainer) restoreNetwork(req *criurpc.CriuReq, criuOpts *CriuOpts
// restore using CRIU. This function is inspired from the code in
// rootfs_linux.go
func (c *linuxContainer) makeCriuRestoreMountpoints(m *configs.Mount) error {
- switch m.Device {
- case "cgroup":
+ if m.Device == "cgroup" {
// No mount point(s) need to be created:
//
// * for v1, mount points are saved by CRIU because
@@ -1288,26 +1287,11 @@ func (c *linuxContainer) makeCriuRestoreMountpoints(m *configs.Mount) error {
// * for v2, /sys/fs/cgroup is a real mount, but
// the mountpoint appears as soon as /sys is mounted
return nil
- case "bind":
- // The prepareBindMount() function checks if source
- // exists. So it cannot be used for other filesystem types.
- // TODO: pass something else than nil? Not sure if criu is
- // impacted by issue #2484
- if err := prepareBindMount(m, c.config.Rootfs, nil); err != nil {
- return err
- }
- default:
- // for all other filesystems just create the mountpoints
- dest, err := securejoin.SecureJoin(c.config.Rootfs, m.Destination)
- if err != nil {
- return err
- }
- if err := checkProcMount(c.config.Rootfs, dest, ""); err != nil {
- return err
- }
- if err := os.MkdirAll(dest, 0o755); err != nil {
- return err
- }
+ }
+ // TODO: pass something else than nil? Not sure if criu is
+ // impacted by issue #2484
+ if _, err := createMountpoint(c.config.Rootfs, m, nil, ""); err != nil {
+ return fmt.Errorf("create criu restore mount for %s mount: %w", m.Destination, err)
}
return nil
}
diff --git a/libcontainer/rootfs_linux.go b/libcontainer/rootfs_linux.go
index 499d753..ea554d3 100644
--- a/libcontainer/rootfs_linux.go
+++ b/libcontainer/rootfs_linux.go
@@ -225,36 +225,6 @@ func mountCmd(cmd configs.Command) error {
return nil
}
-func prepareBindMount(m *configs.Mount, rootfs string, mountFd *int) error {
- source := m.Source
- if mountFd != nil {
- source = "/proc/self/fd/" + strconv.Itoa(*mountFd)
- }
-
- stat, err := os.Stat(source)
- if err != nil {
- // error out if the source of a bind mount does not exist as we will be
- // unable to bind anything to it.
- return err
- }
- // ensure that the destination of the bind mount is resolved of symlinks at mount time because
- // any previous mounts can invalidate the next mount's destination.
- // this can happen when a user specifies mounts within other mounts to cause breakouts or other
- // evil stuff to try to escape the container's rootfs.
- var dest string
- if dest, err = securejoin.SecureJoin(rootfs, m.Destination); err != nil {
- return err
- }
- if err := checkProcMount(rootfs, dest, source); err != nil {
- return err
- }
- if err := createIfNotExists(dest, stat.IsDir()); err != nil {
- return err
- }
-
- return nil
-}
-
func mountCgroupV1(m *configs.Mount, c *mountConfig) error {
binds, err := getCgroupMounts(m)
if err != nil {
@@ -283,6 +253,7 @@ func mountCgroupV1(m *configs.Mount, c *mountConfig) error {
for _, b := range binds {
if c.cgroupns {
subsystemPath := filepath.Join(c.root, b.Destination)
+ subsystemName := filepath.Base(b.Destination)
if err := os.MkdirAll(subsystemPath, 0o755); err != nil {
return err
}
@@ -293,7 +264,7 @@ func mountCgroupV1(m *configs.Mount, c *mountConfig) error {
}
var (
source = "cgroup"
- data = filepath.Base(subsystemPath)
+ data = subsystemName
)
if data == "systemd" {
data = cgroups.CgroupNamePrefix + data
@@ -323,14 +294,7 @@ func mountCgroupV1(m *configs.Mount, c *mountConfig) error {
}
func mountCgroupV2(m *configs.Mount, c *mountConfig) error {
- dest, err := securejoin.SecureJoin(c.root, m.Destination)
- if err != nil {
- return err
- }
- if err := os.MkdirAll(dest, 0o755); err != nil {
- return err
- }
- err = utils.WithProcfd(c.root, m.Destination, func(procfd string) error {
+ err := utils.WithProcfd(c.root, m.Destination, func(procfd string) error {
return mount(m.Source, m.Destination, procfd, "cgroup2", uintptr(m.Flags), m.Data)
})
if err == nil || !(errors.Is(err, unix.EPERM) || errors.Is(err, unix.EBUSY)) {
@@ -412,6 +376,70 @@ func doTmpfsCopyUp(m *configs.Mount, rootfs, mountLabel string) (Err error) {
})
}
+var errRootfsToFile = errors.New("config tries to change rootfs to file")
+
+func createMountpoint(rootfs string, m *configs.Mount, mountFd *int, source string) (string, error) {
+ dest, err := securejoin.SecureJoin(rootfs, m.Destination)
+ if err != nil {
+ return "", err
+ }
+ if err := checkProcMount(rootfs, dest, m, source); err != nil {
+ return "", fmt.Errorf("check proc-safety of %s mount: %w", m.Destination, err)
+ }
+
+ switch m.Device {
+ case "bind":
+ source := m.Source
+ if mountFd != nil {
+ source = "/proc/self/fd/" + strconv.Itoa(*mountFd)
+ }
+
+ fi, err := os.Stat(source)
+ if err != nil {
+ // Error out if the source of a bind mount does not exist as we
+ // will be unable to bind anything to it.
+ return "", fmt.Errorf("bind mount source stat: %w", err)
+ }
+ // If the original source is not a directory, make the target a file.
+ if !fi.IsDir() {
+ // Make sure we aren't tricked into trying to make the root a file.
+ if rootfs == dest {
+ return "", fmt.Errorf("%w: file bind mount over rootfs", errRootfsToFile)
+ }
+ // Make the parent directory.
+ if err := os.MkdirAll(filepath.Dir(dest), 0o755); err != nil {
+ return "", fmt.Errorf("make parent dir of file bind-mount: %w", err)
+ }
+ // Make the target file.
+ f, err := os.OpenFile(dest, os.O_CREATE, 0o755)
+ if err != nil {
+ return "", fmt.Errorf("create target of file bind-mount: %w", err)
+ }
+ _ = f.Close()
+ // Nothing left to do.
+ return dest, nil
+ }
+
+ case "tmpfs":
+ // If the original target exists, copy the mode for the tmpfs mount.
+ if stat, err := os.Stat(dest); err == nil {
+ dt := fmt.Sprintf("mode=%04o", syscallMode(stat.Mode()))
+ if m.Data != "" {
+ dt = dt + "," + m.Data
+ }
+ m.Data = dt
+
+ // Nothing left to do.
+ return dest, nil
+ }
+ }
+
+ if err := os.MkdirAll(dest, 0o755); err != nil {
+ return "", err
+ }
+ return dest, nil
+}
+
func mountToRootfs(m *configs.Mount, c *mountConfig) error {
rootfs := c.root
@@ -446,46 +474,27 @@ func mountToRootfs(m *configs.Mount, c *mountConfig) error {
return mountPropagate(m, rootfs, "", nil)
}
- mountLabel := c.label
mountFd := c.fd
- dest, err := securejoin.SecureJoin(rootfs, m.Destination)
+ dest, err := createMountpoint(rootfs, m, mountFd, m.Source)
if err != nil {
- return err
+ return fmt.Errorf("create mount destination for %s mount: %w", m.Destination, err)
}
+ mountLabel := c.label
switch m.Device {
case "mqueue":
- if err := os.MkdirAll(dest, 0o755); err != nil {
- return err
- }
if err := mountPropagate(m, rootfs, "", nil); err != nil {
return err
}
return label.SetFileLabel(dest, mountLabel)
case "tmpfs":
- if stat, err := os.Stat(dest); err != nil {
- if err := os.MkdirAll(dest, 0o755); err != nil {
- return err
- }
- } else {
- dt := fmt.Sprintf("mode=%04o", stat.Mode())
- if m.Data != "" {
- dt = dt + "," + m.Data
- }
- m.Data = dt
- }
-
if m.Extensions&configs.EXT_COPYUP == configs.EXT_COPYUP {
err = doTmpfsCopyUp(m, rootfs, mountLabel)
} else {
err = mountPropagate(m, rootfs, mountLabel, nil)
}
-
return err
case "bind":
- if err := prepareBindMount(m, rootfs, mountFd); err != nil {
- return err
- }
if err := mountPropagate(m, rootfs, mountLabel, mountFd); err != nil {
return err
}
@@ -513,12 +522,6 @@ func mountToRootfs(m *configs.Mount, c *mountConfig) error {
}
return mountCgroupV1(m, c)
default:
- if err := checkProcMount(rootfs, dest, m.Source); err != nil {
- return err
- }
- if err := os.MkdirAll(dest, 0o755); err != nil {
- return err
- }
return mountPropagate(m, rootfs, mountLabel, mountFd)
}
if err := setRecAttr(m, rootfs); err != nil {
@@ -729,6 +732,9 @@ func createDeviceNode(rootfs string, node *devices.Device, bind bool) error {
if err != nil {
return err
}
+ if dest == rootfs {
+ return fmt.Errorf("%w: mknod over rootfs", errRootfsToFile)
+ }
if err := os.MkdirAll(filepath.Dir(dest), 0o755); err != nil {
return err
}
@@ -995,26 +1001,6 @@ func chroot() error {
return nil
}
-// createIfNotExists creates a file or a directory only if it does not already exist.
-func createIfNotExists(path string, isDir bool) error {
- if _, err := os.Stat(path); err != nil {
- if os.IsNotExist(err) {
- if isDir {
- return os.MkdirAll(path, 0o755)
- }
- if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
- return err
- }
- f, err := os.OpenFile(path, os.O_CREATE, 0o755)
- if err != nil {
- return err
- }
- _ = f.Close()
- }
- }
- return nil
-}
-
// readonlyPath will make a path read only.
func readonlyPath(path string) error {
if err := mount(path, path, "", "", unix.MS_BIND|unix.MS_REC, ""); err != nil {
diff --git a/libcontainer/utils/utils_unix.go b/libcontainer/utils/utils_unix.go
index f57f087..6fe0096 100644
--- a/libcontainer/utils/utils_unix.go
+++ b/libcontainer/utils/utils_unix.go
@@ -10,6 +10,7 @@ import (
"path/filepath"
"runtime"
"strconv"
+ "strings"
"sync"
_ "unsafe" // for go:linkname
@@ -156,6 +157,20 @@ func NewSockPair(name string) (parent, child *os.File, err error) {
return os.NewFile(uintptr(fds[1]), name+"-p"), os.NewFile(uintptr(fds[0]), name+"-c"), nil
}
+// IsLexicallyInRoot is shorthand for strings.HasPrefix(path+"/", root+"/"),
+// but properly handling the case where path or root are "/".
+//
+// NOTE: The return value only make sense if the path doesn't contain "..".
+func IsLexicallyInRoot(root, path string) bool {
+ if root != "/" {
+ root += "/"
+ }
+ if path != "/" {
+ path += "/"
+ }
+ return strings.HasPrefix(path, root)
+}
+
// WithProcfd runs the passed closure with a procfd path (/proc/self/fd/...)
// corresponding to the unsafePath resolved within the root. Before passing the
// fd, this path is verified to have been inside the root -- so operating on it
--
2.33.0

View File

@ -0,0 +1,356 @@
From ef3e5b4ea8f0b2eaac05df8b16f6656aebf05998 Mon Sep 17 00:00:00 2001
From: Aleksa Sarai <cyphar@cyphar.com>
Date: Tue, 2 Jul 2024 20:58:43 +1000
Subject: [PATCH 2/2] rootfs: try to scope MkdirAll to stay inside the rootfs
While we use SecureJoin to try to make all of our target paths inside
the container safe, SecureJoin is not safe against an attacker than can
change the path after we "resolve" it.
os.MkdirAll can inadvertently follow symlinks and thus an attacker could
end up tricking runc into creating empty directories on the host (note
that the container doesn't get access to these directories, and the host
just sees empty directories). However, this could potentially cause DoS
issues by (for instance) creating a directory in a conf.d directory for
a daemon that doesn't handle subdirectories properly.
In addition, the handling for creating file bind-mounts did a plain
open(O_CREAT) on the SecureJoin'd path, which is even more obviously
unsafe (luckily we didn't use O_TRUNC, or this bug could've allowed an
attacker to cause data loss...). Regardless of the symlink issue,
opening an untrusted file could result in a DoS if the file is a hung
tty or some other "nasty" file. We can use mknodat to safely create a
regular file without opening anything anyway (O_CREAT|O_EXCL would also
work but it makes the logic a bit more complicated, and we don't want to
open the file for any particular reason anyway).
libpathrs[1] is the long-term solution for these kinds of problems, but
for now we can patch this particular issue by creating a more restricted
MkdirAll that refuses to resolve symlinks and does the creation using
file descriptors. This is loosely based on a more secure version that
filepath-securejoin now has[2] and will be added to libpathrs soon[3].
[1]: https://github.com/openSUSE/libpathrs
[2]: https://github.com/cyphar/filepath-securejoin/releases/tag/v0.3.0
[3]: https://github.com/openSUSE/libpathrs/issues/10
Fixes: CVE-2024-45310
Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
---
libcontainer/mount_linux.go | 18 +++++
libcontainer/rootfs_linux.go | 33 ++++++---
libcontainer/system/linux.go | 41 +++++++++++
libcontainer/utils/utils_unix.go | 112 +++++++++++++++++++++++++++++++
4 files changed, 193 insertions(+), 11 deletions(-)
diff --git a/libcontainer/mount_linux.go b/libcontainer/mount_linux.go
index 5f49de9..948b6c0 100644
--- a/libcontainer/mount_linux.go
+++ b/libcontainer/mount_linux.go
@@ -1,6 +1,7 @@
package libcontainer
import (
+ "io/fs"
"strconv"
"golang.org/x/sys/unix"
@@ -81,3 +82,20 @@ func unmount(target string, flags int) error {
}
return nil
}
+
+// syscallMode returns the syscall-specific mode bits from Go's portable mode bits.
+// Copy from https://cs.opensource.google/go/go/+/refs/tags/go1.20.7:src/os/file_posix.go;l=61-75
+func syscallMode(i fs.FileMode) (o uint32) {
+ o |= uint32(i.Perm())
+ if i&fs.ModeSetuid != 0 {
+ o |= unix.S_ISUID
+ }
+ if i&fs.ModeSetgid != 0 {
+ o |= unix.S_ISGID
+ }
+ if i&fs.ModeSticky != 0 {
+ o |= unix.S_ISVTX
+ }
+ // No mapping for Go's ModeTemporary (plan9 only).
+ return
+}
diff --git a/libcontainer/rootfs_linux.go b/libcontainer/rootfs_linux.go
index ea554d3..4678c76 100644
--- a/libcontainer/rootfs_linux.go
+++ b/libcontainer/rootfs_linux.go
@@ -254,7 +254,7 @@ func mountCgroupV1(m *configs.Mount, c *mountConfig) error {
if c.cgroupns {
subsystemPath := filepath.Join(c.root, b.Destination)
subsystemName := filepath.Base(b.Destination)
- if err := os.MkdirAll(subsystemPath, 0o755); err != nil {
+ if err := utils.MkdirAllInRoot(c.root, subsystemPath, 0o755); err != nil {
return err
}
if err := utils.WithProcfd(c.root, b.Destination, func(procfd string) error {
@@ -383,7 +383,7 @@ func createMountpoint(rootfs string, m *configs.Mount, mountFd *int, source stri
if err != nil {
return "", err
}
- if err := checkProcMount(rootfs, dest, m, source); err != nil {
+ if err := checkProcMount(rootfs, dest, source); err != nil {
return "", fmt.Errorf("check proc-safety of %s mount: %w", m.Destination, err)
}
@@ -407,15 +407,26 @@ func createMountpoint(rootfs string, m *configs.Mount, mountFd *int, source stri
return "", fmt.Errorf("%w: file bind mount over rootfs", errRootfsToFile)
}
// Make the parent directory.
- if err := os.MkdirAll(filepath.Dir(dest), 0o755); err != nil {
+ destDir, destBase := filepath.Split(dest)
+ destDirFd, err := utils.MkdirAllInRootOpen(rootfs, destDir, 0o755)
+ if err != nil {
return "", fmt.Errorf("make parent dir of file bind-mount: %w", err)
}
- // Make the target file.
- f, err := os.OpenFile(dest, os.O_CREATE, 0o755)
- if err != nil {
- return "", fmt.Errorf("create target of file bind-mount: %w", err)
+ defer destDirFd.Close()
+ // Make the target file. We want to avoid opening any file that is
+ // already there because it could be a "bad" file like an invalid
+ // device or hung tty that might cause a DoS, so we use mknodat.
+ // destBase does not contain any "/" components, and mknodat does
+ // not follow trailing symlinks, so we can safely just call mknodat
+ // here.
+ if err := unix.Mknodat(int(destDirFd.Fd()), destBase, unix.S_IFREG|0o644, 0); err != nil {
+ // If we get EEXIST, there was already an inode there and
+ // we can consider that a success.
+ if !errors.Is(err, unix.EEXIST) {
+ err = &os.PathError{Op: "mknod regular file", Path: dest, Err: err}
+ return "", fmt.Errorf("create target of file bind-mount: %w", err)
+ }
}
- _ = f.Close()
// Nothing left to do.
return dest, nil
}
@@ -434,7 +445,7 @@ func createMountpoint(rootfs string, m *configs.Mount, mountFd *int, source stri
}
}
- if err := os.MkdirAll(dest, 0o755); err != nil {
+ if err := utils.MkdirAllInRoot(rootfs, dest, 0o755); err != nil {
return "", err
}
return dest, nil
@@ -467,7 +478,7 @@ func mountToRootfs(m *configs.Mount, c *mountConfig) error {
if strings.HasPrefix(m.Destination, "/proc/sys/") {
return nil
}
- if err := os.MkdirAll(dest, 0o755); err != nil {
+ if err := utils.MkdirAllInRoot(rootfs, dest, 0o755); err != nil {
return err
}
// Selinux kernels do not support labeling of /proc or /sys.
@@ -735,7 +746,7 @@ func createDeviceNode(rootfs string, node *devices.Device, bind bool) error {
if dest == rootfs {
return fmt.Errorf("%w: mknod over rootfs", errRootfsToFile)
}
- if err := os.MkdirAll(filepath.Dir(dest), 0o755); err != nil {
+ if err := utils.MkdirAllInRoot(rootfs, filepath.Dir(dest), 0o755); err != nil {
return err
}
if bind {
diff --git a/libcontainer/system/linux.go b/libcontainer/system/linux.go
index e1d6eb1..0f97045 100644
--- a/libcontainer/system/linux.go
+++ b/libcontainer/system/linux.go
@@ -6,6 +6,8 @@ package system
import (
"os"
"os/exec"
+ "runtime"
+ "strings"
"unsafe"
"golang.org/x/sys/unix"
@@ -102,3 +104,42 @@ func GetSubreaper() (int, error) {
return int(i), nil
}
+
+func prepareAt(dir *os.File, path string) (int, string) {
+ if dir == nil {
+ return unix.AT_FDCWD, path
+ }
+
+ // Rather than just filepath.Join-ing path here, do it manually so the
+ // error and handle correctly indicate cases like path=".." as being
+ // relative to the correct directory. The handle.Name() might end up being
+ // wrong but because this is (currently) only used in MkdirAllInRoot, that
+ // isn't a problem.
+ dirName := dir.Name()
+ if !strings.HasSuffix(dirName, "/") {
+ dirName += "/"
+ }
+ fullPath := dirName + path
+
+ return int(dir.Fd()), fullPath
+}
+
+func Openat(dir *os.File, path string, flags int, mode uint32) (*os.File, error) {
+ dirFd, fullPath := prepareAt(dir, path)
+ fd, err := unix.Openat(dirFd, path, flags, mode)
+ if err != nil {
+ return nil, &os.PathError{Op: "openat", Path: fullPath, Err: err}
+ }
+ runtime.KeepAlive(dir)
+ return os.NewFile(uintptr(fd), fullPath), nil
+}
+
+func Mkdirat(dir *os.File, path string, mode uint32) error {
+ dirFd, fullPath := prepareAt(dir, path)
+ err := unix.Mkdirat(dirFd, path, mode)
+ if err != nil {
+ err = &os.PathError{Op: "mkdirat", Path: fullPath, Err: err}
+ }
+ runtime.KeepAlive(dir)
+ return err
+}
diff --git a/libcontainer/utils/utils_unix.go b/libcontainer/utils/utils_unix.go
index 6fe0096..66d12e5 100644
--- a/libcontainer/utils/utils_unix.go
+++ b/libcontainer/utils/utils_unix.go
@@ -4,6 +4,7 @@
package utils
import (
+ "errors"
"fmt"
"math"
"os"
@@ -14,6 +15,8 @@ import (
"sync"
_ "unsafe" // for go:linkname
+ "github.com/opencontainers/runc/libcontainer/system"
+
securejoin "github.com/cyphar/filepath-securejoin"
"github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
@@ -171,6 +174,115 @@ func IsLexicallyInRoot(root, path string) bool {
return strings.HasPrefix(path, root)
}
+// MkdirAllInRootOpen attempts to make
+//
+// path, _ := securejoin.SecureJoin(root, unsafePath)
+// os.MkdirAll(path, mode)
+// os.Open(path)
+//
+// safer against attacks where components in the path are changed between
+// SecureJoin returning and MkdirAll (or Open) being called. In particular, we
+// try to detect any symlink components in the path while we are doing the
+// MkdirAll.
+//
+// NOTE: Unlike os.MkdirAll, mode is not Go's os.FileMode, it is the unix mode
+// (the suid/sgid/sticky bits are not the same as for os.FileMode).
+//
+// NOTE: If unsafePath is a subpath of root, we assume that you have already
+// called SecureJoin and so we use the provided path verbatim without resolving
+// any symlinks (this is done in a way that avoids symlink-exchange races).
+// This means that the path also must not contain ".." elements, otherwise an
+// error will occur.
+//
+// This is a somewhat less safe alternative to
+// <https://github.com/cyphar/filepath-securejoin/pull/13>, but it should
+// detect attempts to trick us into creating directories outside of the root.
+// We should migrate to securejoin.MkdirAll once it is merged.
+func MkdirAllInRootOpen(root, unsafePath string, mode uint32) (_ *os.File, Err error) {
+ // If the path is already "within" the root, use it verbatim.
+ fullPath := unsafePath
+ if !IsLexicallyInRoot(root, unsafePath) {
+ var err error
+ fullPath, err = securejoin.SecureJoin(root, unsafePath)
+ if err != nil {
+ return nil, err
+ }
+ }
+ subPath, err := filepath.Rel(root, fullPath)
+ if err != nil {
+ return nil, err
+ }
+
+ // Check for any silly mode bits.
+ if mode&^0o7777 != 0 {
+ return nil, fmt.Errorf("tried to include non-mode bits in MkdirAll mode: 0o%.3o", mode)
+ }
+
+ currentDir, err := os.OpenFile(root, unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
+ if err != nil {
+ return nil, fmt.Errorf("open root handle: %w", err)
+ }
+ defer func() {
+ if Err != nil {
+ currentDir.Close()
+ }
+ }()
+
+ for _, part := range strings.Split(subPath, string(filepath.Separator)) {
+ switch part {
+ case "", ".":
+ // Skip over no-op components.
+ continue
+ case "..":
+ return nil, fmt.Errorf("possible breakout detected: found %q component in SecureJoin subpath %s", part, subPath)
+ }
+
+ nextDir, err := system.Openat(currentDir, part, unix.O_DIRECTORY|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
+ switch {
+ case err == nil:
+ // Update the currentDir.
+ _ = currentDir.Close()
+ currentDir = nextDir
+
+ case errors.Is(err, unix.ENOTDIR):
+ // This might be a symlink or some other random file. Either way,
+ // error out.
+ return nil, fmt.Errorf("cannot mkdir in %s/%s: %w", currentDir.Name(), part, unix.ENOTDIR)
+
+ case errors.Is(err, os.ErrNotExist):
+ // Luckily, mkdirat will not follow trailing symlinks, so this is
+ // safe to do as-is.
+ if err := system.Mkdirat(currentDir, part, mode); err != nil {
+ return nil, err
+ }
+ // Open the new directory. There is a race here where an attacker
+ // could swap the directory with a different directory, but
+ // MkdirAll's fuzzy semantics mean we don't care about that.
+ nextDir, err := system.Openat(currentDir, part, unix.O_DIRECTORY|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
+ if err != nil {
+ return nil, fmt.Errorf("open newly created directory: %w", err)
+ }
+ // Update the currentDir.
+ _ = currentDir.Close()
+ currentDir = nextDir
+
+ default:
+ return nil, err
+ }
+ }
+ return currentDir, nil
+}
+
+// MkdirAllInRoot is a wrapper around MkdirAllInRootOpen which closes the
+// returned handle, for callers that don't need to use it.
+func MkdirAllInRoot(root, unsafePath string, mode uint32) error {
+ f, err := MkdirAllInRootOpen(root, unsafePath, mode)
+ if err == nil {
+ _ = f.Close()
+ }
+ return err
+}
+
// WithProcfd runs the passed closure with a procfd path (/proc/self/fd/...)
// corresponding to the unsafePath resolved within the root. Before passing the
// fd, this path is verified to have been inside the root -- so operating on it
--
2.33.0

View File

@ -3,7 +3,7 @@
Name: runc Name: runc
Version: 1.1.8 Version: 1.1.8
Release: 20 Release: 21
Summary: runc is a CLI tool for spawning and running containers according to the OCI specification. Summary: runc is a CLI tool for spawning and running containers according to the OCI specification.
License: ASL 2.0 License: ASL 2.0
@ -57,6 +57,12 @@ install -p -m 755 runc $RPM_BUILD_ROOT/%{_bindir}/runc
%{_bindir}/runc %{_bindir}/runc
%changelog %changelog
* Tue Sep 10 2024 Song Zhang<zhangsong34@huawei.com> - 1.1.8-21
- Type:CVE
- CVE:CVE-2024-45310
- SUG:NA
- DESC:fix CVE-2024-45310
* Fri Aug 30 2024 zhongjiawei<zhongjiawei1@huawei.com> - 1.1.8-20 * Fri Aug 30 2024 zhongjiawei<zhongjiawei1@huawei.com> - 1.1.8-20
- Type:bugfix - Type:bugfix
- CVE:NA - CVE:NA

View File

@ -41,3 +41,5 @@ patch/0041-runc-Set-temporary-single-CPU-affinity-before-cgroup-cpus.patch
patch/0042-runc-fix-a-data-race.patch patch/0042-runc-fix-a-data-race.patch
patch/0043-runc-do-not-support-set-umask-through-native.umask.patch patch/0043-runc-do-not-support-set-umask-through-native.umask.patch
patch/0044-runc-format-log-instead-panic-when-procError-missing.patch patch/0044-runc-format-log-instead-panic-when-procError-missing.patch
patch/0045-rootfs-consolidate-mountpoint-creation-logic.patch
patch/0046-rootfs-try-to-scope-MkdirAll-to-stay-inside-the-root.patch