github.com/moby/docker@v26.1.3+incompatible/internal/safepath/join_windows.go (about)

     1  package safepath
     2  
     3  import (
     4  	"context"
     5  	"os"
     6  	"path/filepath"
     7  	"strings"
     8  
     9  	"github.com/containerd/log"
    10  	"github.com/docker/docker/internal/cleanups"
    11  	"github.com/docker/docker/internal/compatcontext"
    12  	"github.com/pkg/errors"
    13  	"golang.org/x/sys/windows"
    14  )
    15  
    16  // Join locks all individual components of the path which is the concatenation
    17  // of provided path and its subpath, checks that it doesn't escape the base path
    18  // and returns the concatenated path.
    19  //
    20  // The path is safe (the path target won't change) until the returned SafePath
    21  // is Closed.
    22  // Caller is responsible for calling the Close function which unlocks the path.
    23  func Join(ctx context.Context, path, subpath string) (*SafePath, error) {
    24  	base, subpart, err := evaluatePath(path, subpath)
    25  	if err != nil {
    26  		return nil, err
    27  	}
    28  	parts := strings.Split(subpart, string(os.PathSeparator))
    29  
    30  	cleanups := cleanups.Composite{}
    31  	defer func() {
    32  		if cErr := cleanups.Call(compatcontext.WithoutCancel(ctx)); cErr != nil {
    33  			log.G(ctx).WithError(cErr).Warn("failed to close handles after error")
    34  		}
    35  	}()
    36  
    37  	fullPath := base
    38  	for _, part := range parts {
    39  		fullPath = filepath.Join(fullPath, part)
    40  
    41  		handle, err := lockFile(fullPath)
    42  		if err != nil {
    43  			if errors.Is(err, windows.ERROR_FILE_NOT_FOUND) {
    44  				return nil, &ErrNotAccessible{Path: fullPath, Cause: err}
    45  			}
    46  			return nil, errors.Wrapf(err, "failed to lock file %s", fullPath)
    47  		}
    48  		cleanups.Add(func(context.Context) error {
    49  			if err := windows.CloseHandle(handle); err != nil {
    50  				return &os.PathError{Op: "CloseHandle", Path: fullPath, Err: err}
    51  			}
    52  			return err
    53  		})
    54  
    55  		realPath, err := filepath.EvalSymlinks(fullPath)
    56  		if err != nil {
    57  			return nil, errors.Wrapf(err, "failed to eval symlinks of %s", fullPath)
    58  		}
    59  
    60  		if realPath != fullPath && !isLocalTo(realPath, base) {
    61  			return nil, &ErrEscapesBase{Base: base, Subpath: subpart}
    62  		}
    63  
    64  		var info windows.ByHandleFileInformation
    65  		if err := windows.GetFileInformationByHandle(handle, &info); err != nil {
    66  			return nil, errors.WithStack(&os.PathError{Op: "GetFileInformationByHandle", Path: fullPath, Err: err})
    67  		}
    68  
    69  		if (info.FileAttributes & windows.FILE_ATTRIBUTE_REPARSE_POINT) != 0 {
    70  			return nil, &ErrNotAccessible{Path: fullPath, Cause: err}
    71  		}
    72  	}
    73  
    74  	return &SafePath{
    75  		path:          fullPath,
    76  		sourceBase:    base,
    77  		sourceSubpath: subpart,
    78  		cleanup:       cleanups.Release(),
    79  	}, nil
    80  }
    81  
    82  func lockFile(path string) (windows.Handle, error) {
    83  	p, err := windows.UTF16PtrFromString(path)
    84  	if err != nil {
    85  		return windows.InvalidHandle, &os.PathError{Op: "UTF16PtrFromString", Path: path, Err: err}
    86  	}
    87  	const flags = windows.FILE_FLAG_BACKUP_SEMANTICS | windows.FILE_FLAG_OPEN_REPARSE_POINT
    88  	handle, err := windows.CreateFile(p, windows.GENERIC_READ, windows.FILE_SHARE_READ, nil, windows.OPEN_EXISTING, flags, 0)
    89  	if err != nil {
    90  		return handle, &os.PathError{Op: "CreateFile", Path: path, Err: err}
    91  	}
    92  	return handle, nil
    93  }