Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adopt subcontainer Validation #7001

Merged
merged 1 commit into from May 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -17,8 +17,9 @@
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.hyperledger.besu.evmtool.CodeValidateSubCommand.COMMAND_NAME;

import org.hyperledger.besu.evm.Code;
import org.hyperledger.besu.evm.code.CodeFactory;
import org.hyperledger.besu.evm.code.CodeInvalid;
import org.hyperledger.besu.evm.code.CodeV1Validation;
import org.hyperledger.besu.evm.code.EOFLayout;
import org.hyperledger.besu.util.LogConfigurator;

Expand Down Expand Up @@ -113,16 +114,18 @@ public String considerCode(final String hexCode) {
return "";
}

var layout = EOFLayout.parseEOF(codeBytes);
EOFLayout layout = EOFLayout.parseEOF(codeBytes);
if (!layout.isValid()) {
return "err: layout - " + layout.invalidReason() + "\n";
}

var code = CodeFactory.createCode(codeBytes, 1);
if (!code.isValid()) {
return "err: " + ((CodeInvalid) code).getInvalidReason() + "\n";
String error = CodeV1Validation.validate(layout, true);
if (error != null) {
return "err: " + error + "\n";
}

Code code = CodeFactory.createCode(codeBytes, 1);

return "OK "
+ IntStream.range(0, code.getCodeSectionCount())
.mapToObj(code::getCodeSection)
Expand Down
Expand Up @@ -26,6 +26,8 @@
import org.hyperledger.besu.evm.EvmSpecVersion;
import org.hyperledger.besu.evm.code.CodeFactory;
import org.hyperledger.besu.evm.code.CodeInvalid;
import org.hyperledger.besu.evm.code.CodeV1;
import org.hyperledger.besu.evm.code.CodeV1Validation;
import org.hyperledger.besu.evm.code.EOFLayout;
import org.hyperledger.besu.util.LogConfigurator;

Expand Down Expand Up @@ -213,6 +215,12 @@ public TestResult considerCode(final String hexCode) {
if (!code.isValid()) {
return failed("validate " + ((CodeInvalid) code).getInvalidReason());
}
if (code instanceof CodeV1 codeV1) {
var result = CodeV1Validation.validate(codeV1.getEofLayout(), true);
if (result != null) {
return (failed("deep validate error: " + result));
}
}

return passed();
}
Expand Down
Expand Up @@ -27,6 +27,8 @@
import org.hyperledger.besu.evm.EvmSpecVersion;
import org.hyperledger.besu.evm.code.CodeFactory;
import org.hyperledger.besu.evm.code.CodeInvalid;
import org.hyperledger.besu.evm.code.CodeV1;
import org.hyperledger.besu.evm.code.CodeV1Validation;
import org.hyperledger.besu.evm.code.EOFLayout;
import org.hyperledger.besu.testutil.JsonTestParameters;

Expand Down Expand Up @@ -106,6 +108,18 @@ public static void executeTest(
? null
: ((CodeInvalid) parsedCode).getInvalidReason()))
.isEqualTo(results.result());
if (parsedCode instanceof CodeV1 codeV1) {
var deepValidate = CodeV1Validation.validate(codeV1.getEofLayout(), true);
assertThat(deepValidate)
.withFailMessage(
() ->
codeV1.prettyPrint()
+ "\nExpected exception :"
+ results.exception()
+ " actual exception :"
+ (parsedCode.isValid() ? null : deepValidate))
.isNull();
}

