diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Markup/RestrictiveXamlXmlReader.cs b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Markup/RestrictiveXamlXmlReader.cs index 81cd76fa282..7668a891566 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Markup/RestrictiveXamlXmlReader.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Markup/RestrictiveXamlXmlReader.cs @@ -2,58 +2,69 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -// -// Description: This class provides a XamlXmlReader implementation that skips over some known dangerous -// types when calling into the Read method, this is meant to prevent WpfXamlLoader from instantiating. +// +// Description: This class provides a XamlXmlReader implementation that implements an allow-list of legal +// types when calling into the Read method, meant to prevent instantiation of unexpected types. // -using System.Collections.Concurrent; +using Microsoft.Win32; using System.Collections.Generic; -using System.Reflection; using System.Xaml; using System.Xml; -using System.Security; namespace System.Windows.Markup { /// - /// Provides a XamlXmlReader implementation that that skips over some known dangerous types. + /// Provides a XamlXmlReader implementation that that implements an allow-list of legal types. /// internal class RestrictiveXamlXmlReader : System.Xaml.XamlXmlReader { - /// - /// The RestrictedTypes in the _restrictedTypes list do not initially contain a Type reference, we use a Type reference to determine whether an incoming Type can be assigned to that Type - /// in order to restrict it or allow it. We cannot get a Type reference without loading the assembly, so we need to first go through the loaded assemblies, and assign the Types - /// that are loaded. - /// - static RestrictiveXamlXmlReader() - { - _unloadedTypes = new ConcurrentDictionary>(); + private const string AllowedTypesForRestrictiveXamlContexts = @"SOFTWARE\Microsoft\.NETFramework\Windows Presentation Foundation\XPSAllowedTypes"; + private static readonly HashSet AllXamlNamespaces = new HashSet(XamlLanguage.XamlNamespaces); + private static readonly Type DependencyObjectType = typeof(System.Windows.DependencyObject); + private static readonly HashSet SafeTypesFromRegistry = ReadAllowedTypesForRestrictedXamlContexts(); - // Go through the list of restricted types and add each type under the matching assembly entry in the dictionary. - foreach (RestrictedType type in _restrictedTypes) + private static HashSet ReadAllowedTypesForRestrictedXamlContexts() + { + HashSet allowedTypesFromRegistry = new HashSet(); + try { - if (!String.IsNullOrEmpty(type.AssemblyName)) + // n.b. Registry64 uses the 32-bit registry in 32-bit operating systems. + // The registry key should have this format and is consistent across netfx & netcore: + // + // [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\Windows Presentation Foundation\XPSAllowedTypes] + // "SomeValue1"="Contoso.Controls.MyControl" + // "SomeValue2"="Fabrikam.Controls.MyOtherControl" + // ... + // + // The value names aren't important. The value data should match Type.FullName (including namespace but not assembly). + // If any value data is exactly "*", this serves as a global opt-out and allows everything through the system. + using (RegistryKey hklm = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64)) { - if(!_unloadedTypes.ContainsKey(type.AssemblyName)) + if (hklm != null) { - _unloadedTypes[type.AssemblyName] = new List(); - } - - _unloadedTypes[type.AssemblyName].Add(type); - } - else - { - // If the RestrictedType entry does not provide an assembly name load it from PresentationFramework, - // this is the current assembly so it's already loaded, just get the Type and assign it to the RestrictedType entry - System.Type typeReference = System.Type.GetType(type.TypeName, false); - - if (typeReference != null) - { - type.TypeReference = typeReference; + using (RegistryKey xpsDangerKey = hklm.OpenSubKey(AllowedTypesForRestrictiveXamlContexts, false)) + { + if (xpsDangerKey != null) + { + foreach (string typeName in xpsDangerKey.GetValueNames()) + { + object value = xpsDangerKey.GetValue(typeName); + if (value != null) + { + allowedTypesFromRegistry.Add(value.ToString()); + } + } + } + } } } } + catch + { + // do nothing + } + return allowedTypesFromRegistry; } /// @@ -85,15 +96,15 @@ internal RestrictiveXamlXmlReader(XmlReader xmlReader, XamlSchemaContext schemaC /// public override bool Read() { - bool result = false; + bool result; int skippingDepth = 0; while (result = base.Read()) { if (skippingDepth <= 0) { - if (NodeType == System.Xaml.XamlNodeType.StartObject && - IsRestrictedType(Type.UnderlyingType)) + if ((NodeType == System.Xaml.XamlNodeType.StartObject && !IsAllowedType(Type.UnderlyingType)) || + (NodeType == System.Xaml.XamlNodeType.StartMember && Member is XamlDirective directive && !IsAllowedDirective(directive))) { skippingDepth = 1; } @@ -104,14 +115,18 @@ public override bool Read() } else { - if (NodeType == System.Xaml.XamlNodeType.StartObject || - NodeType == System.Xaml.XamlNodeType.GetObject) - { - skippingDepth += 1; - } - else if (NodeType == System.Xaml.XamlNodeType.EndObject) + switch (NodeType) { - skippingDepth -= 1; + case System.Xaml.XamlNodeType.StartObject: + case System.Xaml.XamlNodeType.StartMember: + case System.Xaml.XamlNodeType.GetObject: + skippingDepth += 1; + break; + + case System.Xaml.XamlNodeType.EndObject: + case System.Xaml.XamlNodeType.EndMember: + skippingDepth -= 1; + break; } } } @@ -120,128 +135,82 @@ public override bool Read() } /// - /// Determines whether an incoming type is either a restricted type or inheriting from one. + /// Determines whether an incoming directive is allowed. /// - private bool IsRestrictedType(Type type) + private bool IsAllowedDirective(XamlDirective directive) { - if (type != null) + // If the global opt-out switch is enabled, all directives are allowed. + if (SafeTypesFromRegistry.Contains("*")) { - // If an incoming type is already in the set we can just return false, we've verified this type is safe. - if (_safeTypesSet.Contains(type)) - { - return false; - } - - // Ensure that the restricted type list has the latest information for the assemblies currently loaded. - EnsureLatestAssemblyLoadInformation(); + return true; + } - // Iterate through our _restrictedTypes list, if an entry has a TypeReference then the type is loaded and we can check it. - foreach (RestrictedType restrictedType in _restrictedTypes) + // If this isn't a XAML directive, allow it through. + // This allows XML directives and other non-XAML directives through. + // This largely follows the logic at XamlMember.Equals, but we trigger for *any* + // overlapping namespace rather than requiring the namespace sets to match exactly. + bool isXamlDirective = false; + foreach (string xmlns in directive.GetXamlNamespaces()) + { + if (AllXamlNamespaces.Contains(xmlns)) { - if (restrictedType.TypeReference?.IsAssignableFrom(type) == true) - { - return true; - } + isXamlDirective = true; + break; } + } - // We've detected this type isn't nor inherits from a restricted type, add it to the safe types set. - _safeTypesSet.Add(type); + if (!isXamlDirective) + { + return true; + } + + // The following is an exhaustive list of all allowed XAML directives. + if (directive.Name == XamlLanguage.Items.Name || + directive.Name == XamlLanguage.Key.Name || + directive.Name == XamlLanguage.Name.Name) + { + return true; } + // This is a XAML directive but isn't in the allow-list; forbid it. return false; } /// - /// Iterates through the currently loaded assemblies and gets the Types for the assemblies we've marked as unloaded. - /// If our thread static assembly count is still the same we can skip this, we know no new assemblies have been loaded. + /// Determines whether an incoming type is present in the allow list. /// - private static void EnsureLatestAssemblyLoadInformation() + private bool IsAllowedType(Type type) { - Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); - - if (assemblies.Length != _loadedAssembliesCount) + // If the global opt-out switch is enabled, or if this type has been explicitly + // allow-listed (or is null, meaning this is a proxy which will be checked elsewhere), + // then it can come through. + if (type is null || SafeTypesFromRegistry.Contains("*") || _safeTypesSet.Contains(type) || SafeTypesFromRegistry.Contains(type.FullName)) { - foreach (Assembly assembly in assemblies) - { - RegisterAssembly(assembly); - } - - _loadedAssembliesCount = assemblies.Length; + return true; } - } + // We also have an implicit allow list which consists of: + // - primitives (int, etc.); and + // - any DependencyObject-derived type which exists in the System.Windows.* namespace. - /// - /// Get the Types from a newly loaded assembly and assign them in the RestrictedType list. - /// - private static void RegisterAssembly(Assembly assembly) - { - if (assembly != null) - { - string fullName = assembly.FullName; + bool isValidNamespace = type.Namespace != null && type.Namespace.StartsWith("System.Windows.", StringComparison.Ordinal); + bool isValidSubClass = type.IsSubclassOf(DependencyObjectType); + bool isValidPrimitive = type.IsPrimitive; - List types = null; - if (_unloadedTypes.TryGetValue(fullName, out types)) - { - if (types != null) - { - foreach (RestrictedType restrictedType in types) - { - Type typeInfo = assembly.GetType(restrictedType.TypeName, false); - restrictedType.TypeReference = typeInfo; - } - } - - _unloadedTypes.TryRemove(fullName, out types); - } + if (isValidPrimitive || (isValidNamespace && isValidSubClass)) + { + // Add it to the explicit allow list to make future lookups on this instance faster. + _safeTypesSet.Add(type); + return true; } + + // Otherwise, it didn't exist on any of our allow lists. + return false; } /// - /// Known dangerous types exploitable through XAML load. - /// - static List _restrictedTypes = new List() { - new RestrictedType("System.Windows.Data.ObjectDataProvider",""), - new RestrictedType("System.Windows.ResourceDictionary",""), - new RestrictedType("System.Configuration.Install.AssemblyInstaller","System.Configuration.Install, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"), - new RestrictedType("System.Activities.Presentation.WorkflowDesigner","System.Activities.Presentation, Version = 4.0.0.0, Culture = neutral, PublicKeyToken = 31bf3856ad364e35"), - new RestrictedType("System.Windows.Forms.BindingSource","System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089") - }; - /// - /// Dictionary to keep track of known types that have not yet been loaded, the key is the assembly name. - /// Once an assembly is loaded we take the known types in that assembly, set the Type property in the RestrictedType entry, then remove the assembly entry. - /// - static ConcurrentDictionary> _unloadedTypes = null; - - /// - /// Per instance set of found restricted types, this is initialized to the known types that have been loaded, - /// if a type is found that is not already in the set and is assignable to a restricted type it is added. + /// Per instance set of allow-listed types, may grow at runtime to encompass implicit allow list. /// HashSet _safeTypesSet = new HashSet(); - - /// - /// Keeps track of the assembly count, if the assembly count is the same we avoid having to go through the loaded assemblies. - /// Once we get a Type in IsRestrictedType we are guaranteed to have already loaded it's assembly and base type assemblies,any other - /// assembly loads happening in other threads during our processing of IsRestrictedType do not affect our ability to check the Type properly. - /// - [ThreadStatic] private static int _loadedAssembliesCount; - - /// - /// Helper class to store type names. - /// - private class RestrictedType - { - public RestrictedType(string typeName, string assemblyName) - { - TypeName = typeName; - AssemblyName = assemblyName; - } - - public string TypeName { get; set; } - public string AssemblyName { get; set; } - public Type TypeReference { get; set; } - } } } - -