|
/***************************************************************************\
*
* File: SafeSecurityHelper.cs
*
* Purpose: Helper functions that require elevation but are safe to use.
*
* History:
* 10/15/04: prasadt Created
*
* Copyright (C) 2004 by Microsoft Corporation. All rights reserved.
*
\***************************************************************************/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Security;
using System.Security.Permissions;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
#if SYSTEM_XAML
using TypeConverterHelper = System.Xaml.TypeConverterHelper;
#else
using Microsoft.Win32;
using MS.Win32;
using TypeConverterHelper = System.Windows.Markup.TypeConverterHelper;
#endif
#if PRESENTATIONFRAMEWORK
using System.Windows;
using System.Windows.Media;
#endif
// The SafeSecurityHelper class differs between assemblies and could not actually be
// shared, so it is duplicated across namespaces to prevent name collision.
#if WINDOWS_BASE
namespace MS.Internal.WindowsBase
#elif PRESENTATION_CORE
namespace MS.Internal.PresentationCore
#elif PRESENTATIONFRAMEWORK
namespace MS.Internal.PresentationFramework
#elif REACHFRAMEWORK
namespace MS.Internal.ReachFramework
#elif DRT
namespace MS.Internal.Drt
#elif SYSTEM_XAML
namespace System.Xaml
#else
#error Class is being used from an unknown assembly.
#endif
{
internal static partial class SafeSecurityHelper
{
#if PRESENTATION_CORE
///<summary>
/// Given a rectangle with coords in local screen coordinates.
/// Return the rectangle in global screen coordinates.
///</summary>
///<SecurityNote>
/// Critical - calls a critical function.
/// TreatAsSafe - handing out a transformed rect is considered safe.
///
/// Although we are calling a function that passes an array, and no. of elements.
/// We limit this call to only call this function with a 2 as the count of elements.
/// Thereby containing any potential for the call to Win32 to cause a buffer overrun.
///</SecurityNote>
[SecurityCritical, SecurityTreatAsSafe ]
internal static void TransformLocalRectToScreen(HandleRef hwnd, ref NativeMethods.RECT rcWindowCoords)
{
int retval = MS.Internal.WindowsBase.NativeMethodsSetLastError.MapWindowPoints(hwnd , new HandleRef(null, IntPtr.Zero), ref rcWindowCoords, 2);
int win32Err = Marshal.GetLastWin32Error();
if(retval == 0 && win32Err != 0)
{
throw new System.ComponentModel.Win32Exception(win32Err);
}
}
#endif
#if PRESENTATION_CORE || PRESENTATIONFRAMEWORK ||REACHFRAMEWORK || DEBUG
#if !WINDOWS_BASE && !SYSTEM_XAML
/// <summary>
/// Given an assembly, returns the partial name of the assembly.
/// </summary>
/// <SecurityNote>
/// This code used to perform an elevation but no longer needs to.
/// The method is being kept in this class to ease auditing and
/// should have a security review if changed.
/// The string returned does not contain path information.
/// This code is duplicated in SafeSecurityHelperPBT.cs.
/// </SecurityNote>
internal static string GetAssemblyPartialName(Assembly assembly)
{
AssemblyName name = new AssemblyName(assembly.FullName);
string partialName = name.Name;
return (partialName != null) ? partialName : String.Empty;
}
#endif
#endif
#if PRESENTATIONFRAMEWORK
/// <summary>
/// Get the full assembly name by combining the partial name passed in
/// with everything else from proto assembly.
/// </summary>
/// <SecurityNote>
/// This code used to perform an elevation but no longer needs to.
/// The method is being kept in this class to ease auditing and
/// should have a security review if changed.
/// The string returned does not contain path information.
/// </SecurityNote>
internal static string GetFullAssemblyNameFromPartialName(
Assembly protoAssembly,
string partialName)
{
AssemblyName name = new AssemblyName(protoAssembly.FullName);
name.Name = partialName;
return name.FullName;
}
/// <SecurityNote>
/// Critical: This code accesses PresentationSource which is critical but does not
/// expose it.
/// TreatAsSafe: PresentationSource is not exposed and Client to Screen co-ordinates is
/// safe to expose
/// </SecurityNote>
[SecurityCritical,SecurityTreatAsSafe]
internal static Point ClientToScreen(UIElement relativeTo, Point point)
{
GeneralTransform transform;
PresentationSource source = PresentationSource.CriticalFromVisual(relativeTo);
if (source == null)
{
return new Point(double.NaN, double.NaN);
}
transform = relativeTo.TransformToAncestor(source.RootVisual);
Point ptRoot;
transform.TryTransform(point, out ptRoot);
Point ptClient = PointUtil.RootToClient(ptRoot, source);
Point ptScreen = PointUtil.ClientToScreen(ptClient, source);
return ptScreen;
}
#endif // PRESENTATIONFRAMEWORK
#if WINDOWS_BASE || PRESENTATION_CORE || SYSTEM_XAML
// Cache of Assembly -> AssemblyName, because calling new AssemblyName() is expensive.
// If the assembly is static, the key is the assembly; if it's dynamic, the key is a WeakRefKey
// pointing to the assembly, so we don't root collectible assemblies.
//
// This cache is bound (gated) by the number of assemblies in the appdomain.
// We use a callback on GC to purge out collected assemblies, so we don't grow indefinitely.
//
static Dictionary<object, AssemblyName> _assemblies; // get key via GetKeyForAssembly
static object syncObject = new object();
static bool _isGCCallbackPending;
// PERF: Cache delegate for CleanupCollectedAssemblies to avoid allocating it each time.
static readonly WaitCallback _cleanupCollectedAssemblies = CleanupCollectedAssemblies;
/// <summary>
/// This function iterates through the assemblies loaded in the current
/// AppDomain and finds one that has the same assembly name passed in.
/// </summary>
/// <SecurityNote>
/// The method is being kept in this class to ease auditing and
/// should have a security review if changed.
///
/// WARNING! Don't use this method for retrievals of assemblies that
/// should rely on a strong match with the given assembly name
/// since this method allows assemblies with the same short name
/// to be returned even when other name parts are different.
///
/// E.g. ----
/// matches ----, Version=2.0.0.0, Culture=en-us, PublicKeyToken=b03f5f7f11d50a3a
/// </SecurityNote>
internal static Assembly GetLoadedAssembly(AssemblyName assemblyName)
{
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
Version reqVersion = assemblyName.Version;
CultureInfo reqCulture = assemblyName.CultureInfo;
byte[] reqKeyToken = assemblyName.GetPublicKeyToken();
for (int i = assemblies.Length - 1; i >= 0; i--)
{
AssemblyName curAsmName = GetAssemblyName(assemblies[i]);
Version curVersion = curAsmName.Version;
CultureInfo curCulture = curAsmName.CultureInfo;
byte[] curKeyToken = curAsmName.GetPublicKeyToken();
if ( (String.Compare(curAsmName.Name, assemblyName.Name, true, TypeConverterHelper.InvariantEnglishUS) == 0) &&
(reqVersion == null || reqVersion.Equals(curVersion)) &&
(reqCulture == null || reqCulture.Equals(curCulture)) &&
(reqKeyToken == null || IsSameKeyToken(reqKeyToken, curKeyToken) ) )
{
return assemblies[i];
}
}
return null;
}
static AssemblyName GetAssemblyName(Assembly assembly)
{
object key = assembly.IsDynamic ? (object)new WeakRefKey(assembly) : assembly;
lock (syncObject)
{
AssemblyName result;
if (_assemblies == null)
{
_assemblies = new Dictionary<object, AssemblyName>();
}
else
{
if (_assemblies.TryGetValue(key, out result))
{
return result;
}
}
//
// We use AssemblyName ctor here because GetName demands FileIOPermission
// and does load more than just the required information.
// Essentially we use AssemblyName just to help parsing the name, version, culture
// and public key token from the assembly's name.
//
result = new AssemblyName(assembly.FullName);
_assemblies.Add(key, result);
if (assembly.IsDynamic && !_isGCCallbackPending)
{
// Make sure we clean up the cache if/when this dynamic assembly is GCed
GCNotificationToken.RegisterCallback(_cleanupCollectedAssemblies, null);
_isGCCallbackPending = true;
}
return result;
}
}
// After a GC, clean up the weakrefs to any collected dynamic assemblies
static void CleanupCollectedAssemblies(object state) // dummy parameter required by WaitCallback definition
{
bool foundLiveDynamicAssemblies = false;
List<object> keysToRemove = null;
lock (syncObject)
{
foreach (object key in _assemblies.Keys)
{
WeakReference weakRef = key as WeakReference;
if (weakRef == null)
{
continue;
}
if (weakRef.IsAlive)
{
// There is a weak ref that is still alive, register another GC callback for next time
foundLiveDynamicAssemblies = true;
}
else
{
// The target has been collected, add it to our list of keys to remove
if (keysToRemove == null)
{
keysToRemove = new List<object>();
}
keysToRemove.Add(key);
}
}
if (keysToRemove != null)
{
foreach (object key in keysToRemove)
{
_assemblies.Remove(key);
}
}
if (foundLiveDynamicAssemblies)
{
GCNotificationToken.RegisterCallback(_cleanupCollectedAssemblies, null);
}
else
{
_isGCCallbackPending = false;
}
}
}
#endif // WINDOWS_BASE || PRESENTATION_CORE || SYSTEM_XAML
//
// Determine if two Public Key Tokens are the same.
//
#if !REACHFRAMEWORK
#if PRESENTATIONFRAMEWORK || SYSTEM_XAML || PRESENTATION_CORE
internal
#else
private
#endif
static bool IsSameKeyToken(byte[] reqKeyToken, byte[] curKeyToken)
{
bool isSame = false;
if (reqKeyToken == null && curKeyToken == null)
{
// Both Key Tokens are not set, treat them as same.
isSame = true;
}
else if (reqKeyToken != null && curKeyToken != null)
{
// Both KeyTokens are set.
if (reqKeyToken.Length == curKeyToken.Length)
{
isSame = true;
for (int i = 0; i < reqKeyToken.Length; i++)
{
if (reqKeyToken[i] != curKeyToken[i])
{
isSame = false;
break;
}
}
}
}
return isSame;
}
#endif //!REACHFRAMEWORK
#if PRESENTATION_CORE || PRESENTATIONFRAMEWORK
// enum to choose between the various keys
internal enum KeyToRead
{
WebBrowserDisable = 0x01 ,
MediaAudioDisable = 0x02 ,
MediaVideoDisable = 0x04 ,
MediaImageDisable = 0x08 ,
MediaAudioOrVideoDisable = KeyToRead.MediaVideoDisable | KeyToRead.MediaAudioDisable ,
ScriptInteropDisable = 0x10 ,
}
/// <SecurityNote>
/// Critical: This code elevates to access registry
/// TreatAsSafe: The information it exposes is safe to give out and all it does is read a specific key
/// </SecurityNote>
[SecurityCritical,SecurityTreatAsSafe]
internal static bool IsFeatureDisabled(KeyToRead key)
{
string regValue = null;
bool fResult = false;
switch (key)
{
case KeyToRead.WebBrowserDisable:
regValue = RegistryKeys.value_WebBrowserDisallow;
break;
case KeyToRead.MediaAudioDisable:
regValue = RegistryKeys.value_MediaAudioDisallow;
break;
case KeyToRead.MediaVideoDisable:
regValue = RegistryKeys.value_MediaVideoDisallow;
break;
case KeyToRead.MediaImageDisable:
regValue = RegistryKeys.value_MediaImageDisallow;
break;
case KeyToRead.MediaAudioOrVideoDisable:
regValue = RegistryKeys.value_MediaAudioDisallow;
break;
case KeyToRead.ScriptInteropDisable:
regValue = RegistryKeys.value_ScriptInteropDisallow;
break;
default:// throw exception for invalid key
throw(new System.ArgumentException(key.ToString()));
}
RegistryKey featureKey;
//Assert for read access to HKLM\Software\Microsoft\Windows\Avalon
RegistryPermission regPerm = new RegistryPermission(RegistryPermissionAccess.Read,"HKEY_LOCAL_MACHINE\\"+RegistryKeys.WPF_Features);//BlessedAssert
regPerm.Assert();//BlessedAssert
try
{
object obj = null;
bool keyValue = false;
// open the key and read the value
featureKey = Registry.LocalMachine.OpenSubKey(RegistryKeys.WPF_Features);
if (featureKey != null)
{
// If key exists and value is 1 return true else false
obj = featureKey.GetValue(regValue);
keyValue = obj is int && ((int)obj == 1);
if (keyValue)
{
fResult = true;
}
// special case for audio and video since they can be orred
// this is in the condition that audio is enabled since that is
// the path that MediaAudioVideoDisable defaults to
// This is purely to optimize perf on the number of calls to assert
// in the media or audio scenario.
if ((fResult == false) && (key == KeyToRead.MediaAudioOrVideoDisable))
{
regValue = RegistryKeys.value_MediaVideoDisallow;
// If key exists and value is 1 return true else false
obj = featureKey.GetValue(regValue);
keyValue = obj is int && ((int)obj == 1);
if (keyValue)
{
fResult = true;
}
}
}
}
finally
{
RegistryPermission.RevertAssert();
}
return fResult;
}
#endif //PRESENTATIONCORE||PRESENTATIONFRAMEWORK
#if PRESENTATION_CORE
/// <summary>
/// This function is a wrapper for CultureInfo.GetCultureInfoByIetfLanguageTag().
/// The wrapper works around a bug in that routine, which causes it to throw
/// a SecurityException in Partial Trust. VSWhidbey bug #572162.
/// </summary>
/// <SecurityNote>
/// Critical: This code elevates to access registry
/// TreatAsSafe: The information it exposes is safe to give out and all it does is read a specific key
/// </SecurityNote>
[SecurityCritical,SecurityTreatAsSafe]
static internal CultureInfo GetCultureInfoByIetfLanguageTag(string languageTag)
{
CultureInfo culture = null;
RegistryPermission regPerm = new RegistryPermission(RegistryPermissionAccess.Read, RegistryKeys.HKLM_IetfLanguage);//BlessedAssert
regPerm.Assert();//BlessedAssert
try
{
culture = CultureInfo.GetCultureInfoByIetfLanguageTag(languageTag);
}
finally
{
RegistryPermission.RevertAssert();
}
return culture;
}
#endif //PRESENTATIONCORE
internal const string IMAGE = "image";
}
#if WINDOWS_BASE || PRESENTATION_CORE || SYSTEM_XAML
// for use as the key to a dictionary, when the "real" key is an object
// that we should not keep alive by a strong reference.
class WeakRefKey : WeakReference
{
public WeakRefKey(object target)
:base(target)
{
Debug.Assert(target != null);
_hashCode = target.GetHashCode();
}
public override int GetHashCode()
{
return _hashCode;
}
public override bool Equals(object o)
{
WeakRefKey weakRef = o as WeakRefKey;
if (weakRef != null)
{
object target1 = Target;
object target2 = weakRef.Target;
if (target1 != null && target2 != null)
{
return (target1 == target2);
}
}
return base.Equals(o);
}
public static bool operator ==(WeakRefKey left, WeakRefKey right)
{
if (object.ReferenceEquals(left, null))
{
return object.ReferenceEquals(right, null);
}
return left.Equals(right);
}
public static bool operator !=(WeakRefKey left, WeakRefKey right)
{
return !(left == right);
}
int _hashCode; // cache target's hashcode, lest it get GC'd out from under us
}
// This cleanup token will be immediately thrown away and as a result it will
// (a couple of GCs later) make it into the finalization queue and when finalized
// will kick off a thread-pool job that you can use to purge a weakref cache.
class GCNotificationToken
{
WaitCallback callback;
object state;
GCNotificationToken(WaitCallback callback, object state)
{
this.callback = callback;
this.state = state;
}
~GCNotificationToken()
{
// Schedule cleanup
ThreadPool.QueueUserWorkItem(callback, state);
}
internal static void RegisterCallback(WaitCallback callback, object state)
{
new GCNotificationToken(callback, state);
}
}
#endif
}
|