VYPR
High severityNVD Advisory· Published Dec 13, 2022· Updated Jul 22, 2025

.NET Framework Remote Code Execution Vulnerability

CVE-2022-41089

Description

.NET Framework Remote Code Execution Vulnerability

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

A remote code execution vulnerability in .NET Framework allows attackers to execute arbitrary code by tricking users into opening a maliciously crafted XPS file.

Vulnerability

Overview

The vulnerability exists in the Windows Presentation Foundation (WPF) XAML parser, specifically in the RestrictiveXamlXmlReader class. Previously, this class used a blocklist approach to filter dangerous types, but it was insufficient and allowed instantiation of arbitrary types via crafted XAML content. The fix changed the parser to use an allowlist, ensuring only safe types can be created [1][2].

Attack

Vector

An attacker can exploit this vulnerability by crafting a malicious XPS (XML Paper Specification) file containing specially designed XAML. The victim must open the malicious XPS file using a .NET WPF or WinForms application. No authentication or special network position is required; only user interaction is needed [3][4].

Impact

Successful exploitation results in remote code execution in the context of the user running the affected application. The attacker can gain full control over the user's system, including the ability to install programs, view, change, or delete data, or create new accounts [3][4].

Mitigation

Microsoft has released patches for all affected versions: .NET Core 3.1 (update to 3.1.32), .NET 6.0 (update to 6.0.12), and .NET 7.0 (update to 7.0.1). No mitigating factors or workarounds are available; users should update their .NET installations immediately [3][4].

AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
Microsoft.WindowsDesktop.App.Runtime.win-x64NuGet
>= 3.1.0, < 3.1.323.1.32
Microsoft.WindowsDesktop.App.Runtime.win-x86NuGet
>= 3.1.0, < 3.1.323.1.32
Microsoft.WindowsDesktop.App.Runtime.win-arm64NuGet
>= 6.0.0, < 6.0.126.0.12
Microsoft.WindowsDesktop.App.Runtime.win-x64NuGet
>= 6.0.0, < 6.0.126.0.12
Microsoft.WindowsDesktop.App.Runtime.win-x86NuGet
>= 6.0.0, < 6.0.126.0.12
Microsoft.WindowsDesktop.App.Runtime.win-arm64NuGet
>= 7.0.0, < 7.0.17.0.1
Microsoft.WindowsDesktop.App.Runtime.win-x86NuGet
>= 7.0.0, < 7.0.17.0.1
Microsoft.WindowsDesktop.App.Runtime.win-x64NuGet
>= 7.0.0, < 7.0.17.0.1

Affected products

23
  • ghsa-coords3 versions
    >= 6.0.0, < 6.0.12+ 2 more
    • (no CPE)range: >= 6.0.0, < 6.0.12
    • (no CPE)range: >= 3.1.0, < 3.1.32
    • (no CPE)range: >= 3.1.0, < 3.1.32
  • Microsoft/Microsoft .NET Framework 2.0 Service Pack 2v5
    Range: 2.0.0
  • Microsoft/Microsoft .NET Framework 3.0 Service Pack 2v5
    Range: 3.0.0
  • Microsoft/Microsoft .NET Framework 3.5v5
    Range: 3.5.0
  • Microsoft/Microsoft .NET Framework 3.5.1v5
    Range: 3.5.0
  • Microsoft/Microsoft .NET Framework 3.5 AND 4.6/4.6.2v5
    Range: 10.0.0
  • Microsoft/Microsoft .NET Framework 3.5 AND 4.7.2v5
    Range: 4.7.0
  • Microsoft/Microsoft .NET Framework 3.5 AND 4.8v5
    Range: 4.8.0
  • Microsoft/Microsoft .NET Framework 3.5 AND 4.8.1v5
    Range: 4.8.1
  • Microsoft/Microsoft .NET Framework 4.6.2v5
    Range: 4.7.0
  • Microsoft/Microsoft .NET Framework 4.6.2/4.7/4.7.1/4.7.2v5
    Range: 4.7.0
  • Microsoft/Microsoft .NET Framework 4.8v5
    Range: 4.8.0
  • Microsoft/Microsoft Visual Studio 2019 version 16.11 (includes 16.0 - 16.10)v5
    Range: 16.11.0
  • Microsoft/Microsoft Visual Studio 2022 version 17.0v5
    Range: 17.0.0
  • Microsoft/Microsoft Visual Studio 2022 version 17.2v5
    Range: 17.2.0
  • Microsoft/Microsoft Visual Studio 2022 version 17.4v5
    Range: 17.4.0
  • Microsoft/.NET 6.0v5
    Range: 6.0.0
  • Microsoft/.NET 7.0v5
    Range: 7.0.0
  • Microsoft/.NET Core 3.1v5
    Range: 3.1
  • Microsoft/PowerShell 7.2v5
    Range: 7.2.0
  • Microsoft/PowerShell 7.3v5
    Range: 7.3.0

