Security of Solana Smart Contracts: why you should always validate PDA Bump Seeds
July 6, 2022

PDAs and Bumps

Program derived addresses (PDAs) are used in virtually all Solana smart contracts. While excellent resources (e.g., the Solana Cookbook) exist for understanding PDAs, there is one important caveat deserving special attention:

PDAs of a Solana program can have the same seeds with multiple valid bumps, giving multiple different addresses (quote from tweet by @armaniferrante)

This has a crucial security implication: PDAs can be faked (by providing different bump seeds) if their bump seeds are not validated.

In other words, please note the following:

  • PDA bump seeds are not unique; A seed can have multiple bumps.
  • The same seed and different valid bumps can generate different valid PDAs.
  • When using a PDA you should always validate its bump seed.

Here is an example:

Example: same seeds, same program_id, three different bumps and PDAs

The output:

Why PDA bump seeds are not unique?

Let’s first understand how PDAs are computed. The source code of Pubkey::find_program_address is shown below:

see find_program_address source code in Solana Github repo

It takes two user-supplied inputs:

  • seeds
  • program_id

and it returns

  • a PDA (of type Pubkey)
  • a corresponding bump seed (of type u8)

Specifically, it calls try_find_program_address, in which bump_seed is introduced:

Note: for target_os = “solana” a system call is used instead to compute try_find_program_address

In the above, line 479, bump_seed is a u8 variable with a value ranging between 0 to 255 (std::u8::MAX).

On lines 480–491, in a loop from 255 to 0, bump_seed is appended to the input seeds to call Pubkey::create_program_address(line 484):

Pubkey::create_program_address either returns a valid PDA or an error

In create_program_address, it basically performs a set of hash operations over the input seeds and program_id to compute a key (line 579 below):

It then verifies if the computed key is a valid PDA or not, i.e., by checking if the key lies on the ed25519 elliptic curve or not (line 581bytes_are_curve_point)

If the computed key lies on the ed25519 elliptic curve, then it is a valid public key, thus not a valid PDA and an error will be returned (line 582).

(Assuming inputs are random) There is a ~50% chance that the computed key lies on the curve, so create_program_address will return error and the loop in try_find_program_address will continue with a new bump_seed.

When create_program_address returns a valid PDA, the corresponding bump_seed in try_find_program_address will also be returned.

Now, it should be clear that given the same seeds and program_id:

  • Pubkey::create_program_address can return different PDAs each with a different bump seed
  • Pubkey::find_program_address returns the PDA with the largest valid bump seed

How attackers may exploit unvalidated PDAs?

An attacker may create a fake PDA with the same seeds and program_id but a different bump seed from the intended PDA.

Consider an example below:

A sample vulnerable PDA

The deposit_account is a PDA that may be faked because its bump seed could be arbitrary, i.e., validated with an external data (bump).

If the faked PDA can pass an instruction’s internal checks, then it could be used to camouflage the intended PDA, e.g., transfer funds based on the fake PDA’s state rather than the intended state.

How does Sec3 Pro detect the vulnerability?

Sec3 Pro is the premier security analysis service for Solana smart contracts.

To detect such unvalidated PDA vulnerabilities:

The tool first locates all the user-supplied PDAs in the smart smart including those declared with Anchor macros.

The tool then verifies if the bump seed of every PDA is properly validated. E.g., with an equality constraint relating the bump seed to a bump returned by Pubkey::find_program_address.

If any PDA bump seed is not validated or it can be controlled by an external input, then the tool flags a potential vulnerability.

The following shows a screenshot of BumpSeedNotValidated issue reported by Sec3 Pro in the previous sample code:

Sec3 Pro is available at https://pro.sec3.dev, including a free plan for the Solana ecosystem.

How to validate PDA bump seeds?

There are two common ways to validate PDA bump seeds:

  1. use Pubkey::find_program_address and check the result against the given bump seed. Abort if it doesn’t match.
  2. store the valid bump in an account, and use Pubkey::create_program_addresswith the stored bump
Approach 1. validate bump seed returned from find_program_address
Approach 2. validate bump seed using create_program_address

For Approach 2, in Anchor, use bump=<stored_valid_bump>. For example, bump=multisig.nonce where nonce is the valid bump stored in the multisigaccount when the account is created:

Multisig sample source code

About Sec3 (formerly Soteria)

Sec3 is founded by leading minds in the fields of blockchain security and software verification. Sec3’s mission is to create a decentralized future that is secure. Sec3 team is currently building a trustworthy platform for securing Solana projects.