Update the consumed compute units cost for hashing syscalls

This change prevents zero-cost computation of hash functions on
unbound number of zero-length slices of data.  The cost for each slice
is at least equal to the base cost of a memory operation, but could be
more for longer slices.
This commit is contained in:
Dmitri Makarov 2022-02-20 13:12:15 -08:00 committed by mergify[bot]
parent 09d064c090
commit 0a3a18744f
3 changed files with 91 additions and 10 deletions

View File

@ -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,

View File

@ -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),
]);
}

View File

@ -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<SyscallError> for EbpfError<BpfError> {
fn from(error: SyscallError) -> Self {
@ -1064,6 +1066,20 @@ impl<'a, 'b> SyscallObject<BpfError> 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<BpfError> 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<BpfError> 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<BpfError> 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<BpfError> 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<BpfError> 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 {