//go:build windows package safeio import ( "errors" "os" "path/filepath" "runtime" "strings" "testing" "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) { if err := setPermissiveDACL(path); err != nil { t.Fatal(err) } var err error if name == "directory" { err = ValidatePrivateDirectory(path) } else { err = ValidatePrivateRegular(path) } if !errors.Is(err, ErrUnsafeFile) { t.Fatalf("private validation error = %v, want ErrUnsafeFile", err) } }) } } 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 { 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) }