Patches

3
7b0be4d41c33

Change RestrictiveXamlXmlReader to use an allow list

https://github.com/dotnet/wpfLevi BroderickNov 7, 2022via ghsa
1 file changed · +214 245
  • src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Markup/RestrictiveXamlXmlReader.cs+214 245 modified
    @@ -1,247 +1,216 @@
    -// Licensed to the .NET Foundation under one or more agreements.
    -// 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.
    -//
    -
    -using System.Collections.Concurrent;
    -using System.Collections.Generic;
    -using System.Reflection;
    -using System.Xaml;
    -using System.Xml;
    -using System.Security;
    -
    -namespace System.Windows.Markup
    -{
    -    /// <summary>
    -    /// Provides a XamlXmlReader implementation that that skips over some known dangerous types.
    -    /// </summary>
    -    internal class RestrictiveXamlXmlReader : System.Xaml.XamlXmlReader
    -    {
    -        /// <summary>
    -        /// 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.
    -        /// </summary>
    -        static RestrictiveXamlXmlReader()
    -        {
    -            _unloadedTypes = new ConcurrentDictionary<string, List<RestrictedType>>();
    -
    -            // Go through the list of restricted types and add each type under the matching assembly entry in the dictionary.
    -            foreach (RestrictedType type in _restrictedTypes)
    -            {
    -                if (!String.IsNullOrEmpty(type.AssemblyName))
    -                {
    -                    if(!_unloadedTypes.ContainsKey(type.AssemblyName))
    -                    {
    -                        _unloadedTypes[type.AssemblyName] = new List<RestrictedType>();
    -                    }
    -
    -                    _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;
    -                    }
    -                }
    -            }
    -        }
    -
    -        /// <summary>
    -        /// Builds the restricted set based on RestrictedTypes that have already been loaded.
    -        /// </summary>
    -        public RestrictiveXamlXmlReader(XmlReader xmlReader, XamlSchemaContext schemaContext, XamlXmlReaderSettings settings) : base(xmlReader, schemaContext, settings)
    -        {
    +// Licensed to the .NET Foundation under one or more agreements.
    
    +// 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 implements an allow-list of legal
    
    +// types when calling into the Read method, meant to prevent instantiation of unexpected types.
    
    +//
    
    +
    
    +using Microsoft.Win32;
    
    +using System.Collections.Generic;
    
    +using System.Xaml;
    
    +using System.Xml;
    
    +
    
    +namespace System.Windows.Markup
    
    +{
    
    +    /// <summary>
    
    +    /// Provides a XamlXmlReader implementation that that implements an allow-list of legal types.
    
    +    /// </summary>
    
    +    internal class RestrictiveXamlXmlReader : System.Xaml.XamlXmlReader
    
    +    {
    
    +        private const string AllowedTypesForRestrictiveXamlContexts = @"SOFTWARE\Microsoft\.NETFramework\Windows Presentation Foundation\XPSAllowedTypes";
    
    +        private static readonly HashSet<string> AllXamlNamespaces = new HashSet<string>(XamlLanguage.XamlNamespaces);
    
    +        private static readonly Type DependencyObjectType = typeof(System.Windows.DependencyObject);
    
    +        private static readonly HashSet<string> SafeTypesFromRegistry = ReadAllowedTypesForRestrictedXamlContexts();
    
    +
    
    +        private static HashSet<string> ReadAllowedTypesForRestrictedXamlContexts()
    
    +        {
    
    +            HashSet<string> allowedTypesFromRegistry = new HashSet<string>();
    
    +            try
    
    +            {
    
    +                // 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 (hklm != null)
    
    +                    {
    
    +                        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;
    
    +        }
    
    +
    
    +        /// <summary>
    
    +        /// Builds the restricted set based on RestrictedTypes that have already been loaded.
    
    +        /// </summary>
    
    +        public RestrictiveXamlXmlReader(XmlReader xmlReader, XamlSchemaContext schemaContext, XamlXmlReaderSettings settings) : base(xmlReader, schemaContext, settings)
    
    +        {
    
    +        }
    
    +
    
    +        /// <summary>
    
    +        /// Builds the restricted set based on RestrictedTypes that have already been loaded but adds the list of Types passed in in safeTypes to the instance of _safeTypesSet
    
    +        /// </summary>
    
    +        internal RestrictiveXamlXmlReader(XmlReader xmlReader, XamlSchemaContext schemaContext, XamlXmlReaderSettings settings, List<Type> safeTypes) : base(xmlReader, schemaContext, settings)
    
    +        {
    
    +            if (safeTypes != null)
    
    +            {
    
    +                foreach (Type safeType in safeTypes)
    
    +                {
    
    +                    _safeTypesSet.Add(safeType);
    
    +                }
    
    +            }
    
    +        }
    
    +        /// <summary>
    
    +
    
    +        /// Calls the base Read method to extract a node from the Xaml parser, if it's found to be a StartObject node for a type we want to restrict we skip that node.
    
    +        /// </summary>
    
    +        /// <returns>
    
    +        /// Returns the next available Xaml node skipping over dangerous types.
    
    +        /// </returns>
    
    +        public override bool Read()
    
    +        {
    
    +            bool result;
    
    +            int skippingDepth = 0;
    
    +
    
    +            while (result = base.Read())
    
    +            {
    
    +                if (skippingDepth <= 0)
    
    +                {
    
    +                    if ((NodeType == System.Xaml.XamlNodeType.StartObject && !IsAllowedType(Type.UnderlyingType)) ||
    
    +                        (NodeType == System.Xaml.XamlNodeType.StartMember && Member is XamlDirective directive && !IsAllowedDirective(directive)))
    
    +                    {
    
    +                        skippingDepth = 1;
    
    +                    }
    
    +                    else
    
    +                    {
    
    +                        break;
    
    +                    }
    
    +                }
    
    +                else
    
    +                {
    
    +                    switch (NodeType)
    
    +                    {
    
    +                        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;
    
    +                    }
    
    +                }
    
    +            }
    
    +
    
    +            return result;
    
    +        }
    
    +
    
    +        /// <summary>
    
    +        /// Determines whether an incoming directive is allowed.
    
    +        /// </summary>
    
    +        private bool IsAllowedDirective(XamlDirective directive)
    
    +        {
    
    +            // If the global opt-out switch is enabled, all directives are allowed.
    
    +            if (SafeTypesFromRegistry.Contains("*"))
    
    +            {
    
    +                return true;
    
    +            }
    
    +
    
    +            // 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))
    
    +                {
    
    +                    isXamlDirective = true;
    
    +                    break;
    
    +                }
    
    +            }
    
    +
    
    +            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;
    
    +        }
    
    +
    
    +        /// <summary>
    
    +        /// Determines whether an incoming type is present in the allow list.
    
    +        /// </summary>
    
    +        private bool IsAllowedType(Type type)
    
    +        {
    
    +            // 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))
    
    +            {
    
    +                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.
    
    +
    
    +            bool isValidNamespace = type.Namespace != null && type.Namespace.StartsWith("System.Windows.", StringComparison.Ordinal);
    
    +            bool isValidSubClass = type.IsSubclassOf(DependencyObjectType);
    
    +            bool isValidPrimitive = type.IsPrimitive;
    
    +
    
    +            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;
    
             }
    
     
    
    -        /// <summary>
    -        /// Builds the restricted set based on RestrictedTypes that have already been loaded but adds the list of Types passed in in safeTypes to the instance of _safeTypesSet
    -        /// </summary>
    -        internal RestrictiveXamlXmlReader(XmlReader xmlReader, XamlSchemaContext schemaContext, XamlXmlReaderSettings settings, List<Type> safeTypes) : base(xmlReader, schemaContext, settings)
    -        {
    -            if (safeTypes != null)
    -            {
    -                foreach (Type safeType in safeTypes)
    -                {
    -                    _safeTypesSet.Add(safeType);
    -                }
    -            }
    -        }
    -
    -        /// <summary>
    -        /// Calls the base Read method to extract a node from the Xaml parser, if it's found to be a StartObject node for a type we want to restrict we skip that node.
    -        /// </summary>
    -        /// <returns>
    -        /// Returns the next available Xaml node skipping over dangerous types.
    -        /// </returns>
    -        public override bool Read()
    -        {
    -            bool result = false;
    -            int skippingDepth = 0;
    -
    -            while (result = base.Read())
    -            {
    -                if (skippingDepth <= 0)
    -                {
    -                    if (NodeType == System.Xaml.XamlNodeType.StartObject &&
    -                        IsRestrictedType(Type.UnderlyingType))
    -                    {
    -                        skippingDepth = 1;
    -                    }
    -                    else
    -                    {
    -                        break;
    -                    }
    -                }
    -                else
    -                {
    -                    if (NodeType == System.Xaml.XamlNodeType.StartObject ||
    -                        NodeType == System.Xaml.XamlNodeType.GetObject)
    -                    {
    -                        skippingDepth += 1;
    -                    }
    -                    else if (NodeType == System.Xaml.XamlNodeType.EndObject)
    -                    {
    -                        skippingDepth -= 1;
    -                    }
    -                }
    -            }
    -
    -            return result;
    -        }
    -
    -        /// <summary>
    -        /// Determines whether an incoming type is either a restricted type or inheriting from one.
    -        /// </summary>
    -        private bool IsRestrictedType(Type type)
    -        {
    -            if (type != null)
    -            {
    -                // 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();
    -
    -                // 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 (restrictedType.TypeReference?.IsAssignableFrom(type) == true)
    -                    {
    -                        return true;
    -                    }
    -                }
    -
    -                // We've detected this type isn't nor inherits from a restricted type, add it to the safe types set.
    -                _safeTypesSet.Add(type);
    -            }
    -
    -            return false;
    -        }
    -
    -        /// <summary>
    -        /// 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.
    -        /// </summary>
    -        private static void EnsureLatestAssemblyLoadInformation()
    -        {
    -            Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
    -
    -            if (assemblies.Length != _loadedAssembliesCount)
    -            {
    -                foreach (Assembly assembly in assemblies)
    -                {
    -                    RegisterAssembly(assembly);
    -                }
    -
    -                _loadedAssembliesCount = assemblies.Length;
    -            }
    -        }
    -
    -
    -        /// <summary>
    -        /// Get the Types from a newly loaded assembly and assign them in the RestrictedType list.
    -        /// </summary>
    -        private static void RegisterAssembly(Assembly assembly)
    -        {
    -            if (assembly != null)
    -            {
    -                string fullName = assembly.FullName;
    -
    -                List<RestrictedType> 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);
    -                }
    -            }
    -        }
    -
    -        /// <summary>
    -        /// Known dangerous types exploitable through XAML load.
    -        /// </summary>
    -        static List<RestrictedType> _restrictedTypes = new List<RestrictedType>() {
    -                    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")
    -                };
    -        /// <summary>
    -        /// 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.
    -        /// </summary>
    -        static ConcurrentDictionary<string, List<RestrictedType>> _unloadedTypes = null;
    -        
    -        /// <summary>
    -        /// 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.
    -        /// </summary>
    -        HashSet<Type> _safeTypesSet = new HashSet<Type>();
    -
    -        /// <summary>
    -        /// 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.
    -        /// </summary>
    -        [ThreadStatic] private static int _loadedAssembliesCount;
    -
    -        /// <summary>
    -        /// Helper class to store type names.
    -        /// </summary>
    -        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; }
    -        }
    -    }
    -}
    -
    -
    +        /// <summary>
    
    +        /// Per instance set of allow-listed types, may grow at runtime to encompass implicit allow list.
    
    +        /// </summary>
    
    +        HashSet<Type> _safeTypesSet = new HashSet<Type>();
    
    +    }
    
    +}
    
    