if (results.result()) {
System.out.println(code);
Expand Down
3 changes: 2 additions & 1 deletion evm/src/main/java/org/hyperledger/besu/evm/Code.java
Expand Up @@ -109,7 +109,8 @@ default int getSubcontainerCount() {
* an empty result is returned. Legacy code always returns empty.
*
* @param index the index in the container to return
* @param auxData any Auxiliary data to append to the subcontainer code
* @param auxData any Auxiliary data to append to the subcontainer code. If fetching an initcode
* container, pass null.
* @return Either the subcontainer, or empty.
*/
default Optional<Code> getSubContainer(final int index, final Bytes auxData) {
Expand Down
20 changes: 10 additions & 10 deletions evm/src/main/java/org/hyperledger/besu/evm/code/CodeFactory.java
Expand Up @@ -16,6 +16,8 @@

package org.hyperledger.besu.evm.code;

import static org.hyperledger.besu.evm.code.EOFLayout.EOFContainerMode.INITCODE;

import org.hyperledger.besu.evm.Code;

import javax.annotation.Nonnull;
Expand Down Expand Up @@ -101,7 +103,10 @@ public static Code createCode(
}

final EOFLayout layout = EOFLayout.parseEOF(bytes, !createTransaction);
return createCode(layout);
if (createTransaction) {
layout.createMode().set(INITCODE);
}
return createCode(layout, createTransaction);
} else {
return new CodeV0(bytes);
}
Expand All @@ -111,19 +116,14 @@ public static Code createCode(
}

@Nonnull
static Code createCode(final EOFLayout layout) {
static Code createCode(final EOFLayout layout, final boolean createTransaction) {
if (!layout.isValid()) {
return new CodeInvalid(layout.container(), "Invalid EOF Layout: " + layout.invalidReason());
}

final String codeValidationError = CodeV1Validation.validateCode(layout);
if (codeValidationError != null) {
return new CodeInvalid(layout.container(), "EOF Code Invalid : " + codeValidationError);
}

final String stackValidationError = CodeV1Validation.validateStack(layout);
if (stackValidationError != null) {
return new CodeInvalid(layout.container(), "EOF Code Invalid : " + stackValidationError);
final String validationError = CodeV1Validation.validate(layout, createTransaction);
if (validationError != null) {
return new CodeInvalid(layout.container(), "EOF Code Invalid : " + validationError);
}

return new CodeV1(layout);
Expand Down
11 changes: 10 additions & 1 deletion evm/src/main/java/org/hyperledger/besu/evm/code/CodeV1.java
Expand Up @@ -106,7 +106,7 @@ public Optional<Code> getSubContainer(final int index, final Bytes auxData) {
subcontainerLayout = EOFLayout.parseEOF(subcontainerWithAuxData);
}

Code subContainerCode = CodeFactory.createCode(subcontainerLayout);
Code subContainerCode = CodeFactory.createCode(subcontainerLayout, auxData == null);

return subContainerCode.isValid() && subContainerCode.getEofVersion() > 0
? Optional.of(subContainerCode)
Expand Down Expand Up @@ -169,6 +169,15 @@ public String prettyPrint() {
return sw.toString();
}

/**
* The EOFLayout object for the code
*
* @return the EOFLayout object for the parsed code
*/
public EOFLayout getEofLayout() {
return eofLayout;
}

/**
* A more readable representation of the hex bytes, including whitespace and comments after hashes
*
Expand Down
Expand Up @@ -19,10 +19,13 @@
import static java.lang.Math.max;
import static java.lang.Math.min;
import static java.lang.String.format;
import static org.hyperledger.besu.evm.code.EOFLayout.EOFContainerMode.INITCODE;
import static org.hyperledger.besu.evm.code.EOFLayout.EOFContainerMode.RUNTIME;
import static org.hyperledger.besu.evm.code.OpcodeInfo.V1_OPCODES;
import static org.hyperledger.besu.evm.internal.Words.readBigEndianI16;
import static org.hyperledger.besu.evm.internal.Words.readBigEndianU16;

import org.hyperledger.besu.evm.code.EOFLayout.EOFContainerMode;
import org.hyperledger.besu.evm.operation.CallFOperation;
import org.hyperledger.besu.evm.operation.DataLoadNOperation;
import org.hyperledger.besu.evm.operation.DupNOperation;
Expand All @@ -41,8 +44,11 @@
import org.hyperledger.besu.evm.operation.StopOperation;
import org.hyperledger.besu.evm.operation.SwapNOperation;

import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.BitSet;
import java.util.List;
import java.util.Queue;
import javax.annotation.Nullable;

import org.apache.tuweni.bytes.Bytes;
Expand All @@ -56,6 +62,38 @@ private CodeV1Validation() {
// to prevent instantiation
}

/**
* Validates the code and stack for the EOF Layout, with optional deep consideration of the
* containers.
*
* @param layout The parsed EOFLayout of the code
* @param deep optional flag to see if we should validate deeply.
* @return either null, indicating no error, or a String describing the validation error.
*/
public static String validate(final EOFLayout layout, final boolean deep) {
Queue<EOFLayout> workList = new ArrayDeque<>(layout.getSubcontainerCount());
workList.add(layout);

while (!workList.isEmpty()) {
EOFLayout container = workList.poll();
if (deep) {
workList.addAll(List.of(container.subContainers()));
}

final String codeValidationError = CodeV1Validation.validateCode(container);
if (codeValidationError != null) {
return codeValidationError;
}

final String stackValidationError = CodeV1Validation.validateStack(container);
if (stackValidationError != null) {
return stackValidationError;
}
}

return null;
}

/**
* Validate Code
*
Expand Down Expand Up @@ -91,6 +129,7 @@ static String validateCode(
final byte[] rawCode = code.toArrayUnsafe();
OpcodeInfo opcodeInfo = V1_OPCODES[0xfe];
int pos = 0;
EOFContainerMode eofContainerMode = eofLayout.createMode().get();
boolean hasReturningOpcode = false;
while (pos < size) {
final int operationNum = rawCode[pos] & 0xff;
Expand All @@ -102,6 +141,16 @@ static String validateCode(
pos += 1;
int pcPostInstruction = pos;
switch (operationNum) {
case StopOperation.OPCODE, ReturnOperation.OPCODE:
if (eofContainerMode == null) {
eofContainerMode = RUNTIME;
eofLayout.createMode().set(RUNTIME);
} else if (!eofContainerMode.equals(RUNTIME)) {
return format(
"%s is only a valid opcode in containers used for runtime operations.",
opcodeInfo.name());
}
break;
case PushOperation.PUSH_BASE,
PushOperation.PUSH_BASE + 1,
PushOperation.PUSH_BASE + 2,
Expand Down Expand Up @@ -215,7 +264,7 @@ static String validateCode(
hasReturningOpcode |= eofLayout.getCodeSection(targetSection).isReturning();
pcPostInstruction += 2;
break;
case EOFCreateOperation.OPCODE, ReturnContractOperation.OPCODE:
case EOFCreateOperation.OPCODE:
if (pos + 1 > size) {
return format(
"Dangling immediate for %s at pc=%d",
Expand All @@ -228,6 +277,13 @@ static String validateCode(
opcodeInfo.name(), subcontainerNum, pos - opcodeInfo.pcAdvance());
}
EOFLayout subContainer = eofLayout.getSubcontainer(subcontainerNum);
var subcontainerMode = subContainer.createMode().get();
if (subcontainerMode == null) {
subContainer.createMode().set(INITCODE);
} else if (subcontainerMode == RUNTIME) {
return format(
"subcontainer %d cannot be used both as initcode and runtime", subcontainerNum);
}
if (subContainer.dataLength() != subContainer.data().size()) {
return format(
"A subcontainer used for %s has a truncated data section, expected %d and is %d.",
Expand All @@ -237,6 +293,35 @@ static String validateCode(
}
pcPostInstruction += 1;
break;
case ReturnContractOperation.OPCODE:
if (eofContainerMode == null) {
eofContainerMode = INITCODE;
eofLayout.createMode().set(INITCODE);
} else if (!eofContainerMode.equals(INITCODE)) {
return format(
"%s is only a valid opcode in containers used for initcode", opcodeInfo.name());
}
if (pos + 1 > size) {
return format(
"Dangling immediate for %s at pc=%d",
opcodeInfo.name(), pos - opcodeInfo.pcAdvance());
}
int returnedContractNum = rawCode[pos] & 0xff;
if (returnedContractNum >= eofLayout.getSubcontainerCount()) {
return format(
"%s refers to non-existent subcontainer %d at pc=%d",
opcodeInfo.name(), returnedContractNum, pos - opcodeInfo.pcAdvance());
}
EOFLayout returnedContract = eofLayout.getSubcontainer(returnedContractNum);
var returnedContractMode = returnedContract.createMode().get();
if (returnedContractMode == null) {
returnedContract.createMode().set(RUNTIME);
} else if (returnedContractMode.equals(INITCODE)) {
return format(
"subcontainer %d cannot be used both as initcode and runtime", returnedContractNum);
}
pcPostInstruction += 1;
break;
default:
// a few opcodes have potentially dangling immediates
if (opcodeInfo.pcAdvance() > 1) {
Expand Down
23 changes: 20 additions & 3 deletions evm/src/main/java/org/hyperledger/besu/evm/code/EOFLayout.java
Expand Up @@ -31,6 +31,7 @@
import java.io.StringWriter;
import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import javax.annotation.Nullable;

import org.apache.tuweni.bytes.Bytes;
Expand All @@ -54,7 +55,14 @@ public record EOFLayout(
EOFLayout[] subContainers,
int dataLength,
Bytes data,
String invalidReason) {
String invalidReason,
AtomicReference<EOFContainerMode> createMode) {

enum EOFContainerMode {
UNKNOWN,
INITCODE,
RUNTIME
}

/** The EOF prefix byte as a (signed) java byte. */
public static final byte EOF_PREFIX_BYTE = (byte) 0xEF;
Expand Down Expand Up @@ -84,11 +92,20 @@ private EOFLayout(
final EOFLayout[] containers,
final int dataSize,
final Bytes data) {
this(container, version, codeSections, containers, dataSize, data, null);
this(
container,
version,
codeSections,
containers,
dataSize,
data,
null,
new AtomicReference<>(null));
}

private EOFLayout(final Bytes container, final int version, final String invalidReason) {
this(container, version, null, null, 0, Bytes.EMPTY, invalidReason);
this(
container, version, null, null, 0, Bytes.EMPTY, invalidReason, new AtomicReference<>(null));
}

private static EOFLayout invalidLayout(
Expand Down
Expand Up @@ -75,7 +75,7 @@ protected Code getInitCode(final MessageFrame frame, final EVM evm) {
int startIndex = frame.getPC() + 1;
final int initContainerIndex = code.readU8(startIndex);

return code.getSubContainer(initContainerIndex, Bytes.EMPTY).orElse(null);
return code.getSubContainer(initContainerIndex, null).orElse(null);
}

@Override
Expand Down
11 changes: 10 additions & 1 deletion evm/src/test/java/org/hyperledger/besu/evm/code/CodeV1Test.java
Expand Up @@ -22,6 +22,7 @@

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.IntStream;
import java.util.stream.Stream;

Expand Down Expand Up @@ -56,7 +57,15 @@ private static void assertValidation(
CodeSection[] codeSections = new CodeSection[i];
Arrays.fill(codeSections, new CodeSection(1, 0, returning ? 0 : 0x80, 1, 1));
EOFLayout testLayout =
new EOFLayout(codeBytes, 1, codeSections, new EOFLayout[0], 0, Bytes.EMPTY, error);
new EOFLayout(
codeBytes,
1,
codeSections,
new EOFLayout[0],
0,
Bytes.EMPTY,
error,
new AtomicReference<>());
assertValidation(error, codeBytes, codeSections[0], testLayout);
}
}
Expand Down