mirror of
https://github.com/opencontainers/runc.git
synced 2026-07-10 21:53:57 +08:00
a8e53f2c6d
These codepaths are very old and operate on pure paths but before pivot_root(2), meaning that a bad image with a malicious /dev symlink could cause us to operate on host paths instead. In practice this means that we could be tricked into removing a file called "ptmx" (note that /dev/pts/ptmx and /dev/ptmx are both immune for different reasons) or creating a very restricted set of symlinks (with fixed targets and names). The scope of these bugs is thus quite limited, but we definitely need to harden against it. These codepaths were unfortunately missed during the fd-based rework in commitd40b3439a9("rootfs: switch to fd-based handling of mountpoint targets") -- I must've assumed they were called after pivot_root(2)... Fixes: GHSA-xjvp-4fhw-gc47 Fixes: CVE-2026-41579 Fixes:d40b3439a9("rootfs: switch to fd-based handling of mountpoint targets") Signed-off-by: Aleksa Sarai <aleksa@amutable.com> (cherry picked from commit864db8042d) Signed-off-by: Aleksa Sarai <aleksa@amutable.com>
111 lines
3.6 KiB
Go
111 lines
3.6 KiB
Go
// SPDX-License-Identifier: Apache-2.0
|
|
/*
|
|
* Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
|
|
* Copyright (C) 2024-2025 SUSE LLC
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package pathrs
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/cyphar/filepath-securejoin/pathrs-lite"
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
// OpenInRoot opens the given path inside the root with the provided flags. It
|
|
// is effectively shorthand for [securejoin.OpenatInRoot] followed by
|
|
// [securejoin.Reopen].
|
|
func OpenInRoot(root *os.File, subpath string, flags int) (*os.File, error) {
|
|
handle, err := retryEAGAIN(func() (*os.File, error) {
|
|
return pathrs.OpenatInRoot(root, subpath)
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer handle.Close()
|
|
|
|
return Reopen(handle, flags)
|
|
}
|
|
|
|
// CreateInRoot creates a new file inside a root (as well as any missing parent
|
|
// directories) and returns a handle to said file. This effectively has
|
|
// open(O_CREAT|O_NOFOLLOW) semantics. If you want the creation to use O_EXCL,
|
|
// include it in the passed flags. The fileMode argument uses unix.* mode bits,
|
|
// *not* os.FileMode.
|
|
func CreateInRoot(root *os.File, subpath string, flags int, fileMode uint32) (*os.File, error) {
|
|
dirFd, filename, err := MkdirAllParentInRoot(root, subpath, 0o755)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer dirFd.Close()
|
|
|
|
// We know that the filename does not have any "/" components, and that
|
|
// dirFd is inside the root. O_NOFOLLOW will stop us from following
|
|
// trailing symlinks, so this is safe to do. libpathrs's Root::create_file
|
|
// works the same way.
|
|
flags |= unix.O_CREAT | unix.O_NOFOLLOW
|
|
fd, err := unix.Openat(int(dirFd.Fd()), filename, flags, fileMode)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
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
|
|
}
|