Anchor 中的 Init if needed 与重初始化攻击

  • RareSkills
  • 发布于 2024-04-23 14:56
  • 阅读 168

本篇文章详细介绍了Anchor框架的init_if_needed宏,提供了一种在一次事务中初始化账户并写入数据的方法。文中阐述了该宏的便利性与可能引发的重初始化攻击风险,特别是在账户状态和lamport余额的处理上。同时,通过示例代码和测试用例,深入分析了如何安全地使用这些功能,以避免潜在的错误和安全隐患。

英雄图片显示 Anchor init_if_needed

在之前的教程中,我们必须在单独的交易中初始化一个账户,然后才能对其写入数据。我们可能希望能够在一个交易中初始化一个账户并对其写入数据,以简化用户的操作。

Anchor 提供了一个方便的宏 init_if_needed,顾名思义,如果账户不存在,则会初始化该账户。

下面的示例计数器不需要单独的初始化交易,它会立即开始将“1”添加到 counter 存储中。

Rust:

use anchor_lang::prelude::*;
use std::mem::size_of;

declare_id!("9DbiqCqtqgP3NYufxBakbeRd7JyNpNYbsm6Jqrn8Z2Hn");

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

    pub fn increment(ctx: Context<Initialize>) -> Result<()> {
        let current_counter = ctx.accounts.my_pda.counter;
        ctx.accounts.my_pda.counter = current_counter + 1;
        Ok(())
    }
}

#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(
        init_if_needed,
        payer = signer,
        space = size_of::<MyPDA>() + 8,
        seeds = [],
        bump
    )]
    pub my_pda: Account<'info, MyPDA>,

    #[account(mut)]
    pub signer: Signer<'info>,
    pub system_program: Program<'info, System>,
}

#[account]
pub struct MyPDA {
    pub counter: u64,
}

Typescript:

import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { InitIfNeeded } from "../target/types/init_if_needed";

describe("init_if_needed", () => {
  // 配置客户端以使用本地集群。
  anchor.setProvider(anchor.AnchorProvider.env());

  const program = anchor.workspace.InitIfNeeded as Program<InitIfNeeded>;

  it("已初始化!", async () => {
    const [myPda, _bump] = anchor.web3.PublicKey.findProgramAddressSync([], program.programId);
    await program.methods.increment().accounts({myPda: myPda}).rpc();
    await program.methods.increment().accounts({myPda: myPda}).rpc();
    await program.methods.increment().accounts({myPda: myPda}).rpc();

    let result = await program.account.myPda.fetch(myPda);
    console.log(`counter是 ${result.counter}`);
  });
});

当我们尝试使用 anchor build 构建这个程序时,将会遇到以下错误:

错误: init_if_needed

为了消除错误 init_if_needed requires that anchor-lang be imported with the init-if-needed cargo feature enabled,我们可以打开 programs/<anchor_project_name> 中的 Cargo.toml 文件并添加以下行:

[dependencies]
anchor-lang = { version = "0.29.0", features = ["init-if-needed"] }

但在我们只是移除错误之前,我们应该了解什么是重新初始化攻击及其如何发生。

在 Anchor 程序中,账户不能被重初始化(默认)

如果我们尝试初始化一个已经初始化的账户,交易将会失败。

Anchor 如何知道一个账户已经初始化?

从 Anchor 的角度来看,如果该账户的 lamport 余额为零 或者该账户由系统程序拥有,那么它就是未初始化的。

由系统程序拥有或具有零 lamport 余额的账户可以再次初始化。

为了说明这一点,我们有一个具有典型 initialize 函数的 Solana 程序(它使用的是 init,而不是 init_if_needed)。它还具有 drain_lamports 函数和 give_to_system_program 函数,这两个函数的作用如其名所示:


use anchor_lang::prelude::*;
use std::mem::size_of;
use anchor_lang::system_program;

declare_id!("FC467mPCCKXG97ut1WdLLi55vuAcyCW8AD1vid27bZfn");

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

    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
        Ok(())
    }

    pub fn drain_lamports(ctx: Context<DrainLamports>) -> Result<()> {
        let lamports = ctx.accounts.my_pda.to_account_info().lamports();
        ctx.accounts.my_pda.sub_lamports(lamports)?;
        ctx.accounts.signer.add_lamports(lamports)?;
        Ok(())
    }

    pub fn give_to_system_program(ctx: Context<GiveToSystemProgram>) -> Result<()> {
        let account_info = &mut ctx.accounts.my_pda.to_account_info();
        // assign 方法更改拥有者
        account_info.assign(&system_program::ID);
        account_info.realloc(0, false)?;
        Ok(())
    }
}

#[derive(Accounts)]
pub struct DrainLamports<'info> {
    #[account(mut)]
    pub my_pda: Account<'info, MyPDA>,
    #[account(mut)]
    pub signer: Signer<'info>,
}

#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(init, payer = signer, space = 8, seeds = [], bump)]
    pub my_pda: Account<'info, MyPDA>,

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

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

#[derive(Accounts)]
pub struct GiveToSystemProgram<'info> {
    #[account(mut)]
    pub my_pda: Account<'info, MyPDA>,...

剩余50%的内容订阅专栏后可查看

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
RareSkills
RareSkills
https://www.rareskills.io/