diff --git a/git-commit b/git-commit index 44f1acf12054eb725aeadea01839fa27036b8e64..6ad7fe488b63dd9440e92001a187a687b9cef677 100644 --- a/git-commit +++ b/git-commit @@ -1 +1 @@ -cb487aade572b4cb5aa3d601fb8184b080f1ef83 +c55fe84414b82992aed5f5f093144051663080b8 diff --git a/patch/0062-runc-fix-CVE-2025-31133.patch b/patch/0062-runc-fix-CVE-2025-31133.patch new file mode 100644 index 0000000000000000000000000000000000000000..8f047ef6b1305e597c3d2ed39c9c926195dedf8e --- /dev/null +++ b/patch/0062-runc-fix-CVE-2025-31133.patch @@ -0,0 +1,253 @@ +From 8476df83b534a2522b878c0507b3491def48db9f Mon Sep 17 00:00:00 2001 +From: Kir Kolyshkin +Date: Thu, 6 Mar 2025 08:19:45 -0800 +Subject: [PATCH] libct: add/use isDevNull, verifyDevNull + +The /dev/null in a container should not be trusted, because when /dev +is a bind mount, /dev/null is not created by runc itself. + +1. Add isDevNull which checks the fd minor/major and device type, + and verifyDevNull which does the stat and the check. + +2. Rewrite maskPath to open and check /dev/null, and use its fd to + perform mounts. Move the loop over the MaskPaths into the function, + and rename it to maskPaths. + +3. reOpenDevNull: use verifyDevNull and isDevNull. + +4. fixStdioPermissions: use isDevNull instead of stat. + +Fixes: GHSA-9493-h29p-rfm2 CVE-2025-31133 +Co-authored-by: Rodrigo Campos +Signed-off-by: Kir Kolyshkin +Signed-off-by: Aleksa Sarai +--- + internal/sys/doc.go | 5 ++ + internal/sys/verify_inode_unix.go | 30 ++++++++++++ + libcontainer/init_linux.go | 11 ++--- + libcontainer/rootfs_linux.go | 76 ++++++++++++++++++++++++----- + libcontainer/standard_init_linux.go | 7 ++- + 5 files changed, 107 insertions(+), 22 deletions(-) + create mode 100644 internal/sys/doc.go + create mode 100644 internal/sys/verify_inode_unix.go + +diff --git a/internal/sys/doc.go b/internal/sys/doc.go +new file mode 100644 +index 0000000..075387f +--- /dev/null ++++ b/internal/sys/doc.go +@@ -0,0 +1,5 @@ ++// Package sys is an internal package that contains helper methods for dealing ++// with Linux that are more complicated than basic wrappers. Basic wrappers ++// usually belong in internal/linux. If you feel something belongs in ++// libcontainer/utils or libcontainer/system, it probably belongs here instead. ++package sys +diff --git a/internal/sys/verify_inode_unix.go b/internal/sys/verify_inode_unix.go +new file mode 100644 +index 0000000..d5019db +--- /dev/null ++++ b/internal/sys/verify_inode_unix.go +@@ -0,0 +1,30 @@ ++package sys ++ ++import ( ++ "fmt" ++ "os" ++ "runtime" ++ ++ "golang.org/x/sys/unix" ++) ++ ++// VerifyInodeFunc is the callback passed to [VerifyInode] to check if the ++// inode is the expected type (and on the correct filesystem type, in the case ++// of filesystem-specific inodes). ++type VerifyInodeFunc func(stat *unix.Stat_t, statfs *unix.Statfs_t) error ++ ++// VerifyInode verifies that the underlying inode for the given file matches an ++// expected inode type (possibly on a particular kind of filesystem). This is ++// mainly a wrapper around [VerifyInodeFunc]. ++func VerifyInode(file *os.File, checkFunc VerifyInodeFunc) error { ++ var stat unix.Stat_t ++ if err := unix.Fstat(int(file.Fd()), &stat); err != nil { ++ return fmt.Errorf("fstat %q: %w", file.Name(), err) ++ } ++ var statfs unix.Statfs_t ++ if err := unix.Fstatfs(int(file.Fd()), &statfs); err != nil { ++ return fmt.Errorf("fstatfs %q: %w", file.Name(), err) ++ } ++ runtime.KeepAlive(file) ++ return checkFunc(&stat, &statfs) ++} +diff --git a/libcontainer/init_linux.go b/libcontainer/init_linux.go +index 8318e5d..20ac8fb 100644 +--- a/libcontainer/init_linux.go ++++ b/libcontainer/init_linux.go +@@ -432,19 +432,16 @@ func setupUser(config *initConfig) error { + // The ownership needs to match because it is created outside of the container and needs to be + // localized. + func fixStdioPermissions(u *user.ExecUser) error { +- var null unix.Stat_t +- if err := unix.Stat("/dev/null", &null); err != nil { +- return &os.PathError{Op: "stat", Path: "/dev/null", Err: err} +- } + for _, file := range []*os.File{os.Stdin, os.Stdout, os.Stderr} { + var s unix.Stat_t + if err := unix.Fstat(int(file.Fd()), &s); err != nil { + return &os.PathError{Op: "fstat", Path: file.Name(), Err: err} + } + +- // Skip chown if uid is already the one we want or any of the STDIO descriptors +- // were redirected to /dev/null. +- if int(s.Uid) == u.Uid || s.Rdev == null.Rdev { ++ // Skip chown if: ++ // - uid is already the one we want, or ++ // - fd is opened to /dev/null. ++ if int(s.Uid) == u.Uid || isDevNull(&s) { + continue + } + +diff --git a/libcontainer/rootfs_linux.go b/libcontainer/rootfs_linux.go +index d49b75f..390b4a3 100644 +--- a/libcontainer/rootfs_linux.go ++++ b/libcontainer/rootfs_linux.go +@@ -16,6 +16,7 @@ import ( + securejoin "github.com/cyphar/filepath-securejoin" + "github.com/moby/sys/mountinfo" + "github.com/mrunalp/fileutils" ++ "github.com/opencontainers/runc/internal/sys" + "github.com/opencontainers/runc/libcontainer/cgroups" + "github.com/opencontainers/runc/libcontainer/cgroups/fs2" + "github.com/opencontainers/runc/libcontainer/configs" +@@ -323,7 +324,7 @@ func mountCgroupV2(m *configs.Mount, c *mountConfig) error { + // Mask `/sys/fs/cgroup` to ensure it is read-only, even when `/sys` is mounted + // with `rbind,ro` (`runc spec --rootless` produces `rbind,ro` for `/sys`). + err = utils.WithProcfd(c.root, m.Destination, func(procfd string) error { +- return maskPath(procfd, c.label) ++ return maskPaths([]string{procfd}, c.label) + }) + } + return err +@@ -669,20 +670,20 @@ func setupDevSymlinks(rootfs string) error { + // needs to be called after we chroot/pivot into the container's rootfs so that any + // symlinks are resolved locally. + func reOpenDevNull() error { +- var stat, devNullStat unix.Stat_t + file, err := os.OpenFile("/dev/null", os.O_RDWR, 0) + if err != nil { + return err + } + defer file.Close() //nolint: errcheck +- if err := unix.Fstat(int(file.Fd()), &devNullStat); err != nil { +- return &os.PathError{Op: "fstat", Path: file.Name(), Err: err} ++ if err := verifyDevNull(file); err != nil { ++ return fmt.Errorf("can't reopen /dev/null: %w", err) + } + for fd := 0; fd < 3; fd++ { ++ var stat unix.Stat_t + if err := unix.Fstat(fd, &stat); err != nil { + return &os.PathError{Op: "fstat", Path: "fd " + strconv.Itoa(fd), Err: err} + } +- if stat.Rdev == devNullStat.Rdev { ++ if isDevNull(&stat) { + // Close and re-open the fd. + if err := unix.Dup3(int(file.Fd()), fd, 0); err != nil { + return &os.PathError{ +@@ -1081,18 +1082,71 @@ func remountReadonly(m *configs.Mount) error { + return fmt.Errorf("unable to mount %s as readonly max retries reached", dest) + } + +-// maskPath masks the top of the specified path inside a container to avoid ++func isDevNull(st *unix.Stat_t) bool { ++ return st.Mode&unix.S_IFMT == unix.S_IFCHR && st.Rdev == unix.Mkdev(1, 3) ++} ++ ++func verifyDevNull(f *os.File) error { ++ return sys.VerifyInode(f, func(st *unix.Stat_t, _ *unix.Statfs_t) error { ++ if !isDevNull(st) { ++ return errors.New("container's /dev/null is invalid") ++ } ++ return nil ++ }) ++} ++ ++// maskPaths masks the top of the specified path inside a container to avoid + // security issues from processes reading information from non-namespace aware + // mounts ( proc/kcore ). + // For files, maskPath bind mounts /dev/null over the top of the specified path. + // For directories, maskPath mounts read-only tmpfs over the top of the specified path. +-func maskPath(path string, mountLabel string) error { +- if err := mount("/dev/null", path, "", "", unix.MS_BIND, ""); err != nil && !errors.Is(err, os.ErrNotExist) { +- if errors.Is(err, unix.ENOTDIR) { +- return mount("tmpfs", path, "", "tmpfs", unix.MS_RDONLY, label.FormatMountLabel("", mountLabel)) ++func maskPaths(paths []string, mountLabel string) error { ++ devNull, err := os.OpenFile("/dev/null", unix.O_PATH, 0) ++ if err != nil { ++ return fmt.Errorf("can't mask paths: %w", err) ++ } ++ defer devNull.Close() ++ if err := verifyDevNull(devNull); err != nil { ++ return fmt.Errorf("can't mask paths: %w", err) ++ } ++ procSelfFd, closer := utils.ProcThreadSelf("fd/") ++ defer closer() ++ ++ for _, path := range paths { ++ // Open the target path; skip if it doesn't exist. ++ dstFh, err := os.OpenFile(path, unix.O_PATH|unix.O_CLOEXEC, 0) ++ if err != nil { ++ if errors.Is(err, os.ErrNotExist) { ++ continue ++ } ++ return fmt.Errorf("can't mask path %q: %w", path, err) + } +- return err ++ st, err := dstFh.Stat() ++ ++ if err != nil { ++ dstFh.Close() ++ return fmt.Errorf("can't mask path %q: %w", path, err) ++ } ++ var dstType string ++ if st.IsDir() { ++ // Destination is a directory: bind mount a ro tmpfs over it. ++ dstType = "dir" ++ err = mount("tmpfs", path, "", "tmpfs", unix.MS_RDONLY, label.FormatMountLabel("", mountLabel)) ++ } else { ++ // Destination is a file: mount it to /dev/null. ++ dstType = "path" ++ src, closer := utils.ProcThreadSelfFd(devNull.Fd()) ++ defer closer() ++ dstFd := filepath.Join(procSelfFd, strconv.Itoa(int(dstFh.Fd()))) ++ err = mount(src, path, dstFd, "", unix.MS_BIND, "") ++ } ++ dstFh.Close() ++ if err != nil { ++ return fmt.Errorf("can't mask %s %q: %w", dstType, path, err) ++ } ++ + } ++ + return nil + } + +diff --git a/libcontainer/standard_init_linux.go b/libcontainer/standard_init_linux.go +index e0d8b4a..fc29a46 100644 +--- a/libcontainer/standard_init_linux.go ++++ b/libcontainer/standard_init_linux.go +@@ -153,10 +153,9 @@ func (l *linuxStandardInit) Init() error { + } + } + } +- for _, path := range l.config.Config.MaskPaths { +- if err := maskPath(path, l.config.Config.MountLabel); err != nil { +- return fmt.Errorf("can't mask path %s: %w", path, err) +- } ++ ++ if err := maskPaths(l.config.Config.MaskPaths, l.config.Config.MountLabel); err != nil { ++ return err + } + pdeath, err := system.GetParentDeathSignal() + if err != nil { +-- +2.43.0 + diff --git a/patch/0063-runc-fix-CVE-2025-52565.patch b/patch/0063-runc-fix-CVE-2025-52565.patch new file mode 100644 index 0000000000000000000000000000000000000000..81146ce7a47ec29b7c6efd6b39f9a09fba6e25e7 --- /dev/null +++ b/patch/0063-runc-fix-CVE-2025-52565.patch @@ -0,0 +1,229 @@ +From 531ef794e4ecd628006a865ad334a048ee2b4b2e Mon Sep 17 00:00:00 2001 +From: Aleksa Sarai +Date: Thu, 15 May 2025 16:12:21 +1000 +Subject: [PATCH] console: use TIOCGPTPEER when allocating peer PTY + +When opening the peer end of a pty, the old kernel API required us to +open /dev/pts/$num inside the container (at least since we fixed console +handling many years ago in commit 244c9fc426ae ("*: console rewrite")). + +The problem is that in a hostile container it is possible for +/dev/pts/$num to be an attacker-controlled symlink that runc can be +tricked into resolving when doing bind-mounts. This allows the attacker +to (among other things) persist /proc/... entries that are later masked +by runc, allowing an attacker to escape through the kernel.core_pattern +sysctl (/proc/sys/kernel/core_pattern). This is the original issue +reported by Lei Wang and Li Fu Bang in CVE-2025-52565. + +However, it should be noted that this is not entirely a newly-discovered +problem. Way back in Linux 4.13 (2017), I added the TIOCGPTPEER ioctl, +which allows us to get a pty peer without touching the /dev/pts inside +the container. The original threat model was around an attacker +replacing /dev/pts/$n or /dev/pts/ptmx with some malicious inode (a DoS +inode, or possibly a PTY they wanted a confused deputy to operate on). +Unfortunately, there was no practical way for runc to cache a safe +O_PATH handle to /dev/pts/ptmx (unlike other runtimes like LXC, which +switched to TIOCGPTPEER way back in 2017). Since it wasn't clear how we +could protect against the main attack TIOCGPTPEER was meant to protect +against, we never switched to it (even though I implemented it +specifically to harden container runtimes). + +Unfortunately, It turns out that mount *sources* are a threat we didn't +fully consider. Since TIOCGPTPEER already solves this problem entirely +for us in a race free way, we should just use that. In a later patch, we +will add some hardening for /dev/pts/$num opening to maintain support +for very old kernels (Linux 4.13 is very old at this point, but RHEL 7 +is still kicking and is stuck on Linux 3.10). + +Fixes: GHSA-qw9x-cqr3-wc7r CVE-2025-52565 +Reported-by: Lei Wang (CVE-2025-52565) +Reported-by: lfbzhm (CVE-2025-52565) +Reported-by: Aleksa Sarai (TIOCGPTPEER) +Signed-off-by: Aleksa Sarai +--- + internal/linux/linux.go | 47 +++++++++++++++++++++++++++ + libcontainer/console_linux.go | 61 +++++++++++++++++++++++------------ + libcontainer/init_linux.go | 7 ++-- + 3 files changed, 91 insertions(+), 24 deletions(-) + create mode 100644 internal/linux/linux.go + +diff --git a/internal/linux/linux.go b/internal/linux/linux.go +new file mode 100644 +index 00000000..eb9c2ed0 +--- /dev/null ++++ b/internal/linux/linux.go +@@ -0,0 +1,47 @@ ++package linux ++ ++import ( ++ "errors" ++ "os" ++ ++ "golang.org/x/sys/unix" ++) ++ ++// retryOnEINTR takes a function that returns an error and calls it ++// until the error returned is not EINTR. ++func retryOnEINTR(fn func() error) error { ++ for { ++ err := fn() ++ if !errors.Is(err, unix.EINTR) { ++ return err ++ } ++ } ++} ++ ++// Dup3 wraps [unix.Dup3]. ++func Dup3(oldfd, newfd, flags int) error { ++ err := retryOnEINTR(func() error { ++ return unix.Dup3(oldfd, newfd, flags) ++ }) ++ return os.NewSyscallError("dup3", err) ++} ++ ++// GetPtyPeer is a wrapper for ioctl(TIOCGPTPEER). ++func GetPtyPeer(ptyFd uintptr, unsafePeerPath string, flags int) (*os.File, error) { ++ // Make sure O_NOCTTY is always set -- otherwise runc might accidentally ++ // gain it as a controlling terminal. O_CLOEXEC also needs to be set to ++ // make sure we don't leak the handle either. ++ flags |= unix.O_NOCTTY | unix.O_CLOEXEC ++ ++ // There is no nice wrapper for this kind of ioctl in unix. ++ peerFd, _, errno := unix.Syscall( ++ unix.SYS_IOCTL, ++ ptyFd, ++ uintptr(unix.TIOCGPTPEER), ++ uintptr(flags), ++ ) ++ if errno != 0 { ++ return nil, os.NewSyscallError("ioctl TIOCGPTPEER", errno) ++ } ++ return os.NewFile(peerFd, unsafePeerPath), nil ++} +diff --git a/libcontainer/console_linux.go b/libcontainer/console_linux.go +index 29b9c3b0..9334e10d 100644 +--- a/libcontainer/console_linux.go ++++ b/libcontainer/console_linux.go +@@ -1,41 +1,60 @@ + package libcontainer + + import ( ++ "fmt" + "os" ++ "runtime" + ++ "github.com/containerd/console" ++ "github.com/opencontainers/runc/internal/linux" ++ "github.com/opencontainers/runc/libcontainer/utils" + "golang.org/x/sys/unix" + ) + +-// mount initializes the console inside the rootfs mounting with the specified mount label +-// and applying the correct ownership of the console. +-func mountConsole(slavePath string) error { +- oldMask := unix.Umask(0o000) +- defer unix.Umask(oldMask) +- f, err := os.Create("/dev/console") +- if err != nil && !os.IsExist(err) { +- return err ++// safeAllocPty returns a new (ptmx, peer pty) allocation for use inside a ++// container. ++func safeAllocPty() (pty console.Console, peer *os.File, Err error) { ++ pty, unsafePeerPath, err := console.NewPty() ++ if err != nil { ++ return nil, nil, err + } +- if f != nil { +- f.Close() ++ defer func() { ++ if Err != nil { ++ _ = pty.Close() ++ } ++ }() ++ ++ peer, err = linux.GetPtyPeer(pty.Fd(), unsafePeerPath, unix.O_RDWR|unix.O_NOCTTY) ++ if err != nil { ++ return nil, nil, fmt.Errorf("failed to get peer end of newly-allocated console: %w", err) + } +- return mount(slavePath, "/dev/console", "", "bind", unix.MS_BIND, "") ++ return pty, peer, nil + } + +-// dupStdio opens the slavePath for the console and dups the fds to the current +-// processes stdio, fd 0,1,2. +-func dupStdio(slavePath string) error { +- fd, err := unix.Open(slavePath, unix.O_RDWR, 0) ++// mountConsole bind-mounts the provided pty on top of /dev/console so programs ++// that operate on /dev/console operate on the correct container pty. ++func mountConsole(peerPty *os.File) error { ++ console, err := os.OpenFile("/dev/console", unix.O_NOFOLLOW|unix.O_CREAT|unix.O_CLOEXEC, 0o666) + if err != nil { +- return &os.PathError{ +- Op: "open", +- Path: slavePath, +- Err: err, +- } ++ return fmt.Errorf("create /dev/console mount target: %w", err) + } ++ defer console.Close() ++ ++ dstFd, closer1 := utils.ProcThreadSelfFd(console.Fd()) ++ defer closer1() ++ ++ src, closer := utils.ProcThreadSelfFd(peerPty.Fd()) ++ defer closer() ++ return mount(src, "/dev/console", dstFd, "bind", unix.MS_BIND, "") ++} ++ ++// dupStdio replaces stdio with the given peerPty. ++func dupStdio(peerPty *os.File) error { + for _, i := range []int{0, 1, 2} { +- if err := unix.Dup3(fd, i, 0); err != nil { ++ if err := linux.Dup3(int(peerPty.Fd()), i, 0); err != nil { + return err + } + } ++ runtime.KeepAlive(peerPty) + return nil + } +diff --git a/libcontainer/init_linux.go b/libcontainer/init_linux.go +index 50c7a129..e4e52612 100644 +--- a/libcontainer/init_linux.go ++++ b/libcontainer/init_linux.go +@@ -248,13 +248,14 @@ func setupConsole(socket *os.File, config *initConfig, mount bool) error { + // the UID owner of the console to be the user the process will run as (so + // they can actually control their console). + +- pty, slavePath, err := console.NewPty() ++ pty, peerPty, err := safeAllocPty() + if err != nil { + return err + } + + // After we return from here, we don't need the console anymore. + defer pty.Close() ++ defer peerPty.Close() + + if config.ConsoleHeight != 0 && config.ConsoleWidth != 0 { + err = pty.Resize(console.WinSize{ +@@ -269,7 +270,7 @@ func setupConsole(socket *os.File, config *initConfig, mount bool) error { + + // Mount the console inside our rootfs. + if mount { +- if err := mountConsole(slavePath); err != nil { ++ if err := mountConsole(peerPty); err != nil { + return err + } + } +@@ -278,7 +279,7 @@ func setupConsole(socket *os.File, config *initConfig, mount bool) error { + return err + } + // Now, dup over all the things. +- return dupStdio(slavePath) ++ return dupStdio(peerPty) + } + + // syncParentReady sends to the given pipe a JSON payload which indicates that +-- +2.33.0 + diff --git a/patch/0064-runc-fix-CVE-2025-52881.patch b/patch/0064-runc-fix-CVE-2025-52881.patch new file mode 100644 index 0000000000000000000000000000000000000000..37415fdb7a4b587bc5b78136733ec145ad22d0c8 --- /dev/null +++ b/patch/0064-runc-fix-CVE-2025-52881.patch @@ -0,0 +1,158 @@ +From ed6b1693b8b3ae7eb0250a7e76fc888cdacf98c1 Mon Sep 17 00:00:00 2001 +From: Aleksa Sarai +Date: Tue, 7 Oct 2025 22:48:50 +1100 +Subject: [PATCH] selinux: use safe procfs API for labels + +Due to the sensitive nature of these fixes, it was not possible to +submit these upstream and vendor the upstream library. Instead, this +patch uses a fork of github.com/opencontainers/selinux, branched at +commit opencontainers/selinux@879a755db558501df06f4ea59461ebc2d0c4a991. + +In order to permit downstreams to build with this patched version, a +snapshot of the forked version has been included in +internal/third_party/selinux. Note that since we use "go mod vendor", +the patched code is usable even without being "go get"-able. Once the +embargo for this issue is lifted we can submit the patches upstream and +switch back to a proper upstream go.mod entry. + +Also, this requires us to temporarily disable the CI job we have that +disallows "replace" directives. + +Fixes: GHSA-cgrx-mc8f-2prm CVE-2025-52881 +Signed-off-by: Aleksa Sarai +--- + libcontainer/rootfs_linux.go | 4 ++ + libcontainer/utils/utils_unix.go | 30 +++++++++++++++ + .../selinux/go-selinux/selinux_linux.go | 38 +++++++++++++++++++ + 3 files changed, 72 insertions(+) + +diff --git a/libcontainer/rootfs_linux.go b/libcontainer/rootfs_linux.go +index 390b4a3..8b1145c 100644 +--- a/libcontainer/rootfs_linux.go ++++ b/libcontainer/rootfs_linux.go +@@ -1154,6 +1154,10 @@ func maskPaths(paths []string, mountLabel string) error { + // For e.g. net.ipv4.ip_forward translated to /proc/sys/net/ipv4/ip_forward. + func writeSystemProperty(key, value string) error { + keyPath := strings.Replace(key, ".", "/", -1) ++ isMount, err := utils.IsMountPoint(keyPath) ++ if err != nil || isMount { ++ return fmt.Errorf("refusing to write %s: %w", keyPath, err) ++ } + return os.WriteFile(path.Join("/proc/sys", keyPath), []byte(value), 0o644) + } + +diff --git a/libcontainer/utils/utils_unix.go b/libcontainer/utils/utils_unix.go +index 66d12e5..0ffe94c 100644 +--- a/libcontainer/utils/utils_unix.go ++++ b/libcontainer/utils/utils_unix.go +@@ -13,6 +13,7 @@ import ( + "strconv" + "strings" + "sync" ++ "syscall" + _ "unsafe" // for go:linkname + + "github.com/opencontainers/runc/libcontainer/system" +@@ -388,3 +389,32 @@ func ProcThreadSelf(subpath string) (string, ProcThreadSelfCloser) { + func ProcThreadSelfFd(fd uintptr) (string, ProcThreadSelfCloser) { + return ProcThreadSelf("fd/" + strconv.FormatUint(uint64(fd), 10)) + } ++ ++// Determine if a path has a mount point. ++func IsMountPoint(path string) (bool, error) { ++ newPath := path ++ for newPath != "/" { ++ stat, err := os.Stat(newPath) ++ if err != nil { ++ return false, fmt.Errorf("fail to get %s stat: %v", newPath, err) ++ } ++ ++ parentDir := filepath.Dir(newPath) ++ if parentDir == "/" { ++ return false, nil ++ } ++ ++ parentStat, err := os.Stat(parentDir) ++ if err != nil { ++ return false, fmt.Errorf("fail to get parent path %s stat: %v", parentDir, err) ++ } ++ ++ targetInode := stat.Sys().(*syscall.Stat_t).Dev ++ parentInode := parentStat.Sys().(*syscall.Stat_t).Dev ++ if targetInode != parentInode { ++ return true, nil ++ } ++ newPath = parentDir ++ } ++ return false, nil ++} +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 ee602ab..0d00067 100644 +--- a/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go ++++ b/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go +@@ -16,6 +16,7 @@ import ( + "strconv" + "strings" + "sync" ++ "syscall" + + "golang.org/x/sys/unix" + ) +@@ -277,6 +278,10 @@ func readCon(fpath string) (string, error) { + if fpath == "" { + return "", ErrEmptyPath + } ++ isMount, err := isMountPoint(fpath) ++ if err != nil || isMount { ++ return "", fmt.Errorf("refusing to read %s: %w", fpath, err) ++ } + + in, err := os.Open(fpath) + if err != nil { +@@ -418,10 +423,43 @@ func execLabel() (string, error) { + return readAttr("exec") + } + ++// Determine if a path has a mount point. ++func isMountPoint(path string) (bool, error) { ++ newPath := path ++ for newPath != "/" { ++ stat, err := os.Stat(newPath) ++ if err != nil { ++ return false, fmt.Errorf("fail to get %s stat: %v", newPath, err) ++ } ++ ++ parentDir := filepath.Dir(newPath) ++ if parentDir == "/" { ++ return false, nil ++ } ++ ++ parentStat, err := os.Stat(parentDir) ++ if err != nil { ++ return false, fmt.Errorf("fail to get parent path %s stat: %v", parentDir, err) ++ } ++ ++ targetInode := stat.Sys().(*syscall.Stat_t).Dev ++ parentInode := parentStat.Sys().(*syscall.Stat_t).Dev ++ if targetInode != parentInode { ++ return true, nil ++ } ++ newPath = parentDir ++ } ++ return false, nil ++} ++ + func writeCon(fpath, val string) error { + if fpath == "" { + return ErrEmptyPath + } ++ isMount, err := isMountPoint(fpath) ++ if err != nil || isMount { ++ return fmt.Errorf("refusing to write %s: %w", fpath, err) ++ } + if val == "" { + if !getEnabled() { + return nil +-- +2.43.0 + diff --git a/runc.spec b/runc.spec index 3edfe7a5e3d861a8adbbdf067a00bb0b2a03b243..04e6a48af90a5eb34e4c364e588e73213c6f8432 100644 --- a/runc.spec +++ b/runc.spec @@ -3,7 +3,7 @@ Name: runc Version: 1.1.3 -Release: 33 +Release: 34 Summary: runc is a CLI tool for spawning and running containers according to the OCI specification. License: ASL 2.0 @@ -54,6 +54,12 @@ install -p -m 755 runc $RPM_BUILD_ROOT/%{_bindir}/runc %{_bindir}/runc %changelog +* Wed Nov 26 2025 dongyuzhen - 1.1.3-34 +- Type:CVE +- CVE:NA +- SUG:NA +- DESC:fix CVE-2025-31133 and CVE-2025-52565 and CVE-2025-52881 + * Wed Mar 26 2025 dongyuzhen - 1.1.3-33 - Type:bugfix - CVE:NA diff --git a/series.conf b/series.conf index 7063ceeef8b1a383ec59d6984280915662c0f390..9d9b8f3a98013eea14ffac2f7cf93d4834ca0423 100644 --- a/series.conf +++ b/series.conf @@ -58,4 +58,7 @@ patch/0057-rootfs-consolidate-mountpoint-creation-logic.patch patch/0058-rootfs-try-to-scope-MkdirAll-to-stay-inside-the-root.patch patch/0059-runc-fix-can-t-set-cpuset-cpus-and-cpuset-mems-at-th.patch patch/0060-runc-fix-failed-exec-after-systemctl-daemon-reload.patch -patch/0061-runc-libct-cap-allow-New-nil.patch \ No newline at end of file +patch/0061-runc-libct-cap-allow-New-nil.patch +patch/0062-runc-fix-CVE-2025-31133.patch +patch/0063-runc-fix-CVE-2025-52565.patch +patch/0064-runc-fix-CVE-2025-52881.patch