github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/helper/escapingfs/escapes.go (about) 1 package escapingfs 2 3 import ( 4 "errors" 5 "os" 6 "path/filepath" 7 "strings" 8 ) 9 10 // PathEscapesAllocViaRelative returns if the given path escapes the allocation 11 // directory using relative paths. 12 // 13 // Only for use in server-side validation, where the real filesystem is not available. 14 // For client-side validation use PathEscapesAllocDir, which includes symlink validation 15 // as well. 16 // 17 // The prefix is joined to the path (e.g. "task/local"), and this function 18 // checks if path escapes the alloc dir, NOT the prefix directory within the alloc dir. 19 // With prefix="task/local", it will return false for "../secret", but 20 // true for "../../../../../../root" path; only the latter escapes the alloc dir. 21 func PathEscapesAllocViaRelative(prefix, path string) (bool, error) { 22 // Verify the destination does not escape the task's directory. The "alloc-dir" 23 // and "alloc-id" here are just placeholders; on a real filesystem they will 24 // have different names. The names are not important, but rather the number of levels 25 // in the path they represent. 26 alloc, err := filepath.Abs(filepath.Join("/", "alloc-dir/", "alloc-id/")) 27 if err != nil { 28 return false, err 29 } 30 abs, err := filepath.Abs(filepath.Join(alloc, prefix, path)) 31 if err != nil { 32 return false, err 33 } 34 rel, err := filepath.Rel(alloc, abs) 35 if err != nil { 36 return false, err 37 } 38 39 return strings.HasPrefix(rel, ".."), nil 40 } 41 42 // pathEscapesBaseViaSymlink returns if path escapes dir, taking into account evaluation 43 // of symlinks. 44 // 45 // The base directory must be an absolute path. 46 func pathEscapesBaseViaSymlink(base, full string) (bool, error) { 47 resolveSym, err := filepath.EvalSymlinks(full) 48 if err != nil { 49 return false, err 50 } 51 52 rel, err := filepath.Rel(resolveSym, base) 53 if err != nil { 54 return true, nil 55 } 56 57 // note: this is not the same as !filesystem.IsAbs; we are asking if the relative 58 // path is descendent of the base path, indicating it does not escape. 59 isRelative := strings.HasPrefix(rel, "..") || rel == "." 60 escapes := !isRelative 61 return escapes, nil 62 } 63 64 // PathEscapesAllocDir returns true if base/prefix/path escapes the given base directory. 65 // 66 // Escaping a directory can be done with relative paths (e.g. ../../ etc.) or by 67 // using symlinks. This checks both methods. 68 // 69 // The base directory must be an absolute path. 70 func PathEscapesAllocDir(base, prefix, path string) (bool, error) { 71 full := filepath.Join(base, prefix, path) 72 73 // If base is not an absolute path, the caller passed in the wrong thing. 74 if !filepath.IsAbs(base) { 75 return false, errors.New("alloc dir must be absolute") 76 } 77 78 // Check path does not escape the alloc dir using relative paths. 79 if escapes, err := PathEscapesAllocViaRelative(prefix, path); err != nil { 80 return false, err 81 } else if escapes { 82 return true, nil 83 } 84 85 // Check path does not escape the alloc dir using symlinks. 86 if escapes, err := pathEscapesBaseViaSymlink(base, full); err != nil { 87 if os.IsNotExist(err) { 88 // Treat non-existent files as non-errors; perhaps not ideal but we 89 // have existing features (log-follow) that depend on this. Still safe, 90 // because we do the symlink check on every ReadAt call also. 91 return false, nil 92 } 93 return false, err 94 } else if escapes { 95 return true, nil 96 } 97 98 return false, nil 99 } 100 101 // PathEscapesSandbox returns whether previously cleaned path inside the 102 // sandbox directory (typically this will be the allocation directory) 103 // escapes. 104 func PathEscapesSandbox(sandboxDir, path string) bool { 105 rel, err := filepath.Rel(sandboxDir, path) 106 if err != nil { 107 return true 108 } 109 if strings.HasPrefix(rel, "..") { 110 return true 111 } 112 return false 113 } 114 115 // EnsurePath is used to make sure a path exists 116 func EnsurePath(path string, dir bool) error { 117 if !dir { 118 path = filepath.Dir(path) 119 } 120 return os.MkdirAll(path, 0755) 121 }