diff --git a/Cargo.lock b/Cargo.lock index 247919e30c..34e7c42062 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4790,9 +4790,9 @@ dependencies = [ [[package]] name = "solana_rbpf" -version = "0.1.31" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "962f8f04ac7239fe4dd45fa4ce706ec78b59a0da9f41def463832857e36c60b0" +checksum = "9a95dbe2b00920ac4e1524b7442cf5319f01e8fa5742930ac60148882fd7738b" dependencies = [ "byteorder", "combine", diff --git a/programs/bpf/Cargo.lock b/programs/bpf/Cargo.lock index 39e7af8d2a..7571a23a37 100644 --- a/programs/bpf/Cargo.lock +++ b/programs/bpf/Cargo.lock @@ -1798,6 +1798,13 @@ dependencies = [ "solana-sdk", ] +[[package]] +name = "solana-bpf-rust-call-depth" +version = "1.4.0" +dependencies = [ + "solana-sdk", +] + [[package]] name = "solana-bpf-rust-custom-heap" version = "1.5.0" @@ -2191,9 +2198,9 @@ dependencies = [ [[package]] name = "solana_rbpf" -version = "0.1.31" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "962f8f04ac7239fe4dd45fa4ce706ec78b59a0da9f41def463832857e36c60b0" +checksum = "9a95dbe2b00920ac4e1524b7442cf5319f01e8fa5742930ac60148882fd7738b" dependencies = [ "byteorder 1.3.4", "combine", diff --git a/programs/bpf/Cargo.toml b/programs/bpf/Cargo.toml index 5eeef4f7dd..0cd6cab8a3 100644 --- a/programs/bpf/Cargo.toml +++ b/programs/bpf/Cargo.toml @@ -27,7 +27,7 @@ solana-logger = { path = "../../logger", version = "1.5.0" } solana-measure = { path = "../../measure", version = "1.5.0" } solana-runtime = { path = "../../runtime", version = "1.5.0" } solana-sdk = { path = "../../sdk", version = "1.5.0" } -solana_rbpf = "=0.1.31" +solana_rbpf = "=0.1.32" [[bench]] name = "bpf_loader" @@ -57,6 +57,7 @@ members = [ "rust/ristretto", "rust/sanity", "rust/sha256", + "rust/call_depth", "rust/sysval", ] diff --git a/programs/bpf/build.rs b/programs/bpf/build.rs index 7a324bc19e..12a4e59c2d 100644 --- a/programs/bpf/build.rs +++ b/programs/bpf/build.rs @@ -85,6 +85,7 @@ fn main() { "ristretto", "sanity", "sha256", + "call_depth", "sysval", ]; for program in rust_programs.iter() { diff --git a/programs/bpf/rust/call_depth/Cargo.toml b/programs/bpf/rust/call_depth/Cargo.toml new file mode 100644 index 0000000000..cd7ddd3dff --- /dev/null +++ b/programs/bpf/rust/call_depth/Cargo.toml @@ -0,0 +1,26 @@ + +# Note: This crate must be built using do.sh + +[package] +name = "solana-bpf-rust-call-depth" +version = "1.4.0" +description = "Solana BPF test program written in Rust" +authors = ["Solana Maintainers "] +repository = "https://github.com/solana-labs/solana" +license = "Apache-2.0" +homepage = "https://solana.com/" +edition = "2018" + +[dependencies] +solana-sdk = { path = "../../../../sdk/", version = "1.4.0", default-features = false } + +[features] +program = ["solana-sdk/program"] +default = ["program", "solana-sdk/default"] + +[lib] +name = "solana_bpf_rust_call_depth" +crate-type = ["cdylib"] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/programs/bpf/rust/call_depth/Xargo.toml b/programs/bpf/rust/call_depth/Xargo.toml new file mode 100644 index 0000000000..1744f098ae --- /dev/null +++ b/programs/bpf/rust/call_depth/Xargo.toml @@ -0,0 +1,2 @@ +[target.bpfel-unknown-unknown.dependencies.std] +features = [] \ No newline at end of file diff --git a/programs/bpf/rust/call_depth/src/lib.rs b/programs/bpf/rust/call_depth/src/lib.rs new file mode 100644 index 0000000000..a2a33e1d34 --- /dev/null +++ b/programs/bpf/rust/call_depth/src/lib.rs @@ -0,0 +1,27 @@ +//! @brief Example Rust-based BPF program that tests call depth and stack usage + +use solana_sdk::{entrypoint::SUCCESS, info}; + +#[inline(never)] +pub fn recurse(data: &mut [u8]) { + if data.len() <= 1 { + return; + } + recurse(&mut data[1..]); + info!(line!(), 0, 0, 0, data[0]); +} + +/// # Safety +#[inline(never)] +#[no_mangle] +pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u64 { + info!("Call depth"); + let depth = *(input.add(16) as *mut u8); + info!(line!(), 0, 0, 0, depth); + let mut data = Vec::with_capacity(depth as usize); + for i in 0_u8..depth { + data.push(i); + } + recurse(&mut data); + SUCCESS +} diff --git a/programs/bpf/tests/programs.rs b/programs/bpf/tests/programs.rs index fee5809cba..709cde5465 100644 --- a/programs/bpf/tests/programs.rs +++ b/programs/bpf/tests/programs.rs @@ -643,6 +643,43 @@ fn test_program_bpf_invoke() { } } +#[cfg(feature = "bpf_rust")] +#[test] +fn test_program_bpf_call_depth() { + solana_logger::setup(); + + println!("Test program: solana_bpf_rust_call_depth"); + + let GenesisConfigInfo { + genesis_config, + mint_keypair, + .. + } = create_genesis_config(50); + let mut bank = Bank::new(&genesis_config); + let (name, id, entrypoint) = solana_bpf_loader_program!(); + bank.add_builtin_loader(&name, id, entrypoint); + let bank_client = BankClient::new(bank); + let program_id = load_bpf_program( + &bank_client, + &bpf_loader::id(), + &mint_keypair, + "solana_bpf_rust_call_depth", + ); + + let instruction = Instruction::new( + program_id, + &(ComputeBudget::default().max_call_depth - 1), + vec![], + ); + let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); + assert!(result.is_ok()); + + let instruction = + Instruction::new(program_id, &ComputeBudget::default().max_call_depth, vec![]); + let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); + assert!(result.is_err()); +} + #[test] fn assert_instruction_count() { solana_logger::setup(); diff --git a/programs/bpf_loader/Cargo.toml b/programs/bpf_loader/Cargo.toml index d4506c2ed0..0d724b06e0 100644 --- a/programs/bpf_loader/Cargo.toml +++ b/programs/bpf_loader/Cargo.toml @@ -16,7 +16,7 @@ num-derive = "0.3" num-traits = "0.2" solana-runtime = { path = "../../runtime", version = "1.5.0" } solana-sdk = { path = "../../sdk", version = "1.5.0" } -solana_rbpf = "=0.1.31" +solana_rbpf = "=0.1.32" thiserror = "1.0" [dev-dependencies] diff --git a/programs/bpf_loader/src/lib.rs b/programs/bpf_loader/src/lib.rs index d7949b7157..a5aa6850d5 100644 --- a/programs/bpf_loader/src/lib.rs +++ b/programs/bpf_loader/src/lib.rs @@ -14,7 +14,7 @@ use num_derive::{FromPrimitive, ToPrimitive}; use solana_rbpf::{ error::{EbpfError, UserDefinedError}, memory_region::MemoryRegion, - vm::{EbpfVm, Executable, InstructionMeter}, + vm::{Config, EbpfVm, Executable, InstructionMeter}, }; use solana_runtime::{ feature_set::compute_budget_balancing, @@ -116,7 +116,14 @@ pub fn create_vm<'a>( parameter_accounts: &'a [KeyedAccount<'a>], invoke_context: &'a mut dyn InvokeContext, ) -> Result<(EbpfVm<'a, BPFError>, MemoryRegion), EbpfError> { - let mut vm = EbpfVm::new(executable)?; + let compute_budget = invoke_context.get_compute_budget(); + let mut vm = EbpfVm::new( + executable, + Config { + max_call_depth: compute_budget.max_call_depth, + stack_frame_size: compute_budget.stack_frame_size, + }, + )?; let heap_region = syscalls::register_syscalls(loader_id, &mut vm, parameter_accounts, invoke_context)?; Ok((vm, heap_region)) @@ -404,7 +411,7 @@ mod tests { let input = &mut [0x00]; let executable = EbpfVm::create_executable_from_text_bytes(program, None).unwrap(); - let mut vm = EbpfVm::::new(executable.as_ref()).unwrap(); + let mut vm = EbpfVm::::new(executable.as_ref(), Config::default()).unwrap(); let instruction_meter = TestInstructionMeter { remaining: 10 }; vm.execute_program_metered(input, &[], &[], instruction_meter) .unwrap(); @@ -603,6 +610,8 @@ mod tests { max_invoke_depth: 2, sha256_base_cost: 85, sha256_byte_cost: 1, + max_call_depth: 20, + stack_frame_size: 4096, }, Rc::new(RefCell::new(Executors::default())), None, diff --git a/runtime/src/feature_set.rs b/runtime/src/feature_set.rs index 6179025333..e7feb5d909 100644 --- a/runtime/src/feature_set.rs +++ b/runtime/src/feature_set.rs @@ -53,6 +53,10 @@ pub mod max_invoke_depth_4 { solana_sdk::declare_id!("EdM9xggY5y7AhNMskRG8NgGMnaP4JFNsWi8ZZtyT1af5"); } +pub mod max_program_call_depth_64 { + solana_sdk::declare_id!("YCKSgA6XmjtkQrHBQjpyNrX6EMhJPcYcLWMVgWn36iv"); +} + lazy_static! { /// Map of feature identifiers to user-visible description pub static ref FEATURE_NAMES: HashMap = [ @@ -68,6 +72,7 @@ lazy_static! { (no_overflow_rent_distribution::id(), "no overflow rent distribution"), (ristretto_mul_syscall_enabled::id(), "ristretto multiply syscall"), (max_invoke_depth_4::id(), "max invoke call depth 4"), + (max_program_call_depth_64::id(), "max program call depth 64") /*************** ADD NEW FEATURES HERE ***************/ ] .iter() diff --git a/runtime/src/process_instruction.rs b/runtime/src/process_instruction.rs index 2eec69ac3a..d39aeb14bf 100644 --- a/runtime/src/process_instruction.rs +++ b/runtime/src/process_instruction.rs @@ -1,4 +1,6 @@ -use crate::feature_set::{compute_budget_balancing, max_invoke_depth_4, FeatureSet}; +use crate::feature_set::{ + compute_budget_balancing, max_invoke_depth_4, max_program_call_depth_64, FeatureSet, +}; use solana_sdk::{ account::{Account, KeyedAccount}, instruction::{CompiledInstruction, Instruction, InstructionError}, @@ -92,6 +94,10 @@ pub struct ComputeBudget { pub sha256_base_cost: u64, /// Incremental number of units consumed by sha256 (based on bytes) pub sha256_byte_cost: u64, + /// Maximum BPF to BPF call depth + pub max_call_depth: usize, + /// Size of a stack frame in bytes, must match the size specified in the LLVM BPF backend + pub stack_frame_size: usize, } impl Default for ComputeBudget { fn default() -> Self { @@ -111,6 +117,8 @@ impl ComputeBudget { max_invoke_depth: 1, sha256_base_cost: 85, sha256_byte_cost: 1, + max_call_depth: 20, + stack_frame_size: 4_096, }; if feature_set.is_active(&compute_budget_balancing::id()) { @@ -127,9 +135,15 @@ impl ComputeBudget { compute_budget = ComputeBudget { max_invoke_depth: 4, ..compute_budget - } + }; } + if feature_set.is_active(&max_program_call_depth_64::id()) { + compute_budget = ComputeBudget { + max_call_depth: 64, + ..compute_budget + }; + } compute_budget } }