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 of the transaction payload
52///
53/// Maximum code size during instantiation taken into account plus some overhead.
54pub const MAX_TRANSACTION_PAYLOAD_SIZE: u32 = code::BLOB_BYTES + CALLDATA_BYTES;
55
56/// Maximum size of storage items.
57pub const STORAGE_BYTES: u32 = 416;
58
59/// Maximum payload size of events.
60pub const EVENT_BYTES: u32 = 64 * 1024;
61
62/// The extra ref time charge of deposit event per byte.
63///
64/// This ensure the block builder has enough memory and pallet storage
65/// to operate under worst case scenarios.
66pub const EXTRA_EVENT_CHARGE_PER_BYTE: u64 = 256 * 1024;
67
68/// The maximum size for calldata and return data.
69///
70/// Please note that the calldata is limited to 128KB on geth anyways.
71pub const CALLDATA_BYTES: u32 = 128 * 1024;
72
73/// The maximum size of the transient storage in bytes.
74///
75/// This includes keys, values, and previous entries used for storage rollback.
76pub const TRANSIENT_STORAGE_BYTES: u32 = 4 * 1024;
77
78/// The maximum allowable length in bytes for (transient) storage keys.
79pub const STORAGE_KEY_BYTES: u32 = 128;
80
81/// The page size in which PolkaVM should allocate memory chunks.
82pub const PAGE_SIZE: u32 = 4 * 1024;
83
84/// The maximum amount of immutable bytes a single contract can store.
85///
86/// The current limit of 4kb allows storing up 16 U256 immutable variables.
87/// Which should always be enough because Solidity allows for 16 local (stack) variables.
88pub const IMMUTABLE_BYTES: u32 = 4 * 1024;
89
90/// upperbound of memory that can be used by the EVM interpreter.
91pub const EVM_MEMORY_BYTES: u32 = 1024 * 1024;
92
93/// EVM interpreter stack limit.
94pub const EVM_STACK_LIMIT: u32 = 1024;
95
96/// Limits that are only enforced on code upload.
97///
98/// # Note
99///
100/// This limit can be increased later without breaking existing contracts
101/// as it is only enforced at code upload time. Code already uploaded
102/// will not be affected by those limits.
103pub mod code {
104	use super::PAGE_SIZE;
105	use crate::{Config, Error, LOG_TARGET};
106	use alloc::vec::Vec;
107	use sp_runtime::{DispatchError, SaturatedConversion};
108
109	/// The maximum length of a code blob in bytes.
110	///
111	/// This mostly exist to prevent parsing too big blobs and to
112	/// have a maximum encoded length.
113	pub const BLOB_BYTES: u32 = 1024 * 1024;
114
115	/// The maximum amount of memory the interpreter is allowed to use for compilation artifacts.
116	pub const INTERPRETER_CACHE_BYTES: u32 = 1024 * 1024;
117
118	/// The maximum size of a basic block in number of instructions.
119	///
120	/// We need to limit the size of basic blocks because the interpreters lazy compilation
121	/// compiles one basic block at a time. A malicious program could trigger the compilation
122	/// of the whole program by creating one giant basic block otherwise.
123	pub const BASIC_BLOCK_SIZE: u32 = 1000;
124
125	/// The limit for memory that can be purged on demand.
126	///
127	/// We purge this memory every time we call into another contract.
128	/// Hence we effectively only need to hold it once in RAM.
129	pub const PURGABLE_MEMORY_LIMIT: u32 = INTERPRETER_CACHE_BYTES + 2 * 1024 * 1024;
130
131	/// The limit for memory that needs to be kept alive for a contracts whole life time.
132	///
133	/// This means tuning this number affects the call stack depth.
134	pub const BASELINE_MEMORY_LIMIT: u32 = BLOB_BYTES + 512 * 1024;
135
136	/// Why [`check_pvm_code`] rejected a code blob.
137	///
138	/// The limits that were exceeded are reported alongside the measured value. They are not
139	/// meant to be hard-coded by tooling since they may be raised in future releases.
140	#[derive(Debug, PartialEq)]
141	#[non_exhaustive]
142	pub enum CodeRejection {
143		/// The blob is larger than allowed. Sizes are in bytes.
144		BlobTooLarge { size: u32, limit: u32 },
145		/// A basic block is larger than allowed. Sizes are in number of instructions.
146		BasicBlockTooLarge { size: u32, limit: u32 },
147		/// The program needs more purgeable memory than allowed. Sizes are in bytes.
148		PurgeableMemoryTooLarge { size: u32, limit: u32 },
149		/// The program needs more baseline memory than allowed. Sizes are in bytes.
150		BaselineMemoryTooLarge { size: u32, limit: u32 },
151		/// The program contains an instruction that contracts are not allowed to use.
152		InvalidInstruction,
153		/// The blob failed to parse, is a 32 bit program, uses the wrong instruction set, or
154		/// contains a malformed or unknown import.
155		Malformed,
156	}
157
158	impl CodeRejection {
159		/// The error the pallet returns on code upload for this rejection.
160		fn into_error<T: Config>(self) -> Error<T> {
161			match self {
162				Self::BlobTooLarge { .. } => Error::<T>::BlobTooLarge,
163				Self::BasicBlockTooLarge { .. } => Error::<T>::BasicBlockTooLarge,
164				Self::PurgeableMemoryTooLarge { .. } | Self::BaselineMemoryTooLarge { .. } => {
165					Error::<T>::StaticMemoryTooLarge
166				},
167				Self::InvalidInstruction => Error::<T>::InvalidInstruction,
168				Self::Malformed => Error::<T>::CodeRejected,
169			}
170		}
171	}
172
173	/// Make sure that the various program parts are within the defined limits.
174	///
175	/// This is the check the pallet runs on code upload. It is not generic over the runtime so
176	/// that tooling can find out ahead of deployment whether a blob would be accepted.
177	///
178	/// The verdict only matches a production runtime when `pallet-revive` is built without the
179	/// `runtime-benchmarks` feature. With it, a blob that uses the reserved `SENTINEL` ecalli
180	/// passes here but is rejected with [`CodeRejection::InvalidInstruction`] on upload, and a
181	/// blob that imports the benchmark-only `noop` syscall passes here but is rejected with
182	/// [`CodeRejection::Malformed`]. Features unify across the build, so a dependency can
183	/// enable it unasked.
184	pub fn check_pvm_code(pvm_blob: &[u8]) -> Result<(), CodeRejection> {
185		use polkavm_common::program::{
186			EstimateInterpreterMemoryUsageArgs, ISA_ReviveV1, InstructionSetKind,
187		};
188
189		check_blob_size(pvm_blob)?;
190
191		let available_syscalls = crate::vm::pvm::env::list_syscalls();
192
193		let program = polkavm::ProgramBlob::parse(pvm_blob.into()).map_err(|err| {
194			log::debug!(target: LOG_TARGET, "failed to parse polkavm blob: {err:?}");
195			CodeRejection::Malformed
196		})?;
197
198		if !program.is_64_bit() {
199			log::debug!(target: LOG_TARGET, "32bit programs are not supported.");
200			Err(CodeRejection::Malformed)?;
201		}
202
203		if program.isa() != InstructionSetKind::ReviveV1 {
204			log::debug!(target: LOG_TARGET, "Program instruction set '{}' is not '{}'", program.isa().name(), InstructionSetKind::ReviveV1.name());
205			Err(CodeRejection::Malformed)?;
206		}
207
208		// Need to check that no non-existent syscalls are used. This allows us to add
209		// new syscalls later without affecting already deployed code.
210		for (idx, import) in program.imports().iter().enumerate() {
211			// We are being defensive in case an attacker is able to somehow include
212			// a lot of imports. This is important because we search the array of host
213			// functions for every import.
214			if idx == available_syscalls.len() {
215				log::debug!(target: LOG_TARGET, "Program contains too many imports.");
216				Err(CodeRejection::Malformed)?;
217			}
218			let Some(import) = import else {
219				log::debug!(target: LOG_TARGET, "Program contains malformed import.");
220				return Err(CodeRejection::Malformed);
221			};
222			if !available_syscalls.contains(&import.as_bytes()) {
223				log::debug!(target: LOG_TARGET, "Program references unknown syscall: {}", import);
224				Err(CodeRejection::Malformed)?;
225			}
226		}
227
228		// This scans the whole program but we only do it once on code deployment.
229		// It is safe to do unchecked math in u32 because the size of the program
230		// was already checked above.
231		let mut max_block_size: u32 = 0;
232		let mut block_size: u32 = 0;
233		let mut basic_block_count: u32 = 0;
234		let mut instruction_count: u32 = 0;
235		for inst in program.instructions_with_isa(ISA_ReviveV1) {
236			use polkavm::program::Instruction;
237			block_size += 1;
238			instruction_count += 1;
239			if inst.kind.opcode().starts_new_basic_block() {
240				max_block_size = max_block_size.max(block_size);
241				block_size = 0;
242				basic_block_count += 1;
243			}
244			match inst.kind {
245				Instruction::invalid => {
246					log::debug!(target: LOG_TARGET, "invalid instruction at offset {}", inst.offset);
247					return Err(CodeRejection::InvalidInstruction);
248				},
249				// Since polkavm `0.30.0` linker will fail if it detects sbrk instruction.
250				// So this branch is never reached for programs built with polkavm >= 0.30.0.
251				//
252				// # Note
253				//
254				// The decoder might decode sbrk to `invalid` under our Revive isa. In this case,
255				// this branch is _not_ hit for sbrk but the one above instead.
256				Instruction::sbrk(_, _) => {
257					log::debug!(target: LOG_TARGET, "sbrk instruction is not allowed. offset {}", inst.offset);
258					return Err(CodeRejection::InvalidInstruction);
259				},
260				// Only benchmarking code is allowed to circumvent the import table. We might want
261				// to remove this magic syscall number later. Hence we need to prevent contracts
262				// from using it.
263				//
264				// The `as` conversion is fine here because we are just testing equality.
265				#[cfg(not(feature = "runtime-benchmarks"))]
266				Instruction::ecalli(idx) if idx as u32 == crate::SENTINEL => {
267					log::debug!(target: LOG_TARGET, "reserved syscall idx {idx}. offset {}", inst.offset);
268					return Err(CodeRejection::InvalidInstruction);
269				},
270				_ => (),
271			}
272		}
273		max_block_size = max_block_size.max(block_size);
274
275		if max_block_size > BASIC_BLOCK_SIZE {
276			log::debug!(target: LOG_TARGET, "basic block too large: {max_block_size} limit: {BASIC_BLOCK_SIZE}");
277			return Err(CodeRejection::BasicBlockTooLarge {
278				size: max_block_size,
279				limit: BASIC_BLOCK_SIZE,
280			});
281		}
282
283		let usage_args = EstimateInterpreterMemoryUsageArgs::BoundedCache {
284			max_cache_size_bytes: INTERPRETER_CACHE_BYTES,
285			instruction_count,
286			max_block_size,
287			basic_block_count,
288			page_size: PAGE_SIZE,
289		};
290
291		let program_info =
292			program.estimate_interpreter_memory_usage(usage_args).map_err(|err| {
293				log::debug!(target: LOG_TARGET, "failed to estimate memory usage of program: {err:?}");
294				CodeRejection::Malformed
295			})?;
296
297		log::trace!(
298			target: LOG_TARGET, "Contract memory usage: purgable={}/{} KB baseline={}/{}",
299			program_info.purgeable_ram_consumption, PURGABLE_MEMORY_LIMIT,
300			program_info.baseline_ram_consumption, BASELINE_MEMORY_LIMIT,
301		);
302
303		if program_info.purgeable_ram_consumption > PURGABLE_MEMORY_LIMIT {
304			log::debug!(target: LOG_TARGET, "contract uses too much purgeable memory: {} limit: {}",
305				program_info.purgeable_ram_consumption,
306				PURGABLE_MEMORY_LIMIT,
307			);
308			return Err(CodeRejection::PurgeableMemoryTooLarge {
309				size: program_info.purgeable_ram_consumption,
310				limit: PURGABLE_MEMORY_LIMIT,
311			});
312		}
313
314		if program_info.baseline_ram_consumption > BASELINE_MEMORY_LIMIT {
315			log::debug!(target: LOG_TARGET, "contract uses too much baseline memory: {} limit: {}",
316				program_info.baseline_ram_consumption,
317				BASELINE_MEMORY_LIMIT,
318			);
319			return Err(CodeRejection::BaselineMemoryTooLarge {
320				size: program_info.baseline_ram_consumption,
321				limit: BASELINE_MEMORY_LIMIT,
322			});
323		}
324
325		Ok(())
326	}
327
328	/// Runs [`check_pvm_code`] and turns a rejection into the pallet's dispatch error.
329	pub fn enforce<T: Config>(pvm_blob: Vec<u8>) -> Result<Vec<u8>, DispatchError> {
330		#[cfg(feature = "std")]
331		if std::env::var_os("REVIVE_SKIP_VALIDATION").is_some() {
332			check_blob_size(&pvm_blob).map_err(CodeRejection::into_error::<T>)?;
333			log::warn!(target: LOG_TARGET, "Skipping validation because env var REVIVE_SKIP_VALIDATION is set");
334			return Ok(pvm_blob);
335		}
336
337		check_pvm_code(&pvm_blob).map_err(CodeRejection::into_error::<T>)?;
338		Ok(pvm_blob)
339	}
340
341	fn check_blob_size(pvm_blob: &[u8]) -> Result<(), CodeRejection> {
342		let len: u64 = pvm_blob.len() as u64;
343		if len > BLOB_BYTES.into() {
344			log::debug!(target: LOG_TARGET, "contract blob too large: {len} limit: {BLOB_BYTES}");
345			return Err(CodeRejection::BlobTooLarge {
346				size: len.saturated_into(),
347				limit: BLOB_BYTES,
348			});
349		}
350		Ok(())
351	}
352
353	#[cfg(test)]
354	mod tests {
355		use super::CodeRejection;
356		use crate::{Error, tests::Test};
357		use sp_runtime::DispatchError;
358
359		fn to_error(rejection: CodeRejection) -> DispatchError {
360			rejection.into_error::<Test>().into()
361		}
362
363		#[test]
364		fn blob_too_large_maps_to_blob_too_large() {
365			let rejection = CodeRejection::BlobTooLarge { size: 2, limit: 1 };
366			assert_eq!(to_error(rejection), Error::<Test>::BlobTooLarge.into());
367		}
368
369		#[test]
370		fn basic_block_too_large_maps_to_basic_block_too_large() {
371			let rejection = CodeRejection::BasicBlockTooLarge { size: 2, limit: 1 };
372			assert_eq!(to_error(rejection), Error::<Test>::BasicBlockTooLarge.into());
373		}
374
375		#[test]
376		fn purgeable_memory_too_large_maps_to_static_memory_too_large() {
377			let rejection = CodeRejection::PurgeableMemoryTooLarge { size: 2, limit: 1 };
378			assert_eq!(to_error(rejection), Error::<Test>::StaticMemoryTooLarge.into());
379		}
380
381		#[test]
382		fn baseline_memory_too_large_maps_to_static_memory_too_large() {
383			let rejection = CodeRejection::BaselineMemoryTooLarge { size: 2, limit: 1 };
384			assert_eq!(to_error(rejection), Error::<Test>::StaticMemoryTooLarge.into());
385		}
386
387		#[test]
388		fn invalid_instruction_maps_to_invalid_instruction() {
389			assert_eq!(
390				to_error(CodeRejection::InvalidInstruction),
391				Error::<Test>::InvalidInstruction.into()
392			);
393		}
394
395		#[test]
396		fn malformed_maps_to_code_rejected() {
397			assert_eq!(to_error(CodeRejection::Malformed), Error::<Test>::CodeRejected.into());
398		}
399	}
400}
401
402/// The amount of total memory we require.
403///
404/// Unchecked math is okay since we evaluate at compile time.
405const fn memory_required() -> u32 {
406	// The root frame is not accounted for in CALL_STACK_DEPTH
407	let max_call_depth = CALL_STACK_DEPTH + 1;
408
409	let per_stack_memory = code::PURGABLE_MEMORY_LIMIT +
410		TRANSIENT_STORAGE_BYTES * 2 +
411		crate::access_list::MAX_ACCESS_LIST_BYTES;
412
413	let evm_max_initcode_size = revm::primitives::eip3860::MAX_INITCODE_SIZE as u32;
414	let evm_overhead = EVM_MEMORY_BYTES + evm_max_initcode_size + EVM_STACK_LIMIT * 32;
415	let per_frame_memory = if code::BASELINE_MEMORY_LIMIT > evm_overhead {
416		code::BASELINE_MEMORY_LIMIT
417	} else {
418		evm_overhead
419	} + CALLDATA_BYTES * 2;
420
421	per_stack_memory + max_call_depth * per_frame_memory
422}