filepath-securejoin – github.com/cyphar/filepath-securejoin Index | Files

package securejoin

import "github.com/cyphar/filepath-securejoin"

Package securejoin is an implementation of the hopefully-soon-to-be-included SecureJoin helper that is meant to be part of the "path/filepath" package. The purpose of this project is to provide a PoC implementation to make the SecureJoin proposal (https://github.com/golang/go/issues/20126) more tangible.

Index

Functions

func IsNotExist

func IsNotExist(err error) bool

IsNotExist tells you if err is an error that implies that either the path accessed does not exist (or path components don't exist). This is effectively a more broad version of os.IsNotExist.

func MkdirAll

func MkdirAll(root, unsafePath string, mode int) error

MkdirAll is a race-safe alternative to the Go stdlib's os.MkdirAll function, where the new directory is guaranteed to be within the root directory (if an attacker can move directories from inside the root to outside the root, the created directory tree might be outside of the root but the key constraint is that at no point will we walk outside of the directory tree we are creating).

Effectively, MkdirAll(root, unsafePath, mode) is equivalent to

path, _ := securejoin.SecureJoin(root, unsafePath)
err := os.MkdirAll(path, mode)

But is much safer. The above implementation is unsafe because if an attacker can modify the filesystem tree between SecureJoin and MkdirAll, it is possible for MkdirAll to resolve unsafe symlink components and create directories outside of the root.

If you plan to open the directory after you have created it or want to use an open directory handle as the root, you should use MkdirAllHandle instead. This function is a wrapper around MkdirAllHandle.

NOTE: The mode argument must be set the unix mode bits (unix.S_I...), not the Go generic mode bits (os.Mode...).

func MkdirAllHandle

func MkdirAllHandle(root *os.File, unsafePath string, mode int) (_ *os.File, Err error)

MkdirAllHandle is equivalent to MkdirAll, except that it is safer to use in two respects:

In addition, the returned handle is obtained far more efficiently than doing a brand new lookup of unsafePath (such as with SecureJoin or openat2) after doing MkdirAll. If you intend to open the directory after creating it, you should use MkdirAllHandle.

func OpenInRoot

func OpenInRoot(root, unsafePath string) (*os.File, error)

OpenInRoot safely opens the provided unsafePath within the root. Effectively, OpenInRoot(root, unsafePath) is equivalent to

path, _ := securejoin.SecureJoin(root, unsafePath)
handle, err := os.OpenFile(path, unix.O_PATH|unix.O_CLOEXEC)

But is much safer. The above implementation is unsafe because if an attacker can modify the filesystem tree between SecureJoin and OpenFile, it is possible for the returned file to be outside of the root.

Note that the returned handle is an O_PATH handle, meaning that only a very limited set of operations will work on the handle. This is done to avoid accidentally opening an untrusted file that could cause issues (such as a disconnected TTY that could cause a DoS, or some other issue). In order to use the returned handle, you can "upgrade" it to a proper handle using Reopen.

func OpenatInRoot

func OpenatInRoot(root *os.File, unsafePath string) (*os.File, error)

OpenatInRoot is equivalent to OpenInRoot, except that the root is provided using an *os.File handle, to ensure that the correct root directory is used.

func Reopen

func Reopen(handle *os.File, flags int) (*os.File, error)

Reopen takes an *os.File handle and re-opens it through /proc/self/fd. Reopen(file, flags) is effectively equivalent to

fdPath := fmt.Sprintf("/proc/self/fd/%d", file.Fd())
os.OpenFile(fdPath, flags|unix.O_CLOEXEC)

But with some extra hardenings to ensure that we are not tricked by a maliciously-configured /proc mount. While this attack scenario is not common, in container runtimes it is possible for higher-level runtimes to be tricked into configuring an unsafe /proc that can be used to attack file operations. See CVE-2019-19921 for more details.

func SecureJoin

func SecureJoin(root, unsafePath string) (string, error)

SecureJoin is a wrapper around SecureJoinVFS that just uses the os.* library of functions as the VFS. If in doubt, use this function over SecureJoinVFS.

func SecureJoinVFS

func SecureJoinVFS(root, unsafePath string, vfs VFS) (string, error)

SecureJoinVFS joins the two given path components (similar to Join) except that the returned path is guaranteed to be scoped inside the provided root path (when evaluated). Any symbolic links in the path are evaluated with the given root treated as the root of the filesystem, similar to a chroot. The filesystem state is evaluated through the given VFS interface (if nil, the standard os.* family of functions are used).

Note that the guarantees provided by this function only apply if the path components in the returned string are not modified (in other words are not replaced with symlinks on the filesystem) after this function has returned. Such a symlink race is necessarily out-of-scope of SecureJoin.

NOTE: Due to the above limitation, Linux users are strongly encouraged to use OpenInRoot instead, which does safely protect against these kinds of attacks. There is no way to solve this problem with SecureJoinVFS because the API is fundamentally wrong (you cannot return a "safe" path string and guarantee it won't be modified afterwards).

Volume names in unsafePath are always discarded, regardless if they are provided via direct input or when evaluating symlinks. Therefore:

"C:\Temp" + "D:\path\to\file.txt" results in "C:\Temp\path\to\file.txt"

Types

type VFS

type VFS interface {
	// Lstat returns a FileInfo describing the named file. If the file is a
	// symbolic link, the returned FileInfo describes the symbolic link. Lstat
	// makes no attempt to follow the link. These semantics are identical to
	// os.Lstat.
	Lstat(name string) (os.FileInfo, error)

	// Readlink returns the destination of the named symbolic link. These
	// semantics are identical to os.Readlink.
	Readlink(name string) (string, error)
}

VFS is the minimal interface necessary to use SecureJoinVFS. A nil VFS is equivalent to using the standard os.* family of functions. This is mainly used for the purposes of mock testing, but also can be used to otherwise use SecureJoin with VFS-like system.

Source Files

join.go lookup_linux.go mkdir_linux.go open_linux.go openat2_linux.go openat_linux.go procfs_linux.go testing_mocks_linux.go vfs.go

Version
v0.3.0
Published
Jul 11, 2024
Platform
linux/amd64
Imports
14 packages
Last checked
now

Tools for package owners.