Trident 介绍:Solana 程序的首个开源模糊测试器

  • Ackee
  • 发布于 2024-05-21 22:38
  • 阅读 23

Trident 是一个基于 Rust 的框架,旨在帮助开发者模糊测试用 Anchor 编写的 Solana 程序。Trident 简化了测试过程,通过发现边界情况漏洞帮助开发者发布安全代码。它通过自动生成测试模板、自适应输入、引导式指令序列和不变量检查等功能提高了测试效率。

Trident 是一个基于 Rust 的框架,旨在帮助开发者进行模糊测试 Solana 程序,这些程序使用 Anchor 编写。Trident 由 Ackee Blockchain 开发并由 Solana 基金会支持,它简化了测试过程,并通过发现边缘案例漏洞帮助开发者交付安全的代码。

Trident 模糊测试视频教程

我们为 School of Solana 准备了一节额外的课程,供开发者学习如何使用 Trident 对 Solana 程序 进行模糊测试。

使用 Trident 进行 Solana 程序的模糊测试 - YouTube

Ackee Blockchain Security 的照片

Ackee Blockchain Security

3.06K 订阅者

使用 Trident 进行 Solana 程序的模糊测试

Ackee Blockchain Security

搜索

稍后观看

分享

复制链接

信息

购物

点按以取消静音

如短时间内未开始播放,请尝试重新启动设备。

更多视频

更多视频

你已退出

你观看的视频可能会被添加到电视的观看历史记录中,并影响电视推荐内容。要避免此情况,请在电脑上取消并登录 YouTube。

取消确认

分享

包含播放列表

检索分享信息时出错。 请稍后重试。

在以下位置观看

0:00

0:00 / 1:12:38 •直播的

Trident 很强大

Trident 的开发始于 2.5 年前的 2021 年,最初的名称是 Trdelník。Trident 在 2022 年的 Solana Riptide Hackathon 期间赢得了 Marinade Finance 社区奖,并在 2023 年获得了 Solana 基金会的开发资助。

特性

  1. 自动模糊测试生成: 简化了 Anchor 程序的测试模板创建,减少了设置时间和精力。
  2. 自适应输入: 生成动态和自适应输入,以提高测试覆盖率并发现隐藏的漏洞。
  3. 引导式指令序列: 使用可定制的指令序列,以实现更快、更有效的测试结果。
  4. 不变性检查: 允许开发者实现自定义检查,以发现漏洞和不需要的行为。

开始使用

安装

Trident 通过 Rust 的 cargo 包管理器分发。要安装 Trident 及其依赖项,请按照以下步骤操作:

  1. 安装 Trident CLI 和 honggfuzz:
cargo install trident-cli
cargo install honggfuzz
  1. 在你的项目中初始化 Trident:
trident init

此命令在你的项目中设置 Trident,生成必要的文件和配置。

模糊测试 Solana 程序

模糊测试是一种自动化技术,它向你的程序提供生成的随机、无效或意外的输入数据。这有助于发现未知的错误和漏洞,从而可能防止零日漏洞利用。Trident 集成了由 Google 开发的著名模糊器 honggfuzz,以方便 Solana 程序 的模糊测试。

示例:设置一个新的 Anchor 项目

  1. 初始化项目:
anchor init my-trident-fuzz-test
cd my-trident-fuzz-test
anchor build
  1. 创建一个有缺陷的程序:

创建一个名为 unchecked_arithmetic_0 的程序,其中包含用于测试的故意错误。

use anchor_lang::prelude::*;

const MAGIC_NUMBER: u8 = 254;

declare_id!("...."); // 在此处粘贴你的程序 ID

#[program]
pub mod unchecked_arithmetic_0 {
    use super::*;

    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
        let counter = &mut ctx.accounts.counter;

        counter.count = 0;
        counter.authority = ctx.accounts.user.key();

