Skip to content

Commit

Permalink
fix(EVM): Wrap access to calldata to prevent out-of-bounds EraVM panic (
Browse files Browse the repository at this point in the history
  • Loading branch information
0xVolosnikov committed Dec 10, 2024
1 parent 8f08c2e commit d865b21
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 36 deletions.
86 changes: 62 additions & 24 deletions system-contracts/contracts/EvmEmulator.yul
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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)

}
Expand Down Expand Up @@ -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)
}
Expand All @@ -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)

}
Expand Down
43 changes: 31 additions & 12 deletions system-contracts/evm-emulator/EvmEmulatorLoop.template.yul
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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)

}
Expand Down

0 comments on commit d865b21

Please sign in to comment.