//go:build windows package recoverytrust import ( "fmt" "os" "unsafe" "gitgud.io/mike/mpv-manager/internal/fileops" "golang.org/x/sys/windows" ) // FILE_DELETE_CHILD is a directory access right, not exposed by x/sys/windows. const fileDeleteChild = 0x0040 func validateRecoveryPermissions(path string, _ os.FileInfo, ancestor bool) error { return validateWindowsRecoveryPermissions(path, ancestor, false) } func validatePrivatePermissions(path string, _ os.FileInfo) error { return validateWindowsRecoveryPermissions(path, false, true) } func validateWindowsRecoveryPermissions(path string, ancestor, private bool) error { sd, err := windows.GetNamedSecurityInfo(path, windows.SE_FILE_OBJECT, windows.OWNER_SECURITY_INFORMATION|windows.DACL_SECURITY_INFORMATION) if err != nil { return fmt.Errorf("read security descriptor for %s: %w", path, err) } user, err := windows.GetCurrentProcessToken().GetTokenUser() if err != nil { return err } // Windows system directories may be owned by the servicing identity. trustedInstaller, _, _, _ := windows.LookupSID("", `NT SERVICE\TrustedInstaller`) trusted := func(sid *windows.SID) bool { return sid != nil && (sid.Equals(user.User.Sid) || sid.IsWellKnown(windows.WinLocalSystemSid) || sid.IsWellKnown(windows.WinBuiltinAdministratorsSid) || (trustedInstaller != nil && sid.Equals(trustedInstaller))) } owner, _, err := sd.Owner() if err != nil || !trusted(owner) { return fmt.Errorf("%s has an untrusted recovery owner", path) } dacl, _, err := sd.DACL() if err != nil || dacl == nil { return fmt.Errorf("%s has no restrictive recovery DACL", path) } // Ancestors may grant permission to create unrelated children, but must // not allow an untrusted principal to replace or change existing paths. mask := uint32(windows.GENERIC_ALL | windows.GENERIC_WRITE | windows.WRITE_DAC | windows.WRITE_OWNER | windows.DELETE | fileDeleteChild) if !ancestor { mask |= windows.FILE_WRITE_DATA | windows.FILE_APPEND_DATA | windows.FILE_WRITE_EA | windows.FILE_WRITE_ATTRIBUTES } if private { mask |= windows.GENERIC_READ | windows.FILE_READ_DATA } for index := uint32(0); index < uint32(dacl.AceCount); index++ { var ace *windows.ACCESS_ALLOWED_ACE if err := windows.GetAce(dacl, index, &ace); err != nil { return err } if ace.Header.AceFlags&windows.INHERIT_ONLY_ACE != 0 || ace.Header.AceType == windows.ACCESS_DENIED_ACE_TYPE { continue } if ace.Header.AceType != windows.ACCESS_ALLOWED_ACE_TYPE { return fmt.Errorf("%s has an unsupported recovery access rule", path) } sid := (*windows.SID)(unsafe.Pointer(&ace.SidStart)) if uint32(ace.Mask)&mask != 0 && !trusted(sid) { if private { return fmt.Errorf("private recovery file %s must be private to its owner", path) } return fmt.Errorf("%s permits another principal to modify recovery state", path) } } return nil } // CreatePrivateFile applies private permissions before bytes are written. func CreatePrivateFile(path string) (*os.File, error) { return fileops.CreatePrivateFile(path) }