mirror of
				https://github.com/tailscale/tailscale.git
				synced 2025-10-31 08:11:32 +01: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)
 | |
| 	}
 | |
| }
 |