1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
|
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
* Copyright (C) 2023 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package osutil
import (
"fmt"
"os"
"path/filepath"
"strings"
)
func dumbJoin(a, b string) string {
if strings.HasSuffix(a, "/") {
return a + b
} else {
return a + "/" + b
}
}
func resolvePathInSysrootRec(sysroot, path string, errorOnEscape bool, symlinkRecursion int) (string, error) {
if path == "" || path == "/" {
// Relative paths are taken from sysroot
return "/", nil
}
if strings.HasSuffix(path, "/") {
path = path[:len(path)-1]
}
dir, file := filepath.Split(path)
resolvedDir, err := resolvePathInSysrootRec(sysroot, dir, errorOnEscape, symlinkRecursion)
if err != nil {
return "", err
}
if file == "" {
return resolvedDir, nil
}
if file == "." {
return resolvedDir, nil
}
if file == ".." {
if errorOnEscape && (resolvedDir == "/") {
return "", fmt.Errorf("invalid escaping path")
}
upperDir, _ := filepath.Split(resolvedDir)
return upperDir, nil
}
fileInResolvedDir := dumbJoin(resolvedDir, file)
realPath := dumbJoin(sysroot, fileInResolvedDir)
st, err := os.Lstat(realPath)
if err != nil {
return "", err
}
if st.Mode()&os.ModeSymlink != 0 {
if symlinkRecursion < 0 {
return "", fmt.Errorf("maximum recursion reached when reading symlinks")
}
target, err := os.Readlink(realPath)
if err != nil {
return "", err
}
if filepath.IsAbs(target) {
if errorOnEscape {
return "", fmt.Errorf("invalid absolute symlink")
}
return resolvePathInSysrootRec(sysroot, target, errorOnEscape, symlinkRecursion-1)
} else {
return resolvePathInSysrootRec(sysroot, dumbJoin(resolvedDir, target), errorOnEscape, symlinkRecursion-1)
}
}
return fileInResolvedDir, nil
}
// ResolvePathInSysroot resolves a path within a sysroot
//
// In a sysroot, abolute symlinks should be relative to the sysroot
// rather than `/`. Also paths with multiple `..` that would escape
// the sysroot should not do so.
//
// The path must point to a file that exists.
//
// Example 1:
// - /sysroot/path1/a is a symlink pointing to /path2/b
// - /sysroot/path2/b is a symlink pointing to /path3/c
// - /sysroot/path3/c is a file
// ResolvePathInSysroot("/sysroot", "/path1/a") will return "/path3/c"
//
// Example 2:
// - /sysroot/path1/a is a symlink pointing to ../../../path2/b
// - /sysroot/path2/b is a symlink pointing to ../../../path3/c
// - /sysroot/path3/c is a file
// ResolvePathInSysroot("/sysroot", "../../../path1/a") will return "/path3/c"
//
// Example 3:
// - /sysroot/path1/a is a symlink pointing to /path2/b
// - /sysroot/path2/b does not exist
// ResolvePathInSysroot("/sysroot", "/path1/a") will fail (IsNotExist)
//
// Example 4:
// - /sysroot/foo is a file or a directory
// - ResolvePathInSysroot("/sysroot", "/../../../../foo") will return "/foo"
//
// The return path is the path within the sysroot. filepath.Join() has
// to be used to get the path in the sysroot.
func ResolvePathInSysroot(sysroot, path string) (string, error) {
return resolvePathInSysrootRec(sysroot, path, false, 255)
}
// ResolvePathNoEscape resolves a path within a pseudo sysroot
//
// Like ResolvePathInSysroot(), it resolves path as if it was a sysroot.
// However, any escaping relative path, or absolute symlink generates
// an error.
//
// The input path can however be absolute, and will be treated as
// relative.
//
// This is useful when a path is expected to be relative only and sees
// any "attempt" to escape the sysroot as a malformed path.
func ResolvePathNoEscape(sysroot, path string) (string, error) {
return resolvePathInSysrootRec(sysroot, path, true, 255)
}
|