1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Limits that are observeable by contract code.
//!
//! It is important to never change this limits without supporting the old limits
//! for already deployed contracts. This is what the [`crate::Contract::behaviour_version`]
//! is meant for. This is true for either increasing or decreasing the limit.
//!
//! Limits in this file are different from the limits configured on the [`Config`] trait which are
//! generally only affect actions that cannot be performed by a contract: For example things related
//! to deposits and weights are allowed to be changed as they are paid by root callers which
//! are not contracts.
//!
//! Exceptions to this rule apply: Limits in the [`code`] module can be increased
//! without emulating the old values for existing contracts. Reason is that those limits are only
//! applied **once** at code upload time. Since this action cannot be performed by contracts we
//! can change those limits without breaking existing contracts. Please keep in mind that we should
//! only ever **increase** those values but never decrease.
/// The maximum depth of the call stack.
///
/// A 0 means that no callings of other contracts are possible. In other words only the origin
/// called "root contract" is allowed to execute then.
pub const CALL_STACK_DEPTH: u32 = 5;
/// The maximum number of topics a call to [`crate::SyscallDoc::deposit_event`] can emit.
///
/// We set it to the same limit that ethereum has. It is unlikely to change.
pub const NUM_EVENT_TOPICS: u32 = 4;
/// The maximum number of code hashes a contract can lock.
pub const DELEGATE_DEPENDENCIES: u32 = 32;
/// Maximum size of events (including topics) and storage values.
pub const PAYLOAD_BYTES: u32 = 448;
/// The maximum size of the transient storage in bytes.
///
/// This includes keys, values, and previous entries used for storage rollback.
pub const TRANSIENT_STORAGE_BYTES: u32 = 4 * 1024;
/// The maximum allowable length in bytes for (transient) storage keys.
pub const STORAGE_KEY_BYTES: u32 = 128;
/// The maximum size of the debug buffer contracts can write messages to.
///
/// The buffer will always be disabled for on-chain execution.
pub const DEBUG_BUFFER_BYTES: u32 = 2 * 1024 * 1024;
/// The page size in which PolkaVM should allocate memory chunks.
pub const PAGE_SIZE: u32 = 4 * 1024;
/// The maximum amount of immutable bytes a single contract can store.
///
/// The current limit of 4kb allows storing up 16 U256 immutable variables.
/// Which should always be enough because Solidity allows for 16 local (stack) variables.
pub const IMMUTABLE_BYTES: u32 = 4 * 1024;
/// Limits that are only enforced on code upload.
///
/// # Note
///
/// This limit can be increased later without breaking existing contracts
/// as it is only enforced at code upload time. Code already uploaded
/// will not be affected by those limits.
pub mod code {
use super::PAGE_SIZE;
use crate::{CodeVec, Config, Error, LOG_TARGET};
use alloc::vec::Vec;
use sp_runtime::DispatchError;
/// The maximum length of a code blob in bytes.
///
/// This mostly exist to prevent parsing too big blobs and to
/// have a maximum encoded length. The actual memory calculation
/// is purely based off [`STATIC_MEMORY_BYTES`].
pub const BLOB_BYTES: u32 = 256 * 1024;
/// Maximum size the program is allowed to take in memory.
///
/// This includes data and code. Increasing this limit will allow
/// for more code or more data. However, since code will decompress
/// into a bigger representation on compilation it will only increase
/// the allowed code size by [`BYTE_PER_INSTRUCTION`].
pub const STATIC_MEMORY_BYTES: u32 = 2 * 1024 * 1024;
/// How much memory each instruction will take in-memory after compilation.
///
/// This is `size_of<usize>() + 16`. But we don't use `usize` here so it isn't
/// different on the native runtime (used for testing).
const BYTES_PER_INSTRUCTION: u32 = 20;
/// The code is stored multiple times as part of the compiled program.
const EXTRA_OVERHEAD_PER_CODE_BYTE: u32 = 4;
/// The maximum size of a basic block in number of instructions.
///
/// We need to limit the size of basic blocks because the interpreters lazy compilation
/// compiles one basic block at a time. A malicious program could trigger the compilation
/// of the whole program by creating one giant basic block otherwise.
const BASIC_BLOCK_SIZE: u32 = 1000;
/// Make sure that the various program parts are within the defined limits.
pub fn enforce<T: Config>(
blob: Vec<u8>,
available_syscalls: &[&[u8]],
) -> Result<CodeVec, DispatchError> {
fn round_page(n: u32) -> u64 {
// performing the rounding in u64 in order to prevent overflow
u64::from(n).next_multiple_of(PAGE_SIZE.into())
}
let blob: CodeVec = blob.try_into().map_err(|_| <Error<T>>::BlobTooLarge)?;
let program = polkavm::ProgramBlob::parse(blob.as_slice().into()).map_err(|err| {
log::debug!(target: LOG_TARGET, "failed to parse polkavm blob: {err:?}");
Error::<T>::CodeRejected
})?;
if !program.is_64_bit() {
log::debug!(target: LOG_TARGET, "32bit programs are not supported.");
Err(Error::<T>::CodeRejected)?;
}
// Need to check that no non-existent syscalls are used. This allows us to add
// new syscalls later without affecting already deployed code.
for (idx, import) in program.imports().iter().enumerate() {
// We are being defensive in case an attacker is able to somehow include
// a lot of imports. This is important because we search the array of host
// functions for every import.
if idx == available_syscalls.len() {
log::debug!(target: LOG_TARGET, "Program contains too many imports.");
Err(Error::<T>::CodeRejected)?;
}
let Some(import) = import else {
log::debug!(target: LOG_TARGET, "Program contains malformed import.");
return Err(Error::<T>::CodeRejected.into());
};
if !available_syscalls.contains(&import.as_bytes()) {
log::debug!(target: LOG_TARGET, "Program references unknown syscall: {}", import);
Err(Error::<T>::CodeRejected)?;
}
}
// This scans the whole program but we only do it once on code deployment.
// It is safe to do unchecked math in u32 because the size of the program
// was already checked above.
use polkavm::program::ISA64_V1 as ISA;
let mut num_instructions: u32 = 0;
let mut max_basic_block_size: u32 = 0;
let mut basic_block_size: u32 = 0;
for inst in program.instructions(ISA) {
use polkavm::program::Instruction;
num_instructions += 1;
basic_block_size += 1;
if inst.kind.opcode().starts_new_basic_block() {
max_basic_block_size = max_basic_block_size.max(basic_block_size);
basic_block_size = 0;
}
match inst.kind {
Instruction::invalid => {
log::debug!(target: LOG_TARGET, "invalid instruction at offset {}", inst.offset);
return Err(<Error<T>>::InvalidInstruction.into())
},
Instruction::sbrk(_, _) => {
log::debug!(target: LOG_TARGET, "sbrk instruction is not allowed. offset {}", inst.offset);
return Err(<Error<T>>::InvalidInstruction.into())
},
_ => (),
}
}
if max_basic_block_size > BASIC_BLOCK_SIZE {
log::debug!(target: LOG_TARGET, "basic block too large: {max_basic_block_size} limit: {BASIC_BLOCK_SIZE}");
return Err(Error::<T>::BasicBlockTooLarge.into())
}
// The memory consumptions is the byte size of the whole blob,
// minus the RO data payload in the blob,
// minus the RW data payload in the blob,
// plus the RO data in memory (which is always equal or bigger than the RO payload),
// plus RW data in memory, plus stack size in memory.
// plus the overhead of instructions in memory which is derived from the code
// size itself and the number of instruction
let memory_size = (blob.len() as u64)
.saturating_add(round_page(program.ro_data_size()))
.saturating_sub(program.ro_data().len() as u64)
.saturating_add(round_page(program.rw_data_size()))
.saturating_sub(program.rw_data().len() as u64)
.saturating_add(round_page(program.stack_size()))
.saturating_add(
u64::from(num_instructions).saturating_mul(BYTES_PER_INSTRUCTION.into()),
)
.saturating_add(
(program.code().len() as u64).saturating_mul(EXTRA_OVERHEAD_PER_CODE_BYTE.into()),
);
if memory_size > STATIC_MEMORY_BYTES.into() {
log::debug!(target: LOG_TARGET, "static memory too large: {memory_size} limit: {STATIC_MEMORY_BYTES}");
return Err(Error::<T>::StaticMemoryTooLarge.into())
}
Ok(blob)
}
}