c08825a9889e

Change RestrictiveXamlXmlReader to use an allow list

https://github.com/dotnet/wpfLevi BroderickNov 7, 2022via ghsa
1 file changed · +109 140
  • src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Markup/RestrictiveXamlXmlReader.cs+109 140 modified
    @@ -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
     {
         /// <summary>
    -    /// 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.
         /// </summary>
         internal class RestrictiveXamlXmlReader : System.Xaml.XamlXmlReader
         {
    -        /// <summary>
    -        /// 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.
    -        /// </summary>
    -        static RestrictiveXamlXmlReader()
    -        {
    -            _unloadedTypes = new ConcurrentDictionary<string, List<RestrictedType>>();
    +        private const string AllowedTypesForRestrictiveXamlContexts = @"SOFTWARE\Microsoft\.NETFramework\Windows Presentation Foundation\XPSAllowedTypes";
    +        private static readonly HashSet<string> AllXamlNamespaces = new HashSet<string>(XamlLanguage.XamlNamespaces);
    +        private static readonly Type DependencyObjectType = typeof(System.Windows.DependencyObject);
    +        private static readonly HashSet<string> 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<string> ReadAllowedTypesForRestrictedXamlContexts()
    +        {
    +            HashSet<string> allowedTypesFromRegistry = new HashSet<string>();
    +            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<RestrictedType>();
    -                    }
    -
    -                    _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;
             }
     
             /// <summary>
    @@ -85,15 +96,15 @@ internal RestrictiveXamlXmlReader(XmlReader xmlReader, XamlSchemaContext schemaC
             /// </returns>
             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()
             }
     
             /// <summary>
    -        /// Determines whether an incoming type is either a restricted type or inheriting from one.
    +        /// Determines whether an incoming directive is allowed.
             /// </summary>
    -        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;
             }
     
             /// <summary>
    -        /// 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.
             /// </summary>
    -        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.
     
    -        /// <summary>
    -        /// Get the Types from a newly loaded assembly and assign them in the RestrictedType list.
    -        /// </summary>
    -        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<RestrictedType> 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;
             }
     
             /// <summary>
    -        /// Known dangerous types exploitable through XAML load.
    -        /// </summary>
    -        static List<RestrictedType> _restrictedTypes = new List<RestrictedType>() {
    -                    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")
    -                };
    -        /// <summary>
    -        /// 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.
    -        /// </summary>
    -        static ConcurrentDictionary<string, List<RestrictedType>> _unloadedTypes = null;
    -        
    -        /// <summary>
    -        /// 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.
             /// </summary>
             HashSet<Type> _safeTypesSet = new HashSet<Type>();
    -
    -        /// <summary>
    -        /// 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.
    -        /// </summary>
    -        [ThreadStatic] private static int _loadedAssembliesCount;
    -
    -        /// <summary>
    -        /// Helper class to store type names.
    -        /// </summary>
    -        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; }
    -        }
         }
     }
    -
    -
    
