547 lines
18 KiB
Go
547 lines
18 KiB
Go
//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)
|
|
}
|
|
if err := ProtectPrivateDirectory(directory); err != nil {
|
|
t.Fatalf("ProtectPrivateDirectory() after inherited file error = %T %v", err, err)
|
|
}
|
|
if err := ProtectPrivateRegular(path); err != nil {
|
|
t.Fatalf("ProtectPrivateRegular() inherited file error = %T %v", err, err)
|
|
}
|
|
if err := ValidatePrivateRegular(path); err != nil {
|
|
t.Fatalf("ValidatePrivateRegular() 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)
|
|
}
|
|
if err := file.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
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)
|
|
}
|