-
Volker Schukai authoredVolker Schukai authored
filepath-securejoin
Old API
This library was originally just an implementation of SecureJoin
which was
intended to be included in the Go standard library as a safer
filepath.Join
that would restrict the path lookup to be inside a root
directory.
The implementation was based on code that existed in several container
runtimes. Unfortunately, this API is fundamentally unsafe against attackers
that can modify path components after SecureJoin
returns and before the
caller uses the path, allowing for some fairly trivial TOCTOU attacks.
SecureJoin
(and SecureJoinVFS
) are still provided by this library to
support legacy users, but new users are strongly suggested to avoid using
SecureJoin
and instead use the new api or switch to
libpathrs.
With the above limitations in mind, this library guarantees the following:
-
If no error is set, the resulting string must be a child path of
root
and will not contain any symlink path components (they will all be expanded). -
When expanding symlinks, all symlink path components must be resolved relative to the provided root. In particular, this can be considered a userspace implementation of how
chroot(2)
operates on file paths. Note that these symlinks will not be expanded lexically (filepath.Clean
is not called on the input before processing). -
Non-existent path components are unaffected by
SecureJoin
(similar tofilepath.EvalSymlinks
's semantics). -
The returned path will always be
filepath.Clean
ed and thus not contain any..
components.
A (trivial) implementation of this function on GNU/Linux systems could be done
with the following (note that this requires root privileges and is far more
opaque than the implementation in this library, and also requires that
readlink
is inside the root
path and is trustworthy):
package securejoin
import (
"os/exec"
"path/filepath"
)
func SecureJoin(root, unsafePath string) (string, error) {
unsafePath = string(filepath.Separator) + unsafePath
cmd := exec.Command("chroot", root,
"readlink", "--canonicalize-missing", "--no-newline", unsafePath)
output, err := cmd.CombinedOutput()
if err != nil {
return "", err
}
expanded := string(output)
return filepath.Join(root, expanded), nil
}