mirror of
https://github.com/opencontainers/runc.git
synced 2026-07-10 21:53:57 +08:00
merge CVE-2026-41579 fixes into runc:main
Aleksa Sarai (3): rootfs: make cgroupv1 subsystem symlinks fd-based rootfs: make /dev initialisation code fd-based rootfs: switch createDevices argument order LGTMs: lifubang kolyshkin rata
This commit is contained in:
@@ -24,6 +24,14 @@ import (
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func splitPath(path string) (dirPath, filename string, err error) {
|
||||
dirPath, filename = filepath.Split(path)
|
||||
if filepath.Join("/", filename) == "/" {
|
||||
return "", "", fmt.Errorf("root subpath %q has bad trailing component %q", path, filename)
|
||||
}
|
||||
return dirPath, filename, nil
|
||||
}
|
||||
|
||||
// MkdirAllParentInRoot is like [MkdirAllInRoot] except that it only creates
|
||||
// the parent directory of the target path, returning the trailing component so
|
||||
// the caller has more flexibility around constructing the final inode.
|
||||
@@ -41,9 +49,9 @@ func MkdirAllParentInRoot(root *os.File, unsafePath string, mode os.FileMode) (*
|
||||
return nil, "", fmt.Errorf("failed to construct hallucinated target path: %w", err)
|
||||
}
|
||||
|
||||
dirPath, filename := filepath.Split(unsafePath)
|
||||
if filepath.Join("/", filename) == "/" {
|
||||
return nil, "", fmt.Errorf("create parent dir in root subpath %q has bad trailing component %q", unsafePath, filename)
|
||||
dirPath, filename, err := splitPath(unsafePath)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("split path %q for mkdir parent: %w", unsafePath, err)
|
||||
}
|
||||
|
||||
dirFd, err := MkdirAllInRoot(root, dirPath, mode)
|
||||
|
||||
@@ -19,7 +19,9 @@
|
||||
package pathrs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/cyphar/filepath-securejoin/pathrs-lite"
|
||||
"golang.org/x/sys/unix"
|
||||
@@ -65,3 +67,46 @@ func CreateInRoot(root *os.File, subpath string, flags int, fileMode uint32) (*o
|
||||
}
|
||||
return os.NewFile(uintptr(fd), root.Name()+"/"+subpath), nil
|
||||
}
|
||||
|
||||
// UnlinkInRoot deletes the inode specified at the given subpath. If you pass
|
||||
// [unix.AT_REMOVEDIR] it will remove directories, otherwise it will remove
|
||||
// non-directory inodes.
|
||||
func UnlinkInRoot(root *os.File, subpath string, flags int) error {
|
||||
dirPath, filename, err := splitPath(subpath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("split path %q for unlink: %w", subpath, err)
|
||||
}
|
||||
|
||||
dirFd := root
|
||||
if filepath.Join("/", dirPath) != "/" {
|
||||
newDirFd, err := OpenInRoot(root, dirPath, unix.O_DIRECTORY|unix.O_PATH)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open parent directory %q for unlink: %w", dirPath, err)
|
||||
}
|
||||
dirFd = newDirFd
|
||||
defer dirFd.Close()
|
||||
}
|
||||
|
||||
err = unix.Unlinkat(int(dirFd.Fd()), filename, flags)
|
||||
if err != nil {
|
||||
err = &os.PathError{Op: "unlinkat", Path: dirFd.Name() + "/" + filename, Err: err}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// SymlinkInRoot creates a symlink inside a root with the given target (as well
|
||||
// as creating any missing parent directories). If the subpath already exists,
|
||||
// an error is returned.
|
||||
func SymlinkInRoot(linktarget string, root *os.File, subpath string) error {
|
||||
dirFd, filename, err := MkdirAllParentInRoot(root, subpath, 0o755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dirFd.Close()
|
||||
|
||||
err = unix.Symlinkat(linktarget, int(dirFd.Fd()), filename)
|
||||
if err != nil {
|
||||
err = &os.PathError{Op: "symlinkat", Path: dirFd.Name() + "/" + filename, Err: err}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -97,6 +97,19 @@ func needsSetupDev(config *configs.Config) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func doSetupDev(rootFd *os.File, config *configs.Config) error {
|
||||
if err := createDevices(rootFd, config); err != nil {
|
||||
return fmt.Errorf("error creating device nodes: %w", err)
|
||||
}
|
||||
if err := setupPtmx(rootFd); err != nil {
|
||||
return fmt.Errorf("error setting up ptmx: %w", err)
|
||||
}
|
||||
if err := setupDevSymlinks(rootFd); err != nil {
|
||||
return fmt.Errorf("error setting up /dev symlinks: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// setupAndMountToRootfs sets up the mount for a single mount point and mounts it to the rootfs.
|
||||
func setupAndMountToRootfs(pipe *syncSocket, config *configs.Config, mountConfig *mountConfig, m *configs.Mount) error {
|
||||
entry := mountEntry{Mount: m}
|
||||
@@ -184,14 +197,8 @@ func prepareRootfs(pipe *syncSocket, iConfig *initConfig) (err error) {
|
||||
|
||||
setupDev := needsSetupDev(config)
|
||||
if setupDev {
|
||||
if err := createDevices(config, rootFd); err != nil {
|
||||
return fmt.Errorf("error creating device nodes: %w", err)
|
||||
}
|
||||
if err := setupPtmx(config); err != nil {
|
||||
return fmt.Errorf("error setting up ptmx: %w", err)
|
||||
}
|
||||
if err := setupDevSymlinks(config.Rootfs); err != nil {
|
||||
return fmt.Errorf("error setting up /dev symlinks: %w", err)
|
||||
if err := doSetupDev(rootFd, config); err != nil {
|
||||
return fmt.Errorf("configuring container /dev: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -380,7 +387,8 @@ func mountCgroupV1(m mountEntry, c *mountConfig) error {
|
||||
// symlink(2) is very dumb, it will just shove the path into
|
||||
// the link and doesn't do any checks or relative path
|
||||
// conversion. Also, don't error out if the cgroup already exists.
|
||||
if err := os.Symlink(mc, filepath.Join(c.root.Name(), m.Destination, ss)); err != nil && !errors.Is(err, os.ErrExist) {
|
||||
ssPath := filepath.Join(m.Destination, ss)
|
||||
if err := pathrs.SymlinkInRoot(mc, c.root, ssPath); err != nil && !errors.Is(err, os.ErrExist) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -893,7 +901,7 @@ func checkProcMount(rootfs, dest string, m mountEntry) error {
|
||||
return fmt.Errorf("%q cannot be mounted because it is inside /proc", dest)
|
||||
}
|
||||
|
||||
func setupDevSymlinks(rootfs string) error {
|
||||
func setupDevSymlinks(rootFd *os.File) error {
|
||||
// In theory, these should be links to /proc/thread-self, but systems
|
||||
// expect these to be /proc/self and this matches how most distributions
|
||||
// work.
|
||||
@@ -909,11 +917,8 @@ func setupDevSymlinks(rootfs string) error {
|
||||
links = append(links, [2]string{"/proc/kcore", "/dev/core"})
|
||||
}
|
||||
for _, link := range links {
|
||||
var (
|
||||
src = link[0]
|
||||
dst = filepath.Join(rootfs, link[1])
|
||||
)
|
||||
if err := os.Symlink(src, dst); err != nil && !errors.Is(err, os.ErrExist) {
|
||||
target, devName := link[0], link[1]
|
||||
if err := pathrs.SymlinkInRoot(target, rootFd, devName); err != nil && !errors.Is(err, os.ErrExist) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -949,7 +954,7 @@ func reOpenDevNull() error {
|
||||
}
|
||||
|
||||
// Create the device nodes in the container.
|
||||
func createDevices(config *configs.Config, rootFd *os.File) error {
|
||||
func createDevices(rootFd *os.File, config *configs.Config) error {
|
||||
useBindMount := userns.RunningInUserNS() || config.Namespaces.Contains(configs.NEWUSER)
|
||||
for _, node := range config.Devices {
|
||||
|
||||
@@ -1129,15 +1134,11 @@ func setReadonly() error {
|
||||
return mount("", "/", "", flags, "")
|
||||
}
|
||||
|
||||
func setupPtmx(config *configs.Config) error {
|
||||
ptmx := filepath.Join(config.Rootfs, "dev/ptmx")
|
||||
if err := os.Remove(ptmx); err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
func setupPtmx(rootFd *os.File) error {
|
||||
if err := pathrs.UnlinkInRoot(rootFd, "/dev/ptmx", 0); err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
return err
|
||||
}
|
||||
if err := os.Symlink("pts/ptmx", ptmx); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return pathrs.SymlinkInRoot("pts/ptmx", rootFd, "/dev/ptmx")
|
||||
}
|
||||
|
||||
// pivotRoot will call pivot_root such that rootfs becomes the new root
|
||||
|
||||
Reference in New Issue
Block a user