From 767850074ff088835675458ed05004a420e0249a Mon Sep 17 00:00:00 2001 From: voith Date: Thu, 16 Sep 2021 15:17:12 +0530 Subject: [PATCH] use safe arithmetic wherever gas calculations can cause U256 to overflow --- .../frontier/vm/instructions/environment.py | 59 ++++++++++++++----- .../frontier/vm/instructions/keccak.py | 21 +++++-- .../frontier/vm/instructions/memory.py | 32 +++++++--- .../frontier/vm/instructions/system.py | 23 ++++++-- .../vm/precompiled_contracts/identity.py | 15 ++++- .../vm/precompiled_contracts/ripemd160.py | 15 ++++- .../vm/precompiled_contracts/sha256.py | 15 ++++- src/ethereum/utils/safe_arithmetic.py | 12 ++-- 8 files changed, 148 insertions(+), 44 deletions(-) diff --git a/src/ethereum/frontier/vm/instructions/environment.py b/src/ethereum/frontier/vm/instructions/environment.py index e0ee1a3cda..0e0bc1d83b 100644 --- a/src/ethereum/frontier/vm/instructions/environment.py +++ b/src/ethereum/frontier/vm/instructions/environment.py @@ -15,8 +15,10 @@ from ethereum.base_types import U256, Uint from ethereum.frontier.state import get_account from ethereum.frontier.utils.address import to_address +from ethereum.frontier.vm.error import OutOfGasError from ethereum.frontier.vm.memory import extend_memory, memory_write from ethereum.utils.numeric import ceil32 +from ethereum.utils.safe_arithmetic import u256_safe_add, u256_safe_multiply from .. import Evm from ..gas import ( @@ -202,12 +204,21 @@ def calldatacopy(evm: Evm) -> None: size = pop(evm.stack) words = ceil32(Uint(size)) // 32 - gas_cost = ( - GAS_VERY_LOW - + (GAS_COPY * words) - + calculate_gas_extend_memory(evm.memory, memory_start_index, size) + copy_gas_cost = u256_safe_multiply( + GAS_COPY, + words, + exception_type=OutOfGasError, ) - evm.gas_left = subtract_gas(evm.gas_left, gas_cost) + memory_extend_gas_cost = calculate_gas_extend_memory( + evm.memory, memory_start_index, size + ) + total_gas_cost = u256_safe_add( + GAS_VERY_LOW, + copy_gas_cost, + memory_extend_gas_cost, + exception_type=OutOfGasError, + ) + evm.gas_left = subtract_gas(evm.gas_left, total_gas_cost) if size == 0: return @@ -264,12 +275,21 @@ def codecopy(evm: Evm) -> None: size = pop(evm.stack) words = ceil32(Uint(size)) // 32 - gas_cost = ( - GAS_VERY_LOW - + (GAS_COPY * words) - + calculate_gas_extend_memory(evm.memory, memory_start_index, size) + copy_gas_cost = u256_safe_multiply( + GAS_COPY, + words, + exception_type=OutOfGasError, + ) + memory_extend_gas_cost = calculate_gas_extend_memory( + evm.memory, memory_start_index, size ) - evm.gas_left = subtract_gas(evm.gas_left, gas_cost) + total_gas_cost = u256_safe_add( + GAS_VERY_LOW, + copy_gas_cost, + memory_extend_gas_cost, + exception_type=OutOfGasError, + ) + evm.gas_left = subtract_gas(evm.gas_left, total_gas_cost) if size == 0: return @@ -355,12 +375,21 @@ def extcodecopy(evm: Evm) -> None: size = pop(evm.stack) words = ceil32(Uint(size)) // 32 - gas_cost = ( - GAS_EXTERNAL - + (GAS_COPY * words) - + calculate_gas_extend_memory(evm.memory, memory_start_index, size) + copy_gas_cost = u256_safe_multiply( + GAS_COPY, + words, + exception_type=OutOfGasError, + ) + memory_extend_gas_cost = calculate_gas_extend_memory( + evm.memory, memory_start_index, size + ) + total_gas_cost = u256_safe_add( + GAS_EXTERNAL, + copy_gas_cost, + memory_extend_gas_cost, + exception_type=OutOfGasError, ) - evm.gas_left = subtract_gas(evm.gas_left, gas_cost) + evm.gas_left = subtract_gas(evm.gas_left, total_gas_cost) if size == 0: return diff --git a/src/ethereum/frontier/vm/instructions/keccak.py b/src/ethereum/frontier/vm/instructions/keccak.py index 4930fda3e8..b4e322eb0d 100644 --- a/src/ethereum/frontier/vm/instructions/keccak.py +++ b/src/ethereum/frontier/vm/instructions/keccak.py @@ -14,7 +14,9 @@ from ethereum.base_types import U256, Uint from ethereum.crypto import keccak256 +from ethereum.frontier.vm.error import OutOfGasError from ethereum.utils.numeric import ceil32 +from ethereum.utils.safe_arithmetic import u256_safe_add, u256_safe_multiply from .. import Evm from ..gas import ( @@ -50,12 +52,21 @@ def keccak(evm: Evm) -> None: size = pop(evm.stack) words = ceil32(Uint(size)) // 32 - gas_cost = ( - GAS_KECCAK256 - + (GAS_KECCAK256_WORD * words) - + calculate_gas_extend_memory(evm.memory, memory_start_index, size) + word_gas_cost = u256_safe_multiply( + GAS_KECCAK256_WORD, + words, + exception_type=OutOfGasError, ) - evm.gas_left = subtract_gas(evm.gas_left, gas_cost) + memory_extend_gas_cost = calculate_gas_extend_memory( + evm.memory, memory_start_index, size + ) + total_gas_cost = u256_safe_add( + GAS_KECCAK256, + word_gas_cost, + memory_extend_gas_cost, + exception_type=OutOfGasError, + ) + evm.gas_left = subtract_gas(evm.gas_left, total_gas_cost) extend_memory(evm.memory, memory_start_index, size) diff --git a/src/ethereum/frontier/vm/instructions/memory.py b/src/ethereum/frontier/vm/instructions/memory.py index 2e344bc66c..2417fbd956 100644 --- a/src/ethereum/frontier/vm/instructions/memory.py +++ b/src/ethereum/frontier/vm/instructions/memory.py @@ -12,6 +12,8 @@ Implementations of the EVM Memory instructions. """ from ethereum.base_types import U8_MAX_VALUE, U256, Uint +from ethereum.frontier.vm.error import OutOfGasError +from ethereum.utils.safe_arithmetic import u256_safe_add from .. import Evm from ..gas import ( @@ -47,9 +49,13 @@ def mstore(evm: Evm) -> None: start_position = Uint(pop(evm.stack)) value = pop(evm.stack).to_be_bytes32() - total_gas_cost = ( - calculate_gas_extend_memory(evm.memory, start_position, U256(32)) - + GAS_VERY_LOW + gas_cost_memory_extend = calculate_gas_extend_memory( + evm.memory, start_position, U256(32) + ) + total_gas_cost = u256_safe_add( + GAS_VERY_LOW, + gas_cost_memory_extend, + exception_type=OutOfGasError, ) evm.gas_left = subtract_gas(evm.gas_left, total_gas_cost) @@ -83,9 +89,13 @@ def mstore8(evm: Evm) -> None: # make sure that value doesn't exceed 1 byte normalized_bytes_value = (value & U8_MAX_VALUE).to_be_bytes() - total_gas_cost = ( - calculate_gas_extend_memory(evm.memory, start_position, U256(1)) - + GAS_VERY_LOW + memory_extend_gas_cost = calculate_gas_extend_memory( + evm.memory, start_position, U256(1) + ) + total_gas_cost = u256_safe_add( + GAS_VERY_LOW, + memory_extend_gas_cost, + exception_type=OutOfGasError, ) evm.gas_left = subtract_gas(evm.gas_left, total_gas_cost) @@ -114,9 +124,13 @@ def mload(evm: Evm) -> None: # convert to Uint as start_position + size_to_extend can overflow. start_position = Uint(pop(evm.stack)) - total_gas_cost = ( - calculate_gas_extend_memory(evm.memory, start_position, U256(32)) - + GAS_VERY_LOW + memory_extend_gas_cost = calculate_gas_extend_memory( + evm.memory, start_position, U256(32) + ) + total_gas_cost = u256_safe_add( + GAS_VERY_LOW, + memory_extend_gas_cost, + exception_type=OutOfGasError, ) evm.gas_left = subtract_gas(evm.gas_left, total_gas_cost) diff --git a/src/ethereum/frontier/vm/instructions/system.py b/src/ethereum/frontier/vm/instructions/system.py index 323907a834..ec548c6755 100644 --- a/src/ethereum/frontier/vm/instructions/system.py +++ b/src/ethereum/frontier/vm/instructions/system.py @@ -12,6 +12,8 @@ Implementations of the EVM system related instructions. """ from ethereum.base_types import U256, Uint +from ethereum.frontier.vm.error import OutOfGasError +from ethereum.utils.safe_arithmetic import u256_safe_add from ...state import get_account, increment_nonce, set_account_balance from ...utils.address import compute_contract_address, to_address @@ -45,10 +47,15 @@ def create(evm: Evm) -> None: memory_start_position = Uint(pop(evm.stack)) memory_size = pop(evm.stack) - gas_cost = GAS_CREATE + calculate_gas_extend_memory( + extend_memory_gas_cost = calculate_gas_extend_memory( evm.memory, memory_start_position, memory_size ) - evm.gas_left = subtract_gas(evm.gas_left, gas_cost) + total_gas_cost = u256_safe_add( + GAS_CREATE, + extend_memory_gas_cost, + exception_type=OutOfGasError, + ) + evm.gas_left = subtract_gas(evm.gas_left, total_gas_cost) extend_memory(evm.memory, memory_start_position, memory_size) sender_address = evm.env.origin sender = get_account(evm.env.state, sender_address) @@ -141,7 +148,11 @@ def call(evm: Evm) -> None: memory_output_size = pop(evm.stack) call_gas_fee = calculate_call_gas_cost(evm.env.state, gas, to, value) - message_call_gas_fee = gas + calculate_message_call_gas_stipend(value) + message_call_gas_fee = u256_safe_add( + gas, + calculate_message_call_gas_stipend(value), + exception_type=OutOfGasError, + ) evm.gas_left = subtract_gas(evm.gas_left, call_gas_fee) @@ -225,7 +236,11 @@ def callcode(evm: Evm) -> None: to = evm.message.current_target call_gas_fee = calculate_call_gas_cost(evm.env.state, gas, to, value) - message_call_gas_fee = gas + calculate_message_call_gas_stipend(value) + message_call_gas_fee = u256_safe_add( + gas, + calculate_message_call_gas_stipend(value), + exception_type=OutOfGasError, + ) evm.gas_left = subtract_gas(evm.gas_left, call_gas_fee) diff --git a/src/ethereum/frontier/vm/precompiled_contracts/identity.py b/src/ethereum/frontier/vm/precompiled_contracts/identity.py index ef7d01c39f..54d3b2dffb 100644 --- a/src/ethereum/frontier/vm/precompiled_contracts/identity.py +++ b/src/ethereum/frontier/vm/precompiled_contracts/identity.py @@ -12,7 +12,9 @@ Implementation of the `IDENTITY` precompiled contract. """ from ethereum.base_types import Uint +from ethereum.frontier.vm.error import OutOfGasError from ethereum.utils.numeric import ceil32 +from ethereum.utils.safe_arithmetic import u256_safe_add, u256_safe_multiply from ...vm import Evm from ...vm.gas import GAS_IDENTITY, GAS_IDENTITY_WORD, subtract_gas @@ -29,6 +31,15 @@ def identity(evm: Evm) -> None: """ data = evm.message.data word_count = ceil32(Uint(len(data))) // 32 - gas_fee = GAS_IDENTITY + word_count * GAS_IDENTITY_WORD - evm.gas_left = subtract_gas(evm.gas_left, gas_fee) + word_count_gas_cost = u256_safe_multiply( + word_count, + GAS_IDENTITY_WORD, + exception_type=OutOfGasError, + ) + total_gas_cost = u256_safe_add( + GAS_IDENTITY, + word_count_gas_cost, + exception_type=OutOfGasError, + ) + evm.gas_left = subtract_gas(evm.gas_left, total_gas_cost) evm.output = data diff --git a/src/ethereum/frontier/vm/precompiled_contracts/ripemd160.py b/src/ethereum/frontier/vm/precompiled_contracts/ripemd160.py index 74cc1c0e2b..8379dc0153 100644 --- a/src/ethereum/frontier/vm/precompiled_contracts/ripemd160.py +++ b/src/ethereum/frontier/vm/precompiled_contracts/ripemd160.py @@ -14,8 +14,10 @@ import hashlib from ethereum.base_types import Uint +from ethereum.frontier.vm.error import OutOfGasError from ethereum.utils.byte import left_pad_zero_bytes from ethereum.utils.numeric import ceil32 +from ethereum.utils.safe_arithmetic import u256_safe_add, u256_safe_multiply from ...vm import Evm from ...vm.gas import GAS_RIPEMD160, GAS_RIPEMD160_WORD, subtract_gas @@ -32,8 +34,17 @@ def ripemd160(evm: Evm) -> None: """ data = evm.message.data word_count = ceil32(Uint(len(data))) // 32 - gas_fee = GAS_RIPEMD160 + word_count * GAS_RIPEMD160_WORD - evm.gas_left = subtract_gas(evm.gas_left, gas_fee) + word_count_gas_cost = u256_safe_multiply( + word_count, + GAS_RIPEMD160_WORD, + exception_type=OutOfGasError, + ) + total_gas_cost = u256_safe_add( + GAS_RIPEMD160, + word_count_gas_cost, + exception_type=OutOfGasError, + ) + evm.gas_left = subtract_gas(evm.gas_left, total_gas_cost) hash_bytes = hashlib.new("ripemd160", data).digest() padded_hash = left_pad_zero_bytes(hash_bytes, 32) evm.output = padded_hash diff --git a/src/ethereum/frontier/vm/precompiled_contracts/sha256.py b/src/ethereum/frontier/vm/precompiled_contracts/sha256.py index a99f5f742f..d62ecc3afe 100644 --- a/src/ethereum/frontier/vm/precompiled_contracts/sha256.py +++ b/src/ethereum/frontier/vm/precompiled_contracts/sha256.py @@ -14,7 +14,9 @@ import hashlib from ethereum.base_types import Uint +from ethereum.frontier.vm.error import OutOfGasError from ethereum.utils.numeric import ceil32 +from ethereum.utils.safe_arithmetic import u256_safe_add, u256_safe_multiply from ...vm import Evm from ...vm.gas import GAS_SHA256, GAS_SHA256_WORD, subtract_gas @@ -31,6 +33,15 @@ def sha256(evm: Evm) -> None: """ data = evm.message.data word_count = ceil32(Uint(len(data))) // 32 - gas_fee = GAS_SHA256 + word_count * GAS_SHA256_WORD - evm.gas_left = subtract_gas(evm.gas_left, gas_fee) + word_count_gas_cost = u256_safe_multiply( + word_count, + GAS_SHA256_WORD, + exception_type=OutOfGasError, + ) + total_gas_cost = u256_safe_add( + GAS_SHA256, + word_count_gas_cost, + exception_type=OutOfGasError, + ) + evm.gas_left = subtract_gas(evm.gas_left, total_gas_cost) evm.output = hashlib.sha256(data).digest() diff --git a/src/ethereum/utils/safe_arithmetic.py b/src/ethereum/utils/safe_arithmetic.py index d1fde8500b..fec05cedb4 100644 --- a/src/ethereum/utils/safe_arithmetic.py +++ b/src/ethereum/utils/safe_arithmetic.py @@ -11,13 +11,14 @@ Safe arithmetic utility functions for U256 integer type. """ -from typing import Optional, Type +from typing import Optional, Type, Union -from ethereum.base_types import U256 +from ethereum.base_types import U256, Uint def u256_safe_add( - *numbers: U256, exception_type: Optional[Type[BaseException]] = None + *numbers: Union[U256, Uint], + exception_type: Optional[Type[BaseException]] = None ) -> U256: """ Adds together the given sequence of numbers. If the total sum of the @@ -54,7 +55,8 @@ def u256_safe_add( def u256_safe_multiply( - *numbers: U256, exception_type: Optional[Type[BaseException]] = None + *numbers: Union[U256, Uint], + exception_type: Optional[Type[BaseException]] = None ) -> U256: """ Multiplies together the given sequence of numbers. If the net product of @@ -85,7 +87,7 @@ def u256_safe_multiply( try: for number in numbers[1:]: result *= number - return result + return U256(result) except ValueError as e: if exception_type: raise exception_type from e