While writing and testing the Merkle Distributor airdrop contract, @ipromise2324 and I discovered a vulnerability in the funC smart contract. This vulnerability is related to left-shift operations and boolean comparisons. In theory, assume variable k=0 when we compute x = (1 << k) and then compare x == 1, we expect the result to be true (-1). However, we received false (0), which deviates from the expected outcome.
Impact
Bitwise operations are commonly used in smart contracts to reduce gas fees and are often employed in validity checks. For instance, in an airdrop contract, a user should not be able to claim the airdrop more than once, so we mark the user as true to indicate they have already claimed. If developers are unaware of this vulnerability, malicious users could potentially claim the airdrop repeatedly until the rewards are depleted. Similarly, if this error is present in access control mechanisms, it could enable hackers to execute contract logic under different identities, leading to significant financial losses.
Problem
When we call the is_claimed function with the index parameter set to 0, the claim_bit_index is calculated as 0 % 256, which results in claim_bit_index = 0. Consequently, int mask = 1 << 0, meaning mask = 1.
As we start using the mask variable for some comparisons, we notice an interesting situation. The mask currently equals 1, but when we compare it with mask == 1, the result dumped is 0, which is not what we expected. It should be -1, because mask = 1 and 1 == 1. Then, when we compare mask != 1, the dumped result turns out to be -1, which is also contrary to common logic.
Temporary Solution
We found that appending a division by 1 after the left-shift operation resolves the issue and yields the expected result.
Background
We have submitted the issue to ton monorepo issue and ton research as well.
Introduction
While writing and testing the Merkle Distributor airdrop contract, @ipromise2324 and I discovered a vulnerability in the funC smart contract. This vulnerability is related to left-shift operations and boolean comparisons. In theory, assume variable
k=0
when we computex = (1 << k)
and then comparex == 1
, we expect the result to be true (-1
). However, we received false (0
), which deviates from the expected outcome.Impact
Bitwise operations are commonly used in smart contracts to reduce gas fees and are often employed in validity checks. For instance, in an airdrop contract, a user should not be able to claim the airdrop more than once, so we mark the user as true to indicate they have already claimed. If developers are unaware of this vulnerability, malicious users could potentially claim the airdrop repeatedly until the rewards are depleted. Similarly, if this error is present in access control mechanisms, it could enable hackers to execute contract logic under different identities, leading to significant financial losses.
Problem
When we call the is_claimed function with the
index
parameter set to0
, the claim_bit_index is calculated as0 % 256
, which results inclaim_bit_index = 0
. Consequently,int mask = 1 << 0
, meaningmask = 1
.As we start using the mask variable for some comparisons, we notice an interesting situation. The mask currently equals 1, but when we compare it with
mask == 1
, the result dumped is0
, which is not what we expected. It should be-1
, becausemask = 1
and1 == 1
. Then, when we comparemask != 1
, the dumped result turns out to be-1
, which is also contrary to common logic.Temporary Solution
We found that appending a division by 1 after the left-shift operation resolves the issue and yields the expected result.
Enviroments
We are using @ton/blueprint": "^0.21.0
Hardware & OS
MacOS (Macbook M2 air) built-in SSD. MacOS (Macbook M1 pro) built-in SSD.