// Package rootpath maps paths inside an image to their place in the // unpacked rootfs on the host, the way the sandbox will see them. package rootpath import ( "fmt" "os" "path" "path/filepath" "strings" ) // Resolve maps an absolute in-image path to its host location // under rootfs, following symlinks the way the sandbox would: relative // to the link's directory, absolute ones relative to rootfs, and ".." // above the root stays at the root, exactly as in a chroot — so a // hostile layer can rewrite the image, never the host. Components that // do not exist yet are appended verbatim. func Resolve(rootfs, name string) (string, error) { root := filepath.Clean(rootfs) resolved := root remaining := strings.Split(strings.Trim(path.Clean("/"+name), "/"), "/") for hops := 0; len(remaining) > 0; { component := remaining[0] remaining = remaining[1:] switch component { case "", ".": continue case "..": if resolved != root { resolved = filepath.Dir(resolved) } continue } next := filepath.Join(resolved, component) fi, err := os.Lstat(next) if err != nil || fi.Mode()&os.ModeSymlink == 0 { resolved = next continue } if hops++; hops > 64 { return "", fmt.Errorf("%s: too many levels of symbolic links", name) } link, err := os.Readlink(next) if err != nil { return "", err } linkParts := strings.Split(strings.Trim(path.Clean(link), "/"), "/") if path.IsAbs(link) { resolved = root } remaining = append(linkParts, remaining...) } if resolved != root && !strings.HasPrefix(resolved, root+string(filepath.Separator)) { return "", fmt.Errorf("%s: escapes the rootfs", name) } return resolved, nil }