
Ensure symlinks cannot be used to breakout of unpack directory. Evaluate absolute symlinks as scoped to unpack directory. Allow symlinks which point outside the root to be created. Scope all resolution of symlinks to the unpack directory. Signed-off-by: Derek McGowan <derek@mcgstyle.net>
124 lines
2.7 KiB
Go
124 lines
2.7 KiB
Go
package archive
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
var (
|
|
errTooManyLinks = errors.New("too many links")
|
|
)
|
|
|
|
// rootPath joins a path with a root, evaluating and bounding any
|
|
// symlink to the root directory.
|
|
// TODO(dmcgowan): Expose and move to fs package or continuity path driver
|
|
func rootPath(root, path string) (string, error) {
|
|
if path == "" {
|
|
return root, nil
|
|
}
|
|
var linksWalked int // to protect against cycles
|
|
for {
|
|
i := linksWalked
|
|
newpath, err := walkLinks(root, path, &linksWalked)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
path = newpath
|
|
if i == linksWalked {
|
|
newpath = rootJoin(newpath)
|
|
if path == newpath {
|
|
return filepath.Join(root, newpath), nil
|
|
}
|
|
path = newpath
|
|
}
|
|
}
|
|
}
|
|
|
|
// rootJoin joins a path with root, cleaning up any links that
|
|
// reference above root.
|
|
func rootJoin(path string) string {
|
|
if filepath.IsAbs(path) {
|
|
path = filepath.Clean(path)
|
|
}
|
|
|
|
// Resolve any ".." or "/.." before joining to root
|
|
for !filepath.IsAbs(path) {
|
|
path = "/" + path
|
|
path = filepath.Clean(path)
|
|
}
|
|
|
|
return path
|
|
}
|
|
|
|
func walkLink(root, path string, linksWalked *int) (newpath string, islink bool, err error) {
|
|
if *linksWalked > 255 {
|
|
return "", false, errTooManyLinks
|
|
}
|
|
|
|
path = rootJoin(path)
|
|
if path == "/" {
|
|
return path, false, nil
|
|
}
|
|
realPath := filepath.Join(root, path)
|
|
|
|
fi, err := os.Lstat(realPath)
|
|
if err != nil {
|
|
// If path does not yet exist, treat as non-symlink
|
|
if os.IsNotExist(err) {
|
|
return path, false, nil
|
|
}
|
|
return "", false, err
|
|
}
|
|
if fi.Mode()&os.ModeSymlink == 0 {
|
|
return path, false, nil
|
|
}
|
|
newpath, err = os.Readlink(realPath)
|
|
if err != nil {
|
|
return "", false, err
|
|
}
|
|
if filepath.IsAbs(newpath) && strings.HasPrefix(newpath, root) {
|
|
newpath = newpath[:len(root)]
|
|
if !strings.HasPrefix(newpath, "/") {
|
|
newpath = "/" + newpath
|
|
}
|
|
}
|
|
*linksWalked++
|
|
return newpath, true, nil
|
|
}
|
|
|
|
func walkLinks(root, path string, linksWalked *int) (string, error) {
|
|
switch dir, file := filepath.Split(path); {
|
|
case dir == "":
|
|
newpath, _, err := walkLink(root, file, linksWalked)
|
|
return newpath, err
|
|
case file == "":
|
|
if os.IsPathSeparator(dir[len(dir)-1]) {
|
|
if dir == "/" {
|
|
return dir, nil
|
|
}
|
|
return walkLinks(root, dir[:len(dir)-1], linksWalked)
|
|
}
|
|
newpath, _, err := walkLink(root, dir, linksWalked)
|
|
return newpath, err
|
|
default:
|
|
newdir, err := walkLinks(root, dir, linksWalked)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
newpath, islink, err := walkLink(root, filepath.Join(newdir, file), linksWalked)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if !islink {
|
|
return newpath, nil
|
|
}
|
|
if filepath.IsAbs(newpath) {
|
|
return newpath, nil
|
|
}
|
|
return filepath.Join(newdir, newpath), nil
|
|
}
|
|
}
|