mirror of
https://github.com/opencontainers/runc.git
synced 2026-07-11 06:03:57 +08:00
pathrs: add "hallucination" helpers for SecureJoin magic
In order to maintain compatibility with previous releases of runc (which permitted dangling symlinks as path components by permitting non-existent path components to be treated like real directories) we have to first do SecureJoin to construct a target path that is compatible with the old behaviour but has all dangling symlinks (or other invalid paths like ".." components after non-existent directories) removed. This is effectively a more generic verison of commit3f925525b4("rootfs: re-allow dangling symlinks in mount targets") and will let us remove the need for open-coding SecureJoin workarounds. Signed-off-by: Aleksa Sarai <cyphar@cyphar.com> (cherry picked from commitcfb74326be) Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
This commit is contained in:
@@ -21,7 +21,6 @@ package pathrs
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/cyphar/filepath-securejoin/pathrs-lite"
|
||||
"github.com/sirupsen/logrus"
|
||||
@@ -50,18 +49,9 @@ import (
|
||||
// needed for a lot of runc callers and fixing this would require reworking a
|
||||
// lot of path logic).
|
||||
func MkdirAllInRoot(root, unsafePath string, mode os.FileMode) (*os.File, error) {
|
||||
// If the path is already "within" the root, get the path relative to the
|
||||
// root and use that as the unsafe path. This is necessary because a lot of
|
||||
// MkdirAllInRoot callers have already done SecureJoin, and refactoring
|
||||
// all of them to stop using these SecureJoin'd paths would require a fair
|
||||
// amount of work.
|
||||
// TODO(cyphar): Do the refactor to libpathrs once it's ready.
|
||||
if IsLexicallyInRoot(root, unsafePath) {
|
||||
subPath, err := filepath.Rel(root, unsafePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
unsafePath = subPath
|
||||
unsafePath, err := hallucinateUnsafePath(root, unsafePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to construct hallucinated target path: %w", err)
|
||||
}
|
||||
|
||||
// Check for any silly mode bits.
|
||||
|
||||
@@ -22,6 +22,8 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
securejoin "github.com/cyphar/filepath-securejoin"
|
||||
)
|
||||
|
||||
// IsLexicallyInRoot is shorthand for strings.HasPrefix(path+"/", root+"/"),
|
||||
@@ -82,3 +84,33 @@ func LexicallyStripRoot(root, path string) string {
|
||||
}
|
||||
return LexicallyCleanPath("/" + path)
|
||||
}
|
||||
|
||||
// hallucinateUnsafePath creates a new unsafePath which has all symlinks
|
||||
// (including dangling symlinks) fully resolved and any non-existent components
|
||||
// treated as though they are real. This is effectively just a wrapper around
|
||||
// [securejoin.SecureJoin] that strips the root. This path *IS NOT* safe to use
|
||||
// as-is, you *MUST* operate on the returned path with pathrs-lite.
|
||||
//
|
||||
// The reason for this methods is that in previous runc versions, we would
|
||||
// tolerate nonsense paths with dangling symlinks as path components.
|
||||
// pathrs-lite does not support this, so instead we have to emulate this
|
||||
// behaviour by doing SecureJoin *purely to get a semi-reasonable path to use*
|
||||
// and then we use pathrs-lite to operate on the path safely.
|
||||
//
|
||||
// It would be quite difficult to emulate this in a race-free way in
|
||||
// pathrs-lite, so instead we use [securejoin.SecureJoin] to simply produce a
|
||||
// new candidate path for operations like [MkdirAllInRoot] so they can then
|
||||
// operate on the new unsafePath as if it was what the user requested.
|
||||
//
|
||||
// If unsafePath is already lexically inside root, it is stripped before
|
||||
// re-resolving it (this is done to ensure compatibility with legacy callers
|
||||
// within runc that call SecureJoin before calling into pathrs).
|
||||
func hallucinateUnsafePath(root, unsafePath string) (string, error) {
|
||||
unsafePath = LexicallyStripRoot(root, unsafePath)
|
||||
weirdPath, err := securejoin.SecureJoin(root, unsafePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
unsafePath = LexicallyStripRoot(root, weirdPath)
|
||||
return unsafePath, nil
|
||||
}
|
||||
|
||||
@@ -50,6 +50,11 @@ func OpenInRoot(root, subpath string, flags int) (*os.File, error) {
|
||||
// include it in the passed flags. The fileMode argument uses unix.* mode bits,
|
||||
// *not* os.FileMode.
|
||||
func CreateInRoot(root, subpath string, flags int, fileMode uint32) (*os.File, error) {
|
||||
subpath, err := hallucinateUnsafePath(root, subpath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to construct hallucinated target path: %w", err)
|
||||
}
|
||||
|
||||
dir, filename := filepath.Split(subpath)
|
||||
if filepath.Join("/", filename) == "/" {
|
||||
return nil, fmt.Errorf("create in root subpath %q has bad trailing component %q", subpath, filename)
|
||||
|
||||
@@ -546,18 +546,6 @@ func (m *mountEntry) createOpenMountpoint(rootfs string) (Err error) {
|
||||
}
|
||||
dstIsFile = !fi.IsDir()
|
||||
}
|
||||
|
||||
// In previous runc versions, we would tolerate nonsense paths with
|
||||
// dangling symlinks as path components. pathrs-lite does not support
|
||||
// this, so instead we have to emulate this behaviour by doing
|
||||
// SecureJoin *purely to get a semi-reasonable path to use* and then we
|
||||
// use pathrs-lite to operate on the path safely.
|
||||
newUnsafePath, err := securejoin.SecureJoin(rootfs, unsafePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
unsafePath = pathrs.LexicallyStripRoot(rootfs, newUnsafePath)
|
||||
|
||||
if dstIsFile {
|
||||
dstFile, err = pathrs.CreateInRoot(rootfs, unsafePath, unix.O_CREAT|unix.O_EXCL|unix.O_NOFOLLOW, 0o644)
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user