//go:build windows package safeio import ( "path/filepath" "sync" "golang.org/x/sys/windows" ) // Windows retained directory handles intentionally omit FILE_SHARE_DELETE. Serialize the // complete path-based claim operations within this process so one operation cannot exhaust // another's bounded external-contention retry while its validated parent handle is retained. var windowsPrivateClaimOperationMu sync.Mutex func lockCanonicalPrivateClaimOperation() func() { windowsPrivateClaimOperationMu.Lock() return windowsPrivateClaimOperationMu.Unlock } type windowsPrivateRegular struct { parents *windowsParentHandles handle windows.Handle info windows.ByHandleFileInformation } func (value *windowsPrivateRegular) Close() { if value.handle != 0 { _ = windows.CloseHandle(value.handle) value.handle = 0 } if value.parents != nil { value.parents.Close() value.parents = nil } } func openWindowsPrivateRegular(path string, links uint32) (*windowsPrivateRegular, error) { parents, target, err := openCanonicalWindowsParent(path) if err != nil || parents == nil || len(parents.handles) == 0 || validateOwnerOnlyDACL(parents.handles[len(parents.handles)-1]) != nil { if parents != nil { parents.Close() } return nil, ErrUnsafeFile } value, err := openWindowsPrivateRegularAt(parents.handles[len(parents.handles)-1], target, windows.GENERIC_READ, links) if err != nil { parents.Close() return nil, err } handle := value.handle value.handle = 0 return &windowsPrivateRegular{ parents: parents, handle: handle, info: value.info, }, nil } func claimCanonicalPrivateRegular(source, claim string) (claimed bool, resultErr error) { directory, sourceName, claimName, err := openWindowsPrivateClaimDirectory(source, claim) if err != nil { return false, ErrUnsafeFile } defer func() { if closeErr := directory.Close(); closeErr != nil && resultErr == nil { claimed = false resultErr = ErrUnsafeFile } }() return directory.ClaimRegular(sourceName, claimName) } func readCanonicalPrivateClaim(source, claim string, maximum int64) (contents []byte, found bool, resultErr error) { directory, sourceName, claimName, err := openWindowsPrivateClaimDirectory(source, claim) if err != nil { return nil, false, ErrUnsafeFile } defer func() { if closeErr := directory.Close(); closeErr != nil && resultErr == nil { contents = nil found = false resultErr = ErrUnsafeFile } }() return directory.ReadClaim(sourceName, claimName, maximum) } func openWindowsPrivateClaimDirectory(source, claim string) (PrivateDirectoryHandle, string, string, error) { parentPath := filepath.Dir(source) sourceName := filepath.Base(source) claimName := filepath.Base(claim) if parentPath != filepath.Dir(claim) || !validPrivateLeafName(sourceName) || !validPrivateLeafName(claimName) { return nil, "", "", ErrUnsafeFile } directory, found, err := OpenPrivateDirectory(parentPath, false) if err != nil || !found { if directory != nil { _ = directory.Close() } return nil, "", "", ErrUnsafeFile } return directory, sourceName, claimName, nil } func removeCanonicalPrivateClaim(source, claim string) (removed bool, resultErr error) { directory, sourceName, claimName, err := openWindowsPrivateClaimDirectory(source, claim) if err != nil { return false, ErrUnsafeFile } defer func() { if closeErr := directory.Close(); closeErr != nil && resultErr == nil { resultErr = ErrUnsafeFile } }() NotifyPrivateDirectoryTestHookForTest("after-canonical-private-claim-parent-open") return directory.RemoveClaim(sourceName, claimName) } func sameWindowsPrivateFile(left, right windows.ByHandleFileInformation) bool { return left.VolumeSerialNumber == right.VolumeSerialNumber && left.FileIndexHigh == right.FileIndexHigh && left.FileIndexLow == right.FileIndexLow && left.FileSizeHigh == right.FileSizeHigh && left.FileSizeLow == right.FileSizeLow && left.LastWriteTime == right.LastWriteTime }