code-423n4 / 2023-03-wenwin-findings

1 stars 1 forks source link

SWC-101 Integer Overflow Lottery.sol buyTickets() testBuyInvalidTicket() #37

Closed code423n4 closed 1 year ago

code423n4 commented 1 year ago

Lines of code

https://github.com/code-423n4/2023-03-wenwin/blob/91b89482aaedf8b8feb73c771d11c257eed997e8/src/Lottery.sol#L110-L131

Vulnerability details

Impact

# Using Foundry:

Arithmetic overflow has caused the owner account to reduce to 0 and the attack user account to increase by the balance transferred from the owner.
# Vulnerable Code> Lottery.sol:

    function buyTickets(
        uint128[] calldata drawIds,
        uint120[] calldata tickets,
        address frontend,
        address referrer
    )
        external
        override
        requireJackpotInitialized
        returns (uint256[] memory ticketIds)
    {
        if (drawIds.length != tickets.length) {
            revert DrawsAndTicketsLenMismatch(drawIds.length, tickets.length);
        }
        ticketIds = new uint256[](tickets.length);
        for (uint256 i = 0; i < drawIds.length; ++i) {
            ticketIds[i] = registerTicket(drawIds[i], tickets[i], frontend, referrer);
        }
        referralRegisterTickets(currentDraw, referrer, msg.sender, tickets.length);
        frontendDueTicketSales[frontend] += tickets.length;
        rewardToken.safeTransferFrom(msg.sender, address(this), ticketPrice * tickets.length);
    }

Proof of Concept


# Command Line:
forge test -vvv  --match-path "test/Lottery.t.sol" --match-test "testBuyInvalidTicket"

# Foundry> Payload> Lottery.t.sol:
function testBuyInvalidTicket() public {
        uint128 currentDraw = lottery.currentDraw();

        vm.startPrank(USER);
        rewardToken.mint(TICKET_PRICE+1);
        rewardToken.approve(address(lottery), TICKET_PRICE);

        vm.expectRevert(InvalidTicket.selector);
        buyTicket(currentDraw, uint120(0x0E)+1, address(0));

        // Cannot buy 10000000111
        vm.expectRevert(InvalidTicket.selector);
        buyTicket(currentDraw, uint120(0x407)+1, address(0));

        // Can buy 1000000111
        buyTicket(currentDraw, uint120(0x207)+1, address(0));
    }
# Log:
username@name-MacBook-Pro 2023-03-wenwin % forge test -vvv  --match-path "test/Lottery.t.sol" --match-test "testBuyInvalidTicket"
[⠢] Compiling...
No files changed, compilation skipped

Running 1 test for test/Lottery.t.sol:LotteryTest
[FAIL. Reason: Call did not revert as expected] testBuyInvalidTicket() (gas: 274887)
Traces:
  [274887] LotteryTest::testBuyInvalidTicket() 
    ├─ [2504] Lottery::currentDraw() [staticcall]
    │   └─ ← 0
    ├─ [0] VM::startPrank(0x000000000000000000000000000000000000007B) 
    │   └─ ← ()
    ├─ [29444] TestToken::mint(5000000000000000001) 
    │   ├─ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: 0x000000000000000000000000000000000000007B, value: 5000000000000000001)
    │   └─ ← ()
    ├─ [24628] TestToken::approve(Lottery: [0x2e234DAe75C793f67A35089C9d99245E1C58470b], 5000000000000000000) 
    │   ├─ emit Approval(owner: 0x000000000000000000000000000000000000007B, spender: Lottery: [0x2e234DAe75C793f67A35089C9d99245E1C58470b], value: 5000000000000000000)
    │   └─ ← true
    ├─ [0] VM::expectRevert(InvalidTicket()) 
    │   └─ ← ()
    ├─ [182634] Lottery::buyTickets([0], [15], 0x00000000000000000000000000000000000001bc, 0x0000000000000000000000000000000000000000) 
    │   ├─ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: 0x000000000000000000000000000000000000007B, tokenId: 0)
    │   ├─ emit NewTicket(currentDraw: 0, ticketId: 0, drawId: 0, user: 0x000000000000000000000000000000000000007B, combination: 15, frontend: 0x00000000000000000000000000000000000001bc, referrer: 0x0000000000000000000000000000000000000000)
    │   ├─ [8498] TestToken::transferFrom(0x000000000000000000000000000000000000007B, Lottery: [0x2e234DAe75C793f67A35089C9d99245E1C58470b], 5000000000000000000) 
    │   │   ├─ emit Approval(owner: 0x000000000000000000000000000000000000007B, spender: Lottery: [0x2e234DAe75C793f67A35089C9d99245E1C58470b], value: 0)
    │   │   ├─ emit Transfer(from: 0x000000000000000000000000000000000000007B, to: Lottery: [0x2e234DAe75C793f67A35089C9d99245E1C58470b], value: 5000000000000000000)
    │   │   └─ ← true
    │   └─ ← [0]
    └─ ← "Call did not revert as expected"

Test result: FAILED. 0 passed; 1 failed; finished in 5.03ms

Failing tests:
Encountered 1 failing test in test/Lottery.t.sol:LotteryTest
[FAIL. Reason: Call did not revert as expected] testBuyInvalidTicket() (gas: 274887)

Encountered a total of 1 failing tests, 0 tests succeeded

Tools Used

Foundry + VS Code

Recommended Mitigation Steps

It is recommended to use vetted safe math libraries for arithmetic operations consistently throughout the smart contract system.
c4-judge commented 1 year ago

thereksfour marked the issue as unsatisfactory: Invalid