//go:build windows package safeio import ( "os" "golang.org/x/sys/windows" ) const windowsRetainedHandleShareMode uint32 = windows.FILE_SHARE_READ | windows.FILE_SHARE_WRITE // ReadCanonicalRegular resolves every component under the already-opened parent with NT // RootDirectory-relative opens. Each retained handle rejects reparse points and denies delete // sharing, so a later ancestor replacement cannot redirect the final regular-file open. func ReadCanonicalRegular(path string, maximum int64) ([]byte, error) { if err := ValidateCanonicalPath(path); err != nil { return nil, err } parents, target, err := openCanonicalWindowsParent(path) if err != nil || parents == nil || len(parents.handles) == 0 { if parents != nil { parents.Close() } return nil, ErrUnsafeFile } defer parents.Close() handle, err := openWindowsRelativeComponent(parents.handles[len(parents.handles)-1], target, false, windows.GENERIC_READ) if err != nil { return nil, ErrUnsafeFile } file := os.NewFile(uintptr(handle), "tht-safeio") if file == nil { windows.CloseHandle(handle) return nil, ErrUnsafeFile } defer file.Close() return readBoundedRegularFile(path, file, maximum) } func openWindowsComponent(path string, directory bool) (windows.Handle, error) { return openWindowsComponentWithAccess(path, directory, windows.GENERIC_READ) } func openWindowsComponentWithAccess(path string, directory bool, access uint32) (windows.Handle, error) { flags := uint32(windows.FILE_FLAG_OPEN_REPARSE_POINT) if directory { flags |= windows.FILE_FLAG_BACKUP_SEMANTICS } else { flags |= windows.FILE_ATTRIBUTE_NORMAL } handle, err := windows.CreateFile( windows.StringToUTF16Ptr(path), access, windowsRetainedHandleShareMode, nil, windows.OPEN_EXISTING, flags, 0, ) if err != nil { return 0, err } var information windows.ByHandleFileInformation if err := windows.GetFileInformationByHandle(handle, &information); err != nil { windows.CloseHandle(handle) return 0, err } if information.FileAttributes&windows.FILE_ATTRIBUTE_REPARSE_POINT != 0 || (directory && information.FileAttributes&windows.FILE_ATTRIBUTE_DIRECTORY == 0) || (!directory && (information.FileAttributes&windows.FILE_ATTRIBUTE_DIRECTORY != 0 || information.NumberOfLinks != 1)) { windows.CloseHandle(handle) return 0, ErrUnsafeFile } return handle, nil } // openWindowsRelativeComponent is the generic NT equivalent of openat for one canonical leaf. // Its root handle has already pinned all lexical ancestors; FILE_OPEN_REPARSE_POINT makes a // reparse point observable so it can be rejected rather than followed. func openWindowsRelativeComponent(parent windows.Handle, name string, directory bool, access uint32) (windows.Handle, error) { options := uint32(windows.FILE_SYNCHRONOUS_IO_NONALERT | windows.FILE_OPEN_REPARSE_POINT) if directory { options |= windows.FILE_DIRECTORY_FILE } else { options |= windows.FILE_NON_DIRECTORY_FILE } handle, err := openWindowsRelativeObject(parent, name, access, windows.FILE_OPEN, options, nil) if err != nil { return 0, err } var information windows.ByHandleFileInformation if err := windows.GetFileInformationByHandle(handle, &information); err != nil { _ = windows.CloseHandle(handle) return 0, err } if information.FileAttributes&windows.FILE_ATTRIBUTE_REPARSE_POINT != 0 || (directory && information.FileAttributes&windows.FILE_ATTRIBUTE_DIRECTORY == 0) || (!directory && (information.FileAttributes&windows.FILE_ATTRIBUTE_DIRECTORY != 0 || information.NumberOfLinks != 1)) { _ = windows.CloseHandle(handle) return 0, ErrUnsafeFile } return handle, nil } func closeWindowsHandles(handles []windows.Handle) { for _, handle := range handles { windows.CloseHandle(handle) } }