2
2
// The .NET Foundation licenses this file to you under the MIT license.
3
3
// See the LICENSE file in the project root for more information.
4
4
5
- //
6
- // Description: This class provides a XamlXmlReader implementation that skips over some known dangerous
7
- // types when calling into the Read method, this is meant to prevent WpfXamlLoader from instantiating .
5
+ //
6
+ // Description: This class provides a XamlXmlReader implementation that implements an allow-list of legal
7
+ // types when calling into the Read method, meant to prevent instantiation of unexpected types .
8
8
//
9
9
10
- using System . Collections . Concurrent ;
10
+ using Microsoft . Win32 ;
11
11
using System . Collections . Generic ;
12
- using System . Reflection ;
13
12
using System . Xaml ;
14
13
using System . Xml ;
15
- using System . Security ;
16
14
17
15
namespace System . Windows . Markup
18
16
{
19
17
/// <summary>
20
- /// Provides a XamlXmlReader implementation that that skips over some known dangerous types.
18
+ /// Provides a XamlXmlReader implementation that that implements an allow-list of legal types.
21
19
/// </summary>
22
20
internal class RestrictiveXamlXmlReader : System . Xaml . XamlXmlReader
23
21
{
24
- /// <summary>
25
- /// 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
26
- /// 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
27
- /// that are loaded.
28
- /// </summary>
29
- static RestrictiveXamlXmlReader ( )
30
- {
31
- _unloadedTypes = new ConcurrentDictionary < string , List < RestrictedType > > ( ) ;
22
+ private const string AllowedTypesForRestrictiveXamlContexts = @"SOFTWARE\Microsoft\.NETFramework\Windows Presentation Foundation\XPSAllowedTypes" ;
23
+ private static readonly HashSet < string > AllXamlNamespaces = new HashSet < string > ( XamlLanguage . XamlNamespaces ) ;
24
+ private static readonly Type DependencyObjectType = typeof ( System . Windows . DependencyObject ) ;
25
+ private static readonly HashSet < string > SafeTypesFromRegistry = ReadAllowedTypesForRestrictedXamlContexts ( ) ;
32
26
33
- // Go through the list of restricted types and add each type under the matching assembly entry in the dictionary.
34
- foreach ( RestrictedType type in _restrictedTypes )
27
+ private static HashSet < string > ReadAllowedTypesForRestrictedXamlContexts ( )
28
+ {
29
+ HashSet < string > allowedTypesFromRegistry = new HashSet < string > ( ) ;
30
+ try
35
31
{
36
- if ( ! String . IsNullOrEmpty ( type . AssemblyName ) )
32
+ // n.b. Registry64 uses the 32-bit registry in 32-bit operating systems.
33
+ // The registry key should have this format and is consistent across netfx & netcore:
34
+ //
35
+ // [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\Windows Presentation Foundation\XPSAllowedTypes]
36
+ // "SomeValue1"="Contoso.Controls.MyControl"
37
+ // "SomeValue2"="Fabrikam.Controls.MyOtherControl"
38
+ // ...
39
+ //
40
+ // The value names aren't important. The value data should match Type.FullName (including namespace but not assembly).
41
+ // If any value data is exactly "*", this serves as a global opt-out and allows everything through the system.
42
+ using ( RegistryKey hklm = RegistryKey . OpenBaseKey ( RegistryHive . LocalMachine , RegistryView . Registry64 ) )
37
43
{
38
- if ( ! _unloadedTypes . ContainsKey ( type . AssemblyName ) )
44
+ if ( hklm != null )
39
45
{
40
- _unloadedTypes [ type . AssemblyName ] = new List < RestrictedType > ( ) ;
41
- }
42
-
43
- _unloadedTypes [ type . AssemblyName ] . Add ( type ) ;
44
- }
45
- else
46
- {
47
- // If the RestrictedType entry does not provide an assembly name load it from PresentationFramework,
48
- // this is the current assembly so it's already loaded, just get the Type and assign it to the RestrictedType entry
49
- System . Type typeReference = System . Type . GetType ( type . TypeName , false ) ;
50
-
51
- if ( typeReference != null )
52
- {
53
- type . TypeReference = typeReference ;
46
+ using ( RegistryKey xpsDangerKey = hklm . OpenSubKey ( AllowedTypesForRestrictiveXamlContexts , false ) )
47
+ {
48
+ if ( xpsDangerKey != null )
49
+ {
50
+ foreach ( string typeName in xpsDangerKey . GetValueNames ( ) )
51
+ {
52
+ object value = xpsDangerKey . GetValue ( typeName ) ;
53
+ if ( value != null )
54
+ {
55
+ allowedTypesFromRegistry . Add ( value . ToString ( ) ) ;
56
+ }
57
+ }
58
+ }
59
+ }
54
60
}
55
61
}
56
62
}
63
+ catch
64
+ {
65
+ // do nothing
66
+ }
67
+ return allowedTypesFromRegistry ;
57
68
}
58
69
59
70
/// <summary>
@@ -85,15 +96,15 @@ internal RestrictiveXamlXmlReader(XmlReader xmlReader, XamlSchemaContext schemaC
85
96
/// </returns>
86
97
public override bool Read ( )
87
98
{
88
- bool result = false ;
99
+ bool result ;
89
100
int skippingDepth = 0 ;
90
101
91
102
while ( result = base . Read ( ) )
92
103
{
93
104
if ( skippingDepth <= 0 )
94
105
{
95
- if ( NodeType == System . Xaml . XamlNodeType . StartObject &&
96
- IsRestrictedType ( Type . UnderlyingType ) )
106
+ if ( ( NodeType == System . Xaml . XamlNodeType . StartObject && ! IsAllowedType ( Type . UnderlyingType ) ) ||
107
+ ( NodeType == System . Xaml . XamlNodeType . StartMember && Member is XamlDirective directive && ! IsAllowedDirective ( directive ) ) )
97
108
{
98
109
skippingDepth = 1 ;
99
110
}
@@ -104,14 +115,18 @@ public override bool Read()
104
115
}
105
116
else
106
117
{
107
- if ( NodeType == System . Xaml . XamlNodeType . StartObject ||
108
- NodeType == System . Xaml . XamlNodeType . GetObject )
109
- {
110
- skippingDepth += 1 ;
111
- }
112
- else if ( NodeType == System . Xaml . XamlNodeType . EndObject )
118
+ switch ( NodeType )
113
119
{
114
- skippingDepth -= 1 ;
120
+ case System . Xaml . XamlNodeType . StartObject :
121
+ case System . Xaml . XamlNodeType . StartMember :
122
+ case System . Xaml . XamlNodeType . GetObject :
123
+ skippingDepth += 1 ;
124
+ break ;
125
+
126
+ case System . Xaml . XamlNodeType . EndObject :
127
+ case System . Xaml . XamlNodeType . EndMember :
128
+ skippingDepth -= 1 ;
129
+ break ;
115
130
}
116
131
}
117
132
}
@@ -120,128 +135,82 @@ public override bool Read()
120
135
}
121
136
122
137
/// <summary>
123
- /// Determines whether an incoming type is either a restricted type or inheriting from one .
138
+ /// Determines whether an incoming directive is allowed .
124
139
/// </summary>
125
- private bool IsRestrictedType ( Type type )
140
+ private bool IsAllowedDirective ( XamlDirective directive )
126
141
{
127
- if ( type != null )
142
+ // If the global opt-out switch is enabled, all directives are allowed.
143
+ if ( SafeTypesFromRegistry . Contains ( "*" ) )
128
144
{
129
- // If an incoming type is already in the set we can just return false, we've verified this type is safe.
130
- if ( _safeTypesSet . Contains ( type ) )
131
- {
132
- return false ;
133
- }
134
-
135
- // Ensure that the restricted type list has the latest information for the assemblies currently loaded.
136
- EnsureLatestAssemblyLoadInformation ( ) ;
145
+ return true ;
146
+ }
137
147
138
- // Iterate through our _restrictedTypes list, if an entry has a TypeReference then the type is loaded and we can check it.
139
- foreach ( RestrictedType restrictedType in _restrictedTypes )
148
+ // If this isn't a XAML directive, allow it through.
149
+ // This allows XML directives and other non-XAML directives through.
150
+ // This largely follows the logic at XamlMember.Equals, but we trigger for *any*
151
+ // overlapping namespace rather than requiring the namespace sets to match exactly.
152
+ bool isXamlDirective = false ;
153
+ foreach ( string xmlns in directive . GetXamlNamespaces ( ) )
154
+ {
155
+ if ( AllXamlNamespaces . Contains ( xmlns ) )
140
156
{
141
- if ( restrictedType . TypeReference ? . IsAssignableFrom ( type ) == true )
142
- {
143
- return true ;
144
- }
157
+ isXamlDirective = true ;
158
+ break ;
145
159
}
160
+ }
146
161
147
- // We've detected this type isn't nor inherits from a restricted type, add it to the safe types set.
148
- _safeTypesSet . Add ( type ) ;
162
+ if ( ! isXamlDirective )
163
+ {
164
+ return true ;
165
+ }
166
+
167
+ // The following is an exhaustive list of all allowed XAML directives.
168
+ if ( directive . Name == XamlLanguage . Items . Name ||
169
+ directive . Name == XamlLanguage . Key . Name ||
170
+ directive . Name == XamlLanguage . Name . Name )
171
+ {
172
+ return true ;
149
173
}
150
174
175
+ // This is a XAML directive but isn't in the allow-list; forbid it.
151
176
return false ;
152
177
}
153
178
154
179
/// <summary>
155
- /// Iterates through the currently loaded assemblies and gets the Types for the assemblies we've marked as unloaded.
156
- /// If our thread static assembly count is still the same we can skip this, we know no new assemblies have been loaded.
180
+ /// Determines whether an incoming type is present in the allow list.
157
181
/// </summary>
158
- private static void EnsureLatestAssemblyLoadInformation ( )
182
+ private bool IsAllowedType ( Type type )
159
183
{
160
- Assembly [ ] assemblies = AppDomain . CurrentDomain . GetAssemblies ( ) ;
161
-
162
- if ( assemblies . Length != _loadedAssembliesCount )
184
+ // If the global opt-out switch is enabled, or if this type has been explicitly
185
+ // allow-listed (or is null, meaning this is a proxy which will be checked elsewhere),
186
+ // then it can come through.
187
+ if ( type is null || SafeTypesFromRegistry . Contains ( "*" ) || _safeTypesSet . Contains ( type ) || SafeTypesFromRegistry . Contains ( type . FullName ) )
163
188
{
164
- foreach ( Assembly assembly in assemblies )
165
- {
166
- RegisterAssembly ( assembly ) ;
167
- }
168
-
169
- _loadedAssembliesCount = assemblies . Length ;
189
+ return true ;
170
190
}
171
- }
172
191
192
+ // We also have an implicit allow list which consists of:
193
+ // - primitives (int, etc.); and
194
+ // - any DependencyObject-derived type which exists in the System.Windows.* namespace.
173
195
174
- /// <summary>
175
- /// Get the Types from a newly loaded assembly and assign them in the RestrictedType list.
176
- /// </summary>
177
- private static void RegisterAssembly ( Assembly assembly )
178
- {
179
- if ( assembly != null )
180
- {
181
- string fullName = assembly . FullName ;
196
+ bool isValidNamespace = type . Namespace != null && type . Namespace . StartsWith ( "System.Windows." , StringComparison . Ordinal ) ;
197
+ bool isValidSubClass = type . IsSubclassOf ( DependencyObjectType ) ;
198
+ bool isValidPrimitive = type . IsPrimitive ;
182
199
183
- List < RestrictedType > types = null ;
184
- if ( _unloadedTypes . TryGetValue ( fullName , out types ) )
185
- {
186
- if ( types != null )
187
- {
188
- foreach ( RestrictedType restrictedType in types )
189
- {
190
- Type typeInfo = assembly . GetType ( restrictedType . TypeName , false ) ;
191
- restrictedType . TypeReference = typeInfo ;
192
- }
193
- }
194
-
195
- _unloadedTypes . TryRemove ( fullName , out types ) ;
196
- }
200
+ if ( isValidPrimitive || ( isValidNamespace && isValidSubClass ) )
201
+ {
202
+ // Add it to the explicit allow list to make future lookups on this instance faster.
203
+ _safeTypesSet . Add ( type ) ;
204
+ return true ;
197
205
}
206
+
207
+ // Otherwise, it didn't exist on any of our allow lists.
208
+ return false ;
198
209
}
199
210
200
211
/// <summary>
201
- /// Known dangerous types exploitable through XAML load.
202
- /// </summary>
203
- static List < RestrictedType > _restrictedTypes = new List < RestrictedType > ( ) {
204
- new RestrictedType ( "System.Windows.Data.ObjectDataProvider" , "" ) ,
205
- new RestrictedType ( "System.Windows.ResourceDictionary" , "" ) ,
206
- new RestrictedType ( "System.Configuration.Install.AssemblyInstaller" , "System.Configuration.Install, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" ) ,
207
- new RestrictedType ( "System.Activities.Presentation.WorkflowDesigner" , "System.Activities.Presentation, Version = 4.0.0.0, Culture = neutral, PublicKeyToken = 31bf3856ad364e35" ) ,
208
- new RestrictedType ( "System.Windows.Forms.BindingSource" , "System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" )
209
- } ;
210
- /// <summary>
211
- /// Dictionary to keep track of known types that have not yet been loaded, the key is the assembly name.
212
- /// 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.
213
- /// </summary>
214
- static ConcurrentDictionary < string , List < RestrictedType > > _unloadedTypes = null ;
215
-
216
- /// <summary>
217
- /// Per instance set of found restricted types, this is initialized to the known types that have been loaded,
218
- /// if a type is found that is not already in the set and is assignable to a restricted type it is added.
212
+ /// Per instance set of allow-listed types, may grow at runtime to encompass implicit allow list.
219
213
/// </summary>
220
214
HashSet < Type > _safeTypesSet = new HashSet < Type > ( ) ;
221
-
222
- /// <summary>
223
- /// Keeps track of the assembly count, if the assembly count is the same we avoid having to go through the loaded assemblies.
224
- /// Once we get a Type in IsRestrictedType we are guaranteed to have already loaded it's assembly and base type assemblies,any other
225
- /// assembly loads happening in other threads during our processing of IsRestrictedType do not affect our ability to check the Type properly.
226
- /// </summary>
227
- [ ThreadStatic ] private static int _loadedAssembliesCount ;
228
-
229
- /// <summary>
230
- /// Helper class to store type names.
231
- /// </summary>
232
- private class RestrictedType
233
- {
234
- public RestrictedType ( string typeName , string assemblyName )
235
- {
236
- TypeName = typeName ;
237
- AssemblyName = assemblyName ;
238
- }
239
-
240
- public string TypeName { get ; set ; }
241
- public string AssemblyName { get ; set ; }
242
- public Type TypeReference { get ; set ; }
243
- }
244
215
}
245
216
}
246
-
247
-
0 commit comments