diff --git a/programs/stake/src/stake_state.rs b/programs/stake/src/stake_state.rs index fa5116fc1f..4c633ad941 100644 --- a/programs/stake/src/stake_state.rs +++ b/programs/stake/src/stake_state.rs @@ -1053,9 +1053,14 @@ impl<'a> StakeAccount for KeyedAccount<'a> { stake_history: &StakeHistory, signers: &HashSet, ) -> Result<(), InstructionError> { + // Ensure source isn't spoofed if source_account.owner()? != id() { return Err(InstructionError::IncorrectProgramId); } + // Close the self-reference loophole + if source_account.unsigned_key() == self.unsigned_key() { + return Err(InstructionError::InvalidArgument); + } let stake_merge_kind = MergeKind::get_if_mergeable(self, clock, stake_history)?; let meta = stake_merge_kind.meta(); @@ -4631,6 +4636,48 @@ mod tests { } } + #[test] + fn test_merge_self_fails() { + let stake_address = Pubkey::new_unique(); + let authority_pubkey = Pubkey::new_unique(); + let signers = HashSet::from_iter(vec![authority_pubkey]); + let rent = Rent::default(); + let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::()); + let stake_amount = 4242424242; + let stake_lamports = rent_exempt_reserve + stake_amount; + + let meta = Meta { + rent_exempt_reserve, + ..Meta::auto(&authority_pubkey) + }; + let stake = Stake { + delegation: Delegation { + stake: stake_amount, + activation_epoch: 0, + ..Delegation::default() + }, + ..Stake::default() + }; + let stake_account = Account::new_ref_data_with_space( + stake_lamports, + &StakeState::Stake(meta, stake), + std::mem::size_of::(), + &id(), + ) + .expect("stake_account"); + let stake_keyed_account = KeyedAccount::new(&stake_address, true, &stake_account); + + assert_eq!( + stake_keyed_account.merge( + &stake_keyed_account, + &Clock::default(), + &StakeHistory::default(), + &signers, + ), + Err(InstructionError::InvalidArgument), + ); + } + #[test] fn test_merge_incorrect_authorized_staker() { let stake_pubkey = solana_sdk::pubkey::new_rand();