diff --git a/src/Neo.VM/JumpTable/JumpTable.Splice.cs b/src/Neo.VM/JumpTable/JumpTable.Splice.cs index 7192872d81..0a56099b2f 100644 --- a/src/Neo.VM/JumpTable/JumpTable.Splice.cs +++ b/src/Neo.VM/JumpTable/JumpTable.Splice.cs @@ -101,7 +101,7 @@ public virtual void SubStr(ExecutionEngine engine, Instruction instruction) if (index < 0) throw new InvalidOperationException($"The index can not be negative for {nameof(OpCode.SUBSTR)}, index: {index}."); var x = engine.Pop().GetSpan(); - if (index + count > x.Length) + if (checked(index + count) > x.Length) throw new InvalidOperationException($"The index + count is out of range for {nameof(OpCode.SUBSTR)}, index: {index}, count: {count}, {index + count}/[0, {x.Length}]."); Types.Buffer result = new(count, false); x.Slice(index, count).CopyTo(result.InnerBuffer.Span); diff --git a/src/Neo/SmartContract/ApplicationEngine.cs b/src/Neo/SmartContract/ApplicationEngine.cs index 7808baae5e..64d9dd7f9c 100644 --- a/src/Neo/SmartContract/ApplicationEngine.cs +++ b/src/Neo/SmartContract/ApplicationEngine.cs @@ -34,6 +34,7 @@ namespace Neo.SmartContract public partial class ApplicationEngine : ExecutionEngine { protected static readonly JumpTable DefaultJumpTable = ComposeDefaultJumpTable(); + protected static readonly JumpTable NotEchidnaJumpTable = ComposeNotEchidnaJumpTable(); /// /// The maximum cost that can be spent when a contract is executed in test mode. @@ -214,6 +215,13 @@ private static JumpTable ComposeDefaultJumpTable() return table; } + public static JumpTable ComposeNotEchidnaJumpTable() + { + var jumpTable = ComposeDefaultJumpTable(); + jumpTable[OpCode.SUBSTR] = VulnerableSubStr; + return jumpTable; + } + protected static void OnCallT(ExecutionEngine engine, Instruction instruction) { if (engine is ApplicationEngine app) @@ -399,13 +407,42 @@ internal override void UnloadContext(ExecutionContext context) /// The engine instance created. public static ApplicationEngine Create(TriggerType trigger, IVerifiable container, DataCache snapshot, Block persistingBlock = null, ProtocolSettings settings = null, long gas = TestModeGas, IDiagnostic diagnostic = null) { + var index = persistingBlock?.Index ?? NativeContract.Ledger.CurrentIndex(snapshot); + // Adjust jump table according persistingBlock - var jumpTable = ApplicationEngine.DefaultJumpTable; + + var jumpTable = settings.IsHardforkEnabled(Hardfork.HF_Echidna, index) ? DefaultJumpTable : NotEchidnaJumpTable; return Provider?.Create(trigger, container, snapshot, persistingBlock, settings, gas, diagnostic, jumpTable) ?? new ApplicationEngine(trigger, container, snapshot, persistingBlock, settings, gas, diagnostic, jumpTable); } + /// + /// Extracts a substring from the specified buffer and pushes it onto the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 3, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void VulnerableSubStr(ExecutionEngine engine, Instruction instruction) + { + var count = (int)engine.Pop().GetInteger(); + if (count < 0) + throw new InvalidOperationException($"The count can not be negative for {nameof(OpCode.SUBSTR)}, count: {count}."); + var index = (int)engine.Pop().GetInteger(); + if (index < 0) + throw new InvalidOperationException($"The index can not be negative for {nameof(OpCode.SUBSTR)}, index: {index}."); + var x = engine.Pop().GetSpan(); + // Note: here it's the main change + if (index + count > x.Length) + throw new InvalidOperationException($"The index + count is out of range for {nameof(OpCode.SUBSTR)}, index: {index}, count: {count}, {index + count}/[0, {x.Length}]."); + + VM.Types.Buffer result = new(count, false); + x.Slice(index, count).CopyTo(result.InnerBuffer.Span); + engine.Push(result); + } + public override void LoadContext(ExecutionContext context) { // Set default execution context state diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Splice/SUBSTR.json b/tests/Neo.VM.Tests/Tests/OpCodes/Splice/SUBSTR.json index 7b78d7e053..13bf8b9b59 100644 --- a/tests/Neo.VM.Tests/Tests/OpCodes/Splice/SUBSTR.json +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Splice/SUBSTR.json @@ -473,6 +473,50 @@ } } ] + }, + { + "name": "Count Exceed Range Test", + "script": [ + "PUSHDATA1", + "0x0a", + "0x00010203040506070809", + "PUSH2", + "PUSHINT32", + "0x7FFFFFFF", + "SUBSTR" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Index Exceed Range Test", + "script": [ + "PUSHDATA1", + "0x0a", + "0x00010203040506070809", + "PUSHINT32", + "0x7FFFFFFF", + "PUSH2", + "SUBSTR" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "FAULT" + } + } + ] } ] }