Skip links

Delegate call bug in ink!

Introduction

ink! is a programming language for smart contracts. It can be used in parachains built on Substrate. There was a bug in the CallBuilder::delegate() method and ink_env::invoke_contract_delegate() function which returns unexpected values.

When using the function build_call::<>().delegate(target) we observe that:

  • The storage cell key is calculated correctly.
  • The storage value is being decoded incorrectly.

This can be traced to the `LangError` implementation that changed in #1450 and was introduced in v4.0.0.

We assess the severity of the vulnerability as low.

CVE reference CVE-2023-34449

Affected versions

The issue affects versions v4.0.0, v4.0.1, v4.1.0. v4.2.0.

Minimal Error Example

// First contract, this will be performing a delegate call to the `Callee`.
#[ink(storage)]
pub struct Caller {
value: u128,
}

#[ink(message)]
pub fn get_value(&self, callee_code_hash: Hash) -> u128 {
let result = build_call::<DefaultEnvironment>()
.delegate(callee_code_hash)
.exec_input(ExecutionInput::new(Selector::new(ink::selector_bytes!(
"get_value"
))))

.returns::<u128>()
.invoke();
result
}

// Different contract, using this code hash for the delegate call.
#[ink(storage)]
pub struct Callee {
value: u128,
}

#[ink(message)]
pub fn get_value(&self) -> u128 {
self.value
}

The delegated contract in this example only returns the value.

Expected: 1

Received: 256

Every time a number is returned, it is equal to the expected value times 256 (1 byte).

This is a minimal representation of the error in the ink! documentation:

Cross-Contract Calling | ink! documentation

Testing on Different Types

We tested the use of delegate calls on different ink! types obtaining the following results.

Uint

We observe that the storage value returned is shifted by one byte (eight bit shifts left wise). The expected value can be obtained by dividing by 256.

String

An empty value is returned.

Lazy

The key is not being resolved consistently (it is dependent on the contract names, addresses and variables). If the value is fixed through a manual key, the returned value maintains the same errors as observed in ‘uint’.

Survey of Repos Using Delegate

We found several repositories on GitHub that could be potentially affected by this malfunctionality.

The vendor, Parity, reported it did not find deployed contracts on Shiden, Astar or Aleph Zero affected by this vulnerability

Fix

We informed Parity about the bug, and it was solved in #1808. If you are using one of the affected versions (v4.0.0, v4.0.1, v4.1.0. v4.2.0), please update to v4.2.1.

References

Credits

This issue was found by Facundo Lerena with the assistance of Agustín Losiggio, Arturo Beccar-Varela and Agustín Aon, all members of CoinFabrik.

Disclosure timeline

This timeline is not exhaustive and only lists events that we deemed relevant to the disclosure process.

  • 2023-06-13 CoinFabrik sent the vulnerability report to Parity.
  • 2023-06-13 Parity acknowledges the report and sends a fix solving the issue.
  • 2023-06-14 CoinFabrik validates the fix.
  • 2023-06-14 Parity pushes the fixed version and releases an advisory.
  • 2023-06-14 CoinFabrik releases this advisory.

About CoinFabrik

We – CoinFabrik – are a research and development company specialized in Web3, with a strong background in cybersecurity. Founded in 2014, we have worked on over 180 blockchain-related projects, EVM based and also for Solana, Algorand, and Polkadot. Beyond development, we offer security audits through a dedicated in-house team of senior cybersecurity professionals, currently working on code in Substrate, Solidity, Clarity, Rust, and TEAL.

Our team has an academic background in computer science and mathematics, with work experience focused on cybersecurity and software development, including academic publications, patents turned into products, and conference presentations. Furthermore, we have an ongoing collaboration on knowledge transfer and open-source projects with the University of Buenos Aires.