mirror of
https://github.com/tailscale/tailscale.git
synced 2025-02-20 11:58:39 +00:00

I moved the actual rename into separate, GOOS-specific files. On non-Windows, we do a simple os.Rename. On Windows, we first try ReplaceFile with a fallback to os.Rename if the target file does not exist. ReplaceFile is the recommended way to rename the file in this use case, as it preserves attributes and ACLs set on the target file. Updates #14428 Signed-off-by: Aaron Klotz <aaron@tailscale.com>
147 lines
3.6 KiB
Go
147 lines
3.6 KiB
Go
// Copyright (c) Tailscale Inc & AUTHORS
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
package atomicfile
|
|
|
|
import (
|
|
"os"
|
|
"testing"
|
|
"unsafe"
|
|
|
|
"golang.org/x/sys/windows"
|
|
)
|
|
|
|
var _SECURITY_RESOURCE_MANAGER_AUTHORITY = windows.SidIdentifierAuthority{[6]byte{0, 0, 0, 0, 0, 9}}
|
|
|
|
// makeRandomSID generates a SID derived from a v4 GUID.
|
|
// This is basically the same algorithm used by browser sandboxes for generating
|
|
// random SIDs.
|
|
func makeRandomSID() (*windows.SID, error) {
|
|
guid, err := windows.GenerateGUID()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
rids := *((*[4]uint32)(unsafe.Pointer(&guid)))
|
|
|
|
var pSID *windows.SID
|
|
if err := windows.AllocateAndInitializeSid(&_SECURITY_RESOURCE_MANAGER_AUTHORITY, 4, rids[0], rids[1], rids[2], rids[3], 0, 0, 0, 0, &pSID); err != nil {
|
|
return nil, err
|
|
}
|
|
defer windows.FreeSid(pSID)
|
|
|
|
// Make a copy that lives on the Go heap
|
|
return pSID.Copy()
|
|
}
|
|
|
|
func getExistingFileSD(name string) (*windows.SECURITY_DESCRIPTOR, error) {
|
|
const infoFlags = windows.DACL_SECURITY_INFORMATION
|
|
return windows.GetNamedSecurityInfo(name, windows.SE_FILE_OBJECT, infoFlags)
|
|
}
|
|
|
|
func getExistingFileDACL(name string) (*windows.ACL, error) {
|
|
sd, err := getExistingFileSD(name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
dacl, _, err := sd.DACL()
|
|
return dacl, err
|
|
}
|
|
|
|
func addDenyACEForRandomSID(dacl *windows.ACL) (*windows.ACL, error) {
|
|
randomSID, err := makeRandomSID()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
randomSIDTrustee := windows.TRUSTEE{nil, windows.NO_MULTIPLE_TRUSTEE,
|
|
windows.TRUSTEE_IS_SID, windows.TRUSTEE_IS_UNKNOWN,
|
|
windows.TrusteeValueFromSID(randomSID)}
|
|
|
|
entries := []windows.EXPLICIT_ACCESS{
|
|
{
|
|
windows.GENERIC_ALL,
|
|
windows.DENY_ACCESS,
|
|
windows.NO_INHERITANCE,
|
|
randomSIDTrustee,
|
|
},
|
|
}
|
|
|
|
return windows.ACLFromEntries(entries, dacl)
|
|
}
|
|
|
|
func setExistingFileDACL(name string, dacl *windows.ACL) error {
|
|
return windows.SetNamedSecurityInfo(name, windows.SE_FILE_OBJECT,
|
|
windows.DACL_SECURITY_INFORMATION, nil, nil, dacl, nil)
|
|
}
|
|
|
|
// makeOrigFileWithCustomDACL creates a new, temporary file with a custom
|
|
// DACL that we can check for later. It returns the name of the temporary
|
|
// file and the security descriptor for the file in SDDL format.
|
|
func makeOrigFileWithCustomDACL() (name, sddl string, err error) {
|
|
f, err := os.CreateTemp("", "foo*.tmp")
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
name = f.Name()
|
|
if err := f.Close(); err != nil {
|
|
return "", "", err
|
|
}
|
|
f = nil
|
|
defer func() {
|
|
if err != nil {
|
|
os.Remove(name)
|
|
}
|
|
}()
|
|
|
|
dacl, err := getExistingFileDACL(name)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
|
|
// Add a harmless, deny-only ACE for a random SID that isn't used for anything
|
|
// (but that we can check for later).
|
|
dacl, err = addDenyACEForRandomSID(dacl)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
|
|
if err := setExistingFileDACL(name, dacl); err != nil {
|
|
return "", "", err
|
|
}
|
|
|
|
sd, err := getExistingFileSD(name)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
|
|
return name, sd.String(), nil
|
|
}
|
|
|
|
func TestPreserveSecurityInfo(t *testing.T) {
|
|
// Make a test file with a custom ACL.
|
|
origFileName, want, err := makeOrigFileWithCustomDACL()
|
|
if err != nil {
|
|
t.Fatalf("makeOrigFileWithCustomDACL returned %v", err)
|
|
}
|
|
t.Cleanup(func() {
|
|
os.Remove(origFileName)
|
|
})
|
|
|
|
if err := WriteFile(origFileName, []byte{}, 0); err != nil {
|
|
t.Fatalf("WriteFile returned %v", err)
|
|
}
|
|
|
|
// We expect origFileName's security descriptor to be unchanged despite
|
|
// the WriteFile call.
|
|
sd, err := getExistingFileSD(origFileName)
|
|
if err != nil {
|
|
t.Fatalf("getExistingFileSD(%q) returned %v", origFileName, err)
|
|
}
|
|
|
|
if got := sd.String(); got != want {
|
|
t.Errorf("security descriptor comparison failed: got %q, want %q", got, want)
|
|
}
|
|
}
|