| // Copyright 2024 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| //go:build js && wasm |
| |
| package os |
| |
| import ( |
| "errors" |
| "slices" |
| "syscall" |
| ) |
| |
| // checkPathEscapes reports whether name escapes the root. |
| // |
| // Due to the lack of openat, checkPathEscapes is subject to TOCTOU races |
| // when symlinks change during the resolution process. |
| func checkPathEscapes(r *Root, name string) error { |
| return checkPathEscapesInternal(r, name, false) |
| } |
| |
| // checkPathEscapesLstat reports whether name escapes the root. |
| // It does not resolve symlinks in the final path component. |
| // |
| // Due to the lack of openat, checkPathEscapes is subject to TOCTOU races |
| // when symlinks change during the resolution process. |
| func checkPathEscapesLstat(r *Root, name string) error { |
| return checkPathEscapesInternal(r, name, true) |
| } |
| |
| func checkPathEscapesInternal(r *Root, name string, lstat bool) error { |
| if r.root.closed.Load() { |
| return ErrClosed |
| } |
| parts, suffixSep, err := splitPathInRoot(name, nil, nil) |
| if err != nil { |
| return err |
| } |
| |
| i := 0 |
| symlinks := 0 |
| base := r.root.name |
| for i < len(parts) { |
| if parts[i] == ".." { |
| // Resolve one or more parent ("..") path components. |
| end := i + 1 |
| for end < len(parts) && parts[end] == ".." { |
| end++ |
| } |
| count := end - i |
| if count > i { |
| return errPathEscapes |
| } |
| parts = slices.Delete(parts, i-count, end) |
| i -= count |
| base = r.root.name |
| for j := range i { |
| base = joinPath(base, parts[j]) |
| } |
| continue |
| } |
| |
| part := parts[i] |
| if i == len(parts)-1 { |
| if lstat { |
| break |
| } |
| part += suffixSep |
| } |
| |
| next := joinPath(base, part) |
| fi, err := Lstat(next) |
| if err != nil { |
| if IsNotExist(err) { |
| return nil |
| } |
| return underlyingError(err) |
| } |
| if fi.Mode()&ModeSymlink != 0 { |
| link, err := Readlink(next) |
| if err != nil { |
| return errPathEscapes |
| } |
| symlinks++ |
| if symlinks > rootMaxSymlinks { |
| return errors.New("too many symlinks") |
| } |
| newparts, newSuffixSep, err := splitPathInRoot(link, parts[:i], parts[i+1:]) |
| if err != nil { |
| return err |
| } |
| if i == len(parts) { |
| // suffixSep contains any trailing path separator characters |
| // in the link target. |
| // If we are replacing the remainder of the path, retain these. |
| // If we're replacing some intermediate component of the path, |
| // ignore them, since intermediate components must always be |
| // directories. |
| suffixSep = newSuffixSep |
| } |
| parts = newparts |
| continue |
| } |
| if !fi.IsDir() && i < len(parts)-1 { |
| return syscall.ENOTDIR |
| } |
| |
| base = next |
| i++ |
| } |
| return nil |
| } |