diff --git a/src/neo/IO/Json/JPathToken.cs b/src/neo/IO/Json/JPathToken.cs index 8599cd3020..3ce73f5d4f 100644 --- a/src/neo/IO/Json/JPathToken.cs +++ b/src/neo/IO/Json/JPathToken.cs @@ -122,16 +122,17 @@ private static JPathToken DequeueToken(Queue tokens) public static void ProcessJsonPath(ref JObject[] objects, Queue tokens) { int maxDepth = 6; + int maxObjects = 1024; while (tokens.Count > 0) { JPathToken token = DequeueToken(tokens); switch (token.Type) { case JPathTokenType.Dot: - ProcessDot(ref objects, ref maxDepth, tokens); + ProcessDot(ref objects, ref maxDepth, maxObjects, tokens); break; case JPathTokenType.LeftBracket: - ProcessBracket(ref objects, ref maxDepth, tokens); + ProcessBracket(ref objects, ref maxDepth, maxObjects, tokens); break; default: throw new FormatException(); @@ -139,26 +140,26 @@ public static void ProcessJsonPath(ref JObject[] objects, Queue toke } } - private static void ProcessDot(ref JObject[] objects, ref int maxDepth, Queue tokens) + private static void ProcessDot(ref JObject[] objects, ref int maxDepth, int maxObjects, Queue tokens) { JPathToken token = DequeueToken(tokens); switch (token.Type) { case JPathTokenType.Asterisk: - Descent(ref objects, ref maxDepth); + Descent(ref objects, ref maxDepth, maxObjects); break; case JPathTokenType.Dot: - ProcessRecursiveDescent(ref objects, ref maxDepth, tokens); + ProcessRecursiveDescent(ref objects, ref maxDepth, maxObjects, tokens); break; case JPathTokenType.Identifier: - Descent(ref objects, ref maxDepth, token.Content); + Descent(ref objects, ref maxDepth, maxObjects, token.Content); break; default: throw new FormatException(); } } - private static void ProcessBracket(ref JObject[] objects, ref int maxDepth, Queue tokens) + private static void ProcessBracket(ref JObject[] objects, ref int maxDepth, int maxObjects, Queue tokens) { JPathToken token = DequeueToken(tokens); switch (token.Type) @@ -166,23 +167,23 @@ private static void ProcessBracket(ref JObject[] objects, ref int maxDepth, Queu case JPathTokenType.Asterisk: if (DequeueToken(tokens).Type != JPathTokenType.RightBracket) throw new FormatException(); - Descent(ref objects, ref maxDepth); + Descent(ref objects, ref maxDepth, maxObjects); break; case JPathTokenType.Colon: - ProcessSlice(ref objects, ref maxDepth, tokens, 0); + ProcessSlice(ref objects, ref maxDepth, maxObjects, tokens, 0); break; case JPathTokenType.Number: JPathToken next = DequeueToken(tokens); switch (next.Type) { case JPathTokenType.Colon: - ProcessSlice(ref objects, ref maxDepth, tokens, int.Parse(token.Content)); + ProcessSlice(ref objects, ref maxDepth, maxObjects, tokens, int.Parse(token.Content)); break; case JPathTokenType.Comma: - ProcessUnion(ref objects, ref maxDepth, tokens, token); + ProcessUnion(ref objects, ref maxDepth, maxObjects, tokens, token); break; case JPathTokenType.RightBracket: - Descent(ref objects, ref maxDepth, int.Parse(token.Content)); + Descent(ref objects, ref maxDepth, maxObjects, int.Parse(token.Content)); break; default: throw new FormatException(); @@ -193,10 +194,10 @@ private static void ProcessBracket(ref JObject[] objects, ref int maxDepth, Queu switch (next.Type) { case JPathTokenType.Comma: - ProcessUnion(ref objects, ref maxDepth, tokens, token); + ProcessUnion(ref objects, ref maxDepth, maxObjects, tokens, token); break; case JPathTokenType.RightBracket: - Descent(ref objects, ref maxDepth, JObject.Parse($"\"{token.Content.Trim('\'')}\"").GetString()); + Descent(ref objects, ref maxDepth, maxObjects, JObject.Parse($"\"{token.Content.Trim('\'')}\"").GetString()); break; default: throw new FormatException(); @@ -207,7 +208,7 @@ private static void ProcessBracket(ref JObject[] objects, ref int maxDepth, Queu } } - private static void ProcessRecursiveDescent(ref JObject[] objects, ref int maxDepth, Queue tokens) + private static void ProcessRecursiveDescent(ref JObject[] objects, ref int maxDepth, int maxObjects, Queue tokens) { List results = new(); JPathToken token = DequeueToken(tokens); @@ -215,12 +216,13 @@ private static void ProcessRecursiveDescent(ref JObject[] objects, ref int maxDe while (objects.Length > 0) { results.AddRange(objects.Where(p => p is not null).SelectMany(p => p.Properties).Where(p => p.Key == token.Content).Select(p => p.Value)); - Descent(ref objects, ref maxDepth); + Descent(ref objects, ref maxDepth, maxObjects); + if (results.Count > maxObjects) throw new InvalidOperationException(nameof(maxObjects)); } objects = results.ToArray(); } - private static void ProcessSlice(ref JObject[] objects, ref int maxDepth, Queue tokens, int start) + private static void ProcessSlice(ref JObject[] objects, ref int maxDepth, int maxObjects, Queue tokens, int start) { JPathToken token = DequeueToken(tokens); switch (token.Type) @@ -228,17 +230,17 @@ private static void ProcessSlice(ref JObject[] objects, ref int maxDepth, Queue< case JPathTokenType.Number: if (DequeueToken(tokens).Type != JPathTokenType.RightBracket) throw new FormatException(); - DescentRange(ref objects, ref maxDepth, start, int.Parse(token.Content)); + DescentRange(ref objects, ref maxDepth, maxObjects, start, int.Parse(token.Content)); break; case JPathTokenType.RightBracket: - DescentRange(ref objects, ref maxDepth, start, 0); + DescentRange(ref objects, ref maxDepth, maxObjects, start, 0); break; default: throw new FormatException(); } } - private static void ProcessUnion(ref JObject[] objects, ref int maxDepth, Queue tokens, JPathToken first) + private static void ProcessUnion(ref JObject[] objects, ref int maxDepth, int maxObjects, Queue tokens, JPathToken first) { List items = new() { first }; while (true) @@ -255,24 +257,25 @@ private static void ProcessUnion(ref JObject[] objects, ref int maxDepth, Queue< switch (first.Type) { case JPathTokenType.Number: - Descent(ref objects, ref maxDepth, items.Select(p => int.Parse(p.Content)).ToArray()); + Descent(ref objects, ref maxDepth, maxObjects, items.Select(p => int.Parse(p.Content)).ToArray()); break; case JPathTokenType.String: - Descent(ref objects, ref maxDepth, items.Select(p => JObject.Parse($"\"{p.Content.Trim('\'')}\"").GetString()).ToArray()); + Descent(ref objects, ref maxDepth, maxObjects, items.Select(p => JObject.Parse($"\"{p.Content.Trim('\'')}\"").GetString()).ToArray()); break; default: throw new FormatException(); } } - private static void Descent(ref JObject[] objects, ref int maxDepth) + private static void Descent(ref JObject[] objects, ref int maxDepth, int maxObjects) { if (maxDepth <= 0) throw new InvalidOperationException(); --maxDepth; objects = objects.Where(p => p is not null).SelectMany(p => p is JArray array ? array : p.Properties.Values).ToArray(); + if (objects.Length > maxObjects) throw new InvalidOperationException(nameof(maxObjects)); } - private static void Descent(ref JObject[] objects, ref int maxDepth, params string[] names) + private static void Descent(ref JObject[] objects, ref int maxDepth, int maxObjects, params string[] names) { static IEnumerable GetProperties(JObject obj, string[] names) { @@ -283,9 +286,10 @@ static IEnumerable GetProperties(JObject obj, string[] names) if (maxDepth <= 0) throw new InvalidOperationException(); --maxDepth; objects = objects.SelectMany(p => GetProperties(p, names)).ToArray(); + if (objects.Length > maxObjects) throw new InvalidOperationException(nameof(maxObjects)); } - private static void Descent(ref JObject[] objects, ref int maxDepth, params int[] indexes) + private static void Descent(ref JObject[] objects, ref int maxDepth, int maxObjects, params int[] indexes) { static IEnumerable GetElements(JArray array, int[] indexes) { @@ -299,9 +303,10 @@ static IEnumerable GetElements(JArray array, int[] indexes) if (maxDepth <= 0) throw new InvalidOperationException(); --maxDepth; objects = objects.OfType().SelectMany(p => GetElements(p, indexes)).ToArray(); + if (objects.Length > maxObjects) throw new InvalidOperationException(nameof(maxObjects)); } - private static void DescentRange(ref JObject[] objects, ref int maxDepth, int start, int end) + private static void DescentRange(ref JObject[] objects, ref int maxDepth, int maxObjects, int start, int end) { if (maxDepth <= 0) throw new InvalidOperationException(); --maxDepth; @@ -313,6 +318,7 @@ private static void DescentRange(ref JObject[] objects, ref int maxDepth, int st int count = iEnd - iStart; return p.Skip(iStart).Take(count); }).ToArray(); + if (objects.Length > maxObjects) throw new InvalidOperationException(nameof(maxObjects)); } } } diff --git a/src/neo/Network/P2P/RemoteNode.ProtocolHandler.cs b/src/neo/Network/P2P/RemoteNode.ProtocolHandler.cs index ad73c50456..d933bb119f 100644 --- a/src/neo/Network/P2P/RemoteNode.ProtocolHandler.cs +++ b/src/neo/Network/P2P/RemoteNode.ProtocolHandler.cs @@ -304,7 +304,7 @@ private void OnHeadersMessageReceived(HeadersPayload payload) private void OnInventoryReceived(IInventory inventory) { - knownHashes.Add(inventory.Hash); + if (!knownHashes.Add(inventory.Hash)) return; pendingKnownHashes.Remove(inventory.Hash); system.TaskManager.Tell(inventory); switch (inventory) diff --git a/src/neo/SmartContract/Native/CryptoLib.cs b/src/neo/SmartContract/Native/CryptoLib.cs index 5b06de66eb..bfd6edaada 100644 --- a/src/neo/SmartContract/Native/CryptoLib.cs +++ b/src/neo/SmartContract/Native/CryptoLib.cs @@ -50,6 +50,19 @@ public static byte[] Sha256(byte[] data) return data.Sha256(); } + /// + /// Computes the hash value for the specified byte array using the murmur32 algorithm. + /// + /// The input to compute the hash code for. + /// The seed of the murmur32 hash function + /// The computed hash code. + [ContractMethod(CpuFee = 1 << 13)] + public static byte[] Murmur32(byte[] data, uint seed) + { + using Murmur32 murmur = new(seed); + return murmur.ComputeHash(data); + } + /// /// Verifies that a digital signature is appropriate for the provided key and message using the ECDSA algorithm. /// diff --git a/src/neo/VM/Helper.cs b/src/neo/VM/Helper.cs index 49a5a78f4b..c719acde11 100644 --- a/src/neo/VM/Helper.cs +++ b/src/neo/VM/Helper.cs @@ -278,48 +278,105 @@ public static byte[] MakeScript(this UInt160 scriptHash, string method, params o /// Converts the to a JSON object. /// /// The to convert. + /// The maximum size in bytes of the result. /// The represented by a JSON object. - public static JObject ToJson(this StackItem item) + public static JObject ToJson(this StackItem item, int maxSize = int.MaxValue) { - return ToJson(item, null); + return ToJson(item, null, ref maxSize); } - private static JObject ToJson(StackItem item, HashSet context) + /// + /// Converts the to a JSON object. + /// + /// The to convert. + /// The maximum size in bytes of the result. + /// The represented by a JSON object. + public static JArray ToJson(this EvaluationStack stack, int maxSize = int.MaxValue) + { + if (maxSize <= 0) throw new ArgumentOutOfRangeException(nameof(maxSize)); + maxSize -= 2/*[]*/+ Math.Max(0, (stack.Count - 1))/*,*/; + JArray result = new(); + foreach (var item in stack) + result.Add(ToJson(item, null, ref maxSize)); + if (maxSize < 0) throw new InvalidOperationException("Max size reached."); + return result; + } + + private static JObject ToJson(StackItem item, HashSet context, ref int maxSize) { - JObject json = new(); - json["type"] = item.Type; + JObject json = new() + { + ["type"] = item.Type + }; + JObject value = null; + maxSize -= 11/*{"type":""}*/+ item.Type.ToString().Length; switch (item) { case Array array: - context ??= new HashSet(ReferenceEqualityComparer.Instance); - if (!context.Add(array)) throw new InvalidOperationException(); - json["value"] = new JArray(array.Select(p => ToJson(p, context))); - break; + { + context ??= new HashSet(ReferenceEqualityComparer.Instance); + if (!context.Add(array)) throw new InvalidOperationException(); + maxSize -= 2/*[]*/+ Math.Max(0, (array.Count - 1))/*,*/; + JArray a = new(); + foreach (StackItem stackItem in array) + a.Add(ToJson(stackItem, context, ref maxSize)); + value = a; + break; + } case Boolean boolean: - json["value"] = boolean.GetBoolean(); - break; + { + bool b = boolean.GetBoolean(); + maxSize -= b ? 4/*true*/: 5/*false*/; + value = b; + break; + } case Buffer _: case ByteString _: - json["value"] = Convert.ToBase64String(item.GetSpan()); - break; + { + string s = Convert.ToBase64String(item.GetSpan()); + maxSize -= 2/*""*/+ s.Length; + value = s; + break; + } case Integer integer: - json["value"] = integer.GetInteger().ToString(); - break; + { + string s = integer.GetInteger().ToString(); + maxSize -= 2/*""*/+ s.Length; + value = s; + break; + } case Map map: - context ??= new HashSet(ReferenceEqualityComparer.Instance); - if (!context.Add(map)) throw new InvalidOperationException(); - json["value"] = new JArray(map.Select(p => { - JObject item = new(); - item["key"] = ToJson(p.Key, context); - item["value"] = ToJson(p.Value, context); - return item; - })); - break; + context ??= new HashSet(ReferenceEqualityComparer.Instance); + if (!context.Add(map)) throw new InvalidOperationException(); + maxSize -= 2/*[]*/+ Math.Max(0, (map.Count - 1))/*,*/; + JArray a = new(); + foreach (var (k, v) in map) + { + maxSize -= 17/*{"key":,"value":}*/; + JObject i = new() + { + ["key"] = ToJson(k, context, ref maxSize), + ["value"] = ToJson(v, context, ref maxSize) + }; + a.Add(i); + } + value = a; + break; + } case Pointer pointer: - json["value"] = pointer.Position; - break; + { + maxSize -= pointer.Position.ToString().Length; + value = pointer.Position; + break; + } + } + if (value is not null) + { + maxSize -= 9/*,"value":*/; + json["value"] = value; } + if (maxSize < 0) throw new InvalidOperationException("Max size reached."); return json; } diff --git a/src/neo/neo.csproj b/src/neo/neo.csproj index 129d2add66..e377320734 100644 --- a/src/neo/neo.csproj +++ b/src/neo/neo.csproj @@ -1,9 +1,9 @@ - 2015-2021 The Neo Project + 2015-2022 The Neo Project Neo - 3.1.0 + 3.2.0 The Neo Project net6.0 true @@ -25,11 +25,11 @@ - - - - - + + + + + diff --git a/tests/neo.UnitTests/IO/Json/UT_JPath.cs b/tests/neo.UnitTests/IO/Json/UT_JPath.cs index 64b5cfdceb..d3d085c4d3 100644 --- a/tests/neo.UnitTests/IO/Json/UT_JPath.cs +++ b/tests/neo.UnitTests/IO/Json/UT_JPath.cs @@ -1,6 +1,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.IO.Json; using System; +using System.Linq; namespace Neo.UnitTests.IO.Json { @@ -54,6 +55,13 @@ public class UT_JPath ["data"] = null, }; + [TestMethod] + public void TestOOM() + { + var filter = "$" + string.Concat(Enumerable.Repeat("[0" + string.Concat(Enumerable.Repeat(",0", 64)) + "]", 6)); + Assert.ThrowsException(() => JObject.Parse("[[[[[[{}]]]]]]").JsonPath(filter)); + } + [TestMethod] public void TestJsonPath() { diff --git a/tests/neo.UnitTests/Wallets/NEP6/UT_NEP6Account.cs b/tests/neo.UnitTests/Wallets/NEP6/UT_NEP6Account.cs index f03f23aeff..d3b5a738bb 100644 --- a/tests/neo.UnitTests/Wallets/NEP6/UT_NEP6Account.cs +++ b/tests/neo.UnitTests/Wallets/NEP6/UT_NEP6Account.cs @@ -136,7 +136,7 @@ public void TestToJson() nep6contract["deployed"] = false; _account.Contract = NEP6Contract.FromJson(nep6contract); JObject json = _account.ToJson(); - json["address"].Should().Equals("AZk5bAanTtD6AvpeesmYgL8CLRYUt5JQsX"); + json["address"].AsString().Should().Be("NdtB8RXRmJ7Nhw1FPTm7E6HoDZGnDw37nf"); json["label"].Should().BeNull(); json["isDefault"].ToString().Should().Be("false"); json["lock"].ToString().Should().Be("false"); diff --git a/tests/neo.UnitTests/neo.UnitTests.csproj b/tests/neo.UnitTests/neo.UnitTests.csproj index f31f2b45a7..c769e172b4 100644 --- a/tests/neo.UnitTests/neo.UnitTests.csproj +++ b/tests/neo.UnitTests/neo.UnitTests.csproj @@ -9,13 +9,13 @@ - - - - - - - + + + + + + +