package fileops import ( "os" "path/filepath" "strings" "testing" "golang.org/x/sys/windows" ) func TestWindowsAtomicWritePreservesPrivateDACLInSharedParent(t *testing.T) { dir := t.TempDir() user, err := windows.GetCurrentProcessToken().GetTokenUser() if err != nil { t.Fatal(err) } sd, err := windows.SecurityDescriptorFromString("D:P(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)(A;OICI;FA;;;" + user.User.Sid.String() + ")(A;OICI;GR;;;WD)") if err != nil { t.Fatal(err) } dacl, _, err := sd.DACL() if err != nil { t.Fatal(err) } if err := windows.SetNamedSecurityInfo(dir, windows.SE_FILE_OBJECT, windows.DACL_SECURITY_INFORMATION|windows.PROTECTED_DACL_SECURITY_INFORMATION, nil, nil, dacl, nil); err != nil { t.Fatal(err) } path := filepath.Join(dir, "private.conf") file, err := CreatePrivateFile(path) if err != nil { t.Fatal(err) } if _, err := file.WriteString("private original"); err != nil { t.Fatal(err) } if err := file.Close(); err != nil { t.Fatal(err) } security := func(path string) string { t.Helper() sd, err := windows.GetNamedSecurityInfo(path, windows.SE_FILE_OBJECT, windows.OWNER_SECURITY_INFORMATION|windows.DACL_SECURITY_INFORMATION) if err != nil { t.Fatal(err) } // SetNamedSecurityInfo adds AUTO_INHERITED while adopting the current // Windows inheritance model. Ignore only that informational control bit; // owner, protection, ordered ACEs, rights and SIDs must match exactly. descriptor := sd.String() prefix, dacl, _ := strings.Cut(descriptor, "D:") control, entries, _ := strings.Cut(dacl, "(") return prefix + "D:" + strings.ReplaceAll(control, "AI", "") + "(" + entries } before := security(path) originalSecurity, err := windows.GetNamedSecurityInfo(path, windows.SE_FILE_OBJECT, windows.OWNER_SECURITY_INFORMATION) if err != nil { t.Fatal(err) } owner, _, err := originalSecurity.Owner() if err != nil || owner == nil || !owner.Equals(user.User.Sid) { t.Fatalf("private file owner must be the process user: %s, %v", before, err) } if err := AtomicWritePreserve(path, []byte("private updated"), 0644); err != nil { t.Fatal(err) } if after := security(path); after != before { t.Fatalf("private security metadata changed:\nbefore %s\nafter %s", before, after) } content, err := os.ReadFile(path) if err != nil || string(content) != "private updated" { t.Fatalf("replacement = %q, %v", content, err) } backup, err := WriteUnique(dir, "private.conf.bak-*", content, 0600) if err != nil { t.Fatal(err) } if descriptor := security(backup); strings.Contains(descriptor, ";;;WD)") { t.Fatalf("backup inherited Everyone read access: %s", descriptor) } fresh := filepath.Join(dir, "new-private.conf") if err := AtomicWritePreserve(fresh, []byte("new private value"), 0600); err != nil { t.Fatal(err) } if descriptor := security(fresh); strings.Contains(descriptor, ";;;WD)") { t.Fatalf("new private config inherited Everyone read access: %s", descriptor) } }