fix(safeio): retain private file parent handles
This commit is contained in:
@@ -3,7 +3,6 @@
|
||||
package safeio
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
@@ -15,38 +14,37 @@ import (
|
||||
|
||||
func createPrivateDirectory(path string) error {
|
||||
parents, target, err := openCanonicalWindowsParent(path)
|
||||
if err != nil {
|
||||
if err != nil || parents == nil || len(parents.handles) == 0 {
|
||||
if parents != nil {
|
||||
parents.Close()
|
||||
}
|
||||
return ErrUnsafeFile
|
||||
}
|
||||
defer parents.Close()
|
||||
security, err := newOwnerOnlySecurityDescriptor()
|
||||
if err != nil {
|
||||
return ErrUnsafeFile
|
||||
}
|
||||
defer security.Close()
|
||||
attributes := &windows.SecurityAttributes{
|
||||
Length: uint32(unsafe.Sizeof(windows.SecurityAttributes{})),
|
||||
SecurityDescriptor: security.descriptor,
|
||||
}
|
||||
err = windows.CreateDirectory(windows.StringToUTF16Ptr(filepath.Join(parents.directory, target)), attributes)
|
||||
runtime.KeepAlive(security)
|
||||
if errors.Is(err, windows.ERROR_ALREADY_EXISTS) {
|
||||
handle, err := createWindowsRelativePrivateDirectory(parents.handles[len(parents.handles)-1], target)
|
||||
if isWindowsRelativeCollision(err) {
|
||||
return os.ErrExist
|
||||
}
|
||||
if err != nil {
|
||||
return ErrUnsafeFile
|
||||
}
|
||||
return ValidatePrivateDirectory(path)
|
||||
if err := windows.CloseHandle(handle); err != nil {
|
||||
return ErrUnsafeFile
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ProtectPrivateDirectory sets a protected DACL containing only the current owner.
|
||||
func ProtectPrivateDirectory(path string) error {
|
||||
parents, target, err := openCanonicalWindowsParent(path)
|
||||
if err != nil {
|
||||
if err != nil || parents == nil || len(parents.handles) == 0 {
|
||||
if parents != nil {
|
||||
parents.Close()
|
||||
}
|
||||
return ErrUnsafeFile
|
||||
}
|
||||
defer parents.Close()
|
||||
handle, err := openWindowsComponentWithAccess(filepath.Join(parents.directory, target), true, windows.GENERIC_READ|windows.WRITE_DAC|windows.WRITE_OWNER)
|
||||
handle, err := openWindowsRelativeComponent(parents.handles[len(parents.handles)-1], target, true, windows.GENERIC_READ|windows.WRITE_DAC|windows.WRITE_OWNER)
|
||||
if err != nil {
|
||||
return ErrUnsafeFile
|
||||
}
|
||||
@@ -60,11 +58,14 @@ func ProtectPrivateDirectory(path string) error {
|
||||
// ValidatePrivateDirectory requires a canonical directory protected for its current owner only.
|
||||
func ValidatePrivateDirectory(path string) error {
|
||||
parents, target, err := openCanonicalWindowsParent(path)
|
||||
if err != nil {
|
||||
if err != nil || parents == nil || len(parents.handles) == 0 {
|
||||
if parents != nil {
|
||||
parents.Close()
|
||||
}
|
||||
return ErrUnsafeFile
|
||||
}
|
||||
defer parents.Close()
|
||||
handle, err := openWindowsComponent(filepath.Join(parents.directory, target), true)
|
||||
handle, err := openWindowsRelativeComponent(parents.handles[len(parents.handles)-1], target, true, windows.GENERIC_READ)
|
||||
if err != nil {
|
||||
return ErrUnsafeFile
|
||||
}
|
||||
@@ -78,11 +79,14 @@ func ValidatePrivateDirectory(path string) error {
|
||||
// ProtectPrivateRegular sets a protected DACL containing only the current owner.
|
||||
func ProtectPrivateRegular(path string) error {
|
||||
parents, target, err := openCanonicalWindowsParent(path)
|
||||
if err != nil {
|
||||
if err != nil || parents == nil || len(parents.handles) == 0 {
|
||||
if parents != nil {
|
||||
parents.Close()
|
||||
}
|
||||
return ErrUnsafeFile
|
||||
}
|
||||
defer parents.Close()
|
||||
handle, err := openWindowsComponentWithAccess(filepath.Join(parents.directory, target), false, windows.GENERIC_READ|windows.WRITE_DAC|windows.WRITE_OWNER)
|
||||
handle, err := openWindowsRelativeComponent(parents.handles[len(parents.handles)-1], target, false, windows.GENERIC_READ|windows.WRITE_DAC|windows.WRITE_OWNER)
|
||||
if err != nil {
|
||||
return ErrUnsafeFile
|
||||
}
|
||||
@@ -109,62 +113,45 @@ 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)
|
||||
if err != nil || len(parents.handles) == 0 || (requirePrivateParent && validateOwnerOnlyDACL(parents.handles[len(parents.handles)-1]) != nil) {
|
||||
if err != nil || parents == nil || len(parents.handles) == 0 || (requirePrivateParent && validateOwnerOnlyDACL(parents.handles[len(parents.handles)-1]) != nil) {
|
||||
if parents != nil {
|
||||
parents.Close()
|
||||
}
|
||||
return nil, ErrUnsafeFile
|
||||
}
|
||||
defer parents.Close()
|
||||
security, err := newOwnerOnlySecurityDescriptor()
|
||||
if err != nil {
|
||||
return nil, ErrUnsafeFile
|
||||
}
|
||||
defer security.Close()
|
||||
attributes := &windows.SecurityAttributes{
|
||||
Length: uint32(unsafe.Sizeof(windows.SecurityAttributes{})),
|
||||
SecurityDescriptor: security.descriptor,
|
||||
}
|
||||
handle, err := windows.CreateFile(
|
||||
windows.StringToUTF16Ptr(filepath.Join(parents.directory, target)),
|
||||
access,
|
||||
windowsRetainedHandleShareMode,
|
||||
attributes,
|
||||
windows.CREATE_NEW,
|
||||
windows.FILE_ATTRIBUTE_NORMAL,
|
||||
0,
|
||||
)
|
||||
runtime.KeepAlive(security)
|
||||
NotifyPrivateDirectoryTestHookForTest("after-canonical-private-file-parent-open")
|
||||
// mode remains accepted for the existing helper contract; Windows installs the owner-only
|
||||
// DACL in the NtCreateFile call below rather than relying on inherited file attributes.
|
||||
_ = mode
|
||||
value, err := createWindowsPrivateRegularAtWithAccess(parents.handles[len(parents.handles)-1], target, access)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateOwnerOnlyDACL(handle); err != nil {
|
||||
_ = windows.CloseHandle(handle)
|
||||
_ = os.Remove(path)
|
||||
return nil, ErrUnsafeFile
|
||||
}
|
||||
file := os.NewFile(uintptr(handle), "tht-safeio-private")
|
||||
file := os.NewFile(uintptr(value.handle), "tht-safeio-private")
|
||||
if file == nil {
|
||||
_ = windows.CloseHandle(handle)
|
||||
_ = os.Remove(path)
|
||||
_ = closeAndDeleteWindowsPrivateRegular(value)
|
||||
return nil, ErrUnsafeFile
|
||||
}
|
||||
value.handle = 0
|
||||
return file, nil
|
||||
}
|
||||
|
||||
// ValidatePrivateRegular requires a canonical, single-link file protected for its current owner only.
|
||||
func ValidatePrivateRegular(path string) error {
|
||||
parents, target, err := openCanonicalWindowsParent(path)
|
||||
if err != nil {
|
||||
if err != nil || parents == nil || len(parents.handles) == 0 {
|
||||
if parents != nil {
|
||||
parents.Close()
|
||||
}
|
||||
return ErrUnsafeFile
|
||||
}
|
||||
defer parents.Close()
|
||||
handle, err := openWindowsComponent(filepath.Join(parents.directory, target), false)
|
||||
value, err := openWindowsPrivateRegularAt(parents.handles[len(parents.handles)-1], target, windows.GENERIC_READ, 1)
|
||||
if err != nil {
|
||||
return ErrUnsafeFile
|
||||
}
|
||||
defer windows.CloseHandle(handle)
|
||||
if err := validateOwnerOnlyDACL(handle); err != nil {
|
||||
if err := value.Close(); err != nil {
|
||||
return ErrUnsafeFile
|
||||
}
|
||||
return nil
|
||||
@@ -182,8 +169,8 @@ func (parents *windowsParentHandles) Close() {
|
||||
}
|
||||
|
||||
// openCanonicalWindowsParent retains every directory handle from the volume root through the
|
||||
// target parent without FILE_SHARE_DELETE. The resulting parent cannot be renamed or replaced by
|
||||
// a reparse point while an operation uses its absolute child paths.
|
||||
// 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) {
|
||||
if err := ValidateCanonicalPath(path); err != nil {
|
||||
return nil, "", err
|
||||
@@ -201,12 +188,12 @@ func openCanonicalWindowsParent(path string) (*windowsParentHandles, string, err
|
||||
}
|
||||
parents.handles = append(parents.handles, rootHandle)
|
||||
for _, component := range components[:len(components)-1] {
|
||||
parents.directory = filepath.Join(parents.directory, component)
|
||||
handle, err := openWindowsComponent(parents.directory, true)
|
||||
handle, err := openWindowsRelativeComponent(parents.handles[len(parents.handles)-1], component, true, windows.GENERIC_READ)
|
||||
if err != nil {
|
||||
parents.Close()
|
||||
return nil, "", err
|
||||
}
|
||||
parents.directory = filepath.Join(parents.directory, component)
|
||||
parents.handles = append(parents.handles, handle)
|
||||
}
|
||||
return parents, components[len(components)-1], nil
|
||||
|
||||
Reference in New Issue
Block a user