922 lines
28 KiB
Go
922 lines
28 KiB
Go
//go:build windows
|
|
|
|
package safeio
|
|
|
|
import (
|
|
"errors"
|
|
"io"
|
|
"os"
|
|
"runtime"
|
|
"sort"
|
|
"time"
|
|
"unsafe"
|
|
|
|
"golang.org/x/sys/windows"
|
|
)
|
|
|
|
// windowsPrivateDirectory keeps the root's canonical component handles alive, then uses NT
|
|
// RootDirectory-relative opens for every descendant. Unlike a lexical child path, an NT relative
|
|
// object name is resolved by the already-open directory handle and cannot be redirected by a
|
|
// rename, replacement, or reparse point at the original root path.
|
|
type windowsPrivateDirectory struct {
|
|
anchors *windowsParentHandles
|
|
parent windows.Handle
|
|
handle windows.Handle
|
|
info windows.ByHandleFileInformation
|
|
}
|
|
|
|
type windowsPrivateRegularAt struct {
|
|
handle windows.Handle
|
|
info windows.ByHandleFileInformation
|
|
}
|
|
|
|
func openPrivateDirectory(path string, ensure bool) (PrivateDirectoryHandle, bool, error) {
|
|
anchors, target, err := openCanonicalWindowsParent(path)
|
|
if err != nil || anchors == nil || len(anchors.handles) == 0 {
|
|
if anchors != nil {
|
|
anchors.Close()
|
|
}
|
|
return nil, false, ErrUnsafeFile
|
|
}
|
|
parent := anchors.handles[len(anchors.handles)-1]
|
|
handle, found, err := openWindowsPrivateDirectoryAt(parent, target, false)
|
|
if err != nil {
|
|
anchors.Close()
|
|
return nil, false, ErrUnsafeFile
|
|
}
|
|
if !found {
|
|
// The final root is absent. The retained canonical parent must prove that the same
|
|
// ensure operation could create it; validation itself must remain side-effect free.
|
|
// FILE_APPEND_DATA is the Win32 spelling of directory FILE_ADD_SUBDIRECTORY.
|
|
probe, probeErr := openWindowsComponentWithAccess(anchors.directory, true, windows.FILE_APPEND_DATA)
|
|
if probeErr != nil {
|
|
anchors.Close()
|
|
return nil, false, ErrUnsafeFile
|
|
}
|
|
_ = windows.CloseHandle(probe)
|
|
if !ensure {
|
|
anchors.Close()
|
|
return nil, false, nil
|
|
}
|
|
// The canonical parent chain remains pinned by anchors; this extra handle supplies
|
|
// FILE_ADD_SUBDIRECTORY for the one initial root creation without reopening a child
|
|
// beneath the private root lexically.
|
|
writableParent, writableErr := openWindowsComponentWithAccess(anchors.directory, true, windows.FILE_APPEND_DATA)
|
|
if writableErr != nil {
|
|
anchors.Close()
|
|
return nil, false, ErrUnsafeFile
|
|
}
|
|
handle, found, err = openWindowsPrivateDirectoryAt(writableParent, target, true)
|
|
_ = windows.CloseHandle(writableParent)
|
|
if err != nil || !found {
|
|
anchors.Close()
|
|
return nil, false, ErrUnsafeFile
|
|
}
|
|
}
|
|
value := &windowsPrivateDirectory{anchors: anchors, handle: handle}
|
|
if value.captureAndValidate() != nil {
|
|
_ = value.Close()
|
|
return nil, false, ErrUnsafeFile
|
|
}
|
|
return value, true, nil
|
|
}
|
|
|
|
func openWindowsPrivateDirectoryAt(parent windows.Handle, name string, ensure bool) (windows.Handle, bool, error) {
|
|
if parent == 0 || !validPrivateLeafName(name) {
|
|
return 0, false, ErrUnsafeFile
|
|
}
|
|
for attempt := 0; attempt < 2; attempt++ {
|
|
handle, err := openWindowsRelativeDirectory(parent, name)
|
|
if err == nil {
|
|
return handle, true, nil
|
|
}
|
|
if !isWindowsRelativeNotFound(err) {
|
|
return 0, false, ErrUnsafeFile
|
|
}
|
|
if !ensure {
|
|
return 0, false, nil
|
|
}
|
|
handle, err = createWindowsRelativePrivateDirectory(parent, name)
|
|
if err == nil {
|
|
return handle, true, nil
|
|
}
|
|
}
|
|
return 0, false, ErrUnsafeFile
|
|
}
|
|
|
|
func openWindowsRelativeDirectory(parent windows.Handle, name string) (windows.Handle, error) {
|
|
handle, err := openWindowsRelativeObject(
|
|
parent,
|
|
name,
|
|
// The retained directory handle is also the RootDirectory for create, rename,
|
|
// hard-link, and delete operations below, so it needs the owner's full private
|
|
// directory capability rather than a read-only probe handle.
|
|
windows.FILE_GENERIC_READ|windows.FILE_GENERIC_WRITE|windows.DELETE,
|
|
windows.FILE_OPEN,
|
|
windows.FILE_DIRECTORY_FILE|windows.FILE_SYNCHRONOUS_IO_NONALERT|windows.FILE_OPEN_REPARSE_POINT,
|
|
nil,
|
|
)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if _, err := privateWindowsDirectoryInfo(handle); err != nil {
|
|
_ = windows.CloseHandle(handle)
|
|
return 0, ErrUnsafeFile
|
|
}
|
|
return handle, nil
|
|
}
|
|
|
|
func createWindowsRelativePrivateDirectory(parent windows.Handle, name string) (windows.Handle, error) {
|
|
security, err := newOwnerOnlySecurityDescriptor()
|
|
if err != nil {
|
|
return 0, ErrUnsafeFile
|
|
}
|
|
defer security.Close()
|
|
handle, err := openWindowsRelativeObject(
|
|
parent,
|
|
name,
|
|
windows.FILE_GENERIC_READ|windows.FILE_GENERIC_WRITE|windows.DELETE,
|
|
windows.FILE_CREATE,
|
|
windows.FILE_DIRECTORY_FILE|windows.FILE_SYNCHRONOUS_IO_NONALERT|windows.FILE_OPEN_REPARSE_POINT,
|
|
security,
|
|
)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if _, err := privateWindowsDirectoryInfo(handle); err != nil {
|
|
_ = markWindowsHandleForDelete(handle)
|
|
_ = windows.CloseHandle(handle)
|
|
return 0, ErrUnsafeFile
|
|
}
|
|
return handle, nil
|
|
}
|
|
|
|
func openWindowsRelativeObject(
|
|
parent windows.Handle,
|
|
name string,
|
|
access uint32,
|
|
disposition uint32,
|
|
options uint32,
|
|
security *ownerOnlySecurityDescriptor,
|
|
) (windows.Handle, error) {
|
|
if parent == 0 || !validPrivateLeafName(name) {
|
|
return 0, ErrUnsafeFile
|
|
}
|
|
objectName, err := windows.NewNTUnicodeString(name)
|
|
if err != nil {
|
|
return 0, ErrUnsafeFile
|
|
}
|
|
attributes := &windows.OBJECT_ATTRIBUTES{
|
|
Length: uint32(unsafe.Sizeof(windows.OBJECT_ATTRIBUTES{})),
|
|
RootDirectory: parent,
|
|
ObjectName: objectName,
|
|
Attributes: windows.OBJ_CASE_INSENSITIVE,
|
|
SecurityDescriptor: nil,
|
|
}
|
|
if security != nil {
|
|
attributes.SecurityDescriptor = security.descriptor
|
|
}
|
|
var (
|
|
handle windows.Handle
|
|
status windows.IO_STATUS_BLOCK
|
|
allocationSize int64
|
|
)
|
|
err = windows.NtCreateFile(
|
|
&handle,
|
|
access,
|
|
attributes,
|
|
&status,
|
|
&allocationSize,
|
|
windows.FILE_ATTRIBUTE_NORMAL,
|
|
windowsRetainedHandleShareMode,
|
|
disposition,
|
|
options,
|
|
0,
|
|
0,
|
|
)
|
|
runtime.KeepAlive(objectName)
|
|
runtime.KeepAlive(security)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return handle, nil
|
|
}
|
|
|
|
func privateWindowsDirectoryInfo(handle windows.Handle) (windows.ByHandleFileInformation, error) {
|
|
var info windows.ByHandleFileInformation
|
|
if handle == 0 || windows.GetFileInformationByHandle(handle, &info) != nil ||
|
|
info.FileAttributes&windows.FILE_ATTRIBUTE_REPARSE_POINT != 0 ||
|
|
info.FileAttributes&windows.FILE_ATTRIBUTE_DIRECTORY == 0 ||
|
|
validateOwnerOnlyDACL(handle) != nil {
|
|
return windows.ByHandleFileInformation{}, ErrUnsafeFile
|
|
}
|
|
return info, nil
|
|
}
|
|
|
|
func privateWindowsRegularInfo(handle windows.Handle, allowedLinks ...uint32) (windows.ByHandleFileInformation, error) {
|
|
var info windows.ByHandleFileInformation
|
|
if handle == 0 || windows.GetFileInformationByHandle(handle, &info) != nil ||
|
|
info.FileAttributes&windows.FILE_ATTRIBUTE_REPARSE_POINT != 0 ||
|
|
info.FileAttributes&windows.FILE_ATTRIBUTE_DIRECTORY != 0 ||
|
|
validateOwnerOnlyDACL(handle) != nil {
|
|
return windows.ByHandleFileInformation{}, ErrUnsafeFile
|
|
}
|
|
for _, links := range allowedLinks {
|
|
if info.NumberOfLinks == links {
|
|
return info, nil
|
|
}
|
|
}
|
|
return windows.ByHandleFileInformation{}, ErrUnsafeFile
|
|
}
|
|
|
|
func isWindowsRelativeNotFound(err error) bool {
|
|
return errors.Is(err, windows.ERROR_FILE_NOT_FOUND) ||
|
|
errors.Is(err, windows.ERROR_PATH_NOT_FOUND) ||
|
|
errors.Is(err, windows.STATUS_NO_SUCH_FILE) ||
|
|
errors.Is(err, windows.STATUS_OBJECT_NAME_NOT_FOUND) ||
|
|
errors.Is(err, windows.STATUS_OBJECT_PATH_NOT_FOUND)
|
|
}
|
|
|
|
func isWindowsRelativeCollision(err error) bool {
|
|
return errors.Is(err, windows.ERROR_FILE_EXISTS) ||
|
|
errors.Is(err, windows.ERROR_ALREADY_EXISTS) ||
|
|
errors.Is(err, windows.STATUS_OBJECT_NAME_COLLISION)
|
|
}
|
|
|
|
func (directory *windowsPrivateDirectory) captureAndValidate() error {
|
|
if directory == nil || directory.handle == 0 {
|
|
return ErrUnsafeFile
|
|
}
|
|
info, err := privateWindowsDirectoryInfo(directory.handle)
|
|
if err != nil {
|
|
return ErrUnsafeFile
|
|
}
|
|
directory.info = info
|
|
return nil
|
|
}
|
|
|
|
func (directory *windowsPrivateDirectory) Close() error {
|
|
if directory == nil {
|
|
return nil
|
|
}
|
|
var result error
|
|
if directory.handle != 0 {
|
|
if err := windows.CloseHandle(directory.handle); err != nil {
|
|
result = ErrUnsafeFile
|
|
}
|
|
directory.handle = 0
|
|
}
|
|
if directory.parent != 0 {
|
|
if err := windows.CloseHandle(directory.parent); err != nil {
|
|
result = ErrUnsafeFile
|
|
}
|
|
directory.parent = 0
|
|
}
|
|
if directory.anchors != nil {
|
|
directory.anchors.Close()
|
|
directory.anchors = nil
|
|
}
|
|
return result
|
|
}
|
|
|
|
func sameWindowsPrivateDirectoryIdentity(left, right windows.ByHandleFileInformation) bool {
|
|
return left.VolumeSerialNumber == right.VolumeSerialNumber && left.FileIndexHigh == right.FileIndexHigh &&
|
|
left.FileIndexLow == right.FileIndexLow && left.FileAttributes == right.FileAttributes
|
|
}
|
|
|
|
func sameWindowsPrivateDirectorySnapshot(left, right windows.ByHandleFileInformation) bool {
|
|
return sameWindowsPrivateDirectoryIdentity(left, right) && left.LastWriteTime == right.LastWriteTime
|
|
}
|
|
|
|
func (directory *windowsPrivateDirectory) Validate() error {
|
|
if directory == nil || directory.handle == 0 || (directory.anchors == nil && directory.parent == 0) {
|
|
return ErrUnsafeFile
|
|
}
|
|
if directory.anchors != nil && len(directory.anchors.handles) == 0 {
|
|
return ErrUnsafeFile
|
|
}
|
|
if directory.parent != 0 {
|
|
if _, err := privateWindowsDirectoryInfo(directory.parent); err != nil {
|
|
return ErrUnsafeFile
|
|
}
|
|
}
|
|
current, err := privateWindowsDirectoryInfo(directory.handle)
|
|
if err != nil || !sameWindowsPrivateDirectoryIdentity(directory.info, current) {
|
|
return ErrUnsafeFile
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func duplicateWindowsRetainedHandle(handle windows.Handle) (windows.Handle, error) {
|
|
if handle == 0 {
|
|
return 0, ErrUnsafeFile
|
|
}
|
|
var duplicate windows.Handle
|
|
process := windows.CurrentProcess()
|
|
if err := windows.DuplicateHandle(process, handle, process, &duplicate, 0, false, windows.DUPLICATE_SAME_ACCESS); err != nil {
|
|
return 0, ErrUnsafeFile
|
|
}
|
|
return duplicate, nil
|
|
}
|
|
|
|
func (directory *windowsPrivateDirectory) OpenChild(name string, ensure bool) (PrivateDirectoryHandle, bool, error) {
|
|
if directory.Validate() != nil || !validPrivateLeafName(name) {
|
|
return nil, false, ErrUnsafeFile
|
|
}
|
|
parent, err := duplicateWindowsRetainedHandle(directory.handle)
|
|
if err != nil {
|
|
return nil, false, ErrUnsafeFile
|
|
}
|
|
handle, found, err := openWindowsPrivateDirectoryAt(parent, name, ensure)
|
|
if err != nil || !found {
|
|
_ = windows.CloseHandle(parent)
|
|
if err != nil {
|
|
return nil, false, ErrUnsafeFile
|
|
}
|
|
return nil, false, nil
|
|
}
|
|
child := &windowsPrivateDirectory{parent: parent, handle: handle}
|
|
if child.captureAndValidate() != nil || directory.Validate() != nil {
|
|
_ = child.Close()
|
|
return nil, false, ErrUnsafeFile
|
|
}
|
|
return child, true, nil
|
|
}
|
|
|
|
func openWindowsPrivateRegularAt(
|
|
parent windows.Handle,
|
|
name string,
|
|
access uint32,
|
|
allowedLinks ...uint32,
|
|
) (*windowsPrivateRegularAt, error) {
|
|
handle, err := openWindowsRelativeObject(
|
|
parent,
|
|
name,
|
|
access,
|
|
windows.FILE_OPEN,
|
|
windows.FILE_NON_DIRECTORY_FILE|windows.FILE_SYNCHRONOUS_IO_NONALERT|windows.FILE_OPEN_REPARSE_POINT,
|
|
nil,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
info, err := privateWindowsRegularInfo(handle, allowedLinks...)
|
|
if err != nil {
|
|
_ = windows.CloseHandle(handle)
|
|
return nil, ErrUnsafeFile
|
|
}
|
|
return &windowsPrivateRegularAt{handle: handle, info: info}, nil
|
|
}
|
|
|
|
func openWindowsPrivateRegularAtAllowedLinks(
|
|
parent windows.Handle,
|
|
name string,
|
|
access uint32,
|
|
allowedLinks ...uint32,
|
|
) (*windowsPrivateRegularAt, error) {
|
|
var (
|
|
lastError error
|
|
unsafeFound bool
|
|
)
|
|
for _, links := range allowedLinks {
|
|
value, err := openWindowsPrivateRegularAt(parent, name, access, links)
|
|
if err == nil {
|
|
return value, nil
|
|
}
|
|
lastError = err
|
|
if !isWindowsRelativeNotFound(err) {
|
|
unsafeFound = true
|
|
}
|
|
}
|
|
if unsafeFound {
|
|
return nil, ErrUnsafeFile
|
|
}
|
|
return nil, lastError
|
|
}
|
|
|
|
func createWindowsPrivateRegularAt(parent windows.Handle, name string) (*windowsPrivateRegularAt, error) {
|
|
security, err := newOwnerOnlySecurityDescriptor()
|
|
if err != nil {
|
|
return nil, ErrUnsafeFile
|
|
}
|
|
defer security.Close()
|
|
handle, err := openWindowsRelativeObject(
|
|
parent,
|
|
name,
|
|
windows.FILE_GENERIC_READ|windows.FILE_GENERIC_WRITE|windows.DELETE,
|
|
windows.FILE_CREATE,
|
|
windows.FILE_NON_DIRECTORY_FILE|windows.FILE_SYNCHRONOUS_IO_NONALERT|windows.FILE_OPEN_REPARSE_POINT,
|
|
security,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
info, err := privateWindowsRegularInfo(handle, 1)
|
|
if err != nil {
|
|
_ = markWindowsHandleForDelete(handle)
|
|
_ = windows.CloseHandle(handle)
|
|
return nil, ErrUnsafeFile
|
|
}
|
|
return &windowsPrivateRegularAt{handle: handle, info: info}, nil
|
|
}
|
|
|
|
func (value *windowsPrivateRegularAt) Close() error {
|
|
if value == nil || value.handle == 0 {
|
|
return nil
|
|
}
|
|
handle := value.handle
|
|
value.handle = 0
|
|
if err := windows.CloseHandle(handle); err != nil {
|
|
return ErrUnsafeFile
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func writeWindowsPrivateRegular(value *windowsPrivateRegularAt, contents []byte) error {
|
|
if value == nil || value.handle == 0 || len(contents) == 0 {
|
|
return ErrUnsafeFile
|
|
}
|
|
for remaining := contents; len(remaining) > 0; {
|
|
var written uint32
|
|
if err := windows.WriteFile(value.handle, remaining, &written, nil); err != nil || written == 0 || int(written) > len(remaining) {
|
|
return ErrUnsafeFile
|
|
}
|
|
remaining = remaining[written:]
|
|
}
|
|
if err := windows.FlushFileBuffers(value.handle); err != nil {
|
|
return ErrUnsafeFile
|
|
}
|
|
info, err := privateWindowsRegularInfo(value.handle, 1)
|
|
if err != nil {
|
|
return ErrUnsafeFile
|
|
}
|
|
value.info = info
|
|
return nil
|
|
}
|
|
|
|
func readWindowsPrivateRegular(value *windowsPrivateRegularAt, maximum int64, links uint32) ([]byte, error) {
|
|
if value == nil || value.handle == 0 || maximum < 0 || maximum == int64(^uint64(0)>>1) {
|
|
return nil, ErrUnsafeFile
|
|
}
|
|
contents := make([]byte, 0, 4096)
|
|
buffer := make([]byte, 4096)
|
|
for {
|
|
var read uint32
|
|
err := windows.ReadFile(value.handle, buffer, &read, nil)
|
|
if read > 0 {
|
|
if int64(len(contents))+int64(read) > maximum {
|
|
return nil, ErrUnsafeFile
|
|
}
|
|
contents = append(contents, buffer[:read]...)
|
|
}
|
|
if err != nil {
|
|
if errors.Is(err, windows.ERROR_HANDLE_EOF) {
|
|
break
|
|
}
|
|
return nil, ErrUnsafeFile
|
|
}
|
|
if read == 0 {
|
|
break
|
|
}
|
|
}
|
|
after, err := privateWindowsRegularInfo(value.handle, links)
|
|
if err != nil || !sameWindowsPrivateFile(value.info, after) {
|
|
return nil, ErrUnsafeFile
|
|
}
|
|
value.info = after
|
|
return contents, nil
|
|
}
|
|
|
|
func markWindowsHandleForDelete(handle windows.Handle) error {
|
|
if handle == 0 {
|
|
return ErrUnsafeFile
|
|
}
|
|
buffer := [1]byte{1}
|
|
var status windows.IO_STATUS_BLOCK
|
|
if err := windows.NtSetInformationFile(handle, &status, &buffer[0], uint32(len(buffer)), windows.FileDispositionInformation); err != nil {
|
|
return ErrUnsafeFile
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func closeAndDeleteWindowsPrivateRegular(value *windowsPrivateRegularAt) error {
|
|
if value == nil || value.handle == 0 || markWindowsHandleForDelete(value.handle) != nil || value.Close() != nil {
|
|
return ErrUnsafeFile
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (directory *windowsPrivateDirectory) CreateRegular(name string, contents []byte) (bool, error) {
|
|
if directory.Validate() != nil || !validPrivateLeafName(name) || len(contents) == 0 {
|
|
return false, ErrUnsafeFile
|
|
}
|
|
value, err := createWindowsPrivateRegularAt(directory.handle, name)
|
|
if err != nil {
|
|
existing, existingErr := openWindowsPrivateRegularAt(directory.handle, name, windows.FILE_GENERIC_READ, 1)
|
|
if existingErr == nil {
|
|
_ = existing.Close()
|
|
return false, nil
|
|
}
|
|
return false, ErrUnsafeFile
|
|
}
|
|
published := false
|
|
defer func() {
|
|
if !published {
|
|
_ = closeAndDeleteWindowsPrivateRegular(value)
|
|
}
|
|
}()
|
|
if writeWindowsPrivateRegular(value, contents) != nil || directory.Validate() != nil {
|
|
return false, ErrUnsafeFile
|
|
}
|
|
if value.Close() != nil {
|
|
return false, ErrUnsafeFile
|
|
}
|
|
published = true
|
|
return true, nil
|
|
}
|
|
|
|
func (directory *windowsPrivateDirectory) ReadRegular(name string, maximum int64) ([]byte, bool, error) {
|
|
if directory.Validate() != nil || !validPrivateLeafName(name) || maximum < 0 || maximum == int64(^uint64(0)>>1) {
|
|
return nil, false, ErrUnsafeFile
|
|
}
|
|
value, err := openWindowsPrivateRegularAt(directory.handle, name, windows.FILE_GENERIC_READ, 1)
|
|
if isWindowsRelativeNotFound(err) {
|
|
return nil, false, nil
|
|
}
|
|
if err != nil {
|
|
return nil, false, ErrUnsafeFile
|
|
}
|
|
defer value.Close()
|
|
contents, err := readWindowsPrivateRegular(value, maximum, 1)
|
|
if err != nil || directory.Validate() != nil {
|
|
return nil, false, ErrUnsafeFile
|
|
}
|
|
return contents, true, nil
|
|
}
|
|
|
|
type windowsRelativeNameInformation struct {
|
|
Flags uint32
|
|
RootDirectory windows.Handle
|
|
FileNameLength uint32
|
|
FileName [1]uint16
|
|
}
|
|
|
|
func setWindowsRelativeNameInformation(
|
|
handle windows.Handle,
|
|
parent windows.Handle,
|
|
name string,
|
|
class uint32,
|
|
flags uint32,
|
|
) error {
|
|
if handle == 0 || parent == 0 || !validPrivateLeafName(name) {
|
|
return ErrUnsafeFile
|
|
}
|
|
encoded, err := windows.UTF16FromString(name)
|
|
if err != nil || len(encoded) < 2 {
|
|
return ErrUnsafeFile
|
|
}
|
|
nameBytes := (len(encoded) - 1) * 2
|
|
var header windowsRelativeNameInformation
|
|
size := int(unsafe.Offsetof(header.FileName)) + nameBytes
|
|
buffer := make([]byte, size)
|
|
value := (*windowsRelativeNameInformation)(unsafe.Pointer(&buffer[0]))
|
|
value.Flags = flags
|
|
value.RootDirectory = parent
|
|
value.FileNameLength = uint32(nameBytes)
|
|
copy(unsafe.Slice(&value.FileName[0], len(encoded)-1), encoded[:len(encoded)-1])
|
|
var status windows.IO_STATUS_BLOCK
|
|
if err := windows.NtSetInformationFile(handle, &status, &buffer[0], uint32(len(buffer)), class); err != nil {
|
|
return ErrUnsafeFile
|
|
}
|
|
runtime.KeepAlive(encoded)
|
|
runtime.KeepAlive(buffer)
|
|
return nil
|
|
}
|
|
|
|
func renameWindowsPrivateRegularAt(handle windows.Handle, parent windows.Handle, name string) error {
|
|
return setWindowsRelativeNameInformation(handle, parent, name, windows.FileRenameInformation, windows.FILE_RENAME_REPLACE_IF_EXISTS)
|
|
}
|
|
|
|
func linkWindowsPrivateRegularAt(handle windows.Handle, parent windows.Handle, name string) error {
|
|
return setWindowsRelativeNameInformation(handle, parent, name, windows.FileLinkInformation, 0)
|
|
}
|
|
|
|
func createWindowsPrivateTemporaryAt(parent windows.Handle, contents []byte) (*windowsPrivateRegularAt, error) {
|
|
for attempt := 0; attempt < 16; attempt++ {
|
|
name, err := randomTemporaryName()
|
|
if err != nil {
|
|
return nil, ErrUnsafeFile
|
|
}
|
|
value, err := createWindowsPrivateRegularAt(parent, name)
|
|
if err != nil {
|
|
if isWindowsRelativeCollision(err) {
|
|
continue
|
|
}
|
|
return nil, ErrUnsafeFile
|
|
}
|
|
if writeWindowsPrivateRegular(value, contents) == nil {
|
|
return value, nil
|
|
}
|
|
_ = closeAndDeleteWindowsPrivateRegular(value)
|
|
return nil, ErrUnsafeFile
|
|
}
|
|
return nil, ErrUnsafeFile
|
|
}
|
|
|
|
func (directory *windowsPrivateDirectory) ReplaceRegular(name string, contents []byte) error {
|
|
if directory.Validate() != nil || !validPrivateLeafName(name) || len(contents) == 0 {
|
|
return ErrUnsafeFile
|
|
}
|
|
existing, err := openWindowsPrivateRegularAt(directory.handle, name, windows.FILE_GENERIC_READ, 1)
|
|
if err != nil {
|
|
return ErrUnsafeFile
|
|
}
|
|
if existing.Close() != nil {
|
|
return ErrUnsafeFile
|
|
}
|
|
temporary, err := createWindowsPrivateTemporaryAt(directory.handle, contents)
|
|
if err != nil {
|
|
return ErrUnsafeFile
|
|
}
|
|
renamed := false
|
|
defer func() {
|
|
if !renamed {
|
|
_ = closeAndDeleteWindowsPrivateRegular(temporary)
|
|
}
|
|
}()
|
|
current, err := openWindowsPrivateRegularAt(directory.handle, name, windows.FILE_GENERIC_READ, 1)
|
|
if err != nil || current.Close() != nil || directory.Validate() != nil {
|
|
return ErrUnsafeFile
|
|
}
|
|
if renameWindowsPrivateRegularAt(temporary.handle, directory.handle, name) != nil || temporary.Close() != nil {
|
|
return ErrUnsafeFile
|
|
}
|
|
renamed = true
|
|
replaced, err := openWindowsPrivateRegularAt(directory.handle, name, windows.FILE_GENERIC_READ, 1)
|
|
if err != nil || replaced.Close() != nil || directory.Validate() != nil {
|
|
return ErrUnsafeFile
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (directory *windowsPrivateDirectory) RemoveRegular(name string) (bool, error) {
|
|
if directory.Validate() != nil || !validPrivateLeafName(name) {
|
|
return false, ErrUnsafeFile
|
|
}
|
|
value, err := openWindowsPrivateRegularAt(directory.handle, name, windows.FILE_GENERIC_READ|windows.DELETE, 1)
|
|
if isWindowsRelativeNotFound(err) {
|
|
return false, nil
|
|
}
|
|
if err != nil {
|
|
return false, ErrUnsafeFile
|
|
}
|
|
if closeAndDeleteWindowsPrivateRegular(value) != nil || directory.Validate() != nil {
|
|
return false, ErrUnsafeFile
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
func (directory *windowsPrivateDirectory) ListPage(
|
|
maximumEntries int,
|
|
afterName string,
|
|
validName func(string) bool,
|
|
validLinks func(string, uint64) bool,
|
|
) (PrivateDirectoryPage, error) {
|
|
if directory.Validate() != nil || maximumEntries < 1 || maximumEntries > 4096 || validName == nil || validLinks == nil ||
|
|
(afterName != "" && !validName(afterName)) {
|
|
return PrivateDirectoryPage{}, ErrUnsafeFile
|
|
}
|
|
var before windows.ByHandleFileInformation
|
|
if err := windows.GetFileInformationByHandle(directory.handle, &before); err != nil ||
|
|
!sameWindowsPrivateDirectoryIdentity(directory.info, before) {
|
|
return PrivateDirectoryPage{}, ErrUnsafeFile
|
|
}
|
|
duplicate, err := duplicateWindowsRetainedHandle(directory.handle)
|
|
if err != nil {
|
|
return PrivateDirectoryPage{}, ErrUnsafeFile
|
|
}
|
|
file := os.NewFile(uintptr(duplicate), "tht-safeio-private-root-list")
|
|
if file == nil {
|
|
_ = windows.CloseHandle(duplicate)
|
|
return PrivateDirectoryPage{}, ErrUnsafeFile
|
|
}
|
|
defer file.Close()
|
|
seen := make(map[string]struct{}, maximumEntries+1)
|
|
selected := make([]PrivateDirectoryEntry, 0, maximumEntries+1)
|
|
scanned := 0
|
|
for {
|
|
entries, readErr := file.ReadDir(1)
|
|
if readErr != nil && !errors.Is(readErr, io.EOF) {
|
|
return PrivateDirectoryPage{}, ErrUnsafeFile
|
|
}
|
|
if len(entries) == 0 {
|
|
break
|
|
}
|
|
if len(entries) != 1 {
|
|
return PrivateDirectoryPage{}, ErrUnsafeFile
|
|
}
|
|
scanned++
|
|
if scanned > maximumPrivateDirectoryPageScanEntries {
|
|
return PrivateDirectoryPage{}, ErrUnsafeFile
|
|
}
|
|
name := entries[0].Name()
|
|
if !validPrivateLeafName(name) || !validName(name) {
|
|
return PrivateDirectoryPage{}, ErrUnsafeFile
|
|
}
|
|
if _, duplicate := seen[name]; duplicate {
|
|
return PrivateDirectoryPage{}, ErrUnsafeFile
|
|
}
|
|
seen[name] = struct{}{}
|
|
value, valueErr := openWindowsPrivateRegularAtAllowedLinks(directory.handle, name, windows.FILE_GENERIC_READ, 1, 2)
|
|
if valueErr != nil {
|
|
return PrivateDirectoryPage{}, ErrUnsafeFile
|
|
}
|
|
info := value.info
|
|
if value.Close() != nil {
|
|
return PrivateDirectoryPage{}, ErrUnsafeFile
|
|
}
|
|
if !validLinks(name, uint64(info.NumberOfLinks)) {
|
|
return PrivateDirectoryPage{}, ErrUnsafeFile
|
|
}
|
|
if name > afterName {
|
|
selected = appendBoundedPrivateDirectoryEntry(selected, PrivateDirectoryEntry{
|
|
Name: name, ModifiedUnixMs: time.Unix(0, info.LastWriteTime.Nanoseconds()).UnixMilli(),
|
|
}, maximumEntries+1)
|
|
}
|
|
if errors.Is(readErr, io.EOF) {
|
|
break
|
|
}
|
|
}
|
|
var after windows.ByHandleFileInformation
|
|
if windows.GetFileInformationByHandle(directory.handle, &after) != nil || directory.Validate() != nil ||
|
|
!sameWindowsPrivateDirectorySnapshot(before, after) {
|
|
return PrivateDirectoryPage{}, ErrUnsafeFile
|
|
}
|
|
sort.Slice(selected, func(left, right int) bool { return selected[left].Name < selected[right].Name })
|
|
more := len(selected) > maximumEntries
|
|
if more {
|
|
selected = selected[:maximumEntries]
|
|
}
|
|
return PrivateDirectoryPage{Entries: selected, More: more}, nil
|
|
}
|
|
|
|
func sameWindowsRelativeClaim(source, claim *windowsPrivateRegularAt) bool {
|
|
return source != nil && claim != nil && sameWindowsPrivateFile(source.info, claim.info)
|
|
}
|
|
|
|
func windowsRelativeClaimPairExists(directory *windowsPrivateDirectory, source, claim string) (bool, error) {
|
|
left, err := openWindowsPrivateRegularAt(directory.handle, source, windows.FILE_GENERIC_READ, 2)
|
|
if isWindowsRelativeNotFound(err) {
|
|
return false, nil
|
|
}
|
|
if err != nil {
|
|
return false, ErrUnsafeFile
|
|
}
|
|
defer left.Close()
|
|
right, err := openWindowsPrivateRegularAt(directory.handle, claim, windows.FILE_GENERIC_READ, 2)
|
|
if isWindowsRelativeNotFound(err) {
|
|
return false, nil
|
|
}
|
|
if err != nil {
|
|
return false, ErrUnsafeFile
|
|
}
|
|
defer right.Close()
|
|
return sameWindowsRelativeClaim(left, right), nil
|
|
}
|
|
|
|
func windowsRelativeClaimAbsentOrOrphan(directory *windowsPrivateDirectory, source, claim string) (bool, error) {
|
|
current, err := openWindowsPrivateRegularAtAllowedLinks(directory.handle, source, windows.FILE_GENERIC_READ, 1, 2)
|
|
if err == nil {
|
|
_ = current.Close()
|
|
return false, nil
|
|
}
|
|
if !isWindowsRelativeNotFound(err) {
|
|
return false, ErrUnsafeFile
|
|
}
|
|
orphan, err := openWindowsPrivateRegularAt(directory.handle, claim, windows.FILE_GENERIC_READ, 1)
|
|
if err == nil {
|
|
_ = orphan.Close()
|
|
return true, nil
|
|
}
|
|
if isWindowsRelativeNotFound(err) {
|
|
return true, nil
|
|
}
|
|
return false, ErrUnsafeFile
|
|
}
|
|
|
|
func (directory *windowsPrivateDirectory) ClaimRegular(source, claim string) (bool, error) {
|
|
if directory.Validate() != nil || !validPrivateLeafName(source) || !validPrivateLeafName(claim) {
|
|
return false, ErrUnsafeFile
|
|
}
|
|
value, err := openWindowsPrivateRegularAt(
|
|
directory.handle,
|
|
source,
|
|
windows.FILE_GENERIC_READ|windows.FILE_GENERIC_WRITE|windows.DELETE,
|
|
1,
|
|
)
|
|
if err != nil {
|
|
pair, pairErr := windowsRelativeClaimPairExists(directory, source, claim)
|
|
if pairErr == nil && pair {
|
|
return false, nil
|
|
}
|
|
orphan, orphanErr := windowsRelativeClaimAbsentOrOrphan(directory, source, claim)
|
|
if orphanErr == nil && orphan {
|
|
return false, nil
|
|
}
|
|
return false, ErrUnsafeFile
|
|
}
|
|
defer value.Close()
|
|
if linkWindowsPrivateRegularAt(value.handle, directory.handle, claim) != nil {
|
|
existing, existingErr := openWindowsPrivateRegularAtAllowedLinks(directory.handle, claim, windows.FILE_GENERIC_READ, 1, 2)
|
|
if existingErr == nil {
|
|
_ = existing.Close()
|
|
return false, nil
|
|
}
|
|
return false, ErrUnsafeFile
|
|
}
|
|
after, err := privateWindowsRegularInfo(value.handle, 2)
|
|
if err != nil {
|
|
return false, ErrUnsafeFile
|
|
}
|
|
value.info = after
|
|
claimed, err := openWindowsPrivateRegularAt(directory.handle, claim, windows.FILE_GENERIC_READ, 2)
|
|
if err != nil {
|
|
return false, ErrUnsafeFile
|
|
}
|
|
defer claimed.Close()
|
|
if !sameWindowsRelativeClaim(value, claimed) || directory.Validate() != nil {
|
|
return false, ErrUnsafeFile
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
func (directory *windowsPrivateDirectory) ReadClaim(source, claim string, maximum int64) ([]byte, bool, error) {
|
|
if directory.Validate() != nil || !validPrivateLeafName(source) || !validPrivateLeafName(claim) ||
|
|
maximum < 0 || maximum == int64(^uint64(0)>>1) {
|
|
return nil, false, ErrUnsafeFile
|
|
}
|
|
value, err := openWindowsPrivateRegularAt(directory.handle, source, windows.FILE_GENERIC_READ, 2)
|
|
if err != nil {
|
|
orphan, orphanErr := windowsRelativeClaimAbsentOrOrphan(directory, source, claim)
|
|
if orphanErr == nil && orphan {
|
|
return nil, false, nil
|
|
}
|
|
return nil, false, ErrUnsafeFile
|
|
}
|
|
defer value.Close()
|
|
claimed, err := openWindowsPrivateRegularAt(directory.handle, claim, windows.FILE_GENERIC_READ, 2)
|
|
if isWindowsRelativeNotFound(err) {
|
|
return nil, false, nil
|
|
}
|
|
if err != nil {
|
|
return nil, false, ErrUnsafeFile
|
|
}
|
|
defer claimed.Close()
|
|
if !sameWindowsRelativeClaim(value, claimed) {
|
|
return nil, false, ErrUnsafeFile
|
|
}
|
|
contents, err := readWindowsPrivateRegular(value, maximum, 2)
|
|
if err != nil {
|
|
return nil, false, ErrUnsafeFile
|
|
}
|
|
afterClaim, err := privateWindowsRegularInfo(claimed.handle, 2)
|
|
if err != nil || !sameWindowsPrivateFile(value.info, afterClaim) || directory.Validate() != nil {
|
|
return nil, false, ErrUnsafeFile
|
|
}
|
|
return contents, true, nil
|
|
}
|
|
|
|
func (directory *windowsPrivateDirectory) RemoveClaim(source, claim string) (bool, error) {
|
|
if directory.Validate() != nil || !validPrivateLeafName(source) || !validPrivateLeafName(claim) {
|
|
return false, ErrUnsafeFile
|
|
}
|
|
value, err := openWindowsPrivateRegularAt(directory.handle, source, windows.FILE_GENERIC_READ|windows.DELETE, 2)
|
|
if err != nil {
|
|
orphan, orphanErr := windowsRelativeClaimAbsentOrOrphan(directory, source, claim)
|
|
if orphanErr == nil && orphan {
|
|
return false, nil
|
|
}
|
|
return false, ErrUnsafeFile
|
|
}
|
|
claimed, err := openWindowsPrivateRegularAt(directory.handle, claim, windows.FILE_GENERIC_READ, 2)
|
|
if isWindowsRelativeNotFound(err) {
|
|
_ = value.Close()
|
|
return false, nil
|
|
}
|
|
if err != nil || !sameWindowsRelativeClaim(value, claimed) {
|
|
_ = value.Close()
|
|
if claimed != nil {
|
|
_ = claimed.Close()
|
|
}
|
|
return false, ErrUnsafeFile
|
|
}
|
|
if claimed.Close() != nil || closeAndDeleteWindowsPrivateRegular(value) != nil {
|
|
return false, ErrUnsafeFile
|
|
}
|
|
remaining, err := openWindowsPrivateRegularAt(directory.handle, claim, windows.FILE_GENERIC_READ|windows.DELETE, 1)
|
|
if err != nil || closeAndDeleteWindowsPrivateRegular(remaining) != nil || directory.Validate() != nil {
|
|
return false, ErrUnsafeFile
|
|
}
|
|
return true, nil
|
|
}
|