From 406bd216e05fb0325a92e6a5c37bb29171616684 Mon Sep 17 00:00:00 2001 From: udaken <7896698+udaken@users.noreply.github.com> Date: Thu, 30 Jun 2022 10:44:04 +0900 Subject: [PATCH] Tag type names in payloads are no longer respected by default. - `SerializerSettings.UnsafeAllowDeserializeFromTagTypeName` is added. - `SerializerSettings.EmitTags` is set to false by default. (for roundtrip) - Calling `SerializerSettings.RegisterAssembly` or `SerializerSettings.RegisterTagMapping`, sets `EmitTags` to true. --- .../Serialization/SerializationTests.cs | 38 ++++++++++++++----- .../Serialization/SerializationTests2.cs | 7 +++- src/SharpYaml.Tests/YamlNodeTest.cs | 7 ++-- .../Serialization/AssemblyRegistry.cs | 21 ++++++---- .../Serialization/SerializerSettings.cs | 19 ++++++++-- 5 files changed, 68 insertions(+), 24 deletions(-) diff --git a/src/SharpYaml.Tests/Serialization/SerializationTests.cs b/src/SharpYaml.Tests/Serialization/SerializationTests.cs index 261c3af..f4abc67 100644 --- a/src/SharpYaml.Tests/Serialization/SerializationTests.cs +++ b/src/SharpYaml.Tests/Serialization/SerializationTests.cs @@ -180,18 +180,24 @@ public void DeserializeScalar() } [Test] - public void DeserializeExplicitType() + public void DeserializeUnsafeExplicitType() { - var settings = new SerializerSettings(); - settings.RegisterAssembly(typeof(SerializationTests).Assembly); - - var serializer = new Serializer(); + var settings = new SerializerSettings(){ UnsafeAllowDeserializeFromTagTypeName = true }; + var serializer = new Serializer(settings); object result = serializer.Deserialize(YamlFile("explicitType.yaml"), typeof(object)); Assert.True(typeof(Z).IsAssignableFrom(result.GetType())); Assert.AreEqual("bbb", ((Z)result).aaa); } + [Test] + public void DeserializeUnregisterdExplicitType() + { + var serializer = new Serializer(); + + Assert.Throws(() => serializer.Deserialize(YamlFile("explicitType.yaml"), typeof(object))); + } + [Test] public void DeserializeDictionary() { @@ -206,9 +212,9 @@ public void DeserializeDictionary() } [Test] - public void DeserializeExplicitDictionary() + public void DeserializeUnsafeExplicitDictionary() { - var serializer = new Serializer(); + var serializer = new Serializer(new SerializerSettings { UnsafeAllowDeserializeFromTagTypeName = true }); object result = serializer.Deserialize(YamlFile("dictionaryExplicit.yaml")); Assert.True(typeof(IDictionary).IsAssignableFrom(result.GetType()), "The deserialized object has the wrong type."); @@ -218,6 +224,13 @@ public void DeserializeExplicitDictionary() Assert.AreEqual(2, dictionary["key2"]); } + [Test] + public void DeserializeUnregisterdExplicitDictionary() + { + var serializer = new Serializer(); + Assert.Throws(() => serializer.Deserialize(YamlFile("dictionaryExplicit.yaml"))); + } + [Test] public void DeserializeListOfDictionaries() { @@ -248,9 +261,9 @@ public void DeserializeList() } [Test] - public void DeserializeExplicitList() + public void DeserializeUnsafeExplicitList() { - var serializer = new Serializer(); + var serializer = new Serializer(new SerializerSettings { UnsafeAllowDeserializeFromTagTypeName = true }); var result = serializer.Deserialize(YamlFile("listExplicit.yaml")); Assert.True(typeof(IList).IsAssignableFrom(result.GetType())); @@ -261,6 +274,13 @@ public void DeserializeExplicitList() Assert.AreEqual(5, list[2]); } + [Test] + public void DeserializeUnregisterdExplicitList() + { + var serializer = new Serializer(); + Assert.Throws(() => serializer.Deserialize(YamlFile("listExplicit.yaml"))); + } + [Test] public void DeserializeEnumerable() { diff --git a/src/SharpYaml.Tests/Serialization/SerializationTests2.cs b/src/SharpYaml.Tests/Serialization/SerializationTests2.cs index e6bc198..5a48449 100644 --- a/src/SharpYaml.Tests/Serialization/SerializationTests2.cs +++ b/src/SharpYaml.Tests/Serialization/SerializationTests2.cs @@ -706,6 +706,8 @@ public void TestNoEmitTags() { var settings = new SerializerSettings() { EmitTags = false }; settings.RegisterTagMapping("ClassWithObjectAndScalar", typeof(ClassWithObjectAndScalar)); + Assert.True(settings.EmitTags); + settings.EmitTags = false; var serializer = new Serializer(settings); var text = serializer.Serialize(new ClassWithObjectAndScalar { Value4 = new ClassWithObjectAndScalar() }); Assert.False(text.Contains("!")); @@ -885,6 +887,7 @@ public void TestClassMemberWithInheritance() public void TestEmitShortTypeName() { var settings = new SerializerSettings() { EmitShortTypeName = true }; + settings.RegisterAssembly(typeof(ClassWithObjectAndScalar).Assembly); SerialRoundTrip(settings, new ClassWithObjectAndScalar()); } @@ -899,6 +902,7 @@ public class ClassWithChars public void TestClassWithChars() { var settings = new SerializerSettings() { EmitShortTypeName = true }; + settings.RegisterAssembly(typeof(ClassWithChars).Assembly); SerialRoundTrip(settings, new ClassWithChars() { Start = ' ', @@ -910,6 +914,7 @@ public void TestClassWithChars() public void TestClassWithSpecialChars() { var settings = new SerializerSettings() { EmitShortTypeName = true }; + settings.RegisterAssembly(typeof(ClassWithObjectAndScalar).Assembly); for (int i = 0; i < 32; i++) { SerialRoundTrip(settings, new ClassWithChars() @@ -1554,7 +1559,7 @@ public void TestIgnoreNulls() var text = serializer.Serialize(testObject); Assert.False(text.Contains("DontSerializeWhenNull: null")); - var deserialized = serializer.Deserialize(new StringReader(text)) as ClassToIgnoreNulls; + var deserialized = serializer.Deserialize(new StringReader(text)); Assert.NotNull(deserialized); Assert.AreEqual(testObject.Id, deserialized.Id); Assert.Null(deserialized.DontSerializeWhenNull); diff --git a/src/SharpYaml.Tests/YamlNodeTest.cs b/src/SharpYaml.Tests/YamlNodeTest.cs index df4050c..15b76e0 100644 --- a/src/SharpYaml.Tests/YamlNodeTest.cs +++ b/src/SharpYaml.Tests/YamlNodeTest.cs @@ -189,7 +189,7 @@ public void StyleTest() } [Test] - public void TagTest() + public void UnsafeTagTest() { var file = System.Reflection.Assembly.GetExecutingAssembly() .GetManifestResourceStream("SharpYaml.Tests.files.dictionaryExplicit.yaml"); @@ -197,14 +197,15 @@ public void TagTest() var fileStream = new StreamReader(file); var stream = YamlStream.Load(fileStream); - var dict = stream[0].Contents.ToObject(); + var settings = new SerializerSettings() { UnsafeAllowDeserializeFromTagTypeName = true }; + var dict = stream[0].Contents.ToObject(settings); Assert.AreEqual(typeof(Dictionary), dict.GetType()); Assert.AreEqual("!System.Collections.Generic.Dictionary`2[System.String,System.Int32],mscorlib", stream[0].Contents.Tag); stream[0].Contents.Tag = "!System.Collections.Generic.Dictionary`2[System.String,System.Double],mscorlib"; - var dict2 = stream[0].Contents.ToObject(); + var dict2 = stream[0].Contents.ToObject(settings); Assert.AreEqual(typeof(Dictionary), dict2.GetType()); } diff --git a/src/SharpYaml/Serialization/AssemblyRegistry.cs b/src/SharpYaml/Serialization/AssemblyRegistry.cs index 8dcddf9..a53731b 100644 --- a/src/SharpYaml/Serialization/AssemblyRegistry.cs +++ b/src/SharpYaml/Serialization/AssemblyRegistry.cs @@ -61,11 +61,6 @@ internal class AssemblyRegistry : ITagTypeRegistry private readonly List lookupAssemblies; private readonly object lockCache = new object(); - private static readonly List DefaultLookupAssemblies = new List() - { - typeof(int).GetTypeInfo().Assembly, - }; - /// /// Initializes a new instance of the class. /// @@ -92,6 +87,12 @@ public AssemblyRegistry(IYamlSchema schema) /// true if [use short type name]; otherwise, false. public bool UseShortTypeName { get; set; } + /// + /// If set to true, Deserialize using unregistered type names contained in YAML. + /// This should be set up carefully as vulnerabilities can occur. + /// + public bool UnsafeAllowDeserializeFromTagTypeName { get; set; } = false; + public void RegisterAssembly(Assembly assembly, IAttributeRegistry attributeRegistry) { if (assembly == null) @@ -100,7 +101,7 @@ public void RegisterAssembly(Assembly assembly, IAttributeRegistry attributeRegi throw new ArgumentNullException("attributeRegistry"); // Add automatically the assembly for lookup - if (!DefaultLookupAssemblies.Contains(assembly) && !lookupAssemblies.Contains(assembly)) + if (!lookupAssemblies.Contains(assembly)) { lookupAssemblies.Add(assembly); @@ -261,14 +262,18 @@ public virtual string TagFromType(Type type) public virtual Type? ResolveType(string typeName) { - var type = Type.GetType(typeName); + Type? type = null; + if (UnsafeAllowDeserializeFromTagTypeName) + { + type = Type.GetType(typeName); + } + if (type == null) { string? assemblyName = null; // Find assembly name start (skip up to one space if needed) // We ignore everything else (version, publickeytoken, etc...) - if (UseShortTypeName) { var typeNameEnd = typeName.IndexOf(','); var assemblyNameStart = typeNameEnd; diff --git a/src/SharpYaml/Serialization/SerializerSettings.cs b/src/SharpYaml/Serialization/SerializerSettings.cs index 1742422..d6b6c2c 100644 --- a/src/SharpYaml/Serialization/SerializerSettings.cs +++ b/src/SharpYaml/Serialization/SerializerSettings.cs @@ -83,7 +83,6 @@ public SerializerSettings(IYamlSchema? schema) PreferredIndent = 2; IndentLess = false; EmitAlias = true; - EmitTags = true; SortKeyForMapping = true; EmitJsonCompatible = false; EmitCapacityForList = false; @@ -131,10 +130,10 @@ public int PreferredIndent public bool EmitAlias { get; set; } /// - /// Gets or sets a value indicating whether to emit tags when serializing. Default is true. + /// Gets or sets a value indicating whether to emit tags when serializing. Default is false. /// /// true to emit tags when serializing; otherwise, false. - public bool EmitTags { get; set; } + public bool EmitTags { get; set; } = false; /// /// Gets or sets a value indicating whether the identation is trying to less @@ -230,6 +229,16 @@ public IMemberNamingConvention NamingConvention } } + /// + /// If set to true, Deserialize using unregistered type names contained in YAML. + /// This should be set up carefully as vulnerabilities can occur. + /// + public bool UnsafeAllowDeserializeFromTagTypeName + { + get => AssemblyRegistry.UnsafeAllowDeserializeFromTagTypeName; + set => AssemblyRegistry.UnsafeAllowDeserializeFromTagTypeName = value; + } + /// /// Gets or sets a value indicating whether to emit short type name (type, assembly name) or full . Default is false. /// @@ -340,15 +349,18 @@ public IObjectFactory ObjectFactory /// /// Register a mapping between a tag and a type. + /// When this method is called, EmitTags is then set to true. /// /// The assembly. public void RegisterAssembly(Assembly assembly) { AssemblyRegistry.RegisterAssembly(assembly, attributeRegistry); + EmitTags = true; } /// /// Register a mapping between a tag and a type. + /// When this method is called, EmitTags is then set to true. /// /// Name of the tag. /// Type of the tag. @@ -356,6 +368,7 @@ public void RegisterAssembly(Assembly assembly) public void RegisterTagMapping(string tagName, Type tagType, bool isAlias = false) { AssemblyRegistry.RegisterTagMapping(tagName, tagType, isAlias); + EmitTags = true; } ///