Skip to content
This repository was archived by the owner on Dec 24, 2022. It is now read-only.

Commit d099a9a

Browse files
committed
Add whitelist to ensure late-bound object types are only used to instantiate data types
1 parent 90c652a commit d099a9a

File tree

10 files changed

+327
-1
lines changed

10 files changed

+327
-1
lines changed

src/ServiceStack.Text/Common/DeserializeType.cs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ public static Type ExtractType(string strType)
9292

9393
var type = JsConfig.TypeFinder(typeName);
9494

95+
JsWriter.AssertAllowedRuntimeType(type);
96+
9597
if (type == null)
9698
{
9799
Tracer.Instance.WriteWarning("Could not find type: " + typeName);
@@ -285,11 +287,38 @@ public static TypeAccessor Create(ITypeSerializer serializer, TypeConfig typeCon
285287
return new TypeAccessor
286288
{
287289
PropertyType = propertyInfo.PropertyType,
288-
GetProperty = serializer.GetParseFn(propertyInfo.PropertyType),
290+
GetProperty = GetPropertyMethod(serializer, propertyInfo),
289291
SetProperty = GetSetPropertyMethod(typeConfig, propertyInfo),
290292
};
291293
}
292294

295+
internal static ParseStringDelegate GetPropertyMethod(ITypeSerializer serializer, PropertyInfo propertyInfo)
296+
{
297+
var getPropertyFn = serializer.GetParseFn(propertyInfo.PropertyType);
298+
if (propertyInfo.PropertyType == typeof(object) ||
299+
propertyInfo.PropertyType.HasInterface(typeof(IEnumerable<object>)))
300+
{
301+
var declaringTypeNamespace = propertyInfo.DeclaringType?.Namespace;
302+
if (declaringTypeNamespace == null || !JsConfig.AllowRuntimeTypeInTypesWithNamespaces.Contains(declaringTypeNamespace))
303+
{
304+
return value =>
305+
{
306+
var hold = JsState.IsRuntimeType;
307+
try
308+
{
309+
JsState.IsRuntimeType = true;
310+
return getPropertyFn(value);
311+
}
312+
finally
313+
{
314+
JsState.IsRuntimeType = hold;
315+
}
316+
};
317+
}
318+
}
319+
return getPropertyFn;
320+
}
321+
293322
private static SetPropertyDelegate GetSetPropertyMethod(TypeConfig typeConfig, PropertyInfo propertyInfo)
294323
{
295324
if (typeConfig.Type != propertyInfo.DeclaringType)

src/ServiceStack.Text/Common/DeserializeTypeRefJson.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ internal static object StringToType(
101101
}
102102
else
103103
{
104+
JsWriter.AssertAllowedRuntimeType(explicitType);
104105
instance = explicitType.CreateInstance();
105106
}
106107

@@ -179,6 +180,7 @@ internal static object StringToType(
179180
propertyValue = typeConfig.OnDeserializing(instance, propertyName, propertyValue);
180181
typeAccessor.SetProperty(instance, propertyValue);
181182
}
183+
catch (NotSupportedException) { throw; }
182184
catch (Exception e)
183185
{
184186
if (JsConfig.OnDeserializationError != null) JsConfig.OnDeserializationError(instance, propType ?? typeAccessor.PropertyType, propertyName, propertyValueStr, e);

src/ServiceStack.Text/Common/DeserializeTypeRefJsv.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ internal static object StringToType(
5959
}
6060
else
6161
{
62+
JsWriter.AssertAllowedRuntimeType(explicitType);
6263
instance = explicitType.CreateInstance();
6364
}
6465

@@ -130,6 +131,7 @@ internal static object StringToType(
130131
propertyValue = typeConfig.OnDeserializing(instance, propertyName, propertyValue);
131132
typeAccessor.SetProperty(instance, propertyValue);
132133
}
134+
catch (NotSupportedException) { throw; }
133135
catch (Exception e)
134136
{
135137
if (JsConfig.OnDeserializationError != null) JsConfig.OnDeserializationError(instance, propType ?? typeAccessor.PropertyType, propertyName, propertyValueStr, e);

src/ServiceStack.Text/Common/JsState.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ internal static class JsState
1515
[ThreadStatic]
1616
internal static bool IsWritingDynamic = false;
1717

18+
[ThreadStatic]
19+
internal static bool IsRuntimeType = false;
20+
1821
[ThreadStatic]
1922
internal static bool QueryStringMode = false;
2023

src/ServiceStack.Text/Common/JsWriter.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,41 @@ public static void WriteEnumFlags(TextWriter writer, object enumFlagValue)
152152
break;
153153
}
154154
}
155+
156+
public static void AssertAllowedRuntimeType(Type type)
157+
{
158+
if (!JsState.IsRuntimeType)
159+
return;
160+
161+
if (JsConfig.AllowRuntimeType?.Invoke(type) == true)
162+
return;
163+
164+
var allowAttributesNamed = JsConfig.AllowRuntimeTypeWithAttributesNamed;
165+
if (allowAttributesNamed?.Count > 0)
166+
{
167+
var OAttrs = type.AllAttributes();
168+
foreach (var oAttr in OAttrs)
169+
{
170+
var attr = oAttr as Attribute;
171+
if (attr == null) continue;
172+
if (allowAttributesNamed.Contains(attr.GetType().Name))
173+
return;
174+
}
175+
}
176+
177+
var allowInterfacesNamed = JsConfig.AllowRuntimeTypeWithInterfacesNamed;
178+
if (allowInterfacesNamed?.Count > 0)
179+
{
180+
var interfaces = type.GetTypeInterfaces();
181+
foreach (var interfaceType in interfaces)
182+
{
183+
if (allowInterfacesNamed.Contains(interfaceType.Name))
184+
return;
185+
}
186+
}
187+
188+
throw new NotSupportedException($"{type.Name} is not an allowed Runtime Type. Whitelist Type with [RuntimeSerializable] or IRuntimeSerializable.");
189+
}
155190
}
156191

157192
public class JsWriter<TSerializer>

src/ServiceStack.Text/JsConfig.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using System.IO;
44
using System.Runtime.CompilerServices;
5+
using System.Runtime.Serialization;
56
using System.Threading;
67
using ServiceStack.Text.Common;
78
using ServiceStack.Text.Json;
@@ -916,6 +917,14 @@ public static string[] IgnoreAttributesNamed
916917
get { return ReflectionExtensions.IgnoreAttributesNamed; }
917918
}
918919

