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 }