Skip to content

Commit

Permalink
Restrict the serialization (neo-project#1353)
Browse files Browse the repository at this point in the history
  • Loading branch information
erikzhang authored and Luchuan committed Jan 10, 2020
1 parent a29d18e commit f36d521
Show file tree
Hide file tree
Showing 8 changed files with 247 additions and 42 deletions.
19 changes: 12 additions & 7 deletions src/neo/IO/Json/JObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,12 +138,7 @@ private static JObject ReadObject(ref Utf8JsonReader reader)
throw new FormatException();
}

public override string ToString()
{
return ToString(false);
}

public string ToString(bool indented)
public byte[] ToByteArray(bool indented)
{
using MemoryStream ms = new MemoryStream();
using Utf8JsonWriter writer = new Utf8JsonWriter(ms, new JsonWriterOptions
Expand All @@ -153,7 +148,17 @@ public string ToString(bool indented)
});
Write(writer);
writer.Flush();
return Encoding.UTF8.GetString(ms.ToArray());
return ms.ToArray();
}

public override string ToString()
{
return ToString(false);
}

public string ToString(bool indented)
{
return Encoding.UTF8.GetString(ToByteArray(indented));
}

public virtual T TryGetEnum<T>(T defaultValue = default, bool ignoreCase = false) where T : Enum
Expand Down
32 changes: 22 additions & 10 deletions src/neo/SmartContract/InteropService.NEO.cs
Original file line number Diff line number Diff line change
Expand Up @@ -376,21 +376,33 @@ private static bool Iterator_Concat(ApplicationEngine engine)

private static bool Json_Deserialize(ApplicationEngine engine)
{
var json = engine.CurrentContext.EvaluationStack.Pop().GetString();
var obj = JObject.Parse(json, 10);
var item = JsonSerializer.Deserialize(obj, engine.ReferenceCounter);

engine.CurrentContext.EvaluationStack.Push(item);
return true;
var json = engine.CurrentContext.EvaluationStack.Pop().GetSpan();
try
{
var obj = JObject.Parse(json, 10);
var item = JsonSerializer.Deserialize(obj, engine.ReferenceCounter);
engine.CurrentContext.EvaluationStack.Push(item);
return true;
}
catch
{
return false;
}
}

private static bool Json_Serialize(ApplicationEngine engine)
{
var item = engine.CurrentContext.EvaluationStack.Pop();
var json = JsonSerializer.Serialize(item);

engine.CurrentContext.EvaluationStack.Push(json.ToString());
return true;
try
{
var json = JsonSerializer.SerializeToByteArray(item, engine.MaxItemSize);
engine.CurrentContext.EvaluationStack.Push(json);
return true;
}
catch
{
return false;
}
}
}
}
6 changes: 2 additions & 4 deletions src/neo/SmartContract/InteropService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -265,14 +265,12 @@ private static bool Runtime_Serialize(ApplicationEngine engine)
byte[] serialized;
try
{
serialized = StackItemSerializer.Serialize(engine.CurrentContext.EvaluationStack.Pop());
serialized = StackItemSerializer.Serialize(engine.CurrentContext.EvaluationStack.Pop(), engine.MaxItemSize);
}
catch (NotSupportedException)
catch
{
return false;
}
if (serialized.Length > engine.MaxItemSize)
return false;
engine.CurrentContext.EvaluationStack.Push(serialized);
return true;
}
Expand Down
69 changes: 69 additions & 0 deletions src/neo/SmartContract/JsonSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@
using Neo.VM;
using Neo.VM.Types;
using System;
using System.Collections;
using System.IO;
using System.Linq;
using System.Numerics;
using System.Text.Json;
using Array = Neo.VM.Types.Array;
using Boolean = Neo.VM.Types.Boolean;

Expand Down Expand Up @@ -61,6 +64,72 @@ public static JObject Serialize(StackItem item)
}
}

public static byte[] SerializeToByteArray(StackItem item, uint maxSize)
{
using MemoryStream ms = new MemoryStream();
using Utf8JsonWriter writer = new Utf8JsonWriter(ms, new JsonWriterOptions
{
Indented = false,
SkipValidation = false
});
Stack stack = new Stack();
stack.Push(item);
while (stack.Count > 0)
{
switch (stack.Pop())
{
case Array array:
writer.WriteStartArray();
stack.Push(JsonTokenType.EndArray);
for (int i = array.Count - 1; i >= 0; i--)
stack.Push(array[i]);
break;
case JsonTokenType.EndArray:
writer.WriteEndArray();
break;
case ByteArray buffer:
writer.WriteStringValue(Convert.ToBase64String(buffer.GetSpan()));
break;
case Integer num:
{
var integer = num.GetBigInteger();
if (integer > JNumber.MAX_SAFE_INTEGER || integer < JNumber.MIN_SAFE_INTEGER)
throw new InvalidOperationException();
writer.WriteNumberValue((double)num.GetBigInteger());
break;
}
case Boolean boolean:
writer.WriteBooleanValue(boolean.ToBoolean());
break;
case Map map:
writer.WriteStartObject();
stack.Push(JsonTokenType.EndObject);
foreach (var pair in map.Reverse())
{
stack.Push(pair.Value);
stack.Push(pair.Key);
stack.Push(JsonTokenType.PropertyName);
}
break;
case JsonTokenType.EndObject:
writer.WriteEndObject();
break;
case JsonTokenType.PropertyName:
writer.WritePropertyName(((PrimitiveType)stack.Pop()).GetSpan());
break;
case Null _:
writer.WriteNullValue();
break;
default:
throw new InvalidOperationException();
}
if (ms.Position > maxSize) throw new InvalidOperationException();
}
writer.Flush();
if (ms.Position > maxSize) throw new InvalidOperationException();
return ms.ToArray();
}

