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

fix(levm): prevent unsafe arithmetic #1095

Open
wants to merge 18 commits into
base: levm_fixes
Choose a base branch
from

Conversation

maximopalopoli
Copy link
Contributor

@maximopalopoli maximopalopoli commented Nov 6, 2024

Motivation

The idea is to prevent arithmetic overflows and underflows in the levm code.

Description

Design desitions:

  • For the gas increment I used checked_add and added a ConsumedGasOverflow error.
  • For depth used checked_add and the StackOverflow error
  • In the VM::execute method I decided to do not raise an error, because the previous behavior was to do not raise errors, so used the saturating functions
  • In operations.rs used VMError::Internal because the value was restricted on input an is never going to cause problems
  • For gas_cost in system.rs created GasCostOverflow
  • For opcode operations used InvalidOpcode error
  • When adding to a size value I use DataSizeOverflow

Closes #1075

@maximopalopoli maximopalopoli added the levm Lambda EVM implementation label Nov 6, 2024
@maximopalopoli maximopalopoli self-assigned this Nov 6, 2024
.checked_add(WORD_SIZE)
.ok_or(VMError::VeryLargeNumber)?
.saturating_sub(1))
/ WORD_SIZE;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we use checked_div here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

checked_div only checks if denominator is zero, and WORD_SIZE is a non-zero constant

Comment on lines 46 to 53
let memory_byte_size = (args_offset
.checked_add(args_size)
.ok_or(VMError::VeryLargeNumber)?)
.max(
ret_offset
.checked_add(ret_size)
.ok_or(VMError::VeryLargeNumber)?,
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit. The parenthesis is not needed (do not commit this, it needs to be formatted).

Suggested change
let memory_byte_size = (args_offset
.checked_add(args_size)
.ok_or(VMError::VeryLargeNumber)?)
.max(
ret_offset
.checked_add(ret_size)
.ok_or(VMError::VeryLargeNumber)?,
);
let memory_byte_size = args_offset
.checked_add(args_size)
.ok_or(VMError::VeryLargeNumber)?
.max(
ret_offset
.checked_add(ret_size)
.ok_or(VMError::VeryLargeNumber)?,
);

call_opcode::NON_ZERO_VALUE_COST + call_opcode::BASIC_FALLBACK_FUNCTION_STIPEND
call_opcode::NON_ZERO_VALUE_COST
.checked_add(call_opcode::BASIC_FALLBACK_FUNCTION_STIPEND)
.ok_or(VMError::OverflowInArithmeticOp)?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are the criteria for returning a VMError::OverflowInArithmeticOp and VMError::InternalError?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Internal's are those that should not happen in any case of execution, and the OverflowInArithmeticOp's are those that should not happen normally.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now I understand, should be an Internal because should not happen in any case

Comment on lines 642 to 650
current_call_frame.gas_used = match current_call_frame
.gas_used
.checked_add(tx_report.gas_used.into())
{
Some(gas) => gas,
None => {
return Err(VMError::ConsumedGasOverflow);
}
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit. We could use .ok_or here (it is manually implemented).

@@ -737,11 +813,23 @@ impl VM {
current_call_frame: &mut CallFrame,
gas: U256,
) -> Result<(), VMError> {
if current_call_frame.gas_used + gas > current_call_frame.gas_limit {
let potential_consumed_gas = match current_call_frame.gas_used.checked_add(gas) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto.

self.env.consumed_gas += gas;

current_call_frame.gas_used = potential_consumed_gas;
self.env.consumed_gas = match self.env.consumed_gas.checked_add(gas) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto.

@maximopalopoli maximopalopoli linked an issue Nov 7, 2024 that may be closed by this pull request
@maximopalopoli maximopalopoli marked this pull request as ready for review November 7, 2024 19:59
@maximopalopoli maximopalopoli requested a review from a team as a code owner November 7, 2024 19:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
levm Lambda EVM implementation
Projects
None yet
Development

Successfully merging this pull request may close these issues.

LEVM: make operations safe
2 participants