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:
parent
d47f1fae40
commit
8399851d11
|
@ -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)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)?;
|
||||||
|
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -116,4 +116,5 @@ pub enum InstructionErrorType {
|
||||||
IncorrectAuthority = 43,
|
IncorrectAuthority = 43,
|
||||||
BorshIoError = 44,
|
BorshIoError = 44,
|
||||||
AccountNotRentExempt = 45,
|
AccountNotRentExempt = 45,
|
||||||
|
InvalidAccountOwner = 46,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in New Issue