/// <summary>
/// Convert json object to stack item
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion src/neo/SmartContract/Native/Tokens/Nep5AccountState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ protected virtual void FromStruct(Struct @struct)

public byte[] ToByteArray()
{
return StackItemSerializer.Serialize(ToStruct());
return StackItemSerializer.Serialize(ToStruct(), 4096);
}

protected virtual Struct ToStruct()
Expand Down
8 changes: 5 additions & 3 deletions src/neo/SmartContract/StackItemSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,16 +116,16 @@ private static StackItem Deserialize(BinaryReader reader, uint maxItemSize, Refe
return stack_temp.Peek();
}

public static byte[] Serialize(StackItem item)
public static byte[] Serialize(StackItem item, uint maxSize)
{
using MemoryStream ms = new MemoryStream();
using BinaryWriter writer = new BinaryWriter(ms);
Serialize(item, writer);
Serialize(item, writer, maxSize);
writer.Flush();
return ms.ToArray();
}

private static void Serialize(StackItem item, BinaryWriter writer)
private static void Serialize(StackItem item, BinaryWriter writer, uint maxSize)
{
List<StackItem> serialized = new List<StackItem>();
Stack<StackItem> unserialized = new Stack<StackItem>();
Expand Down Expand Up @@ -177,6 +177,8 @@ private static void Serialize(StackItem item, BinaryWriter writer)
writer.Write((byte)StackItemType.Null);
break;
}
if (writer.BaseStream.Position > maxSize)
throw new InvalidOperationException();
}
}
}
Expand Down
35 changes: 18 additions & 17 deletions tests/neo.UnitTests/SmartContract/UT_StackItemSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,33 +11,34 @@ namespace Neo.UnitTests.SmartContract
[TestClass]
public class UT_StackItemSerializer
{
private const int MaxItemSize = 1024 * 1024;

[TestMethod]
public void TestSerialize()
{
byte[] result1 = StackItemSerializer.Serialize(new byte[5]);
byte[] result1 = StackItemSerializer.Serialize(new byte[5], MaxItemSize);
byte[] expectedArray1 = new byte[] {
0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00
};
Assert.AreEqual(Encoding.Default.GetString(expectedArray1), Encoding.Default.GetString(result1));

byte[] result2 = StackItemSerializer.Serialize(true);
byte[] result2 = StackItemSerializer.Serialize(true, MaxItemSize);
byte[] expectedArray2 = new byte[] {
0x01, 0x01
};
Assert.AreEqual(Encoding.Default.GetString(expectedArray2), Encoding.Default.GetString(result2));

byte[] result3 = StackItemSerializer.Serialize(1);
byte[] result3 = StackItemSerializer.Serialize(1, MaxItemSize);
byte[] expectedArray3 = new byte[] {
0x02, 0x01, 0x01
};
Assert.AreEqual(Encoding.Default.GetString(expectedArray3), Encoding.Default.GetString(result3));

StackItem stackItem4 = new InteropInterface<object>(new object());
Action action4 = () => StackItemSerializer.Serialize(stackItem4);
Action action4 = () => StackItemSerializer.Serialize(stackItem4, MaxItemSize);
action4.Should().Throw<NotSupportedException>();

byte[] result5 = StackItemSerializer.Serialize(1);
byte[] result5 = StackItemSerializer.Serialize(1, MaxItemSize);
byte[] expectedArray5 = new byte[] {
0x02, 0x01, 0x01
};
Expand All @@ -46,79 +47,79 @@ public void TestSerialize()

List<StackItem> list6 = new List<StackItem> { 1 };
StackItem stackItem62 = new VM.Types.Array(list6);
byte[] result6 = StackItemSerializer.Serialize(stackItem62);
byte[] result6 = StackItemSerializer.Serialize(stackItem62, MaxItemSize);
byte[] expectedArray6 = new byte[] {
0x80,0x01,0x02,0x01,0x01
};
Assert.AreEqual(Encoding.Default.GetString(expectedArray6), Encoding.Default.GetString(result6));

List<StackItem> list7 = new List<StackItem> { 1 };
StackItem stackItem72 = new Struct(list7);
byte[] result7 = StackItemSerializer.Serialize(stackItem72);
byte[] result7 = StackItemSerializer.Serialize(stackItem72, MaxItemSize);
byte[] expectedArray7 = new byte[] {
0x81,0x01,0x02,0x01,0x01
};
Assert.AreEqual(Encoding.Default.GetString(expectedArray7), Encoding.Default.GetString(result7));

Dictionary<PrimitiveType, StackItem> list8 = new Dictionary<PrimitiveType, StackItem> { [2] = 1 };
StackItem stackItem82 = new Map(list8);
byte[] result8 = StackItemSerializer.Serialize(stackItem82);
byte[] result8 = StackItemSerializer.Serialize(stackItem82, MaxItemSize);
byte[] expectedArray8 = new byte[] {
0x82,0x01,0x02,0x01,0x02,0x02,0x01,0x01
};
Assert.AreEqual(Encoding.Default.GetString(expectedArray8), Encoding.Default.GetString(result8));

Map stackItem91 = new Map();
stackItem91[1] = stackItem91;
Action action9 = () => StackItemSerializer.Serialize(stackItem91);
Action action9 = () => StackItemSerializer.Serialize(stackItem91, MaxItemSize);
action9.Should().Throw<NotSupportedException>();

VM.Types.Array stackItem10 = new VM.Types.Array();
stackItem10.Add(stackItem10);
Action action10 = () => StackItemSerializer.Serialize(stackItem10);
Action action10 = () => StackItemSerializer.Serialize(stackItem10, MaxItemSize);
action10.Should().Throw<NotSupportedException>();
}

[TestMethod]
public void TestDeserializeStackItem()
{
StackItem stackItem1 = new ByteArray(new byte[5]);
byte[] byteArray1 = StackItemSerializer.Serialize(stackItem1);
byte[] byteArray1 = StackItemSerializer.Serialize(stackItem1, MaxItemSize);
StackItem result1 = StackItemSerializer.Deserialize(byteArray1, (uint)byteArray1.Length);
Assert.AreEqual(stackItem1, result1);

StackItem stackItem2 = new VM.Types.Boolean(true);
byte[] byteArray2 = StackItemSerializer.Serialize(stackItem2);
byte[] byteArray2 = StackItemSerializer.Serialize(stackItem2, MaxItemSize);
StackItem result2 = StackItemSerializer.Deserialize(byteArray2, (uint)byteArray2.Length);
Assert.AreEqual(stackItem2, result2);

StackItem stackItem3 = new Integer(1);
byte[] byteArray3 = StackItemSerializer.Serialize(stackItem3);
byte[] byteArray3 = StackItemSerializer.Serialize(stackItem3, MaxItemSize);
StackItem result3 = StackItemSerializer.Deserialize(byteArray3, (uint)byteArray3.Length);
Assert.AreEqual(stackItem3, result3);

byte[] byteArray4 = StackItemSerializer.Serialize(1);
byte[] byteArray4 = StackItemSerializer.Serialize(1, MaxItemSize);
byteArray4[0] = 0x40;
Action action4 = () => StackItemSerializer.Deserialize(byteArray4, (uint)byteArray4.Length);
action4.Should().Throw<FormatException>();

List<StackItem> list5 = new List<StackItem> { 1 };
StackItem stackItem52 = new VM.Types.Array(list5);
byte[] byteArray5 = StackItemSerializer.Serialize(stackItem52);
byte[] byteArray5 = StackItemSerializer.Serialize(stackItem52, MaxItemSize);
StackItem result5 = StackItemSerializer.Deserialize(byteArray5, (uint)byteArray5.Length);
Assert.AreEqual(((VM.Types.Array)stackItem52).Count, ((VM.Types.Array)result5).Count);
Assert.AreEqual(((VM.Types.Array)stackItem52).GetEnumerator().Current, ((VM.Types.Array)result5).GetEnumerator().Current);

List<StackItem> list6 = new List<StackItem> { 1 };
StackItem stackItem62 = new Struct(list6);
byte[] byteArray6 = StackItemSerializer.Serialize(stackItem62);
byte[] byteArray6 = StackItemSerializer.Serialize(stackItem62, MaxItemSize);
StackItem result6 = StackItemSerializer.Deserialize(byteArray6, (uint)byteArray6.Length);
Assert.AreEqual(((Struct)stackItem62).Count, ((Struct)result6).Count);
Assert.AreEqual(((Struct)stackItem62).GetEnumerator().Current, ((Struct)result6).GetEnumerator().Current);

Dictionary<PrimitiveType, StackItem> list7 = new Dictionary<PrimitiveType, StackItem> { [2] = 1 };
StackItem stackItem72 = new Map(list7);
byte[] byteArray7 = StackItemSerializer.Serialize(stackItem72);
byte[] byteArray7 = StackItemSerializer.Serialize(stackItem72, MaxItemSize);
StackItem result7 = StackItemSerializer.Deserialize(byteArray7, (uint)byteArray7.Length);
Assert.AreEqual(((Map)stackItem72).Count, ((Map)result7).Count);
Assert.AreEqual(((Map)stackItem72).Keys.GetEnumerator().Current, ((Map)result7).Keys.GetEnumerator().Current);
Expand Down
Loading

0 comments on commit f36d521

Please sign in to comment.