From 8dd23e0cff636fd317cc6b26c81ec170b3fbb964 Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Fri, 6 Dec 2024 04:03:54 +0800 Subject: [PATCH] feat: deplete compute meter for vm errors (#3751) * feat: deplete compute meter for vm errors * add tests * rekey apply_cost_tracker_during_replay (cherry picked from commit b120fc1388f6efecc75f826f97ff89f519b11c4d) # Conflicts: # programs/sbf/tests/programs.rs # sdk/src/feature_set.rs --- programs/bpf_loader/src/lib.rs | 18 + programs/sbf/Cargo.lock | 7 + programs/sbf/Cargo.toml | 1 + programs/sbf/rust/divide_by_zero/Cargo.toml | 15 + programs/sbf/rust/divide_by_zero/src/lib.rs | 27 ++ programs/sbf/tests/programs.rs | 511 +++++++++++++++++++- sdk/src/feature_set.rs | 4 + 7 files changed, 568 insertions(+), 15 deletions(-) create mode 100644 programs/sbf/rust/divide_by_zero/Cargo.toml create mode 100644 programs/sbf/rust/divide_by_zero/src/lib.rs diff --git a/programs/bpf_loader/src/lib.rs b/programs/bpf_loader/src/lib.rs index 2c2b16245c88ce..a10e5e7674410d 100644 --- a/programs/bpf_loader/src/lib.rs +++ b/programs/bpf_loader/src/lib.rs @@ -1433,6 +1433,24 @@ fn execute<'a, 'b: 'a>( Err(Box::new(error) as Box) } ProgramResult::Err(mut error) => { + if invoke_context + .get_feature_set() + .is_active(&solana_feature_set::apply_cost_tracker_during_replay::id()) + && !matches!(error, EbpfError::SyscallError(_)) + { + // when an exception is thrown during the execution of a + // Basic Block (e.g., a null memory dereference or other + // faults), determining the exact number of CUs consumed + // up to the point of failure requires additional effort + // and is unnecessary since these cases are rare. + // + // In order to simplify CU tracking, simply consume all + // remaining compute units so that the block cost + // tracker uses the full requested compute unit cost for + // this failed transaction. + invoke_context.consume(invoke_context.get_remaining()); + } + if direct_mapping { if let EbpfError::AccessViolation( AccessType::Store, diff --git a/programs/sbf/Cargo.lock b/programs/sbf/Cargo.lock index e4ab2688741ada..bf4c5fe6c68802 100644 --- a/programs/sbf/Cargo.lock +++ b/programs/sbf/Cargo.lock @@ -5768,6 +5768,13 @@ dependencies = [ "solana-program", ] +[[package]] +name = "solana-sbf-rust-divide-by-zero" +version = "2.2.0" +dependencies = [ + "solana-program", +] + [[package]] name = "solana-sbf-rust-dup-accounts" version = "2.0.20" diff --git a/programs/sbf/Cargo.toml b/programs/sbf/Cargo.toml index cf334e7ba59efc..162add8efbff97 100644 --- a/programs/sbf/Cargo.toml +++ b/programs/sbf/Cargo.toml @@ -125,6 +125,7 @@ members = [ "rust/custom_heap", "rust/dep_crate", "rust/deprecated_loader", + "rust/divide_by_zero", "rust/dup_accounts", "rust/error_handling", "rust/external_spend", diff --git a/programs/sbf/rust/divide_by_zero/Cargo.toml b/programs/sbf/rust/divide_by_zero/Cargo.toml new file mode 100644 index 00000000000000..029b78eea386e3 --- /dev/null +++ b/programs/sbf/rust/divide_by_zero/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "solana-sbf-rust-divide-by-zero" +version = { workspace = true } +description = { workspace = true } +authors = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +license = { workspace = true } +edition = { workspace = true } + +[dependencies] +solana-program = { workspace = true } + +[lib] +crate-type = ["cdylib"] diff --git a/programs/sbf/rust/divide_by_zero/src/lib.rs b/programs/sbf/rust/divide_by_zero/src/lib.rs new file mode 100644 index 00000000000000..446faa0730dd2c --- /dev/null +++ b/programs/sbf/rust/divide_by_zero/src/lib.rs @@ -0,0 +1,27 @@ +//! Example/test program to trigger vm error by dividing by zero + +#![feature(asm_experimental_arch)] + +extern crate solana_program; +use { + solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey}, + std::arch::asm, +}; + +solana_program::entrypoint_no_alloc!(process_instruction); +fn process_instruction( + _program_id: &Pubkey, + _accounts: &[AccountInfo], + _instruction_data: &[u8], +) -> ProgramResult { + unsafe { + asm!( + " + mov64 r0, 0 + mov64 r1, 0 + div64 r0, r1 + " + ); + } + Ok(()) +} diff --git a/programs/sbf/tests/programs.rs b/programs/sbf/tests/programs.rs index 55802a89dcbc33..3e4bd50bce98c0 100644 --- a/programs/sbf/tests/programs.rs +++ b/programs/sbf/tests/programs.rs @@ -91,8 +91,27 @@ fn process_transaction_and_record_inner( Result<(), TransactionError>, Vec>, Vec, + u64, ) { +<<<<<<< HEAD let signature = tx.signatures.first().unwrap().clone(); +======= + let commit_result = load_execute_and_commit_transaction(bank, tx); + let CommittedTransaction { + inner_instructions, + log_messages, + status, + executed_units, + .. + } = commit_result.unwrap(); + let inner_instructions = inner_instructions.expect("cpi recording should be enabled"); + let log_messages = log_messages.expect("log recording should be enabled"); + (status, inner_instructions, log_messages, executed_units) +} + +#[cfg(feature = "sbf_rust")] +fn load_execute_and_commit_transaction(bank: &Bank, tx: Transaction) -> TransactionCommitResult { +>>>>>>> b120fc1388 (feat: deplete compute meter for vm errors (#3751)) let txs = vec![tx]; let tx_batch = bank.prepare_batch_for_tests(txs); let mut results = bank @@ -790,7 +809,7 @@ fn test_program_sbf_invoke_sanity() { message.clone(), bank.last_blockhash(), ); - let (result, inner_instructions, _log_messages) = + let (result, inner_instructions, _log_messages, _executed_units) = process_transaction_and_record_inner(&bank, tx); assert_eq!(result, Ok(())); @@ -859,11 +878,12 @@ fn test_program_sbf_invoke_sanity() { // failure cases - let do_invoke_failure_test_local = + let do_invoke_failure_test_local_with_compute_check = |test: u8, expected_error: TransactionError, expected_invoked_programs: &[Pubkey], - expected_log_messages: Option>| { + expected_log_messages: Option>, + should_deplete_compute_meter: bool| { println!("Running failure test #{:?}", test); let instruction_data = &[test, bump_seed1, bump_seed2, bump_seed3]; let signers = vec![ @@ -872,14 +892,21 @@ fn test_program_sbf_invoke_sanity() { &invoked_argument_keypair, &from_keypair, ]; + let compute_unit_limit = 1_000_000; let instruction = Instruction::new_with_bytes( invoke_program_id, instruction_data, account_metas.clone(), ); - let message = Message::new(&[instruction], Some(&mint_pubkey)); + let message = Message::new( + &[ + instruction, + ComputeBudgetInstruction::set_compute_unit_limit(compute_unit_limit), + ], + Some(&mint_pubkey), + ); let tx = Transaction::new(&signers, message.clone(), bank.last_blockhash()); - let (result, inner_instructions, log_messages) = + let (result, inner_instructions, log_messages, executed_units) = process_transaction_and_record_inner(&bank, tx); let invoked_programs: Vec = inner_instructions[0] .iter() @@ -888,6 +915,11 @@ fn test_program_sbf_invoke_sanity() { .collect(); assert_eq!(result, Err(expected_error)); assert_eq!(invoked_programs, expected_invoked_programs); + if should_deplete_compute_meter { + assert_eq!(executed_units, compute_unit_limit as u64); + } else { + assert!(executed_units < compute_unit_limit as u64); + } if let Some(expected_log_messages) = expected_log_messages { assert_eq!(log_messages.len(), expected_log_messages.len()); expected_log_messages @@ -901,6 +933,20 @@ fn test_program_sbf_invoke_sanity() { } }; + let do_invoke_failure_test_local = + |test: u8, + expected_error: TransactionError, + expected_invoked_programs: &[Pubkey], + expected_log_messages: Option>| { + do_invoke_failure_test_local_with_compute_check( + test, + expected_error, + expected_invoked_programs, + expected_log_messages, + false, // should_deplete_compute_meter + ) + }; + let program_lang = match program.0 { Languages::Rust => "Rust", Languages::C => "C", @@ -1008,11 +1054,12 @@ fn test_program_sbf_invoke_sanity() { None, ); - do_invoke_failure_test_local( + do_invoke_failure_test_local_with_compute_check( TEST_WRITABLE_DEESCALATION_WRITABLE, TransactionError::InstructionError(0, InstructionError::ReadonlyDataModified), &[invoked_program_id.clone()], None, + true, // should_deplete_compute_meter ); do_invoke_failure_test_local( @@ -1094,7 +1141,7 @@ fn test_program_sbf_invoke_sanity() { message.clone(), bank.last_blockhash(), ); - let (result, inner_instructions, _log_messages) = + let (result, inner_instructions, _log_messages, _executed_units) = process_transaction_and_record_inner(&bank, tx); let invoked_programs: Vec = inner_instructions[0] .iter() @@ -1731,7 +1778,7 @@ fn test_program_sbf_invoke_stable_genesis_and_bank() { message.clone(), bank.last_blockhash(), ); - let (result, _, _) = process_transaction_and_record_inner(&bank, tx); + let (result, _, _, _) = process_transaction_and_record_inner(&bank, tx); assert_eq!( result.unwrap_err(), TransactionError::InstructionError(1, InstructionError::InvalidAccountData), @@ -1778,7 +1825,7 @@ fn test_program_sbf_invoke_stable_genesis_and_bank() { message.clone(), bank.last_blockhash(), ); - let (result, _, _) = process_transaction_and_record_inner(&bank, tx); + let (result, _, _, _) = process_transaction_and_record_inner(&bank, tx); assert_eq!( result.unwrap_err(), TransactionError::InstructionError(1, InstructionError::InvalidAccountData), @@ -1889,7 +1936,7 @@ fn test_program_sbf_invoke_in_same_tx_as_deployment() { &TransactionError::ProgramAccountNotFound, ); } else { - let (result, _, _) = process_transaction_and_record_inner(&bank, tx); + let (result, _, _, _) = process_transaction_and_record_inner(&bank, tx); assert_eq!( result.unwrap_err(), TransactionError::InstructionError(2, InstructionError::InvalidAccountData), @@ -2000,7 +2047,7 @@ fn test_program_sbf_invoke_in_same_tx_as_redeployment() { message.clone(), bank.last_blockhash(), ); - let (result, _, _) = process_transaction_and_record_inner(&bank, tx); + let (result, _, _, _) = process_transaction_and_record_inner(&bank, tx); assert_eq!( result.unwrap_err(), TransactionError::InstructionError(1, InstructionError::InvalidAccountData), @@ -2095,7 +2142,7 @@ fn test_program_sbf_invoke_in_same_tx_as_undeployment() { message.clone(), bank.last_blockhash(), ); - let (result, _, _) = process_transaction_and_record_inner(&bank, tx); + let (result, _, _, _) = process_transaction_and_record_inner(&bank, tx); assert_eq!( result.unwrap_err(), TransactionError::InstructionError(1, InstructionError::InvalidAccountData), @@ -4145,7 +4192,7 @@ fn test_cpi_account_ownership_writability() { ); let message = Message::new(&[instruction], Some(&mint_pubkey)); let tx = Transaction::new(&[&mint_keypair], message.clone(), bank.last_blockhash()); - let (result, _, logs) = process_transaction_and_record_inner(&bank, tx); + let (result, _, logs, _) = process_transaction_and_record_inner(&bank, tx); if direct_mapping { assert_eq!( result.unwrap_err(), @@ -4581,7 +4628,7 @@ fn test_cpi_invalid_account_info_pointers() { let message = Message::new(&[instruction], Some(&mint_pubkey)); let tx = Transaction::new(&[&mint_keypair], message.clone(), bank.last_blockhash()); - let (result, _, logs) = process_transaction_and_record_inner(&bank, tx); + let (result, _, logs, _) = process_transaction_and_record_inner(&bank, tx); assert!(result.is_err(), "{result:?}"); assert!( logs.iter().any(|log| log.contains("Invalid pointer")), @@ -4591,6 +4638,134 @@ fn test_cpi_invalid_account_info_pointers() { } } +#[test] +#[cfg(feature = "sbf_rust")] +fn test_deplete_cost_meter_with_access_violation() { + solana_logger::setup(); + let GenesisConfigInfo { + genesis_config, + mint_keypair, + .. + } = create_genesis_config(100_123_456_789); + + for apply_cost_tracker in [false, true] { + let mut bank = Bank::new_for_tests(&genesis_config); + let feature_set = Arc::make_mut(&mut bank.feature_set); + // by default test banks have all features enabled, so we only need to + // disable when needed + if !apply_cost_tracker { + feature_set.deactivate(&feature_set::apply_cost_tracker_during_replay::id()); + } + let (bank, bank_forks) = bank.wrap_with_bank_forks_for_tests(); + let mut bank_client = BankClient::new_shared(bank.clone()); + let authority_keypair = Keypair::new(); + let (bank, invoke_program_id) = load_upgradeable_program_and_advance_slot( + &mut bank_client, + bank_forks.as_ref(), + &mint_keypair, + &authority_keypair, + "solana_sbf_rust_invoke", + ); + + let account_keypair = Keypair::new(); + let mint_pubkey = mint_keypair.pubkey(); + let account_metas = vec![ + AccountMeta::new(mint_pubkey, true), + AccountMeta::new(account_keypair.pubkey(), false), + AccountMeta::new_readonly(invoke_program_id, false), + ]; + + let mut instruction_data = vec![TEST_WRITE_ACCOUNT, 2]; + instruction_data.extend_from_slice(3usize.to_le_bytes().as_ref()); + instruction_data.push(42); + + let instruction = Instruction::new_with_bytes( + invoke_program_id, + &instruction_data, + account_metas.clone(), + ); + + let compute_unit_limit = 10_000u32; + let message = Message::new( + &[ + ComputeBudgetInstruction::set_compute_unit_limit(compute_unit_limit), + instruction, + ], + Some(&mint_keypair.pubkey()), + ); + let tx = Transaction::new(&[&mint_keypair], message, bank.last_blockhash()); + + let result = load_execute_and_commit_transaction(&bank, tx).unwrap(); + + assert_eq!( + result.status.unwrap_err(), + TransactionError::InstructionError(1, InstructionError::ReadonlyDataModified) + ); + + if apply_cost_tracker { + assert_eq!(result.executed_units, u64::from(compute_unit_limit)); + } else { + assert!(result.executed_units < u64::from(compute_unit_limit)); + } + } +} + +#[test] +#[cfg(feature = "sbf_rust")] +fn test_program_sbf_deplete_cost_meter_with_divide_by_zero() { + solana_logger::setup(); + + let GenesisConfigInfo { + genesis_config, + mint_keypair, + .. + } = create_genesis_config(50); + + for apply_cost_tracker in [false, true] { + let mut bank = Bank::new_for_tests(&genesis_config); + let feature_set = Arc::make_mut(&mut bank.feature_set); + // by default test banks have all features enabled, so we only need to + // disable when needed + if !apply_cost_tracker { + feature_set.deactivate(&feature_set::apply_cost_tracker_during_replay::id()); + } + let (bank, bank_forks) = bank.wrap_with_bank_forks_for_tests(); + let mut bank_client = BankClient::new_shared(bank.clone()); + let authority_keypair = Keypair::new(); + let (bank, program_id) = load_upgradeable_program_and_advance_slot( + &mut bank_client, + bank_forks.as_ref(), + &mint_keypair, + &authority_keypair, + "solana_sbf_rust_divide_by_zero", + ); + + let instruction = Instruction::new_with_bytes(program_id, &[], vec![]); + let compute_unit_limit = 10_000; + let message = Message::new( + &[ + ComputeBudgetInstruction::set_compute_unit_limit(compute_unit_limit), + instruction, + ], + Some(&mint_keypair.pubkey()), + ); + let tx = Transaction::new(&[&mint_keypair], message, bank.last_blockhash()); + + let result = load_execute_and_commit_transaction(&bank, tx).unwrap(); + + assert_eq!( + result.status.unwrap_err(), + TransactionError::InstructionError(1, InstructionError::ProgramFailedToComplete) + ); + + if apply_cost_tracker { + assert_eq!(result.executed_units, u64::from(compute_unit_limit)); + } else { + assert!(result.executed_units < u64::from(compute_unit_limit)); + } + } +} + #[test] #[cfg(feature = "sbf_rust")] fn test_deny_executable_write() { @@ -4860,6 +5035,312 @@ fn test_update_callee_account() { } #[test] +<<<<<<< HEAD +======= +fn test_account_info_in_account() { + solana_logger::setup(); + + let GenesisConfigInfo { + genesis_config, + mint_keypair, + .. + } = create_genesis_config(100_123_456_789); + + let mut programs = Vec::new(); + #[cfg(feature = "sbf_c")] + { + programs.push("invoke"); + } + #[cfg(feature = "sbf_rust")] + { + programs.push("solana_sbf_rust_invoke"); + } + + for program in programs { + for direct_mapping in [false, true] { + let mut bank = Bank::new_for_tests(&genesis_config); + let feature_set = Arc::make_mut(&mut bank.feature_set); + // by default test banks have all features enabled, so we only need to + // disable when needed + if !direct_mapping { + feature_set.deactivate(&feature_set::bpf_account_data_direct_mapping::id()); + } + + let (bank, bank_forks) = bank.wrap_with_bank_forks_for_tests(); + let mut bank_client = BankClient::new_shared(bank.clone()); + let authority_keypair = Keypair::new(); + + let (bank, invoke_program_id) = load_upgradeable_program_and_advance_slot( + &mut bank_client, + bank_forks.as_ref(), + &mint_keypair, + &authority_keypair, + program, + ); + + let account_keypair = Keypair::new(); + + let mint_pubkey = mint_keypair.pubkey(); + + let account_metas = vec![ + AccountMeta::new(mint_pubkey, true), + AccountMeta::new(account_keypair.pubkey(), false), + AccountMeta::new_readonly(invoke_program_id, false), + ]; + + let mut instruction_data = vec![TEST_ACCOUNT_INFO_IN_ACCOUNT]; + instruction_data.extend_from_slice(32usize.to_le_bytes().as_ref()); + + let instruction = + Instruction::new_with_bytes(invoke_program_id, &instruction_data, account_metas); + + let account = AccountSharedData::new(42, 10240, &invoke_program_id); + + bank.store_account(&account_keypair.pubkey(), &account); + + let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); + if direct_mapping { + assert!(result.is_err()); + } else { + assert!(result.is_ok()); + } + } + } +} + +#[test] +fn test_account_info_rc_in_account() { + solana_logger::setup(); + + let GenesisConfigInfo { + genesis_config, + mint_keypair, + .. + } = create_genesis_config(100_123_456_789); + + for direct_mapping in [false, true] { + let mut bank = Bank::new_for_tests(&genesis_config); + let feature_set = Arc::make_mut(&mut bank.feature_set); + // by default test banks have all features enabled, so we only need to + // disable when needed + if !direct_mapping { + feature_set.deactivate(&feature_set::bpf_account_data_direct_mapping::id()); + } + + let (bank, bank_forks) = bank.wrap_with_bank_forks_for_tests(); + let mut bank_client = BankClient::new_shared(bank.clone()); + let authority_keypair = Keypair::new(); + + let (bank, invoke_program_id) = load_upgradeable_program_and_advance_slot( + &mut bank_client, + bank_forks.as_ref(), + &mint_keypair, + &authority_keypair, + "solana_sbf_rust_invoke", + ); + + let account_keypair = Keypair::new(); + + let mint_pubkey = mint_keypair.pubkey(); + + let account_metas = vec![ + AccountMeta::new(mint_pubkey, true), + AccountMeta::new(account_keypair.pubkey(), false), + AccountMeta::new_readonly(invoke_program_id, false), + ]; + + let instruction_data = vec![TEST_ACCOUNT_INFO_LAMPORTS_RC, 0, 0, 0]; + + let instruction = Instruction::new_with_bytes( + invoke_program_id, + &instruction_data, + account_metas.clone(), + ); + + let account = AccountSharedData::new(42, 10240, &invoke_program_id); + + bank.store_account(&account_keypair.pubkey(), &account); + + let message = Message::new(&[instruction], Some(&mint_pubkey)); + let tx = Transaction::new(&[&mint_keypair], message.clone(), bank.last_blockhash()); + let (result, _, logs, _) = process_transaction_and_record_inner(&bank, tx); + + if direct_mapping { + assert!( + logs.last().unwrap().ends_with(" failed: Invalid pointer"), + "{logs:?}" + ); + assert!(result.is_err()); + } else { + assert!(result.is_ok(), "{logs:?}"); + } + + let instruction_data = vec![TEST_ACCOUNT_INFO_DATA_RC, 0, 0, 0]; + + let instruction = + Instruction::new_with_bytes(invoke_program_id, &instruction_data, account_metas); + + let account = AccountSharedData::new(42, 10240, &invoke_program_id); + + bank.store_account(&account_keypair.pubkey(), &account); + + let message = Message::new(&[instruction], Some(&mint_pubkey)); + let tx = Transaction::new(&[&mint_keypair], message.clone(), bank.last_blockhash()); + let (result, _, logs, _) = process_transaction_and_record_inner(&bank, tx); + + if direct_mapping { + assert!( + logs.last().unwrap().ends_with(" failed: Invalid pointer"), + "{logs:?}" + ); + assert!(result.is_err()); + } else { + assert!(result.is_ok(), "{logs:?}"); + } + } +} + +#[test] +fn test_clone_account_data() { + // Test cloning account data works as expect with + solana_logger::setup(); + + let GenesisConfigInfo { + genesis_config, + mint_keypair, + .. + } = create_genesis_config(100_123_456_789); + + let mut bank = Bank::new_for_tests(&genesis_config); + let feature_set = Arc::make_mut(&mut bank.feature_set); + + feature_set.deactivate(&feature_set::bpf_account_data_direct_mapping::id()); + + let (bank, bank_forks) = bank.wrap_with_bank_forks_for_tests(); + let mut bank_client = BankClient::new_shared(bank.clone()); + let authority_keypair = Keypair::new(); + + let (_, invoke_program_id) = load_upgradeable_program_and_advance_slot( + &mut bank_client, + bank_forks.as_ref(), + &mint_keypair, + &authority_keypair, + "solana_sbf_rust_invoke", + ); + + let (bank, invoke_program_id2) = load_upgradeable_program_and_advance_slot( + &mut bank_client, + bank_forks.as_ref(), + &mint_keypair, + &authority_keypair, + "solana_sbf_rust_invoke", + ); + + assert_ne!(invoke_program_id, invoke_program_id2); + + println!("invoke_program_id:{invoke_program_id}"); + println!("invoke_program_id2:{invoke_program_id2}"); + + let account_keypair = Keypair::new(); + + let mint_pubkey = mint_keypair.pubkey(); + + let account_metas = vec![ + AccountMeta::new(mint_pubkey, true), + AccountMeta::new(account_keypair.pubkey(), false), + AccountMeta::new_readonly(invoke_program_id2, false), + AccountMeta::new_readonly(invoke_program_id, false), + ]; + + // I. clone data and CPI; modify data in callee. + // Now the original data in the caller is unmodified, and we get a "instruction modified data of an account it does not own" + // error in the caller + let mut account = AccountSharedData::new(42, 10240, &invoke_program_id2); + let data: Vec = (0..10240).map(|n| n as u8).collect(); + account.set_data(data); + + bank.store_account(&account_keypair.pubkey(), &account); + + let mut instruction_data = vec![TEST_CALLEE_ACCOUNT_UPDATES, 1, 1]; + instruction_data.extend_from_slice(0usize.to_le_bytes().as_ref()); + instruction_data.extend_from_slice(0usize.to_le_bytes().as_ref()); + instruction_data.extend_from_slice(0usize.to_le_bytes().as_ref()); + + // instruction data for inner CPI: modify account + instruction_data.extend_from_slice(&[TEST_CALLEE_ACCOUNT_UPDATES, 0, 0]); + instruction_data.extend_from_slice(0usize.to_le_bytes().as_ref()); + instruction_data.extend_from_slice(0usize.to_le_bytes().as_ref()); + instruction_data.extend_from_slice(8190usize.to_le_bytes().as_ref()); + + let instruction = + Instruction::new_with_bytes(invoke_program_id, &instruction_data, account_metas.clone()); + + let message = Message::new(&[instruction], Some(&mint_pubkey)); + let tx = Transaction::new(&[&mint_keypair], message.clone(), bank.last_blockhash()); + let (result, _, logs, _) = process_transaction_and_record_inner(&bank, tx); + assert!(result.is_err(), "{result:?}"); + let error = format!("Program {invoke_program_id} failed: instruction modified data of an account it does not own"); + assert!(logs.iter().any(|log| log.contains(&error)), "{logs:?}"); + + // II. clone data, modify and then CPI + // The deserialize checks should verify that we're not allowed to modify an account we don't own, even though + // we have only modified a copy of the data. Fails in caller + let mut account = AccountSharedData::new(42, 10240, &invoke_program_id2); + let data: Vec = (0..10240).map(|n| n as u8).collect(); + account.set_data(data); + + bank.store_account(&account_keypair.pubkey(), &account); + + let mut instruction_data = vec![TEST_CALLEE_ACCOUNT_UPDATES, 1, 1]; + instruction_data.extend_from_slice(0usize.to_le_bytes().as_ref()); + instruction_data.extend_from_slice(8190usize.to_le_bytes().as_ref()); + instruction_data.extend_from_slice(0usize.to_le_bytes().as_ref()); + + // instruction data for inner CPI + instruction_data.extend_from_slice(&[TEST_CALLEE_ACCOUNT_UPDATES, 0, 0]); + instruction_data.extend_from_slice(0usize.to_le_bytes().as_ref()); + instruction_data.extend_from_slice(0usize.to_le_bytes().as_ref()); + instruction_data.extend_from_slice(0usize.to_le_bytes().as_ref()); + + let instruction = + Instruction::new_with_bytes(invoke_program_id, &instruction_data, account_metas.clone()); + + let message = Message::new(&[instruction], Some(&mint_pubkey)); + let tx = Transaction::new(&[&mint_keypair], message.clone(), bank.last_blockhash()); + let (result, _, logs, _) = process_transaction_and_record_inner(&bank, tx); + assert!(result.is_err(), "{result:?}"); + let error = format!("Program {invoke_program_id} failed: instruction modified data of an account it does not own"); + assert!(logs.iter().any(|log| log.contains(&error)), "{logs:?}"); + + // II. Clone data, call, modifiy in callee and then make the same change in the caller - transaction succeeds + // Note the caller needs to modify the original account data, not the copy + let mut account = AccountSharedData::new(42, 10240, &invoke_program_id2); + let data: Vec = (0..10240).map(|n| n as u8).collect(); + account.set_data(data); + + bank.store_account(&account_keypair.pubkey(), &account); + + let mut instruction_data = vec![TEST_CALLEE_ACCOUNT_UPDATES, 1, 1]; + instruction_data.extend_from_slice(0usize.to_le_bytes().as_ref()); + instruction_data.extend_from_slice(0usize.to_le_bytes().as_ref()); + instruction_data.extend_from_slice(8190usize.to_le_bytes().as_ref()); + + // instruction data for inner CPI + instruction_data.extend_from_slice(&[TEST_CALLEE_ACCOUNT_UPDATES, 0, 0]); + instruction_data.extend_from_slice(0usize.to_le_bytes().as_ref()); + instruction_data.extend_from_slice(0usize.to_le_bytes().as_ref()); + instruction_data.extend_from_slice(8190usize.to_le_bytes().as_ref()); + + let instruction = + Instruction::new_with_bytes(invoke_program_id, &instruction_data, account_metas.clone()); + let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); + + // works because the account is exactly the same in caller as callee + assert!(result.is_ok(), "{result:?}"); +} + +#[test] +>>>>>>> b120fc1388 (feat: deplete compute meter for vm errors (#3751)) fn test_stack_heap_zeroed() { solana_logger::setup(); @@ -4915,7 +5396,7 @@ fn test_stack_heap_zeroed() { Some(&mint_pubkey), ); let tx = Transaction::new(&[&mint_keypair], message.clone(), bank.last_blockhash()); - let (result, _, logs) = process_transaction_and_record_inner(&bank, tx); + let (result, _, logs, _) = process_transaction_and_record_inner(&bank, tx); assert!(result.is_err(), "{result:?}"); assert!( logs.iter() diff --git a/sdk/src/feature_set.rs b/sdk/src/feature_set.rs index ac5dbe6765f41e..79f3e6d703fa48 100644 --- a/sdk/src/feature_set.rs +++ b/sdk/src/feature_set.rs @@ -619,7 +619,11 @@ pub mod delay_visibility_of_program_deployment { } pub mod apply_cost_tracker_during_replay { +<<<<<<< HEAD:sdk/src/feature_set.rs solana_sdk::declare_id!("2ry7ygxiYURULZCrypHhveanvP5tzZ4toRwVp89oCNSj"); +======= + solana_pubkey::declare_id!("B7H2caeia4ZFcpE3QcgMqbiWiBtWrdBRBSJ1DY6Ktxbq"); +>>>>>>> b120fc1388 (feat: deplete compute meter for vm errors (#3751)):sdk/feature-set/src/lib.rs } pub mod bpf_account_data_direct_mapping { solana_sdk::declare_id!("EenyoWx9UMXYKpR8mW5Jmfmy2fRjzUtM7NduYMY8bx33");