        Ok(())
    }

    pub fn update(ctx: Context<Update>, input1: u8, input2: u8) -> Result<()> {
        let counter = &mut ctx.accounts.counter;

        msg!("input1 = {}, input2 = {}", input1, input2);

        counter.count = buggy_math_function(input1, input2).into();
        Ok(())
    }
}
pub fn buggy_math_function(input1: u8, input2: u8) -> u8 {
    // INFO 取消注释 if 语句可以防止
    // 除零和减法溢出 panic
    // if input2 >= MAGIC_NUMBER {
    //  return 0;
    // }
    let divisor = MAGIC_NUMBER - input2;
    input1 / divisor
}

#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(init, payer = user, space = 8 + 40)]
    pub counter: Account<'info, Counter>,

    #[account(mut)]
    pub user: Signer<'info>,

    pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct Update<'info> {
    #[account(mut, has_one = authority)]
    pub counter: Account<'info, Counter>,
    pub authority: Signer<'info>,
}

#[account]
pub struct Counter {
    pub authority: Pubkey,
    pub count: u64,
}
  1. 初始化 Trident:
trident init
  1. 编写一个模糊测试:

修改位于 'trident-tests/fuzz_tests/fuzz_0/fuzz_instructions.rs' 的模糊测试模板,并完成 get_dataget_accounts 方法以及 FuzzAccounts 结构体的实现:

pub mod unchecked_arithmetic_0_fuzz_instructions {
    use crate::accounts_snapshots::*;
    use trident_client::{fuzzing::*, solana_sdk::native_token::LAMPORTS_PER_SOL};
    #[derive(Arbitrary, DisplayIx, FuzzTestExecutor, FuzzDeserialize)]
    pub enum FuzzInstruction {
        Initialize(Initialize),
        Update(Update),
    }
    #[derive(Arbitrary, Debug)]
    pub struct Initialize {
        pub accounts: InitializeAccounts,
        pub data: InitializeData,
    }
    #[derive(Arbitrary, Debug)]
    pub struct InitializeAccounts {
        pub counter: AccountId,
        pub user: AccountId,
        pub system_program: AccountId,
    }
    #[derive(Arbitrary, Debug)]
    pub struct InitializeData {}
    #[derive(Arbitrary, Debug)]
    pub struct Update {
        pub accounts: UpdateAccounts,
        pub data: UpdateData,
    }
    #[derive(Arbitrary, Debug)]
    pub struct UpdateAccounts {
        pub counter: AccountId,
        pub authority: AccountId,
    }
    #[derive(Arbitrary, Debug)]
    pub struct UpdateData {
        pub input1: u8,
        pub input2: u8,
    }
    impl<'info> IxOps<'info> for Initialize {
        type IxData = unchecked_arithmetic_0::instruction::Initialize;
        type IxAccounts = FuzzAccounts;
        type IxSnapshot = InitializeSnapshot<'info>;
        fn get_data(
            &self,
            _client: &mut impl FuzzClient,
            _fuzz_accounts: &mut FuzzAccounts,
        ) -> Result<Self::IxData, FuzzingError> {
            let data = unchecked_arithmetic_0::instruction::Initialize {};
            Ok(data)
        }
        fn get_accounts(
            &self,
            client: &mut impl FuzzClient,
            fuzz_accounts: &mut FuzzAccounts,
        ) -> Result<(Vec<Keypair>, Vec<AccountMeta>), FuzzingError> {
            let user = fuzz_accounts.user.get_or_create_account(
                self.accounts.user,
                client,
                5 * LAMPORTS_PER_SOL,
            );
            let counter = fuzz_accounts.counter.get_or_create_account(
                self.accounts.counter,
                client,
                5 * LAMPORTS_PER_SOL,
            );

            let acc_meta = unchecked_arithmetic_0::accounts::Initialize {
                counter: counter.pubkey(),
                user: user.pubkey(),
                system_program: SYSTEM_PROGRAM_ID,
            }
            .to_account_metas(None);
            Ok((vec![user, counter], acc_meta))
        }
    }
    impl<'info> IxOps<'info> for Update {
        type IxData = unchecked_arithmetic_0::instruction::Update;
        type IxAccounts = FuzzAccounts;
        type IxSnapshot = UpdateSnapshot<'info>;
        fn get_data(
            &self,
            _client: &mut impl FuzzClient,
            _fuzz_accounts: &mut FuzzAccounts,
        ) -> Result<Self::IxData, FuzzingError> {
            let data = unchecked_arithmetic_0::instruction::Update {
                input1: self.data.input1,
                input2: self.data.input2,
            };
            Ok(data)
        }
        fn get_accounts(
            &self,
            client: &mut impl FuzzClient,
            fuzz_accounts: &mut FuzzAccounts,
        ) -> Result<(Vec<Keypair>, Vec<AccountMeta>), FuzzingError> {
            let user = fuzz_accounts.user.get_or_create_account(
                self.accounts.authority,
                client,
                15 * LAMPORTS_PER_SOL,
            );
            let counter = fuzz_accounts.counter.get_or_create_account(
                self.accounts.counter,
                client,
                5 * LAMPORTS_PER_SOL,
            );

            let acc_meta = unchecked_arithmetic_0::accounts::Update {
                counter: counter.pubkey(),
                authority: user.pubkey(),
            }
            .to_account_metas(None);
            Ok((vec![user], acc_meta))
        }
    }
    #[doc = r" Use AccountsStorage<T> where T can be one of:"]
    #[doc = r" Keypair, PdaStore, TokenStore, MintStore, ProgramStore"]
    #[derive(Default)]
    pub struct FuzzAccounts {
        // The 'authority' and 'system_program' were automatically
        // generated in the FuzzAccounts struct, as they are both
        // used in the program. However, the 'authority' is in fact
        // the user account, just named differently. Therefore, we will use only
        // the generated user accounts for both 'user' and 'authority account' fields
        // in this fuzz test. Additionally, there is no need to fuzz the 'system_program' account.
        user: AccountsStorage<Keypair>,
        counter: AccountsStorage<Keypair>,
    }
}

