Skip to content

Commit 1bf6c2f

Browse files
authored
Merge branch 'master-n3' into feat/ownable-two-step
2 parents f569710 + f36ab74 commit 1bf6c2f

4 files changed

Lines changed: 122 additions & 6 deletions

File tree

src/Neo.Compiler.CSharp/Optimizer/DumpNef.cs

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ namespace Neo.Optimizer
2828
{
2929
internal static class DumpNef
3030
{
31+
internal const int MaxDebugInfoJsonBytes = 16 * 1024 * 1024;
32+
private const int CopyBufferSize = 81920;
33+
3134
internal static readonly Regex DocumentRegex = new(@"\[(\d+)\](\d+)\:(\d+)\-(\d+)\:(\d+)", RegexOptions.Compiled);
3235
internal static readonly Regex RangeRegex = new(@"(\d+)\-(\d+)", RegexOptions.Compiled);
3336
internal static readonly Regex SequencePointRegex = new(@"(\d+)(\[\d+\]\d+\:\d+\-\d+\:\d+)", RegexOptions.Compiled);
@@ -55,15 +58,30 @@ public static string UnzipDebugInfo(byte[] zippedBuffer)
5558
var entry = archive.Entries.FirstOrDefault();
5659
if (entry != null)
5760
{
61+
if (entry.Length > MaxDebugInfoJsonBytes)
62+
throw new InvalidDataException($"Debug info JSON exceeds the maximum size of {MaxDebugInfoJsonBytes} bytes.");
5863
using var unzippedEntryStream = entry.Open();
59-
using var ms = new MemoryStream();
60-
unzippedEntryStream.CopyTo(ms);
61-
var unzippedArray = ms.ToArray();
62-
return Encoding.UTF8.GetString(unzippedArray);
64+
return Encoding.UTF8.GetString(ReadToEndWithLimit(unzippedEntryStream, MaxDebugInfoJsonBytes));
6365
}
6466
throw new ArgumentException("No file found in zip archive");
6567
}
6668

69+
private static byte[] ReadToEndWithLimit(Stream stream, int maxBytes)
70+
{
71+
using var ms = new MemoryStream();
72+
byte[] buffer = new byte[CopyBufferSize];
73+
int read;
74+
long totalRead = 0;
75+
while ((read = stream.Read(buffer, 0, buffer.Length)) > 0)
76+
{
77+
totalRead += read;
78+
if (totalRead > maxBytes)
79+
throw new InvalidDataException($"Debug info JSON exceeds the maximum size of {maxBytes} bytes.");
80+
ms.Write(buffer, 0, read);
81+
}
82+
return ms.ToArray();
83+
}
84+
6785
public static string GetInstructionAddressPadding(this Script script)
6886
{
6987
var digitCount = EnumerateInstructions(script).Last().address switch

src/Neo.SmartContract.Testing/Coverage/NeoDebugInfo.cs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
using System.IO;
1717
using System.IO.Compression;
1818
using System.Linq;
19+
using System.Text;
1920
using System.Text.RegularExpressions;
2021

2122
namespace Neo.SmartContract.Testing.Coverage
@@ -37,6 +38,9 @@ namespace Neo.SmartContract.Testing.Coverage
3738
/// </remarks>
3839
public partial class NeoDebugInfo(UInt160 hash, string documentRoot, IReadOnlyList<string> documents, IReadOnlyList<NeoDebugInfo.Method> methods)
3940
{
41+
internal const int MaxDebugInfoJsonBytes = 16 * 1024 * 1024;
42+
private const int CopyBufferSize = 81920;
43+
4044
static readonly Regex spRegex = new(@"^(\d+)\[(-?\d+)\](\d+)\:(\d+)\-(\d+)\:(\d+)$");
4145

4246
public const string MANIFEST_FILE_EXTENSION = ".manifest.json";
@@ -168,6 +172,8 @@ internal static bool TryLoadCompressed(Stream stream, [MaybeNullWhen(false)] out
168172
{
169173
if (entry.FullName.EndsWith(DEBUG_JSON_EXTENSION, StringComparison.OrdinalIgnoreCase))
170174
{
175+
if (entry.Length > MaxDebugInfoJsonBytes)
176+
throw new InvalidDataException($"Debug info JSON exceeds the maximum size of {MaxDebugInfoJsonBytes} bytes.");
171177
using var entryStream = entry.Open();
172178
debugInfo = Load(entryStream);
173179
return true;
@@ -199,13 +205,28 @@ static bool TryLoadUncompressed(string debugInfoPath, [MaybeNullWhen(false)] out
199205

200206
internal static NeoDebugInfo Load(Stream stream)
201207
{
202-
using StreamReader reader = new(stream);
203-
var text = reader.ReadToEnd();
208+
var text = ReadUtf8TextWithLimit(stream, MaxDebugInfoJsonBytes);
204209
var json = JToken.Parse(text) ?? throw new InvalidOperationException();
205210
if (json is not JObject jo) throw new FormatException("The debug info root must be a JSON object.");
206211
return FromDebugInfoJson(jo);
207212
}
208213

214+
private static string ReadUtf8TextWithLimit(Stream stream, int maxBytes)
215+
{
216+
using var ms = new MemoryStream();
217+
byte[] buffer = new byte[CopyBufferSize];
218+
int read;
219+
long totalRead = 0;
220+
while ((read = stream.Read(buffer, 0, buffer.Length)) > 0)
221+
{
222+
totalRead += read;
223+
if (totalRead > maxBytes)
224+
throw new InvalidDataException($"Debug info JSON exceeds the maximum size of {maxBytes} bytes.");
225+
ms.Write(buffer, 0, read);
226+
}
227+
return Encoding.UTF8.GetString(ms.ToArray());
228+
}
229+
209230
public static NeoDebugInfo FromDebugInfoJson(string json)
210231
{
211232
var jsonToken = JToken.Parse(json);

tests/Neo.Compiler.CSharp.UnitTests/Peripheral/UnitTest_DumpNef.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,38 @@
1818
using System.Collections.Generic;
1919
using System.IO;
2020
using System.Linq;
21+
using System.Text;
2122
using System.Text.RegularExpressions;
2223
using static System.Net.Mime.MediaTypeNames;
2324

2425
namespace Neo.Compiler.CSharp.UnitTests.Peripheral
2526
{
27+
[TestClass]
28+
public class UnitTest_DumpNefDebugInfoLimits
29+
{
30+
private const int MaxDebugInfoJsonBytes = 16 * 1024 * 1024;
31+
32+
[TestMethod]
33+
public void Test_UnzipDebugInfoReadsSmallArchive()
34+
{
35+
const string json = "{\"hash\":\"0x0000000000000000000000000000000000000000\",\"documents\":[],\"methods\":[]}";
36+
byte[] archive = DumpNef.ZipDebugInfo(Encoding.UTF8.GetBytes(json), "test.debug.json");
37+
38+
Assert.AreEqual(json, DumpNef.UnzipDebugInfo(archive));
39+
}
40+
41+
[TestMethod]
42+
public void Test_UnzipDebugInfoRejectsOversizedArchive()
43+
{
44+
string json = "{\"hash\":\"0x0000000000000000000000000000000000000000\",\"documents\":[\"" +
45+
new string('a', MaxDebugInfoJsonBytes) +
46+
"\"],\"methods\":[]}";
47+
byte[] archive = DumpNef.ZipDebugInfo(Encoding.UTF8.GetBytes(json), "test.debug.json");
48+
49+
Assert.ThrowsException<InvalidDataException>(() => DumpNef.UnzipDebugInfo(archive));
50+
}
51+
}
52+
2653
[TestClass]
2754
public class UnitTest_DumpNef
2855
{

tests/Neo.SmartContract.Testing.UnitTests/Coverage/CoverageDataTests.cs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
using Neo.SmartContract.Testing.Coverage.Formats;
1616
using System;
1717
using System.IO;
18+
using System.IO.Compression;
1819
using System.Numerics;
1920
using System.Reflection;
2021
using System.Text;
@@ -26,6 +27,7 @@ namespace Neo.SmartContract.Testing.UnitTests.Coverage
2627
public class CoverageDataTests
2728
{
2829
private static readonly Regex WhiteSpaceRegex = new("\\s");
30+
private const int MaxDebugInfoJsonBytes = 16 * 1024 * 1024;
2931

3032
[TestMethod]
3133
public void NeoDebugInfoLoadThrowsHelpfulMessageForNonObjectJson()
@@ -43,6 +45,54 @@ public void NeoDebugInfoLoadThrowsHelpfulMessageForNonObjectJson()
4345
Assert.AreEqual("The debug info root must be a JSON object.", exception.InnerException!.Message);
4446
}
4547

48+
[TestMethod]
49+
public void NeoDebugInfoTryLoadCompressedLoadsSmallDebugInfo()
50+
{
51+
const string json = "{\"hash\":\"0x0000000000000000000000000000000000000000\",\"documents\":[],\"methods\":[]}";
52+
byte[] archive = CreateDebugInfoArchive(json);
53+
54+
Assert.IsTrue(TryLoadCompressed(archive, out var debugInfo));
55+
Assert.IsNotNull(debugInfo);
56+
Assert.AreEqual(UInt160.Zero, debugInfo.Hash);
57+
}
58+
59+
[TestMethod]
60+
public void NeoDebugInfoTryLoadCompressedRejectsOversizedDebugInfo()
61+
{
62+
string json = "{\"hash\":\"0x0000000000000000000000000000000000000000\",\"documents\":[\"" +
63+
new string('a', MaxDebugInfoJsonBytes) +
64+
"\"],\"methods\":[]}";
65+
byte[] archive = CreateDebugInfoArchive(json);
66+
67+
Assert.IsFalse(TryLoadCompressed(archive, out var debugInfo));
68+
Assert.IsNull(debugInfo);
69+
}
70+
71+
private static byte[] CreateDebugInfoArchive(string json)
72+
{
73+
using var compressedFileStream = new MemoryStream();
74+
using (var zipArchive = new ZipArchive(compressedFileStream, ZipArchiveMode.Create, false))
75+
{
76+
var zipEntry = zipArchive.CreateEntry("test.debug.json");
77+
using var zipEntryStream = zipEntry.Open();
78+
byte[] content = Encoding.UTF8.GetBytes(json);
79+
zipEntryStream.Write(content, 0, content.Length);
80+
}
81+
return compressedFileStream.ToArray();
82+
}
83+
84+
private static bool TryLoadCompressed(byte[] archive, out NeoDebugInfo? debugInfo)
85+
{
86+
var tryLoadCompressed = typeof(NeoDebugInfo).GetMethod("TryLoadCompressed", BindingFlags.Static | BindingFlags.NonPublic, [typeof(Stream), typeof(NeoDebugInfo).MakeByRefType()]);
87+
Assert.IsNotNull(tryLoadCompressed);
88+
89+
using var stream = new MemoryStream(archive);
90+
object?[] args = [stream, null];
91+
bool loaded = (bool)tryLoadCompressed!.Invoke(null, args)!;
92+
debugInfo = (NeoDebugInfo?)args[1];
93+
return loaded;
94+
}
95+
4696
[TestMethod]
4797
public void TestDump()
4898
{

0 commit comments

Comments
 (0)