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.

(cherry picked from commit 0a3a18744f)
This commit is contained in:
Dmitri Makarov
2022-02-20 13:12:15 -08:00
committed by Dmitri Makarov
parent f4f0b64af4
commit 023ab1c427
2 changed files with 89 additions and 8 deletions

View File

@ -36,6 +36,8 @@ pub struct ComputeBudget {
pub sha256_base_cost: u64, pub sha256_base_cost: u64,
/// Incremental number of units consumed by SHA256 (based on bytes) /// Incremental number of units consumed by SHA256 (based on bytes)
pub sha256_byte_cost: u64, pub sha256_byte_cost: u64,
/// Maximum number of slices hashed per syscall
pub sha256_max_slices: u64,
/// Maximum BPF to BPF call depth /// Maximum BPF to BPF call depth
pub max_call_depth: usize, pub max_call_depth: usize,
/// Size of a stack frame in bytes, must match the size specified in the LLVM BPF backend /// Size of a stack frame in bytes, must match the size specified in the LLVM BPF backend
@ -82,6 +84,7 @@ impl ComputeBudget {
max_invoke_depth: 4, max_invoke_depth: 4,
sha256_base_cost: 85, sha256_base_cost: 85,
sha256_byte_cost: 1, sha256_byte_cost: 1,
sha256_max_slices: 20_000,
max_call_depth: 64, max_call_depth: 64,
stack_frame_size: 4_096, stack_frame_size: 4_096,
log_pubkey_units: 100, log_pubkey_units: 100,

View File

@ -89,6 +89,8 @@ pub enum SyscallError {
CopyOverlapping, CopyOverlapping,
#[error("Return data too large ({0} > {1})")] #[error("Return data too large ({0} > {1})")]
ReturnDataTooLarge(u64, u64), ReturnDataTooLarge(u64, u64),
#[error("Hashing too many sequences")]
TooManySlices,
} }
impl From<SyscallError> for EbpfError<BpfError> { impl From<SyscallError> for EbpfError<BpfError> {
fn from(error: SyscallError) -> Self { fn from(error: SyscallError) -> Self {
@ -1039,6 +1041,20 @@ impl<'a, 'b> SyscallObject<BpfError> for SyscallSha256<'a, 'b> {
result result
); );
let compute_budget = invoke_context.get_compute_budget(); 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!( question_mark!(
invoke_context invoke_context
.get_compute_meter() .get_compute_meter()
@ -1072,7 +1088,18 @@ impl<'a, 'b> SyscallObject<BpfError> for SyscallSha256<'a, 'b> {
), ),
result 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); question_mark!(invoke_context.get_compute_meter().consume(cost), result);
hasher.hash(bytes); hasher.hash(bytes);
} }
@ -1268,6 +1295,20 @@ impl<'a, 'b> SyscallObject<BpfError> for SyscallKeccak256<'a, 'b> {
result result
); );
let compute_budget = invoke_context.get_compute_budget(); 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!( question_mark!(
invoke_context invoke_context
.get_compute_meter() .get_compute_meter()
@ -1306,9 +1347,19 @@ impl<'a, 'b> SyscallObject<BpfError> for SyscallKeccak256<'a, 'b> {
), ),
result 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); question_mark!(invoke_context.get_compute_meter().consume(cost), result);
hasher.hash(bytes); hasher.hash(bytes);
} }
} }
@ -1667,6 +1718,20 @@ impl<'a, 'b> SyscallObject<BpfError> for SyscallBlake3<'a, 'b> {
result result
); );
let compute_budget = invoke_context.get_compute_budget(); 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!( question_mark!(
invoke_context invoke_context
.get_compute_meter() .get_compute_meter()
@ -1705,10 +1770,19 @@ impl<'a, 'b> SyscallObject<BpfError> for SyscallBlake3<'a, 'b> {
), ),
result result
); );
let cost = if invoke_context
let cost = compute_budget.sha256_byte_cost * (val.len() as u64 / 2); .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); question_mark!(invoke_context.get_compute_meter().consume(cost), result);
hasher.hash(bytes); hasher.hash(bytes);
} }
} }
@ -3687,8 +3761,12 @@ mod tests {
.borrow_mut() .borrow_mut()
.mock_set_remaining( .mock_set_remaining(
(invoke_context.get_compute_budget().sha256_base_cost (invoke_context.get_compute_budget().sha256_base_cost
+ ((bytes1.len() + bytes2.len()) as u64 / 2) + invoke_context.get_compute_budget().mem_op_base_cost.max(
* invoke_context.get_compute_budget().sha256_byte_cost) invoke_context
.get_compute_budget()
.sha256_byte_cost
.saturating_mul((bytes1.len() + bytes2.len()) as u64 / 2),
))
* 4, * 4,
); );
invoke_context invoke_context