diff --git a/program-runtime/src/compute_budget.rs b/program-runtime/src/compute_budget.rs index cba062f2e6..4edb658c5a 100644 --- a/program-runtime/src/compute_budget.rs +++ b/program-runtime/src/compute_budget.rs @@ -36,6 +36,8 @@ pub struct ComputeBudget { pub sha256_base_cost: u64, /// Incremental number of units consumed by SHA256 (based on bytes) pub sha256_byte_cost: u64, + /// Maximum number of slices hashed per syscall + pub sha256_max_slices: 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 @@ -84,6 +86,7 @@ impl ComputeBudget { max_invoke_depth: 4, sha256_base_cost: 85, sha256_byte_cost: 1, + sha256_max_slices: 20_000, max_call_depth: 64, stack_frame_size: 4_096, log_pubkey_units: 100, diff --git a/programs/bpf/tests/programs.rs b/programs/bpf/tests/programs.rs index 8b6094a55d..67eb7c2589 100644 --- a/programs/bpf/tests/programs.rs +++ b/programs/bpf/tests/programs.rs @@ -1419,7 +1419,7 @@ fn assert_instruction_count() { ("sanity", 2378), ("sanity++", 2278), ("secp256k1_recover", 25383), - ("sha", 1328), + ("sha", 1895), ("struct_pass", 108), ("struct_ret", 122), ]); @@ -1441,7 +1441,7 @@ fn assert_instruction_count() { ("solana_bpf_rust_rand", 418), ("solana_bpf_rust_sanity", 9128), ("solana_bpf_rust_secp256k1_recover", 25707), - ("solana_bpf_rust_sha", 26467), + ("solana_bpf_rust_sha", 27033), ]); } diff --git a/programs/bpf_loader/src/syscalls.rs b/programs/bpf_loader/src/syscalls.rs index 7f584fdc35..47d8b91b1b 100644 --- a/programs/bpf_loader/src/syscalls.rs +++ b/programs/bpf_loader/src/syscalls.rs @@ -90,6 +90,8 @@ pub enum SyscallError { CopyOverlapping, #[error("Return data too large ({0} > {1})")] ReturnDataTooLarge(u64, u64), + #[error("Hashing too many sequences")] + TooManySlices, } impl From for EbpfError { fn from(error: SyscallError) -> Self { @@ -1064,6 +1066,20 @@ impl<'a, 'b> SyscallObject for SyscallSha256<'a, 'b> { result ); let compute_budget = invoke_context.get_compute_budget(); + if invoke_context + .feature_set + .is_active(&update_syscall_base_costs::id()) + && compute_budget.sha256_max_slices < vals_len + { + ic_msg!( + invoke_context, + "Sha256 hashing {} sequences in one syscall is over the limit {}", + vals_len, + compute_budget.sha256_max_slices, + ); + *result = Err(SyscallError::TooManySlices.into()); + return; + } question_mark!( invoke_context .get_compute_meter() @@ -1092,7 +1108,18 @@ impl<'a, 'b> SyscallObject for SyscallSha256<'a, 'b> { ), result ); - let cost = compute_budget.sha256_byte_cost * (val.len() as u64 / 2); + let cost = if invoke_context + .feature_set + .is_active(&update_syscall_base_costs::id()) + { + compute_budget.mem_op_base_cost.max( + compute_budget + .sha256_byte_cost + .saturating_mul(val.len() as u64 / 2), + ) + } else { + compute_budget.sha256_byte_cost * (val.len() as u64 / 2) + }; question_mark!(invoke_context.get_compute_meter().consume(cost), result); hasher.hash(bytes); } @@ -1268,6 +1295,20 @@ impl<'a, 'b> SyscallObject for SyscallKeccak256<'a, 'b> { result ); let compute_budget = invoke_context.get_compute_budget(); + if invoke_context + .feature_set + .is_active(&update_syscall_base_costs::id()) + && compute_budget.sha256_max_slices < vals_len + { + ic_msg!( + invoke_context, + "Keccak256 hashing {} sequences in one syscall is over the limit {}", + vals_len, + compute_budget.sha256_max_slices, + ); + *result = Err(SyscallError::TooManySlices.into()); + return; + } question_mark!( invoke_context .get_compute_meter() @@ -1301,9 +1342,19 @@ impl<'a, 'b> SyscallObject for SyscallKeccak256<'a, 'b> { ), result ); - let cost = compute_budget.sha256_byte_cost * (val.len() as u64 / 2); + let cost = if invoke_context + .feature_set + .is_active(&update_syscall_base_costs::id()) + { + compute_budget.mem_op_base_cost.max( + compute_budget + .sha256_byte_cost + .saturating_mul(val.len() as u64 / 2), + ) + } else { + compute_budget.sha256_byte_cost * (val.len() as u64 / 2) + }; question_mark!(invoke_context.get_compute_meter().consume(cost), result); - hasher.hash(bytes); } } @@ -1819,6 +1870,20 @@ impl<'a, 'b> SyscallObject for SyscallBlake3<'a, 'b> { result ); let compute_budget = invoke_context.get_compute_budget(); + if invoke_context + .feature_set + .is_active(&update_syscall_base_costs::id()) + && compute_budget.sha256_max_slices < vals_len + { + ic_msg!( + invoke_context, + "Blake3 hashing {} sequences in one syscall is over the limit {}", + vals_len, + compute_budget.sha256_max_slices, + ); + *result = Err(SyscallError::TooManySlices.into()); + return; + } question_mark!( invoke_context .get_compute_meter() @@ -1852,10 +1917,19 @@ impl<'a, 'b> SyscallObject for SyscallBlake3<'a, 'b> { ), result ); - - let cost = compute_budget.sha256_byte_cost * (val.len() as u64 / 2); + let cost = if invoke_context + .feature_set + .is_active(&update_syscall_base_costs::id()) + { + compute_budget.mem_op_base_cost.max( + compute_budget + .sha256_byte_cost + .saturating_mul(val.len() as u64 / 2), + ) + } else { + compute_budget.sha256_byte_cost * (val.len() as u64 / 2) + }; question_mark!(invoke_context.get_compute_meter().consume(cost), result); - hasher.hash(bytes); } } @@ -3856,8 +3930,12 @@ mod tests { .borrow_mut() .mock_set_remaining( (invoke_context.get_compute_budget().sha256_base_cost - + ((bytes1.len() + bytes2.len()) as u64 / 2) - * invoke_context.get_compute_budget().sha256_byte_cost) + + invoke_context.get_compute_budget().mem_op_base_cost.max( + invoke_context + .get_compute_budget() + .sha256_byte_cost + .saturating_mul((bytes1.len() + bytes2.len()) as u64 / 2), + )) * 4, ); let mut syscall = SyscallSha256 {