Skip to content

Latest commit

 

History

History
110 lines (76 loc) · 5.15 KB

031.md

File metadata and controls

110 lines (76 loc) · 5.15 KB

Flat Cider Sardine

Medium

Race condition in blacklist mechanism can disrupt pending transactions

Summary

The Blacklist mechanism in the stablecoin contract is vulnerable to a race condition. If an address is blacklisted after initiating a transaction but before the transaction completes, the transaction may behave unexpectedly, potentially leading to disrupted or reverted operations. This vulnerability can be exploited in high-concurrency scenarios to disrupt services or cause financial inconsistencies in the system, especially in cases where multiple transactions are processed in parallel.

Root Cause

The contract’s blacklist mechanism prevents blacklisted addresses from executing transactions by checking their status at various points. However, the blacklist status is evaluated only once, and subsequent operations assume that the account remains authorized for the entire duration of the transaction. In high-concurrency scenarios, if an address is blacklisted mid-transaction, this can lead to unexpected behavior such as partial fund transfers, unexpected reverts, or even transaction completion with no fund transfer.

This issue arises in the following vulnerable code snippet: https://github.com/sherlock-audit/2024-11-telcoin/blob/main/telcoin-audit/contracts/stablecoin/Stablecoin.sol#L127-L136

function _update(address from, address to, uint256 value) internal override {
    if (blacklisted(from)) revert Blacklisted(from);
    if (blacklisted(to)) revert Blacklisted(to);
    super._update(from, to, value);
}

Here, the blacklist check is performed only once. However, if an address is blacklisted after the blacklisted(from) check but before the _update function completes, the transaction may proceed unexpectedly or revert depending on concurrent updates to the blacklist status.

Internal pre-conditions

No response

External pre-conditions

No response

Attack Path

No response

Impact

When an address is blacklisted, transactions involving that address are expected to fail (e.g., the transfer will revert). This behavior is normal as per the contract's design and logic. In normal circumstances, when a user is blacklisted, any transaction involving that user will revert as expected. There is no immediate loss or exploit if the system is functioning properly.

The problem arises when a transaction is initiated by a user before they are blacklisted, but the user’s address is added to the blacklist before the transaction is completed. This creates a race condition where the transaction should have been valid but fails because the blacklist check is performed too late.

The transaction fails despite the user initiating it before being blacklisted, causing unexpected behavior for the user. For example, the user might not be aware that their address has been blacklisted during the execution, leading to frustration or confusion. This can result in loss of time, gas costs, and potentially missed opportunities if the transaction was part of an important sequence (e.g., a time-sensitive transfer).

If a legitimate user attempts multiple transactions and faces repeated reverts due to this race condition, they might waste gas on failed transactions. This can lead to economic losses for users, especially if they are interacting with the contract frequently or in large amounts.

PoC

The PoC simulates an account initiating a transaction and then being blacklisted before the transaction completes.

pragma solidity ^0.8.24;

import {Test} from "forge-std/Test.sol";
import "./Stablecoin.sol";

contract BlacklistRaceConditionTest is Test {
    Stablecoin stablecoin;
    address sender;
    address receiver;

    function setUp() public {
        stablecoin = new Stablecoin();
        stablecoin.initialize("TestStablecoin", "TSC", 18);
        
        sender = address(0xSender);
        receiver = address(0xReceiver);
        
        // Mint tokens to sender and set roles
        stablecoin.mintTo(sender, 1000 ether);
        stablecoin.grantRole(stablecoin.SUPPORT_ROLE(), address(this));
    }

    function testRaceCondition() public {
        vm.startPrank(sender);

        // Initiate a transfer transaction
        stablecoin.approve(receiver, 500 ether);

        // Simulate a delay to allow for blacklist status change
        vm.warp(block.timestamp + 1);

        // Blacklist the sender mid-transaction
        vm.stopPrank();
        stablecoin.blacklistUser(sender);

        // Attempt to complete the transfer after blacklist
        vm.startPrank(sender);
        try stablecoin.transfer(receiver, 500 ether) {
            fail("Transaction should revert due to blacklist");
        } catch {
            emit log("Transaction reverted as expected due to blacklist");
        }

        vm.stopPrank();
    }
}

Output:

[info] Running 1 test for BlacklistRaceConditionTest.testRaceCondition
[PASS] BlacklistRaceConditionTest.testRaceCondition

Logs:
- Transaction reverted as expected due to blacklist

Mitigation

Prevent accounts from being blacklisted in the middle of a transaction. Implement a delayed blacklist status update or ensure a transaction completes before blacklist status changes take effect.