Skip to content

Commit

Permalink
Enforce EOF version validation rules (hyperledger#4959)
Browse files Browse the repository at this point in the history
Add enforcement of the rule that EOF cannot create prior versions or
legacy EVM code contracts in create opcodes and create transactions.

Signed-off-by: Danno Ferrin <danno.ferrin@swirldslabs.com>
  • Loading branch information
shemnon authored Jan 19, 2023
1 parent 5627ee0 commit db10beb
Show file tree
Hide file tree
Showing 17 changed files with 401 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult;
import org.hyperledger.besu.evm.MainnetEVMs;
import org.hyperledger.besu.evm.account.MutableAccount;
import org.hyperledger.besu.evm.contractvalidation.EOFValidationCodeRule;
import org.hyperledger.besu.evm.contractvalidation.MaxCodeSizeRule;
import org.hyperledger.besu.evm.contractvalidation.PrefixCodeRule;
import org.hyperledger.besu.evm.frame.MessageFrame;
Expand Down Expand Up @@ -716,6 +717,9 @@ static ProtocolSpecBuilder cancunDefinition(
final boolean quorumCompatibilityMode,
final EvmConfiguration evmConfiguration) {

final int contractSizeLimit =
configContractSizeLimit.orElse(SPURIOUS_DRAGON_CONTRACT_SIZE_LIMIT);

return shanghaiDefinition(
chainId,
configContractSizeLimit,
Expand All @@ -729,6 +733,17 @@ static ProtocolSpecBuilder cancunDefinition(
(gasCalculator, jdCacheConfig) ->
MainnetEVMs.cancun(
gasCalculator, chainId.orElse(BigInteger.ZERO), evmConfiguration))
// change contract call creator to accept EOF code
.contractCreationProcessorBuilder(
(gasCalculator, evm) ->
new ContractCreationProcessor(
gasCalculator,
evm,
true,
List.of(
MaxCodeSizeRule.of(contractSizeLimit), EOFValidationCodeRule.of(1, false)),
1,
SPURIOUS_DRAGON_FORCE_DELETE_WHEN_EMPTY_ADDRESSES))
.name("Cancun");
}

Expand Down
7 changes: 7 additions & 0 deletions evm/src/main/java/org/hyperledger/besu/evm/Code.java
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,11 @@ public interface Code {
* @return 1 for legacy, count for valid, zero for invalid.
*/
int getCodeSectionCount();

/**
* Returns the EOF version of the code. Legacy code is version 0, invalid code -1.
*
* @return The version of hte ode.
*/
int getEofVersion();
}
11 changes: 11 additions & 0 deletions evm/src/main/java/org/hyperledger/besu/evm/MainnetEVMs.java
Original file line number Diff line number Diff line change
Expand Up @@ -723,6 +723,17 @@ public static void registerShanghaiOperations(
registry.put(new Create2Operation(gasCalculator, SHANGHAI_INIT_CODE_SIZE_LIMIT));
}

/**
* Cancun evm.
*
* @param chainId the chain id
* @param evmConfiguration the evm configuration
* @return the evm
*/
public static EVM cancun(final BigInteger chainId, final EvmConfiguration evmConfiguration) {
return cancun(new ShanghaiGasCalculator(), chainId, evmConfiguration);
}

/**
* Cancun evm.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,9 @@ public CodeSection getCodeSection(final int section) {
public int getCodeSectionCount() {
return 0;
}

@Override
public int getEofVersion() {
return -1;
}
}
5 changes: 5 additions & 0 deletions evm/src/main/java/org/hyperledger/besu/evm/code/CodeV0.java
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,11 @@ public int getCodeSectionCount() {
return 1;
}

@Override
public int getEofVersion() {
return 0;
}

/**
* Calculate jump destination.
*
Expand Down
5 changes: 5 additions & 0 deletions evm/src/main/java/org/hyperledger/besu/evm/code/CodeV1.java
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ public boolean isValid() {
return true;
}

@Override
public int getEofVersion() {
return eofLayout.getVersion();
}

@Override
public boolean equals(final Object o) {
if (this == o) return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.hyperledger.besu.evm.EvmSpecVersion;
import org.hyperledger.besu.evm.code.CodeFactory;
import org.hyperledger.besu.evm.frame.ExceptionalHaltReason;
import org.hyperledger.besu.evm.frame.MessageFrame;

import java.util.Optional;

Expand All @@ -41,7 +42,8 @@ public CachedInvalidCodeRule(final int maxEofVersion) {
}

@Override
public Optional<ExceptionalHaltReason> validate(final Bytes contractCode) {
public Optional<ExceptionalHaltReason> validate(
final Bytes contractCode, final MessageFrame frame) {
final Code code =
CodeFactory.createCode(contractCode, Hash.hash(contractCode), maxEofVersion, false);
if (!code.isValid()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package org.hyperledger.besu.evm.contractvalidation;

import org.hyperledger.besu.evm.frame.ExceptionalHaltReason;
import org.hyperledger.besu.evm.frame.MessageFrame;

import java.util.Optional;

Expand All @@ -28,7 +29,8 @@ public interface ContractValidationRule {
* Validate.
*
* @param contractCode the contract code to validate
* @param frame the message frame to use for context
* @return the optional halt reason
*/
Optional<ExceptionalHaltReason> validate(Bytes contractCode);
Optional<ExceptionalHaltReason> validate(Bytes contractCode, MessageFrame frame);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright contributors to Hyperledger Besu
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.evm.contractvalidation;

import org.hyperledger.besu.datatypes.Hash;
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.frame.ExceptionalHaltReason;
import org.hyperledger.besu.evm.frame.MessageFrame;

import java.util.Optional;

import org.apache.tuweni.bytes.Bytes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Validates code is properly EOF formatted if it has the correct magic bytes. This supplants
* PrefixRule.
*/
public class EOFValidationCodeRule implements ContractValidationRule {

private static final Logger LOG = LoggerFactory.getLogger(EOFValidationCodeRule.class);

final int maxEofVersion;
final boolean inCreateTransaction;

private EOFValidationCodeRule(final int maxEofVersion, final boolean inCreateTransaction) {
this.maxEofVersion = maxEofVersion;
this.inCreateTransaction = inCreateTransaction;
}

/**
* Runs eof validation rules as per the various EIPs covering it.
*
* @param contractCode the contract code to validate
* @param frame the message frame to use for context
* @return Either an empty optional on success, or an optional containing one of the invalid
* reasons.
*/
@Override
public Optional<ExceptionalHaltReason> validate(
final Bytes contractCode, final MessageFrame frame) {
Code code =
CodeFactory.createCode(
contractCode, Hash.hash(contractCode), maxEofVersion, inCreateTransaction);
if (!code.isValid()) {
LOG.trace("EOF Validation Error: {}", ((CodeInvalid) code).getInvalidReason());
return Optional.of(ExceptionalHaltReason.INVALID_CODE);
}

if (frame.getCode().getEofVersion() > code.getEofVersion()) {
LOG.trace(
"Cannot deploy older eof versions: initcode version - {} runtime code version - {}",
frame.getCode().getEofVersion(),
code.getEofVersion());
return Optional.of(ExceptionalHaltReason.EOF_CREATE_VERSION_INCOMPATIBLE);
}

return Optional.empty();
}

/**
* Create EOF validation.
*
* @param maxEofVersion Maximum EOF version to validate
* @param inCreateTransaction Is this inside a create transaction?
* @return The EOF validation contract validation rule.
*/
public static ContractValidationRule of(
final int maxEofVersion, final boolean inCreateTransaction) {
return new EOFValidationCodeRule(maxEofVersion, inCreateTransaction);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package org.hyperledger.besu.evm.contractvalidation;

import org.hyperledger.besu.evm.frame.ExceptionalHaltReason;
import org.hyperledger.besu.evm.frame.MessageFrame;

import java.util.Optional;

Expand All @@ -39,7 +40,8 @@ public class MaxCodeSizeRule implements ContractValidationRule {
}

@Override
public Optional<ExceptionalHaltReason> validate(final Bytes contractCode) {
public Optional<ExceptionalHaltReason> validate(
final Bytes contractCode, final MessageFrame frame) {
final int contractCodeSize = contractCode.size();
if (contractCodeSize <= maxCodeSize) {
return Optional.empty();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package org.hyperledger.besu.evm.contractvalidation;

import org.hyperledger.besu.evm.frame.ExceptionalHaltReason;
import org.hyperledger.besu.evm.frame.MessageFrame;

import java.util.Optional;

Expand All @@ -31,7 +32,8 @@ public class PrefixCodeRule implements ContractValidationRule {

@Override
// As per https://eips.ethereum.org/EIPS/eip-3541
public Optional<ExceptionalHaltReason> validate(final Bytes contractCode) {
public Optional<ExceptionalHaltReason> validate(
final Bytes contractCode, final MessageFrame frame) {
if (!contractCode.isEmpty() && contractCode.get(0) == FORMAT_RESERVED) {
LOG.trace("Contract creation error: code cannot start with {}", FORMAT_RESERVED);
return Optional.of(ExceptionalHaltReason.INVALID_CODE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ public interface ExceptionalHaltReason {
DefaultExceptionalHaltReason.TOO_FEW_INPUTS_FOR_CODE_SECTION;
/** The constant JUMPF_STACK_MISMATCH. */
ExceptionalHaltReason JUMPF_STACK_MISMATCH = DefaultExceptionalHaltReason.JUMPF_STACK_MISMATCH;
/** The constant EOF_CREATE_VERSION_INCOMPATIBLE. */
ExceptionalHaltReason EOF_CREATE_VERSION_INCOMPATIBLE =
DefaultExceptionalHaltReason.EOF_CREATE_VERSION_INCOMPATIBLE;

/**
* Name string.
Expand Down Expand Up @@ -107,7 +110,10 @@ enum DefaultExceptionalHaltReason implements ExceptionalHaltReason {
TOO_FEW_INPUTS_FOR_CODE_SECTION("Not enough stack items for a function call"),
/** The Jumpf stack mismatch. */
JUMPF_STACK_MISMATCH(
"The stack height for a JUMPF does not match the requirements of the target section");
"The stack height for a JUMPF does not match the requirements of the target section"),
/** The Eof version incompatible. */
EOF_CREATE_VERSION_INCOMPATIBLE(
"EOF Code is attempting to create EOF code of an earlier version");

/** The Description. */
final String description;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public OperationResult execute(final MessageFrame frame, final EVM evm) {
final Bytes inputData = frame.readMemory(inputOffset, inputSize);
Code code = evm.getCode(Hash.hash(inputData), inputData);

if (code.isValid()) {
if (code.isValid() && frame.getCode().getEofVersion() <= code.getEofVersion()) {
frame.decrementRemainingGas(cost);
spawnChildMessage(frame, code, evm);
frame.incrementRemainingGas(cost);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ public void codeSuccess(final MessageFrame frame, final OperationTracer operatio
} else {
final var invalidReason =
contractValidationRules.stream()
.map(rule -> rule.validate(contractCode))
.map(rule -> rule.validate(contractCode, frame))
.filter(Optional::isPresent)
.findFirst();
if (invalidReason.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import org.hyperledger.besu.evm.operation.Create2Operation;
import org.hyperledger.besu.evm.operation.Operation.OperationResult;
import org.hyperledger.besu.evm.processor.ContractCreationProcessor;
import org.hyperledger.besu.evm.testutils.TestMessageFrameBuilder;
import org.hyperledger.besu.evm.tracing.OperationTracer;
import org.hyperledger.besu.evm.worldstate.WorldUpdater;
import org.hyperledger.besu.evm.worldstate.WrappedEvmAccount;
Expand Down Expand Up @@ -81,6 +82,8 @@ public class Create2OperationTest {
+ "6000" // PUSH1 0x00
+ "F3" // RETURN
);
public static final Bytes SIMPLE_EOF =
Bytes.fromHexString("0xEF00010100040200010001030000000000000000");
public static final String SENDER = "0xdeadc0de00000000000000000000000000000000";
private static final int SHANGHAI_CREATE_GAS = 41240 + (0xc000 / 32) * 6;

Expand Down Expand Up @@ -302,4 +305,58 @@ private MessageFrame testMemoryFrame(
memoryOffset.trimLeadingZeros().toInt(), SIMPLE_CREATE.size(), SIMPLE_CREATE);
return messageFrame;
}

@Test
void eofV1CannotCreateLegacy() {
final UInt256 memoryOffset = UInt256.fromHexString("0xFF");
final UInt256 memoryLength = UInt256.valueOf(SIMPLE_CREATE.size());
final MessageFrame messageFrame =
new TestMessageFrameBuilder()
.code(CodeFactory.createCode(SIMPLE_EOF, Hash.hash(SIMPLE_EOF), 1, true))
.pushStackItem(Bytes.EMPTY)
.pushStackItem(memoryLength)
.pushStackItem(memoryOffset)
.pushStackItem(Bytes.EMPTY)
.worldUpdater(worldUpdater)
.build();
messageFrame.writeMemory(memoryOffset.toLong(), memoryLength.toLong(), SIMPLE_CREATE);

when(account.getMutable()).thenReturn(mutableAccount);
when(mutableAccount.getBalance()).thenReturn(Wei.ZERO);
when(worldUpdater.getAccount(any())).thenReturn(account);

final EVM evm = MainnetEVMs.cancun(DEV_NET_CHAIN_ID, EvmConfiguration.DEFAULT);
var result = operation.execute(messageFrame, evm);
assertThat(result.getHaltReason()).isNull();
assertThat(messageFrame.getStackItem(0)).isEqualTo(UInt256.ZERO);
}

@Test
void legacyCanCreateEOFv1() {
final UInt256 memoryOffset = UInt256.fromHexString("0xFF");
final UInt256 memoryLength = UInt256.valueOf(SIMPLE_EOF.size());
final MessageFrame messageFrame =
new TestMessageFrameBuilder()
.code(CodeFactory.createCode(SIMPLE_CREATE, Hash.hash(SIMPLE_CREATE), 1, true))
.pushStackItem(Bytes.EMPTY)
.pushStackItem(memoryLength)
.pushStackItem(memoryOffset)
.pushStackItem(Bytes.EMPTY)
.worldUpdater(worldUpdater)
.build();
messageFrame.writeMemory(memoryOffset.toLong(), memoryLength.toLong(), SIMPLE_EOF);

when(account.getMutable()).thenReturn(mutableAccount);
when(account.getNonce()).thenReturn(55L);
when(mutableAccount.getBalance()).thenReturn(Wei.ZERO);
when(worldUpdater.getAccount(any())).thenReturn(account);
when(worldUpdater.get(any())).thenReturn(account);
when(worldUpdater.getSenderAccount(any())).thenReturn(account);
when(worldUpdater.updater()).thenReturn(worldUpdater);

final EVM evm = MainnetEVMs.cancun(DEV_NET_CHAIN_ID, EvmConfiguration.DEFAULT);
var result = operation.execute(messageFrame, evm);
assertThat(result.getHaltReason()).isNull();
assertThat(messageFrame.getStackItem(0)).isNotEqualTo(UInt256.ZERO);
}
}
Loading

0 comments on commit db10beb

Please sign in to comment.