fix(auth): close Windows remediation review findings

This commit is contained in:
2026-08-18 12:46:54 +02:00
parent fa499a9bdd
commit cd5f505c8a
10 changed files with 310 additions and 33 deletions
@@ -539,13 +539,30 @@ func markWindowsHandleForDelete(handle windows.Handle) error {
return nil
}
func closeAndDeleteWindowsPrivateRegular(value *windowsPrivateRegularAt) error {
if value == nil || value.handle == 0 || markWindowsHandleForDelete(value.handle) != nil || value.Close() != nil {
func finishWindowsPrivateRegularCleanup(mark func() error, close func() error) error {
failed := false
if mark == nil || mark() != nil {
failed = true
}
if close == nil || close() != nil {
failed = true
}
if failed {
return ErrUnsafeFile
}
return nil
}
func closeAndDeleteWindowsPrivateRegular(value *windowsPrivateRegularAt) error {
if value == nil || value.handle == 0 {
return ErrUnsafeFile
}
return finishWindowsPrivateRegularCleanup(
func() error { return markWindowsHandleForDelete(value.handle) },
value.Close,
)
}
func (directory *windowsPrivateDirectory) CreateRegular(name string, contents []byte) (bool, error) {
if directory.Validate() != nil || !validPrivateLeafName(name) || len(contents) == 0 {
return false, ErrUnsafeFile
@@ -554,7 +571,11 @@ func (directory *windowsPrivateDirectory) CreateRegular(name string, contents []
if err != nil {
existing, existingErr := openWindowsPrivateRegularAt(directory.handle, name, windows.FILE_GENERIC_READ, 1)
if existingErr == nil {
_ = existing.Close()
closeErr := existing.Close()
validateErr := directory.Validate()
if closeErr != nil || validateErr != nil {
return false, ErrUnsafeFile
}
return false, nil
}
return false, ErrUnsafeFile
@@ -720,15 +741,27 @@ func (directory *windowsPrivateDirectory) ReplaceRegular(name string, contents [
}
}()
current, err := openWindowsPrivateRegularAt(directory.handle, name, windows.FILE_GENERIC_READ, 1)
if err != nil || current.Close() != nil || directory.Validate() != nil {
if err != nil {
return ErrUnsafeFile
}
if renameWindowsPrivateRegularAt(temporary.handle, directory.handle, name) != nil || temporary.Close() != nil {
currentCloseErr := current.Close()
currentValidateErr := directory.Validate()
if currentCloseErr != nil || currentValidateErr != nil {
return ErrUnsafeFile
}
renameErr := renameWindowsPrivateRegularAt(temporary.handle, directory.handle, name)
temporaryCloseErr := temporary.Close()
if renameErr != nil || temporaryCloseErr != 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 {
if err != nil {
return ErrUnsafeFile
}
replacedCloseErr := replaced.Close()
replacedValidateErr := directory.Validate()
if replacedCloseErr != nil || replacedValidateErr != nil {
return ErrUnsafeFile
}
return nil
@@ -745,7 +778,9 @@ func (directory *windowsPrivateDirectory) RemoveRegular(name string) (bool, erro
if err != nil {
return false, ErrUnsafeFile
}
if closeAndDeleteWindowsPrivateRegular(value) != nil || directory.Validate() != nil {
cleanupErr := closeAndDeleteWindowsPrivateRegular(value)
validateErr := directory.Validate()
if cleanupErr != nil || validateErr != nil {
return false, ErrUnsafeFile
}
return true, nil
@@ -839,6 +874,19 @@ func sameWindowsRelativeClaim(source, claim *windowsPrivateRegularAt) bool {
return source != nil && claim != nil && sameWindowsPrivateFile(source.info, claim.info)
}
func finishWindowsClaimCleanup(closeClaim, deleteSource, deleteClaim, validate func() error) error {
failed := false
for _, operation := range []func() error{closeClaim, deleteSource, deleteClaim, validate} {
if operation == nil || operation() != nil {
failed = true
}
}
if failed {
return ErrUnsafeFile
}
return nil
}
func windowsRelativeClaimPairExists(directory *windowsPrivateDirectory, source, claim string) (bool, error) {
left, err := openWindowsPrivateRegularAt(directory.handle, source, windows.FILE_GENERIC_READ, 2)
if isWindowsRelativeNotFound(err) {
@@ -981,21 +1029,37 @@ func (directory *windowsPrivateDirectory) RemoveClaim(source, claim string) (boo
2,
)
if isWindowsRelativeNotFound(err) {
_ = value.Close()
closeErr := value.Close()
validateErr := directory.Validate()
if closeErr != nil || validateErr != nil {
return false, ErrUnsafeFile
}
return false, nil
}
if err != nil || !sameWindowsRelativeClaim(value, claimed) {
_ = value.Close()
valueCloseErr := value.Close()
claimedCloseErr := error(nil)
if claimed != nil {
_ = claimed.Close()
claimedCloseErr = claimed.Close()
}
if valueCloseErr != nil || claimedCloseErr != nil || directory.Validate() != nil {
return false, ErrUnsafeFile
}
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 {
cleanupErr := finishWindowsClaimCleanup(
claimed.Close,
func() error { return closeAndDeleteWindowsPrivateRegular(value) },
func() error {
remaining, remainingErr := openWindowsPrivateRegularAt(directory.handle, claim, windows.FILE_GENERIC_READ|windows.DELETE, 1)
if remainingErr != nil {
return ErrUnsafeFile
}
return closeAndDeleteWindowsPrivateRegular(remaining)
},
directory.Validate,
)
if cleanupErr != nil {
return false, ErrUnsafeFile
}
return true, nil
+10 -1
View File
@@ -316,7 +316,7 @@ func validateOwnerOnlyDACL(handle windows.Handle) error {
return ErrUnsafeFile
}
var ace *windows.ACCESS_ALLOWED_ACE
if err := windows.GetAce(dacl, 0, &ace); err != nil || ace == nil || ace.Header.AceType != windows.ACCESS_ALLOWED_ACE_TYPE || ace.Header.AceFlags != 0 || ace.Mask != windows.GENERIC_ALL {
if err := windows.GetAce(dacl, 0, &ace); err != nil || ace == nil || ace.Header.AceType != windows.ACCESS_ALLOWED_ACE_TYPE || ace.Header.AceFlags != 0 || !isOwnerOnlyFullControlMask(uint32(ace.Mask)) {
return ErrUnsafeFile
}
aceSID := (*windows.SID)(unsafe.Pointer(&ace.SidStart))
@@ -327,6 +327,15 @@ func validateOwnerOnlyDACL(handle windows.Handle) error {
})
}
func isOwnerOnlyFullControlMask(mask uint32) bool {
// Windows may persist GENERIC_ALL in the ACE or expand it to the file-object
// full-control mask (including FILE_DELETE_CHILD). Both are the same semantic
// authority; any additional bit remains unsafe.
const fileDeleteChild = uint32(0x40)
effective := uint32(windows.FILE_GENERIC_READ|windows.FILE_GENERIC_WRITE|windows.FILE_GENERIC_EXECUTE|windows.DELETE) | fileDeleteChild
return mask == uint32(windows.GENERIC_ALL) || mask == effective
}
// withWindowsSecurityDescriptor confines inspection to x/sys's Go-owned descriptor copy. Its
// GetSecurityInfo wrapper releases the native LocalAlloc result with LocalFree before returning.
func withWindowsSecurityDescriptor(handle windows.Handle, inspect func(*windows.SECURITY_DESCRIPTOR) error) error {
@@ -7,6 +7,7 @@ import (
"os"
"path/filepath"
"runtime"
"strings"
"testing"
"golang.org/x/sys/windows"
@@ -53,6 +54,68 @@ func TestPrivateWindowsDACLRejectsPermissiveDirectoryAndRegularFile(t *testing.T
}
}
func TestOwnerOnlyDACLAcceptsWindowsFullControlMask(t *testing.T) {
const fileDeleteChild = uint32(0x40)
effectiveFullControl := uint32(windows.FILE_GENERIC_READ|windows.FILE_GENERIC_WRITE|windows.FILE_GENERIC_EXECUTE|windows.DELETE) | fileDeleteChild
if !isOwnerOnlyFullControlMask(effectiveFullControl) {
t.Fatalf("effective Windows full-control mask %#x was rejected", effectiveFullControl)
}
if !isOwnerOnlyFullControlMask(uint32(windows.GENERIC_ALL)) {
t.Fatal("generic full-control mask was rejected")
}
if isOwnerOnlyFullControlMask(effectiveFullControl | uint32(windows.ACCESS_SYSTEM_SECURITY)) {
t.Fatal("full-control mask with an extra right was accepted")
}
}
func TestWindowsPrivateRegularCleanupClosesAfterDeleteDispositionFailure(t *testing.T) {
var calls []string
err := finishWindowsPrivateRegularCleanup(
func() error {
calls = append(calls, "delete")
return ErrUnsafeFile
},
func() error {
calls = append(calls, "close")
return nil
},
)
if !errors.Is(err, ErrUnsafeFile) {
t.Fatalf("finishWindowsPrivateRegularCleanup() error = %v, want ErrUnsafeFile", err)
}
if got, want := strings.Join(calls, ","), "delete,close"; got != want {
t.Fatalf("cleanup order = %q, want %q", got, want)
}
}
func TestWindowsClaimCleanupAttemptsLaterOperationsAfterEarlierFailure(t *testing.T) {
var calls []string
err := finishWindowsClaimCleanup(
func() error {
calls = append(calls, "claim-close")
return ErrUnsafeFile
},
func() error {
calls = append(calls, "source-delete")
return nil
},
func() error {
calls = append(calls, "claim-delete")
return nil
},
func() error {
calls = append(calls, "validate")
return nil
},
)
if !errors.Is(err, ErrUnsafeFile) {
t.Fatalf("finishWindowsClaimCleanup() error = %v, want ErrUnsafeFile", err)
}
if got, want := strings.Join(calls, ","), "claim-close,source-delete,claim-delete,validate"; got != want {
t.Fatalf("cleanup order = %q, want %q", got, want)
}
}
func TestCreateCanonicalNewPrivateFileInstallsOwnerOnlyDACLAtCreation(t *testing.T) {
directory := filepath.Join(t.TempDir(), "auth")
if err := os.Mkdir(directory, 0o700); err != nil {