referrerpolicy=no-referrer-when-downgrade

pallet_revive/
limits.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! Limits that are observeable by contract code.
19//!
20//! It is important to never change this limits without supporting the old limits
21//! for already deployed contracts. This is what the [`crate::Contract::behaviour_version`]
22//! is meant for. This is true for either increasing or decreasing the limit.
23//!
24//! Limits in this file are different from the limits configured on the [`Config`] trait which are
25//! generally only affect actions that cannot be performed by a contract: For example things related
26//! to deposits and weights are allowed to be changed as they are paid by root callers which
27//! are not contracts.
28//!
29//! Exceptions to this rule apply: Limits in the [`code`] module can be increased
30//! without emulating the old values for existing contracts. Reason is that those limits are only
31//! applied **once** at code upload time. Since this action cannot be performed by contracts we
32//! can change those limits without breaking existing contracts. Please keep in mind that we should
33//! only ever **increase** those values but never decrease.
34
35/// The amount of total memory we require to safely operate.
36///
37/// This is not a config knob but derived from the limits in this file.
38pub const MEMORY_REQUIRED: u32 = memory_required();
39
40/// The maximum depth of the call stack.
41///
42/// A 0 means that no callings of other contracts are possible. In other words only the origin
43/// called "root contract" is allowed to execute then.
44pub const CALL_STACK_DEPTH: u32 = 25;
45
46/// The maximum number of topics a call to [`crate::SyscallDoc::deposit_event`] can emit.
47///
48/// We set it to the same limit that ethereum has. It is unlikely to change.
49pub const NUM_EVENT_TOPICS: u32 = 4;
50
51/// Maximum size of events (including topics) and storage values.
52pub const PAYLOAD_BYTES: u32 = 416;
53
54/// The maximum size for calldata and return data.
55///
56/// Please note that the calldata is limited to 128KB on geth anyways.
57pub const CALLDATA_BYTES: u32 = 128 * 1024;
58
59/// The maximum size of the transient storage in bytes.
60///
61/// This includes keys, values, and previous entries used for storage rollback.
62pub const TRANSIENT_STORAGE_BYTES: u32 = 4 * 1024;
63
64/// The maximum allowable length in bytes for (transient) storage keys.
65pub const STORAGE_KEY_BYTES: u32 = 128;
66
67/// The page size in which PolkaVM should allocate memory chunks.
68pub const PAGE_SIZE: u32 = 4 * 1024;
69
70/// The maximum amount of immutable bytes a single contract can store.
71///
72/// The current limit of 4kb allows storing up 16 U256 immutable variables.
73/// Which should always be enough because Solidity allows for 16 local (stack) variables.
74pub const IMMUTABLE_BYTES: u32 = 4 * 1024;
75
76/// Limits that are only enforced on code upload.
77///
78/// # Note
79///
80/// This limit can be increased later without breaking existing contracts
81/// as it is only enforced at code upload time. Code already uploaded
82/// will not be affected by those limits.
83pub mod code {
84	use super::PAGE_SIZE;
85	use crate::{Config, Error, LOG_TARGET};
86	use alloc::vec::Vec;
87	use sp_runtime::DispatchError;
88
89	/// The maximum length of a code blob in bytes.
90	///
91	/// This mostly exist to prevent parsing too big blobs and to
92	/// have a maximum encoded length.
93	pub const BLOB_BYTES: u32 = 1024 * 1024;
94
95	/// The maximum amount of memory the interpreter is allowed to use for compilation artifacts.
96	pub const INTERPRETER_CACHE_BYTES: u32 = 1024 * 1024;
97
98	/// The maximum size of a basic block in number of instructions.
99	///
100	/// We need to limit the size of basic blocks because the interpreters lazy compilation
101	/// compiles one basic block at a time. A malicious program could trigger the compilation
102	/// of the whole program by creating one giant basic block otherwise.
103	pub const BASIC_BLOCK_SIZE: u32 = 1000;
104
105	/// The limit for memory that can be purged on demand.
106	///
107	/// We purge this memory every time we call into another contract.
108	/// Hence we effectively only need to hold it once in RAM.
109	pub const PURGABLE_MEMORY_LIMIT: u32 = INTERPRETER_CACHE_BYTES + 2 * 1024 * 1024;
110
111	/// The limit for memory that needs to be kept alive for a contracts whole life time.
112	///
113	/// This means tuning this number affects the call stack depth.
114	pub const BASELINE_MEMORY_LIMIT: u32 = BLOB_BYTES + 512 * 1024;
115
116	/// Make sure that the various program parts are within the defined limits.
117	pub fn enforce<T: Config>(
118		pvm_blob: Vec<u8>,
119		available_syscalls: &[&[u8]],
120	) -> Result<Vec<u8>, DispatchError> {
121		use polkavm::program::ISA64_V1 as ISA;
122		use polkavm_common::program::EstimateInterpreterMemoryUsageArgs;
123
124		let len: u64 = pvm_blob.len() as u64;
125		if len > crate::limits::code::BLOB_BYTES.into() {
126			log::debug!(target: LOG_TARGET, "contract blob too large: {len} limit: {BLOB_BYTES}");
127			return Err(<Error<T>>::BlobTooLarge.into())
128		}
129
130		#[cfg(feature = "std")]
131		if std::env::var_os("REVIVE_SKIP_VALIDATION").is_some() {
132			log::warn!(target: LOG_TARGET, "Skipping validation because env var REVIVE_SKIP_VALIDATION is set");
133			return Ok(pvm_blob)
134		}
135
136		let program = polkavm::ProgramBlob::parse(pvm_blob.as_slice().into()).map_err(|err| {
137			log::debug!(target: LOG_TARGET, "failed to parse polkavm blob: {err:?}");
138			Error::<T>::CodeRejected
139		})?;
140
141		if !program.is_64_bit() {
142			log::debug!(target: LOG_TARGET, "32bit programs are not supported.");
143			Err(Error::<T>::CodeRejected)?;
144		}
145
146		// Need to check that no non-existent syscalls are used. This allows us to add
147		// new syscalls later without affecting already deployed code.
148		for (idx, import) in program.imports().iter().enumerate() {
149			// We are being defensive in case an attacker is able to somehow include
150			// a lot of imports. This is important because we search the array of host
151			// functions for every import.
152			if idx == available_syscalls.len() {
153				log::debug!(target: LOG_TARGET, "Program contains too many imports.");
154				Err(Error::<T>::CodeRejected)?;
155			}
156			let Some(import) = import else {
157				log::debug!(target: LOG_TARGET, "Program contains malformed import.");
158				return Err(Error::<T>::CodeRejected.into());
159			};
160			if !available_syscalls.contains(&import.as_bytes()) {
161				log::debug!(target: LOG_TARGET, "Program references unknown syscall: {}", import);
162				Err(Error::<T>::CodeRejected)?;
163			}
164		}
165
166		// This scans the whole program but we only do it once on code deployment.
167		// It is safe to do unchecked math in u32 because the size of the program
168		// was already checked above.
169		let mut max_block_size: u32 = 0;
170		let mut block_size: u32 = 0;
171		let mut basic_block_count: u32 = 0;
172		let mut instruction_count: u32 = 0;
173		for inst in program.instructions(ISA) {
174			use polkavm::program::Instruction;
175			block_size += 1;
176			instruction_count += 1;
177			if inst.kind.opcode().starts_new_basic_block() {
178				max_block_size = max_block_size.max(block_size);
179				block_size = 0;
180				basic_block_count += 1;
181			}
182			match inst.kind {
183				Instruction::invalid => {
184					log::debug!(target: LOG_TARGET, "invalid instruction at offset {}", inst.offset);
185					return Err(<Error<T>>::InvalidInstruction.into())
186				},
187				Instruction::sbrk(_, _) => {
188					log::debug!(target: LOG_TARGET, "sbrk instruction is not allowed. offset {}", inst.offset);
189					return Err(<Error<T>>::InvalidInstruction.into())
190				},
191				// Only benchmarking code is allowed to circumvent the import table. We might want
192				// to remove this magic syscall number later. Hence we need to prevent contracts
193				// from using it.
194				#[cfg(not(feature = "runtime-benchmarks"))]
195				Instruction::ecalli(idx) if idx == crate::SENTINEL => {
196					log::debug!(target: LOG_TARGET, "reserved syscall idx {idx}. offset {}", inst.offset);
197					return Err(<Error<T>>::InvalidInstruction.into())
198				},
199				_ => (),
200			}
201		}
202		max_block_size = max_block_size.max(block_size);
203
204		if max_block_size > BASIC_BLOCK_SIZE {
205			log::debug!(target: LOG_TARGET, "basic block too large: {max_block_size} limit: {BASIC_BLOCK_SIZE}");
206			return Err(Error::<T>::BasicBlockTooLarge.into())
207		}
208
209		let usage_args = EstimateInterpreterMemoryUsageArgs::BoundedCache {
210			max_cache_size_bytes: INTERPRETER_CACHE_BYTES,
211			instruction_count,
212			max_block_size,
213			basic_block_count,
214			page_size: PAGE_SIZE,
215		};
216
217		let program_info =
218			program.estimate_interpreter_memory_usage(usage_args).map_err(|err| {
219				log::debug!(target: LOG_TARGET, "failed to estimate memory usage of program: {err:?}");
220				Error::<T>::CodeRejected
221			})?;
222
223		log::trace!(
224			target: LOG_TARGET, "Contract memory usage: purgable={}/{} KB baseline={}/{}",
225			program_info.purgeable_ram_consumption, PURGABLE_MEMORY_LIMIT,
226			program_info.baseline_ram_consumption, BASELINE_MEMORY_LIMIT,
227		);
228
229		if program_info.purgeable_ram_consumption > PURGABLE_MEMORY_LIMIT {
230			log::debug!(target: LOG_TARGET, "contract uses too much purgeable memory: {} limit: {}",
231				program_info.purgeable_ram_consumption,
232				PURGABLE_MEMORY_LIMIT,
233			);
234			return Err(Error::<T>::StaticMemoryTooLarge.into())
235		}
236
237		if program_info.baseline_ram_consumption > BASELINE_MEMORY_LIMIT {
238			log::debug!(target: LOG_TARGET, "contract uses too much baseline memory: {} limit: {}",
239				program_info.baseline_ram_consumption,
240				BASELINE_MEMORY_LIMIT,
241			);
242			return Err(Error::<T>::StaticMemoryTooLarge.into())
243		}
244
245		Ok(pvm_blob)
246	}
247}
248
249/// The amount of total memory we require.
250///
251/// Unchecked math is okay since we evaluate at compile time.
252const fn memory_required() -> u32 {
253	// The root frame is not accounted for in CALL_STACK_DEPTH
254	let max_call_depth = CALL_STACK_DEPTH + 1;
255
256	let per_stack_memory = code::PURGABLE_MEMORY_LIMIT + TRANSIENT_STORAGE_BYTES * 2;
257	let per_frame_memory = code::BASELINE_MEMORY_LIMIT + CALLDATA_BYTES * 2;
258
259	per_stack_memory + max_call_depth * per_frame_memory
260}