//go:build windows package safeio import ( "errors" "os" "path/filepath" "testing" "github.com/aritmolab/thothii/tools/thothctl/internal/testsupport" "golang.org/x/sys/windows" ) const expectedWindowsRetainedHandleShareMode = windows.FILE_SHARE_READ | windows.FILE_SHARE_WRITE const expectedWindowsOutputHandleShareMode = windows.FILE_SHARE_READ // Keep this contract compile-enforced so Windows cross-test compilation catches a future // FILE_SHARE_DELETE regression even when the tests are compiled on a non-Windows host. var _ [windowsRetainedHandleShareMode - expectedWindowsRetainedHandleShareMode]struct{} var _ [expectedWindowsRetainedHandleShareMode - windowsRetainedHandleShareMode]struct{} var _ [windowsOutputHandleShareMode - expectedWindowsOutputHandleShareMode]struct{} var _ [expectedWindowsOutputHandleShareMode - windowsOutputHandleShareMode]struct{} func TestOpenWindowsComponentBlocksMutationWhileHandleIsRetained(t *testing.T) { t.Run("parent rename", func(t *testing.T) { parent := filepath.Join(t.TempDir(), "parent") if err := os.Mkdir(parent, 0o700); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(parent, "secret"), []byte("secret"), 0o600); err != nil { t.Fatal(err) } handle, err := openWindowsComponent(parent, true) if err != nil { t.Fatal(err) } renamed := parent + "-renamed" if err := os.Rename(parent, renamed); err == nil { windows.CloseHandle(handle) t.Fatal("parent rename succeeded while its safe-I/O handle was retained") } if err := windows.CloseHandle(handle); err != nil { t.Fatal(err) } if err := os.Rename(parent, renamed); err != nil { t.Fatalf("parent rename after closing its safe-I/O handle: %v", err) } }) t.Run("final delete", func(t *testing.T) { path := filepath.Join(t.TempDir(), "secret") if err := os.WriteFile(path, []byte("secret"), 0o600); err != nil { t.Fatal(err) } handle, err := openWindowsComponent(path, false) if err != nil { t.Fatal(err) } if err := os.Remove(path); err == nil { windows.CloseHandle(handle) t.Fatal("final-file deletion succeeded while its safe-I/O handle was retained") } if err := windows.CloseHandle(handle); err != nil { t.Fatal(err) } if err := os.Remove(path); err != nil { t.Fatalf("final-file deletion after closing its safe-I/O handle: %v", err) } }) } func TestWriteCanonicalExclusiveRequiresRestrictiveMode(t *testing.T) { if err := writeCanonicalExclusive(`C:\\tmp\\thothctl-output.yaml`, []byte("x"), 0o640); err == nil { t.Fatal("accepted non-restrictive output mode") } } func TestWriteCanonicalExclusiveCreatesProtectedOwnerOnlyDACL(t *testing.T) { root, err := filepath.EvalSymlinks(t.TempDir()) if err != nil { t.Fatal(err) } path := filepath.Join(root, "candidate.yaml") if err := writeCanonicalExclusive(path, []byte("x"), 0o600); err != nil { t.Fatal(err) } sd, err := windows.GetNamedSecurityInfo(path, windows.SE_FILE_OBJECT, windows.DACL_SECURITY_INFORMATION) if err != nil { t.Fatal(err) } control, _, err := sd.Control() if err != nil { t.Fatal(err) } if control&windows.SE_DACL_PROTECTED == 0 { t.Fatalf("output DACL control = %#x, want protected", control) } acl, _, err := sd.DACL() if err != nil || acl == nil || acl.AceCount != 1 { t.Fatalf("output DACL = %#v, err=%v; want one owner ACE", acl, err) } } func TestWriteCanonicalExclusiveAllowsOwnerOnlyWriteAndIdentityRecheck(t *testing.T) { root, err := filepath.EvalSymlinks(t.TempDir()) if err != nil { t.Fatal(err) } path := filepath.Join(root, "candidate.yaml") if err := writeCanonicalExclusive(path, []byte("candidates: []\n"), 0o600); err != nil { t.Fatalf("owner-only output write/recheck failed: %v", err) } contents, err := os.ReadFile(path) if err != nil || string(contents) != "candidates: []\n" { t.Fatalf("output = %q, err=%v", contents, err) } if err := writeCanonicalExclusive(path, []byte("replacement\n"), 0o600); !errors.Is(err, ErrUnsafeFile) { t.Fatalf("existing output replacement = %v, want ErrUnsafeFile", err) } } func TestWriteCanonicalExclusiveRejectsReparseParent(t *testing.T) { root, err := filepath.EvalSymlinks(t.TempDir()) if err != nil { t.Fatal(err) } realParent := filepath.Join(root, "real-parent") if err := os.Mkdir(realParent, 0o700); err != nil { t.Fatal(err) } linkedParent := filepath.Join(root, "linked-parent") testsupport.SymlinkOrSkip(t, realParent, linkedParent) path := filepath.Join(linkedParent, "candidate.yaml") if err := writeCanonicalExclusive(path, []byte("unsafe\n"), 0o600); !errors.Is(err, ErrUnsafeFile) { t.Fatalf("reparse parent output = %v, want ErrUnsafeFile", err) } if _, err := os.Stat(filepath.Join(realParent, "candidate.yaml")); !os.IsNotExist(err) { t.Fatalf("reparse parent write created target: stat err=%v", err) } }