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; }
- }
}
}
-
-