From 930293f673802fc7736abf40b504f1d3a5e1704c Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Tue, 1 Oct 2024 20:01:37 -0600 Subject: [PATCH 01/32] Merge pull request #1978 from BertanAygun/bertan/issue1952 Update DynamicAssembly usage to honor different AssemblyLoadContext's --- .../DynamicCodeDumper.csproj | 3 + .../MessagePack/Internal/DynamicAssembly.cs | 5 + .../Internal/DynamicAssemblyFactory.cs | 63 +++++++ .../Resolvers/DynamicEnumResolver.cs | 8 +- .../Resolvers/DynamicObjectResolver.cs | 16 +- .../Resolvers/DynamicUnionResolver.cs | 9 +- .../AssemblyLoadContextTests.cs | 174 ++++++++++++++++++ 7 files changed, 262 insertions(+), 16 deletions(-) create mode 100644 src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Internal/DynamicAssemblyFactory.cs create mode 100644 tests/MessagePack.Tests/AssemblyLoadContextTests.cs diff --git a/sandbox/DynamicCodeDumper/DynamicCodeDumper.csproj b/sandbox/DynamicCodeDumper/DynamicCodeDumper.csproj index 8d1a7c7e2..1718c5f3c 100644 --- a/sandbox/DynamicCodeDumper/DynamicCodeDumper.csproj +++ b/sandbox/DynamicCodeDumper/DynamicCodeDumper.csproj @@ -46,6 +46,9 @@ Code\DynamicAssembly.cs + + Code\DynamicAssemblyFactory.cs + Code\ExpressionUtility.cs diff --git a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Internal/DynamicAssembly.cs b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Internal/DynamicAssembly.cs index 7e410e446..90ee1698f 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Internal/DynamicAssembly.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Internal/DynamicAssembly.cs @@ -20,6 +20,11 @@ internal class DynamicAssembly // don't expose ModuleBuilder //// public ModuleBuilder ModuleBuilder { get { return moduleBuilder; } } + /// + /// Initializes a new instance of the class. + /// Please use instead in order to work across different AssemblyLoadContext that may have duplicate modules. + /// + /// Name of the module to be generated. public DynamicAssembly(string moduleName) { #if NETFRAMEWORK // We don't ship a net472 target, but we might add one for debugging purposes diff --git a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Internal/DynamicAssemblyFactory.cs b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Internal/DynamicAssemblyFactory.cs new file mode 100644 index 000000000..1529e07bf --- /dev/null +++ b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Internal/DynamicAssemblyFactory.cs @@ -0,0 +1,63 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Reflection; + +#if NET +using System.Runtime.Loader; +#endif + +namespace MessagePack.Internal +{ + /// + /// This class is responsible for managing DynamicAssembly instance creation taking into account + /// AssemblyLoadContext when running under .NET. + /// + internal class DynamicAssemblyFactory + { + private readonly string moduleName; + + private readonly Lazy singletonAssembly; + +#if NET + private readonly Dictionary alcCache = new(); +#endif + + public DynamicAssemblyFactory(string moduleName) + { + this.moduleName = moduleName; + this.singletonAssembly = new Lazy(() => new DynamicAssembly(this.moduleName)); + } + +#if NET + public DynamicAssembly GetDynamicAssembly(Type? type) + { + if (type is null || AssemblyLoadContext.GetLoadContext(type.Assembly) is not AssemblyLoadContext loadContext) + { + return this.singletonAssembly.Value; + } + else + { + DynamicAssembly? assembly = null; + lock (this.alcCache) + { + if (!this.alcCache.TryGetValue(loadContext, out assembly)) + { + assembly = new DynamicAssembly(this.moduleName); + this.alcCache[loadContext] = assembly; + } + + return assembly; + } + } + } +#else + public DynamicAssembly GetDynamicAssembly(Type? type) + { + return this.singletonAssembly.Value; + } +#endif + } +} diff --git a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Resolvers/DynamicEnumResolver.cs b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Resolvers/DynamicEnumResolver.cs index f11be19e2..c9ce48e0c 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Resolvers/DynamicEnumResolver.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Resolvers/DynamicEnumResolver.cs @@ -25,7 +25,7 @@ public sealed class DynamicEnumResolver : IFormatterResolver private const string ModuleName = "MessagePack.Resolvers.DynamicEnumResolver"; - private static readonly Lazy DynamicAssembly; + private static readonly DynamicAssemblyFactory DynamicAssemblyFactory; private static int nameSequence = 0; @@ -35,13 +35,13 @@ private DynamicEnumResolver() static DynamicEnumResolver() { - DynamicAssembly = new Lazy(() => new DynamicAssembly(ModuleName)); + DynamicAssemblyFactory = new DynamicAssemblyFactory(ModuleName); } #if NETFRAMEWORK internal AssemblyBuilder Save() { - return DynamicAssembly.Value.Save(); + return DynamicAssemblyFactory.GetDynamicAssembly(type: null).Save(); } #endif @@ -95,7 +95,7 @@ private static TypeInfo BuildType(Type enumType) { using (MonoProtection.EnterRefEmitLock()) { - TypeBuilder typeBuilder = DynamicAssembly.Value.DefineType("MessagePack.Formatters." + enumType.FullName!.Replace(".", "_") + "Formatter" + Interlocked.Increment(ref nameSequence), TypeAttributes.Public | TypeAttributes.Sealed, null, new[] { formatterType }); + TypeBuilder typeBuilder = DynamicAssemblyFactory.GetDynamicAssembly(enumType).DefineType("MessagePack.Formatters." + enumType.FullName!.Replace(".", "_") + "Formatter" + Interlocked.Increment(ref nameSequence), TypeAttributes.Public | TypeAttributes.Sealed, null, new[] { formatterType }); // void Serialize(ref MessagePackWriter writer, T value, MessagePackSerializerOptions options); { diff --git a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Resolvers/DynamicObjectResolver.cs b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Resolvers/DynamicObjectResolver.cs index 6769e0ac6..e05283e88 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Resolvers/DynamicObjectResolver.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Resolvers/DynamicObjectResolver.cs @@ -38,13 +38,13 @@ public sealed class DynamicObjectResolver : IFormatterResolver /// public static readonly MessagePackSerializerOptions Options; - internal static readonly Lazy DynamicAssembly; + internal static readonly DynamicAssemblyFactory DynamicAssemblyFactory; static DynamicObjectResolver() { Instance = new DynamicObjectResolver(); Options = new MessagePackSerializerOptions(Instance); - DynamicAssembly = new Lazy(() => new DynamicAssembly(ModuleName)); + DynamicAssemblyFactory = new DynamicAssemblyFactory(ModuleName); } private DynamicObjectResolver() @@ -54,7 +54,7 @@ private DynamicObjectResolver() #if NETFRAMEWORK internal AssemblyBuilder Save() { - return DynamicAssembly.Value.Save(); + return DynamicAssemblyFactory.GetDynamicAssembly(type: null).Save(); } #endif @@ -99,7 +99,7 @@ static FormatterCache() TypeInfo? formatterTypeInfo; try { - formatterTypeInfo = DynamicObjectTypeBuilder.BuildType(DynamicAssembly.Value, typeof(T), false, false); + formatterTypeInfo = DynamicObjectTypeBuilder.BuildType(DynamicAssemblyFactory.GetDynamicAssembly(typeof(T)), typeof(T), false, false); } catch (InitAccessorInGenericClassNotSupportedException) { @@ -181,7 +181,7 @@ public sealed class DynamicContractlessObjectResolver : IFormatterResolver private const string ModuleName = "MessagePack.Resolvers.DynamicContractlessObjectResolver"; - private static readonly Lazy DynamicAssembly; + private static readonly DynamicAssemblyFactory DynamicAssemblyFactory; private DynamicContractlessObjectResolver() { @@ -189,13 +189,13 @@ private DynamicContractlessObjectResolver() static DynamicContractlessObjectResolver() { - DynamicAssembly = new Lazy(() => new DynamicAssembly(ModuleName)); + DynamicAssemblyFactory = new DynamicAssemblyFactory(ModuleName); } #if NETFRAMEWORK internal AssemblyBuilder Save() { - return DynamicAssembly.Value.Save(); + return DynamicAssemblyFactory.GetDynamicAssembly(type: null).Save(); } #endif @@ -242,7 +242,7 @@ static FormatterCache() return; } - TypeInfo? formatterTypeInfo = DynamicObjectTypeBuilder.BuildType(DynamicAssembly.Value, typeof(T), true, true); + TypeInfo? formatterTypeInfo = DynamicObjectTypeBuilder.BuildType(DynamicAssemblyFactory.GetDynamicAssembly(typeof(T)), typeof(T), true, true); if (formatterTypeInfo == null) { return; diff --git a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Resolvers/DynamicUnionResolver.cs b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Resolvers/DynamicUnionResolver.cs index 90e26a755..7f1c436b3 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Resolvers/DynamicUnionResolver.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Resolvers/DynamicUnionResolver.cs @@ -37,7 +37,8 @@ public sealed class DynamicUnionResolver : IFormatterResolver /// public static readonly MessagePackSerializerOptions Options; - private static readonly Lazy DynamicAssembly; + private static readonly DynamicAssemblyFactory DynamicAssemblyFactory; + #if !UNITY_2018_3_OR_NEWER private static readonly Regex SubtractFullNameRegex = new Regex(@", Version=\d+.\d+.\d+.\d+, Culture=\w+, PublicKeyToken=\w+", RegexOptions.Compiled); #else @@ -50,7 +51,7 @@ static DynamicUnionResolver() { Instance = new DynamicUnionResolver(); Options = new MessagePackSerializerOptions(Instance); - DynamicAssembly = new Lazy(() => new DynamicAssembly(ModuleName)); + DynamicAssemblyFactory = new DynamicAssemblyFactory(ModuleName); } private DynamicUnionResolver() @@ -60,7 +61,7 @@ private DynamicUnionResolver() #if NETFRAMEWORK internal AssemblyBuilder Save() { - return DynamicAssembly.Value.Save(); + return DynamicAssemblyFactory.GetDynamicAssembly(type: null).Save(); } #endif @@ -138,7 +139,7 @@ static FormatterCache() Type formatterType = typeof(IMessagePackFormatter<>).MakeGenericType(type); using (MonoProtection.EnterRefEmitLock()) { - TypeBuilder typeBuilder = DynamicAssembly.Value.DefineType("MessagePack.Formatters." + SubtractFullNameRegex.Replace(type.FullName!, string.Empty).Replace(".", "_") + "Formatter" + +Interlocked.Increment(ref nameSequence), TypeAttributes.Public | TypeAttributes.Sealed, null, new[] { formatterType }); + TypeBuilder typeBuilder = DynamicAssemblyFactory.GetDynamicAssembly(type).DefineType("MessagePack.Formatters." + SubtractFullNameRegex.Replace(type.FullName!, string.Empty).Replace(".", "_") + "Formatter" + +Interlocked.Increment(ref nameSequence), TypeAttributes.Public | TypeAttributes.Sealed, null, new[] { formatterType }); FieldBuilder? typeToKeyAndJumpMap = null; // Dictionary> FieldBuilder? keyToJumpMap = null; // Dictionary diff --git a/tests/MessagePack.Tests/AssemblyLoadContextTests.cs b/tests/MessagePack.Tests/AssemblyLoadContextTests.cs new file mode 100644 index 000000000..5a9d30d6b --- /dev/null +++ b/tests/MessagePack.Tests/AssemblyLoadContextTests.cs @@ -0,0 +1,174 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#if NET + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.Loader; +using System.Text; +using System.Threading.Tasks; +using ComplexdUnion; +using MessagePack; +using MessagePack.Formatters; +using MessagePack.Resolvers; +using SharedData; +using Xunit; + +#pragma warning disable SA1302 // Interface names should begin with I +#pragma warning disable SA1403 // File may only contain a single namespace + +public class AssemblyLoadContextTests : IDisposable +{ + private static readonly string SharedDataAssemblyName = typeof(RootUnionType).Assembly.Location; + private readonly AssemblyLoadContext loadContext = new AssemblyLoadContext("TestContext", isCollectible: true); + + public void Dispose() + { + this.loadContext.Unload(); + } + + [Fact] + public void DynamicUnionResolverWorksAcrossAssemblyLoadContexts() + { + RootUnionType unionTypeInMainLoadContext = new SubUnionType1(); + var options = this.CreateSerializerOptions(); + + var buffer1 = MessagePackSerializer.Serialize(unionTypeInMainLoadContext, options: options); + var o1 = MessagePackSerializer.Deserialize(buffer1, options: options); + + Assert.True(o1 is SubUnionType1); + + var assembly = this.loadContext.LoadFromAssemblyPath(SharedDataAssemblyName); + object unionTypeInOtherContext = assembly.CreateInstance(typeof(SubUnionType1).FullName); + Type rootUnionType = assembly.GetType(typeof(RootUnionType).FullName); + + var buffer2 = MessagePackSerializer.Serialize(rootUnionType, unionTypeInOtherContext, options: options); + var o2 = MessagePackSerializer.Deserialize(rootUnionType, buffer2, options: options); + + Assert.True(o2.GetType().IsAssignableTo(rootUnionType)); + } + + [Fact] + public void DynamicEnumResolverWorksAcrossAssemblyLoadContexts() + { + ByteEnum e1 = ByteEnum.A; + var options = this.CreateSerializerOptions(); + + var b1 = MessagePackSerializer.Serialize(e1, options: options); + var o1 = MessagePackSerializer.Deserialize(b1, options: options); + + Assert.Equal(typeof(ByteEnum), o1.GetType()); + + var assembly = this.loadContext.LoadFromAssemblyPath(SharedDataAssemblyName); + Type enumType = assembly.GetType(typeof(ByteEnum).FullName); + object e2 = Enum.GetValues(enumType).GetValue(1); + + var b2 = MessagePackSerializer.Serialize(enumType, e2, options: options); + var o2 = MessagePackSerializer.Deserialize(enumType, b2, options: options); + + Assert.Equal(o2.GetType(), e2.GetType()); + } + + [Fact] + public void DynamicObjectResolverWorksAcrossAssemblyLoadContexts() + { + FirstSimpleData e1 = new FirstSimpleData(); + var options = this.CreateSerializerOptions(); + + var b1 = MessagePackSerializer.Serialize(e1, options: options); + var o1 = MessagePackSerializer.Deserialize(b1, options: options); + + Assert.Equal(typeof(FirstSimpleData), o1.GetType()); + + var assembly = this.loadContext.LoadFromAssemblyPath(SharedDataAssemblyName); + Type objectType = assembly.GetType(typeof(FirstSimpleData).FullName); + object e2 = assembly.CreateInstance(typeof(FirstSimpleData).FullName); + + var b2 = MessagePackSerializer.Serialize(objectType, e2, options: options); + var o2 = MessagePackSerializer.Deserialize(objectType, b2, options: options); + + Assert.Equal(o2.GetType(), e2.GetType()); + } + + [Fact] + public void DynamiObjectResolverWorksWithGenericsAcrossAssemblyLoadContexts() + { + IList e1 = new List { new FirstSimpleData(), new FirstSimpleData() }; + var options = this.CreateSerializerOptions(); + + var b1 = MessagePackSerializer.Serialize(e1, options: options); + var o1 = MessagePackSerializer.Deserialize>(b1, options: options); + + Assert.Equal(typeof(List), o1.GetType()); + Assert.Equal(2, o1.Count); + Assert.All(o1, item => Assert.IsType(item)); + + var assembly = this.loadContext.LoadFromAssemblyPath(SharedDataAssemblyName); + Type objectType = assembly.GetType(typeof(FirstSimpleData).FullName); + Type listType = typeof(List<>).MakeGenericType(objectType); + object list = Activator.CreateInstance(listType); + + // Add two instances to the list + var addMethod = listType.GetMethod("Add"); + addMethod.Invoke(list, new[] { assembly.CreateInstance(typeof(FirstSimpleData).FullName) }); + addMethod.Invoke(list, new[] { assembly.CreateInstance(typeof(FirstSimpleData).FullName) }); + + Assert.Equal(objectType, (list as System.Collections.IList)[0].GetType()); + + var b2 = MessagePackSerializer.Serialize(listType, list, options: options); + var o2 = MessagePackSerializer.Deserialize(listType, b2, options: options); + + // Verify the element type directly from the generic type arguments + Type elementType = o2.GetType().GetGenericArguments()[0]; + Assert.Equal(objectType, elementType); + + // Get the first item to verify its actual runtime type + var enumerable = o2 as System.Collections.IList; + Assert.Equal(objectType, enumerable[0].GetType()); + } + + [Fact] + public void DynamicContractlessObjectResolverWorksAcrossAssemblyLoadContexts() + { + FirstSimpleData e1 = new FirstSimpleData(); + var options = new MessagePackSerializerOptions( + CompositeResolver.Create( + BuiltinResolver.Instance, + PrimitiveObjectResolver.Instance, + DynamicContractlessObjectResolver.Instance)); + + var b1 = MessagePackSerializer.Serialize(e1, options: options); + var o1 = MessagePackSerializer.Deserialize(b1, options: options); + + Assert.Equal(typeof(FirstSimpleData), o1.GetType()); + + var assembly = this.loadContext.LoadFromAssemblyPath(SharedDataAssemblyName); + Type objectType = assembly.GetType(typeof(FirstSimpleData).FullName); + object e2 = assembly.CreateInstance(typeof(FirstSimpleData).FullName); + + var b2 = MessagePackSerializer.Serialize(objectType, e2, options: options); + var o2 = MessagePackSerializer.Deserialize(objectType, b2, options: options); + + Assert.Equal(o2.GetType(), e2.GetType()); + } + + private MessagePackSerializerOptions CreateSerializerOptions() + { + // Avoid default options as it will use source generated formatter which works in this scenario. + return new MessagePackSerializerOptions( + CompositeResolver.Create( + BuiltinResolver.Instance, + AttributeFormatterResolver.Instance, + DynamicEnumResolver.Instance, + DynamicGenericResolver.Instance, + DynamicUnionResolver.Instance, + DynamicObjectResolver.Instance, + PrimitiveObjectResolver.Instance)); + } +} + +#endif From db8a24229996b4e8d41a935f965b20cd012e053f Mon Sep 17 00:00:00 2001 From: Bertan Aygun Date: Wed, 9 Apr 2025 14:54:08 -0700 Subject: [PATCH 02/32] Update unit tests to include a custom generic type. --- .../DynamicCodeDumper.csproj | 2 +- sandbox/Sandbox/Generated.cs | 47 +++++++++++++++++++ .../Assets/Scripts/Tests/Class1.cs | 12 +++++ .../AssemblyLoadContextTests.cs | 30 ++++++++++-- 4 files changed, 87 insertions(+), 4 deletions(-) diff --git a/sandbox/DynamicCodeDumper/DynamicCodeDumper.csproj b/sandbox/DynamicCodeDumper/DynamicCodeDumper.csproj index 1718c5f3c..676074044 100644 --- a/sandbox/DynamicCodeDumper/DynamicCodeDumper.csproj +++ b/sandbox/DynamicCodeDumper/DynamicCodeDumper.csproj @@ -46,7 +46,7 @@ Code\DynamicAssembly.cs - + Code\DynamicAssemblyFactory.cs diff --git a/sandbox/Sandbox/Generated.cs b/sandbox/Sandbox/Generated.cs index 696478189..16b349886 100644 --- a/sandbox/Sandbox/Generated.cs +++ b/sandbox/Sandbox/Generated.cs @@ -3093,6 +3093,53 @@ public void Serialize(ref global::MessagePack.MessagePackWriter writer, global:: } } + public sealed class SimpleGenericDataFormatter : global::MessagePack.Formatters.IMessagePackFormatter> + { + + public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.SimpleGenericData value, global::MessagePack.MessagePackSerializerOptions options) + { + if (value == null) + { + writer.WriteNil(); + return; + } + + global::MessagePack.IFormatterResolver formatterResolver = options.Resolver; + writer.WriteArrayHeader(1); + global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.Value, options); + } + + public global::SharedData.SimpleGenericData Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) + { + if (reader.TryReadNil()) + { + return null; + } + + options.Security.DepthStep(ref reader); + global::MessagePack.IFormatterResolver formatterResolver = options.Resolver; + var length = reader.ReadArrayHeader(); + var __Value__ = default(T); + + for (int i = 0; i < length; i++) + { + switch (i) + { + case 0: + __Value__ = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); + break; + default: + reader.Skip(); + break; + } + } + + var ____result = new global::SharedData.SimpleGenericData(__Value__); + reader.Depth--; + return ____result; + } + } + public sealed class SimpleIntKeyDataFormatter : global::MessagePack.Formatters.IMessagePackFormatter { diff --git a/src/MessagePack.UnityClient/Assets/Scripts/Tests/Class1.cs b/src/MessagePack.UnityClient/Assets/Scripts/Tests/Class1.cs index 1af2f9e0e..20676dbfc 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/Tests/Class1.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/Tests/Class1.cs @@ -87,6 +87,18 @@ public class SimpleIntKeyData ////public int Prop7 { get; set; } } + [MessagePackObject] + public class SimpleGenericData + { + [Key(0)] + public T Value { get; set; } + + public SimpleGenericData(T value) + { + this.Value = value; + } + } + public class OreOreFormatter : IMessagePackFormatter { public int Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) diff --git a/tests/MessagePack.Tests/AssemblyLoadContextTests.cs b/tests/MessagePack.Tests/AssemblyLoadContextTests.cs index 5a9d30d6b..d2ed970a7 100644 --- a/tests/MessagePack.Tests/AssemblyLoadContextTests.cs +++ b/tests/MessagePack.Tests/AssemblyLoadContextTests.cs @@ -95,7 +95,7 @@ public void DynamicObjectResolverWorksAcrossAssemblyLoadContexts() } [Fact] - public void DynamiObjectResolverWorksWithGenericsAcrossAssemblyLoadContexts() + public void DynamiObjectResolverWorksWithGenericCollectionsAcrossAssemblyLoadContexts() { IList e1 = new List { new FirstSimpleData(), new FirstSimpleData() }; var options = this.CreateSerializerOptions(); @@ -104,8 +104,6 @@ public void DynamiObjectResolverWorksWithGenericsAcrossAssemblyLoadContexts() var o1 = MessagePackSerializer.Deserialize>(b1, options: options); Assert.Equal(typeof(List), o1.GetType()); - Assert.Equal(2, o1.Count); - Assert.All(o1, item => Assert.IsType(item)); var assembly = this.loadContext.LoadFromAssemblyPath(SharedDataAssemblyName); Type objectType = assembly.GetType(typeof(FirstSimpleData).FullName); @@ -131,6 +129,32 @@ public void DynamiObjectResolverWorksWithGenericsAcrossAssemblyLoadContexts() Assert.Equal(objectType, enumerable[0].GetType()); } + [Fact] + public void DynamiObjectResolverWorksWithGenericsAcrossAssemblyLoadContexts() + { + SimpleGenericData e1 = new SimpleGenericData(new FirstSimpleData()); + var options = this.CreateSerializerOptions(); + + var b1 = MessagePackSerializer.Serialize(e1, options: options); + var o1 = MessagePackSerializer.Deserialize>(b1, options: options); + + Assert.Equal(typeof(SimpleGenericData), o1.GetType()); + + var assembly = this.loadContext.LoadFromAssemblyPath(SharedDataAssemblyName); + Type dataType = assembly.GetType(typeof(FirstSimpleData).FullName); + Type simpleGenericType = assembly.GetType(typeof(SimpleGenericData<>).FullName); + Type genericType = simpleGenericType.MakeGenericType(dataType); + object data = Activator.CreateInstance(dataType); + object genericData = Activator.CreateInstance(genericType, data); + + var b2 = MessagePackSerializer.Serialize(genericType, genericData, options: options); + var o2 = MessagePackSerializer.Deserialize(genericType, b2, options: options); + + // Verify the element type directly from the generic type arguments + Type elementType = o2.GetType().GetGenericArguments()[0]; + Assert.Equal(dataType, elementType); + } + [Fact] public void DynamicContractlessObjectResolverWorksAcrossAssemblyLoadContexts() { From 71892eb33aeec69adc84362197c134b2e09feea1 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Thu, 10 Apr 2025 09:47:31 -0600 Subject: [PATCH 03/32] Build v2.5 as stable from v2.x --- version.json | 1 + 1 file changed, 1 insertion(+) diff --git a/version.json b/version.json index f24af0f90..d711c9c79 100644 --- a/version.json +++ b/version.json @@ -4,6 +4,7 @@ "publicReleaseRefSpec": [ "^refs/heads/master$", "^refs/heads/v1\\.x$", + "^refs/heads/v2\\.x$", "^refs/heads/v\\d+(?:.\\d+)?$", "^refs/heads/develop$" ], From 58be50d9773aaf541c3d37ea4f6f0aad490e36a2 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Thu, 14 May 2026 17:58:25 -0600 Subject: [PATCH 04/32] Backport .gitignore from master --- .gitignore | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 7e74065d1..7209c6cbc 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,9 @@ bld/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ +# Jetbrains Rider cache directory +.idea/ + # Visual Studio 2017 auto generated files Generated\ Files/ @@ -65,7 +68,6 @@ StyleCopReport.xml *_p.c *_h.h *.ilk -*.meta *.obj *.iobj *.pch @@ -353,12 +355,21 @@ MigrationBackup/ # mac-created file to track user view preferences for a directory .DS_Store +# Analysis results +*.sarif + # Unity src/MessagePack.UnityClient/bin/* src/MessagePack.UnityClient/Library/* src/MessagePack.UnityClient/obj/* src/MessagePack.UnityClient/Temp/* +src/MessagePack.UnityClient/UserSettings/* +src/MessagePack.UnityClient/Assets/Packages/ # BenchmarkDotNet results BenchmarkDotNet.Artifacts/ + +src/MessagePack.UnityClient/.vsconfig + +*.lscache From 9e214ba6f5196d3d9cab98c2617ef7773ea86e9a Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Thu, 14 May 2026 18:33:54 -0600 Subject: [PATCH 05/32] Build on Ubuntu 22 --- azure-pipelines/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines/build.yml b/azure-pipelines/build.yml index 4021879d8..c9f5fc170 100644 --- a/azure-pipelines/build.yml +++ b/azure-pipelines/build.yml @@ -26,7 +26,7 @@ jobs: - job: Linux pool: - vmImage: Ubuntu 20.04 + vmImage: ubuntu-22.04 steps: - checkout: self fetchDepth: 0 # avoid shallow clone so nbgv can do its work. From 94b32dd7aa450b92bf97b85164ff792c384dedd2 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Mon, 30 Mar 2026 16:13:47 -0600 Subject: [PATCH 06/32] Add more types to the default disallow list of named types to be deserialized --- .../Scripts/MessagePack/MessagePackSerializerOptions.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/MessagePackSerializerOptions.cs b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/MessagePackSerializerOptions.cs index f750f93df..dc4598239 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/MessagePackSerializerOptions.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/MessagePackSerializerOptions.cs @@ -25,7 +25,13 @@ public class MessagePackSerializerOptions private static readonly HashSet DisallowedTypes = new HashSet { "System.CodeDom.Compiler.TempFileCollection", + "System.IdentityModel.Tokens.SessionSecurityToken", "System.Management.IWbemClassObjectFreeThreaded", + "System.Security.Claims.ClaimsIdentity", + "System.Security.Principal.WindowsIdentity", + "System.Web.Security.RolePrincipal", + "System.Windows.Data.ObjectDataProvider", + "System.Windows.ResourceDictionary", }; #if !DYNAMICCODEDUMPER From 3538bc1110ed8507b394348188c8075d3d8a3dbf Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Wed, 29 Apr 2026 13:59:17 -0600 Subject: [PATCH 07/32] Bound LZ4 input reads for CWE-125 The LZ4 block decoder accepted only the destination length and advanced through the compressed input without knowing where the source buffer ended. Malformed compressed payloads could therefore drive unchecked native reads before the existing post-decode length check ran. Pass the compressed input length into the 32-bit and 64-bit decoders and reject malformed blocks before token, literal, offset, and match-length reads would move past the source buffer. Add a regression test that verifies malformed LZ4 data fails as a normal serialization exception. --- .../MessagePack/LZ4/LZ4Codec.Unsafe.cs | 4 +- .../LZ4/LZ4Codec.Unsafe32.Dirty.cs | 49 ++++++++++++++++--- .../LZ4/LZ4Codec.Unsafe64.Dirty.cs | 49 ++++++++++++++++--- .../Scripts/Tests/ShareTests/LZ4Test.cs | 41 ++++++++++++++++ 4 files changed, 125 insertions(+), 18 deletions(-) diff --git a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/LZ4/LZ4Codec.Unsafe.cs b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/LZ4/LZ4Codec.Unsafe.cs index ec9a28bc9..1efabf184 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/LZ4/LZ4Codec.Unsafe.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/LZ4/LZ4Codec.Unsafe.cs @@ -99,11 +99,11 @@ public static unsafe int Decode(ReadOnlySpan input, Span output) int length; if (IntPtr.Size == 4) { - length = LZ4_uncompress_32(inputPtr, outputPtr, output.Length); + length = LZ4_uncompress_32(inputPtr, input.Length, outputPtr, output.Length); } else { - length = LZ4_uncompress_64(inputPtr, outputPtr, output.Length); + length = LZ4_uncompress_64(inputPtr, input.Length, outputPtr, output.Length); } if (length != input.Length) diff --git a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/LZ4/LZ4Codec.Unsafe32.Dirty.cs b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/LZ4/LZ4Codec.Unsafe32.Dirty.cs index 22d39b25d..23a40ddca 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/LZ4/LZ4Codec.Unsafe32.Dirty.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/LZ4/LZ4Codec.Unsafe32.Dirty.cs @@ -600,6 +600,7 @@ private static unsafe int LZ4_compress64kCtx_32( private static unsafe int LZ4_uncompress_32( byte* src, + int src_len, byte* dst, int dst_len) { @@ -609,6 +610,7 @@ private static unsafe int LZ4_uncompress_32( { // r93 var src_p = src; + var src_end = src + src_len; byte* xxx_ref; var dst_p = dst; @@ -627,16 +629,26 @@ private static unsafe int LZ4_uncompress_32( int length; // get runlength + if (src_p >= src_end) + { + goto _output_error; + } + xxx_token = *src_p++; if ((length = (int)(xxx_token >> ML_BITS)) == RUN_MASK) { int len; - for (; (len = *src_p++) == 255; length += 255) + do { - /* do nothing */ - } + if (src_p >= src_end) + { + goto _output_error; + } - length += len; + len = *src_p++; + length += len; + } + while (len == 255); } // copy literals @@ -649,11 +661,21 @@ private static unsafe int LZ4_uncompress_32( goto _output_error; // Error : not enough place for another match (min 4) + 5 literals } + if (length > src_end - src_p) + { + goto _output_error; + } + BlockCopy32(src_p, dst_p, length); src_p += length; break; // EOF } + if (length > src_end - src_p) + { + goto _output_error; + } + do { *(uint*)dst_p = *(uint*)src_p; @@ -668,6 +690,11 @@ private static unsafe int LZ4_uncompress_32( dst_p = dst_cpy; // get offset + if (src_end - src_p < 2) + { + goto _output_error; + } + xxx_ref = dst_cpy - (*(ushort*)src_p); src_p += 2; if (xxx_ref < dst) @@ -678,12 +705,18 @@ private static unsafe int LZ4_uncompress_32( // get matchlength if ((length = (int)(xxx_token & ML_MASK)) == ML_MASK) { - for (; *src_p == 255; length += 255) + int len; + do { - src_p++; - } + if (src_p >= src_end) + { + goto _output_error; + } - length += *src_p++; + len = *src_p++; + length += len; + } + while (len == 255); } // copy repeated sequence diff --git a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/LZ4/LZ4Codec.Unsafe64.Dirty.cs b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/LZ4/LZ4Codec.Unsafe64.Dirty.cs index ba10e68d2..f807ef17e 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/LZ4/LZ4Codec.Unsafe64.Dirty.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/LZ4/LZ4Codec.Unsafe64.Dirty.cs @@ -612,6 +612,7 @@ private static unsafe int LZ4_compress64kCtx_64( private static unsafe int LZ4_uncompress_64( byte* src, + int src_len, byte* dst, int dst_len) { @@ -622,6 +623,7 @@ private static unsafe int LZ4_uncompress_64( { // r93 var src_p = src; + var src_end = src + src_len; byte* dst_ref; var dst_p = dst; @@ -640,16 +642,26 @@ private static unsafe int LZ4_uncompress_64( int length; // get runlength + if (src_p >= src_end) + { + goto _output_error; + } + token = *src_p++; if ((length = token >> ML_BITS) == RUN_MASK) { int len; - for (; (len = *src_p++) == 255; length += 255) + do { - /* do nothing */ - } + if (src_p >= src_end) + { + goto _output_error; + } - length += len; + len = *src_p++; + length += len; + } + while (len == 255); } // copy literals @@ -662,11 +674,21 @@ private static unsafe int LZ4_uncompress_64( goto _output_error; // Error : not enough place for another match (min 4) + 5 literals } + if (length > src_end - src_p) + { + goto _output_error; + } + BlockCopy64(src_p, dst_p, length); src_p += length; break; // EOF } + if (length > src_end - src_p) + { + goto _output_error; + } + do { *(ulong*)dst_p = *(ulong*)src_p; @@ -678,6 +700,11 @@ private static unsafe int LZ4_uncompress_64( dst_p = dst_cpy; // get offset + if (src_end - src_p < 2) + { + goto _output_error; + } + dst_ref = dst_cpy - (*(ushort*)src_p); src_p += 2; if (dst_ref < dst) @@ -688,12 +715,18 @@ private static unsafe int LZ4_uncompress_64( // get matchlength if ((length = token & ML_MASK) == ML_MASK) { - for (; *src_p == 255; length += 255) + int len; + do { - src_p++; - } + if (src_p >= src_end) + { + goto _output_error; + } - length += *src_p++; + len = *src_p++; + length += len; + } + while (len == 255); } // copy repeated sequence diff --git a/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/LZ4Test.cs b/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/LZ4Test.cs index f314b572a..062576fcd 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/LZ4Test.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/LZ4Test.cs @@ -32,6 +32,47 @@ public void Lz4Compress() Execute(10000); } + [Fact] + [Trait("CWE", "125")] + public void Lz4BlockRejectsTruncatedLiteralRun() + { + const int extensionByteCount = 1024; + int uncompressedLength = 15 + (255 * extensionByteCount) + 16; + + byte[] sizeHeader = + { + 0xCE, + (byte)(uncompressedLength >> 24), + (byte)(uncompressedLength >> 16), + (byte)(uncompressedLength >> 8), + (byte)uncompressedLength, + }; + + byte[] lz4 = new byte[1 + extensionByteCount]; + lz4[0] = 0xF0; + for (int i = 1; i < lz4.Length; i++) + { + lz4[i] = 0xFF; + } + + int bodyLength = sizeHeader.Length + lz4.Length; + byte[] payload = new byte[6 + bodyLength]; + int offset = 0; + payload[offset++] = 0xC9; + payload[offset++] = (byte)(bodyLength >> 24); + payload[offset++] = (byte)(bodyLength >> 16); + payload[offset++] = (byte)(bodyLength >> 8); + payload[offset++] = (byte)bodyLength; + payload[offset++] = 99; + System.Array.Copy(sizeHeader, 0, payload, offset, sizeHeader.Length); + offset += sizeHeader.Length; + System.Array.Copy(lz4, 0, payload, offset, lz4.Length); + + var options = MessagePackSerializerOptions.Standard.WithCompression(MessagePackCompression.Lz4Block); + + Assert.Throws(() => MessagePackSerializer.Deserialize(payload, options)); + } + private void Execute(int count) { // Large From adeb4eff1584d700383b94dcade7db6a60eac1b6 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Thu, 14 May 2026 20:37:06 -0600 Subject: [PATCH 08/32] Add test to verify that we reject invalid DateTime ext lengths for CWE-789 --- .../Scripts/Tests/ShareTests/MessagePackReaderTests.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/MessagePackReaderTests.cs b/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/MessagePackReaderTests.cs index 774927d13..d3151fe27 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/MessagePackReaderTests.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/MessagePackReaderTests.cs @@ -322,6 +322,15 @@ void AssertIncomplete(WriterEncoder encoder, ReadOperation decoder, bool v AssertIncomplete((ref MessagePackWriter writer) => writer.Write(0xff), (ref MessagePackReader reader) => reader.ReadUInt64()); } + [Fact] + [Trait("CWE", "789")] + public void ReadDateTime_RejectsInvalidExtensionLengths() + { + byte[] payload = new byte[] { MessagePackCode.Ext32, 0x00, 0x10, 0x00, 0x00, 0xff }; + + Assert.Throws(() => new MessagePackReader(new ReadOnlySequence(payload)).ReadDateTime()); + } + [Fact] public void CreatePeekReader() { From e97f71e7caff627e915a486f495f91a388c19276 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Wed, 29 Apr 2026 14:18:40 -0600 Subject: [PATCH 09/32] Use secure lookup comparer for CWE-407 InterfaceLookupFormatter created its intermediate Dictionary with the default comparer, so ILookup deserialization did not honor MessagePackSecurity.UntrustedData hash-collision resistance. Pass the security-provided equality comparer into the intermediate dictionary and add regression coverage that verifies colliding long keys use the hardened comparer. --- .../Scripts/MessagePack/Formatters/CollectionFormatter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Formatters/CollectionFormatter.cs b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Formatters/CollectionFormatter.cs index 29d8ca7d7..02da01f9e 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Formatters/CollectionFormatter.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Formatters/CollectionFormatter.cs @@ -765,7 +765,7 @@ protected override ILookup Complete(Dictionary> Create(int count, MessagePackSerializerOptions options) { - return new Dictionary>(count); + return new Dictionary>(count, options.Security.GetEqualityComparer()); } } From dc6f63241f1edd956a96b56c8ac17c193f40ca8a Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Wed, 29 Apr 2026 14:15:31 -0600 Subject: [PATCH 10/32] Fix CWE-789 multidimensional array allocation validation Multidimensional array deserialization trusted dimension values and allocated arrays before confirming that the flattened element count matched the serialized element array header. This could let malformed data request disproportionate allocations before validation. Validate non-negative dimensions and checked flattened lengths for 2D, 3D, and 4D array formatters before allocation, and add regression coverage for mismatched element counts under untrusted data options. --- .../MultiDimensionalArrayFormatter.cs | 38 ++++++++++++- .../ShareTests/MultiDimensionalArrayTest.cs | 55 +++++++++++++++++++ 2 files changed, 90 insertions(+), 3 deletions(-) diff --git a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Formatters/MultiDimensionalArrayFormatter.cs b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Formatters/MultiDimensionalArrayFormatter.cs index 44812d1a0..128e52e42 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Formatters/MultiDimensionalArrayFormatter.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Formatters/MultiDimensionalArrayFormatter.cs @@ -2,9 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; -using System.Buffers; -using System.Collections.Generic; -using System.Text; +using System.Diagnostics.CodeAnalysis; #pragma warning disable SA1402 // File may only contain a single type #pragma warning disable SA1649 // File name should match first type name @@ -62,6 +60,7 @@ public void Serialize(ref MessagePackWriter writer, T[,]? value, MessagePackSeri var iLength = reader.ReadInt32(); var jLength = reader.ReadInt32(); var maxLen = reader.ReadArrayHeader(); + MultiDimensionalArrayFormatterHelper.ThrowIfLengthsDontMatch("T[,]", maxLen, iLength, jLength); var array = new T[iLength, jLength]; @@ -148,6 +147,7 @@ public void Serialize(ref MessagePackWriter writer, T[,,]? value, MessagePackSer var jLength = reader.ReadInt32(); var kLength = reader.ReadInt32(); var maxLen = reader.ReadArrayHeader(); + MultiDimensionalArrayFormatterHelper.ThrowIfLengthsDontMatch("T[,,]", maxLen, iLength, jLength, kLength); var array = new T[iLength, jLength, kLength]; @@ -244,6 +244,8 @@ public void Serialize(ref MessagePackWriter writer, T[,,,]? value, MessagePackSe var kLength = reader.ReadInt32(); var lLength = reader.ReadInt32(); var maxLen = reader.ReadArrayHeader(); + MultiDimensionalArrayFormatterHelper.ThrowIfLengthsDontMatch("T[,,,]", maxLen, iLength, jLength, kLength, lLength); + var array = new T[iLength, jLength, kLength, lLength]; var i = 0; @@ -291,4 +293,34 @@ public void Serialize(ref MessagePackWriter writer, T[,,,]? value, MessagePackSe } } } + + internal static class MultiDimensionalArrayFormatterHelper + { + internal static void ThrowIfLengthsDontMatch(string format, int actualLength, int firstLength, int secondLength, int thirdLength = 1, int fourthLength = 1) + { + if (firstLength < 0 || secondLength < 0 || thirdLength < 0 || fourthLength < 0) + { + ThrowInvalidFormat(format); + } + + int expectedLength; + try + { + expectedLength = checked(firstLength * secondLength * thirdLength * fourthLength); + } + catch (OverflowException) + { + ThrowInvalidFormat(format); + return; + } + + if (expectedLength != actualLength) + { + ThrowInvalidFormat(format); + } + } + + [DoesNotReturn] + private static void ThrowInvalidFormat(string format) => throw new MessagePackSerializationException($"Invalid {format} format"); + } } diff --git a/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/MultiDimensionalArrayTest.cs b/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/MultiDimensionalArrayTest.cs index f4a0a2619..59daa7fab 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/MultiDimensionalArrayTest.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/MultiDimensionalArrayTest.cs @@ -67,5 +67,60 @@ public void MultiDimensional(int dataI, int dataJ, int dataK, int dataL) } } } + + [Fact] + [Trait("CWE", "789")] + public void RejectsTwoDimensionalArrayWithMismatchedElementCount() + { + byte[] payload = + { + 0x93, + 0xCE, 0x00, 0x00, 0x07, 0xD0, + 0xCE, 0x00, 0x00, 0x07, 0xD0, + 0x90, + }; + + AssertRejects(payload); + } + + [Fact] + [Trait("CWE", "789")] + public void RejectsThreeDimensionalArrayWithMismatchedElementCount() + { + byte[] payload = + { + 0x94, + 0xCC, 0x80, + 0xCC, 0x80, + 0xCC, 0x80, + 0x90, + }; + + AssertRejects(payload); + } + + [Fact] + [Trait("CWE", "789")] + public void RejectsFourDimensionalArrayWithMismatchedElementCount() + { + byte[] payload = + { + 0x95, + 0x20, + 0x20, + 0x20, + 0x20, + 0x90, + }; + + AssertRejects(payload); + } + + private void AssertRejects(byte[] payload) + { + var options = MessagePackSerializerOptions.Standard.WithSecurity(MessagePackSecurity.UntrustedData); + + Assert.Throws(() => MessagePackSerializer.Deserialize(payload, options)); + } } } From 7b12e5b5663e20b61823f599a1f10398c9a996ce Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Wed, 29 Apr 2026 14:29:15 -0600 Subject: [PATCH 11/32] Guard JSON conversion depth for CWE-674 ConvertFromJson now applies the configured MessagePackSecurity maximum object graph depth while translating nested JSON objects and arrays, preventing deeply nested input from recursing until stack exhaustion. Added a bounded regression test that verifies over-depth JSON is rejected for both compressed and uncompressed conversion paths. --- .../MessagePack/MessagePackSerializer.Json.cs | 21 +++++++++++++++++-- .../Scripts/Tests/ShareTests/ToJsonTest.cs | 13 ++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/MessagePackSerializer.Json.cs b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/MessagePackSerializer.Json.cs index b9d02ac62..8db891345 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/MessagePackSerializer.Json.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/MessagePackSerializer.Json.cs @@ -184,6 +184,11 @@ public static void ConvertFromJson(TextReader reader, ref MessagePackWriter writ } private static uint FromJsonCore(TinyJsonReader jr, ref MessagePackWriter writer, MessagePackSerializerOptions options) + { + return FromJsonCore(jr, ref writer, options, 0); + } + + private static uint FromJsonCore(TinyJsonReader jr, ref MessagePackWriter writer, MessagePackSerializerOptions options, int depth) { uint count = 0; while (jr.Read()) @@ -193,11 +198,13 @@ private static uint FromJsonCore(TinyJsonReader jr, ref MessagePackWriter writer case TinyJsonToken.None: break; case TinyJsonToken.StartObject: + VerifyJsonObjectGraphDepth(options, depth); + // Set up a scratch area to serialize the collection since we don't know its length yet, which must be written first. using (var scratchRental = options.SequencePool.Rent()) { MessagePackWriter scratchWriter = writer.Clone(scratchRental.Value); - var mapCount = FromJsonCore(jr, ref scratchWriter, options); + var mapCount = FromJsonCore(jr, ref scratchWriter, options, depth + 1); scratchWriter.Flush(); mapCount = mapCount / 2; // remove propertyname string count. @@ -210,11 +217,13 @@ private static uint FromJsonCore(TinyJsonReader jr, ref MessagePackWriter writer case TinyJsonToken.EndObject: return count; // break case TinyJsonToken.StartArray: + VerifyJsonObjectGraphDepth(options, depth); + // Set up a scratch area to serialize the collection since we don't know its length yet, which must be written first. using (var scratchRental = options.SequencePool.Rent()) { MessagePackWriter scratchWriter = writer.Clone(scratchRental.Value); - var arrayCount = FromJsonCore(jr, ref scratchWriter, options); + var arrayCount = FromJsonCore(jr, ref scratchWriter, options, depth + 1); scratchWriter.Flush(); writer.WriteArrayHeader(arrayCount); @@ -270,6 +279,14 @@ private static uint FromJsonCore(TinyJsonReader jr, ref MessagePackWriter writer return count; } + private static void VerifyJsonObjectGraphDepth(MessagePackSerializerOptions options, int depth) + { + if (depth >= options.Security.MaximumObjectGraphDepth) + { + throw new InsufficientExecutionStackException($"This JSON sequence has an object graph that exceeds the maximum depth allowed of {options.Security.MaximumObjectGraphDepth}."); + } + } + private static void ToJsonCore(ref MessagePackReader reader, TextWriter writer, MessagePackSerializerOptions options) { MessagePackType type = reader.NextMessagePackType; diff --git a/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/ToJsonTest.cs b/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/ToJsonTest.cs index 8069ea266..c2e3be2bb 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/ToJsonTest.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/ToJsonTest.cs @@ -47,6 +47,19 @@ public void ComplexToJson() this.JsonConvert(json, LZ4Standard).Is(json); } + [Theory] + [InlineData(false)] + [InlineData(true)] + [Trait("CWE", "674")] + public void ConvertFromJsonRejectsExcessiveNesting(bool compression) + { + var options = MessagePackSerializerOptions.Standard + .WithCompression(compression ? MessagePackCompression.Lz4Block : MessagePackCompression.None) + .WithSecurity(MessagePackSecurity.UntrustedData.WithMaximumObjectGraphDepth(3)); + + Assert.Throws(() => MessagePackSerializer.ConvertFromJson("[[[[1]]]]", options)); + } + [Fact] public void FloatJson() { From e01f07cfb2343425cfa1045f044d0831b26f60dd Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Wed, 29 Apr 2026 14:26:16 -0600 Subject: [PATCH 12/32] Validate Unity blit lengths for CWE-789 UnsafeBlitFormatter trusted the nested byte count from extension payloads when allocating arrays, even though the surrounding extension length had already bounded the body. Malformed inputs could request allocations that were not supported by the declared extension data. Parse the extension body through a bounded reader and reject negative, unaligned, or mismatched byte counts before allocating. Add a regression test for the malformed length case. --- .../Unity/Extension/UnsafeBlitFormatter.cs | 19 +++++++++++++------ .../ExtensionTests/UnityShimTest.cs | 12 ++++++++++++ 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Unity/Extension/UnsafeBlitFormatter.cs b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Unity/Extension/UnsafeBlitFormatter.cs index 14efd4fa0..011b91997 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Unity/Extension/UnsafeBlitFormatter.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Unity/Extension/UnsafeBlitFormatter.cs @@ -48,19 +48,26 @@ public void Serialize(ref MessagePackWriter writer, T[]? value, MessagePackSeria return null; } - ExtensionHeader header = reader.ReadExtensionFormatHeader(); - if (header.TypeCode != this.TypeCode) + ExtensionResult extension = reader.ReadExtensionFormat(); + if (extension.TypeCode != this.TypeCode) { throw new InvalidOperationException("Invalid typeCode."); } - var byteLength = reader.ReadInt32(); - var isLittleEndian = reader.ReadBoolean(); + MessagePackReader extensionReader = reader.Clone(extension.Data); + var byteLength = extensionReader.ReadInt32(); + var isLittleEndian = extensionReader.ReadBoolean(); + int elementSize = Marshal.SizeOf(); + long remainingBytes = extensionReader.Sequence.Length - extensionReader.Consumed; + if (byteLength < 0 || byteLength % elementSize != 0 || byteLength != remainingBytes) + { + throw new MessagePackSerializationException("Invalid Unity blit extension length."); + } // Allocate a T[] that we will return. We'll then cast the T[] as byte[] so we can copy the byte sequence directly into it. - var result = new T[byteLength / Marshal.SizeOf()]; + var result = new T[byteLength / elementSize]; Span resultAsBytes = MemoryMarshal.Cast(result); - reader.ReadRaw(byteLength).CopyTo(resultAsBytes); + extensionReader.ReadRaw(byteLength).CopyTo(resultAsBytes); // Reverse the byte order if necessary. if (isLittleEndian != BitConverter.IsLittleEndian) diff --git a/tests/MessagePack.Tests/ExtensionTests/UnityShimTest.cs b/tests/MessagePack.Tests/ExtensionTests/UnityShimTest.cs index 7e35b2448..df5b1abce 100644 --- a/tests/MessagePack.Tests/ExtensionTests/UnityShimTest.cs +++ b/tests/MessagePack.Tests/ExtensionTests/UnityShimTest.cs @@ -94,6 +94,18 @@ public void EnsureSpecCompatibilityTest(BlitContainer data) EnsureSpecCompatibility(data.Array); } + [Fact] + [Trait("CWE", "789")] + public void BlitRejectsByteLengthThatExceedsExtensionBody() + { + MessagePackSerializerOptions options = MessagePackSerializerOptions.Standard.WithResolver(new WithUnityBlitResolver()); + byte[] payload = { 0xC7, 0x06, unchecked((byte)ThisLibraryExtensionTypeCodes.UnityInt), 0xCE, 0x00, 0x00, 0x00, 0x08, 0xC3 }; + + var ex = Assert.Throws(() => MessagePackSerializer.Deserialize(payload, options)); + var inner = Assert.IsType(ex.InnerException); + Assert.Contains("Invalid Unity blit extension length", inner.Message); + } + public class WithUnityBlitResolver : IFormatterResolver { public IMessagePackFormatter GetFormatter() From 940b8508786f8db48f843fee0481f04e71b2a10e Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Wed, 29 Apr 2026 14:22:23 -0600 Subject: [PATCH 13/32] Guard dynamic union depth for CWE-674 DynamicUnionResolver emitted deserializers did not count the union formatter frame against MessagePackSecurity's object graph depth budget. That left recursive union values and skipped unknown union payloads less constrained than source-generated unions and dynamic object formatters. Emit DepthStep after nil handling and decrement reader.Depth before returning, matching the existing dynamic object formatter pattern. Add a regression test proving unknown union payloads respect the depth limit without including exploit payload details. --- .../Resolvers/DynamicUnionResolver.cs | 21 +++++++++- .../ShareTests/MessagePackSerializerTest.cs | 38 +++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Resolvers/DynamicUnionResolver.cs b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Resolvers/DynamicUnionResolver.cs index 7f1c436b3..3f321b787 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Resolvers/DynamicUnionResolver.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Resolvers/DynamicUnionResolver.cs @@ -331,6 +331,14 @@ private static void BuildDeserialize(Type type, UnionAttribute[] infos, MethodBu il.MarkLabel(falseLabel); + var reader = new ArgumentField(il, 1); + + // options.Security.DepthStep(ref reader); + il.EmitLdarg(2); + il.EmitCall(getSecurityFromOptions); + reader.EmitLdarg(); + il.EmitCall(securityDepthStep); + // IFormatterResolver resolver = options.Resolver; LocalBuilder localResolver = il.DeclareLocal(typeof(IFormatterResolver)); il.EmitLdarg(2); @@ -339,7 +347,6 @@ private static void BuildDeserialize(Type type, UnionAttribute[] infos, MethodBu // read-array header and validate, reader.ReadArrayHeader() != 2) throw; Label rightLabel = il.DefineLabel(); - var reader = new ArgumentField(il, 1); reader.EmitLdarg(); il.EmitCall(MessagePackReaderTypeInfo.ReadArrayHeader); il.EmitLdc_I4(2); @@ -406,6 +413,14 @@ private static void BuildDeserialize(Type type, UnionAttribute[] infos, MethodBu il.MarkLabel(loopEnd); + // reader.Depth--; + reader.EmitLdarg(); + il.Emit(OpCodes.Dup); + il.EmitCall(readerDepthGet); + il.Emit(OpCodes.Ldc_I4_1); + il.Emit(OpCodes.Sub_Ovf); + il.EmitCall(readerDepthSet); + il.Emit(OpCodes.Ldloc, result); il.Emit(OpCodes.Ret); } @@ -430,6 +445,10 @@ private static bool IsZeroStartSequential(UnionAttribute[] infos) private static readonly Type refKvp = typeof(KeyValuePair).MakeByRefType(); private static readonly MethodInfo getFormatterWithVerify = typeof(FormatterResolverExtensions).GetRuntimeMethods().First(x => x.Name == "GetFormatterWithVerify"); private static readonly MethodInfo getResolverFromOptions = typeof(MessagePackSerializerOptions).GetRuntimeProperty(nameof(MessagePackSerializerOptions.Resolver))!.GetMethod!; + private static readonly MethodInfo getSecurityFromOptions = typeof(MessagePackSerializerOptions).GetRuntimeProperty(nameof(MessagePackSerializerOptions.Security))!.GetMethod!; + private static readonly MethodInfo securityDepthStep = typeof(MessagePackSecurity).GetRuntimeMethod(nameof(MessagePackSecurity.DepthStep), new[] { typeof(MessagePackReader).MakeByRefType() })!; + private static readonly MethodInfo readerDepthGet = typeof(MessagePackReader).GetRuntimeProperty(nameof(MessagePackReader.Depth))!.GetMethod!; + private static readonly MethodInfo readerDepthSet = typeof(MessagePackReader).GetRuntimeProperty(nameof(MessagePackReader.Depth))!.SetMethod!; private static readonly Func getSerialize = t => typeof(IMessagePackFormatter<>).MakeGenericType(t).GetRuntimeMethod("Serialize", new[] { typeof(MessagePackWriter).MakeByRefType(), t, typeof(MessagePackSerializerOptions) })!; private static readonly Func getDeserialize = t => typeof(IMessagePackFormatter<>).MakeGenericType(t).GetRuntimeMethod("Deserialize", new[] { typeof(MessagePackReader).MakeByRefType(), typeof(MessagePackSerializerOptions) })!; diff --git a/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/MessagePackSerializerTest.cs b/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/MessagePackSerializerTest.cs index 211a35143..31f1a95e8 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/MessagePackSerializerTest.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/MessagePackSerializerTest.cs @@ -275,6 +275,18 @@ public void StackDepthCheck_DynamicObjectResolver() Assert.IsType(ex.InnerException); } + [Fact] + [Trait("CWE", "674")] + public void StackDepthCheck_DynamicUnionResolver() + { + byte[] msgpack = MessagePackSerializer.Serialize(new DepthCheckedUnionBranch()); + var options = MessagePackSerializerOptions.Standard + .WithSecurity(MessagePackSecurity.UntrustedData.WithMaximumObjectGraphDepth(1)); + + var ex = Assert.Throws(() => MessagePackSerializer.Deserialize(msgpack, options)); + Assert.IsType(ex.InnerException); + } + #endif private delegate void WriterHelper(ref MessagePackWriter writer); @@ -414,4 +426,30 @@ protected override void Dispose(bool disposing) base.Dispose(disposing); } } + + [MessagePackObject(keyAsPropertyName: true)] + public class SkipUnknownMemberTarget + { + public int Known { get; set; } + } + + [Union(0, typeof(DepthCheckedUnionLeaf))] + [Union(999, typeof(DepthCheckedUnionBranch))] + public interface IDepthCheckedUnionNode + { + } + + [MessagePackObject] + public class DepthCheckedUnionLeaf : IDepthCheckedUnionNode + { + [Key(0)] + public int Value { get; set; } + } + + [MessagePackObject] + public class DepthCheckedUnionBranch : IDepthCheckedUnionNode + { + [Key(0)] + public IDepthCheckedUnionNode Child { get; set; } + } } From 9674352313a8472669a29004a55e88d299e009a5 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Wed, 29 Apr 2026 14:35:35 -0600 Subject: [PATCH 14/32] Guard typeless JSON depth for CWE-674 ConvertToJson recursively expands typeless extension values while composing JSON, but that path was not counted against MessagePackSecurity.MaximumObjectGraphDepth. Deeply nested typeless values could therefore bypass UntrustedData depth checks and exhaust the call stack. Wrap typeless extension JSON processing in DepthStep/decrement accounting and add regression coverage that expects the configured depth limit to be enforced. --- .../MessagePack/MessagePackSerializer.Json.cs | 68 +++++++++++-------- .../ShareTests/MessagePackSerializerTest.cs | 34 ++++++++++ 2 files changed, 72 insertions(+), 30 deletions(-) diff --git a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/MessagePackSerializer.Json.cs b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/MessagePackSerializer.Json.cs index b9d02ac62..c0a89b5c7 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/MessagePackSerializer.Json.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/MessagePackSerializer.Json.cs @@ -393,46 +393,54 @@ private static void ToJsonCore(ref MessagePackReader reader, TextWriter writer, #if !UNITY_2018_3_OR_NEWER else if (extHeader.TypeCode == ThisLibraryExtensionTypeCodes.TypelessFormatter) { - // prepare type name token - var privateBuilder = new StringBuilder(); - var typeNameTokenBuilder = new StringBuilder(); - SequencePosition positionBeforeTypeNameRead = reader.Position; - ToJsonCore(ref reader, new StringWriter(typeNameTokenBuilder), options); - int typeNameReadSize = (int)reader.Sequence.Slice(positionBeforeTypeNameRead, reader.Position).Length; - if (extHeader.Length > typeNameReadSize) + options.Security.DepthStep(ref reader); + try { - // object map or array - MessagePackType typeInside = reader.NextMessagePackType; - if (typeInside != MessagePackType.Array && typeInside != MessagePackType.Map) + // prepare type name token + var privateBuilder = new StringBuilder(); + var typeNameTokenBuilder = new StringBuilder(); + SequencePosition positionBeforeTypeNameRead = reader.Position; + ToJsonCore(ref reader, new StringWriter(typeNameTokenBuilder), options); + int typeNameReadSize = (int)reader.Sequence.Slice(positionBeforeTypeNameRead, reader.Position).Length; + if (extHeader.Length > typeNameReadSize) { - privateBuilder.Append("{"); - } + // object map or array + MessagePackType typeInside = reader.NextMessagePackType; + if (typeInside != MessagePackType.Array && typeInside != MessagePackType.Map) + { + privateBuilder.Append("{"); + } - ToJsonCore(ref reader, new StringWriter(privateBuilder), options); + ToJsonCore(ref reader, new StringWriter(privateBuilder), options); - // insert type name token to start of object map or array - if (typeInside != MessagePackType.Array) - { - typeNameTokenBuilder.Insert(0, "\"$type\":"); - } + // insert type name token to start of object map or array + if (typeInside != MessagePackType.Array) + { + typeNameTokenBuilder.Insert(0, "\"$type\":"); + } - if (typeInside != MessagePackType.Array && typeInside != MessagePackType.Map) - { - privateBuilder.Append("}"); - } + if (typeInside != MessagePackType.Array && typeInside != MessagePackType.Map) + { + privateBuilder.Append("}"); + } - if (privateBuilder.Length > 2) - { - typeNameTokenBuilder.Append(","); - } + if (privateBuilder.Length > 2) + { + typeNameTokenBuilder.Append(","); + } - privateBuilder.Insert(1, typeNameTokenBuilder.ToString()); + privateBuilder.Insert(1, typeNameTokenBuilder.ToString()); - writer.Write(privateBuilder.ToString()); + writer.Write(privateBuilder.ToString()); + } + else + { + writer.Write("{\"$type\":" + typeNameTokenBuilder.ToString() + "}"); + } } - else + finally { - writer.Write("{\"$type\":" + typeNameTokenBuilder.ToString() + "}"); + reader.Depth--; } } #endif diff --git a/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/MessagePackSerializerTest.cs b/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/MessagePackSerializerTest.cs index 211a35143..9f0a399e2 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/MessagePackSerializerTest.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/MessagePackSerializerTest.cs @@ -209,6 +209,18 @@ public async Task SerializeAndDeserializeAsync_MultipleValues_SeekableStream(boo Assert.Equal(3, await MessagePackSerializer.DeserializeAsync(stream)); } + [Fact] + [Trait("CWE", "674")] + public void StackDepthCheck_ConvertToJsonTypelessExtension() + { + const int maxDepth = 3; + byte[] msgpack = BuildNestedTypelessExtension(maxDepth + 1); + var options = MessagePackSerializerOptions.Standard + .WithSecurity(MessagePackSecurity.UntrustedData.WithMaximumObjectGraphDepth(maxDepth)); + + AssertConvertToJsonRecursionCheckThrows(new ReadOnlySequence(msgpack), options); + } + [Theory] [InlineData(true)] [InlineData(false)] @@ -318,6 +330,28 @@ private static void AssertConvertToJsonRecursionCheckThrows(ReadOnlySequence(ex.InnerException); } + private static byte[] BuildNestedTypelessExtension(int levels) + { + byte[] msgpack = new byte[(levels * 6) + 2]; + int offset = msgpack.Length; + msgpack[--offset] = (byte)'x'; + msgpack[--offset] = 0xa1; + int innerLength = 2; + + for (int level = 0; level < levels; level++) + { + msgpack[--offset] = unchecked((byte)ThisLibraryExtensionTypeCodes.TypelessFormatter); + msgpack[--offset] = (byte)innerLength; + msgpack[--offset] = (byte)(innerLength >> 8); + msgpack[--offset] = (byte)(innerLength >> 16); + msgpack[--offset] = (byte)(innerLength >> 24); + msgpack[--offset] = 0xc9; + innerLength += 6; + } + + return msgpack; + } + [DataContract] public class RecursiveObjectGraph { From a3c8a18398d69f4901a7e6c9784fa58be02c48ed Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Wed, 29 Apr 2026 14:32:00 -0600 Subject: [PATCH 15/32] Avoid JSON separator recursion for CWE-674 TinyJsonReader previously skipped JSON separators with a recursive ReadNextToken call, so a long separator sequence supplied to ConvertFromJson could consume one stack frame per separator and terminate the process. Change separator skipping to stay within the tokenizer loop instead, preserving the existing tokenization behavior without stack growth. Add regression coverage that converts a long separator-prefixed JSON value successfully. --- .../MessagePack/Internal/TinyJsonReader.cs | 108 +++++++++--------- .../Scripts/Tests/ShareTests/ToJsonTest.cs | 10 ++ 2 files changed, 65 insertions(+), 53 deletions(-) diff --git a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Internal/TinyJsonReader.cs b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Internal/TinyJsonReader.cs index 2d082f9f7..89036c12e 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Internal/TinyJsonReader.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Internal/TinyJsonReader.cs @@ -134,62 +134,64 @@ private static bool IsWordBreak(char c) private void ReadNextToken() { - this.SkipWhiteSpace(); - - var intChar = this.reader.Peek(); - if (intChar == -1) + while (true) { - this.TokenType = TinyJsonToken.None; - return; - } + this.SkipWhiteSpace(); - var c = (char)intChar; - switch (c) - { - case '{': - this.TokenType = TinyJsonToken.StartObject; - return; - case '}': - this.TokenType = TinyJsonToken.EndObject; - return; - case '[': - this.TokenType = TinyJsonToken.StartArray; - return; - case ']': - this.TokenType = TinyJsonToken.EndArray; - return; - case '"': - this.TokenType = TinyJsonToken.String; - return; - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - case '-': - this.TokenType = TinyJsonToken.Number; - return; - case 't': - this.TokenType = TinyJsonToken.True; - return; - case 'f': - this.TokenType = TinyJsonToken.False; - return; - case 'n': - this.TokenType = TinyJsonToken.Null; - return; - case ',': - case ':': - this.reader.Read(); - this.ReadNextToken(); + var intChar = this.reader.Peek(); + if (intChar == -1) + { + this.TokenType = TinyJsonToken.None; return; - default: - throw new TinyJsonException("Invalid String:" + c); + } + + var c = (char)intChar; + switch (c) + { + case '{': + this.TokenType = TinyJsonToken.StartObject; + return; + case '}': + this.TokenType = TinyJsonToken.EndObject; + return; + case '[': + this.TokenType = TinyJsonToken.StartArray; + return; + case ']': + this.TokenType = TinyJsonToken.EndArray; + return; + case '"': + this.TokenType = TinyJsonToken.String; + return; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + this.TokenType = TinyJsonToken.Number; + return; + case 't': + this.TokenType = TinyJsonToken.True; + return; + case 'f': + this.TokenType = TinyJsonToken.False; + return; + case 'n': + this.TokenType = TinyJsonToken.Null; + return; + case ',': + case ':': + this.reader.Read(); + continue; + default: + throw new TinyJsonException("Invalid String:" + c); + } } } diff --git a/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/ToJsonTest.cs b/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/ToJsonTest.cs index 8069ea266..d65acb5ca 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/ToJsonTest.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/ToJsonTest.cs @@ -47,6 +47,16 @@ public void ComplexToJson() this.JsonConvert(json, LZ4Standard).Is(json); } + [Fact] + [Trait("CWE", "674")] + public void ConvertFromJsonSkipsLongSeparatorRunIteratively() + { + var json = new string(',', 200_000) + "null"; + var msgpack = MessagePackSerializer.ConvertFromJson(json); + + MessagePackSerializer.ConvertToJson(msgpack).Is("null"); + } + [Fact] public void FloatJson() { From 0124048c8921d1c695760a8ed0d148ccb2920387 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Wed, 29 Apr 2026 14:43:06 -0600 Subject: [PATCH 16/32] Fix CWE-190 map header length overflow Promote map entry count multiplication to long when validating the minimum encoded payload length and when skipping map contents. This keeps oversized malformed map headers on the insufficient-buffer path instead of relying on Int32 arithmetic behavior. Add regression coverage for oversized map headers in ReadMapHeader and Skip. --- .../Scripts/MessagePack/MessagePackReader.cs | 2 +- .../ShareTests/MessagePackReaderTests.cs | 27 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/MessagePackReader.cs b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/MessagePackReader.cs index d2c630d73..3fd74dc14 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/MessagePackReader.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/MessagePackReader.cs @@ -390,7 +390,7 @@ public int ReadMapHeader() // Protect against corrupted or mischievious data that may lead to allocating way too much memory. // We allow for each primitive to be the minimal 1 byte in size, and we have a key=value map, so that's 2 bytes. // Formatters that know each element is larger can optionally add a stronger check. - ThrowInsufficientBufferUnless(this.reader.Remaining >= count * 2); + ThrowInsufficientBufferUnless(this.reader.Remaining >= (long)count * 2); return count; } diff --git a/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/MessagePackReaderTests.cs b/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/MessagePackReaderTests.cs index 774927d13..8f754b6ed 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/MessagePackReaderTests.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/MessagePackReaderTests.cs @@ -101,6 +101,33 @@ public void ReadMapHeader_MitigatesLargeAllocations() }); } + [Fact] + [Trait("CWE", "190")] + public void ReadMapHeader_MitigatesLargeAllocations_WhenMinimumPayloadLengthOverflowsInt32() + { + byte[] msgpack = { MessagePackCode.Map32, 0x40, 0, 0, 0 }; + + Assert.Throws(() => + { + var reader = new MessagePackReader(msgpack); + reader.ReadMapHeader(); + }); + } + + [Fact] + [Trait("CWE", "190")] + public void SkipMap_MitigatesLargeAllocations_WhenMinimumPayloadLengthOverflowsInt32() + { + byte[] msgpack = { MessagePackCode.Map32, 0x40, 0, 0, 0 }; + + var ex = Assert.ThrowsAny(() => + { + var reader = new MessagePackReader(msgpack); + reader.Skip(); + }); + Assert.True(ex is EndOfStreamException or MessagePackSerializationException or OverflowException); + } + [Fact] public void TryReadMapHeader() { From ae90f2b1c65b62e87a3f51a933843ba943014e92 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Wed, 29 Apr 2026 14:39:01 -0600 Subject: [PATCH 17/32] Limit untrusted ExpandoObject maps for CWE-407 ExpandoObject map materialization inserts each member through ExpandoObject, whose member table grows with linear scans and array copies. Under the untrusted-data resolver preset, very large maps could consume disproportionate CPU before application code sees the result. Reject oversized ExpandoObject maps when hash-collision hardening is active, covering both direct ExpandoObject deserialization and nested maps produced by ExpandoObjectResolver. Add focused regression coverage for both paths. --- .../Formatters/ExpandoObjectFormatter.cs | 11 +++++++ .../Resolvers/ExpandoObjectResolver.cs | 1 + .../Tests/ShareTests/ExpandoObjectTests.cs | 30 +++++++++++++++++++ 3 files changed, 42 insertions(+) diff --git a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Formatters/ExpandoObjectFormatter.cs b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Formatters/ExpandoObjectFormatter.cs index 2d9328880..98932401f 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Formatters/ExpandoObjectFormatter.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Formatters/ExpandoObjectFormatter.cs @@ -8,6 +8,8 @@ namespace MessagePack.Formatters { public class ExpandoObjectFormatter : IMessagePackFormatter { + internal const int MaximumUntrustedDataMemberCount = 1024; + public static readonly IMessagePackFormatter Instance = new ExpandoObjectFormatter(); private ExpandoObjectFormatter() @@ -23,6 +25,7 @@ private ExpandoObjectFormatter() var result = new ExpandoObject(); int count = reader.ReadMapHeader(); + ThrowIfMapTooLargeForUntrustedData(count, options); if (count > 0) { IFormatterResolver resolver = options.Resolver; @@ -49,6 +52,14 @@ private ExpandoObjectFormatter() return result; } + internal static void ThrowIfMapTooLargeForUntrustedData(int count, MessagePackSerializerOptions options) + { + if (options.Security.HashCollisionResistant && count > MaximumUntrustedDataMemberCount) + { + throw new MessagePackSerializationException($"ExpandoObject map size exceeds the limit of {MaximumUntrustedDataMemberCount} entries allowed under untrusted data security mode."); + } + } + public void Serialize(ref MessagePackWriter writer, ExpandoObject? value, MessagePackSerializerOptions options) { if (value is null) diff --git a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Resolvers/ExpandoObjectResolver.cs b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Resolvers/ExpandoObjectResolver.cs index ffc7b149c..11107efac 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Resolvers/ExpandoObjectResolver.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Resolvers/ExpandoObjectResolver.cs @@ -40,6 +40,7 @@ private class PrimitiveObjectWithExpandoMaps : PrimitiveObjectFormatter { protected override object DeserializeMap(ref MessagePackReader reader, int length, MessagePackSerializerOptions options) { + ExpandoObjectFormatter.ThrowIfMapTooLargeForUntrustedData(length, options); IMessagePackFormatter keyFormatter = options.Resolver.GetFormatterWithVerify(); IMessagePackFormatter? objectFormatter = options.Resolver.GetFormatterWithVerify(); IDictionary dictionary = new ExpandoObject(); diff --git a/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/ExpandoObjectTests.cs b/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/ExpandoObjectTests.cs index 99f3b808a..9cbf63bb8 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/ExpandoObjectTests.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/ExpandoObjectTests.cs @@ -3,6 +3,7 @@ #if !UNITY_2018_3_OR_NEWER +using System.Collections.Generic; using System.Dynamic; using System.Runtime.Serialization; using MessagePack.Resolvers; @@ -85,6 +86,24 @@ public void ExpandoObject_DeepGraphContainsCustomTypes() Assert.Equal(expando.Other.OtherProperty, expando2.Other.OtherProperty); } + [Fact] + [Trait("CWE", "407")] + public void ExpandoObject_UntrustedDataRejectsLargeMaps() + { + byte[] msgpack = CreateMapWithNilValues(1025); + + Assert.Throws(() => MessagePackSerializer.Deserialize(msgpack, ExpandoObjectResolver.Options)); + } + + [Fact] + [Trait("CWE", "407")] + public void ExpandoObjectNestedMap_UntrustedDataRejectsLargeMaps() + { + byte[] msgpack = CreateMapWithNilValues(1025); + + Assert.Throws(() => MessagePackSerializer.Deserialize(msgpack, ExpandoObjectResolver.Options)); + } + #if !UNITY_2018_3_OR_NEWER [Fact] @@ -115,6 +134,17 @@ public class CustomObject [DataMember] public string OtherProperty { get; set; } } + + private static byte[] CreateMapWithNilValues(int count) + { + var dictionary = new Dictionary(); + for (int index = 0; index < count; index++) + { + dictionary.Add("k" + index.ToString(System.Globalization.CultureInfo.InvariantCulture), null); + } + + return MessagePackSerializer.Serialize(dictionary, MessagePackSerializerOptions.Standard); + } } } From c98d31f2db4c1602fb462bc683d5519e07578246 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Wed, 29 Apr 2026 14:50:01 -0600 Subject: [PATCH 18/32] Default MVC input formatter to UntrustedData for CWE-1188 MessagePackInputFormatter handles HTTP request bodies, but its default/null options path fell back to MessagePackSerializerOptions.Standard and therefore TrustedData. That left hash-based model binding without the untrusted-data collision-resistance defaults expected at this trust boundary. Default null input-formatter options to Standard.WithSecurity(UntrustedData), while preserving caller-supplied options. Add a regression test that verifies the parameterless formatter deserializes dictionary request bodies with the collision-resistant comparer. --- .../MessagePackInputFormatter.cs | 7 +++-- .../AspNetCoreMvcFormatterTest.cs | 29 +++++++++++++++++++ 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/MessagePack.AspNetCoreMvcFormatter/MessagePackInputFormatter.cs b/src/MessagePack.AspNetCoreMvcFormatter/MessagePackInputFormatter.cs index c2038f5a0..f84b4bf7a 100644 --- a/src/MessagePack.AspNetCoreMvcFormatter/MessagePackInputFormatter.cs +++ b/src/MessagePack.AspNetCoreMvcFormatter/MessagePackInputFormatter.cs @@ -9,16 +9,17 @@ namespace MessagePack.AspNetCoreMvcFormatter public class MessagePackInputFormatter : InputFormatter { private const string ContentType = "application/x-msgpack"; - private readonly MessagePackSerializerOptions? options; + private static readonly MessagePackSerializerOptions DefaultOptions = MessagePackSerializerOptions.Standard.WithSecurity(MessagePackSecurity.UntrustedData); + private readonly MessagePackSerializerOptions options; public MessagePackInputFormatter() - : this(null) + : this(DefaultOptions) { } public MessagePackInputFormatter(MessagePackSerializerOptions? options) { - this.options = options; + this.options = options ?? DefaultOptions; SupportedMediaTypes.Add(ContentType); } diff --git a/tests/MessagePack.AspNetCoreMvcFormatter.Tests/AspNetCoreMvcFormatterTest.cs b/tests/MessagePack.AspNetCoreMvcFormatter.Tests/AspNetCoreMvcFormatterTest.cs index 96b54b315..ed2219b1e 100644 --- a/tests/MessagePack.AspNetCoreMvcFormatter.Tests/AspNetCoreMvcFormatterTest.cs +++ b/tests/MessagePack.AspNetCoreMvcFormatter.Tests/AspNetCoreMvcFormatterTest.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; +using System.Collections.Generic; using System.IO; using System.Text; using System.Threading; @@ -195,6 +196,34 @@ public void MessagePackInputFormatterSupportsXMsgPack() inputFormatter.SupportedMediaTypes.Is(MsgPackContentType); } + [Fact] + public async Task MessagePackInputFormatterDefaultsToUntrustedData() + { + const long value1 = 0x100000001; + const long value2 = 0x200000002; + + var messagePackBinary = MessagePackSerializer.Serialize(new Dictionary + { + [value1] = 1, + [value2] = 2, + }); + + var httpContext = new DefaultHttpContext(); + httpContext.Features.Set(new TestResponseFeature()); + httpContext.Request.Body = new NonSeekableReadStream(messagePackBinary); + httpContext.Request.ContentType = MsgPackContentType; + + InputFormatterContext inputFormatterContext = this.CreateInputFormatterContext(typeof(Dictionary), httpContext); + var inputFormatter = new MessagePackInputFormatter(); + + InputFormatterResult result = await inputFormatter.ReadAsync(inputFormatterContext); + + Assert.False(result.HasError); + var dictionary = Assert.IsType>(result.Model); + Assert.Equal(EqualityComparer.Default.GetHashCode(value1), EqualityComparer.Default.GetHashCode(value2)); + Assert.NotEqual(dictionary.Comparer.GetHashCode(value1), dictionary.Comparer.GetHashCode(value2)); + } + /// /// JsonOutputFormatterTests.cs#L453. /// From 696b4a76accd2434a5eb6106255971c382e0c589 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Wed, 13 May 2026 16:30:16 -0600 Subject: [PATCH 19/32] Use iteration for skipping msgpack structures for CWE-674 The `MessagePackReader` has no concept of a depth limit, and it implemented its `Skip()` method recursively which can easily blow the stack for deeply nested msgpack structures. Rather than introduce a new API with an adjustable depth limit, set to a 'secure' but otherwise arbitrary default, we can make `Skip()` iterate instead of recurse in order to avoid ever crashing. --- .../Scripts/MessagePack/MessagePackReader.cs | 195 ++++++++++-------- .../ShareTests/MessagePackReaderTests.cs | 43 ++++ 2 files changed, 153 insertions(+), 85 deletions(-) diff --git a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/MessagePackReader.cs b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/MessagePackReader.cs index d2c630d73..10555cb5b 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/MessagePackReader.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/MessagePackReader.cs @@ -154,81 +154,123 @@ public byte NextCode /// internal bool TrySkip() { - if (this.reader.Remaining == 0) + long remainingStructures = 1; + while (remainingStructures > 0) { - return false; - } + if (this.reader.Remaining == 0) + { + return false; + } - byte code = this.NextCode; - switch (code) - { - case MessagePackCode.Nil: - case MessagePackCode.True: - case MessagePackCode.False: - return this.reader.TryAdvance(1); - case MessagePackCode.Int8: - case MessagePackCode.UInt8: - return this.reader.TryAdvance(2); - case MessagePackCode.Int16: - case MessagePackCode.UInt16: - return this.reader.TryAdvance(3); - case MessagePackCode.Int32: - case MessagePackCode.UInt32: - case MessagePackCode.Float32: - return this.reader.TryAdvance(5); - case MessagePackCode.Int64: - case MessagePackCode.UInt64: - case MessagePackCode.Float64: - return this.reader.TryAdvance(9); - case MessagePackCode.Map16: - case MessagePackCode.Map32: - return this.TrySkipNextMap(); - case MessagePackCode.Array16: - case MessagePackCode.Array32: - return this.TrySkipNextArray(); - case MessagePackCode.Str8: - case MessagePackCode.Str16: - case MessagePackCode.Str32: - return this.TryGetStringLengthInBytes(out int length) && this.reader.TryAdvance(length); - case MessagePackCode.Bin8: - case MessagePackCode.Bin16: - case MessagePackCode.Bin32: - return this.TryGetBytesLength(out length) && this.reader.TryAdvance(length); - case MessagePackCode.FixExt1: - case MessagePackCode.FixExt2: - case MessagePackCode.FixExt4: - case MessagePackCode.FixExt8: - case MessagePackCode.FixExt16: - case MessagePackCode.Ext8: - case MessagePackCode.Ext16: - case MessagePackCode.Ext32: - return this.TryReadExtensionFormatHeader(out ExtensionHeader header) && this.reader.TryAdvance(header.Length); - default: - if ((code >= MessagePackCode.MinNegativeFixInt && code <= MessagePackCode.MaxNegativeFixInt) || - (code >= MessagePackCode.MinFixInt && code <= MessagePackCode.MaxFixInt)) - { - return this.reader.TryAdvance(1); - } + remainingStructures--; + byte code = this.NextCode; + switch (code) + { + case byte x when (x >= MessagePackCode.MinNegativeFixInt && x <= MessagePackCode.MaxNegativeFixInt) || (x >= MessagePackCode.MinFixInt && x <= MessagePackCode.MaxFixInt): + case MessagePackCode.Nil: + case MessagePackCode.True: + case MessagePackCode.False: + if (!this.reader.TryAdvance(1)) + { + return false; + } - if (code >= MessagePackCode.MinFixMap && code <= MessagePackCode.MaxFixMap) - { - return this.TrySkipNextMap(); - } + break; + case MessagePackCode.Int8: + case MessagePackCode.UInt8: + if (!this.reader.TryAdvance(2)) + { + return false; + } - if (code >= MessagePackCode.MinFixArray && code <= MessagePackCode.MaxFixArray) - { - return this.TrySkipNextArray(); - } + break; + case MessagePackCode.Int16: + case MessagePackCode.UInt16: + if (!this.reader.TryAdvance(3)) + { + return false; + } - if (code >= MessagePackCode.MinFixStr && code <= MessagePackCode.MaxFixStr) - { - return this.TryGetStringLengthInBytes(out length) && this.reader.TryAdvance(length); - } + break; + case MessagePackCode.Int32: + case MessagePackCode.UInt32: + case MessagePackCode.Float32: + if (!this.reader.TryAdvance(5)) + { + return false; + } - // We don't actually expect to ever hit this point, since every code is supported. - Debug.Fail("Missing handler for code: " + code); - throw ThrowInvalidCode(code); + break; + case MessagePackCode.Int64: + case MessagePackCode.UInt64: + case MessagePackCode.Float64: + if (!this.reader.TryAdvance(9)) + { + return false; + } + + break; + case byte x when x >= MessagePackCode.MinFixMap && x <= MessagePackCode.MaxFixMap: + case MessagePackCode.Map16: + case MessagePackCode.Map32: + if (!this.TryReadMapHeader(out int count)) + { + return false; + } + + remainingStructures = checked(remainingStructures + ((long)count * 2)); + break; + case byte x when x >= MessagePackCode.MinFixArray && x <= MessagePackCode.MaxFixArray: + case MessagePackCode.Array16: + case MessagePackCode.Array32: + if (!this.TryReadArrayHeader(out count)) + { + return false; + } + + remainingStructures = checked(remainingStructures + count); + break; + case byte x when x >= MessagePackCode.MinFixStr && x <= MessagePackCode.MaxFixStr: + case MessagePackCode.Str8: + case MessagePackCode.Str16: + case MessagePackCode.Str32: + if (!this.TryGetStringLengthInBytes(out int length) || !this.reader.TryAdvance(length)) + { + return false; + } + + break; + case MessagePackCode.Bin8: + case MessagePackCode.Bin16: + case MessagePackCode.Bin32: + if (!this.TryGetBytesLength(out length) || !this.reader.TryAdvance(length)) + { + return false; + } + + break; + case MessagePackCode.FixExt1: + case MessagePackCode.FixExt2: + case MessagePackCode.FixExt4: + case MessagePackCode.FixExt8: + case MessagePackCode.FixExt16: + case MessagePackCode.Ext8: + case MessagePackCode.Ext16: + case MessagePackCode.Ext32: + if (!this.TryReadExtensionFormatHeader(out ExtensionHeader header) || !this.reader.TryAdvance(header.Length)) + { + return false; + } + + break; + default: + // We don't actually expect to ever hit this point, since every code is supported. + Debug.Fail("Missing handler for code: " + code); + throw ThrowInvalidCode(code); + } } + + return true; } /// @@ -1130,22 +1172,5 @@ private string ReadStringSlow(int byteLength) ArrayPool.Shared.Return(charArray); return value; } - - private bool TrySkipNextArray() => this.TryReadArrayHeader(out int count) && this.TrySkip(count); - - private bool TrySkipNextMap() => this.TryReadMapHeader(out int count) && this.TrySkip(count * 2); - - private bool TrySkip(int count) - { - for (int i = 0; i < count; i++) - { - if (!this.TrySkip()) - { - return false; - } - } - - return true; - } } } diff --git a/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/MessagePackReaderTests.cs b/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/MessagePackReaderTests.cs index 774927d13..8234823b5 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/MessagePackReaderTests.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/MessagePackReaderTests.cs @@ -252,6 +252,49 @@ public void ReadRaw() Assert.True(reader.End); } + [Fact] + [Trait("CWE", "674")] + public void Skip_DeeplyNestedArrays_DoesNotOverflowStack() + { + const int depth = 100_000; + byte[] msgpack = new byte[depth + 1]; + + for (int i = 0; i < depth; i++) + { + msgpack[i] = MessagePackCode.MinFixArray + 1; + } + + msgpack[msgpack.Length - 1] = MessagePackCode.Nil; + + MessagePackReader reader = new(msgpack); + + reader.Skip(); + + Assert.True(reader.End); + } + + [Fact] + [Trait("CWE", "674")] + public void Skip_DeeplyNestedMaps_DoesNotOverflowStack() + { + const int depth = 100_000; + byte[] msgpack = new byte[(depth * 2) + 1]; + + for (int i = 0; i < depth; i++) + { + msgpack[i * 2] = MessagePackCode.MinFixMap + 1; + msgpack[(i * 2) + 1] = MessagePackCode.Nil; + } + + msgpack[msgpack.Length - 1] = MessagePackCode.Nil; + + MessagePackReader reader = new(msgpack); + + reader.Skip(); + + Assert.True(reader.End); + } + [Fact] public void Depth() { From 826f17c78f2e21c83425c51257056ec5c29c5ff9 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Wed, 29 Apr 2026 14:06:32 -0600 Subject: [PATCH 20/32] Reject nested typeless blocklist bypass for CWE-502 Typeless deserialization previously validated only the outer wire-supplied type before resolving a formatter. Container types could therefore hide a disallowed element or generic argument from the mitigation and from custom option overrides. Validate element and constructed generic argument types before formatter resolution, and add regression coverage for nested typeless disallowed types. --- .../MessagePackSerializerOptions.cs | 47 +++++++++++++++++-- .../MessagePackSerializerTypelessTests.cs | 21 ++++++++- src/MessagePack/net472/PublicAPI.Shipped.txt | 1 + src/MessagePack/net6.0/PublicAPI.Shipped.txt | 1 + .../netstandard2.0/PublicAPI.Shipped.txt | 1 + 5 files changed, 67 insertions(+), 4 deletions(-) diff --git a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/MessagePackSerializerOptions.cs b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/MessagePackSerializerOptions.cs index dc4598239..18c197f78 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/MessagePackSerializerOptions.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/MessagePackSerializerOptions.cs @@ -175,16 +175,32 @@ protected MessagePackSerializerOptions(MessagePackSerializerOptions copyFrom) /// The type to be instantiated. /// Thrown if the is not allowed to be deserialized. /// + /// /// This method provides a means for an important security mitigation when using the Typeless formatter to prevent untrusted messagepack from /// deserializing objects that may be harmful if instantiated, disposed or finalized. - /// The default implementation throws for only a few known dangerous types. + /// The default implementation throws for only a few known dangerous types, or types that nest those dangerous types as generic type arguments or array element types. /// Applications that deserialize from untrusted sources should override this method and throw if the type is not among the expected set. + /// + /// + /// This method is for backward compatibility reasons. + /// For better security, the preferred method to override is . + /// /// public virtual void ThrowIfDeserializingTypeIsDisallowed(Type type) { - if (type.FullName is string fullName && DisallowedTypes.Contains(fullName)) + this.ThrowIfDeserializingTypeIsDisallowedCore(type); + + if (type.HasElementType && type.GetElementType() is Type elementType) { - throw new MessagePackSerializationException($"Deserialization attempted to create the type {fullName} which is not allowed."); + this.ThrowIfDeserializingTypeIsDisallowed(elementType); + } + + if (type.IsConstructedGenericType) + { + foreach (Type genericTypeArgument in type.GenericTypeArguments) + { + this.ThrowIfDeserializingTypeIsDisallowed(genericTypeArgument); + } } } @@ -361,6 +377,31 @@ public MessagePackSerializerOptions WithPool(SequencePool pool) return result; } + /// + /// Checks whether a specific given type may be deserialized, disregarding generic type arguments or array element types. + /// + /// The type to be instantiated. + /// Thrown if the is not allowed to be deserialized. + /// + /// + /// This method provides a means for an important security mitigation when using the Typeless formatter to prevent untrusted messagepack from + /// deserializing objects that may be harmful if instantiated, disposed or finalized. + /// The default implementation throws for only a few known dangerous types. + /// Applications that deserialize from untrusted sources should override this method and throw if the type is not among the expected set. + /// + /// + /// This method is called from the default implementation of + /// for the top-level type and again for each generic type argument or array element type. + /// + /// + protected virtual void ThrowIfDeserializingTypeIsDisallowedCore(Type type) + { + if (type.FullName is string fullName && DisallowedTypes.Contains(fullName)) + { + throw new MessagePackSerializationException($"Deserialization attempted to create the type {fullName} which is not allowed."); + } + } + /// /// Creates a clone of this instance with the same properties set. /// diff --git a/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/MessagePackSerializerTypelessTests.cs b/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/MessagePackSerializerTypelessTests.cs index 995aaa6c2..9243854f6 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/MessagePackSerializerTypelessTests.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/MessagePackSerializerTypelessTests.cs @@ -4,6 +4,7 @@ #if !UNITY_2018_3_OR_NEWER using System; +using System.Collections.Generic; using System.Runtime.Serialization; using MessagePack; using MessagePack.Formatters; @@ -47,6 +48,24 @@ public void SerializationOfDisallowedType() Assert.IsType(ex.InnerException); } + [Theory] + [MemberData(nameof(DisallowedNestedTypeData))] + [Trait("CWE", "502")] + public void SerializationOfDisallowedNestedType(object value) + { + var myOptions = new MyTypelessOptions(); + byte[] msgpack = MessagePackSerializer.Typeless.Serialize(value, myOptions); + this.logger.WriteLine(MessagePackSerializer.ConvertToJson(msgpack, myOptions)); + var ex = Assert.Throws(() => MessagePackSerializer.Typeless.Deserialize(msgpack, myOptions)); + Assert.IsType(ex.InnerException); + } + + public static IEnumerable DisallowedNestedTypeData() + { + yield return new object[] { new MyObject[] { new() { SomeValue = 5 } } }; + yield return new object[] { new List { new() { SomeValue = 5 } } }; + } + [Fact(Skip = "Known bug https://github.com/neuecc/MessagePack-CSharp/issues/651")] public void DecimalShouldBeDeserializedAsDecimal() { @@ -137,7 +156,7 @@ internal MyTypelessOptions(MyTypelessOptions copyFrom) { } - public override void ThrowIfDeserializingTypeIsDisallowed(Type type) + protected override void ThrowIfDeserializingTypeIsDisallowedCore(Type type) { if (type == typeof(MyObject)) { diff --git a/src/MessagePack/net472/PublicAPI.Shipped.txt b/src/MessagePack/net472/PublicAPI.Shipped.txt index 2893efd85..487632817 100644 --- a/src/MessagePack/net472/PublicAPI.Shipped.txt +++ b/src/MessagePack/net472/PublicAPI.Shipped.txt @@ -1183,3 +1183,4 @@ MessagePack.MessagePackSerializerOptions.WithCompressionMinLength(int compressio MessagePack.MessagePackSerializerOptions.WithSuggestedContiguousMemorySize(int suggestedContiguousMemorySize) -> MessagePack.MessagePackSerializerOptions! static MessagePack.MessagePackWriter.GetEncodedLength(long value) -> int static MessagePack.MessagePackWriter.GetEncodedLength(ulong value) -> int +virtual MessagePack.MessagePackSerializerOptions.ThrowIfDeserializingTypeIsDisallowedCore(System.Type! type) -> void diff --git a/src/MessagePack/net6.0/PublicAPI.Shipped.txt b/src/MessagePack/net6.0/PublicAPI.Shipped.txt index 2c67e9468..fd4520972 100644 --- a/src/MessagePack/net6.0/PublicAPI.Shipped.txt +++ b/src/MessagePack/net6.0/PublicAPI.Shipped.txt @@ -1197,3 +1197,4 @@ static MessagePack.MessagePackWriter.GetEncodedLength(long value) -> int static MessagePack.MessagePackWriter.GetEncodedLength(ulong value) -> int static readonly MessagePack.Formatters.DateOnlyFormatter.Instance -> MessagePack.Formatters.DateOnlyFormatter! static readonly MessagePack.Formatters.TimeOnlyFormatter.Instance -> MessagePack.Formatters.TimeOnlyFormatter! +virtual MessagePack.MessagePackSerializerOptions.ThrowIfDeserializingTypeIsDisallowedCore(System.Type! type) -> void diff --git a/src/MessagePack/netstandard2.0/PublicAPI.Shipped.txt b/src/MessagePack/netstandard2.0/PublicAPI.Shipped.txt index 2893efd85..487632817 100644 --- a/src/MessagePack/netstandard2.0/PublicAPI.Shipped.txt +++ b/src/MessagePack/netstandard2.0/PublicAPI.Shipped.txt @@ -1183,3 +1183,4 @@ MessagePack.MessagePackSerializerOptions.WithCompressionMinLength(int compressio MessagePack.MessagePackSerializerOptions.WithSuggestedContiguousMemorySize(int suggestedContiguousMemorySize) -> MessagePack.MessagePackSerializerOptions! static MessagePack.MessagePackWriter.GetEncodedLength(long value) -> int static MessagePack.MessagePackWriter.GetEncodedLength(ulong value) -> int +virtual MessagePack.MessagePackSerializerOptions.ThrowIfDeserializingTypeIsDisallowedCore(System.Type! type) -> void From 853429a0f438fcdc8a7ee3c158ee838f12a5be48 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Wed, 29 Apr 2026 14:46:53 -0600 Subject: [PATCH 21/32] Guard LZ4 decompression length for CWE-409 Validate declared LZ4 decompressed block sizes against the compressed block size before requesting an output buffer. This rejects unreasonable Lz4Block and Lz4BlockArray declarations before allocation while preserving normal compressed payload handling. --- .../MessagePack/MessagePackSecurity.cs | 32 +++++++ .../MessagePack/MessagePackSerializer.Json.cs | 2 +- .../MessagePack/MessagePackSerializer.cs | 16 +++- .../Scripts/Tests/ShareTests/LZ4Test.cs | 91 +++++++++++++++++++ .../ShareTests/MessagePackSecurityTests.cs | 11 +++ src/MessagePack/net472/PublicAPI.Shipped.txt | 2 + src/MessagePack/net6.0/PublicAPI.Shipped.txt | 2 + .../netstandard2.0/PublicAPI.Shipped.txt | 2 + 8 files changed, 155 insertions(+), 3 deletions(-) diff --git a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/MessagePackSecurity.cs b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/MessagePackSecurity.cs index 3a6715299..9d8a71d5c 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/MessagePackSecurity.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/MessagePackSecurity.cs @@ -19,6 +19,8 @@ namespace MessagePack /// public class MessagePackSecurity { + private const int DefaultUntrustedDataMaximumDecompressedSize = 64 * 1024 * 1024; + /// /// Gets an instance preconfigured with settings that omit all protections. Useful for deserializing fully-trusted and valid msgpack sequences. /// @@ -31,6 +33,7 @@ public class MessagePackSecurity { HashCollisionResistant = true, MaximumObjectGraphDepth = 500, + MaximumDecompressedSize = DefaultUntrustedDataMaximumDecompressedSize, }; private static readonly SipHash Hash = new(); @@ -57,6 +60,7 @@ protected MessagePackSecurity(MessagePackSecurity copyFrom) this.HashCollisionResistant = copyFrom.HashCollisionResistant; this.MaximumObjectGraphDepth = copyFrom.MaximumObjectGraphDepth; + this.MaximumDecompressedSize = copyFrom.MaximumDecompressedSize; } /// @@ -81,6 +85,12 @@ protected MessagePackSecurity(MessagePackSecurity copyFrom) /// public int MaximumObjectGraphDepth { get; private set; } = int.MaxValue; + /// + /// Gets the maximum decompressed size in bytes allowed when deserializing compressed payloads. + /// + /// The default value is for and 64MB for . + public int MaximumDecompressedSize { get; private set; } = int.MaxValue; + /// /// Gets a copy of these options with the property set to a new value. /// @@ -98,6 +108,28 @@ public MessagePackSecurity WithMaximumObjectGraphDepth(int maximumObjectGraphDep return clone; } + /// + /// Gets a copy of these options with the property set to a new value. + /// + /// The new value for the property. Must not be negative. + /// The new instance; or the original if the value is unchanged. + public MessagePackSecurity WithMaximumDecompressedSize(int maximumDecompressedSize) + { + if (this.MaximumDecompressedSize == maximumDecompressedSize) + { + return this; + } + + if (maximumDecompressedSize < 0) + { + throw new ArgumentOutOfRangeException(nameof(maximumDecompressedSize)); + } + + var clone = this.Clone(); + clone.MaximumDecompressedSize = maximumDecompressedSize; + return clone; + } + /// /// Gets a copy of these options with the property set to a new value. /// diff --git a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/MessagePackSerializer.Json.cs b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/MessagePackSerializer.Json.cs index b9d02ac62..34fbdf0e9 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/MessagePackSerializer.Json.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/MessagePackSerializer.Json.cs @@ -89,7 +89,7 @@ public static void ConvertToJson(ref MessagePackReader reader, TextWriter jsonWr { using (var scratchRental = options.SequencePool.Rent()) { - if (TryDecompress(ref reader, scratchRental.Value)) + if (TryDecompress(ref reader, scratchRental.Value, options)) { var scratchReader = new MessagePackReader(scratchRental.Value) { diff --git a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/MessagePackSerializer.cs b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/MessagePackSerializer.cs index a75917b0b..8d11b3122 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/MessagePackSerializer.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/MessagePackSerializer.cs @@ -234,7 +234,7 @@ public static T Deserialize(ref MessagePackReader reader, MessagePackSerializ using (var msgPackUncompressedRental = options.SequencePool.Rent()) { var msgPackUncompressed = msgPackUncompressedRental.Value; - if (TryDecompress(ref reader, msgPackUncompressed)) + if (TryDecompress(ref reader, msgPackUncompressed, options)) { MessagePackReader uncompressedReader = reader.Clone(msgPackUncompressed.AsReadOnlySequence); return options.Resolver.GetFormatterWithVerify().Deserialize(ref uncompressedReader, options); @@ -482,7 +482,7 @@ private static int LZ4Operation(in ReadOnlySequence input, Span outp } } - private static bool TryDecompress(ref MessagePackReader reader, IBufferWriter writer) + private static bool TryDecompress(ref MessagePackReader reader, IBufferWriter writer, MessagePackSerializerOptions options) { if (!reader.End) { @@ -504,6 +504,7 @@ private static bool TryDecompress(ref MessagePackReader reader, IBufferWriter compressedData = extReader.Sequence.Slice(extReader.Position); + ThrowIfInvalidLz4BlockLength(uncompressedLength, options.Security.MaximumDecompressedSize); Span uncompressedSpan = writer.GetSpan(uncompressedLength).Slice(0, uncompressedLength); int actualUncompressedLength = LZ4Operation(compressedData, uncompressedSpan, LZ4CodecDecode); Debug.Assert(actualUncompressedLength == uncompressedLength, "Unexpected length of uncompressed data."); @@ -530,6 +531,7 @@ private static bool TryDecompress(ref MessagePackReader reader, IBufferWriter.Shared.Rent(sequenceCount); try { + long remainingMaxDecompressedSize = options.Security.MaximumDecompressedSize; for (int i = 0; i < sequenceCount; i++) { uncompressedLengths[i] = reader.ReadInt32(); @@ -539,6 +541,8 @@ private static bool TryDecompress(ref MessagePackReader reader, IBufferWriter lz4Block = reader.ReadBytes() ?? throw MessagePackSerializationException.ThrowUnexpectedNilWhileDeserializing>(); + ThrowIfInvalidLz4BlockLength(uncompressedLength, remainingMaxDecompressedSize); + remainingMaxDecompressedSize -= uncompressedLength; Span uncompressedSpan = writer.GetSpan(uncompressedLength).Slice(0, uncompressedLength); var actualUncompressedLength = LZ4Operation(lz4Block, uncompressedSpan, LZ4CodecDecode); Debug.Assert(actualUncompressedLength == uncompressedLength, "Unexpected length of uncompressed data."); @@ -559,6 +563,14 @@ private static bool TryDecompress(ref MessagePackReader reader, IBufferWriter remainingMaxDecompressedSize) + { + throw new MessagePackSerializationException("LZ4 block declares a decompressed length that exceeds the configured maximum."); + } + } + private static void ToLZ4BinaryCore(in ReadOnlySequence msgpackUncompressedData, ref MessagePackWriter writer, MessagePackCompression compression, int minCompressionSize) { if (msgpackUncompressedData.Length < minCompressionSize) diff --git a/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/LZ4Test.cs b/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/LZ4Test.cs index f314b572a..d31092e3b 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/LZ4Test.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/LZ4Test.cs @@ -1,6 +1,7 @@ // Copyright (c) All contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System.Buffers; using System.Linq; using Xunit; using Xunit.Abstractions; @@ -22,6 +23,64 @@ public LZ4Test(ITestOutputHelper logger) #endif + [Theory] + [InlineData(MessagePackCompression.Lz4Block)] + [InlineData(MessagePackCompression.Lz4BlockArray)] + [Trait("CWE", "409")] + public void Lz4RejectsDeclaredOutputOverMaximumBeforeAllocating(MessagePackCompression compression) + { + byte[] payload = compression == MessagePackCompression.Lz4Block + ? new byte[] { 0xC7, 0x06, 0x63, 0xD2, 0x7F, 0xFF, 0xFF, 0xFF, 0x00 } + : new byte[] { 0x92, 0xC7, 0x05, 0x62, 0xD2, 0x7F, 0xFF, 0xFF, 0xFF, 0xC4, 0x01, 0x00 }; + var arrayPool = new ThrowingArrayPool(1024); + var options = MessagePackSerializerOptions.Standard + .WithCompression(compression) + .WithSecurity(MessagePackSecurity.UntrustedData) + .WithPool(new SequencePool(1, arrayPool)); + + MessagePackSerializationException ex = Assert.Throws(() => MessagePackSerializer.Deserialize(payload, options)); + + Assert.Contains("exceeds the configured maximum", FlattenMessages(ex)); + Assert.Null(arrayPool.LargestRequestedLength); + } + + [Fact] + [Trait("CWE", "409")] + public void Lz4BlockArrayRejectsTotalDeclaredOutputOverMaximum() + { + byte[] payload = + { + 0x93, + 0xC7, 0x02, 0x62, 0x04, 0x04, + 0xC4, 0x05, 0x40, 0x20, 0x20, 0x20, 0x20, + 0xC4, 0x05, 0x40, 0x20, 0x20, 0x20, 0x20, + }; + var options = MessagePackSerializerOptions.Standard + .WithCompression(MessagePackCompression.Lz4BlockArray) + .WithSecurity(MessagePackSecurity.UntrustedData.WithMaximumDecompressedSize(7)); + + MessagePackSerializationException ex = Assert.Throws(() => MessagePackSerializer.Deserialize(payload, options)); + + Assert.Contains("exceeds the configured maximum", FlattenMessages(ex)); + } + + [Theory] + [InlineData(MessagePackCompression.Lz4Block)] + [InlineData(MessagePackCompression.Lz4BlockArray)] + [Trait("CWE", "409")] + public void Lz4AllowsHighlyCompressiblePayloadWithinMaximum(MessagePackCompression compression) + { + string data = new string(' ', 100_000); + var options = MessagePackSerializerOptions.Standard + .WithCompression(compression) + .WithSecurity(MessagePackSecurity.UntrustedData.WithMaximumDecompressedSize(200_000)); + + byte[] payload = MessagePackSerializer.Serialize(data, options); + string actual = MessagePackSerializer.Deserialize(payload, options); + + Assert.Equal(data, actual); + } + [Fact] public void Lz4Compress() { @@ -82,5 +141,37 @@ private static void SequenceStructuralEqual(SharedData.SimpleStringKeyData[] act actual[i].Prop3.Is(expected[i].Prop3); } } + + private static string FlattenMessages(System.Exception ex) + { + return ex.InnerException is null ? ex.Message : ex.Message + " " + FlattenMessages(ex.InnerException); + } + + private class ThrowingArrayPool : ArrayPool + { + private readonly int maximumLength; + + internal ThrowingArrayPool(int maximumLength) + { + this.maximumLength = maximumLength; + } + + internal int? LargestRequestedLength { get; private set; } + + public override byte[] Rent(int minimumLength) + { + this.LargestRequestedLength = this.LargestRequestedLength.HasValue ? System.Math.Max(this.LargestRequestedLength.Value, minimumLength) : minimumLength; + if (minimumLength > this.maximumLength) + { + throw new System.InvalidOperationException("Unexpected decompression allocation request: " + minimumLength); + } + + return new byte[minimumLength]; + } + + public override void Return(byte[] array, bool clearArray = false) + { + } + } } } diff --git a/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/MessagePackSecurityTests.cs b/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/MessagePackSecurityTests.cs index 74121e5d7..ef1cd3f4d 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/MessagePackSecurityTests.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/MessagePackSecurityTests.cs @@ -31,12 +31,14 @@ public MessagePackSecurityTests(ITestOutputHelper logger) public void Untrusted() { Assert.True(MessagePackSecurity.UntrustedData.HashCollisionResistant); + Assert.Equal(64 * 1024 * 1024, MessagePackSecurity.UntrustedData.MaximumDecompressedSize); } [Fact] public void Trusted() { Assert.False(MessagePackSecurity.TrustedData.HashCollisionResistant); + Assert.Equal(int.MaxValue, MessagePackSecurity.TrustedData.MaximumDecompressedSize); } [Fact] @@ -46,6 +48,15 @@ public void WithHashCollisionResistant() Assert.True(MessagePackSecurity.TrustedData.WithHashCollisionResistant(true).HashCollisionResistant); } + [Fact] + [Trait("CWE", "409")] + public void WithMaximumDecompressedSize() + { + Assert.Same(MessagePackSecurity.UntrustedData, MessagePackSecurity.UntrustedData.WithMaximumDecompressedSize(64 * 1024 * 1024)); + Assert.Throws(() => MessagePackSecurity.UntrustedData.WithMaximumDecompressedSize(-1)); + Assert.Equal(1024, MessagePackSecurity.UntrustedData.WithMaximumDecompressedSize(1024).MaximumDecompressedSize); + } + [Fact] public void EqualityComparer_CollisionResistance_Int64() { diff --git a/src/MessagePack/net472/PublicAPI.Shipped.txt b/src/MessagePack/net472/PublicAPI.Shipped.txt index 2893efd85..5f2428685 100644 --- a/src/MessagePack/net472/PublicAPI.Shipped.txt +++ b/src/MessagePack/net472/PublicAPI.Shipped.txt @@ -1177,6 +1177,8 @@ MessagePack.Formatters.StringInterningFormatter MessagePack.Formatters.StringInterningFormatter.Deserialize(ref MessagePack.MessagePackReader reader, MessagePack.MessagePackSerializerOptions! options) -> string? MessagePack.Formatters.StringInterningFormatter.Serialize(ref MessagePack.MessagePackWriter writer, string? value, MessagePack.MessagePackSerializerOptions! options) -> void MessagePack.Formatters.StringInterningFormatter.StringInterningFormatter() -> void +MessagePack.MessagePackSecurity.MaximumDecompressedSize.get -> int +MessagePack.MessagePackSecurity.WithMaximumDecompressedSize(int maximumDecompressedSize) -> MessagePack.MessagePackSecurity! MessagePack.MessagePackSerializerOptions.CompressionMinLength.get -> int MessagePack.MessagePackSerializerOptions.SuggestedContiguousMemorySize.get -> int MessagePack.MessagePackSerializerOptions.WithCompressionMinLength(int compressionMinLength) -> MessagePack.MessagePackSerializerOptions! diff --git a/src/MessagePack/net6.0/PublicAPI.Shipped.txt b/src/MessagePack/net6.0/PublicAPI.Shipped.txt index 2c67e9468..fcf21bd70 100644 --- a/src/MessagePack/net6.0/PublicAPI.Shipped.txt +++ b/src/MessagePack/net6.0/PublicAPI.Shipped.txt @@ -1189,6 +1189,8 @@ MessagePack.Formatters.StringInterningFormatter.StringInterningFormatter() -> vo MessagePack.Formatters.TimeOnlyFormatter MessagePack.Formatters.TimeOnlyFormatter.Deserialize(ref MessagePack.MessagePackReader reader, MessagePack.MessagePackSerializerOptions! options) -> System.TimeOnly MessagePack.Formatters.TimeOnlyFormatter.Serialize(ref MessagePack.MessagePackWriter writer, System.TimeOnly value, MessagePack.MessagePackSerializerOptions! options) -> void +MessagePack.MessagePackSecurity.MaximumDecompressedSize.get -> int +MessagePack.MessagePackSecurity.WithMaximumDecompressedSize(int maximumDecompressedSize) -> MessagePack.MessagePackSecurity! MessagePack.MessagePackSerializerOptions.CompressionMinLength.get -> int MessagePack.MessagePackSerializerOptions.SuggestedContiguousMemorySize.get -> int MessagePack.MessagePackSerializerOptions.WithCompressionMinLength(int compressionMinLength) -> MessagePack.MessagePackSerializerOptions! diff --git a/src/MessagePack/netstandard2.0/PublicAPI.Shipped.txt b/src/MessagePack/netstandard2.0/PublicAPI.Shipped.txt index 2893efd85..5f2428685 100644 --- a/src/MessagePack/netstandard2.0/PublicAPI.Shipped.txt +++ b/src/MessagePack/netstandard2.0/PublicAPI.Shipped.txt @@ -1177,6 +1177,8 @@ MessagePack.Formatters.StringInterningFormatter MessagePack.Formatters.StringInterningFormatter.Deserialize(ref MessagePack.MessagePackReader reader, MessagePack.MessagePackSerializerOptions! options) -> string? MessagePack.Formatters.StringInterningFormatter.Serialize(ref MessagePack.MessagePackWriter writer, string? value, MessagePack.MessagePackSerializerOptions! options) -> void MessagePack.Formatters.StringInterningFormatter.StringInterningFormatter() -> void +MessagePack.MessagePackSecurity.MaximumDecompressedSize.get -> int +MessagePack.MessagePackSecurity.WithMaximumDecompressedSize(int maximumDecompressedSize) -> MessagePack.MessagePackSecurity! MessagePack.MessagePackSerializerOptions.CompressionMinLength.get -> int MessagePack.MessagePackSerializerOptions.SuggestedContiguousMemorySize.get -> int MessagePack.MessagePackSerializerOptions.WithCompressionMinLength(int compressionMinLength) -> MessagePack.MessagePackSerializerOptions! From b0f8c5e223418db0567f44d3c1dc652d9c51431e Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Thu, 14 May 2026 14:39:43 -0600 Subject: [PATCH 22/32] Fix WriteRawX methods to advance by written length Before this change, the length of the source span would dictate how large the destination span became and how far we advanced the writer, without any regard to how many bytes would actually be copied into that buffer. --- .../MessagePack/Internal/UnsafeMemory.Low.cs | 56 +++-- .../Scripts/MessagePack/T4/UnsafeMemory.cs | 208 +++++++++--------- .../Tests/ShareTests/UnsafeMemoryTest.cs | 34 ++- src/MessagePack/Internal/UnsafeMemory.cs | 208 +++++++++--------- src/MessagePack/Internal/UnsafeMemory.tt | 8 +- 5 files changed, 277 insertions(+), 237 deletions(-) diff --git a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Internal/UnsafeMemory.Low.cs b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Internal/UnsafeMemory.Low.cs index 1e6627029..07690fd8e 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Internal/UnsafeMemory.Low.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Internal/UnsafeMemory.Low.cs @@ -17,12 +17,20 @@ public static class UnsafeMemory public static readonly bool Is32Bit = IntPtr.Size == 4; } + /// + /// Highly tuned method for writing raw bytes to a . + /// + /// + /// The methods on this class are not safe, in that they use pointer arithmetic + /// and assume that the caller has provided a with a length + /// of at least the number of bytes being written. The caller must ensure that this is the case. + /// public static partial class UnsafeMemory32 { [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw1(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(1); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -30,13 +38,13 @@ public static unsafe void WriteRaw1(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(2); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -44,13 +52,13 @@ public static unsafe void WriteRaw2(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(3); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -59,16 +67,24 @@ public static unsafe void WriteRaw3(ref MessagePackWriter writer, ReadOnlySpan + /// Highly tuned method for writing raw bytes to a . + /// + /// + /// The methods on this class are not safe, in that they use pointer arithmetic + /// and assume that the caller has provided a with a length + /// of at least the number of bytes being written. The caller must ensure that this is the case. + /// public static partial class UnsafeMemory64 { [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw1(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(1); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -76,13 +92,13 @@ public static unsafe void WriteRaw1(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(2); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -90,13 +106,13 @@ public static unsafe void WriteRaw2(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(3); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -105,13 +121,13 @@ public static unsafe void WriteRaw3(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(4); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -119,13 +135,13 @@ public static unsafe void WriteRaw4(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(5); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -134,13 +150,13 @@ public static unsafe void WriteRaw5(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(6); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -149,13 +165,13 @@ public static unsafe void WriteRaw6(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(7); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -164,7 +180,7 @@ public static unsafe void WriteRaw7(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(4); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -28,13 +28,13 @@ public static unsafe void WriteRaw4(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(5); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -43,13 +43,13 @@ public static unsafe void WriteRaw5(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(6); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -58,13 +58,13 @@ public static unsafe void WriteRaw6(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(7); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -73,13 +73,13 @@ public static unsafe void WriteRaw7(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(8); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -88,13 +88,13 @@ public static unsafe void WriteRaw8(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(9); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -104,13 +104,13 @@ public static unsafe void WriteRaw9(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(10); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -120,13 +120,13 @@ public static unsafe void WriteRaw10(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 6) = *(int*)(pSrc + 6); } - writer.Advance(src.Length); + writer.Advance(10); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw11(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(11); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -136,13 +136,13 @@ public static unsafe void WriteRaw11(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 7) = *(int*)(pSrc + 7); } - writer.Advance(src.Length); + writer.Advance(11); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw12(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(12); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -152,13 +152,13 @@ public static unsafe void WriteRaw12(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 8) = *(int*)(pSrc + 8); } - writer.Advance(src.Length); + writer.Advance(12); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw13(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(13); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -169,13 +169,13 @@ public static unsafe void WriteRaw13(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 9) = *(int*)(pSrc + 9); } - writer.Advance(src.Length); + writer.Advance(13); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw14(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(14); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -186,13 +186,13 @@ public static unsafe void WriteRaw14(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 10) = *(int*)(pSrc + 10); } - writer.Advance(src.Length); + writer.Advance(14); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw15(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(15); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -203,13 +203,13 @@ public static unsafe void WriteRaw15(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 11) = *(int*)(pSrc + 11); } - writer.Advance(src.Length); + writer.Advance(15); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw16(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(16); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -220,13 +220,13 @@ public static unsafe void WriteRaw16(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 12) = *(int*)(pSrc + 12); } - writer.Advance(src.Length); + writer.Advance(16); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw17(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(17); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -238,13 +238,13 @@ public static unsafe void WriteRaw17(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 13) = *(int*)(pSrc + 13); } - writer.Advance(src.Length); + writer.Advance(17); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw18(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(18); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -256,13 +256,13 @@ public static unsafe void WriteRaw18(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 14) = *(int*)(pSrc + 14); } - writer.Advance(src.Length); + writer.Advance(18); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw19(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(19); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -274,13 +274,13 @@ public static unsafe void WriteRaw19(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 15) = *(int*)(pSrc + 15); } - writer.Advance(src.Length); + writer.Advance(19); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw20(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(20); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -292,13 +292,13 @@ public static unsafe void WriteRaw20(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 16) = *(int*)(pSrc + 16); } - writer.Advance(src.Length); + writer.Advance(20); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw21(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(21); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -311,13 +311,13 @@ public static unsafe void WriteRaw21(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 17) = *(int*)(pSrc + 17); } - writer.Advance(src.Length); + writer.Advance(21); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw22(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(22); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -330,13 +330,13 @@ public static unsafe void WriteRaw22(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 18) = *(int*)(pSrc + 18); } - writer.Advance(src.Length); + writer.Advance(22); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw23(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(23); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -349,13 +349,13 @@ public static unsafe void WriteRaw23(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 19) = *(int*)(pSrc + 19); } - writer.Advance(src.Length); + writer.Advance(23); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw24(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(24); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -368,13 +368,13 @@ public static unsafe void WriteRaw24(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 20) = *(int*)(pSrc + 20); } - writer.Advance(src.Length); + writer.Advance(24); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw25(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(25); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -388,13 +388,13 @@ public static unsafe void WriteRaw25(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 21) = *(int*)(pSrc + 21); } - writer.Advance(src.Length); + writer.Advance(25); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw26(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(26); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -408,13 +408,13 @@ public static unsafe void WriteRaw26(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 22) = *(int*)(pSrc + 22); } - writer.Advance(src.Length); + writer.Advance(26); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw27(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(27); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -428,13 +428,13 @@ public static unsafe void WriteRaw27(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 23) = *(int*)(pSrc + 23); } - writer.Advance(src.Length); + writer.Advance(27); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw28(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(28); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -448,13 +448,13 @@ public static unsafe void WriteRaw28(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 24) = *(int*)(pSrc + 24); } - writer.Advance(src.Length); + writer.Advance(28); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw29(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(29); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -469,13 +469,13 @@ public static unsafe void WriteRaw29(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 25) = *(int*)(pSrc + 25); } - writer.Advance(src.Length); + writer.Advance(29); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw30(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(30); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -490,13 +490,13 @@ public static unsafe void WriteRaw30(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 26) = *(int*)(pSrc + 26); } - writer.Advance(src.Length); + writer.Advance(30); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw31(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(31); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -511,7 +511,7 @@ public static unsafe void WriteRaw31(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 27) = *(int*)(pSrc + 27); } - writer.Advance(src.Length); + writer.Advance(31); } } @@ -520,7 +520,7 @@ public static partial class UnsafeMemory64 [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw8(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(8); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -528,13 +528,13 @@ public static unsafe void WriteRaw8(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(9); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -543,13 +543,13 @@ public static unsafe void WriteRaw9(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(10); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -558,13 +558,13 @@ public static unsafe void WriteRaw10(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 2) = *(long*)(pSrc + 2); } - writer.Advance(src.Length); + writer.Advance(10); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw11(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(11); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -573,13 +573,13 @@ public static unsafe void WriteRaw11(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 3) = *(long*)(pSrc + 3); } - writer.Advance(src.Length); + writer.Advance(11); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw12(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(12); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -588,13 +588,13 @@ public static unsafe void WriteRaw12(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 4) = *(long*)(pSrc + 4); } - writer.Advance(src.Length); + writer.Advance(12); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw13(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(13); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -603,13 +603,13 @@ public static unsafe void WriteRaw13(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 5) = *(long*)(pSrc + 5); } - writer.Advance(src.Length); + writer.Advance(13); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw14(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(14); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -618,13 +618,13 @@ public static unsafe void WriteRaw14(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 6) = *(long*)(pSrc + 6); } - writer.Advance(src.Length); + writer.Advance(14); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw15(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(15); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -633,13 +633,13 @@ public static unsafe void WriteRaw15(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 7) = *(long*)(pSrc + 7); } - writer.Advance(src.Length); + writer.Advance(15); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw16(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(16); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -648,13 +648,13 @@ public static unsafe void WriteRaw16(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 8) = *(long*)(pSrc + 8); } - writer.Advance(src.Length); + writer.Advance(16); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw17(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(17); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -664,13 +664,13 @@ public static unsafe void WriteRaw17(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 9) = *(long*)(pSrc + 9); } - writer.Advance(src.Length); + writer.Advance(17); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw18(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(18); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -680,13 +680,13 @@ public static unsafe void WriteRaw18(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 10) = *(long*)(pSrc + 10); } - writer.Advance(src.Length); + writer.Advance(18); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw19(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(19); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -696,13 +696,13 @@ public static unsafe void WriteRaw19(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 11) = *(long*)(pSrc + 11); } - writer.Advance(src.Length); + writer.Advance(19); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw20(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(20); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -712,13 +712,13 @@ public static unsafe void WriteRaw20(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 12) = *(long*)(pSrc + 12); } - writer.Advance(src.Length); + writer.Advance(20); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw21(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(21); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -728,13 +728,13 @@ public static unsafe void WriteRaw21(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 13) = *(long*)(pSrc + 13); } - writer.Advance(src.Length); + writer.Advance(21); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw22(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(22); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -744,13 +744,13 @@ public static unsafe void WriteRaw22(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 14) = *(long*)(pSrc + 14); } - writer.Advance(src.Length); + writer.Advance(22); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw23(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(23); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -760,13 +760,13 @@ public static unsafe void WriteRaw23(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 15) = *(long*)(pSrc + 15); } - writer.Advance(src.Length); + writer.Advance(23); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw24(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(24); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -776,13 +776,13 @@ public static unsafe void WriteRaw24(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 16) = *(long*)(pSrc + 16); } - writer.Advance(src.Length); + writer.Advance(24); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw25(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(25); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -793,13 +793,13 @@ public static unsafe void WriteRaw25(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 17) = *(long*)(pSrc + 17); } - writer.Advance(src.Length); + writer.Advance(25); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw26(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(26); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -810,13 +810,13 @@ public static unsafe void WriteRaw26(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 18) = *(long*)(pSrc + 18); } - writer.Advance(src.Length); + writer.Advance(26); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw27(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(27); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -827,13 +827,13 @@ public static unsafe void WriteRaw27(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 19) = *(long*)(pSrc + 19); } - writer.Advance(src.Length); + writer.Advance(27); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw28(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(28); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -844,13 +844,13 @@ public static unsafe void WriteRaw28(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 20) = *(long*)(pSrc + 20); } - writer.Advance(src.Length); + writer.Advance(28); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw29(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(29); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -861,13 +861,13 @@ public static unsafe void WriteRaw29(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 21) = *(long*)(pSrc + 21); } - writer.Advance(src.Length); + writer.Advance(29); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw30(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(30); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -878,13 +878,13 @@ public static unsafe void WriteRaw30(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 22) = *(long*)(pSrc + 22); } - writer.Advance(src.Length); + writer.Advance(30); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw31(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(31); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -895,7 +895,7 @@ public static unsafe void WriteRaw31(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 23) = *(long*)(pSrc + 23); } - writer.Advance(src.Length); + writer.Advance(31); } } } diff --git a/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/UnsafeMemoryTest.cs b/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/UnsafeMemoryTest.cs index c9b5f023e..14002930b 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/UnsafeMemoryTest.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/UnsafeMemoryTest.cs @@ -3,12 +3,7 @@ using System; using System.Buffers; -using System.Collections.Generic; using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; -using MessagePack.Formatters; using MessagePack.Internal; using Nerdbank.Streams; using Xunit; @@ -70,6 +65,35 @@ public void WriteRaw() } } + [Fact] + public void WriteRaw_LargerSpan() + { + ReadOnlySpan src = new byte[MessagePackRange.MaxFixStringLength + 15]; + Sequence dst = new(); + + // x86 + for (int i = 1; i <= MessagePackRange.MaxFixStringLength; i++) + { + dst.Reset(); + MessagePackWriter dstWriter = new(dst); + (typeof(UnsafeMemory32).GetMethod("WriteRaw" + i).CreateDelegate(typeof(WriteDelegate)) as WriteDelegate).Invoke(ref dstWriter, src); + dstWriter.Flush(); + dst.Length.Is(i); + src.Slice(0, i).SequenceEqual(CodeGenHelpers.GetSpanFromSequence(dst.AsReadOnlySequence)).IsTrue(); + } + + // x64 + for (int i = 1; i <= MessagePackRange.MaxFixStringLength; i++) + { + dst.Reset(); + var dstWriter = new MessagePackWriter(dst); + (typeof(UnsafeMemory64).GetMethod("WriteRaw" + i).CreateDelegate(typeof(WriteDelegate)) as WriteDelegate).Invoke(ref dstWriter, src); + dstWriter.Flush(); + dst.Length.Is(i); + src.Slice(0, i).SequenceEqual(CodeGenHelpers.GetSpanFromSequence(dst.AsReadOnlySequence)).IsTrue(); + } + } + #endif } } diff --git a/src/MessagePack/Internal/UnsafeMemory.cs b/src/MessagePack/Internal/UnsafeMemory.cs index 7956db01f..9ebcd6e79 100644 --- a/src/MessagePack/Internal/UnsafeMemory.cs +++ b/src/MessagePack/Internal/UnsafeMemory.cs @@ -20,7 +20,7 @@ public static partial class UnsafeMemory32 [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw4(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(4); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -28,13 +28,13 @@ public static unsafe void WriteRaw4(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(5); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -43,13 +43,13 @@ public static unsafe void WriteRaw5(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(6); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -58,13 +58,13 @@ public static unsafe void WriteRaw6(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(7); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -73,13 +73,13 @@ public static unsafe void WriteRaw7(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(8); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -88,13 +88,13 @@ public static unsafe void WriteRaw8(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(9); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -104,13 +104,13 @@ public static unsafe void WriteRaw9(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(10); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -120,13 +120,13 @@ public static unsafe void WriteRaw10(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 6) = *(int*)(pSrc + 6); } - writer.Advance(src.Length); + writer.Advance(10); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw11(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(11); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -136,13 +136,13 @@ public static unsafe void WriteRaw11(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 7) = *(int*)(pSrc + 7); } - writer.Advance(src.Length); + writer.Advance(11); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw12(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(12); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -152,13 +152,13 @@ public static unsafe void WriteRaw12(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 8) = *(int*)(pSrc + 8); } - writer.Advance(src.Length); + writer.Advance(12); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw13(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(13); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -169,13 +169,13 @@ public static unsafe void WriteRaw13(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 9) = *(int*)(pSrc + 9); } - writer.Advance(src.Length); + writer.Advance(13); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw14(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(14); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -186,13 +186,13 @@ public static unsafe void WriteRaw14(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 10) = *(int*)(pSrc + 10); } - writer.Advance(src.Length); + writer.Advance(14); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw15(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(15); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -203,13 +203,13 @@ public static unsafe void WriteRaw15(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 11) = *(int*)(pSrc + 11); } - writer.Advance(src.Length); + writer.Advance(15); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw16(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(16); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -220,13 +220,13 @@ public static unsafe void WriteRaw16(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 12) = *(int*)(pSrc + 12); } - writer.Advance(src.Length); + writer.Advance(16); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw17(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(17); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -238,13 +238,13 @@ public static unsafe void WriteRaw17(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 13) = *(int*)(pSrc + 13); } - writer.Advance(src.Length); + writer.Advance(17); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw18(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(18); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -256,13 +256,13 @@ public static unsafe void WriteRaw18(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 14) = *(int*)(pSrc + 14); } - writer.Advance(src.Length); + writer.Advance(18); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw19(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(19); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -274,13 +274,13 @@ public static unsafe void WriteRaw19(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 15) = *(int*)(pSrc + 15); } - writer.Advance(src.Length); + writer.Advance(19); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw20(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(20); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -292,13 +292,13 @@ public static unsafe void WriteRaw20(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 16) = *(int*)(pSrc + 16); } - writer.Advance(src.Length); + writer.Advance(20); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw21(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(21); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -311,13 +311,13 @@ public static unsafe void WriteRaw21(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 17) = *(int*)(pSrc + 17); } - writer.Advance(src.Length); + writer.Advance(21); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw22(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(22); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -330,13 +330,13 @@ public static unsafe void WriteRaw22(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 18) = *(int*)(pSrc + 18); } - writer.Advance(src.Length); + writer.Advance(22); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw23(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(23); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -349,13 +349,13 @@ public static unsafe void WriteRaw23(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 19) = *(int*)(pSrc + 19); } - writer.Advance(src.Length); + writer.Advance(23); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw24(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(24); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -368,13 +368,13 @@ public static unsafe void WriteRaw24(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 20) = *(int*)(pSrc + 20); } - writer.Advance(src.Length); + writer.Advance(24); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw25(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(25); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -388,13 +388,13 @@ public static unsafe void WriteRaw25(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 21) = *(int*)(pSrc + 21); } - writer.Advance(src.Length); + writer.Advance(25); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw26(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(26); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -408,13 +408,13 @@ public static unsafe void WriteRaw26(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 22) = *(int*)(pSrc + 22); } - writer.Advance(src.Length); + writer.Advance(26); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw27(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(27); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -428,13 +428,13 @@ public static unsafe void WriteRaw27(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 23) = *(int*)(pSrc + 23); } - writer.Advance(src.Length); + writer.Advance(27); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw28(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(28); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -448,13 +448,13 @@ public static unsafe void WriteRaw28(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 24) = *(int*)(pSrc + 24); } - writer.Advance(src.Length); + writer.Advance(28); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw29(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(29); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -469,13 +469,13 @@ public static unsafe void WriteRaw29(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 25) = *(int*)(pSrc + 25); } - writer.Advance(src.Length); + writer.Advance(29); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw30(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(30); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -490,13 +490,13 @@ public static unsafe void WriteRaw30(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 26) = *(int*)(pSrc + 26); } - writer.Advance(src.Length); + writer.Advance(30); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw31(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(31); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -511,7 +511,7 @@ public static unsafe void WriteRaw31(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 27) = *(int*)(pSrc + 27); } - writer.Advance(src.Length); + writer.Advance(31); } } @@ -520,7 +520,7 @@ public static partial class UnsafeMemory64 [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw8(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(8); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -528,13 +528,13 @@ public static unsafe void WriteRaw8(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(9); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -543,13 +543,13 @@ public static unsafe void WriteRaw9(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(10); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -558,13 +558,13 @@ public static unsafe void WriteRaw10(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 2) = *(long*)(pSrc + 2); } - writer.Advance(src.Length); + writer.Advance(10); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw11(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(11); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -573,13 +573,13 @@ public static unsafe void WriteRaw11(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 3) = *(long*)(pSrc + 3); } - writer.Advance(src.Length); + writer.Advance(11); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw12(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(12); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -588,13 +588,13 @@ public static unsafe void WriteRaw12(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 4) = *(long*)(pSrc + 4); } - writer.Advance(src.Length); + writer.Advance(12); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw13(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(13); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -603,13 +603,13 @@ public static unsafe void WriteRaw13(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 5) = *(long*)(pSrc + 5); } - writer.Advance(src.Length); + writer.Advance(13); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw14(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(14); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -618,13 +618,13 @@ public static unsafe void WriteRaw14(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 6) = *(long*)(pSrc + 6); } - writer.Advance(src.Length); + writer.Advance(14); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw15(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(15); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -633,13 +633,13 @@ public static unsafe void WriteRaw15(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 7) = *(long*)(pSrc + 7); } - writer.Advance(src.Length); + writer.Advance(15); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw16(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(16); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -648,13 +648,13 @@ public static unsafe void WriteRaw16(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 8) = *(long*)(pSrc + 8); } - writer.Advance(src.Length); + writer.Advance(16); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw17(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(17); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -664,13 +664,13 @@ public static unsafe void WriteRaw17(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 9) = *(long*)(pSrc + 9); } - writer.Advance(src.Length); + writer.Advance(17); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw18(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(18); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -680,13 +680,13 @@ public static unsafe void WriteRaw18(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 10) = *(long*)(pSrc + 10); } - writer.Advance(src.Length); + writer.Advance(18); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw19(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(19); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -696,13 +696,13 @@ public static unsafe void WriteRaw19(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 11) = *(long*)(pSrc + 11); } - writer.Advance(src.Length); + writer.Advance(19); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw20(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(20); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -712,13 +712,13 @@ public static unsafe void WriteRaw20(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 12) = *(long*)(pSrc + 12); } - writer.Advance(src.Length); + writer.Advance(20); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw21(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(21); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -728,13 +728,13 @@ public static unsafe void WriteRaw21(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 13) = *(long*)(pSrc + 13); } - writer.Advance(src.Length); + writer.Advance(21); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw22(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(22); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -744,13 +744,13 @@ public static unsafe void WriteRaw22(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 14) = *(long*)(pSrc + 14); } - writer.Advance(src.Length); + writer.Advance(22); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw23(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(23); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -760,13 +760,13 @@ public static unsafe void WriteRaw23(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 15) = *(long*)(pSrc + 15); } - writer.Advance(src.Length); + writer.Advance(23); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw24(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(24); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -776,13 +776,13 @@ public static unsafe void WriteRaw24(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 16) = *(long*)(pSrc + 16); } - writer.Advance(src.Length); + writer.Advance(24); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw25(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(25); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -793,13 +793,13 @@ public static unsafe void WriteRaw25(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 17) = *(long*)(pSrc + 17); } - writer.Advance(src.Length); + writer.Advance(25); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw26(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(26); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -810,13 +810,13 @@ public static unsafe void WriteRaw26(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 18) = *(long*)(pSrc + 18); } - writer.Advance(src.Length); + writer.Advance(26); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw27(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(27); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -827,13 +827,13 @@ public static unsafe void WriteRaw27(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 19) = *(long*)(pSrc + 19); } - writer.Advance(src.Length); + writer.Advance(27); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw28(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(28); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -844,13 +844,13 @@ public static unsafe void WriteRaw28(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 20) = *(long*)(pSrc + 20); } - writer.Advance(src.Length); + writer.Advance(28); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw29(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(29); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -861,13 +861,13 @@ public static unsafe void WriteRaw29(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 21) = *(long*)(pSrc + 21); } - writer.Advance(src.Length); + writer.Advance(29); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw30(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(30); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -878,13 +878,13 @@ public static unsafe void WriteRaw30(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 22) = *(long*)(pSrc + 22); } - writer.Advance(src.Length); + writer.Advance(30); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw31(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(31); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -895,7 +895,7 @@ public static unsafe void WriteRaw31(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 23) = *(long*)(pSrc + 23); } - writer.Advance(src.Length); + writer.Advance(31); } } } diff --git a/src/MessagePack/Internal/UnsafeMemory.tt b/src/MessagePack/Internal/UnsafeMemory.tt index cace009d7..6d1891701 100644 --- a/src/MessagePack/Internal/UnsafeMemory.tt +++ b/src/MessagePack/Internal/UnsafeMemory.tt @@ -28,7 +28,7 @@ namespace MessagePack.Internal [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw<#= i #>(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(<#= i #>); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -41,7 +41,7 @@ namespace MessagePack.Internal <# } #> } - writer.Advance(src.Length); + writer.Advance(<#= i #>); } <# } #> } @@ -52,7 +52,7 @@ namespace MessagePack.Internal [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw<#= i #>(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(<#= i #>); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -65,7 +65,7 @@ namespace MessagePack.Internal <# } #> } - writer.Advance(src.Length); + writer.Advance(<#= i #>); } <# } #> } From 814bc4c187769d22b29abf2163b3169fbf1c6d6b Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Wed, 29 Apr 2026 15:35:24 -0600 Subject: [PATCH 23/32] Honor TypeFormatter options hooks for CWE-470 --- .../StandardClassLibraryFormatter.cs | 11 ++- .../StandardClassLibraryFormatterTests.cs | 84 +++++++++++++++++++ 2 files changed, 92 insertions(+), 3 deletions(-) diff --git a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Formatters/StandardClassLibraryFormatter.cs b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Formatters/StandardClassLibraryFormatter.cs index da2444b41..1cc840e5b 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Formatters/StandardClassLibraryFormatter.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Formatters/StandardClassLibraryFormatter.cs @@ -649,9 +649,14 @@ public void Serialize(ref MessagePackWriter writer, T? value, MessagePackSeriali public T? Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) { - return reader.ReadString() is string value - ? (T?)Type.GetType(value, throwOnError: true) - : null; + if (reader.ReadString() is not string value) + { + return null; + } + + Type type = options.LoadType(value) ?? throw new TypeLoadException(value); + options.ThrowIfDeserializingTypeIsDisallowed(type); + return (T?)type; } } diff --git a/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/StandardClassLibraryFormatterTests.cs b/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/StandardClassLibraryFormatterTests.cs index 351234765..c3c60cef1 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/StandardClassLibraryFormatterTests.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/StandardClassLibraryFormatterTests.cs @@ -35,6 +35,27 @@ public void SystemType_Serializable_Null() Assert.Equal(type, type2); } + [Fact] + public void SystemType_DeserializeUsesLoadType() + { + byte[] msgpack = MessagePackSerializer.Serialize(new TypeHolder { Type = typeof(Uri) }, MessagePackSerializerOptions.Standard); + var options = new RejectingTypeLoadOptions(typeof(Uri)); + + var ex = Assert.Throws(() => MessagePackSerializer.Deserialize(msgpack, options)); + Assert.IsType(ex.InnerException); + Assert.Equal(1, options.LoadTypeCalls); + } + + [Fact] + public void SystemType_DeserializeRejectsDisallowedType() + { + byte[] msgpack = MessagePackSerializer.Serialize(new TypeHolder { Type = typeof(Uri) }, MessagePackSerializerOptions.Standard); + var options = new DisallowingTypeOptions(typeof(Uri)); + + var ex = Assert.Throws(() => MessagePackSerializer.Deserialize(msgpack, options)); + Assert.IsType(ex.InnerException); + } + [Fact] public void DeserializeByteArrayFromFixArray() { @@ -132,5 +153,68 @@ private T Roundtrip(T value, bool breakupBuffer = false) return MessagePackSerializer.Deserialize(msgpack, MessagePackSerializerOptions.Standard); } } + + [MessagePackObject] + public class TypeHolder + { + [Key(0)] + public Type Type { get; set; } + } + + private class RejectingTypeLoadOptions : MessagePackSerializerOptions + { + private readonly Type rejectedType; + + internal RejectingTypeLoadOptions(Type rejectedType) + : base(MessagePackSerializerOptions.Standard) + { + this.rejectedType = rejectedType; + } + + private RejectingTypeLoadOptions(RejectingTypeLoadOptions copyFrom) + : base(copyFrom) + { + this.rejectedType = copyFrom.rejectedType; + this.LoadTypeCalls = copyFrom.LoadTypeCalls; + } + + public int LoadTypeCalls { get; private set; } + + public override Type LoadType(string typeName) + { + Type type = base.LoadType(typeName); + this.LoadTypeCalls++; + return type == this.rejectedType ? null : type; + } + + protected override MessagePackSerializerOptions Clone() => new RejectingTypeLoadOptions(this); + } + + private class DisallowingTypeOptions : MessagePackSerializerOptions + { + private readonly Type rejectedType; + + internal DisallowingTypeOptions(Type rejectedType) + : base(MessagePackSerializerOptions.Standard) + { + this.rejectedType = rejectedType; + } + + private DisallowingTypeOptions(DisallowingTypeOptions copyFrom) + : base(copyFrom) + { + this.rejectedType = copyFrom.rejectedType; + } + + public override void ThrowIfDeserializingTypeIsDisallowed(Type type) + { + if (type == this.rejectedType) + { + throw new TypeAccessException(); + } + } + + protected override MessagePackSerializerOptions Clone() => new DisallowingTypeOptions(this); + } } } From ffb151a67a2cf7fa6e58f3ac4692350cbe1b740f Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Thu, 21 May 2026 14:25:38 -0600 Subject: [PATCH 24/32] Add several known unsafe 'gadgets' to the disallow list Thanks to @svenclaesson for calling these types out as valuable to block by default. --- .../MessagePack/MessagePackSerializerOptions.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/MessagePackSerializerOptions.cs b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/MessagePackSerializerOptions.cs index dc4598239..8249dbcdf 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/MessagePackSerializerOptions.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/MessagePackSerializerOptions.cs @@ -24,14 +24,25 @@ public class MessagePackSerializerOptions /// private static readonly HashSet DisallowedTypes = new HashSet { + "Microsoft.VisualStudio.Text.Formatting.TextFormattingRunProperties", + "System.CodeDom.Compiler.CompilerResults", "System.CodeDom.Compiler.TempFileCollection", + "System.Configuration.SettingsPropertyValue", + "System.Data.DataSet", + "System.Data.DataTable", + "System.Diagnostics.Process", + "System.Diagnostics.ProcessStartInfo", + "System.Drawing.Design.ToolboxItemContainer", "System.IdentityModel.Tokens.SessionSecurityToken", "System.Management.IWbemClassObjectFreeThreaded", "System.Security.Claims.ClaimsIdentity", + "System.Security.Claims.ClaimsPrincipal", "System.Security.Principal.WindowsIdentity", + "System.Security.Principal.WindowsPrincipal", "System.Web.Security.RolePrincipal", "System.Windows.Data.ObjectDataProvider", "System.Windows.ResourceDictionary", + "System.Workflow.ComponentModel.Serialization.ActivitySurrogateSelector", }; #if !DYNAMICCODEDUMPER From 135abbc98a69be3eed04d646e06655ecf9a52f1f Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Tue, 21 Jul 2026 14:24:13 -0600 Subject: [PATCH 25/32] Revise end-of-life date for version 2.x (#2282) Updated the end-of-life date for version 2.x. --- SECURITY.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SECURITY.md b/SECURITY.md index e2612cff1..604e2c061 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -5,7 +5,7 @@ | Version | Supported | End-of-life date | | ------- | --------- | ---------------- | | 1.x | ❌ | -| 2.x | ✅ | 2025-12-31 | +| 2.x | ✅ | 2026-12-31 (at least) | | 3.x | ✅ | not yet determined | Each supported major version is only serviced for security issues at its tip. From 21f6e635d5b5a40ec82a0cd642b1d19b7d1fbe88 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue, 4 Aug 2026 10:18:56 -0600 Subject: [PATCH 26/32] Fix OldSpec byte[] encoding emitting unsupported str8 headers (#2287) * Initial plan * Initial plan * Fix OldSpec byte[] encoding to avoid str8 for lengths 32-255 * Simplify OldSpec str16 high byte to constant 0 --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> --- src/MessagePack/MessagePackWriter.cs | 25 +++++++- .../MessagePackWriterTests.cs | 64 +++++++++++++++++++ .../OldSpecBinaryFormatterTest.cs | 8 +++ 3 files changed, 94 insertions(+), 3 deletions(-) diff --git a/src/MessagePack/MessagePackWriter.cs b/src/MessagePack/MessagePackWriter.cs index 36695da27..b6bd188fd 100644 --- a/src/MessagePack/MessagePackWriter.cs +++ b/src/MessagePack/MessagePackWriter.cs @@ -454,7 +454,7 @@ public void Write(byte[]? src) /// /// The span of bytes to write. /// - /// When is , the msgpack code used is , or instead. + /// When is , the msgpack code used is fixstr, or instead (never , which is not defined in the old spec). /// public void Write(scoped ReadOnlySpan src) { @@ -473,7 +473,7 @@ public void Write(scoped ReadOnlySpan src) /// /// The span of bytes to write. /// - /// When is , the msgpack code used is , or instead. + /// When is , the msgpack code used is fixstr, or instead (never , which is not defined in the old spec). /// public void Write(in ReadOnlySequence src) { @@ -498,7 +498,7 @@ public void Write(in ReadOnlySequence src) /// Alternatively a single call to or will take care of the header and content in one call. /// /// - /// When is , the msgpack code used is , or instead. + /// When is , the msgpack code used is fixstr, or instead (never , which is not defined in the old spec). /// /// public void WriteBinHeader(int length) @@ -559,15 +559,34 @@ public void WriteString(ReadOnlySpan utf8stringBytes) /// /// The number of bytes in the string that will follow this header. /// + /// /// The caller should use or /// after calling this method to actually write the content. /// Alternatively a single call to or will take care of the header and content in one call. + /// + /// + /// When is , is never used because it is not defined in the old spec; + /// lengths that would otherwise use str8 are encoded with instead. + /// /// public void WriteStringHeader(int byteCount) { // When we write the header, we'll ask for all the space we need for the payload as well // as that may help ensure we only allocate a buffer once. Span span = this.writer.GetSpan(byteCount + 5); + + // The old msgpack spec does not define str8 (0xd9). Use str16 for lengths 32-255 when OldSpec is set. + // This matches WriteString_PostEncoding and keeps binary-as-string (WriteBinHeader) legacy-compatible. + if (this.OldSpec && byteCount > MessagePackRange.MaxFixStringLength && byteCount <= byte.MaxValue) + { + span[0] = MessagePackCode.Str16; + span[1] = 0; + span[2] = unchecked((byte)byteCount); + + this.writer.Advance(3); + return; + } + AssumesTrue(MessagePackPrimitives.TryWriteStringHeader(span, (uint)byteCount, out int written)); this.writer.Advance(written); } diff --git a/tests/MessagePack.Tests/MessagePackWriterTests.cs b/tests/MessagePack.Tests/MessagePackWriterTests.cs index 85d293650..3f990845a 100644 --- a/tests/MessagePack.Tests/MessagePackWriterTests.cs +++ b/tests/MessagePack.Tests/MessagePackWriterTests.cs @@ -143,6 +143,70 @@ public void WriteBinHeader() Assert.Equal(new byte[] { 1, 2, 3, 4, 5 }, reader.ReadBytes().Value.ToArray()); } + /// + /// Regression for https://github.com/MessagePack-CSharp/MessagePack-CSharp/issues/2286: + /// OldSpec must not emit str8 (0xD9) for lengths 32-255. + /// + [Theory] + [InlineData(31)] // fixstr + [InlineData(32)] // str16 under OldSpec (would be str8 otherwise) + [InlineData(255)] // str16 under OldSpec (would be str8 otherwise) + [InlineData(256)] // str16 + public void Write_ByteArray_OldSpec_AvoidsStr8(int length) + { + byte[] value = new byte[length]; + Array.Fill(value, (byte)'A'); + + var sequence = new Sequence(); + var writer = new MessagePackWriter(sequence) { OldSpec = true }; + writer.Write(value); + writer.Flush(); + + ReadOnlySpan written = sequence.AsReadOnlySequence.ToArray(); + Assert.NotEqual(MessagePackCode.Str8, written[0]); + + if (length <= MessagePackRange.MaxFixStringLength) + { + Assert.Equal((byte)(MessagePackCode.MinFixStr | length), written[0]); + Assert.Equal(value, written.Slice(1).ToArray()); + } + else if (length <= ushort.MaxValue) + { + Assert.Equal(MessagePackCode.Str16, written[0]); + Assert.Equal((byte)(length >> 8), written[1]); + Assert.Equal((byte)length, written[2]); + Assert.Equal(value, written.Slice(3).ToArray()); + } + } + + [Theory] + [InlineData(32)] + [InlineData(255)] + public void WriteStringHeader_OldSpec_AvoidsStr8(int length) + { + var sequence = new Sequence(); + var writer = new MessagePackWriter(sequence) { OldSpec = true }; + writer.WriteStringHeader(length); + writer.Flush(); + + ReadOnlySpan written = sequence.AsReadOnlySequence.ToArray(); + Assert.Equal(new byte[] { MessagePackCode.Str16, (byte)(length >> 8), (byte)length }, written.ToArray()); + } + + [Theory] + [InlineData(32)] + [InlineData(255)] + public void WriteBinHeader_OldSpec_AvoidsStr8(int length) + { + var sequence = new Sequence(); + var writer = new MessagePackWriter(sequence) { OldSpec = true }; + writer.WriteBinHeader(length); + writer.Flush(); + + ReadOnlySpan written = sequence.AsReadOnlySequence.ToArray(); + Assert.Equal(new byte[] { MessagePackCode.Str16, (byte)(length >> 8), (byte)length }, written.ToArray()); + } + [Fact] public void WriteExtensionFormatHeader_NegativeExtension() { diff --git a/tests/MessagePack.Tests/OldSpecBinaryFormatterTest.cs b/tests/MessagePack.Tests/OldSpecBinaryFormatterTest.cs index 748451f4d..78d1473a7 100644 --- a/tests/MessagePack.Tests/OldSpecBinaryFormatterTest.cs +++ b/tests/MessagePack.Tests/OldSpecBinaryFormatterTest.cs @@ -23,6 +23,8 @@ public class OldSpecBinaryFormatterTest { [Theory] [InlineData(10)] // fixstr + [InlineData(32)] // str 16 (must not use str8 under OldSpec) + [InlineData(255)] // str 16 (must not use str8 under OldSpec) [InlineData(1000)] // str 16 [InlineData(100000)] // str 32 public void SerializeSimpleByteArray(int arrayLength) @@ -57,6 +59,8 @@ public void SerializeNil() [Theory] [InlineData(10)] // fixstr + [InlineData(32)] // str 16 (must not use str8 under OldSpec) + [InlineData(255)] // str 16 (must not use str8 under OldSpec) [InlineData(1000)] // str 16 [InlineData(100000)] // str 32 public void SerializeObject(int arrayLength) @@ -78,6 +82,8 @@ public void SerializeObject(int arrayLength) [Theory] [InlineData(10)] // fixstr + [InlineData(32)] // str 16 (must not use str8 under OldSpec) + [InlineData(255)] // str 16 (must not use str8 under OldSpec) [InlineData(1000)] // str 16 [InlineData(100000)] // str 32 public void DeserializeSimpleByteArray(int arrayLength) @@ -103,6 +109,8 @@ public void DeserializeNil() [Theory] [InlineData(10)] // fixstr + [InlineData(32)] // str 16 (must not use str8 under OldSpec) + [InlineData(255)] // str 16 (must not use str8 under OldSpec) [InlineData(1000)] // str 16 [InlineData(100000)] // str 32 public void DeserializeObject(int arrayLength) From 7fd78bbf3476e03beae8b0f7a521125525a8a64a Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Wed, 2 Sep 2026 21:20:49 -0600 Subject: [PATCH 27/32] Prevent nested container allocation amplification Track outstanding minimum child bytes across array and map headers so nested structures cannot repeatedly use the same unread payload to justify allocations. Retire commitments as bytes are consumed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Scripts/MessagePack/MessagePackReader.cs | 29 ++- .../ShareTests/MessagePackReaderTests.cs | 224 ++++++++++++++++++ .../PrimitiveObjectFormatterTests.cs | 29 +++ 3 files changed, 280 insertions(+), 2 deletions(-) diff --git a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/MessagePackReader.cs b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/MessagePackReader.cs index 5a38fccd6..7d4aea009 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/MessagePackReader.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/MessagePackReader.cs @@ -32,6 +32,17 @@ ref partial struct MessagePackReader /// private SequenceReader reader; + /// + /// The minimum number of bytes that previously-read container headers have committed to their children + /// but that have not yet been consumed. + /// + private long minimumRemainingChildBytes; + + /// + /// The value of when was last updated. + /// + private long minimumRemainingChildBytesCheckpoint; + /// /// Initializes a new instance of the struct. /// @@ -354,7 +365,7 @@ public int ReadArrayHeader() // Protect against corrupted or mischievious data that may lead to allocating way too much memory. // We allow for each primitive to be the minimal 1 byte in size. // Formatters that know each element is larger can optionally add a stronger check. - ThrowInsufficientBufferUnless(this.reader.Remaining >= count); + this.ReserveMinimumChildBytes(count); return count; } @@ -432,7 +443,7 @@ public int ReadMapHeader() // Protect against corrupted or mischievious data that may lead to allocating way too much memory. // We allow for each primitive to be the minimal 1 byte in size, and we have a key=value map, so that's 2 bytes. // Formatters that know each element is larger can optionally add a stronger check. - ThrowInsufficientBufferUnless(this.reader.Remaining >= (long)count * 2); + this.ReserveMinimumChildBytes((long)count * 2); return count; } @@ -999,6 +1010,20 @@ private static void ThrowInsufficientBufferUnless(bool condition) } } + private void ReserveMinimumChildBytes(long minimumChildBytes) + { + long consumed = this.reader.Consumed; + long bytesConsumedSinceCheckpoint = consumed - this.minimumRemainingChildBytesCheckpoint; + this.minimumRemainingChildBytes = Math.Max(0, this.minimumRemainingChildBytes - bytesConsumedSinceCheckpoint); + this.minimumRemainingChildBytesCheckpoint = consumed; + + ThrowInsufficientBufferUnless( + minimumChildBytes >= 0 + && this.minimumRemainingChildBytes <= this.reader.Remaining + && minimumChildBytes <= this.reader.Remaining - this.minimumRemainingChildBytes); + this.minimumRemainingChildBytes += minimumChildBytes; + } + private int GetBytesLength() { ThrowInsufficientBufferUnless(this.TryGetBytesLength(out int length)); diff --git a/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/MessagePackReaderTests.cs b/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/MessagePackReaderTests.cs index 33c363436..9bfcc3d54 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/MessagePackReaderTests.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/MessagePackReaderTests.cs @@ -69,6 +69,142 @@ public void ReadArrayHeader_MitigatesLargeAllocations() }); } + [Fact] + [Trait("CWE", "789")] + public void ReadArrayHeader_MitigatesNestedAllocationAmplification() + { + var sequence = new Sequence(); + var writer = new MessagePackWriter(sequence); + writer.WriteArrayHeader(4); + writer.WriteArrayHeader(4); + writer.WriteNil(); + writer.WriteNil(); + writer.WriteNil(); + writer.WriteNil(); + writer.Flush(); + + Assert.Throws(() => + { + var reader = new MessagePackReader(sequence); + Assert.Equal(4, reader.ReadArrayHeader()); + reader.ReadArrayHeader(); + }); + } + + [Fact] + public void ReadArrayHeader_AllowsValidNestedAndConsecutiveContainers() + { + var sequence = new Sequence(); + var writer = new MessagePackWriter(sequence); + writer.WriteArrayHeader(2); + writer.Write("long child"); + writer.WriteArrayHeader(2); + writer.WriteNil(); + writer.WriteNil(); + writer.WriteArrayHeader(1); + writer.WriteNil(); + writer.Flush(); + + var reader = new MessagePackReader(sequence); + Assert.Equal(2, reader.ReadArrayHeader()); + Assert.Equal("long child", reader.ReadString()); + Assert.Equal(2, reader.ReadArrayHeader()); + reader.ReadNil(); + reader.ReadNil(); + Assert.Equal(1, reader.ReadArrayHeader()); + reader.ReadNil(); + Assert.True(reader.End); + } + + [Fact] + public void TryReadArrayHeader_DoesNotReserveMinimumChildBytes() + { + var sequence = new Sequence(); + var writer = new MessagePackWriter(sequence); + writer.WriteArrayHeader(4); + writer.WriteArrayHeader(4); + writer.WriteNil(); + writer.WriteNil(); + writer.WriteNil(); + writer.WriteNil(); + writer.Flush(); + + var reader = new MessagePackReader(sequence); + Assert.Equal(4, reader.ReadArrayHeader()); + Assert.True(reader.TryReadArrayHeader(out int count)); + Assert.Equal(4, count); + } + + [Fact] + [Trait("CWE", "190")] + public void ReadArrayHeader_MitigatesLargeAllocations_WhenMinimumPayloadLengthIsNegative() + { + byte[] msgpack = { MessagePackCode.Array32, 0x80, 0, 0, 0 }; + + Assert.Throws(() => + { + var reader = new MessagePackReader(msgpack); + reader.ReadArrayHeader(); + }); + } + + [Fact] + public void CreatePeekReader_CopiesMinimumChildByteReservation() + { + var sequence = new Sequence(); + var writer = new MessagePackWriter(sequence); + writer.WriteArrayHeader(4); + writer.WriteArrayHeader(4); + writer.WriteNil(); + writer.WriteNil(); + writer.WriteNil(); + writer.WriteNil(); + writer.Flush(); + + Assert.Throws(() => + { + var reader = new MessagePackReader(sequence); + Assert.Equal(4, reader.ReadArrayHeader()); + MessagePackReader peekReader = reader.CreatePeekReader(); + peekReader.ReadArrayHeader(); + }); + + Assert.Throws(() => + { + var reader = new MessagePackReader(sequence); + Assert.Equal(4, reader.ReadArrayHeader()); + reader.ReadArrayHeader(); + }); + } + + [Fact] + public void Clone_StartsMinimumChildByteReservationForReplacementBuffer() + { + var sequence = new Sequence(); + var writer = new MessagePackWriter(sequence); + writer.WriteArrayHeader(4); + writer.WriteArrayHeader(4); + writer.WriteNil(); + writer.WriteNil(); + writer.WriteNil(); + writer.WriteNil(); + writer.Flush(); + + var reader = new MessagePackReader(sequence); + Assert.Equal(4, reader.ReadArrayHeader()); + + var replacementSequence = new Sequence(); + writer = new MessagePackWriter(replacementSequence); + writer.WriteArrayHeader(1); + writer.WriteNil(); + writer.Flush(); + + MessagePackReader clone = reader.Clone(replacementSequence); + Assert.Equal(1, clone.ReadArrayHeader()); + clone.ReadNil(); + Assert.True(clone.End); + } + [Fact] public void TryReadArrayHeader() { @@ -101,6 +237,81 @@ public void ReadMapHeader_MitigatesLargeAllocations() }); } + [Fact] + [Trait("CWE", "789")] + public void ReadMapHeader_MitigatesNestedAllocationAmplification() + { + var sequence = new Sequence(); + var writer = new MessagePackWriter(sequence); + writer.WriteMapHeader(2); + writer.WriteMapHeader(2); + writer.WriteNil(); + writer.WriteNil(); + writer.WriteNil(); + writer.WriteNil(); + writer.Flush(); + + Assert.Throws(() => + { + var reader = new MessagePackReader(sequence); + Assert.Equal(2, reader.ReadMapHeader()); + reader.ReadMapHeader(); + }); + } + + [Fact] + [Trait("CWE", "789")] + public void ReadMapHeader_MitigatesMixedNestedAllocationAmplification() + { + var sequence = new Sequence(); + var writer = new MessagePackWriter(sequence); + writer.WriteArrayHeader(4); + writer.WriteMapHeader(2); + writer.WriteNil(); + writer.WriteNil(); + writer.WriteNil(); + writer.WriteNil(); + writer.Flush(); + + Assert.Throws(() => + { + var reader = new MessagePackReader(sequence); + Assert.Equal(4, reader.ReadArrayHeader()); + reader.ReadMapHeader(); + }); + } + + [Fact] + public void ReadMapHeader_ReturnsReservationAsContentsAreConsumed() + { + var sequence = new Sequence(); + var writer = new MessagePackWriter(sequence); + writer.WriteMapHeader(2); + writer.WriteNil(); + writer.WriteNil(); + writer.WriteNil(); + writer.WriteNil(); + writer.WriteMapHeader(2); + writer.WriteNil(); + writer.WriteNil(); + writer.WriteNil(); + writer.WriteNil(); + writer.Flush(); + + var reader = new MessagePackReader(sequence); + Assert.Equal(2, reader.ReadMapHeader()); + reader.ReadNil(); + reader.ReadNil(); + reader.ReadNil(); + reader.ReadNil(); + Assert.Equal(2, reader.ReadMapHeader()); + reader.ReadNil(); + reader.ReadNil(); + reader.ReadNil(); + reader.ReadNil(); + Assert.True(reader.End); + } + [Fact] [Trait("CWE", "190")] public void ReadMapHeader_MitigatesLargeAllocations_WhenMinimumPayloadLengthOverflowsInt32() @@ -114,6 +325,19 @@ public void ReadMapHeader_MitigatesLargeAllocations_WhenMinimumPayloadLengthOver }); } + [Fact] + [Trait("CWE", "190")] + public void ReadMapHeader_MitigatesLargeAllocations_WhenMinimumPayloadLengthIsNegative() + { + byte[] msgpack = { MessagePackCode.Map32, 0x80, 0, 0, 0 }; + + Assert.Throws(() => + { + var reader = new MessagePackReader(msgpack); + reader.ReadMapHeader(); + }); + } + [Fact] [Trait("CWE", "190")] public void SkipMap_MitigatesLargeAllocations_WhenMinimumPayloadLengthOverflowsInt32() diff --git a/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/PrimitiveObjectFormatterTests.cs b/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/PrimitiveObjectFormatterTests.cs index 83761ba63..cc20b9b5a 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/PrimitiveObjectFormatterTests.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/PrimitiveObjectFormatterTests.cs @@ -3,11 +3,13 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using MessagePack.Formatters; using MessagePack.Resolvers; +using Nerdbank.Streams; using Xunit; namespace MessagePack.Tests @@ -58,6 +60,33 @@ public void EnumRetainsUnderlyingType() Assert.Equal(SomeEnum.SomeValue, result); } + [Fact] + [Trait("CWE", "789")] + public void NestedArraysCannotReuseTrailingBytesToJustifyAllocations() + { + const int arrayLength = 1000; + const int nestingDepth = 10; + var sequence = new Sequence(); + var writer = new MessagePackWriter(sequence); + for (int i = 0; i < nestingDepth; i++) + { + writer.WriteArrayHeader(arrayLength); + } + + for (int i = 0; i < arrayLength; i++) + { + writer.WriteNil(); + } + + writer.Flush(); + + MessagePackSerializationException exception = Assert.Throws( + () => MessagePackSerializer.Deserialize( + sequence.AsReadOnlySequence, + ContractlessStandardResolver.Options.WithSecurity(MessagePackSecurity.UntrustedData))); + Assert.IsType(exception.InnerException); + } + public enum SomeEnum : ushort { None = 0, From 0b7a3bc740c707b472520552269ee75b93463894 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Wed, 2 Sep 2026 21:42:53 -0600 Subject: [PATCH 28/32] Adjust oversized container tests for v3 The v3 reader rejects array32 and map32 counts above Int32.MaxValue while decoding their headers, before allocation-length accounting runs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tests/MessagePack.Tests/MessagePackReaderTests.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/MessagePack.Tests/MessagePackReaderTests.cs b/tests/MessagePack.Tests/MessagePackReaderTests.cs index bf4122d26..c069d60fb 100644 --- a/tests/MessagePack.Tests/MessagePackReaderTests.cs +++ b/tests/MessagePack.Tests/MessagePackReaderTests.cs @@ -137,11 +137,11 @@ public void TryReadArrayHeader_DoesNotReserveMinimumChildBytes() [Fact] [Trait("CWE", "190")] - public void ReadArrayHeader_MitigatesLargeAllocations_WhenMinimumPayloadLengthIsNegative() + public void ReadArrayHeader_MitigatesLargeAllocations_WhenElementCountExceedsInt32() { byte[] msgpack = { MessagePackCode.Array32, 0x80, 0, 0, 0 }; - Assert.Throws(() => + Assert.Throws(() => { var reader = new MessagePackReader(msgpack); reader.ReadArrayHeader(); @@ -327,11 +327,11 @@ public void ReadMapHeader_MitigatesLargeAllocations_WhenMinimumPayloadLengthOver [Fact] [Trait("CWE", "190")] - public void ReadMapHeader_MitigatesLargeAllocations_WhenMinimumPayloadLengthIsNegative() + public void ReadMapHeader_MitigatesLargeAllocations_WhenElementCountExceedsInt32() { byte[] msgpack = { MessagePackCode.Map32, 0x80, 0, 0, 0 }; - Assert.Throws(() => + Assert.Throws(() => { var reader = new MessagePackReader(msgpack); reader.ReadMapHeader(); From ab3119e22b9c501ec28cfa62977bd3552db2c149 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Thu, 17 Sep 2026 10:12:17 -0600 Subject: [PATCH 29/32] Fix build warning and Windows build break (#2300) * Validate Windows builds Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Treat CI build warnings as errors Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/dotnet.yml | 8 ++++++-- .../Verifiers/CSharpSourceGeneratorVerifier`1+Test.cs | 2 +- tests/MessagePack.Tests/MessagePackWriterTests.cs | 5 ++++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 578c78cd3..400846fb7 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -13,12 +13,16 @@ on: jobs: build-dotnet: - runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest] + runs-on: ${{ matrix.os }} timeout-minutes: 15 steps: - uses: actions/checkout@v4 with: fetch-depth: 0 # avoid shallow clone so nbgv can do its work. - uses: ./.github/actions/setup-dotnet - - run: dotnet build -c Release -t:build,pack + - run: dotnet build -c Release -t:build,pack -warnaserror - run: dotnet test -c Release --no-build diff --git a/tests/MessagePack.SourceGenerator.Tests/Verifiers/CSharpSourceGeneratorVerifier`1+Test.cs b/tests/MessagePack.SourceGenerator.Tests/Verifiers/CSharpSourceGeneratorVerifier`1+Test.cs index 74288a368..6ff839d83 100644 --- a/tests/MessagePack.SourceGenerator.Tests/Verifiers/CSharpSourceGeneratorVerifier`1+Test.cs +++ b/tests/MessagePack.SourceGenerator.Tests/Verifiers/CSharpSourceGeneratorVerifier`1+Test.cs @@ -2,7 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // Uncomment the following line to write expected files to disk -#define WRITE_EXPECTED +// #define WRITE_EXPECTED #if WRITE_EXPECTED #warning WRITE_EXPECTED is fine for local builds, but should not be merged to the main branch. diff --git a/tests/MessagePack.Tests/MessagePackWriterTests.cs b/tests/MessagePack.Tests/MessagePackWriterTests.cs index 3f990845a..b3a02d907 100644 --- a/tests/MessagePack.Tests/MessagePackWriterTests.cs +++ b/tests/MessagePack.Tests/MessagePackWriterTests.cs @@ -155,7 +155,10 @@ public void WriteBinHeader() public void Write_ByteArray_OldSpec_AvoidsStr8(int length) { byte[] value = new byte[length]; - Array.Fill(value, (byte)'A'); + for (int i = 0; i < value.Length; i++) + { + value[i] = (byte)'A'; + } var sequence = new Sequence(); var writer = new MessagePackWriter(sequence) { OldSpec = true }; From 4fcae98bc88179cc5209361e154032059845aa88 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 17 Sep 2026 17:14:47 +0000 Subject: [PATCH 30/32] feat: Update package.json to 3.1.9 Commit by [GitHub Actions](https://github.com/MessagePack-CSharp/MessagePack-CSharp/actions/runs/35251264581) --- .../Assets/Scripts/MessagePack/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/package.json b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/package.json index f2b502787..ec902447d 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/package.json +++ b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/package.json @@ -1,7 +1,7 @@ { "name": "com.github.messagepack-csharp", "displayName": "MessagePack", - "version": "3.1.8", + "version": "3.1.9", "unity": "2021.3", "description": "Extremely Fast MessagePack Serializer for C#.", "keywords": [ From 3fcb0607fa8c51a07259f5286d128407d0969f46 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Thu, 17 Sep 2026 11:37:23 -0600 Subject: [PATCH 31/32] Fix source generator snapshot ordering Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Verifiers/CSharpSourceGeneratorVerifier`1+Test.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tests/MessagePack.SourceGenerator.Tests/Verifiers/CSharpSourceGeneratorVerifier`1+Test.cs b/tests/MessagePack.SourceGenerator.Tests/Verifiers/CSharpSourceGeneratorVerifier`1+Test.cs index 6ff839d83..05ca9ef26 100644 --- a/tests/MessagePack.SourceGenerator.Tests/Verifiers/CSharpSourceGeneratorVerifier`1+Test.cs +++ b/tests/MessagePack.SourceGenerator.Tests/Verifiers/CSharpSourceGeneratorVerifier`1+Test.cs @@ -117,13 +117,11 @@ static void AddGeneratedSources(ProjectState project, string testMethod, bool wi .Replace('(', '_') .Replace(')', '_'); - foreach (var resourceName in typeof(Test).Assembly.GetManifestResourceNames()) + foreach (var resourceName in typeof(Test).Assembly.GetManifestResourceNames() + .Where(name => name.StartsWith(expectedPrefix, StringComparison.Ordinal)) + .OrderByDescending(name => name.EndsWith(".MessagePack.GeneratedMessagePackResolver.g.cs", StringComparison.Ordinal)) + .ThenBy(name => name, StringComparer.Ordinal)) { - if (!resourceName.StartsWith(expectedPrefix)) - { - continue; - } - using var resourceStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName); if (resourceStream is null) { From 33b996ddae6e87a2a1d8c05695b4394c0396413f Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Thu, 17 Sep 2026 12:13:41 -0600 Subject: [PATCH 32/32] Test before versioned release build Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/build-release.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index b73370cf9..d980617c8 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -40,9 +40,11 @@ jobs: ref: ${{ needs.update-packagejson.outputs.sha }} fetch-depth: 0 - uses: ./.github/actions/setup-dotnet - # pack nuget + # The source generator snapshot tests load a project reference rather than the version-stamped analyzer. + - run: dotnet test -c Release + - run: git clean -fdx + # Pack NuGet with the requested release version. - run: dotnet build -c Release -p:Version=${{ needs.update-packagejson.outputs.normalized_tag }} - - run: dotnet test -c Release --no-build - run: dotnet pack -c Release -p:Version=${{ needs.update-packagejson.outputs.normalized_tag }} -o ./publish - name: upload artifacts uses: actions/upload-artifact@v7 # must sync with actions/download-artifact@v8 in create-release