Files
ThothII/tools/tht/internal/safeio/private_windows.go
T

343 lines
10 KiB
Go

//go:build windows
package safeio
import (
"errors"
"os"
"path/filepath"
"runtime"
"strings"
"unsafe"
"golang.org/x/sys/windows"
)
func createPrivateDirectory(path string) error {
parents, target, err := openCanonicalWindowsParent(path)
if err != nil {
return ErrUnsafeFile
}
defer parents.Close()
security, err := newOwnerOnlySecurityDescriptor()
if err != nil {
return ErrUnsafeFile
}
defer security.Close()
attributes := &windows.SecurityAttributes{
Length: uint32(unsafe.Sizeof(windows.SecurityAttributes{})),
SecurityDescriptor: security.descriptor,
}
err = windows.CreateDirectory(windows.StringToUTF16Ptr(filepath.Join(parents.directory, target)), attributes)
runtime.KeepAlive(security)
if errors.Is(err, windows.ERROR_ALREADY_EXISTS) {
return os.ErrExist
}
if err != nil {
return ErrUnsafeFile
}
return ValidatePrivateDirectory(path)
}
// ProtectPrivateDirectory sets a protected DACL containing only the current owner.
func ProtectPrivateDirectory(path string) error {
parents, target, err := openCanonicalWindowsParent(path)
if err != nil {
return ErrUnsafeFile
}
defer parents.Close()
handle, err := openWindowsComponentWithAccess(filepath.Join(parents.directory, target), true, windows.GENERIC_READ|windows.WRITE_DAC|windows.WRITE_OWNER)
if err != nil {
return ErrUnsafeFile
}
defer windows.CloseHandle(handle)
if err := setOwnerOnlyDACL(handle); err != nil {
return ErrUnsafeFile
}
return validateOwnerOnlyDACL(handle)
}
// ValidatePrivateDirectory requires a canonical directory protected for its current owner only.
func ValidatePrivateDirectory(path string) error {
parents, target, err := openCanonicalWindowsParent(path)
if err != nil {
return ErrUnsafeFile
}
defer parents.Close()
handle, err := openWindowsComponent(filepath.Join(parents.directory, target), true)
if err != nil {
return ErrUnsafeFile
}
defer windows.CloseHandle(handle)
if err := validateOwnerOnlyDACL(handle); err != nil {
return ErrUnsafeFile
}
return nil
}
// ProtectPrivateRegular sets a protected DACL containing only the current owner.
func ProtectPrivateRegular(path string) error {
parents, target, err := openCanonicalWindowsParent(path)
if err != nil {
return ErrUnsafeFile
}
defer parents.Close()
handle, err := openWindowsComponentWithAccess(filepath.Join(parents.directory, target), false, windows.GENERIC_READ|windows.WRITE_DAC|windows.WRITE_OWNER)
if err != nil {
return ErrUnsafeFile
}
defer windows.CloseHandle(handle)
if err := setOwnerOnlyDACL(handle); err != nil {
return ErrUnsafeFile
}
return validateOwnerOnlyDACL(handle)
}
// createCanonicalNewPrivateFile installs the owner-only protected DACL in the CreateFile call, so
// another mutation can never observe a newly-created lock with an inherited/default DACL.
func createCanonicalNewPrivateFile(path string, mode os.FileMode) (*os.File, error) {
return createCanonicalNewFile(path, mode, false)
}
func createCanonicalNewPrivateParentFile(path string, mode os.FileMode) (*os.File, error) {
return createCanonicalNewFile(path, mode, true)
}
func createCanonicalNewFile(path string, mode os.FileMode, requirePrivateParent bool) (*os.File, error) {
parents, target, err := openCanonicalWindowsParent(path)
if err != nil || len(parents.handles) == 0 || (requirePrivateParent && validateOwnerOnlyDACL(parents.handles[len(parents.handles)-1]) != nil) {
if parents != nil {
parents.Close()
}
return nil, ErrUnsafeFile
}
defer parents.Close()
security, err := newOwnerOnlySecurityDescriptor()
if err != nil {
return nil, ErrUnsafeFile
}
defer security.Close()
attributes := &windows.SecurityAttributes{
Length: uint32(unsafe.Sizeof(windows.SecurityAttributes{})),
SecurityDescriptor: security.descriptor,
}
handle, err := windows.CreateFile(
windows.StringToUTF16Ptr(filepath.Join(parents.directory, target)),
windows.GENERIC_WRITE,
windowsRetainedHandleShareMode,
attributes,
windows.CREATE_NEW,
windows.FILE_ATTRIBUTE_NORMAL,
0,
)
runtime.KeepAlive(security)
if err != nil {
return nil, err
}
if err := validateOwnerOnlyDACL(handle); err != nil {
_ = windows.CloseHandle(handle)
_ = os.Remove(path)
return nil, ErrUnsafeFile
}
file := os.NewFile(uintptr(handle), "tht-safeio-private")
if file == nil {
_ = windows.CloseHandle(handle)
_ = os.Remove(path)
return nil, ErrUnsafeFile
}
return file, nil
}
// ValidatePrivateRegular requires a canonical, single-link file protected for its current owner only.
func ValidatePrivateRegular(path string) error {
parents, target, err := openCanonicalWindowsParent(path)
if err != nil {
return ErrUnsafeFile
}
defer parents.Close()
handle, err := openWindowsComponent(filepath.Join(parents.directory, target), false)
if err != nil {
return ErrUnsafeFile
}
defer windows.CloseHandle(handle)
if err := validateOwnerOnlyDACL(handle); err != nil {
return ErrUnsafeFile
}
return nil
}
type windowsParentHandles struct {
directory string
handles []windows.Handle
}
func (parents *windowsParentHandles) Close() {
for index := len(parents.handles) - 1; index >= 0; index-- {
_ = windows.CloseHandle(parents.handles[index])
}
}
// openCanonicalWindowsParent retains every directory handle from the volume root through the
// target parent without FILE_SHARE_DELETE. The resulting parent cannot be renamed or replaced by
// a reparse point while an operation uses its absolute child paths.
func openCanonicalWindowsParent(path string) (*windowsParentHandles, string, error) {
if err := ValidateCanonicalPath(path); err != nil {
return nil, "", err
}
volume := filepath.VolumeName(path)
root := volume + `\`
components := strings.Split(strings.TrimPrefix(path, root), `\`)
if volume == "" || len(components) == 0 || components[0] == "" {
return nil, "", ErrUnsafeFile
}
parents := &windowsParentHandles{directory: root}
rootHandle, err := openWindowsComponent(root, true)
if err != nil {
return nil, "", err
}
parents.handles = append(parents.handles, rootHandle)
for _, component := range components[:len(components)-1] {
parents.directory = filepath.Join(parents.directory, component)
handle, err := openWindowsComponent(parents.directory, true)
if err != nil {
parents.Close()
return nil, "", err
}
parents.handles = append(parents.handles, handle)
}
return parents, components[len(components)-1], nil
}
type ownerOnlyDACL struct {
sid *windows.SID
acl *windows.ACL
pinner runtime.Pinner
}
func newOwnerOnlyDACL() (*ownerOnlyDACL, error) {
tokenUser, err := windows.GetCurrentProcessToken().GetTokenUser()
if err != nil || tokenUser == nil || tokenUser.User.Sid == nil {
return nil, ErrUnsafeFile
}
sid, err := tokenUser.User.Sid.Copy()
if err != nil {
return nil, ErrUnsafeFile
}
owner := &ownerOnlyDACL{sid: sid}
owner.pinner.Pin(owner.sid)
acl, err := windows.ACLFromEntries([]windows.EXPLICIT_ACCESS{{
AccessPermissions: windows.GENERIC_ALL,
AccessMode: windows.GRANT_ACCESS,
Trustee: windows.TRUSTEE{
TrusteeForm: windows.TRUSTEE_IS_SID,
TrusteeType: windows.TRUSTEE_IS_USER,
TrusteeValue: windows.TrusteeValueFromSID(owner.sid),
},
}}, nil)
if err != nil {
owner.pinner.Unpin()
return nil, err
}
owner.acl = acl
return owner, nil
}
func (owner *ownerOnlyDACL) Close() {
owner.pinner.Unpin()
}
type ownerOnlySecurityDescriptor struct {
*ownerOnlyDACL
descriptor *windows.SECURITY_DESCRIPTOR
}
func newOwnerOnlySecurityDescriptor() (*ownerOnlySecurityDescriptor, error) {
owner, err := newOwnerOnlyDACL()
if err != nil {
return nil, err
}
descriptor, err := windows.NewSecurityDescriptor()
if err == nil {
err = descriptor.SetOwner(owner.sid, false)
}
if err == nil {
err = descriptor.SetDACL(owner.acl, true, false)
}
if err == nil {
err = descriptor.SetControl(windows.SE_DACL_PROTECTED, windows.SE_DACL_PROTECTED)
}
if err != nil || !descriptor.IsValid() {
owner.Close()
return nil, ErrUnsafeFile
}
return &ownerOnlySecurityDescriptor{ownerOnlyDACL: owner, descriptor: descriptor}, nil
}
func (descriptor *ownerOnlySecurityDescriptor) Close() {
descriptor.ownerOnlyDACL.Close()
}
func setOwnerOnlyDACL(handle windows.Handle) error {
owner, err := newOwnerOnlyDACL()
if err != nil {
return err
}
defer owner.Close()
return windows.SetSecurityInfo(handle, windows.SE_FILE_OBJECT,
windows.OWNER_SECURITY_INFORMATION|windows.DACL_SECURITY_INFORMATION|windows.PROTECTED_DACL_SECURITY_INFORMATION,
owner.sid, nil, owner.acl, nil)
}
func validateOwnerOnlyDACL(handle windows.Handle) error {
ownerSID, err := currentOwnerSID()
if err != nil {
return err
}
return withWindowsSecurityDescriptor(handle, func(descriptor *windows.SECURITY_DESCRIPTOR) error {
owner, _, err := descriptor.Owner()
if err != nil || owner == nil || !windows.EqualSid(owner, ownerSID) {
return ErrUnsafeFile
}
control, _, err := descriptor.Control()
if err != nil || control&windows.SE_DACL_PROTECTED == 0 {
return ErrUnsafeFile
}
dacl, defaulted, err := descriptor.DACL()
if err != nil || defaulted || dacl == nil || dacl.AceCount != 1 {
return ErrUnsafeFile
}
var ace *windows.ACCESS_ALLOWED_ACE
if err := windows.GetAce(dacl, 0, &ace); err != nil || ace == nil || ace.Header.AceType != windows.ACCESS_ALLOWED_ACE_TYPE || ace.Header.AceFlags != 0 || ace.Mask != windows.GENERIC_ALL {
return ErrUnsafeFile
}
aceSID := (*windows.SID)(unsafe.Pointer(&ace.SidStart))
if !windows.EqualSid(aceSID, ownerSID) {
return ErrUnsafeFile
}
return nil
})
}
// withWindowsSecurityDescriptor confines inspection to x/sys's Go-owned descriptor copy. Its
// GetSecurityInfo wrapper releases the native LocalAlloc result with LocalFree before returning.
func withWindowsSecurityDescriptor(handle windows.Handle, inspect func(*windows.SECURITY_DESCRIPTOR) error) error {
descriptor, err := windows.GetSecurityInfo(handle, windows.SE_FILE_OBJECT,
windows.OWNER_SECURITY_INFORMATION|windows.DACL_SECURITY_INFORMATION)
if err != nil || descriptor == nil {
return ErrUnsafeFile
}
return inspect(descriptor)
}
func currentOwnerSID() (*windows.SID, error) {
user, err := windows.GetCurrentProcessToken().GetTokenUser()
if err != nil || user == nil || user.User.Sid == nil {
return nil, ErrUnsafeFile
}
sid, err := user.User.Sid.Copy()
if err != nil {
return nil, ErrUnsafeFile
}
return sid, nil
}