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, endsInSlash, 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 && !endsInSlash {
    67  				break
    68  			}
    69  		}
    70  
    71  		next := joinPath(base, part)
    72  		fi, err := Lstat(next)
    73  		if err != nil {
    74  			if IsNotExist(err) {
    75  				return nil
    76  			}
    77  			return underlyingError(err)
    78  		}
    79  		if fi.Mode()&ModeSymlink != 0 {
    80  			link, err := Readlink(next)
    81  			if err != nil {
    82  				return errPathEscapes
    83  			}
    84  			symlinks++
    85  			if symlinks > rootMaxSymlinks {
    86  				return errors.New("too many symlinks")
    87  			}
    88  			newparts, newEndsInSlash, err := splitPathInRoot(link, parts[:i], parts[i+1:])
    89  			if err != nil {
    90  				return err
    91  			}
    92  			if i == len(parts)-1 && newEndsInSlash {
    93  				endsInSlash = true
    94  			}
    95  			parts = newparts
    96  			continue
    97  		}
    98  		if !fi.IsDir() && i < len(parts)-1 {
    99  			return syscall.ENOTDIR
   100  		}
   101  
   102  		base = next
   103  		i++
   104  	}
   105  	return nil
   106  }
   107  

View as plain text