Source file src/os/root_js.go

     1  // Copyright 2024 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  //go:build js && wasm
     6  
     7  package os
     8  
     9  import (
    10  	"errors"
    11  	"slices"
    12  	"syscall"
    13  )
    14  
    15  // checkPathEscapes reports whether name escapes the root.
    16  //
    17  // Due to the lack of openat, checkPathEscapes is subject to TOCTOU races
    18  // when symlinks change during the resolution process.
    19  func checkPathEscapes(r *Root, name string) error {
    20  	return checkPathEscapesInternal(r, name, false)
    21  }
    22  
    23  // checkPathEscapesLstat reports whether name escapes the root.
    24  // It does not resolve symlinks in the final path component.
    25  //
    26  // Due to the lack of openat, checkPathEscapes is subject to TOCTOU races
    27  // when symlinks change during the resolution process.
    28  func checkPathEscapesLstat(r *Root, name string) error {
    29  	return checkPathEscapesInternal(r, name, true)
    30  }
    31  
    32  func checkPathEscapesInternal(r *Root, name string, lstat bool) error {
    33  	if r.root.closed.Load() {
    34  		return ErrClosed
    35  	}
    36  	parts, suffixSep, err := splitPathInRoot(name, nil, nil)
    37  	if err != nil {
    38  		return err
    39  	}
    40  
    41  	i := 0
    42  	symlinks := 0
    43  	base := r.root.name
    44  	for i < len(parts) {
    45  		if parts[i] == ".." {
    46  			// Resolve one or more parent ("..") path components.
    47  			end := i + 1
    48  			for end < len(parts) && parts[end] == ".." {
    49  				end++
    50  			}
    51  			count := end - i
    52  			if count > i {
    53  				return errPathEscapes
    54  			}
    55  			parts = slices.Delete(parts, i-count, end)
    56  			i -= count
    57  			base = r.root.name
    58  			for j := range i {
    59  				base = joinPath(base, parts[j])
    60  			}
    61  			continue
    62  		}
    63  
    64  		part := parts[i]
    65  		if i == len(parts)-1 {
    66  			if lstat {
    67  				break
    68  			}
    69  			part += suffixSep
    70  		}
    71  
    72  		next := joinPath(base, part)
    73  		fi, err := Lstat(next)
    74  		if err != nil {
    75  			if IsNotExist(err) {
    76  				return nil
    77  			}
    78  			return underlyingError(err)
    79  		}
    80  		if fi.Mode()&ModeSymlink != 0 {
    81  			link, err := Readlink(next)
    82  			if err != nil {
    83  				return errPathEscapes
    84  			}
    85  			symlinks++
    86  			if symlinks > rootMaxSymlinks {
    87  				return errors.New("too many symlinks")
    88  			}
    89  			newparts, newSuffixSep, err := splitPathInRoot(link, parts[:i], parts[i+1:])
    90  			if err != nil {
    91  				return err
    92  			}
    93  			if i == len(parts) {
    94  				// suffixSep contains any trailing path separator characters
    95  				// in the link target.
    96  				// If we are replacing the remainder of the path, retain these.
    97  				// If we're replacing some intermediate component of the path,
    98  				// ignore them, since intermediate components must always be
    99  				// directories.
   100  				suffixSep = newSuffixSep
   101  			}
   102  			parts = newparts
   103  			continue
   104  		}
   105  		if !fi.IsDir() && i < len(parts)-1 {
   106  			return syscall.ENOTDIR
   107  		}
   108  
   109  		base = next
   110  		i++
   111  	}
   112  	return nil
   113  }
   114  

View as plain text