Skip to content

Commit

Permalink
Fix - loader-v4 (#3586)
Browse files Browse the repository at this point in the history
* Swapping the roles of the macro and wrapper function
allows us to get rid of some lifetime shenanigans and explicit drop().
It is a pure syntactic refactoring and should have no effect on the behavior.

* Loader-v4 needs to use the same verification logic during deployment as is in loader-v3.

* Removes program account check in loader-v4 execution.

* Fix program runtime environment of loader-v4.
  • Loading branch information
Lichtso authored Nov 14, 2024
1 parent 8652ecc commit 8bf688f
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 167 deletions.
195 changes: 95 additions & 100 deletions programs/bpf_loader/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use {
invoke_context::{BpfAllocator, InvokeContext, SerializedAccountMetadata, SyscallContext},
loaded_programs::{
LoadProgramMetrics, ProgramCacheEntry, ProgramCacheEntryOwner, ProgramCacheEntryType,
DELAY_VISIBILITY_SLOT_OFFSET,
ProgramCacheForTxBatch, ProgramRuntimeEnvironment, DELAY_VISIBILITY_SLOT_OFFSET,
},
mem_pool::VmMemoryPool,
stable_log,
Expand Down Expand Up @@ -103,94 +103,96 @@ pub fn load_program_from_bytes(
Ok(loaded_program)
}

macro_rules! deploy_program {
($invoke_context:expr, $program_id:expr, $loader_key:expr,
$account_size:expr, $slot:expr, $drop:expr, $new_programdata:expr $(,)?) => {{
let mut load_program_metrics = LoadProgramMetrics::default();
let mut register_syscalls_time = Measure::start("register_syscalls_time");
let deployment_slot: Slot = $slot;
let environments = $invoke_context.get_environments_for_slot(
deployment_slot.saturating_add(DELAY_VISIBILITY_SLOT_OFFSET)
).map_err(|e| {
// This will never fail since the epoch schedule is already configured.
ic_msg!($invoke_context, "Failed to get runtime environment: {}", e);
InstructionError::ProgramEnvironmentSetupFailure
})?;
let deployment_program_runtime_environment = morph_into_deployment_environment_v1(
environments.program_runtime_v1.clone(),
).map_err(|e| {
ic_msg!($invoke_context, "Failed to register syscalls: {}", e);
InstructionError::ProgramEnvironmentSetupFailure
})?;
register_syscalls_time.stop();
load_program_metrics.register_syscalls_us = register_syscalls_time.as_us();
// Verify using stricter deployment_program_runtime_environment
let mut load_elf_time = Measure::start("load_elf_time");
let executable = Executable::<InvokeContext>::load(
$new_programdata,
Arc::new(deployment_program_runtime_environment),
).map_err(|err| {
ic_logger_msg!($invoke_context.get_log_collector(), "{}", err);
InstructionError::InvalidAccountData
})?;
load_elf_time.stop();
load_program_metrics.load_elf_us = load_elf_time.as_us();
let mut verify_code_time = Measure::start("verify_code_time");
executable.verify::<RequisiteVerifier>().map_err(|err| {
ic_logger_msg!($invoke_context.get_log_collector(), "{}", err);
InstructionError::InvalidAccountData
})?;
verify_code_time.stop();
load_program_metrics.verify_code_us = verify_code_time.as_us();
// Reload but with environments.program_runtime_v1
let executor = load_program_from_bytes(
$invoke_context.get_log_collector(),
&mut load_program_metrics,
$new_programdata,
$loader_key,
$account_size,
$slot,
environments.program_runtime_v1.clone(),
true,
)?;
if let Some(old_entry) = $invoke_context.program_cache_for_tx_batch.find(&$program_id) {
executor.tx_usage_counter.store(
old_entry.tx_usage_counter.load(Ordering::Relaxed),
Ordering::Relaxed
);
executor.ix_usage_counter.store(
old_entry.ix_usage_counter.load(Ordering::Relaxed),
Ordering::Relaxed
);
}
$drop
load_program_metrics.program_id = $program_id.to_string();
load_program_metrics.submit_datapoint(&mut $invoke_context.timings);
$invoke_context.program_cache_for_tx_batch.store_modified_entry($program_id, Arc::new(executor));
}};
}

/// Directly deploy a program using a provided invoke context.
/// This function should only be invoked from the runtime, since it does not
/// provide any account loads or checks.
pub fn direct_deploy_program(
invoke_context: &mut InvokeContext,
pub fn deploy_program_internal(
log_collector: Option<Rc<RefCell<LogCollector>>>,
program_cache_for_tx_batch: &mut ProgramCacheForTxBatch,
program_runtime_environment: ProgramRuntimeEnvironment,
program_id: &Pubkey,
loader_key: &Pubkey,
account_size: usize,
elf: &[u8],
slot: Slot,
) -> Result<(), InstructionError> {
deploy_program!(
invoke_context,
*program_id,
programdata: &[u8],
deployment_slot: Slot,
) -> Result<LoadProgramMetrics, InstructionError> {
let mut load_program_metrics = LoadProgramMetrics::default();
let mut register_syscalls_time = Measure::start("register_syscalls_time");
let deployment_program_runtime_environment =
morph_into_deployment_environment_v1(program_runtime_environment.clone()).map_err(|e| {
ic_logger_msg!(log_collector, "Failed to register syscalls: {}", e);
InstructionError::ProgramEnvironmentSetupFailure
})?;
register_syscalls_time.stop();
load_program_metrics.register_syscalls_us = register_syscalls_time.as_us();
// Verify using stricter deployment_program_runtime_environment
let mut load_elf_time = Measure::start("load_elf_time");
let executable = Executable::<InvokeContext>::load(
programdata,
Arc::new(deployment_program_runtime_environment),
)
.map_err(|err| {
ic_logger_msg!(log_collector, "{}", err);
InstructionError::InvalidAccountData
})?;
load_elf_time.stop();
load_program_metrics.load_elf_us = load_elf_time.as_us();
let mut verify_code_time = Measure::start("verify_code_time");
executable.verify::<RequisiteVerifier>().map_err(|err| {
ic_logger_msg!(log_collector, "{}", err);
InstructionError::InvalidAccountData
})?;
verify_code_time.stop();
load_program_metrics.verify_code_us = verify_code_time.as_us();
// Reload but with program_runtime_environment
let executor = load_program_from_bytes(
log_collector,
&mut load_program_metrics,
programdata,
loader_key,
account_size,
slot,
{},
elf,
);
Ok(())
deployment_slot,
program_runtime_environment,
true,
)?;
if let Some(old_entry) = program_cache_for_tx_batch.find(program_id) {
executor.tx_usage_counter.store(
old_entry.tx_usage_counter.load(Ordering::Relaxed),
Ordering::Relaxed,
);
executor.ix_usage_counter.store(
old_entry.ix_usage_counter.load(Ordering::Relaxed),
Ordering::Relaxed,
);
}
load_program_metrics.program_id = program_id.to_string();
program_cache_for_tx_batch.store_modified_entry(*program_id, Arc::new(executor));
Ok(load_program_metrics)
}

#[macro_export]
macro_rules! deploy_program {
($invoke_context:expr, $program_id:expr, $loader_key:expr, $account_size:expr, $programdata:expr, $deployment_slot:expr $(,)?) => {
let environments = $invoke_context
.get_environments_for_slot($deployment_slot.saturating_add(
solana_program_runtime::loaded_programs::DELAY_VISIBILITY_SLOT_OFFSET,
))
.map_err(|_err| {
// This will never fail since the epoch schedule is already configured.
InstructionError::ProgramEnvironmentSetupFailure
})?;
let load_program_metrics = deploy_program_internal(
$invoke_context.get_log_collector(),
$invoke_context.program_cache_for_tx_batch,
environments.program_runtime_v1.clone(),
$program_id,
$loader_key,
$account_size,
$programdata,
$deployment_slot,
)?;
load_program_metrics.submit_datapoint(&mut $invoke_context.timings);
};
}

fn write_program_data(
Expand Down Expand Up @@ -689,18 +691,16 @@ fn process_loader_upgradeable_instruction(
instruction_context.try_borrow_instruction_account(transaction_context, 3)?;
deploy_program!(
invoke_context,
new_program_id,
&new_program_id,
&owner_id,
UpgradeableLoaderState::size_of_program().saturating_add(programdata_len),
clock.slot,
{
drop(buffer);
},
buffer
.get_data()
.get(buffer_data_offset..)
.ok_or(InstructionError::AccountDataTooSmall)?,
clock.slot,
);
drop(buffer);

let transaction_context = &invoke_context.transaction_context;
let instruction_context = transaction_context.get_current_instruction_context()?;
Expand Down Expand Up @@ -873,18 +873,16 @@ fn process_loader_upgradeable_instruction(
instruction_context.try_borrow_instruction_account(transaction_context, 2)?;
deploy_program!(
invoke_context,
new_program_id,
&new_program_id,
program_id,
UpgradeableLoaderState::size_of_program().saturating_add(programdata_len),
clock.slot,
{
drop(buffer);
},
buffer
.get_data()
.get(buffer_data_offset..)
.ok_or(InstructionError::AccountDataTooSmall)?,
clock.slot,
);
drop(buffer);

let transaction_context = &invoke_context.transaction_context;
let instruction_context = transaction_context.get_current_instruction_context()?;
Expand Down Expand Up @@ -1312,18 +1310,16 @@ fn process_loader_upgradeable_instruction(

deploy_program!(
invoke_context,
program_key,
&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)?,
clock_slot,
);
drop(programdata_account);

let mut programdata_account = instruction_context
.try_borrow_instruction_account(transaction_context, PROGRAM_DATA_ACCOUNT_INDEX)?;
Expand Down Expand Up @@ -3831,12 +3827,11 @@ mod tests {
file.read_to_end(&mut elf).unwrap();
deploy_program!(
invoke_context,
program_id,
&program_id,
&bpf_loader_upgradeable::id(),
elf.len(),
2,
{},
&elf
&elf,
2_u64,
);
Ok(())
}
Expand Down
69 changes: 12 additions & 57 deletions programs/loader-v4/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
use {
solana_bpf_loader_program::execute,
solana_bpf_loader_program::{deploy_program, deploy_program_internal, execute},
solana_log_collector::{ic_logger_msg, LogCollector},
solana_measure::measure::Measure,
solana_program_runtime::{
invoke_context::InvokeContext,
loaded_programs::{
LoadProgramMetrics, ProgramCacheEntry, ProgramCacheEntryOwner, ProgramCacheEntryType,
DELAY_VISIBILITY_SLOT_OFFSET,
},
loaded_programs::{ProgramCacheEntry, ProgramCacheEntryOwner, ProgramCacheEntryType},
},
solana_rbpf::{declare_builtin_function, memory_region::MemoryMapping},
solana_sdk::{
Expand Down Expand Up @@ -263,36 +260,15 @@ pub fn process_instruction_deploy(
.get_data()
.get(LoaderV4State::program_data_offset()..)
.ok_or(InstructionError::AccountDataTooSmall)?;

let deployment_slot = state.slot;
let effective_slot = deployment_slot.saturating_add(DELAY_VISIBILITY_SLOT_OFFSET);

let environments = invoke_context
.get_environments_for_slot(effective_slot)
.map_err(|err| {
// This will never fail since the epoch schedule is already configured.
ic_logger_msg!(log_collector, "Failed to get runtime environment {}", err);
InstructionError::InvalidArgument
})?;

let mut load_program_metrics = LoadProgramMetrics {
program_id: buffer.get_key().to_string(),
..LoadProgramMetrics::default()
};
let executor = ProgramCacheEntry::new(
deploy_program!(
invoke_context,
program.get_key(),
&loader_v4::id(),
environments.program_runtime_v1.clone(),
deployment_slot,
effective_slot,
programdata,
buffer.get_data().len(),
&mut load_program_metrics,
)
.map_err(|err| {
ic_logger_msg!(log_collector, "{}", err);
InstructionError::InvalidAccountData
})?;
load_program_metrics.submit_datapoint(&mut invoke_context.timings);
programdata,
current_slot,
);

if let Some(mut source_program) = source_program {
let rent = invoke_context.get_sysvar_cache().get_rent()?;
let required_lamports = rent.minimum_balance(source_program.get_data().len());
Expand All @@ -305,23 +281,6 @@ pub fn process_instruction_deploy(
let state = get_state_mut(program.get_data_mut()?)?;
state.slot = current_slot;
state.status = LoaderV4Status::Deployed;

if let Some(old_entry) = invoke_context
.program_cache_for_tx_batch
.find(program.get_key())
{
executor.tx_usage_counter.store(
old_entry.tx_usage_counter.load(Ordering::Relaxed),
Ordering::Relaxed,
);
executor.ix_usage_counter.store(
old_entry.ix_usage_counter.load(Ordering::Relaxed),
Ordering::Relaxed,
);
}
invoke_context
.program_cache_for_tx_batch
.store_modified_entry(*program.get_key(), Arc::new(executor));
Ok(())
}

Expand Down Expand Up @@ -488,11 +447,6 @@ pub fn process_instruction_inner(
.map_err(|err| Box::new(err) as Box<dyn std::error::Error>)
} else {
let program = instruction_context.try_borrow_last_program_account(transaction_context)?;
let state = get_state(program.get_data())?;
if matches!(state.status, LoaderV4Status::Retracted) {
ic_logger_msg!(log_collector, "Program is retracted");
return Err(Box::new(InstructionError::UnsupportedProgramId));
}
let mut get_or_create_executor_time = Measure::start("get_or_create_executor_time");
let loaded_program = invoke_context
.program_cache_for_tx_batch
Expand Down Expand Up @@ -1590,16 +1544,17 @@ mod tests {
&[0, 1, 2, 3],
transaction_accounts.clone(),
&[(1, false, true)],
Err(InstructionError::AccountDataTooSmall),
Err(InstructionError::UnsupportedProgramId),
);

// Error: Program is not deployed
// This is only checked in integration with load_program_accounts() in the SVM
process_instruction(
vec![3],
&[0, 1, 2, 3],
transaction_accounts.clone(),
&[(1, false, true)],
Err(InstructionError::UnsupportedProgramId),
Ok(()),
);

// Error: Program fails verification
Expand Down
Loading

0 comments on commit 8bf688f

Please sign in to comment.