//go:build windows package safeio import ( "errors" "fmt" "os" "path/filepath" "runtime" "strings" "testing" "unsafe" "golang.org/x/sys/windows" ) func TestPrivateWindowsDACLRejectsPermissiveDirectoryAndRegularFile(t *testing.T) { directory := filepath.Join(t.TempDir(), "auth") if err := os.Mkdir(directory, 0o700); err != nil { t.Fatal(err) } if err := ProtectPrivateDirectory(directory); err != nil { t.Fatal(err) } if err := ValidatePrivateDirectory(directory); err != nil { t.Fatalf("ValidatePrivateDirectory() protected directory error = %v", err) } path := filepath.Join(directory, "users.yaml") if err := os.WriteFile(path, []byte("private"), 0o600); err != nil { t.Fatal(err) } if err := ProtectPrivateRegular(path); err != nil { t.Fatal(err) } if err := ValidatePrivateRegular(path); err != nil { t.Fatalf("ValidatePrivateRegular() protected file error = %v", err) } for name, path := range map[string]string{"directory": directory, "regular file": path} { t.Run(name, func(t *testing.T) { parents, target, err := openCanonicalWindowsParent(path) if err != nil { t.Fatal(err) } handle, err := openWindowsRelativeComponent( parents.handles[len(parents.handles)-1], target, name == "directory", windows.GENERIC_READ|windows.WRITE_DAC|windows.WRITE_OWNER, ) if err != nil { parents.Close() t.Fatal(err) } t.Cleanup(func() { if restoreErr := setOwnerOnlyDACL(handle); restoreErr != nil { t.Errorf("restore owner-only DACL: %v", restoreErr) } _ = windows.CloseHandle(handle) parents.Close() }) if err := setPermissiveDACL(path); err != nil { t.Fatal(err) } var validationErr error if name == "directory" { validationErr = ValidatePrivateDirectory(path) } else { validationErr = ValidatePrivateRegular(path) } if !errors.Is(validationErr, ErrUnsafeFile) { t.Fatalf("private validation error = %v, want ErrUnsafeFile", validationErr) } }) } } func TestOwnerOnlySecurityDescriptorNativeCreate(t *testing.T) { directory := filepath.Join(t.TempDir(), "auth") if err := os.Mkdir(directory, 0o700); err != nil { t.Fatal(err) } if err := ProtectPrivateDirectory(directory); err != nil { t.Fatal(err) } path := filepath.Join(directory, "created.env") parents, target, err := openCanonicalWindowsParent(path) if err != nil { t.Fatal(err) } defer parents.Close() value, err := createWindowsPrivateRegularAtWithAccess( parents.handles[len(parents.handles)-1], target, windows.GENERIC_WRITE, ) if err != nil { t.Fatalf("createWindowsPrivateRegularAtWithAccess() write-only error = %T %v", err, err) } if err := value.Close(); err != nil { t.Fatalf("close private regular after write-only create = %T %v", err, err) } } func TestOwnerOnlyDACLCanProtectInheritedRegularFile(t *testing.T) { directory := filepath.Join(t.TempDir(), "auth") if err := os.Mkdir(directory, 0o700); err != nil { t.Fatal(err) } path := filepath.Join(directory, "operator.env") if err := os.WriteFile(path, []byte("private"), 0o600); err != nil { t.Fatal(err) } parents, target, err := openCanonicalWindowsParent(path) if err != nil { t.Fatalf("openCanonicalWindowsParent() error = %T %v", err, err) } defer parents.Close() handle, err := openWindowsRelativeComponent( parents.handles[len(parents.handles)-1], target, false, windows.GENERIC_READ|windows.WRITE_DAC|windows.WRITE_OWNER, ) if err != nil { t.Fatalf("openWindowsRelativeComponent() error = %T %v", err, err) } defer windows.CloseHandle(handle) if err := setOwnerOnlyDACL(handle); err != nil { t.Fatalf("setOwnerOnlyDACL() inherited file error = %T %v", err, err) } if err := validateOwnerOnlyDACL(handle); err != nil { t.Fatalf("validateOwnerOnlyDACL() inherited file error = %T %v", err, err) } } func TestOwnerOnlyDACLAcceptsWindowsFullControlMask(t *testing.T) { const fileSpecificAll = uint32(0x1ff) effectiveFullControl := uint32(windows.STANDARD_RIGHTS_REQUIRED|windows.SYNCHRONIZE) | fileSpecificAll 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 TestWindowsNTDesiredAccessMapsGenericBits(t *testing.T) { if got, want := normalizeWindowsNTDesiredAccess(windows.GENERIC_READ), uint32(windows.FILE_GENERIC_READ); got != want { t.Fatalf("normalized generic read access = %#x, want %#x", got, want) } want := uint32(windows.FILE_GENERIC_READ | windows.WRITE_DAC | windows.WRITE_OWNER) if got := normalizeWindowsNTDesiredAccess(windows.GENERIC_READ | windows.WRITE_DAC | windows.WRITE_OWNER); got != want { t.Fatalf("normalized protected read access = %#x, want %#x", got, want) } } func TestOwnerOnlyDACLNativeShape(t *testing.T) { directory := filepath.Join(t.TempDir(), "auth") if err := os.Mkdir(directory, 0o700); err != nil { t.Fatal(err) } parents, target, err := openCanonicalWindowsParent(directory) if err != nil { t.Fatalf("openCanonicalWindowsParent() error = %T %v; relative-open matrix: %s", err, err, windowsRelativeOpenMatrix(directory)) } defer parents.Close() handle, err := openWindowsRelativeComponent( parents.handles[len(parents.handles)-1], target, true, windows.GENERIC_READ|windows.WRITE_DAC|windows.WRITE_OWNER, ) if err != nil { t.Fatalf("openWindowsRelativeComponent() error = %T %v", err, err) } defer windows.CloseHandle(handle) if err := setOwnerOnlyDACL(handle); err != nil { t.Fatalf("setOwnerOnlyDACL() error = %T %v", err, err) } descriptor, err := windows.GetSecurityInfo(handle, windows.SE_FILE_OBJECT, windows.OWNER_SECURITY_INFORMATION|windows.DACL_SECURITY_INFORMATION) if err != nil || descriptor == nil { t.Fatalf("GetSecurityInfo() error = %T %v descriptor=%p", err, err, descriptor) } owner, ownerDefaulted, err := descriptor.Owner() if err != nil || owner == nil { t.Fatalf("security descriptor owner error = %T %v owner=%p defaulted=%t", err, err, owner, ownerDefaulted) } control, _, err := descriptor.Control() if err != nil { t.Fatalf("security descriptor control error = %T %v", err, err) } dacl, daclDefaulted, err := descriptor.DACL() if err != nil || dacl == nil { t.Fatalf("security descriptor DACL error = %T %v dacl=%p defaulted=%t control=%#x", err, err, dacl, daclDefaulted, uint16(control)) } if dacl.AceCount != 1 { t.Fatalf("security descriptor ACE count = %d, want 1 (control=%#x)", dacl.AceCount, uint16(control)) } var ace *windows.ACCESS_ALLOWED_ACE if err := windows.GetAce(dacl, 0, &ace); err != nil || ace == nil { t.Fatalf("security descriptor ACE error = %T %v ace=%p", err, err, ace) } ownerSID, err := currentOwnerSID() if err != nil { t.Fatalf("currentOwnerSID() error = %T %v", err, err) } aceSID := (*windows.SID)(unsafe.Pointer(&ace.SidStart)) if !windows.EqualSid(owner, ownerSID) || !windows.EqualSid(aceSID, ownerSID) || control&windows.SE_DACL_PROTECTED == 0 || daclDefaulted || ace.Header.AceType != windows.ACCESS_ALLOWED_ACE_TYPE || ace.Header.AceFlags != 0 || !isOwnerOnlyFullControlMask(uint32(ace.Mask)) { t.Fatalf("owner-only DACL shape invalid: ownerMatch=%t aceOwnerMatch=%t control=%#x daclDefaulted=%t aceType=%d aceFlags=%#x mask=%#x", windows.EqualSid(owner, ownerSID), windows.EqualSid(aceSID, ownerSID), uint16(control), daclDefaulted, ace.Header.AceType, ace.Header.AceFlags, uint32(ace.Mask)) } if err := validateOwnerOnlyDACL(handle); err != nil { t.Fatalf("validateOwnerOnlyDACL() rejected native shape: %T %v (control=%#x mask=%#x)", err, err, uint16(control), uint32(ace.Mask)) } } func windowsRelativeOpenMatrix(path string) string { volume := filepath.VolumeName(path) root := volume + `\` components := strings.Split(strings.TrimPrefix(path, root), `\`) if volume == "" || len(components) == 0 || components[0] == "" { return "invalid diagnostic path" } rootHandle, err := openWindowsComponentWithAccess(root, true, windows.GENERIC_READ) if err != nil { return fmt.Sprintf("root=%T %v", err, err) } defer windows.CloseHandle(rootHandle) type attempt struct { name string access uint32 options uint32 shareMode uint32 } attempts := []attempt{ {name: "generic-read-directory", access: windows.GENERIC_READ, options: windows.FILE_DIRECTORY_FILE}, {name: "generic-read-sync-directory", access: windows.GENERIC_READ, options: windows.FILE_DIRECTORY_FILE | windows.FILE_SYNCHRONOUS_IO_NONALERT}, {name: "generic-read-reparse-directory", access: windows.GENERIC_READ, options: windows.FILE_DIRECTORY_FILE | windows.FILE_OPEN_REPARSE_POINT}, {name: "generic-read-all-options", access: windows.GENERIC_READ, options: windows.FILE_DIRECTORY_FILE | windows.FILE_SYNCHRONOUS_IO_NONALERT | windows.FILE_OPEN_REPARSE_POINT}, {name: "file-read-all-options", access: windows.FILE_GENERIC_READ, options: windows.FILE_DIRECTORY_FILE | windows.FILE_SYNCHRONOUS_IO_NONALERT | windows.FILE_OPEN_REPARSE_POINT}, {name: "traverse-read-all-options", access: windows.GENERIC_READ | windows.FILE_TRAVERSE, options: windows.FILE_DIRECTORY_FILE | windows.FILE_SYNCHRONOUS_IO_NONALERT | windows.FILE_OPEN_REPARSE_POINT}, {name: "generic-read-all-share", access: windows.GENERIC_READ, options: windows.FILE_DIRECTORY_FILE | windows.FILE_SYNCHRONOUS_IO_NONALERT | windows.FILE_OPEN_REPARSE_POINT, shareMode: windows.FILE_SHARE_READ | windows.FILE_SHARE_WRITE | windows.FILE_SHARE_DELETE}, } results := make([]string, 0, len(attempts)) for _, test := range attempts { shareMode := test.shareMode if shareMode == 0 { shareMode = windowsRetainedHandleShareMode } handle, openErr := openWindowsRelativeObjectWithShareMode( rootHandle, components[0], test.access, windows.FILE_OPEN, test.options, nil, shareMode, ) if openErr == nil { windows.CloseHandle(handle) results = append(results, test.name+"=ok") continue } results = append(results, fmt.Sprintf("%s=%T %v", test.name, openErr, openErr)) } return strings.Join(results, "; ") } 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 { t.Fatal(err) } if err := ProtectPrivateDirectory(directory); err != nil { t.Fatal(err) } path := filepath.Join(directory, ".auth.lock") file, err := createCanonicalNewPrivateFile(path, 0o600) if err != nil { t.Fatal(err) } defer file.Close() if err := ValidatePrivateRegular(path); err != nil { t.Fatalf("new lock DACL error = %v", err) } } func TestGenericNewFileAllowsInheritedOperatorParentButAuthNewFileRequiresPrivateParent(t *testing.T) { directory := filepath.Join(t.TempDir(), "operator-output") if err := os.Mkdir(directory, 0o700); err != nil { t.Fatal(err) } if err := setPermissiveDACL(directory); err != nil { t.Fatal(err) } if err := WriteCanonicalNewFile(filepath.Join(directory, "candidates.yaml"), []byte("reviewed: []\n"), 0o600); err != nil { t.Fatalf("generic operator output error = %v", err) } if err := WriteCanonicalNewPrivateFile(filepath.Join(directory, "auth.json"), []byte("record"), 0o600); !errors.Is(err, ErrUnsafeFile) { t.Fatalf("auth record in inherited directory error = %v, want ErrUnsafeFile", err) } } func TestWithWindowsSecurityDescriptorKeepsOwnedDescriptorValidDuringInspection(t *testing.T) { directory := filepath.Join(t.TempDir(), "auth") if err := os.Mkdir(directory, 0o700); err != nil { t.Fatal(err) } if err := ProtectPrivateDirectory(directory); err != nil { t.Fatal(err) } path := filepath.Join(directory, "users.yaml") if err := os.WriteFile(path, []byte("private"), 0o600); err != nil { t.Fatal(err) } if err := ProtectPrivateRegular(path); err != nil { t.Fatal(err) } handle, err := openWindowsComponent(path, false) if err != nil { t.Fatal(err) } defer windows.CloseHandle(handle) if err := withWindowsSecurityDescriptor(handle, func(descriptor *windows.SECURITY_DESCRIPTOR) error { runtime.GC() owner, _, err := descriptor.Owner() if err != nil || owner == nil { t.Fatalf("descriptor owner error = %v", err) } dacl, _, err := descriptor.DACL() if err != nil || dacl == nil || dacl.AceCount != 1 { t.Fatalf("descriptor DACL error = %v", err) } return nil }); err != nil { t.Fatalf("withWindowsSecurityDescriptor() error = %v", err) } } func TestReplaceCanonicalRegularCreatesPrivateTemporaryAndReplacement(t *testing.T) { directory := filepath.Join(t.TempDir(), "auth") if err := os.Mkdir(directory, 0o700); err != nil { t.Fatal(err) } if err := ProtectPrivateDirectory(directory); err != nil { t.Fatal(err) } path := filepath.Join(directory, "users.yaml") if err := os.WriteFile(path, []byte("old"), 0o600); err != nil { t.Fatal(err) } if err := ProtectPrivateRegular(path); err != nil { t.Fatal(err) } temporary, err := writePrivateTemporary(directory, []byte("temporary")) if err != nil { t.Fatal(err) } t.Cleanup(func() { _ = os.Remove(temporary) }) if err := ValidatePrivateRegular(temporary); err != nil { t.Fatalf("temporary DACL error = %v", err) } if err := ReplaceCanonicalRegular(path, []byte("replacement"), 0o600); err != nil { t.Fatal(err) } if err := ValidatePrivateRegular(path); err != nil { t.Fatalf("replacement DACL error = %v", err) } } func TestReplaceCanonicalRegularRejectsReparseParent(t *testing.T) { root := t.TempDir() realDirectory := filepath.Join(root, "real") if err := os.Mkdir(realDirectory, 0o700); err != nil { t.Fatal(err) } if err := ProtectPrivateDirectory(realDirectory); err != nil { t.Fatal(err) } target := filepath.Join(realDirectory, "users.yaml") if err := os.WriteFile(target, []byte("old"), 0o600); err != nil { t.Fatal(err) } if err := ProtectPrivateRegular(target); err != nil { t.Fatal(err) } parentLink := filepath.Join(root, "reparse-parent") if err := os.Symlink(realDirectory, parentLink); err != nil { t.Skipf("Windows host does not permit test symlink creation: %v", err) } if err := ReplaceCanonicalRegular(filepath.Join(parentLink, "users.yaml"), []byte("new"), 0o600); !errors.Is(err, ErrUnsafeFile) { t.Fatalf("reparse-parent replacement error = %v, want ErrUnsafeFile", err) } } func TestOpenCanonicalWindowsParentBlocksParentRename(t *testing.T) { root := t.TempDir() realDirectory := filepath.Join(root, "real") if err := os.Mkdir(realDirectory, 0o700); err != nil { t.Fatal(err) } if err := ProtectPrivateDirectory(realDirectory); err != nil { t.Fatal(err) } target := filepath.Join(realDirectory, "users.yaml") if err := os.WriteFile(target, []byte("old"), 0o600); err != nil { t.Fatal(err) } if err := ProtectPrivateRegular(target); err != nil { t.Fatal(err) } parents, _, err := openCanonicalWindowsParent(target) if err != nil { t.Fatal(err) } renamed := realDirectory + "-renamed" if err := os.Rename(realDirectory, renamed); err == nil { parents.Close() t.Fatal("parent rename succeeded while canonical replacement handles were retained") } parents.Close() if err := os.Rename(realDirectory, renamed); err != nil { t.Fatalf("parent rename after closing canonical replacement handles: %v", err) } } func TestCreateCanonicalNewPrivateFilePinsWindowsParentBeforeCreate(t *testing.T) { root := t.TempDir() parent := filepath.Join(root, "auth") if err := os.Mkdir(parent, 0o700); err != nil { t.Fatal(err) } if err := ProtectPrivateDirectory(parent); err != nil { t.Fatal(err) } path := filepath.Join(parent, "archive.zip") attemptedSwap := false restoreHook := SetPrivateDirectoryTestHookForTest(func(stage string) { if stage != "after-canonical-private-file-parent-open" || attemptedSwap { return } attemptedSwap = true if err := os.Rename(parent, parent+"-renamed"); err == nil { t.Fatal("parent rename succeeded while private file creation retained its handle") } }) t.Cleanup(restoreHook) file, err := CreateCanonicalNewPrivateFile(path) if err != nil { t.Fatal(err) } if _, err := file.Write([]byte("staged")); err != nil { _ = file.Close() t.Fatal(err) } if err := file.Close(); err != nil { t.Fatal(err) } if !attemptedSwap { t.Fatal("private file creator did not retain the parent before creation") } if err := ValidatePrivateRegular(path); err != nil { t.Fatalf("ValidatePrivateRegular() = %v, want owner-private staged file", err) } } func setPermissiveDACL(path string) error { world, err := windows.StringToSid("S-1-1-0") if err != nil { return err } var pinner runtime.Pinner pinner.Pin(world) defer pinner.Unpin() acl, err := windows.ACLFromEntries([]windows.EXPLICIT_ACCESS{{ AccessPermissions: windows.GENERIC_READ | windows.GENERIC_WRITE, AccessMode: windows.GRANT_ACCESS, Trustee: windows.TRUSTEE{ TrusteeForm: windows.TRUSTEE_IS_SID, TrusteeType: windows.TRUSTEE_IS_GROUP, TrusteeValue: windows.TrusteeValueFromSID(world), }, }}, nil) if err != nil { return err } return windows.SetNamedSecurityInfo(path, windows.SE_FILE_OBJECT, windows.DACL_SECURITY_INFORMATION|windows.PROTECTED_DACL_SECURITY_INFORMATION, nil, nil, acl, nil) }