fix(safeio): retain writable Windows parent handles

This commit is contained in:
2026-08-18 09:05:42 +02:00
parent e7a7f4f066
commit 74b062f1a7
3 changed files with 41 additions and 25 deletions
+23 -5
View File
@@ -13,7 +13,7 @@ import (
)
func createPrivateDirectory(path string) error {
parents, target, err := openCanonicalWindowsParent(path)
parents, target, err := openCanonicalWindowsParentWithFinalAccess(path, windows.FILE_APPEND_DATA)
if err != nil || parents == nil || len(parents.handles) == 0 {
if parents != nil {
parents.Close()
@@ -112,7 +112,10 @@ func createCanonicalNewPrivateParentReadWriteFile(path string, mode os.FileMode)
}
func createCanonicalNewFile(path string, mode os.FileMode, requirePrivateParent bool, access uint32) (*os.File, error) {
parents, target, err := openCanonicalWindowsParent(path)
// FILE_WRITE_DATA is FILE_ADD_FILE when the retained handle names a directory. Request it
// while that final parent is opened, rather than reopening its lexical path later to gain
// create permission.
parents, target, err := openCanonicalWindowsParentWithFinalAccess(path, windows.FILE_WRITE_DATA)
if err != nil || parents == nil || len(parents.handles) == 0 || (requirePrivateParent && validateOwnerOnlyDACL(parents.handles[len(parents.handles)-1]) != nil) {
if parents != nil {
parents.Close()
@@ -172,6 +175,13 @@ func (parents *windowsParentHandles) Close() {
// target parent without FILE_SHARE_DELETE. Every component after the volume root is resolved
// through the prior retained handle's NT RootDirectory, never by re-opening an absolute prefix.
func openCanonicalWindowsParent(path string) (*windowsParentHandles, string, error) {
return openCanonicalWindowsParentWithFinalAccess(path, 0)
}
// openCanonicalWindowsParentWithFinalAccess gives only the final retained parent the requested
// child-operation capability. It is the Windows openat traversal for a later relative create;
// reopening that parent by its reconstructed path would recreate the ancestor-swap race.
func openCanonicalWindowsParentWithFinalAccess(path string, finalParentAccess uint32) (*windowsParentHandles, string, error) {
if err := ValidateCanonicalPath(path); err != nil {
return nil, "", err
}
@@ -182,13 +192,21 @@ func openCanonicalWindowsParent(path string) (*windowsParentHandles, string, err
return nil, "", ErrUnsafeFile
}
parents := &windowsParentHandles{directory: root}
rootHandle, err := openWindowsComponent(root, true)
rootAccess := uint32(windows.GENERIC_READ)
if len(components) == 1 {
rootAccess |= finalParentAccess
}
rootHandle, err := openWindowsComponentWithAccess(root, true, rootAccess)
if err != nil {
return nil, "", err
}
parents.handles = append(parents.handles, rootHandle)
for _, component := range components[:len(components)-1] {
handle, err := openWindowsRelativeComponent(parents.handles[len(parents.handles)-1], component, true, windows.GENERIC_READ)
for index, component := range components[:len(components)-1] {
componentAccess := uint32(windows.GENERIC_READ)
if index == len(components)-2 {
componentAccess |= finalParentAccess
}
handle, err := openWindowsRelativeComponent(parents.handles[len(parents.handles)-1], component, true, componentAccess)
if err != nil {
parents.Close()
return nil, "", err