1use crate::{
22 code::{GENERIC_OS_PAGE_SIZE, errors::*},
23 env::WasmEntryPoint,
24 message::DispatchKind,
25 pages::{WasmPage, WasmPagesAmount},
26};
27use alloc::collections::BTreeSet;
28use gear_wasm_instrument::{
29 ConstExpr, ElementItems, Export, Global, Instruction, Module, STACK_END_EXPORT_NAME,
30 SyscallName,
31};
32use wasmparser::{ExternalKind, Payload, TypeRef, ValType};
33
34pub const MAX_WASM_PAGES_AMOUNT: u16 = u16::MAX / 2 + 1; pub(crate) const REF_TYPE_SIZE: u32 = 4;
38
39pub const ALLOWED_EXPORTS: [&str; 6] = [
41 "init",
42 "handle",
43 "handle_reply",
44 "handle_signal",
45 "state",
46 "metahash",
47];
48
49pub const REQUIRED_EXPORTS: [&str; 2] = ["init", "handle"];
51
52pub fn get_static_pages(module: &Module) -> Result<WasmPagesAmount, CodeError> {
53 let static_pages = module
55 .import_section
56 .as_ref()
57 .ok_or(SectionError::NotFound(SectionName::Import))?
58 .iter()
59 .find_map(|entry| match entry.ty {
60 TypeRef::Memory(mem_ty) => Some(mem_ty.initial as u32),
61 _ => None,
62 })
63 .map(WasmPagesAmount::try_from)
64 .ok_or(MemoryError::EntryNotFound)?
65 .map_err(|_| MemoryError::InvalidStaticPageCount)?;
66
67 if static_pages > WasmPagesAmount::from(MAX_WASM_PAGES_AMOUNT) {
68 Err(MemoryError::InvalidStaticPageCount)?;
69 }
70
71 Ok(static_pages)
72}
73
74pub fn get_exports(module: &Module) -> BTreeSet<DispatchKind> {
75 let mut entries = BTreeSet::new();
76
77 for entry in module
78 .export_section
79 .as_ref()
80 .expect("Exports section has been checked for already")
81 {
82 if let ExternalKind::Func = entry.kind
83 && let Some(entry) = DispatchKind::try_from_entry(&entry.name)
84 {
85 entries.insert(entry);
86 }
87 }
88
89 entries
90}
91
92pub fn check_exports(module: &Module) -> Result<(), CodeError> {
93 let types = module
94 .type_section
95 .as_ref()
96 .ok_or(SectionError::NotFound(SectionName::Type))?;
97
98 let funcs = module
99 .function_section
100 .as_ref()
101 .ok_or(SectionError::NotFound(SectionName::Function))?;
102
103 let import_count = module.import_count(|ty| matches!(ty, TypeRef::Func(_)));
104
105 let exports = module
106 .export_section
107 .as_ref()
108 .ok_or(SectionError::NotFound(SectionName::Export))?;
109
110 let mut entry_point_found = false;
111 for (export_index, export) in exports.iter().enumerate() {
112 let ExternalKind::Func = export.kind else {
113 continue;
114 };
115
116 let index = export.index.checked_sub(import_count as u32).ok_or(
117 ExportError::ExportReferencesToImportFunction(export_index as u32, export.index),
118 )?;
119
120 let type_id = funcs
122 .get(index as usize)
123 .copied()
124 .unwrap_or_else(|| unreachable!("Module structure is invalid"))
125 as usize;
126
127 let func_type = types
129 .get(type_id)
130 .unwrap_or_else(|| unreachable!("Module structure is invalid"));
131
132 if !ALLOWED_EXPORTS.contains(&&*export.name) {
133 Err(ExportError::ExcessExport(export_index as u32))?;
134 }
135
136 if !(func_type.params().is_empty() && func_type.results().is_empty()) {
137 Err(ExportError::InvalidExportFnSignature(export_index as u32))?;
138 }
139
140 if REQUIRED_EXPORTS.contains(&&*export.name) {
141 entry_point_found = true;
142 }
143 }
144
145 entry_point_found
146 .then_some(())
147 .ok_or(ExportError::RequiredExportNotFound)
148 .map_err(CodeError::Export)
149}
150
151pub fn check_imports(module: &Module) -> Result<(), CodeError> {
152 let types = module
153 .type_section
154 .as_ref()
155 .ok_or(SectionError::NotFound(SectionName::Type))?;
156
157 let imports = module
158 .import_section
159 .as_ref()
160 .ok_or(SectionError::NotFound(SectionName::Import))?;
161
162 let syscalls = SyscallName::instrumentable_map();
163
164 let mut visited_imports = BTreeSet::new();
165
166 for (import_index, import) in imports.iter().enumerate() {
167 let import_index: u32 = import_index
168 .try_into()
169 .unwrap_or_else(|_| unreachable!("Import index should fit in u32"));
170
171 match import.ty {
172 TypeRef::Func(i) => {
173 let &func_type = &types
175 .get(i as usize)
176 .unwrap_or_else(|| unreachable!("Module structure is invalid"));
177
178 let syscall = syscalls
179 .get(import.name.as_ref())
180 .ok_or(ImportError::UnknownImport(import_index))?;
181
182 if !visited_imports.insert(*syscall) {
183 Err(ImportError::DuplicateImport(import_index))?;
184 }
185
186 let signature = syscall.signature();
187 let signature_func_type = signature.func_type();
188
189 if signature_func_type != *func_type {
190 Err(ImportError::InvalidImportFnSignature(import_index))?;
191 }
192 }
193 TypeRef::Global(_) => Err(ImportError::UnexpectedImportKind {
194 kind: &"Global",
195 index: import_index,
196 })?,
197 TypeRef::Table(_) => Err(ImportError::UnexpectedImportKind {
198 kind: &"Table",
199 index: import_index,
200 })?,
201 _ => continue,
202 }
203 }
204
205 Ok(())
206}
207
208fn get_export_entry_with_index<'a>(module: &'a Module, name: &str) -> Option<(u32, &'a Export)> {
209 module
210 .export_section
211 .as_ref()?
212 .iter()
213 .enumerate()
214 .find_map(|(export_index, export)| {
215 (export.name == name).then_some((export_index as u32, export))
216 })
217}
218
219fn get_export_global_with_index(module: &Module, name: &str) -> Option<(u32, u32)> {
220 let (export_index, export) = get_export_entry_with_index(module, name)?;
221 match export.kind {
222 ExternalKind::Global => Some((export_index, export.index)),
223 _ => None,
224 }
225}
226
227fn get_init_expr_const_i32(init_expr: &ConstExpr) -> Option<i32> {
228 match init_expr.instructions.as_slice() {
229 [Instruction::I32Const(value)] => Some(*value),
230 _ => None,
231 }
232}
233
234fn get_export_global_entry(
235 module: &Module,
236 export_index: u32,
237 global_index: u32,
238) -> Result<&Global, CodeError> {
239 let index = global_index
240 .checked_sub(module.import_count(|ty| matches!(ty, TypeRef::Global(_))) as u32)
241 .ok_or(ExportError::ExportReferencesToImportGlobal(
242 export_index,
243 global_index,
244 ))? as usize;
245
246 module
247 .global_section
248 .as_ref()
249 .and_then(|s| s.get(index))
250 .ok_or(ExportError::IncorrectGlobalIndex(global_index, export_index).into())
251}
252
253pub fn check_data_section(
255 module: &Module,
256 static_pages: WasmPagesAmount,
257 stack_end: Option<WasmPage>,
258 data_section_amount_limit: Option<u32>,
259) -> Result<(), CodeError> {
260 let Some(data_section) = &module.data_section else {
261 return Ok(());
263 };
264
265 if let Some(data_segments_amount_limit) = data_section_amount_limit {
267 let number_of_data_segments = data_section.len() as u32;
268 if number_of_data_segments > data_segments_amount_limit {
269 Err(DataSectionError::DataSegmentsAmountLimit {
270 limit: data_segments_amount_limit,
271 actual: number_of_data_segments,
272 })?;
273 }
274 }
275
276 for data_segment in data_section {
277 let data_segment_offset = get_init_expr_const_i32(&data_segment.offset_expr)
278 .ok_or(DataSectionError::Initialization)? as u32;
279
280 if let Some(stack_end_offset) = stack_end.map(|p| p.offset()) {
281 (data_segment_offset >= stack_end_offset)
283 .then_some(())
284 .ok_or(DataSectionError::GearStackOverlaps(
285 data_segment_offset,
286 stack_end_offset,
287 ))?;
288 }
289
290 let Some(size) = u32::try_from(data_segment.data.len())
291 .map_err(|_| DataSectionError::EndAddressOverflow(data_segment_offset))?
292 .checked_sub(1)
293 else {
294 continue;
296 };
297
298 let data_segment_last_byte_offset = data_segment_offset
299 .checked_add(size)
300 .ok_or(DataSectionError::EndAddressOverflow(data_segment_offset))?;
301
302 ((data_segment_last_byte_offset as u64) < static_pages.offset())
303 .then_some(())
304 .ok_or(DataSectionError::EndAddressOutOfStaticMemory(
305 data_segment_offset,
306 data_segment_last_byte_offset,
307 static_pages.offset(),
308 ))?;
309 }
310
311 Ok(())
312}
313
314pub fn check_type_section(
316 module: &Module,
317 type_section_params_per_type_limit: u32,
318) -> Result<(), CodeError> {
319 if let Some(type_section) = &module.type_section {
320 for ty in type_section.iter() {
321 if (ty.params().len() + ty.results().len()) as u32 > type_section_params_per_type_limit
323 {
324 Err(TypeSectionError::ParametersPerTypeLimitExceeded {
325 limit: type_section_params_per_type_limit,
326 actual: ty.params().len() as u32,
327 })?;
328 }
329 }
330 }
331
332 Ok(())
333}
334
335fn get_stack_end_offset(module: &Module) -> Result<Option<u32>, CodeError> {
336 let Some((export_index, global_index)) =
337 get_export_global_with_index(module, STACK_END_EXPORT_NAME)
338 else {
339 return Ok(None);
340 };
341
342 Ok(Some(
343 get_init_expr_const_i32(
344 &get_export_global_entry(module, export_index, global_index)?.init_expr,
345 )
346 .ok_or(StackEndError::Initialization)? as u32,
347 ))
348}
349
350pub fn check_and_canonize_gear_stack_end(
351 module: &mut Module,
352 static_pages: WasmPagesAmount,
353) -> Result<Option<WasmPage>, CodeError> {
354 let Some(stack_end_offset) = get_stack_end_offset(module)? else {
355 return Ok(None);
356 };
357
358 module
361 .export_section
362 .as_mut()
363 .unwrap_or_else(|| unreachable!("Cannot find export section"))
364 .retain(|export| export.name != STACK_END_EXPORT_NAME);
365
366 if !stack_end_offset.is_multiple_of(WasmPage::SIZE) {
367 return Err(StackEndError::NotAligned(stack_end_offset).into());
368 }
369
370 let stack_end = WasmPage::from_offset(stack_end_offset);
371 if stack_end > static_pages {
372 return Err(StackEndError::OutOfStatic(stack_end_offset, static_pages.offset()).into());
373 }
374
375 Ok(Some(stack_end))
376}
377
378pub fn check_mut_global_exports(module: &Module) -> Result<(), CodeError> {
383 let Some(export_section) = &module.export_section else {
384 return Ok(());
385 };
386
387 export_section
388 .iter()
389 .enumerate()
390 .filter_map(|(export_index, export)| match export.kind {
391 ExternalKind::Global => Some((export_index as u32, export.index)),
392 _ => None,
393 })
394 .try_for_each(|(export_index, global_index)| {
395 let entry = get_export_global_entry(module, export_index, global_index)?;
396 if entry.ty.mutable {
397 Err(ExportError::MutableGlobalExport(global_index, export_index).into())
398 } else {
399 Ok(())
400 }
401 })
402}
403
404pub fn check_start_section(module: &Module) -> Result<(), CodeError> {
405 if module.start_section.is_some() {
406 log::debug!("Found start section in program code, which is not allowed");
407 Err(SectionError::NotSupported(SectionName::Start))?
408 } else {
409 Ok(())
410 }
411}
412
413pub fn get_data_section_size(module: &Module) -> Result<u32, CodeError> {
418 let Some(data_section) = &module.data_section else {
419 return Ok(0);
421 };
422
423 let mut used_pages = BTreeSet::new();
424 for data_segment in data_section {
425 let data_segment_offset = get_init_expr_const_i32(&data_segment.offset_expr)
426 .ok_or(DataSectionError::Initialization)? as u32;
427 let data_segment_start = data_segment_offset / GENERIC_OS_PAGE_SIZE;
428
429 let data_segment_size = data_segment.data.len() as u32;
430
431 if data_segment_size == 0 {
432 continue;
434 }
435
436 let data_segment_end = data_segment_offset .saturating_add(data_segment_size.saturating_sub(1)) / GENERIC_OS_PAGE_SIZE;
439
440 used_pages.extend(data_segment_start..=data_segment_end);
441 }
442
443 Ok(used_pages.len() as u32 * GENERIC_OS_PAGE_SIZE)
444}
445
446pub fn get_instantiated_global_section_size(module: &Module) -> Result<u32, CodeError> {
448 let Some(global_section) = &module.global_section else {
449 return Ok(0);
451 };
452
453 Ok(global_section.iter().fold(0, |total_bytes, global| {
454 let value_size = match global.ty.content_type {
455 ValType::I32 => size_of::<i32>(),
456 ValType::I64 => size_of::<i64>(),
457 ValType::F32 | ValType::F64 | ValType::V128 | ValType::Ref(_) => {
458 unreachable!("f32/64, SIMD and reference types are not supported")
459 }
460 } as u32;
461 total_bytes.saturating_add(value_size)
462 }))
463}
464
465pub fn get_instantiated_table_section_size(module: &Module) -> u32 {
467 let Some(table) = &module.table_section else {
468 return 0;
469 };
470
471 (table.ty.initial as u32).saturating_mul(REF_TYPE_SIZE)
473}
474
475pub fn get_instantiated_element_section_size(module: &Module) -> Result<u32, CodeError> {
477 if module.table_section.is_none() {
478 return Ok(0);
479 }
480
481 let Some(element_section) = &module.element_section else {
482 return Ok(0);
484 };
485
486 Ok(element_section.iter().fold(0, |total_bytes, segment| {
487 let count = match &segment.items {
488 ElementItems::Functions(section) => section.len(),
489 } as u32;
490 total_bytes.saturating_add(count.saturating_mul(REF_TYPE_SIZE))
492 }))
493}
494
495pub struct CodeTypeSectionSizes {
496 pub code_section: u32,
497 pub type_section: u32,
498}
499
500pub fn get_code_type_sections_sizes(
503 code_bytes: &[u8],
504 type_section_len_limit: Option<u32>,
505) -> Result<CodeTypeSectionSizes, CodeError> {
506 let mut code_section_size = 0;
507 let mut type_section_size = 0;
508
509 let parser = wasmparser::Parser::new(0);
510
511 for item in parser.parse_all(code_bytes) {
512 let item = item.map_err(CodeError::Validation)?;
513 match item {
514 Payload::CodeSectionStart { size, .. } => {
515 code_section_size = size;
516 }
517 Payload::TypeSection(t) => {
518 type_section_size += t.range().len() as u32;
520 }
521 _ => {}
522 }
523 }
524
525 if let Some(type_section_len_limit) = type_section_len_limit
527 && type_section_size > type_section_len_limit
528 {
529 Err(TypeSectionError::LengthLimitExceeded {
530 limit: type_section_len_limit,
531 actual: type_section_size,
532 })?;
533 }
534
535 Ok(CodeTypeSectionSizes {
536 code_section: code_section_size,
537 type_section: type_section_size,
538 })
539}