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
314fn get_stack_end_offset(module: &Module) -> Result<Option<u32>, CodeError> {
315 let Some((export_index, global_index)) =
316 get_export_global_with_index(module, STACK_END_EXPORT_NAME)
317 else {
318 return Ok(None);
319 };
320
321 Ok(Some(
322 get_init_expr_const_i32(
323 &get_export_global_entry(module, export_index, global_index)?.init_expr,
324 )
325 .ok_or(StackEndError::Initialization)? as u32,
326 ))
327}
328
329pub fn check_and_canonize_gear_stack_end(
330 module: &mut Module,
331 static_pages: WasmPagesAmount,
332) -> Result<Option<WasmPage>, CodeError> {
333 let Some(stack_end_offset) = get_stack_end_offset(module)? else {
334 return Ok(None);
335 };
336
337 module
340 .export_section
341 .as_mut()
342 .unwrap_or_else(|| unreachable!("Cannot find export section"))
343 .retain(|export| export.name != STACK_END_EXPORT_NAME);
344
345 if !stack_end_offset.is_multiple_of(WasmPage::SIZE) {
346 return Err(StackEndError::NotAligned(stack_end_offset).into());
347 }
348
349 let stack_end = WasmPage::from_offset(stack_end_offset);
350 if stack_end > static_pages {
351 return Err(StackEndError::OutOfStatic(stack_end_offset, static_pages.offset()).into());
352 }
353
354 Ok(Some(stack_end))
355}
356
357pub fn check_mut_global_exports(module: &Module) -> Result<(), CodeError> {
362 let Some(export_section) = &module.export_section else {
363 return Ok(());
364 };
365
366 export_section
367 .iter()
368 .enumerate()
369 .filter_map(|(export_index, export)| match export.kind {
370 ExternalKind::Global => Some((export_index as u32, export.index)),
371 _ => None,
372 })
373 .try_for_each(|(export_index, global_index)| {
374 let entry = get_export_global_entry(module, export_index, global_index)?;
375 if entry.ty.mutable {
376 Err(ExportError::MutableGlobalExport(global_index, export_index).into())
377 } else {
378 Ok(())
379 }
380 })
381}
382
383pub fn check_start_section(module: &Module) -> Result<(), CodeError> {
384 if module.start_section.is_some() {
385 log::debug!("Found start section in program code, which is not allowed");
386 Err(SectionError::NotSupported(SectionName::Start))?
387 } else {
388 Ok(())
389 }
390}
391
392pub fn get_data_section_size(module: &Module) -> Result<u32, CodeError> {
397 let Some(data_section) = &module.data_section else {
398 return Ok(0);
400 };
401
402 let mut used_pages = BTreeSet::new();
403 for data_segment in data_section {
404 let data_segment_offset = get_init_expr_const_i32(&data_segment.offset_expr)
405 .ok_or(DataSectionError::Initialization)? as u32;
406 let data_segment_start = data_segment_offset / GENERIC_OS_PAGE_SIZE;
407
408 let data_segment_size = data_segment.data.len() as u32;
409
410 if data_segment_size == 0 {
411 continue;
413 }
414
415 let data_segment_end = data_segment_offset .saturating_add(data_segment_size.saturating_sub(1)) / GENERIC_OS_PAGE_SIZE;
418
419 used_pages.extend(data_segment_start..=data_segment_end);
420 }
421
422 Ok(used_pages.len() as u32 * GENERIC_OS_PAGE_SIZE)
423}
424
425pub fn get_instantiated_global_section_size(module: &Module) -> Result<u32, CodeError> {
427 let Some(global_section) = &module.global_section else {
428 return Ok(0);
430 };
431
432 Ok(global_section.iter().fold(0, |total_bytes, global| {
433 let value_size = match global.ty.content_type {
434 ValType::I32 => size_of::<i32>(),
435 ValType::I64 => size_of::<i64>(),
436 ValType::F32 | ValType::F64 | ValType::V128 | ValType::Ref(_) => {
437 unreachable!("f32/64, SIMD and reference types are not supported")
438 }
439 } as u32;
440 total_bytes.saturating_add(value_size)
441 }))
442}
443
444pub fn get_instantiated_table_section_size(module: &Module) -> u32 {
446 let Some(table) = &module.table_section else {
447 return 0;
448 };
449
450 (table.ty.initial as u32).saturating_mul(REF_TYPE_SIZE)
452}
453
454pub fn get_instantiated_element_section_size(module: &Module) -> Result<u32, CodeError> {
456 if module.table_section.is_none() {
457 return Ok(0);
458 }
459
460 let Some(element_section) = &module.element_section else {
461 return Ok(0);
463 };
464
465 Ok(element_section.iter().fold(0, |total_bytes, segment| {
466 let count = match &segment.items {
467 ElementItems::Functions(section) => section.len(),
468 } as u32;
469 total_bytes.saturating_add(count.saturating_mul(REF_TYPE_SIZE))
471 }))
472}
473
474pub struct CodeTypeSectionSizes {
475 pub code_section: u32,
476 pub type_section: u32,
477}
478
479pub fn get_code_type_sections_sizes(code_bytes: &[u8]) -> Result<CodeTypeSectionSizes, CodeError> {
481 let mut code_section_size = 0;
482 let mut type_section_size = 0;
483
484 let parser = wasmparser::Parser::new(0);
485
486 for item in parser.parse_all(code_bytes) {
487 let item = item.map_err(CodeError::Validation)?;
488 match item {
489 Payload::CodeSectionStart { size, .. } => {
490 code_section_size = size;
491 }
492 Payload::TypeSection(t) => {
493 type_section_size += t.range().len() as u32;
494 }
495 _ => {}
496 }
497 }
498
499 Ok(CodeTypeSectionSizes {
500 code_section: code_section_size,
501 type_section: type_section_size,
502 })
503}