Article realeased on Nov 23, 2022
Introduction.
In comparison to Solidity-based chains, Solana takes a different approach to the account mechanism. This different implementation of handling accounts introduces an attack vector that is unfamiliar to solidity developers. In this article, we'll go over how Solana handles and stores account state, as well as the security considerations to keep in mind when developing and auditing Solana-based programs.
Account on the Solana.
Let’s skim over the Account struct of Solana. There are 4 fields consisting Account struct.
pub struct Account {
pub lamports: u64,
#[serde(with = "serde_bytes")]
pub data: Vec<u8>,
pub owner: Pubkey,
pub executable: bool,
pub rent_epoch: Epoch,
}
Rust
The 'lamports' field keeps track of the account's balance, and the 'rent epoch' field gives the next epoch number for which the account owes rent. When the account is a program (smart contract), the 'executable' field is set.
Before delving into the ‘data’ and ‘owner’ fields, keep the following in mind:
•
Every Account should be owned by a program account.
: When you create an account and make a transaction on that account, you are not the owner of that account rather you are the holder of the private key of that account. Each account’s owner is a program account, especially native programs.
System Program in charge of creating a new account and assigns the owned program. The System Program is default owner of account created. System Program owns the account. BPF Loader creates a program-data account and owns created program account.
•
Program accounts are immutable.
: Unlike Ethereum which has seperate storage for code and data, In Solana, program account stores its executable byte code on the account data that means the data field is immutable.
The ‘owner’ field specify which program owns the account. The account owner has ability to change owner of account(if the account's data is zeroed out) and modify data field. That means even though you are the holder of the account, you have to modify the state of account through owning program account.
For example, If you want transfer SOL to other account, you need to sign your instructions with your private key and send it to the System Program. After pass the verification of the request by the System Program, it would deduct your account’s lamports field and credit to receiving account.
The ‘data’ field contains raw byte array of account. As the program account’s data field cannot be modified, Instead of storing data inside the program account, storage account whose owner is the program account is used.
Program Derived Addresses (PDA)
To use a storage account, the program needs to create an account whose owner is the program account, and the program account must also be capable of signing addresses programmatically. The Program Derived Address(PDA) allows programs to programmatically sign for specific addresses without requiring a private key.
The PDA is 32 bytes address that doesn’t have a corresponding private key and only the owning program account can sign. PDAs can be found using a seed value and program id. The return value is PDA and bump value. The bump value(0~255) assures that the PDA is not on the elliptic curve.
The example of finding a PDA and Creating a PDA account is as follows.
...
// Get account informations authority_account is set to signer
...
// Find PDA
let (pda_address, pda_bump) = Pubkey::find_program_address(
&[b"SEED".as_ref(), authority_account.key.as_ref()],
program_id
);
...
// validate found PDA
...
// Create account whose address is PDA
let create_pda_account = &system_instruction::create_account(
authority_account.key,
blog_pda,
rent_lamports,
Data::LEN.try_into().unwrap(),
program_id
);
Rust
The authority account is the signer of the transaction and is used for seed value in the example. The PDA account can be validated by using pda_bump and authority account.
Security Considerations.
The structure of Transaction is as follow image. Unlike an Ethereum transaction, Solana can include multiple signatures and accounts that can be used on the program account. In addition, multiple instructions can be specify each program ID and parameters. As the structure implies, failure to validate user inputs (signatures, accounts, commands) may result in serious security vulnerabilities.
•
Missing ownership check
: Be Assured that the interacting account is owned by a valid account. Cross-program invocation with an untrusted program account can lead to unexpected behavior of implementation such as miscalculation of balance. For example, if you need to interact with a token account, you need to check the owner of the account is SPL token account.
if token_accounts.owner != &spl_token::ID {
return Err(ProgramError::InvalidAccountData);
}
Rust
•
Missing signer check
: For modifying logic user’s account, signature validation is required because only owner of the account can sign for the account. For example, SPL token has authority field that specify authorized account’s public key. Verifying signer and authority value is crucial security requirement.
if token_account.authority.key != &token.owner {
return Err(ProgramError::InvalidAccountData);
}
Rust
•
Bump and seed check
: Program contract can derive multiple PDAs using different seeds, also, multiple PDAs can be generated using different bumps. Validate seed phrase and PDA using valid bump before interact with PDA to prevent arbitrary program account invocation.
if address != ctx.accounts.data.key() {
return Err(ProgramError::InvalidArgument);
}
if expected_bump != bump {
return Err(ProgramError::InvalidArgument);
}
Rust
Related Security Incidences.
•
Wormhole Bridge
: The verify_signatures function has no account verification check. The attacker had provided a malicious program account that implement a fake Secp256k1 contract. Consequently, the attacker had triggered an unauthorized mint.
...
let current_instruction = solana_program::sysvar::instructions::load_current_index(
&accs.instruction_acc.try_borrow_mut_data()?,
);
...
let secp_ix = solana_program::sysvar::instructions::load_instruction_at(
secp_ix_index as usize,
&accs.instruction_acc.try_borrow_mut_data()?,
)
Rust
•
Cashio
: An omission of validation of ‘bank’ PDA account on the Validate function allowed the attacker to mint arbitrary amount of CASH token.
impl<'info> Validate<'info> for BrrrCommon<'info> {
fn validate(&self) -> Result<()> {
assert_keys_eq!(self.bank, self.collateral.bank);
assert_keys_eq!(self.bank.crate_mint, self.crate_mint); //! Omitted
assert_keys_eq!(self.crate_token, self.crate_collateral_tokens.owner);
assert_keys_eq!(self.crate_mint, self.crate_token.mint);
assert_keys_eq!(self.crate_collateral_tokens.mint, self.collateral.mint);
Rust
•
Crema finance
: The attacker constructed a false tick account and claimed outstanding LP fees, which were altered by the fake tick account; as a result, the attacker drained about 8.8 million tokens from the system.
export interface Tick {
tick: number;
tickPrice: Decimal;
liquidityGross: Decimal;
liquidityNet: Decimal;
feeGrowthOutside0: Decimal;
feeGrowthOutside1: Decimal;
}
export interface TickAccount {
swapVersion: number;
tokenSwapKey: PublicKey;
accountTupe: number;
len: number;
ticks: Tick[];
}
Rust
Conclusion.
In this post, we discussed the security concerns involved with the implementation of Solana's Account. As input validation is a necessity of all security measures, account validation is a critical consideration of the Solana program. There are more considerations for securing the Solana program, such as Overflow, Deleted account check, and Oracle validation. Contact us! We are here to assist and protect the Solana community.
About KALOS
KALOS is a flagship service of HAECHI LABS, the leader of the global blockchain industry. We bring together the best Web2 and Web3 experts. Security Researchers with expertise in cryptography, leaders of the global best hacker team, and blockchain/smart contract experts are responsible for securing your Web3 service.
We have secured over $60b worth of crypto assets across 400+ global crypto projects — L1/L2 projects, defi protocols, P2E games, and bridges — notably 1inch, SushiSwap, Badger DAO, SuperRare, Klaytn and Chainsafe.
KALOS is the only blockchain technology company selected for the Samsung Electronics Startup Incubation Program in recognition of our expertise. We have also received technology grants from the Ethereum Foundation and Ethereum Community Fund.
Secure your smart contracts with KALOS.
•
Email: audit@kalos.xyz
•
Official website: https://kalos.xyz
•
Twitter: https://twitter.com/kalos_security