check program owners (#15495)

* check program owners

* BankSlotDelta should change because InstructionError variant added

Co-authored-by: Tyera Eulberg <tyera@solana.com>
This commit is contained in:
sakridge 2021-02-26 14:21:34 -08:00 committed by GitHub
parent d47f1fae40
commit 8399851d11
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 118 additions and 18 deletions

View File

@ -3,7 +3,7 @@
use crate::ConfigKeys; use crate::ConfigKeys;
use bincode::deserialize; use bincode::deserialize;
use solana_sdk::{ use solana_sdk::{
ic_msg, feature_set, ic_msg,
instruction::InstructionError, instruction::InstructionError,
keyed_account::{next_keyed_account, KeyedAccount}, keyed_account::{next_keyed_account, KeyedAccount},
process_instruction::InvokeContext, process_instruction::InvokeContext,
@ -20,8 +20,15 @@ pub fn process_instruction(
let key_list: ConfigKeys = limited_deserialize(data)?; let key_list: ConfigKeys = limited_deserialize(data)?;
let keyed_accounts_iter = &mut keyed_accounts.iter(); let keyed_accounts_iter = &mut keyed_accounts.iter();
let config_keyed_account = &mut next_keyed_account(keyed_accounts_iter)?; let config_keyed_account = &mut next_keyed_account(keyed_accounts_iter)?;
let current_data: ConfigKeys = { let current_data: ConfigKeys = {
let config_account = config_keyed_account.try_account_ref_mut()?; let config_account = config_keyed_account.try_account_ref_mut()?;
if invoke_context.is_feature_active(&feature_set::check_program_owner::id())
&& config_account.owner != crate::id()
{
return Err(InstructionError::InvalidAccountOwner);
}
deserialize(&config_account.data).map_err(|err| { deserialize(&config_account.data).map_err(|err| {
ic_msg!( ic_msg!(
invoke_context, invoke_context,
@ -174,6 +181,7 @@ mod tests {
}; };
let config_account = RefCell::new(Account { let config_account = RefCell::new(Account {
data: vec![0; space as usize], data: vec![0; space as usize],
owner: id(),
..Account::default() ..Account::default()
}); });
let accounts = vec![(&config_pubkey, true, &config_account)]; let accounts = vec![(&config_pubkey, true, &config_account)];
@ -326,7 +334,10 @@ mod tests {
let my_config = MyConfig::new(42); let my_config = MyConfig::new(42);
let instruction = config_instruction::store(&config_pubkey, false, keys, &my_config); let instruction = config_instruction::store(&config_pubkey, false, keys, &my_config);
let signer0_account = RefCell::new(Account::default()); let signer0_account = RefCell::new(Account {
owner: id(),
..Account::default()
});
let accounts = vec![(&signer0_pubkey, true, &signer0_account)]; let accounts = vec![(&signer0_pubkey, true, &signer0_account)];
let keyed_accounts = create_keyed_is_signer_accounts(&accounts); let keyed_accounts = create_keyed_is_signer_accounts(&accounts);
assert_eq!( assert_eq!(
@ -588,4 +599,35 @@ mod tests {
Err(InstructionError::NotEnoughAccountKeys) Err(InstructionError::NotEnoughAccountKeys)
); );
} }
#[test]
fn test_config_bad_owner() {
let from_pubkey = solana_sdk::pubkey::new_rand();
let config_pubkey = solana_sdk::pubkey::new_rand();
let new_config = MyConfig::new(84);
let signer0_pubkey = solana_sdk::pubkey::new_rand();
let signer0_account = RefCell::new(Account::default());
let config_account = RefCell::new(Account::default());
let keys = vec![
(from_pubkey, false),
(signer0_pubkey, true),
(config_pubkey, true),
];
let instruction = config_instruction::store(&config_pubkey, true, keys, &new_config);
let accounts = vec![
(&config_pubkey, true, &config_account),
(&signer0_pubkey, true, &signer0_account),
];
let keyed_accounts = create_keyed_is_signer_accounts(&accounts);
assert_eq!(
process_instruction(
&id(),
&keyed_accounts,
&instruction.data,
&mut MockInvokeContext::default()
),
Err(InstructionError::InvalidAccountOwner)
);
}
} }

View File

@ -496,7 +496,11 @@ pub fn process_instruction(
let me = &next_keyed_account(keyed_accounts)?; let me = &next_keyed_account(keyed_accounts)?;
if me.owner()? != id() { if me.owner()? != id() {
return Err(InstructionError::IncorrectProgramId); if invoke_context.is_feature_active(&feature_set::check_program_owner::id()) {
return Err(InstructionError::InvalidAccountOwner);
} else {
return Err(InstructionError::IncorrectProgramId);
}
} }
match limited_deserialize(data)? { match limited_deserialize(data)? {
@ -802,7 +806,7 @@ mod tests {
&Authorized::default(), &Authorized::default(),
&Lockup::default() &Lockup::default()
)), )),
Err(InstructionError::IncorrectProgramId), Err(InstructionError::InvalidAccountOwner),
); );
assert_eq!( assert_eq!(
process_instruction(&authorize( process_instruction(&authorize(
@ -812,7 +816,7 @@ mod tests {
StakeAuthorize::Staker, StakeAuthorize::Staker,
None, None,
)), )),
Err(InstructionError::IncorrectProgramId), Err(InstructionError::InvalidAccountOwner),
); );
assert_eq!( assert_eq!(
process_instruction( process_instruction(
@ -823,7 +827,7 @@ mod tests {
&Pubkey::default(), &Pubkey::default(),
)[1] )[1]
), ),
Err(InstructionError::IncorrectProgramId), Err(InstructionError::InvalidAccountOwner),
); );
assert_eq!( assert_eq!(
process_instruction( process_instruction(
@ -844,7 +848,7 @@ mod tests {
&Pubkey::default(), &Pubkey::default(),
)[0] )[0]
), ),
Err(InstructionError::IncorrectProgramId), Err(InstructionError::InvalidAccountOwner),
); );
assert_eq!( assert_eq!(
process_instruction( process_instruction(
@ -867,7 +871,7 @@ mod tests {
"seed" "seed"
)[1] )[1]
), ),
Err(InstructionError::IncorrectProgramId), Err(InstructionError::InvalidAccountOwner),
); );
assert_eq!( assert_eq!(
process_instruction(&delegate_stake( process_instruction(&delegate_stake(
@ -875,7 +879,7 @@ mod tests {
&Pubkey::default(), &Pubkey::default(),
&Pubkey::default(), &Pubkey::default(),
)), )),
Err(InstructionError::IncorrectProgramId), Err(InstructionError::InvalidAccountOwner),
); );
assert_eq!( assert_eq!(
process_instruction(&withdraw( process_instruction(&withdraw(
@ -885,14 +889,14 @@ mod tests {
100, 100,
None, None,
)), )),
Err(InstructionError::IncorrectProgramId), Err(InstructionError::InvalidAccountOwner),
); );
assert_eq!( assert_eq!(
process_instruction(&deactivate_stake( process_instruction(&deactivate_stake(
&spoofed_stake_state_pubkey(), &spoofed_stake_state_pubkey(),
&Pubkey::default() &Pubkey::default()
)), )),
Err(InstructionError::IncorrectProgramId), Err(InstructionError::InvalidAccountOwner),
); );
assert_eq!( assert_eq!(
process_instruction(&set_lockup( process_instruction(&set_lockup(
@ -900,7 +904,7 @@ mod tests {
&LockupArgs::default(), &LockupArgs::default(),
&Pubkey::default() &Pubkey::default()
)), )),
Err(InstructionError::IncorrectProgramId), Err(InstructionError::InvalidAccountOwner),
); );
} }

View File

@ -8,6 +8,7 @@ use solana_config_program::date_instruction::DateConfig;
use solana_config_program::get_config_data; use solana_config_program::get_config_data;
use solana_sdk::{ use solana_sdk::{
account::Account, account::Account,
feature_set,
instruction::InstructionError, instruction::InstructionError,
keyed_account::{next_keyed_account, KeyedAccount}, keyed_account::{next_keyed_account, KeyedAccount},
process_instruction::InvokeContext, process_instruction::InvokeContext,
@ -60,10 +61,15 @@ pub fn process_instruction(
_program_id: &Pubkey, _program_id: &Pubkey,
keyed_accounts: &[KeyedAccount], keyed_accounts: &[KeyedAccount],
data: &[u8], data: &[u8],
_invoke_context: &mut dyn InvokeContext, invoke_context: &mut dyn InvokeContext,
) -> Result<(), InstructionError> { ) -> Result<(), InstructionError> {
let keyed_accounts_iter = &mut keyed_accounts.iter(); let keyed_accounts_iter = &mut keyed_accounts.iter();
let contract_account = &mut next_keyed_account(keyed_accounts_iter)?.try_account_ref_mut()?; let contract_account = &mut next_keyed_account(keyed_accounts_iter)?.try_account_ref_mut()?;
if invoke_context.is_feature_active(&feature_set::check_program_owner::id())
&& contract_account.owner != crate::id()
{
return Err(InstructionError::InvalidAccountOwner);
}
let instruction = limited_deserialize(data)?; let instruction = limited_deserialize(data)?;

View File

@ -289,6 +289,12 @@ pub fn process_instruction(
let keyed_accounts = &mut keyed_accounts.iter(); let keyed_accounts = &mut keyed_accounts.iter();
let me = &mut next_keyed_account(keyed_accounts)?; let me = &mut next_keyed_account(keyed_accounts)?;
if invoke_context.is_feature_active(&feature_set::check_program_owner::id())
&& me.owner()? != id()
{
return Err(InstructionError::InvalidAccountOwner);
}
match limited_deserialize(data)? { match limited_deserialize(data)? {
VoteInstruction::InitializeAccount(vote_init) => { VoteInstruction::InitializeAccount(vote_init) => {
verify_rent_exemption(me, next_keyed_account(keyed_accounts)?)?; verify_rent_exemption(me, next_keyed_account(keyed_accounts)?)?;
@ -341,6 +347,7 @@ mod tests {
rent::Rent, rent::Rent,
}; };
use std::cell::RefCell; use std::cell::RefCell;
use std::str::FromStr;
// these are for 100% coverage in this file // these are for 100% coverage in this file
#[test] #[test]
@ -368,8 +375,16 @@ mod tests {
account::create_account(&SlotHashes::default(), 1) account::create_account(&SlotHashes::default(), 1)
} else if sysvar::rent::check_id(&meta.pubkey) { } else if sysvar::rent::check_id(&meta.pubkey) {
account::create_account(&Rent::free(), 1) account::create_account(&Rent::free(), 1)
} else if meta.pubkey == invalid_vote_state_pubkey() {
Account {
owner: invalid_vote_state_pubkey(),
..Account::default()
}
} else { } else {
Account::default() Account {
owner: id(),
..Account::default()
}
}) })
}) })
.collect(); .collect();
@ -393,8 +408,25 @@ mod tests {
} }
} }
fn invalid_vote_state_pubkey() -> Pubkey {
Pubkey::from_str("BadVote111111111111111111111111111111111111").unwrap()
}
#[test]
fn test_spoofed_vote() {
assert_eq!(
process_instruction(&vote(
&invalid_vote_state_pubkey(),
&Pubkey::default(),
Vote::default(),
)),
Err(InstructionError::InvalidAccountOwner),
);
}
#[test] #[test]
fn test_vote_process_instruction() { fn test_vote_process_instruction() {
solana_logger::setup();
let instructions = create_account( let instructions = create_account(
&Pubkey::default(), &Pubkey::default(),
&Pubkey::default(), &Pubkey::default(),

View File

@ -109,7 +109,7 @@ impl ExecuteTimings {
} }
type BankStatusCache = StatusCache<Result<()>>; type BankStatusCache = StatusCache<Result<()>>;
#[frozen_abi(digest = "9wmxRM64shxBcCCjQtRMwCGkWun4VeUufNoHgLXwwFfz")] #[frozen_abi(digest = "3ZaEt781qwhfQSE4DZPBHhng2S6MuimchRjkR9ZWzDFs")]
pub type BankSlotDelta = SlotDelta<Result<()>>; pub type BankSlotDelta = SlotDelta<Result<()>>;
type TransactionAccountRefCells = Vec<Rc<RefCell<Account>>>; type TransactionAccountRefCells = Vec<Rc<RefCell<Account>>>;
type TransactionAccountDepRefCells = Vec<(Pubkey, RefCell<Account>)>; type TransactionAccountDepRefCells = Vec<(Pubkey, RefCell<Account>)>;
@ -10466,7 +10466,7 @@ pub(crate) mod tests {
bank.process_transaction(&tx), bank.process_transaction(&tx),
Err(TransactionError::InstructionError( Err(TransactionError::InstructionError(
0, 0,
InstructionError::InvalidAccountData InstructionError::InvalidAccountOwner
)) ))
); );
assert_eq!(bank.get_balance(&mint_keypair.pubkey()), 498); // transaction fee charged assert_eq!(bank.get_balance(&mint_keypair.pubkey()), 498); // transaction fee charged
@ -10488,7 +10488,7 @@ pub(crate) mod tests {
bank.process_transaction(&tx), bank.process_transaction(&tx),
Err(TransactionError::InstructionError( Err(TransactionError::InstructionError(
0, 0,
InstructionError::InvalidAccountData InstructionError::InvalidAccountOwner
)) ))
); );
assert_eq!(bank.get_balance(&mint_keypair.pubkey()), 496); // transaction fee charged assert_eq!(bank.get_balance(&mint_keypair.pubkey()), 496); // transaction fee charged

View File

@ -195,6 +195,9 @@ pub enum InstructionError {
#[error("An account does not have enough lamports to be rent-exempt")] #[error("An account does not have enough lamports to be rent-exempt")]
AccountNotRentExempt, AccountNotRentExempt,
#[error("Invalid account owner")]
InvalidAccountOwner,
} }
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]

View File

@ -123,6 +123,10 @@ pub mod check_init_vote_data {
solana_sdk::declare_id!("3ccR6QpxGYsAbWyfevEtBNGfWV4xBffxRj2tD6A9i39F"); solana_sdk::declare_id!("3ccR6QpxGYsAbWyfevEtBNGfWV4xBffxRj2tD6A9i39F");
} }
pub mod check_program_owner {
solana_sdk::declare_id!("5XnbR5Es9YXEARRuP6mdvoxiW3hx5atNNeBmwVd8P3QD");
}
lazy_static! { lazy_static! {
/// Map of feature identifiers to user-visible description /// Map of feature identifiers to user-visible description
pub static ref FEATURE_NAMES: HashMap<Pubkey, &'static str> = [ pub static ref FEATURE_NAMES: HashMap<Pubkey, &'static str> = [
@ -153,7 +157,8 @@ lazy_static! {
(full_inflation::mainnet::certusone::vote::id(), "Community vote allowing Certus One to enable full inflation"), (full_inflation::mainnet::certusone::vote::id(), "Community vote allowing Certus One to enable full inflation"),
(warp_timestamp_again::id(), "warp timestamp again, adjust bounding to 25% fast 80% slow #15204"), (warp_timestamp_again::id(), "warp timestamp again, adjust bounding to 25% fast 80% slow #15204"),
(per_byte_logging_cost::id(), "charge the compute budget per byte for logging"), (per_byte_logging_cost::id(), "charge the compute budget per byte for logging"),
(check_init_vote_data::id(), "check initialized Vote data") (check_init_vote_data::id(), "check initialized Vote data"),
(check_program_owner::id(), "limit programs to operating on accounts owned by itself")
/*************** ADD NEW FEATURES HERE ***************/ /*************** ADD NEW FEATURES HERE ***************/
] ]
.iter() .iter()

View File

@ -116,4 +116,5 @@ pub enum InstructionErrorType {
IncorrectAuthority = 43, IncorrectAuthority = 43,
BorshIoError = 44, BorshIoError = 44,
AccountNotRentExempt = 45, AccountNotRentExempt = 45,
InvalidAccountOwner = 46,
} }

View File

@ -483,6 +483,9 @@ impl TryFrom<tx_by_addr::TransactionError> for TransactionError {
41 => InstructionError::ProgramFailedToCompile, 41 => InstructionError::ProgramFailedToCompile,
42 => InstructionError::Immutable, 42 => InstructionError::Immutable,
43 => InstructionError::IncorrectAuthority, 43 => InstructionError::IncorrectAuthority,
44 => InstructionError::BorshIoError(String::new()),
45 => InstructionError::AccountNotRentExempt,
46 => InstructionError::InvalidAccountOwner,
_ => return Err("Invalid InstructionError"), _ => return Err("Invalid InstructionError"),
}; };
@ -706,6 +709,9 @@ impl From<TransactionError> for tx_by_addr::TransactionError {
InstructionError::AccountNotRentExempt => { InstructionError::AccountNotRentExempt => {
tx_by_addr::InstructionErrorType::AccountNotRentExempt tx_by_addr::InstructionErrorType::AccountNotRentExempt
} }
InstructionError::InvalidAccountOwner => {
tx_by_addr::InstructionErrorType::InvalidAccountOwner
}
} as i32, } as i32,
custom: match instruction_error { custom: match instruction_error {
InstructionError::Custom(custom) => { InstructionError::Custom(custom) => {

View File

@ -95,6 +95,7 @@ enum InstructionErrorType {
INCORRECT_AUTHORITY = 43; INCORRECT_AUTHORITY = 43;
BORSH_IO_ERROR = 44; BORSH_IO_ERROR = 44;
ACCOUNT_NOT_RENT_EXEMPT = 45; ACCOUNT_NOT_RENT_EXEMPT = 45;
INVALID_ACCOUNT_OWNER = 46;
} }
message UnixTimestamp { message UnixTimestamp {