5f4c0f7763a0

Change RestrictiveXamlXmlReader to use an allow list

https://github.com/dotnet/wpfLevi BroderickNov 7, 2022via ghsa
1 file changed · +108 139
  • src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Markup/RestrictiveXamlXmlReader.cs+108 139 modified
    @@ -3,57 +3,68 @@
     // 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
     {
         /// <summary>
    -    /// 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.
         /// </summary>
         internal class RestrictiveXamlXmlReader : System.Xaml.XamlXmlReader
         {
    -        /// <summary>
    -        /// 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.
    -        /// </summary>
    -        static RestrictiveXamlXmlReader()
    -        {
    -            _unloadedTypes = new ConcurrentDictionary<string, List<RestrictedType>>();
    +        private const string AllowedTypesForRestrictiveXamlContexts = @"SOFTWARE\Microsoft\.NETFramework\Windows Presentation Foundation\XPSAllowedTypes";
    +        private static readonly HashSet<string> AllXamlNamespaces = new HashSet<string>(XamlLanguage.XamlNamespaces);
    +        private static readonly Type DependencyObjectType = typeof(System.Windows.DependencyObject);
    +        private static readonly HashSet<string> 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<string> ReadAllowedTypesForRestrictedXamlContexts()
    +        {
    +            HashSet<string> allowedTypesFromRegistry = new HashSet<string>();
    +            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<RestrictedType>();
    -                    }
    -
    -                    _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;
             }
     
             /// <summary>
    @@ -85,15 +96,15 @@ internal RestrictiveXamlXmlReader(XmlReader xmlReader, XamlSchemaContext schemaC
             /// </returns>
             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()
             }
     
             /// <summary>
    -        /// Determines whether an incoming type is either a restricted type or inheriting from one.
    +        /// Determines whether an incoming directive is allowed.
             /// </summary>
    -        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;
             }
     
             /// <summary>
    -        /// 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.
             /// </summary>
    -        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.
     
    -        /// <summary>
    -        /// Get the Types from a newly loaded assembly and assign them in the RestrictedType list.
    -        /// </summary>
    -        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<RestrictedType> 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;
             }
     
             /// <summary>
    -        /// Known dangerous types exploitable through XAML load.
    -        /// </summary>
    -        static List<RestrictedType> _restrictedTypes = new List<RestrictedType>() {
    -                    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")
    -                };
    -        /// <summary>
    -        /// 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.
    -        /// </summary>
    -        static ConcurrentDictionary<string, List<RestrictedType>> _unloadedTypes = null;
    -        
    -        /// <summary>
    -        /// 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.
             /// </summary>
             HashSet<Type> _safeTypesSet = new HashSet<Type>();
    -
    -        /// <summary>
    -        /// 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.
    -        /// </summary>
    -        [ThreadStatic] private static int _loadedAssembliesCount;
    -
    -        /// <summary>
    -        /// Helper class to store type names.
    -        /// </summary>
    -        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; }
    -        }
         }
     }
    -
    -
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

7

News mentions

0

No linked articles in our index yet.