Reverify programs that are extended using ExtendProgram (#31886)

This commit is contained in:
Pankaj Garg 2023-06-01 06:17:42 -07:00 committed by GitHub
parent 0495051a67
commit 449f92e32c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 137 additions and 8 deletions

BIN
programs/bpf-loader-tests/noop.so Executable file

Binary file not shown.

View File

@ -10,7 +10,7 @@ use {
signature::{Keypair, Signer},
system_instruction::{self, SystemError, MAX_PERMITTED_DATA_LENGTH},
system_program,
transaction::Transaction,
transaction::{Transaction, TransactionError},
},
};
@ -19,10 +19,11 @@ mod common;
#[tokio::test]
async fn test_extend_program() {
let mut context = setup_test_context().await;
let program_file = find_file("noop.so").expect("Failed to find the file");
let data = read_file(program_file);
let program_address = Pubkey::new_unique();
let (programdata_address, _) = Pubkey::find_program_address(&[program_address.as_ref()], &id());
let program_data_len = 100;
add_upgradeable_loader_account(
&mut context,
&program_address,
@ -33,6 +34,8 @@ async fn test_extend_program() {
|_| {},
)
.await;
let programdata_data_offset = UpgradeableLoaderState::size_of_programdata_metadata();
let program_data_len = data.len() + programdata_data_offset;
add_upgradeable_loader_account(
&mut context,
&programdata_address,
@ -41,7 +44,7 @@ async fn test_extend_program() {
upgrade_authority_address: Some(Pubkey::new_unique()),
},
program_data_len,
|_| {},
|account| account.data_as_mut_slice()[programdata_data_offset..].copy_from_slice(&data),
)
.await;
@ -72,6 +75,90 @@ async fn test_extend_program() {
);
}
#[tokio::test]
async fn test_failed_extend_twice_in_same_slot() {
let mut context = setup_test_context().await;
let program_file = find_file("noop.so").expect("Failed to find the file");
let data = read_file(program_file);
let program_address = Pubkey::new_unique();
let (programdata_address, _) = Pubkey::find_program_address(&[program_address.as_ref()], &id());
add_upgradeable_loader_account(
&mut context,
&program_address,
&UpgradeableLoaderState::Program {
programdata_address,
},
UpgradeableLoaderState::size_of_program(),
|_| {},
)
.await;
let programdata_data_offset = UpgradeableLoaderState::size_of_programdata_metadata();
let program_data_len = data.len() + programdata_data_offset;
add_upgradeable_loader_account(
&mut context,
&programdata_address,
&UpgradeableLoaderState::ProgramData {
slot: 0,
upgrade_authority_address: Some(Pubkey::new_unique()),
},
program_data_len,
|account| account.data_as_mut_slice()[programdata_data_offset..].copy_from_slice(&data),
)
.await;
let client = &mut context.banks_client;
let payer = &context.payer;
let recent_blockhash = context.last_blockhash;
const ADDITIONAL_BYTES: u32 = 42;
let transaction = Transaction::new_signed_with_payer(
&[extend_program(
&program_address,
Some(&payer.pubkey()),
ADDITIONAL_BYTES,
)],
Some(&payer.pubkey()),
&[payer],
recent_blockhash,
);
assert_matches!(client.process_transaction(transaction).await, Ok(()));
let updated_program_data_account = client
.get_account(programdata_address)
.await
.unwrap()
.unwrap();
assert_eq!(
updated_program_data_account.data().len(),
program_data_len + ADDITIONAL_BYTES as usize
);
let recent_blockhash = client
.get_new_latest_blockhash(&recent_blockhash)
.await
.unwrap();
// Extending the program in the same slot should fail
let transaction = Transaction::new_signed_with_payer(
&[extend_program(
&program_address,
Some(&payer.pubkey()),
ADDITIONAL_BYTES,
)],
Some(&payer.pubkey()),
&[payer],
recent_blockhash,
);
assert_matches!(
client
.process_transaction(transaction)
.await
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(0, InstructionError::InvalidArgument)
);
}
#[tokio::test]
async fn test_extend_program_not_upgradeable() {
let mut context = setup_test_context().await;
@ -293,6 +380,9 @@ async fn test_extend_program_without_payer() {
let mut context = setup_test_context().await;
let rent = context.banks_client.get_rent().await.unwrap();
let program_file = find_file("noop.so").expect("Failed to find the file");
let data = read_file(program_file);
let program_address = Pubkey::new_unique();
let (programdata_address, _) = Pubkey::find_program_address(&[program_address.as_ref()], &id());
add_upgradeable_loader_account(
@ -305,7 +395,8 @@ async fn test_extend_program_without_payer() {
|_| {},
)
.await;
let program_data_len = 100;
let programdata_data_offset = UpgradeableLoaderState::size_of_programdata_metadata();
let program_data_len = data.len() + programdata_data_offset;
add_upgradeable_loader_account(
&mut context,
&programdata_address,
@ -314,7 +405,7 @@ async fn test_extend_program_without_payer() {
upgrade_authority_address: Some(Pubkey::new_unique()),
},
program_data_len,
|_| {},
|account| account.data_as_mut_slice()[programdata_data_offset..].copy_from_slice(&data),
)
.await;

View File

@ -1325,6 +1325,7 @@ fn process_loader_upgradeable_instruction(
ic_logger_msg!(log_collector, "Program account not owned by loader");
return Err(InstructionError::InvalidAccountOwner);
}
let program_key = *program_account.get_key();
match program_account.get_state()? {
UpgradeableLoaderState::Program {
programdata_address,
@ -1356,11 +1357,21 @@ fn process_loader_upgradeable_instruction(
return Err(InstructionError::InvalidRealloc);
}
if let UpgradeableLoaderState::ProgramData {
slot: _,
let clock_slot = invoke_context
.get_sysvar_cache()
.get_clock()
.map(|clock| clock.slot)?;
let upgrade_authority_address = if let UpgradeableLoaderState::ProgramData {
slot,
upgrade_authority_address,
} = programdata_account.get_state()?
{
if clock_slot == slot {
ic_logger_msg!(log_collector, "Program was extended in this block already");
return Err(InstructionError::InvalidArgument);
}
if upgrade_authority_address.is_none() {
ic_logger_msg!(
log_collector,
@ -1368,10 +1379,11 @@ fn process_loader_upgradeable_instruction(
);
return Err(InstructionError::Immutable);
}
upgrade_authority_address
} else {
ic_logger_msg!(log_collector, "ProgramData state is invalid");
return Err(InstructionError::InvalidAccountData);
}
};
let required_payment = {
let balance = programdata_account.get_lamports();
@ -1383,6 +1395,8 @@ fn process_loader_upgradeable_instruction(
// Borrowed accounts need to be dropped before native_invoke
drop(programdata_account);
// Dereference the program ID to prevent overlapping mutable/immutable borrow of invoke context
let program_id = *program_id;
if required_payment > 0 {
let payer_key = *transaction_context.get_key_of_account_at_index(
instruction_context.get_index_of_instruction_account_in_transaction(
@ -1403,6 +1417,30 @@ fn process_loader_upgradeable_instruction(
.try_borrow_instruction_account(transaction_context, PROGRAM_DATA_ACCOUNT_INDEX)?;
programdata_account.set_data_length(new_len)?;
let programdata_data_offset = UpgradeableLoaderState::size_of_programdata_metadata();
deploy_program!(
invoke_context,
program_key,
&program_id,
UpgradeableLoaderState::size_of_program().saturating_add(new_len),
clock_slot,
{
drop(programdata_account);
},
programdata_account
.get_data()
.get(programdata_data_offset..)
.ok_or(InstructionError::AccountDataTooSmall)?,
);
let mut programdata_account = instruction_context
.try_borrow_instruction_account(transaction_context, PROGRAM_DATA_ACCOUNT_INDEX)?;
programdata_account.set_state(&UpgradeableLoaderState::ProgramData {
slot: clock_slot,
upgrade_authority_address,
})?;
ic_logger_msg!(
log_collector,
"Extended ProgramData account by {} bytes",