修改位于 'trident-tests/fuzz_tests/fuzz_0/test_fuzz.rs' 的模糊测试模板:

use fuzz_instructions::unchecked_arithmetic_0_fuzz_instructions::FuzzInstruction;
use fuzz_instructions::unchecked_arithmetic_0_fuzz_instructions::Initialize;
use trident_client::{convert_entry, fuzz_trident, fuzzing::*};
use unchecked_arithmetic_0::entry;
use unchecked_arithmetic_0::ID as PROGRAM_ID;
mod accounts_snapshots;
mod fuzz_instructions;

const PROGRAM_NAME: &str = "unchecked_arithmetic_0";

struct MyFuzzData;

impl FuzzDataBuilder<FuzzInstruction> for MyFuzzData {
    fn pre_ixs(u: &mut arbitrary::Unstructured) -> arbitrary::Result<Vec<FuzzInstruction>> {
        let init = FuzzInstruction::Initialize(Initialize::arbitrary(u)?);
        Ok(vec![init])
    }
}

fn main() {
    loop {
        fuzz_trident!(fuzz_ix: FuzzInstruction, |fuzz_data: MyFuzzData| {
            let mut client =
                ProgramTestClientBlocking::new(PROGRAM_NAME, PROGRAM_ID, processor!(convert_entry!(entry)))
                    .unwrap();
            let _ = fuzz_data.run_with_runtime(PROGRAM_ID, &mut client);
        });
    }
}
  1. 运行模糊测试:
trident fuzz run fuzz_0
  1. 使用崩溃文件进行调试:使用崩溃文件来调试和检查问题:
trident fuzz run-debug fuzz_0
trident-tests/fuzz_tests/fuzzing/hfuzz_workspace/fuzz_0/<CRASH_FILE>.fuzz

集成测试

Trident 还支持集成测试,允许开发者在模拟环境中测试他们的 Solana 程序,该环境与实际的 Solana 区块链非常相似。这确保了程序与区块链和其他程序正确交互。

Trident 开发者支持

如需开发者支持,请在我们的 Discord #trident-chatwarpcast /trident 频道 中找到我们。请阅读文档,为我们的 GitHub 仓库 加星,并在 Twitter/X 上关注 Trident @TridentSolana 以获取更新。

  • 原文链接: ackee.xyz/blog/introduci...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Ackee
Ackee
Cybersecurity experts | We audit Ethereum and Solana | Creators of @WakeFramework , Solidity (Wake) & @TridentSolana | Educational partner of Solana Foundation