fix(safeio): retain private file parent handles
This commit is contained in:
@@ -4,46 +4,28 @@ package safeio
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
const windowsRetainedHandleShareMode uint32 = windows.FILE_SHARE_READ | windows.FILE_SHARE_WRITE
|
||||
|
||||
// ReadCanonicalRegular opens each component with FILE_FLAG_OPEN_REPARSE_POINT and rejects a
|
||||
// reparse point on the opened handle before opening the next component. Retained handles allow
|
||||
// ordinary read/write sharing but deny delete sharing, which blocks rename or deletion after a
|
||||
// component is opened and throughout the final read. Windows' Win32 API does not expose a
|
||||
// portable descriptor-relative equivalent of POSIX openat, so a hostile local actor can still
|
||||
// replace a not-yet-opened normal component between absolute-path opens. Installation directories
|
||||
// therefore need trusted local filesystem/ACL ownership on Windows.
|
||||
// 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
|
||||
}
|
||||
volume := filepath.VolumeName(path)
|
||||
root := volume + string(filepath.Separator)
|
||||
components := strings.Split(strings.TrimPrefix(path, root), string(filepath.Separator))
|
||||
if volume == "" || len(components) == 0 || components[0] == "" {
|
||||
parents, target, err := openCanonicalWindowsParent(path)
|
||||
if err != nil || parents == nil || len(parents.handles) == 0 {
|
||||
if parents != nil {
|
||||
parents.Close()
|
||||
}
|
||||
return nil, ErrUnsafeFile
|
||||
}
|
||||
|
||||
current := root
|
||||
parents := make([]windows.Handle, 0, len(components)-1)
|
||||
defer func() { closeWindowsHandles(parents) }()
|
||||
for _, component := range components[:len(components)-1] {
|
||||
current = filepath.Join(current, component)
|
||||
handle, err := openWindowsComponent(current, true)
|
||||
if err != nil {
|
||||
return nil, ErrUnsafeFile
|
||||
}
|
||||
parents = append(parents, handle)
|
||||
}
|
||||
|
||||
current = filepath.Join(current, components[len(components)-1])
|
||||
handle, err := openWindowsComponent(current, false)
|
||||
defer parents.Close()
|
||||
handle, err := openWindowsRelativeComponent(parents.handles[len(parents.handles)-1], target, false, windows.GENERIC_READ)
|
||||
if err != nil {
|
||||
return nil, ErrUnsafeFile
|
||||
}
|
||||
@@ -93,6 +75,34 @@ func openWindowsComponentWithAccess(path string, directory bool, access uint32)
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user