From 89d3bdba624229f3c4e1a3530f9d8d15ff8937d4 Mon Sep 17 00:00:00 2001 From: Vladislav Volosnikov Date: Thu, 28 Nov 2024 13:49:35 +0100 Subject: [PATCH] fix(EVM): Wrap access to calldata to prevent out-of-bounds EraVM panic (#1097) --- system-contracts/contracts/EvmEmulator.yul | 86 +++++++++++++------ .../evm-emulator/EvmEmulatorLoop.template.yul | 43 +++++++--- 2 files changed, 93 insertions(+), 36 deletions(-) diff --git a/system-contracts/contracts/EvmEmulator.yul b/system-contracts/contracts/EvmEmulator.yul index 3d754f38f..ad827c5bb 100644 --- a/system-contracts/contracts/EvmEmulator.yul +++ b/system-contracts/contracts/EvmEmulator.yul @@ -1552,7 +1552,13 @@ object "EvmEmulator" { case 0x35 { // OP_CALLDATALOAD evmGasLeft := chargeGas(evmGasLeft, 3) - stackHead := calldataload(accessStackHead(sp, stackHead)) + let calldataOffset := accessStackHead(sp, stackHead) + + stackHead := 0 + // EraVM will revert if offset + length overflows uint32 + if lt(calldataOffset, UINT32_MAX()) { + stackHead := calldataload(calldataOffset) + } ip := add(ip, 1) } @@ -1565,25 +1571,38 @@ object "EvmEmulator" { case 0x37 { // OP_CALLDATACOPY evmGasLeft := chargeGas(evmGasLeft, 3) - let destOffset, offset, size + let dstOffset, sourceOffset, len popStackCheck(sp, 3) - destOffset, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) - offset, sp, stackHead:= popStackItemWithoutCheck(sp, stackHead) - size, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) - - checkMemIsAccessible(destOffset, size) + dstOffset, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) + sourceOffset, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) + len, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) - if gt(offset, MAX_UINT64()) { - offset := MAX_UINT64() - } + checkMemIsAccessible(dstOffset, len) // dynamicGas = 3 * minimum_word_size + memory_expansion_cost // minimum_word_size = (size + 31) / 32 - let dynamicGas := add(mul(3, shr(5, add(size, 31))), expandMemory(destOffset, size)) + let dynamicGas := add(mul(3, shr(5, add(len, 31))), expandMemory(dstOffset, len)) evmGasLeft := chargeGas(evmGasLeft, dynamicGas) - calldatacopy(add(destOffset, MEM_OFFSET()), offset, size) + dstOffset := add(dstOffset, MEM_OFFSET()) + + // EraVM will revert if offset + length overflows uint32 + if gt(sourceOffset, UINT32_MAX()) { + sourceOffset := UINT32_MAX() + } + + // Check bytecode out-of-bounds access + let truncatedLen := len + if gt(add(sourceOffset, len), UINT32_MAX()) { + truncatedLen := sub(UINT32_MAX(), sourceOffset) // truncate + $llvm_AlwaysInline_llvm$_memsetToZero(add(dstOffset, truncatedLen), sub(len, truncatedLen)) // pad with zeroes any out-of-bounds + } + + if truncatedLen { + calldatacopy(dstOffset, sourceOffset, truncatedLen) + } + ip := add(ip, 1) } @@ -4584,7 +4603,13 @@ object "EvmEmulator" { case 0x35 { // OP_CALLDATALOAD evmGasLeft := chargeGas(evmGasLeft, 3) - stackHead := calldataload(accessStackHead(sp, stackHead)) + let calldataOffset := accessStackHead(sp, stackHead) + + stackHead := 0 + // EraVM will revert if offset + length overflows uint32 + if lt(calldataOffset, UINT32_MAX()) { + stackHead := calldataload(calldataOffset) + } ip := add(ip, 1) } @@ -4597,25 +4622,38 @@ object "EvmEmulator" { case 0x37 { // OP_CALLDATACOPY evmGasLeft := chargeGas(evmGasLeft, 3) - let destOffset, offset, size + let dstOffset, sourceOffset, len popStackCheck(sp, 3) - destOffset, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) - offset, sp, stackHead:= popStackItemWithoutCheck(sp, stackHead) - size, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) - - checkMemIsAccessible(destOffset, size) + dstOffset, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) + sourceOffset, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) + len, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) - if gt(offset, MAX_UINT64()) { - offset := MAX_UINT64() - } + checkMemIsAccessible(dstOffset, len) // dynamicGas = 3 * minimum_word_size + memory_expansion_cost // minimum_word_size = (size + 31) / 32 - let dynamicGas := add(mul(3, shr(5, add(size, 31))), expandMemory(destOffset, size)) + let dynamicGas := add(mul(3, shr(5, add(len, 31))), expandMemory(dstOffset, len)) evmGasLeft := chargeGas(evmGasLeft, dynamicGas) - calldatacopy(add(destOffset, MEM_OFFSET()), offset, size) + dstOffset := add(dstOffset, MEM_OFFSET()) + + // EraVM will revert if offset + length overflows uint32 + if gt(sourceOffset, UINT32_MAX()) { + sourceOffset := UINT32_MAX() + } + + // Check bytecode out-of-bounds access + let truncatedLen := len + if gt(add(sourceOffset, len), UINT32_MAX()) { + truncatedLen := sub(UINT32_MAX(), sourceOffset) // truncate + $llvm_AlwaysInline_llvm$_memsetToZero(add(dstOffset, truncatedLen), sub(len, truncatedLen)) // pad with zeroes any out-of-bounds + } + + if truncatedLen { + calldatacopy(dstOffset, sourceOffset, truncatedLen) + } + ip := add(ip, 1) } diff --git a/system-contracts/evm-emulator/EvmEmulatorLoop.template.yul b/system-contracts/evm-emulator/EvmEmulatorLoop.template.yul index d0d4bbfaa..0c3196a9e 100644 --- a/system-contracts/evm-emulator/EvmEmulatorLoop.template.yul +++ b/system-contracts/evm-emulator/EvmEmulatorLoop.template.yul @@ -339,7 +339,13 @@ for { } true { } { case 0x35 { // OP_CALLDATALOAD evmGasLeft := chargeGas(evmGasLeft, 3) - stackHead := calldataload(accessStackHead(sp, stackHead)) + let calldataOffset := accessStackHead(sp, stackHead) + + stackHead := 0 + // EraVM will revert if offset + length overflows uint32 + if lt(calldataOffset, UINT32_MAX()) { + stackHead := calldataload(calldataOffset) + } ip := add(ip, 1) } @@ -352,25 +358,38 @@ for { } true { } { case 0x37 { // OP_CALLDATACOPY evmGasLeft := chargeGas(evmGasLeft, 3) - let destOffset, offset, size + let dstOffset, sourceOffset, len popStackCheck(sp, 3) - destOffset, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) - offset, sp, stackHead:= popStackItemWithoutCheck(sp, stackHead) - size, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) - - checkMemIsAccessible(destOffset, size) + dstOffset, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) + sourceOffset, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) + len, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) - if gt(offset, MAX_UINT64()) { - offset := MAX_UINT64() - } + checkMemIsAccessible(dstOffset, len) // dynamicGas = 3 * minimum_word_size + memory_expansion_cost // minimum_word_size = (size + 31) / 32 - let dynamicGas := add(mul(3, shr(5, add(size, 31))), expandMemory(destOffset, size)) + let dynamicGas := add(mul(3, shr(5, add(len, 31))), expandMemory(dstOffset, len)) evmGasLeft := chargeGas(evmGasLeft, dynamicGas) - calldatacopy(add(destOffset, MEM_OFFSET()), offset, size) + dstOffset := add(dstOffset, MEM_OFFSET()) + + // EraVM will revert if offset + length overflows uint32 + if gt(sourceOffset, UINT32_MAX()) { + sourceOffset := UINT32_MAX() + } + + // Check bytecode out-of-bounds access + let truncatedLen := len + if gt(add(sourceOffset, len), UINT32_MAX()) { + truncatedLen := sub(UINT32_MAX(), sourceOffset) // truncate + $llvm_AlwaysInline_llvm$_memsetToZero(add(dstOffset, truncatedLen), sub(len, truncatedLen)) // pad with zeroes any out-of-bounds + } + + if truncatedLen { + calldatacopy(dstOffset, sourceOffset, truncatedLen) + } + ip := add(ip, 1) }