920+
public static HashSet<string> AllowRuntimeTypeWithAttributesNamed { get; set; }
921+
922+
public static HashSet<string> AllowRuntimeTypeWithInterfacesNamed { get; set; }
923+
924+
public static HashSet<string> AllowRuntimeTypeInTypesWithNamespaces { get; set; }
925+
926+
public static Func<Type, bool> AllowRuntimeType { get; set; }
927+
919928
public static void Reset()
920929
{
921930
foreach (var rawSerializeType in HasSerializeFn.ToArray())
@@ -970,6 +979,26 @@ public static void Reset()
970979
sMaxDepth = 50;
971980
sParsePrimitiveIntegerTypes = null;
972981
sParsePrimitiveFloatingPointTypes = null;
982+
AllowRuntimeType = null;
983+
AllowRuntimeTypeWithAttributesNamed = new HashSet<string>
984+
{
985+
nameof(DataContractAttribute),
986+
nameof(RuntimeSerializableAttribute),
987+
"SerializableAttribute",
988+
};
989+
AllowRuntimeTypeWithInterfacesNamed = new HashSet<string>
990+
{
991+
"IConvertible",
992+
"ISerializable",
993+
"IRuntimeSerializable",
994+
"IMeta",
995+
"IReturn`1",
996+
"IReturnVoid",
997+
};
998+
AllowRuntimeTypeInTypesWithNamespaces = new HashSet<string>
999+
{
1000+
"ServiceStack.Messaging",
1001+
};
9731002
PlatformExtensions.ClearRuntimeAttributes();
9741003
ReflectionExtensions.Reset();
9751004
JsState.Reset();
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using System;
2+
3+
namespace ServiceStack.Text
4+
{
5+
/// <summary>
6+
/// Allow Type to be deserialized into late-bould object Types using __type info
7+
/// </summary>
8+
[AttributeUsage(AttributeTargets.Class)]
9+
public class RuntimeSerializableAttribute : Attribute {}
10+
11+
/// <summary>
12+
/// Allow Type to be deserialized into late-bould object Types using __type info
13+
/// </summary>
14+
public interface IRuntimeSerializable { }
15+
}

tests/ServiceStack.Text.Tests/DynamicModels/DataModel/CustomCollectionDto.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace ServiceStack.Text.Tests.DynamicModels.DataModel
66
{
7+
[RuntimeSerializable]
78
public class CustomCollectionDto
89
{
910
public Exception Exception { get; set; }

tests/ServiceStack.Text.Tests/DynamicModels/DynamicMessageTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ public class StrictMessage : IMessageHeaders
4141
public MessageBody Body { get; set; }
4242
}
4343

44+
[RuntimeSerializable]
4445
public class MessageBody
4546
{
4647
public MessageBody()

0 commit comments

Comments
 (0)