From 25f197eeb9f6d01f3be730f579b172e517e29c90 Mon Sep 17 00:00:00 2001 From: Danno Ferrin Date: Mon, 9 Jan 2023 08:25:14 -0700 Subject: [PATCH] EOF Fuzzing Fixes (#4872) Fix a variety of issues found during the fuzzing sprint. * Validate type length matches code length * Remove JUMPF from shanghai * check retf output size * handle zero length types and code better * Fix table error on RETURNDATACOPY * RJUMPV byte should be unsigned * RJUMPV stack validation fix and more tests * dead code detection via counting considered bytes * EVMTool EOF Fuzzing support- remove all alphanumerics (punctuation) and comment lines from code in CLI Signed-off-by: Danno Ferrin --- .../besu/evmtool/CodeValidateSubCommand.java | 59 ++++- .../evmtool/CodeValidationSubCommandTest.java | 37 ++- .../java/org/hyperledger/besu/evm/Code.java | 7 + .../java/org/hyperledger/besu/evm/EVM.java | 1 - .../org/hyperledger/besu/evm/MainnetEVMs.java | 2 - .../besu/evm/code/CodeInvalid.java | 5 + .../org/hyperledger/besu/evm/code/CodeV0.java | 5 + .../org/hyperledger/besu/evm/code/CodeV1.java | 219 ++++++++++-------- .../hyperledger/besu/evm/code/EOFLayout.java | 13 +- .../besu/evm/operation/RetFOperation.java | 2 +- .../hyperledger/besu/evm/code/CodeV1Test.java | 207 +++++++++++------ .../besu/evm/code/EOFLayoutTest.java | 41 ++-- 12 files changed, 394 insertions(+), 204 deletions(-) diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/CodeValidateSubCommand.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/CodeValidateSubCommand.java index 897650bd0e..ffd3c4cefc 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/CodeValidateSubCommand.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/CodeValidateSubCommand.java @@ -20,15 +20,20 @@ import static org.hyperledger.besu.evmtool.CodeValidateSubCommand.COMMAND_NAME; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.evm.code.CodeFactory; import org.hyperledger.besu.evm.code.CodeInvalid; +import org.hyperledger.besu.evm.code.CodeSection; import org.hyperledger.besu.evm.code.EOFLayout; import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintStream; import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; import org.apache.tuweni.bytes.Bytes; import picocli.CommandLine; @@ -43,6 +48,11 @@ public class CodeValidateSubCommand implements Runnable { private final InputStream input; private final PrintStream output; + @CommandLine.Option( + names = {"--file"}, + description = "A file containing a set of inputs") + private final File codeFile = null; + @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") // picocli does it magically @CommandLine.Parameters private final List cliCode = new ArrayList<>(); @@ -60,40 +70,65 @@ public class CodeValidateSubCommand implements Runnable { @Override public void run() { - if (cliCode.isEmpty()) { - BufferedReader in = new BufferedReader(new InputStreamReader(input, UTF_8)); - try { - for (String code = in.readLine(); code != null; code = in.readLine()) { - output.println(considerCode(code)); - } + if (cliCode.isEmpty() && codeFile == null) { + try (BufferedReader in = new BufferedReader(new InputStreamReader(input, UTF_8))) { + checkCodeFromBufferedReader(in); } catch (IOException e) { throw new RuntimeException(e); } } else { + if (codeFile != null) { + try (BufferedReader in = new BufferedReader(new FileReader(codeFile, UTF_8))) { + checkCodeFromBufferedReader(in); + } catch (IOException e) { + throw new RuntimeException(e); + } + } for (String code : cliCode) { - output.println(considerCode(code)); + output.print(considerCode(code)); + } + } + } + + private void checkCodeFromBufferedReader(final BufferedReader in) { + try { + for (String code = in.readLine(); code != null; code = in.readLine()) { + output.print(considerCode(code)); } + } catch (IOException e) { + throw new RuntimeException(e); } } public String considerCode(final String hexCode) { Bytes codeBytes; try { - codeBytes = Bytes.fromHexString(hexCode.replaceAll("\\s+", "")); + codeBytes = + Bytes.fromHexString( + hexCode.replaceAll("(^|\n)#[^\n]*($|\n)", "").replaceAll("[^0-9A-Za-z]", "")); } catch (RuntimeException re) { - return "err: hex string -" + re; + return "err: hex string -" + re + "\n"; + } + if (codeBytes.size() == 0) { + return ""; } var layout = EOFLayout.parseEOF(codeBytes); if (!layout.isValid()) { - return "err: layout - " + layout.getInvalidReason(); + return "err: layout - " + layout.getInvalidReason() + "\n"; } var code = CodeFactory.createCode(codeBytes, Hash.hash(codeBytes), 1, true); if (!code.isValid()) { - return "err: " + ((CodeInvalid) code).getInvalidReason(); + return "err: " + ((CodeInvalid) code).getInvalidReason() + "\n"; } - return "OK " + code.getCodeBytes(0).toUnprefixedHexString(); + return "OK " + + IntStream.range(0, code.getCodeSectionCount()) + .mapToObj(code::getCodeSection) + .map(CodeSection::getCode) + .map(Bytes::toUnprefixedHexString) + .collect(Collectors.joining(",")) + + "\n"; } } diff --git a/ethereum/evmtool/src/test/java/org/hyperledger/besu/evmtool/CodeValidationSubCommandTest.java b/ethereum/evmtool/src/test/java/org/hyperledger/besu/evmtool/CodeValidationSubCommandTest.java index 70efe742a0..3a17791a12 100644 --- a/ethereum/evmtool/src/test/java/org/hyperledger/besu/evmtool/CodeValidationSubCommandTest.java +++ b/ethereum/evmtool/src/test/java/org/hyperledger/besu/evmtool/CodeValidationSubCommandTest.java @@ -26,9 +26,16 @@ import picocli.CommandLine; public class CodeValidationSubCommandTest { - static final String CODE_STOP_ONLY = "0xef0001 010001 020001 0001 030000 00 00000000 00"; - static final String CODE_RETF_ONLY = "0xef0001 010001 020001 0001 030000 00 00000000 b1"; - static final String CODE_BAD_MAGIC = "0xefffff 010001 020001 0001 030000 00 00000000 b1"; + static final String CODE_STOP_ONLY = "0xef0001 010004 020001-0001 030000 00 00000000 00"; + static final String CODE_RETF_ONLY = "0xef0001 010004 020001-0001 030000 00 00000000 b1"; + static final String CODE_BAD_MAGIC = "0xefffff 010004 020001-0001 030000 00 00000000 b1"; + static final String CODE_INTERIOR_COMMENTS = + "0xef0001 010008 020002-000c-0002 030000 00 \n" + + "# 7 inputs 1 output,\n" + + "00000007-07010007 \n" + + "59-59-59-59-59-59-59-b00001-50-b1\n" + + "# No immediate data\n" + + "f1-b1"; static final String CODE_MULTIPLE = CODE_STOP_ONLY + "\n" + CODE_BAD_MAGIC + "\n" + CODE_RETF_ONLY + "\n"; @@ -111,4 +118,28 @@ public class CodeValidationSubCommandTest { codeValidateSubCommand.run(); assertThat(baos.toString(UTF_8)).contains("OK b1\n"); } + + @Test + public void testInteriorCommentsSkipped() { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + final ByteArrayInputStream bais = new ByteArrayInputStream(new byte[0]); + final CodeValidateSubCommand codeValidateSubCommand = + new CodeValidateSubCommand(bais, new PrintStream(baos)); + final CommandLine cmd = new CommandLine(codeValidateSubCommand); + cmd.parseArgs(CODE_INTERIOR_COMMENTS); + codeValidateSubCommand.run(); + assertThat(baos.toString(UTF_8)).contains("OK 59595959595959b0000150b1,f1b1\n"); + } + + @Test + public void testBlankLinesAndCommentsSkipped() { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + final ByteArrayInputStream bais = + new ByteArrayInputStream(("# comment\n\n#blank line\n\n" + CODE_MULTIPLE).getBytes(UTF_8)); + final CodeValidateSubCommand codeValidateSubCommand = + new CodeValidateSubCommand(bais, new PrintStream(baos)); + codeValidateSubCommand.run(); + assertThat(baos.toString(UTF_8)) + .isEqualTo("OK 00\n" + "err: layout - EOF header byte 1 incorrect\n" + "OK b1\n"); + } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/Code.java b/evm/src/main/java/org/hyperledger/besu/evm/Code.java index 664c21db64..559ca47644 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/Code.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/Code.java @@ -77,4 +77,11 @@ public interface Code { * @return The code section, or null of there is no associated section */ CodeSection getCodeSection(final int section); + + /** + * The number of code sections in this container. + * + * @return 1 for legacy, count for valid, zero for invalid. + */ + int getCodeSectionCount(); } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/EVM.java b/evm/src/main/java/org/hyperledger/besu/evm/EVM.java index c63c9f5266..304523830f 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/EVM.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/EVM.java @@ -279,7 +279,6 @@ public class EVM { break; case 0xb0: // CALLF case 0xb1: // RETF - case 0xb2: // JUMPF // Function operations reset code if (enableCancun) { frame.setCurrentOperation(currentOperation); diff --git a/evm/src/main/java/org/hyperledger/besu/evm/MainnetEVMs.java b/evm/src/main/java/org/hyperledger/besu/evm/MainnetEVMs.java index 7434f8c68d..936cb7c585 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/MainnetEVMs.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/MainnetEVMs.java @@ -64,7 +64,6 @@ import org.hyperledger.besu.evm.operation.GtOperation; import org.hyperledger.besu.evm.operation.InvalidOperation; import org.hyperledger.besu.evm.operation.IsZeroOperation; import org.hyperledger.besu.evm.operation.JumpDestOperation; -import org.hyperledger.besu.evm.operation.JumpFOperation; import org.hyperledger.besu.evm.operation.JumpOperation; import org.hyperledger.besu.evm.operation.JumpiOperation; import org.hyperledger.besu.evm.operation.Keccak256Operation; @@ -492,7 +491,6 @@ public class MainnetEVMs { registry.put(new RelativeJumpVectorOperation(gasCalculator)); registry.put(new CallFOperation(gasCalculator)); registry.put(new RetFOperation(gasCalculator)); - registry.put(new JumpFOperation(gasCalculator)); } public static EVM futureEips(final BigInteger chainId, final EvmConfiguration evmConfiguration) { diff --git a/evm/src/main/java/org/hyperledger/besu/evm/code/CodeInvalid.java b/evm/src/main/java/org/hyperledger/besu/evm/code/CodeInvalid.java index c3bd2e677f..6b834ceccd 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/code/CodeInvalid.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/code/CodeInvalid.java @@ -76,4 +76,9 @@ public class CodeInvalid implements Code { public CodeSection getCodeSection(final int section) { return null; } + + @Override + public int getCodeSectionCount() { + return 0; + } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/code/CodeV0.java b/evm/src/main/java/org/hyperledger/besu/evm/code/CodeV0.java index 0a10785c83..86713d6949 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/code/CodeV0.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/code/CodeV0.java @@ -128,6 +128,11 @@ public class CodeV0 implements Code { return null; } + @Override + public int getCodeSectionCount() { + return 1; + } + long[] calculateJumpDests() { final int size = getSize(); final long[] bitmap = new long[(size >> 6) + 1]; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/code/CodeV1.java b/evm/src/main/java/org/hyperledger/besu/evm/code/CodeV1.java index f8dabc9c3a..54b6aa076a 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/code/CodeV1.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/code/CodeV1.java @@ -23,11 +23,11 @@ import static org.hyperledger.besu.evm.internal.Words.readBigEndianU16; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.evm.Code; import org.hyperledger.besu.evm.operation.CallFOperation; -import org.hyperledger.besu.evm.operation.JumpFOperation; import org.hyperledger.besu.evm.operation.PushOperation; import org.hyperledger.besu.evm.operation.RelativeJumpIfOperation; import org.hyperledger.besu.evm.operation.RelativeJumpOperation; import org.hyperledger.besu.evm.operation.RelativeJumpVectorOperation; +import org.hyperledger.besu.evm.operation.RetFOperation; import java.util.Arrays; import java.util.BitSet; @@ -42,7 +42,7 @@ public class CodeV1 implements Code { static final byte VALID = 0x02; static final byte TERMINAL = 0x04; static final byte VALID_AND_TERMINAL = VALID | TERMINAL; - private static final byte[] opcodeAttributes = { + static final byte[] OPCODE_ATTRIBUTES = { VALID_AND_TERMINAL, // 0x00 STOP VALID, // 0x01 - ADD VALID, // 0x02 - MUL @@ -135,7 +135,7 @@ public class CodeV1 implements Code { VALID, // 0x59 - MSIZE VALID, // 0x5a - GAS VALID, // 0x5b - NOOOP (née JUMPDEST) - VALID, // 0X5c - RJUMP + VALID_AND_TERMINAL, // 0X5c - RJUMP VALID, // 0X5d - RJUMPI VALID, // 0X5e - RJUMPV VALID, // 0X5f - PUSH0 @@ -221,7 +221,7 @@ public class CodeV1 implements Code { INVALID, // 0xaf VALID, // 0xb0 - CALLF VALID_AND_TERMINAL, // 0xb1 - RETF - VALID_AND_TERMINAL, // 0xb2 - JUMPF + INVALID, // 0xb2 - JUMPF INVALID, // 0xb3 INVALID, // 0xb4 INVALID, // 0xb5 @@ -305,7 +305,7 @@ public class CodeV1 implements Code { // [0] - stack input consumed // [1] - stack outputs added // [2] - PC advance - private static final byte[][] opcodeStackValidation = { + static final byte[][] OPCODE_STACK_VALIDATION = { {0, 0, -1}, // 0x00 - STOP {2, 1, 1}, // 0x01 - ADD {2, 1, 1}, // 0x02 - MUL @@ -327,7 +327,7 @@ public class CodeV1 implements Code { {2, 1, 1}, // 0x12 - SLT {2, 1, 1}, // 0x13 - SGT {2, 1, 1}, // 0x14 - EQ - {2, 1, 1}, // 0x15 - ISZERO + {1, 1, 1}, // 0x15 - ISZERO {2, 1, 1}, // 0x16 - AND {2, 1, 1}, // 0x17 - OR {2, 1, 1}, // 0x18 - XOR @@ -366,9 +366,9 @@ public class CodeV1 implements Code { {3, 0, 1}, // 0x39 - CODECOPY {0, 1, 1}, // 0x3a - GASPRICE {1, 1, 1}, // 0x3b - EXTCODESIZE - {4, 1, 1}, // 0x3c - EXTCODECOPY + {4, 0, 1}, // 0x3c - EXTCODECOPY {0, 1, 1}, // 0x3d - RETURNDATASIZE - {3, 1, 1}, // 0x3e - RETURNDATACOPY + {3, 0, 1}, // 0x3e - RETURNDATACOPY {1, 1, 1}, // 0x3f - EXTCODEHASH {1, 1, 1}, // 0x40 - BLOCKHASH {0, 1, 1}, // 0x41 - COINBASE @@ -398,7 +398,7 @@ public class CodeV1 implements Code { {0, 1, 1}, // 0x59 - MSIZE {0, 1, 1}, // 0x5a - GAS {0, 0, 1}, // 0x5b - NOOP (née JUMPDEST) - {0, 0, -1}, // 0x5c - RJUMP + {0, 0, -3}, // 0x5c - RJUMP {1, 0, 3}, // 0x5d - RJUMPI {1, 0, 2}, // 0x5e - RJUMPV {0, 1, 1}, // 0x5f - PUSH0 @@ -484,7 +484,7 @@ public class CodeV1 implements Code { {0, 0, 0}, // 0xaf {0, 0, 3}, // 0xb0 - CALLF {0, 0, -1}, // 0xb1 - RETF - {0, 0, -1}, // 0xb2 - JUMPF + {0, 0, 0}, // 0xb2 - JUMPF {0, 0, 0}, // 0xb3 {0, 0, 0}, // 0xb4 {0, 0, 0}, // 0xb5 @@ -548,7 +548,7 @@ public class CodeV1 implements Code { {0, 0, 0}, // 0xef {3, 1, 1}, // 0xf0 - CREATE {7, 1, 1}, // 0xf1 - CALL - {0, 1, 1}, // 0xf2 - CALLCODE + {0, 0, 0}, // 0xf2 - CALLCODE {2, 0, -1}, // 0xf3 - RETURN {6, 1, 1}, // 0xf4 - DELEGATECALL {4, 1, 1}, // 0xf5 - CREATE2 @@ -598,10 +598,10 @@ public class CodeV1 implements Code { int pos = 0; while (pos < size) { final int operationNum = rawCode[pos] & 0xff; - attribute = opcodeAttributes[operationNum]; + attribute = OPCODE_ATTRIBUTES[operationNum]; if ((attribute & INVALID) == INVALID) { // undefined instruction - return "Invalid Instruction 0x" + Integer.toHexString(operationNum); + return String.format("Invalid Instruction 0x%02x", operationNum); } pos += 1; int pcPostInstruction = pos; @@ -640,13 +640,13 @@ public class CodeV1 implements Code { } rjumpdests.set(rjumpdest); } - } else if (operationNum == CallFOperation.OPCODE || operationNum == JumpFOperation.OPCODE) { + } else if (operationNum == CallFOperation.OPCODE) { if (pos + 2 > size) { - return "Truncated CALLF/JUMPF"; + return "Truncated CALLF"; } int section = readBigEndianU16(pos, rawCode); if (section >= sectionCount) { - return "CALLF/JUMPF to non-existent section - " + Integer.toHexString(section); + return "CALLF to non-existent section - " + Integer.toHexString(section); } pcPostInstruction += 2; } @@ -677,107 +677,123 @@ public class CodeV1 implements Code { * immediates as well as no immediates falling off of the end of code sections. * * @param codeSectionToValidate The index of code to validate in the code sections - * @param codeSections The code sections to use for CALLF and JUMPF reference validation. + * @param codeSections The code sections to use for CALLF reference validation. * @return null if valid, otherwise an error string providing the validation error. */ public static String validateStack( final int codeSectionToValidate, final CodeSection[] codeSections) { - byte[] code = codeSections[codeSectionToValidate].code.toArrayUnsafe(); - int codeLength = code.length; - int[] stackHeights = new int[codeLength]; - Arrays.fill(stackHeights, -1); - - int thisWork = 0; - int maxWork = 1; - int[][] workList = new int[codeLength][2]; - - int initialStackHeight = codeSections[codeSectionToValidate].getInputs(); - int maxStackHeight = initialStackHeight; - stackHeights[0] = initialStackHeight; - workList[0][1] = initialStackHeight; - - while (thisWork < maxWork) { - int currentPC = workList[thisWork][0]; - int currentStackHeight = workList[thisWork][1]; - if (thisWork > 0 && stackHeights[currentPC] >= 0) { - // we've been here, validate the jump is what is expected - if (stackHeights[currentPC] != currentStackHeight) { - return String.format( - "Jump into code stack height (%d) does not match previous value (%d)", - stackHeights[currentPC], currentStackHeight); - } else { - thisWork++; - continue; - } - } + try { + byte[] code = codeSections[codeSectionToValidate].code.toArrayUnsafe(); + int codeLength = code.length; + int[] stackHeights = new int[codeLength]; + Arrays.fill(stackHeights, -1); - while (currentPC < codeLength) { - int thisOp = code[currentPC] & 0xff; - int recordedStack = stackHeights[currentPC]; - if (recordedStack >= 0 && currentStackHeight != recordedStack) { - return String.format( - "Stack height mismatch %d/%d at %d", recordedStack, currentStackHeight, currentPC); - } + int thisWork = 0; + int maxWork = 1; + int[][] workList = new int[codeLength][2]; - byte[] stackInfo = opcodeStackValidation[thisOp]; - int stackInputs; - int stackOutputs; - int pcAdvance = stackInfo[2]; - if (pcAdvance == 0) { - return String.format("Invalid opcode 0x%02X", thisOp); - } - if (thisOp == CallFOperation.OPCODE || thisOp == JumpFOperation.OPCODE) { - int section = readBigEndianU16(currentPC + 1, code); - stackInputs = codeSections[section].getInputs(); - stackOutputs = codeSections[section].getOutputs(); + int initialStackHeight = codeSections[codeSectionToValidate].getInputs(); + int maxStackHeight = initialStackHeight; + stackHeights[0] = initialStackHeight; + workList[0][1] = initialStackHeight; + int unusedBytes = codeLength; + + while (thisWork < maxWork) { + int currentPC = workList[thisWork][0]; + int currentStackHeight = workList[thisWork][1]; + if (thisWork > 0 && stackHeights[currentPC] >= 0) { + // we've been here, validate the jump is what is expected + if (stackHeights[currentPC] != currentStackHeight) { + return String.format( + "Jump into code stack height (%d) does not match previous value (%d)", + stackHeights[currentPC], currentStackHeight); + } else { + thisWork++; + continue; + } } else { - stackInputs = stackInfo[0]; - stackOutputs = stackInfo[1]; + stackHeights[currentPC] = currentStackHeight; } - if (stackInputs > currentStackHeight) { - return String.format( - "Operation 0x%02X requires stack of %d but only has %d items", - thisOp, stackInputs, currentStackHeight); - } + while (currentPC < codeLength) { + int thisOp = code[currentPC] & 0xff; - currentStackHeight = currentStackHeight - stackInputs + stackOutputs; - if (currentStackHeight > MAX_STACK_HEIGHT) { - return "Stack height exceeds 1024"; - } + byte[] stackInfo = OPCODE_STACK_VALIDATION[thisOp]; + int stackInputs; + int stackOutputs; + int pcAdvance = stackInfo[2]; + if (thisOp == CallFOperation.OPCODE) { + int section = readBigEndianU16(currentPC + 1, code); + stackInputs = codeSections[section].getInputs(); + stackOutputs = codeSections[section].getOutputs(); + } else { + stackInputs = stackInfo[0]; + stackOutputs = stackInfo[1]; + } + + if (stackInputs > currentStackHeight) { + return String.format( + "Operation 0x%02X requires stack of %d but only has %d items", + thisOp, stackInputs, currentStackHeight); + } + + currentStackHeight = currentStackHeight - stackInputs + stackOutputs; + if (currentStackHeight > MAX_STACK_HEIGHT) { + return "Stack height exceeds 1024"; + } - maxStackHeight = Math.max(maxStackHeight, currentStackHeight); - - if (thisOp == RelativeJumpOperation.OPCODE || thisOp == RelativeJumpIfOperation.OPCODE) { - // no `& 0xff` on high byte because this is one case we want sign extension - int rvalue = readBigEndianI16(currentPC + 1, code); - workList[maxWork] = new int[] {currentPC + rvalue + 3, currentStackHeight}; - maxWork++; - } else if (thisOp == RelativeJumpVectorOperation.OPCODE) { - int tableEnd = code[currentPC + 1] * 2 + currentPC + 2; - for (int i = currentPC + 2; i < tableEnd; i += 2) { - int rvalue = readBigEndianI16(i + 1, code); + maxStackHeight = Math.max(maxStackHeight, currentStackHeight); + + if (thisOp == RelativeJumpOperation.OPCODE || thisOp == RelativeJumpIfOperation.OPCODE) { + // no `& 0xff` on high byte because this is one case we want sign extension + int rvalue = readBigEndianI16(currentPC + 1, code); workList[maxWork] = new int[] {currentPC + rvalue + 3, currentStackHeight}; maxWork++; + } else if (thisOp == RelativeJumpVectorOperation.OPCODE) { + int immediateDataSize = (code[currentPC + 1] & 0xff) * 2; + unusedBytes -= immediateDataSize; + int tableEnd = immediateDataSize + currentPC + 2; + for (int i = currentPC + 2; i < tableEnd; i += 2) { + int rvalue = readBigEndianI16(i, code); + workList[maxWork] = new int[] {tableEnd + rvalue, currentStackHeight}; + maxWork++; + } + currentPC = tableEnd - 2; + } else if (thisOp == RetFOperation.OPCODE) { + int returnStackItems = codeSections[codeSectionToValidate].getOutputs(); + if (currentStackHeight != returnStackItems) { + return String.format( + "Section return (RETF) calculated height 0x%x does not match configured height 0x%x", + currentStackHeight, returnStackItems); + } } - currentPC = tableEnd - 2; - } - if (pcAdvance < 0) { - break; + if (pcAdvance < 0) { + unusedBytes += pcAdvance; + break; + } else if (pcAdvance == 0) { + return String.format("Invalid Instruction 0x%02x", thisOp); + } + + currentPC += pcAdvance; + stackHeights[currentPC] = currentStackHeight; + unusedBytes -= pcAdvance; } - currentPC += pcAdvance; - stackHeights[currentPC] = currentStackHeight; - } - thisWork++; - } + thisWork++; + } + if (maxStackHeight != codeSections[codeSectionToValidate].maxStackHeight) { + return String.format( + "Calculated max stack height (%d) does not match reported stack height (%d)", + maxStackHeight, codeSections[codeSectionToValidate].maxStackHeight); + } + if (unusedBytes != 0) { + return String.format("Dead code detected in section %d", codeSectionToValidate); + } - if (maxStackHeight != codeSections[codeSectionToValidate].maxStackHeight) { - return String.format( - "Calculated max stack height (%d) does not match reported stack height (%d)", - maxStackHeight, codeSections[codeSectionToValidate].maxStackHeight); + return null; + } catch (RuntimeException re) { + return "Internal Exception " + re.getMessage(); } - return null; } @Override @@ -816,6 +832,11 @@ public class CodeV1 implements Code { return codeSectionInfos[section]; } + @Override + public int getCodeSectionCount() { + return codeSectionInfos.length; + } + @Override public Bytes getContainerBytes() { return container; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/code/EOFLayout.java b/evm/src/main/java/org/hyperledger/besu/evm/code/EOFLayout.java index 1645ee1b05..4f58e513ee 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/code/EOFLayout.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/code/EOFLayout.java @@ -87,7 +87,7 @@ public class EOFLayout { return invalidLayout(container, version, error); } int typesLength = readUnsignedShort(inputStream); - if (typesLength < 0) { + if (typesLength <= 0) { return invalidLayout(container, version, "Invalid Types section size"); } @@ -96,9 +96,18 @@ public class EOFLayout { return invalidLayout(container, version, error); } int codeSectionCount = readUnsignedShort(inputStream); - if (codeSectionCount < 0) { + if (codeSectionCount <= 0) { return invalidLayout(container, version, "Invalid Code section count"); } + if (codeSectionCount * 4 != typesLength) { + return invalidLayout( + container, + version, + "Type section length incompatible with code section count - 0x" + + Integer.toHexString(codeSectionCount) + + " * 4 != 0x" + + Integer.toHexString(typesLength)); + } if (codeSectionCount > 1024) { return invalidLayout( container, diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/RetFOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/RetFOperation.java index 739f3684ad..b64437efa8 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/RetFOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/RetFOperation.java @@ -20,7 +20,7 @@ import org.hyperledger.besu.evm.gascalculator.GasCalculator; public class RetFOperation extends AbstractOperation { - static final int OPCODE = 0xb1; + public static final int OPCODE = 0xb1; static final OperationResult retfSuccess = new OperationResult(4, null); public RetFOperation(final GasCalculator gasCalculator) { diff --git a/evm/src/test/java/org/hyperledger/besu/evm/code/CodeV1Test.java b/evm/src/test/java/org/hyperledger/besu/evm/code/CodeV1Test.java index b30a991c20..90b46afa92 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/code/CodeV1Test.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/code/CodeV1Test.java @@ -25,6 +25,7 @@ import java.util.stream.IntStream; import java.util.stream.Stream; import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -37,12 +38,13 @@ import org.junit.jupiter.params.provider.ValueSource; */ class CodeV1Test { - public static final String ZERO_HEX = String.format("%02x", 0); + public static final String ZERO_HEX = "00"; + public static final String NOOP_HEX = "5b"; @Test void validCode() { String codeHex = - "0xEF0001 010010 020003 000A 0002 0008 030000 00 00000000 02010001 01000002 60016002b00001b20002 01b1 60005360106000f3"; + "0xEF0001 01000C 020003 000b 0002 0008 030000 00 00000000 02010001 01000002 60016002b00001b00002b1 01b1 60005360106000f3"; final EOFLayout layout = EOFLayout.parseEOF(Bytes.fromHexString(codeHex.replace(" ", ""))); String validationError = validateCode(layout); @@ -59,7 +61,7 @@ class CodeV1Test { } @ParameterizedTest - @ValueSource(strings = {"00", "f3", "fd", "fe"}) + @ValueSource(strings = {"00", "3030f3", "3030fd", "fe"}) void testValidCodeTerminator(final String code) { final String validationError = validateCode(Bytes.fromHexString(code), 1); assertThat(validationError).isNull(); @@ -91,12 +93,12 @@ class CodeV1Test { "5c000000", "5c00010000", "5c00010000000000", - "5c0100" + ZERO_HEX.repeat(256) + ZERO_HEX, - "5c7fff" + ZERO_HEX.repeat(32767) + ZERO_HEX, + "5c0100" + NOOP_HEX.repeat(256) + ZERO_HEX, + "5c7fff" + NOOP_HEX.repeat(32767) + ZERO_HEX, "5cfffd0000", "005cfffc00", - ZERO_HEX.repeat(253) + "5cff0000", - ZERO_HEX.repeat(32765) + "5c800000") + NOOP_HEX.repeat(253) + "5cff0000", + NOOP_HEX.repeat(32765) + "5c800000") .map(Arguments::arguments); } @@ -116,8 +118,8 @@ class CodeV1Test { "60015d7fff" + "5b".repeat(32767) + ZERO_HEX, "60015dfffd0000", "60015dfffb00", - ZERO_HEX.repeat(252) + "60015dff0000", - ZERO_HEX.repeat(32763) + "60015d800000", + NOOP_HEX.repeat(252) + "60015dff0000", + NOOP_HEX.repeat(32763) + "60015d800000", "5d000000") .map(Arguments::arguments); } @@ -178,7 +180,7 @@ class CodeV1Test { return Stream.concat( Stream.of("60"), IntStream.range(0, 31) - .mapToObj(i -> String.format("%02x", 0x61 + i) + ZERO_HEX.repeat(i + 1))) + .mapToObj(i -> String.format("%02x", 0x61 + i) + NOOP_HEX.repeat(i + 1))) .map(Arguments::arguments); } @@ -323,22 +325,46 @@ class CodeV1Test { } @ParameterizedTest - @ValueSource(strings = {"b0", "b000", "b2", "b200"}) + @ValueSource(strings = {"b0", "b000"}) + void testCallFTruncated(final String code) { + final String validationError = validateCode(Bytes.fromHexString(code), 1); + assertThat(validationError).isEqualTo("Truncated CALLF"); + } + + @ParameterizedTest + @ValueSource(strings = {"b2", "b200"}) + @Disabled("Out of Shahghai, will likely return in Cancun or Prague") void testJumpCallFTruncated(final String code) { final String validationError = validateCode(Bytes.fromHexString(code), 1); - assertThat(validationError).isEqualTo("Truncated CALLF/JUMPF"); + assertThat(validationError).isEqualTo("Truncated CALLF"); + } + + @ParameterizedTest + @ValueSource(strings = {"b00004", "b003ff", "b0ffff"}) + void testCallFWrongSection(final String code) { + final String validationError = validateCode(Bytes.fromHexString(code), 3); + assertThat(validationError).startsWith("CALLF to non-existent section -"); } @ParameterizedTest - @ValueSource(strings = {"b00004", "b003ff", "b0ffff", "b20004", "b203ff", "b2ffff"}) - void testJumpCallFWrongSection(final String code) { + @ValueSource(strings = {"b20004", "b203ff", "b2ffff"}) + @Disabled("Out of Shahghai, will likely return in Cancun or Prague") + void testJumpFWrongSection(final String code) { final String validationError = validateCode(Bytes.fromHexString(code), 3); - assertThat(validationError).startsWith("CALLF/JUMPF to non-existent section -"); + assertThat(validationError).startsWith("CALLF to non-existent section -"); } @ParameterizedTest - @ValueSource(strings = {"b0000100", "b0000200", "b0000000", "b20001", "b20002", "b20000"}) - void testJumpCallFValid(final String code) { + @ValueSource(strings = {"b0000100", "b0000200", "b0000000"}) + void testCallFValid(final String code) { + final String validationError = validateCode(Bytes.fromHexString(code), 3); + assertThat(validationError).isNull(); + } + + @ParameterizedTest + @ValueSource(strings = {"b20001", "b20002", "b20000"}) + @Disabled("Out of Shahghai, will likely return in Cancun or Prague") + void testJumpFValid(final String code) { final String validationError = validateCode(Bytes.fromHexString(code), 3); assertThat(validationError).isNull(); } @@ -386,7 +412,9 @@ class CodeV1Test { "stackRJumpI", "stackCallF", "stackRetF", - "stackUnreachable" + "stackUnreachable", + "stackHeight", + "invalidInstructions", }) void validateStackAnalysis( final String ignoredName, @@ -427,7 +455,7 @@ class CodeV1Test { "Stack empty with input", null, 1, - List.of(List.of("5000", 0, 0, 0), List.of("50 00", 1, 0, 1))), + List.of(List.of("00", 0, 0, 0), List.of("50 00", 1, 0, 1))), // this depends on requiring stacks to be "clean" returns Arguments.of( "Stack not empty at output", @@ -454,35 +482,42 @@ class CodeV1Test { static Stream stackRJumpForward() { return Stream.of( Arguments.of("RJUMP 0", null, 0, List.of(List.of("5C0000 00", 0, 0, 0))), - Arguments.of("RJUMP 1 w/ dead code", null, 0, List.of(List.of("5C0001 43 00", 0, 0, 0))), - Arguments.of("RJUMP 2 w/ dead code", null, 0, List.of(List.of("5C0002 43 50 00", 0, 0, 0))), + Arguments.of( + "RJUMP 1 w/ dead code", + "Dead code detected in section 0", + 0, + List.of(List.of("5C0001 43 00", 0, 0, 0))), + Arguments.of( + "RJUMP 2 w/ dead code", + "Dead code detected in section 0", + 0, + List.of(List.of("5C0002 43 50 00", 0, 0, 0))), Arguments.of( "RJUMP 3 and -10", null, 0, - List.of(List.of("5C0003 01 50 00 6001 6001 5Cfff6 00", 0, 0, 2)))); + List.of(List.of("5C0003 01 50 00 6001 6001 5Cfff6", 0, 0, 2)))); } static Stream stackRJumpBackward() { return Stream.of( - Arguments.of("RJUMP -3", null, 0, List.of(List.of("5Cfffd 00", 0, 0, 0))), - Arguments.of("RJUMP -4", null, 0, List.of(List.of("5B 5Cfffc 00", 0, 0, 0))), + Arguments.of("RJUMP -3", null, 0, List.of(List.of("5Cfffd", 0, 0, 0))), + Arguments.of("RJUMP -4", null, 0, List.of(List.of("5B 5Cfffc", 0, 0, 0))), Arguments.of( "RJUMP -4 unmatched stack", "Jump into code stack height (0) does not match previous value (1)", 0, - List.of(List.of("43 5Cfffc 50 00", 0, 0, 0))), + List.of(List.of("43 5Cfffc", 0, 0, 0))), Arguments.of( "RJUMP -4 unmatched stack", "Jump into code stack height (1) does not match previous value (0)", 0, List.of(List.of("43 50 5Cfffc 00", 0, 0, 0))), + Arguments.of("RJUMP -3 matched stack", null, 0, List.of(List.of("43 50 5Cfffd", 0, 0, 1))), Arguments.of( - "RJUMP -3 matched stack", null, 0, List.of(List.of("43 50 5Cfffd 00", 0, 0, 1))), + "RJUMP -4 matched stack", null, 0, List.of(List.of("43 50 5B 5Cfffc", 0, 0, 1))), Arguments.of( - "RJUMP -4 matched stack", null, 0, List.of(List.of("43 50 5B 5Cfffc 00", 0, 0, 1))), - Arguments.of( - "RJUMP -5 matched stack", null, 0, List.of(List.of("43 50 43 5Cfffb 50 00", 0, 0, 1))), + "RJUMP -5 matched stack", null, 0, List.of(List.of("43 50 43 5Cfffb", 0, 0, 1))), Arguments.of( "RJUMP -4 unmatched stack", "Jump into code stack height (0) does not match previous value (1)", @@ -559,98 +594,101 @@ class CodeV1Test { "0 input 0 output", null, 0, - List.of(List.of("B00001 00", 0, 0, 0), List.of("", 0, 0, 0))), + List.of(List.of("B00001 00", 0, 0, 0), List.of("b1", 0, 0, 0))), Arguments.of( "0 inputs, 0 output 3 sections", null, 0, - List.of(List.of("B00002 00", 0, 0, 0), List.of("", 1, 1, 1), List.of("", 0, 0, 0))), + List.of(List.of("B00002 00", 0, 0, 0), List.of("b1", 1, 1, 1), List.of("b1", 0, 0, 0))), Arguments.of( "more than 0 inputs", null, 0, - List.of(List.of("30 B00001 00", 0, 0, 1), List.of("", 1, 0, 1))), + List.of(List.of("30 B00001 00", 0, 0, 1), List.of("00", 1, 0, 1))), Arguments.of( "forwarding an argument", null, 1, - List.of(List.of("", 0, 0, 0), List.of("B00002 00", 1, 0, 1), List.of("", 1, 0, 1))), + List.of(List.of("00", 0, 0, 0), List.of("B00002 00", 1, 0, 1), List.of("00", 1, 0, 1))), Arguments.of( "more than 1 inputs", null, 0, - List.of(List.of("30 80 B00001 00", 0, 0, 2), List.of("", 2, 0, 2))), + List.of(List.of("30 80 B00001 00", 0, 0, 2), List.of("00", 2, 0, 2))), Arguments.of( "more than 0 outputs", null, 0, - List.of(List.of("B00001 50 00", 0, 0, 1), List.of("", 0, 1, 1))), + List.of(List.of("B00001 50 00", 0, 0, 1), List.of("3000", 0, 1, 1))), Arguments.of( "more than 0 outputs 3 sections", null, 0, - List.of(List.of("B00002 50 00", 0, 0, 1), List.of("", 0, 0, 0), List.of("", 0, 1, 2))), + List.of( + List.of("B00002 50 00", 0, 0, 1), + List.of("00", 0, 0, 0), + List.of("30305000", 0, 1, 2))), Arguments.of( "more than 1 outputs", null, 0, - List.of(List.of("B00001 50 50 00", 0, 0, 2), List.of("", 0, 2, 2))), + List.of(List.of("B00001 50 50 00", 0, 0, 2), List.of("303000", 0, 2, 2))), Arguments.of( "more than 0 inputs, more than 0 outputs", null, 0, List.of( List.of("30 30 B00001 50 50 50 00", 0, 0, 3), - List.of("30 30 B00001 50 50 50 00", 2, 3, 3))), + List.of("30 30 B00001 50 50 00", 2, 3, 5))), Arguments.of("recursion", null, 0, List.of(List.of("B00000 00", 0, 0, 0))), Arguments.of( "recursion 2 inputs", null, 1, - List.of(List.of("", 0, 0, 0), List.of("B00000 00", 2, 0, 2))), + List.of(List.of("00", 0, 0, 0), List.of("B00000 00", 2, 0, 2))), Arguments.of( "recursion 2 inputs 2 outputs", null, 1, - List.of(List.of("", 0, 0, 0), List.of("B00000 50 50 00", 2, 2, 2))), + List.of(List.of("00", 0, 0, 0), List.of("B00000 50 50 00", 2, 2, 2))), Arguments.of( "recursion 2 inputs 1 output", null, 1, - List.of(List.of("", 0, 0, 0), List.of("30 30 B00001 50 50 50 00", 2, 1, 4))), + List.of(List.of("00", 0, 0, 0), List.of("30 30 B00001 50 50 50 00", 2, 1, 4))), Arguments.of( "multiple CALLFs with different types", null, 1, List.of( - List.of("", 0, 0, 0), + List.of("00", 0, 0, 0), List.of("44 B00002 80 80 B00003 44 80 B00004 50 50 00", 0, 0, 3), - List.of("", 1, 1, 3), - List.of("", 3, 0, 3), - List.of("", 2, 2, 2))), + List.of("3030505000", 1, 1, 3), + List.of("50505000", 3, 0, 3), + List.of("00", 2, 2, 2))), Arguments.of( "underflow", "Operation 0xB0 requires stack of 1 but only has 0 items", 0, - List.of(List.of("B00001 00", 0, 0, 0), List.of("", 1, 0, 0))), + List.of(List.of("B00001 00", 0, 0, 0), List.of("00", 1, 0, 0))), Arguments.of( "underflow 2", "Operation 0xB0 requires stack of 2 but only has 1 items", 0, - List.of(List.of("30 B00001 00", 0, 0, 0), List.of("", 2, 0, 2))), + List.of(List.of("30 B00001 00", 0, 0, 0), List.of("00", 2, 0, 2))), Arguments.of( "underflow 3", "Operation 0xB0 requires stack of 1 but only has 0 items", 1, - List.of(List.of("", 0, 0, 0), List.of("50 B00001 00", 1, 0, 1))), + List.of(List.of("00", 0, 0, 0), List.of("50 B00001 00", 1, 0, 1))), Arguments.of( "underflow 4", "Operation 0xB0 requires stack of 3 but only has 2 items", 0, List.of( List.of("44 B00001 80 B00002 00", 0, 0, 0), - List.of("", 1, 1, 1), - List.of("", 3, 0, 3)))); + List.of("00", 1, 1, 1), + List.of("00", 3, 0, 3)))); } static Stream stackRetF() { @@ -659,95 +697,122 @@ class CodeV1Test { "0 outputs at section 0", null, 0, - List.of(List.of("B1", 0, 0, 0), List.of("", 0, 0, 0))), + List.of(List.of("B1", 0, 0, 0), List.of("00", 0, 0, 0))), Arguments.of( "0 outputs at section 1", null, 1, - List.of(List.of("", 0, 0, 0), List.of("B1", 0, 0, 0))), + List.of(List.of("00", 0, 0, 0), List.of("B1", 0, 0, 0))), Arguments.of( "0 outputs at section 2", null, 2, - List.of(List.of("", 0, 0, 0), List.of("", 1, 1, 1), List.of("B1", 0, 0, 0))), + List.of(List.of("00", 0, 0, 0), List.of("00", 1, 1, 1), List.of("B1", 0, 0, 0))), Arguments.of( "more than 0 outputs section 0", null, 0, - List.of(List.of("44 B1", 0, 0, 1), List.of("", 0, 1, 1))), + List.of(List.of("44 50 B1", 0, 0, 1), List.of("4400", 0, 1, 1))), Arguments.of( "more than 0 outputs section 0", null, 1, - List.of(List.of("", 0, 0, 0), List.of("44 B1", 0, 1, 1))), + List.of(List.of("00", 0, 0, 0), List.of("44 B1", 0, 1, 1))), Arguments.of( "more than 1 outputs section 1", null, 1, - List.of(List.of("", 0, 0, 0), List.of("44 80 B1", 0, 2, 2))), + List.of(List.of("00", 0, 0, 0), List.of("44 80 B1", 0, 2, 2))), Arguments.of( "Forwarding return values", null, 1, - List.of(List.of("", 0, 0, 0), List.of("B1", 1, 1, 1))), + List.of(List.of("00", 0, 0, 0), List.of("B1", 1, 1, 1))), Arguments.of( "Forwarding of return values 2", null, 1, - List.of(List.of("", 0, 0, 0), List.of("B00002 B1", 0, 1, 1), List.of("", 0, 1, 1))), + List.of( + List.of("00", 0, 0, 0), List.of("B00002 B1", 0, 1, 1), List.of("3000", 0, 1, 1))), Arguments.of( "Multiple RETFs", null, 1, - List.of(List.of("", 0, 0, 0), List.of("5D0003 44 80 B1 30 80 B1", 1, 2, 2))), + List.of(List.of("00", 0, 0, 0), List.of("5D0003 44 80 B1 30 80 B1", 1, 2, 2))), Arguments.of( "underflow 1", - "Calculated max stack height (0) does not match reported stack height (1)", + "Section return (RETF) calculated height 0x0 does not match configured height 0x1", 1, - List.of(List.of("", 0, 0, 0), List.of("B1", 0, 1, 1))), + List.of(List.of("00", 0, 0, 0), List.of("B1", 0, 1, 0))), Arguments.of( "underflow 2", - "Calculated max stack height (1) does not match reported stack height (2)", + "Section return (RETF) calculated height 0x1 does not match configured height 0x2", 1, - List.of(List.of("", 0, 0, 0), List.of("44 B1", 0, 2, 2))), + List.of(List.of("00", 0, 0, 0), List.of("44 B1", 0, 2, 1))), Arguments.of( "underflow 3", - "Calculated max stack height (2) does not match reported stack height (0)", + "Section return (RETF) calculated height 0x1 does not match configured height 0x2", 1, - List.of(List.of("", 0, 0, 0), List.of("5D0003 44 80 B1 30 B1", 1, 2, 0)))); + List.of(List.of("00", 0, 0, 0), List.of("5D0003 44 80 B1 30 B1", 1, 2, 2)))); } static Stream stackUnreachable() { return Stream.of( Arguments.of( "Max stack not changed by unreachable code", - null, + "Dead code detected in section 0", 0, List.of(List.of("30 50 00 30 30 30 50 50 50 00", 0, 0, 1))), Arguments.of( "Max stack not changed by unreachable code RETf", - null, + "Dead code detected in section 0", 0, List.of(List.of("30 50 B1 30 30 30 50 50 50 00", 0, 0, 1))), Arguments.of( "Max stack not changed by unreachable code RJUMP", - null, + "Dead code detected in section 0", 0, List.of(List.of("30 50 5C0006 30 30 30 50 50 50 00", 0, 0, 1))), Arguments.of( "Stack underflow in unreachable code", - null, + "Dead code detected in section 0", 0, List.of(List.of("30 50 00 50 00", 0, 0, 1))), Arguments.of( "Stack underflow in unreachable code RETF", - null, + "Dead code detected in section 0", 0, List.of(List.of("30 50 B1 50 00", 0, 0, 1))), Arguments.of( "Stack underflow in unreachable code RJUMP", - null, + "Dead code detected in section 0", 0, List.of(List.of("30 50 5C0001 50 00", 0, 0, 1)))); } + + static Stream stackHeight() { + return Stream.of( + Arguments.of( + "Stack height mismatch backwards", + "Jump into code stack height (0) does not match previous value (1)", + 0, + List.of(List.of("30 5Cfffc00", 0, 0, 1))), + Arguments.of( + "Stack height mismatch forwards", + "Jump into code stack height (3) does not match previous value (0)", + 0, + List.of(List.of("305D0003303030303000", 0, 0, 2)))); + } + + static Stream invalidInstructions() { + return IntStream.range(0, 256) + .filter(opcode -> CodeV1.OPCODE_ATTRIBUTES[opcode] == CodeV1.INVALID) + .mapToObj( + opcode -> + Arguments.of( + String.format("Invalid opcode %02x", opcode), + String.format("Invalid Instruction 0x%02x", opcode), + 0, + List.of(List.of(String.format("0x%02x", opcode), 0, 0, 0)))); + } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/code/EOFLayoutTest.java b/evm/src/test/java/org/hyperledger/besu/evm/code/EOFLayoutTest.java index 1b27007422..1e287557a1 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/code/EOFLayoutTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/code/EOFLayoutTest.java @@ -82,15 +82,15 @@ public class EOFLayoutTest { 1 }, {"EF0001 010004 0200010001 030000", "No Terminator", "Improper section headers", 1}, - {"EF0001 010008 0200010002 030000 00", "No type section", "Incomplete type section", 1}, + {"EF0001 010004 0200010002 030000 00", "No type section", "Incomplete type section", 1}, { - "EF0001 010008 0200010002 030001 030001 00 DA DA", + "EF0001 010004 0200010002 030001 030001 00 DA DA", "Duplicate data sections", "Expected kind 0 but read kind 3", 1 }, { - "EF0001 010008 0200010002 030000 00 00", + "EF0001 010004 0200010002 030000 00 00", "Incomplete type section", "Incomplete type section", 1 @@ -102,25 +102,31 @@ public class EOFLayoutTest { 1 }, { - "EF0001 010008 02000200020002 030000 00 0100000000000000", + "EF0001 010008 0200010001 030000 00 00000000 FE ", + "Incorrect type section size", + "Type section length incompatible with code section count - 0x1 * 4 != 0x8", + 1 + }, + { + "EF0001 010008 02000200010001 030000 00 0100000000000000 FE FE", "Incorrect section zero type input", "Code section does not have zero inputs and outputs", 1 }, { - "EF0001 010008 02000200020002 030000 00 0001000000000000", + "EF0001 010008 02000200010001 030000 00 0001000000000000 FE FE", "Incorrect section zero type output", "Code section does not have zero inputs and outputs", 1 }, { - "EF0001 010008 0200010002 030000 00 00000000 ", + "EF0001 010004 0200010002 030000 00 00000000 ", "Incomplete code section", "Incomplete code section 0", 1 }, { - "EF0001 010008 0200010002 030000 00 00000000 FE", + "EF0001 010004 0200010002 030000 00 00000000 FE", "Incomplete code section", "Incomplete code section 0", 1 @@ -138,13 +144,13 @@ public class EOFLayoutTest { 1 }, { - "EF0001 010008 0200010001 030003 00 00000000 FE DEADBEEF", + "EF0001 010004 0200010001 030003 00 00000000 FE DEADBEEF", "Incomplete data section", "Dangling data after end of all sections", 1 }, { - "EF0001 010008 0200010001 030003 00 00000000 FE BEEF", + "EF0001 010004 0200010001 030003 00 00000000 FE BEEF", "Incomplete data section", "Incomplete data section", 1 @@ -187,7 +193,7 @@ public class EOFLayoutTest { }, {"EF0001 00", "all sections missing", "Expected kind 1 but read kind 0", 1}, { - "EF0001 010004 020401" + "EF0001 011004 020401" + " 0001".repeat(1025) + " 030000 00" + " 00000000".repeat(1025) @@ -196,6 +202,15 @@ public class EOFLayoutTest { "Too many code sections - 0x401", 1 }, + {"ef000101000002000003000000", "All kinds zero size", "Invalid Types section size", 1}, + {"ef0001010000020001000103000000ef", "Zero type size ", "Invalid Types section size", 1}, + { + "ef0001010004020001000003000000", + "Zero code section length", + "Invalid Code section size for section 0", + 1 + }, + {"ef000101000402000003000000", "Zero code sections", "Invalid Code section count", 1}, }); } @@ -227,13 +242,13 @@ public class EOFLayoutTest { 1 }, { - "EF0001 010010 0200040001000200020002 030000 00 00000000 01000000 00010000 02030000 FE 5000 3000 8000", + "EF0001 010010 0200040001000200020002 030000 00 00000000 01000001 00010001 02030003 FE 5000 3000 8000", "non-void input and output types", null, 1 }, { - "EF0001 010004 020400" + "EF0001 011000 020400" + " 0001".repeat(1024) + " 030000 00" + " 00000000".repeat(1024) @@ -279,7 +294,7 @@ public class EOFLayoutTest { 1 }, { - "EF0001 010010 0200040001000200020002 030000 00 00000000 01000000 00010000 02030000 FE 5000 3000 8000", + "EF0001 010010 0200040001000200020002 030000 00 00000000 01000001 00010001 02030003 FE 5000 3000 8000", "non-void input and output types", null, 1