4

Let's say that I have a program printing receipts for an action in order to either enable further actions or restrict actions depending on the existence of the PDA address.

The question is, could someone else create the account if they know the seeds and assign data to it? Maybe even set the owner value to the program?

3 Answers 3

3

Leaving info here for anyone else that might be wondering about this topic.

tldr: it would be quite hard to create an account in the space of another program's PDA space without using that program to do it.


Some quick notes on PDAs:

  1. Program derived address allow programs to control specific addresses, called program addresses, in such a way that no external user can generate valid transactions with signatures for those addresses.

  2. Program derived address allow programs to programmatically sign for program addresses that are present in instructions invoked via Cross-Program Invocations.


Assuming that the goal is to create an account at a known PDA for another program, and that we know the seeds for that address, we still would not be able to create an account at that location.

Solana currently provides 2 ways to create an account through the SystemProgram:

  1. Using the SystemProgram::CreateAccount instruction
  2. Using the SystemProgram::CreateAccountWithSeed instruction

CreateAccount

Let's look at the first of these, the CreateAccount instruction.

/// Create a new account
///
/// # Account references
///   0. `[WRITE, SIGNER]` Funding account
///   1. `[WRITE, >>>> SIGNER <<<<]` New account
CreateAccount {
    /// Number of lamports to transfer to the new account
    lamports: u64,

    /// Number of bytes of memory to allocate
    space: u64,

    /// Address of program that will own the new account
    owner: Pubkey,
},

It requires a signature from the account being created. So either you'd need a private key or you'd need to be able to sign for it using invoke_signed in an on-chain program.

However, a PDA address won't have a private key; it is bumped off the curve so the only option is to use invoke_signed.

The issue is that you'll get a "signer privilege escalated" error if you try to sign for an account that doesn't belong to the calling program.

For example, the following will not work:

// Try to create an account using a PDA address for another program

let other_program = Pubkey::try_from("base58_key_here").unwrap();
let (__pda_address, __bump) =
// Works, but you get a totally different PDA than would be generated
// using "other_program"

    //Pubkey::find_program_address(
    //    &[b"my_seeds_go_here"],
    //    &id().key()); 

// Does not work, results in "signer privilege escalated" error
    Pubkey::find_program_address(
        &[b"my_seeds_go_here"],
        &other_program);

Then call invoke_signed, as follows:

// Example 1: ask system program to create a new account
invoke_signed(
    &create_account(
        &ctx.accounts.payer.key(),
        &ctx.accounts.a.key(),
        3000000,
        165,
        &ctx.accounts.token_program.key(),
    ),
    &[
        ctx.accounts.payer.to_account_info().clone(),
        ctx.accounts.a.to_account_info().clone(),
    ],
    &[
        &[b"my_seeds_go_here", &[__bump][..]][..],
    ]
)?;

Or, with Anchor

    // Example 2: ask system program to create a new account
    let cpi_accounts = anchor_lang::system_program::CreateAccount {
        from: ctx.accounts.payer.to_account_info(),
        to: ctx.accounts.a.to_account_info(),
    };
    let cpi_context = anchor_lang::context::CpiContext::new(
        ctx.accounts.system_program.to_account_info(),
        cpi_accounts,
    );
    anchor_lang::system_program::create_account(
        cpi_context.with_signer(
            &[&[b"my_seeds_go_here", &[__bump][..]][..]]),
        lamports,
        anchor_spl::token::TokenAccount::LEN as u64,
        &ctx.accounts.token_program.key(),
    )?;

Once again, both these will result in signer privilege escalated


CreateAccountWithSeed

Similarly, the CreateAccountWithSeed instruction provides a method to create a new account.

Note that it does not require the direct signature of the account being created, but it does require a signature to a base account.

/// Create a new account at an address derived from a base pubkey and a seed
///
/// # Account references
///   0. `[WRITE, SIGNER]` Funding account
///   1. `[WRITE]` Created account
///   2. `[SIGNER]` (optional) Base account; the account matching the base Pubkey below must be
///                          provided as a signer, but may be the same as the funding account
///                          and provided as account 0
CreateAccountWithSeed {
    /// Base public key
    base: Pubkey,

    /// String of ASCII chars, no longer than `Pubkey::MAX_SEED_LEN`
    seed: String,

    /// Number of lamports to transfer to the new account
    lamports: u64,

    /// Number of bytes of memory to allocate
    space: u64,

    /// Owner program account address
    owner: Pubkey,
},

In order to use this as an attack vector, you'd need to find a base account keypair + seeds that map to a PDA.

However, note that PDA's are specifically off the curve.

1

Program-Derived-Addresses (PDAs) are of a special type of data account because they don't have a private key associated.

Only programs can create a PDA and only the program that the PDA was derived from can sign the PDA. The program the PDA was derived from can change the data of the PDA. You can derive the PDA public key outside of the program but you will never be able to change the data outside the program.

1

Who can create a PDA is up to the program under which PDA address space the account is to be created.

Typically user PDAs will require the intended owner to sign the account creation transaction, then the owner's pubkey is one of the seeds, preventing the addresses from being "griefed". Similarly admin/config accounts might have some hard coded authority who must sign to create them.

Another way to go about it is for PDAs which will be assigned an authority during initialization, require that the seed and assigned authority match, so worst case the "attacker" creates an account in the legitimate owner's stead. This is how the SPL Token ATA program allows for the sender to safely create a token account for the recipient

4
  • Thanks! My question was primarily around whether it would be possible for someone to fake an account by creating it elsewhere and then assigning ownership to the program (all while respecting the PDA path). After a deep dive on this, it seems quite unlikely. You'd need a base account + seeds that collide with the PDA somehow.
    – zfedoran
    Jul 26, 2022 at 22:31
  • Posted my deep dive on this question as a separate answer; I'd really appreciate a review to ensure my assumptions are correct. Thanks!
    – zfedoran
    Jul 26, 2022 at 23:01
  • 1
    Assuming "elsewhere" means using another program's address space, since PDAs don't have private keys to try to obtain, this would require a successful second preimage attack on SHA256
    – trent.sol
    Jul 26, 2022 at 23:09
  • Thanks Trent! I suspected as much when asking. Just wasn't getting the specific "who can create the account" answer from the cookbook or docs solanacookbook.com/core-concepts/pdas.html#facts
    – zfedoran
    Jul 26, 2022 at 23:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.