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