casper_execution_engine/runtime/
wasm_prep.rs

1//! Preprocessing of Wasm modules.
2use std::{convert::TryInto, num::NonZeroU32};
3
4use thiserror::Error;
5
6use casper_types::{OpcodeCosts, WasmConfig};
7use casper_wasm::elements::{
8    self, External, Instruction, Internal, MemorySection, Module, Section, SignExtInstruction,
9    TableType, Type,
10};
11use casper_wasm_utils::{
12    self,
13    rules::{MemoryGrowCost, Rules},
14    stack_height,
15};
16
17use crate::execution::ExecError;
18
19const ATOMIC_OPCODE_PREFIX: u8 = 0xfe;
20const BULK_OPCODE_PREFIX: u8 = 0xfc;
21const SIMD_OPCODE_PREFIX: u8 = 0xfd;
22
23const DEFAULT_GAS_MODULE_NAME: &str = "env";
24/// Name of the internal gas function injected by [`casper_wasm_utils::inject_gas_counter`].
25const INTERNAL_GAS_FUNCTION_NAME: &str = "gas";
26
27/// We only allow maximum of 4k function pointers in a table section.
28pub const DEFAULT_MAX_TABLE_SIZE: u32 = 4096;
29/// Maximum number of elements that can appear as immediate value to the br_table instruction.
30pub const DEFAULT_BR_TABLE_MAX_SIZE: u32 = 256;
31/// Maximum number of global a module is allowed to declare.
32pub const DEFAULT_MAX_GLOBALS: u32 = 256;
33/// Maximum number of parameters a function can have.
34pub const DEFAULT_MAX_PARAMETER_COUNT: u32 = 256;
35
36/// An error emitted by the Wasm preprocessor.
37#[derive(Debug, Clone, Error)]
38#[non_exhaustive]
39pub enum WasmValidationError {
40    /// Initial table size outside allowed bounds.
41    #[error("initial table size of {actual} exceeds allowed limit of {max}")]
42    InitialTableSizeExceeded {
43        /// Allowed maximum table size.
44        max: u32,
45        /// Actual initial table size specified in the Wasm.
46        actual: u32,
47    },
48    /// Maximum table size outside allowed bounds.
49    #[error("maximum table size of {actual} exceeds allowed limit of {max}")]
50    MaxTableSizeExceeded {
51        /// Allowed maximum table size.
52        max: u32,
53        /// Actual max table size specified in the Wasm.
54        actual: u32,
55    },
56    /// Number of the tables in a Wasm must be at most one.
57    #[error("the number of tables must be at most one")]
58    MoreThanOneTable,
59    /// Length of a br_table exceeded the maximum allowed size.
60    #[error("maximum br_table size of {actual} exceeds allowed limit of {max}")]
61    BrTableSizeExceeded {
62        /// Maximum allowed br_table length.
63        max: u32,
64        /// Actual size of a br_table in the code.
65        actual: usize,
66    },
67    /// Declared number of globals exceeds allowed limit.
68    #[error("declared number of globals ({actual}) exceeds allowed limit of {max}")]
69    TooManyGlobals {
70        /// Maximum allowed globals.
71        max: u32,
72        /// Actual number of globals declared in the Wasm.
73        actual: usize,
74    },
75    /// Module declares a function type with too many parameters.
76    #[error("use of a function type with too many parameters (limit of {max} but function declares {actual})")]
77    TooManyParameters {
78        /// Maximum allowed parameters.
79        max: u32,
80        /// Actual number of parameters a function has in the Wasm.
81        actual: usize,
82    },
83    /// Module tries to import a function that the host does not provide.
84    #[error("module imports a non-existent function")]
85    MissingHostFunction,
86    /// Opcode for a global access refers to a non-existing global
87    #[error("opcode for a global access refers to non-existing global index {index}")]
88    IncorrectGlobalOperation {
89        /// Provided index.
90        index: u32,
91    },
92    /// Missing function index.
93    #[error("missing function index {index}")]
94    MissingFunctionIndex {
95        /// Provided index.
96        index: u32,
97    },
98    /// Missing function type.
99    #[error("missing type index {index}")]
100    MissingFunctionType {
101        /// Provided index.
102        index: u32,
103    },
104}
105
106/// An error emitted by the Wasm preprocessor.
107#[derive(Debug, Clone, Error)]
108#[non_exhaustive]
109pub enum PreprocessingError {
110    /// Unable to deserialize Wasm bytes.
111    #[error("Deserialization error: {0}")]
112    Deserialize(String),
113    /// Found opcodes forbidden by gas rules.
114    #[error(
115        "Encountered operation forbidden by gas rules. Consult instruction -> metering config map"
116    )]
117    OperationForbiddenByGasRules,
118    /// Stack limiter was unable to instrument the binary.
119    #[error("Stack limiter error")]
120    StackLimiter,
121    /// Wasm bytes is missing memory section.
122    #[error("Memory section should exist")]
123    MissingMemorySection,
124    /// The module is missing.
125    #[error("Missing module")]
126    MissingModule,
127    /// Unable to validate wasm bytes.
128    #[error("Wasm validation error: {0}")]
129    WasmValidation(#[from] WasmValidationError),
130}
131
132impl From<elements::Error> for PreprocessingError {
133    fn from(error: elements::Error) -> Self {
134        PreprocessingError::Deserialize(error.to_string())
135    }
136}
137
138/// Ensures that all the references to functions and global variables in the wasm bytecode are
139/// properly declared.
140///
141/// This validates that:
142///
143/// - Start function points to a function declared in the Wasm bytecode
144/// - All exported functions are pointing to functions declared in the Wasm bytecode
145/// - `call` instructions reference a function declared in the Wasm bytecode.
146/// - `global.set`, `global.get` instructions are referencing an existing global declared in the
147///   Wasm bytecode.
148/// - All members of the "elem" section point at functions declared in the Wasm bytecode.
149fn ensure_valid_access(module: &Module) -> Result<(), WasmValidationError> {
150    let function_types_count = module
151        .type_section()
152        .map(|ts| ts.types().len())
153        .unwrap_or_default();
154
155    let mut function_count = 0_u32;
156    if let Some(import_section) = module.import_section() {
157        for import_entry in import_section.entries() {
158            if let External::Function(function_type_index) = import_entry.external() {
159                if (*function_type_index as usize) < function_types_count {
160                    function_count = function_count.saturating_add(1);
161                } else {
162                    return Err(WasmValidationError::MissingFunctionType {
163                        index: *function_type_index,
164                    });
165                }
166            }
167        }
168    }
169    if let Some(function_section) = module.function_section() {
170        for function_entry in function_section.entries() {
171            let function_type_index = function_entry.type_ref();
172            if (function_type_index as usize) < function_types_count {
173                function_count = function_count.saturating_add(1);
174            } else {
175                return Err(WasmValidationError::MissingFunctionType {
176                    index: function_type_index,
177                });
178            }
179        }
180    }
181
182    if let Some(function_index) = module.start_section() {
183        ensure_valid_function_index(function_index, function_count)?;
184    }
185    if let Some(export_section) = module.export_section() {
186        for export_entry in export_section.entries() {
187            if let Internal::Function(function_index) = export_entry.internal() {
188                ensure_valid_function_index(*function_index, function_count)?;
189            }
190        }
191    }
192
193    if let Some(code_section) = module.code_section() {
194        let global_len = module
195            .global_section()
196            .map(|global_section| global_section.entries().len())
197            .unwrap_or(0);
198
199        for instr in code_section
200            .bodies()
201            .iter()
202            .flat_map(|body| body.code().elements())
203        {
204            match instr {
205                Instruction::Call(idx) => {
206                    ensure_valid_function_index(*idx, function_count)?;
207                }
208                Instruction::GetGlobal(idx) | Instruction::SetGlobal(idx)
209                    if *idx as usize >= global_len =>
210                {
211                    return Err(WasmValidationError::IncorrectGlobalOperation { index: *idx });
212                }
213                _ => {}
214            }
215        }
216    }
217
218    if let Some(element_section) = module.elements_section() {
219        for element_segment in element_section.entries() {
220            for idx in element_segment.members() {
221                ensure_valid_function_index(*idx, function_count)?;
222            }
223        }
224    }
225
226    Ok(())
227}
228
229fn ensure_valid_function_index(index: u32, function_count: u32) -> Result<(), WasmValidationError> {
230    if index >= function_count {
231        return Err(WasmValidationError::MissingFunctionIndex { index });
232    }
233    Ok(())
234}
235
236/// Checks if given wasm module contains a non-empty memory section.
237fn memory_section(module: &Module) -> Option<&MemorySection> {
238    for section in module.sections() {
239        if let Section::Memory(section) = section {
240            return if section.entries().is_empty() {
241                None
242            } else {
243                Some(section)
244            };
245        }
246    }
247    None
248}
249
250/// Ensures (table) section has at most one table entry, and initial, and maximum values are
251/// normalized.
252///
253/// If a maximum value is not specified it will be defaulted to 4k to prevent OOM.
254fn ensure_table_size_limit(mut module: Module, limit: u32) -> Result<Module, WasmValidationError> {
255    if let Some(sect) = module.table_section_mut() {
256        // Table section is optional and there can be at most one.
257        if sect.entries().len() > 1 {
258            return Err(WasmValidationError::MoreThanOneTable);
259        }
260
261        if let Some(table_entry) = sect.entries_mut().first_mut() {
262            let initial = table_entry.limits().initial();
263            if initial > limit {
264                return Err(WasmValidationError::InitialTableSizeExceeded {
265                    max: limit,
266                    actual: initial,
267                });
268            }
269
270            match table_entry.limits().maximum() {
271                Some(max) => {
272                    if max > limit {
273                        return Err(WasmValidationError::MaxTableSizeExceeded {
274                            max: limit,
275                            actual: max,
276                        });
277                    }
278                }
279                None => {
280                    // rewrite wasm and provide a maximum limit for a table section
281                    *table_entry = TableType::new(initial, Some(limit))
282                }
283            }
284        }
285    }
286
287    Ok(module)
288}
289
290/// Ensure that any `br_table` instruction adheres to its immediate value limit.
291fn ensure_br_table_size_limit(module: &Module, limit: u32) -> Result<(), WasmValidationError> {
292    let code_section = if let Some(type_section) = module.code_section() {
293        type_section
294    } else {
295        return Ok(());
296    };
297    for instr in code_section
298        .bodies()
299        .iter()
300        .flat_map(|body| body.code().elements())
301    {
302        if let Instruction::BrTable(br_table_data) = instr {
303            if br_table_data.table.len() > limit as usize {
304                return Err(WasmValidationError::BrTableSizeExceeded {
305                    max: limit,
306                    actual: br_table_data.table.len(),
307                });
308            }
309        }
310    }
311    Ok(())
312}
313
314/// Ensures that module doesn't declare too many globals.
315///
316/// Globals are not limited through the `stack_height` as locals are. Neither does
317/// the linear memory limit `memory_pages` applies to them.
318fn ensure_global_variable_limit(module: &Module, limit: u32) -> Result<(), WasmValidationError> {
319    if let Some(global_section) = module.global_section() {
320        let actual = global_section.entries().len();
321        if actual > limit as usize {
322            return Err(WasmValidationError::TooManyGlobals { max: limit, actual });
323        }
324    }
325    Ok(())
326}
327
328/// Ensure maximum numbers of parameters a function can have.
329///
330/// Those need to be limited to prevent a potentially exploitable interaction with
331/// the stack height instrumentation: The costs of executing the stack height
332/// instrumentation for an indirectly called function scales linearly with the amount
333/// of parameters of this function. Because the stack height instrumentation itself is
334/// is not weight metered its costs must be static (via this limit) and included in
335/// the costs of the instructions that cause them (call, call_indirect).
336fn ensure_parameter_limit(module: &Module, limit: u32) -> Result<(), WasmValidationError> {
337    let type_section = if let Some(type_section) = module.type_section() {
338        type_section
339    } else {
340        return Ok(());
341    };
342
343    for Type::Function(func) in type_section.types() {
344        let actual = func.params().len();
345        if actual > limit as usize {
346            return Err(WasmValidationError::TooManyParameters { max: limit, actual });
347        }
348    }
349
350    Ok(())
351}
352
353/// Ensures that Wasm module has valid imports.
354fn ensure_valid_imports(module: &Module) -> Result<(), WasmValidationError> {
355    let import_entries = module
356        .import_section()
357        .map(|is| is.entries())
358        .unwrap_or(&[]);
359
360    // Gas counter is currently considered an implementation detail.
361    //
362    // If a wasm module tries to import it will be rejected.
363
364    for import in import_entries {
365        if import.module() == DEFAULT_GAS_MODULE_NAME
366            && import.field() == INTERNAL_GAS_FUNCTION_NAME
367        {
368            return Err(WasmValidationError::MissingHostFunction);
369        }
370    }
371
372    Ok(())
373}
374
375/// Preprocesses Wasm bytes and returns a module.
376///
377/// This process consists of a few steps:
378/// - Validate that the given bytes contain a memory section, and check the memory page limit.
379/// - Inject gas counters into the code, which makes it possible for the executed Wasm to be charged
380///   for opcodes; this also validates opcodes and ensures that there are no forbidden opcodes in
381///   use, such as floating point opcodes.
382/// - Ensure that the code has a maximum stack height.
383///
384/// In case the preprocessing rules can't be applied, an error is returned.
385/// Otherwise, this method returns a valid module ready to be executed safely on the host.
386pub fn preprocess(
387    wasm_config: WasmConfig,
388    module_bytes: &[u8],
389) -> Result<Module, PreprocessingError> {
390    let module = deserialize(module_bytes)?;
391
392    ensure_valid_access(&module)?;
393
394    if memory_section(&module).is_none() {
395        // `casper_wasm_utils::externalize_mem` expects a non-empty memory section to exist in the
396        // module, and panics otherwise.
397        return Err(PreprocessingError::MissingMemorySection);
398    }
399
400    let module = ensure_table_size_limit(module, DEFAULT_MAX_TABLE_SIZE)?;
401    ensure_br_table_size_limit(&module, DEFAULT_BR_TABLE_MAX_SIZE)?;
402    ensure_global_variable_limit(&module, DEFAULT_MAX_GLOBALS)?;
403    ensure_parameter_limit(&module, DEFAULT_MAX_PARAMETER_COUNT)?;
404    ensure_valid_imports(&module)?;
405
406    let costs = RuledOpcodeCosts(wasm_config.v1().opcode_costs());
407    let module = casper_wasm_utils::externalize_mem(module, None, wasm_config.v1().max_memory());
408    let module = casper_wasm_utils::inject_gas_counter(module, &costs, DEFAULT_GAS_MODULE_NAME)
409        .map_err(|_| PreprocessingError::OperationForbiddenByGasRules)?;
410    let module = stack_height::inject_limiter(module, wasm_config.v1().max_stack_height())
411        .map_err(|_| PreprocessingError::StackLimiter)?;
412    Ok(module)
413}
414
415/// Returns a parity Module from the given bytes without making modifications or checking limits.
416pub fn deserialize(module_bytes: &[u8]) -> Result<Module, PreprocessingError> {
417    casper_wasm::deserialize_buffer::<Module>(module_bytes).map_err(|deserialize_error| {
418        match deserialize_error {
419            casper_wasm::SerializationError::UnknownOpcode(BULK_OPCODE_PREFIX) => {
420                PreprocessingError::Deserialize(
421                    "Bulk memory operations are not supported".to_string(),
422                )
423            }
424            casper_wasm::SerializationError::UnknownOpcode(SIMD_OPCODE_PREFIX) => {
425                PreprocessingError::Deserialize("SIMD operations are not supported".to_string())
426            }
427            casper_wasm::SerializationError::UnknownOpcode(ATOMIC_OPCODE_PREFIX) => {
428                PreprocessingError::Deserialize("Atomic operations are not supported".to_string())
429            }
430            casper_wasm::SerializationError::UnknownOpcode(_) => {
431                PreprocessingError::Deserialize("Encountered an unsupported operation".to_string())
432            }
433            casper_wasm::SerializationError::Other(
434                "Enable the multi_value feature to deserialize more than one function result",
435            ) => {
436                // Due to the way casper-wasm crate works, it's always deserializes opcodes
437                // from multi_value proposal but if the feature is not enabled, then it will
438                // error with very specific message (as compared to other extensions).
439                //
440                // That's OK since we'd prefer to not inspect deserialized bytecode. We
441                // can simply replace the error message with a more user friendly one.
442                PreprocessingError::Deserialize(
443                    "Multi value extension is not supported".to_string(),
444                )
445            }
446            _ => deserialize_error.into(),
447        }
448    })
449}
450
451/// Creates new wasm module from entry points.
452pub fn get_module_from_entry_points(
453    entry_point_names: Vec<&str>,
454    mut module: Module,
455) -> Result<Vec<u8>, ExecError> {
456    let export_section = module
457        .export_section()
458        .ok_or_else(|| ExecError::FunctionNotFound(String::from("Missing Export Section")))?;
459
460    let maybe_missing_name: Option<String> = entry_point_names
461        .iter()
462        .find(|name| {
463            !export_section
464                .entries()
465                .iter()
466                .any(|export_entry| export_entry.field() == **name)
467        })
468        .map(|s| String::from(*s));
469
470    match maybe_missing_name {
471        Some(missing_name) => Err(ExecError::FunctionNotFound(missing_name)),
472        None => {
473            casper_wasm_utils::optimize(&mut module, entry_point_names)?;
474            casper_wasm::serialize(module).map_err(ExecError::ParityWasm)
475        }
476    }
477}
478
479/// Returns the cost of executing a single instruction.
480///
481/// This is benchmarked on a reference hardware, and calculated based on the multiplies of the
482/// cheapest opcode (nop) in the given instruction.
483///
484/// For instance, nop will always have cycle cost of 1, and all other opcodes will have a multiple
485/// of that.
486///
487/// The number of cycles for each instruction correlates, but not directly, to the reference x86_64
488/// CPU cycles it takes to execute the instruction as the interpreter does extra work to invoke an
489/// instruction.
490pub fn cycles_for_instruction(instruction: &Instruction) -> u32 {
491    match instruction {
492        // The following instructions signal the beginning of a block, loop, or if construct. They
493        // don't have any static cost. Validated in benchmarks.
494        Instruction::Loop(_) => 1,
495        Instruction::Block(_) => 1,
496        Instruction::Else => 1,
497        Instruction::End => 1,
498
499        Instruction::Unreachable => 1,
500        Instruction::Nop => 1,
501
502        Instruction::If(_) => 3,
503
504        // These instructions are resuming execution from previously saved location (produced by
505        // loop or block).
506        Instruction::Br(_) => 1,
507        Instruction::BrIf(_) => 3,
508        Instruction::BrTable(_) => 5,
509
510        Instruction::Return => 1,
511
512        // Call opcodes are charged for each of the opcode individually. Validated in benchmarks.
513        Instruction::Call(_) => 22,
514        Instruction::CallIndirect(_, _) => 27,
515
516        Instruction::Drop => 1,
517
518        // Select opcode is validated in benchmarks.
519        Instruction::Select => 11,
520
521        Instruction::GetLocal(_) | Instruction::SetLocal(_) | Instruction::TeeLocal(_) => 5,
522
523        Instruction::GetGlobal(_) => 7,
524        Instruction::SetGlobal(_) => 5,
525
526        Instruction::I64Load32S(_, _)
527        | Instruction::F32Load(_, _)
528        | Instruction::F64Load(_, _)
529        | Instruction::I32Load(_, _)
530        | Instruction::I64Load(_, _)
531        | Instruction::I32Load8S(_, _)
532        | Instruction::I64Load32U(_, _)
533        | Instruction::I64Load8U(_, _)
534        | Instruction::I64Load8S(_, _)
535        | Instruction::I32Load8U(_, _)
536        | Instruction::I64Load16U(_, _)
537        | Instruction::I32Load16U(_, _)
538        | Instruction::I64Load16S(_, _)
539        | Instruction::I32Load16S(_, _) => 8,
540
541        Instruction::I32Store(_, _)
542        | Instruction::I64Store(_, _)
543        | Instruction::F32Store(_, _)
544        | Instruction::F64Store(_, _)
545        | Instruction::I32Store8(_, _)
546        | Instruction::I32Store16(_, _)
547        | Instruction::I64Store8(_, _)
548        | Instruction::I64Store16(_, _)
549        | Instruction::I64Store32(_, _) => 4,
550
551        Instruction::CurrentMemory(_) => 5,
552        Instruction::GrowMemory(_) => 5,
553
554        Instruction::I32Const(_)
555        | Instruction::I64Const(_)
556        | Instruction::F32Const(_)
557        | Instruction::F64Const(_) => 5,
558
559        Instruction::I32Eqz
560        | Instruction::I32Eq
561        | Instruction::I32Ne
562        | Instruction::I32LtS
563        | Instruction::I32LtU
564        | Instruction::I32GtS
565        | Instruction::I32GtU
566        | Instruction::I32LeS
567        | Instruction::I32LeU
568        | Instruction::I32GeS
569        | Instruction::I32GeU
570        | Instruction::I64Eqz
571        | Instruction::I64Eq
572        | Instruction::I64Ne
573        | Instruction::I64LtS
574        | Instruction::I64LtU
575        | Instruction::I64GtS
576        | Instruction::I64GtU
577        | Instruction::I64LeS
578        | Instruction::I64LeU
579        | Instruction::I64GeS
580        | Instruction::I64GeU => 5,
581
582        Instruction::F32Eq
583        | Instruction::F32Ne
584        | Instruction::F32Lt
585        | Instruction::F32Gt
586        | Instruction::F32Le
587        | Instruction::F32Ge
588        | Instruction::F64Eq
589        | Instruction::F64Ne
590        | Instruction::F64Lt
591        | Instruction::F64Gt
592        | Instruction::F64Le
593        | Instruction::F64Ge => 5,
594
595        Instruction::I32Clz | Instruction::I32Ctz | Instruction::I32Popcnt => 5,
596
597        Instruction::I32Add | Instruction::I32Sub => 5,
598
599        Instruction::I32Mul => 5,
600
601        Instruction::I32DivS
602        | Instruction::I32DivU
603        | Instruction::I32RemS
604        | Instruction::I32RemU => 5,
605
606        Instruction::I32And
607        | Instruction::I32Or
608        | Instruction::I32Xor
609        | Instruction::I32Shl
610        | Instruction::I32ShrS
611        | Instruction::I32ShrU
612        | Instruction::I32Rotl
613        | Instruction::I32Rotr
614        | Instruction::I64Clz
615        | Instruction::I64Ctz
616        | Instruction::I64Popcnt => 5,
617
618        Instruction::I64Add | Instruction::I64Sub => 5,
619        Instruction::I64Mul => 5,
620
621        Instruction::I64DivS
622        | Instruction::I64DivU
623        | Instruction::I64RemS
624        | Instruction::I64RemU => 5,
625
626        Instruction::I64And
627        | Instruction::I64Or
628        | Instruction::I64Xor
629        | Instruction::I64Shl
630        | Instruction::I64ShrS
631        | Instruction::I64ShrU
632        | Instruction::I64Rotl
633        | Instruction::I64Rotr => 5,
634
635        Instruction::F32Abs
636        | Instruction::F32Neg
637        | Instruction::F32Ceil
638        | Instruction::F32Floor
639        | Instruction::F32Trunc
640        | Instruction::F32Nearest
641        | Instruction::F32Sqrt
642        | Instruction::F32Add
643        | Instruction::F32Sub
644        | Instruction::F32Mul
645        | Instruction::F32Div
646        | Instruction::F32Min
647        | Instruction::F32Max
648        | Instruction::F32Copysign
649        | Instruction::F64Abs
650        | Instruction::F64Neg
651        | Instruction::F64Ceil
652        | Instruction::F64Floor
653        | Instruction::F64Trunc
654        | Instruction::F64Nearest
655        | Instruction::F64Sqrt
656        | Instruction::F64Add
657        | Instruction::F64Sub
658        | Instruction::F64Mul
659        | Instruction::F64Div
660        | Instruction::F64Min
661        | Instruction::F64Max
662        | Instruction::F64Copysign => 5,
663
664        Instruction::I32WrapI64 | Instruction::I64ExtendSI32 | Instruction::I64ExtendUI32 => 5,
665
666        Instruction::F32ConvertSI32
667        | Instruction::F32ConvertUI32
668        | Instruction::F32ConvertSI64
669        | Instruction::F32ConvertUI64
670        | Instruction::F32DemoteF64
671        | Instruction::F64ConvertSI32
672        | Instruction::F64ConvertUI32
673        | Instruction::F64ConvertSI64
674        | Instruction::F64ConvertUI64
675        | Instruction::F64PromoteF32 => 5,
676
677        // Unsupported reinterpretation operators for floats.
678        Instruction::I32ReinterpretF32
679        | Instruction::I64ReinterpretF64
680        | Instruction::F32ReinterpretI32
681        | Instruction::F64ReinterpretI64 => 5,
682
683        Instruction::SignExt(SignExtInstruction::I32Extend8S)
684        | Instruction::SignExt(SignExtInstruction::I32Extend16S)
685        | Instruction::SignExt(SignExtInstruction::I64Extend8S)
686        | Instruction::SignExt(SignExtInstruction::I64Extend16S)
687        | Instruction::SignExt(SignExtInstruction::I64Extend32S) => 5,
688
689        Instruction::I32TruncUF32 | Instruction::I64TruncSF32 => 40,
690
691        Instruction::I32TruncSF32 | Instruction::I64TruncUF32 => 42,
692
693        Instruction::I32TruncSF64
694        | Instruction::I32TruncUF64
695        | Instruction::I64TruncUF64
696        | Instruction::I64TruncSF64 => 195,
697    }
698}
699
700struct RuledOpcodeCosts(OpcodeCosts);
701
702impl RuledOpcodeCosts {
703    /// Returns the cost multiplier of executing a single instruction.
704    fn instruction_cost_multiplier(&self, instruction: &Instruction) -> Option<u32> {
705        let costs = self.0;
706
707        // Obtain the gas cost multiplier for the instruction.
708        match instruction {
709            Instruction::Unreachable => Some(costs.unreachable),
710            Instruction::Nop => Some(costs.nop),
711
712            // Control flow class of opcodes is charged for each of the opcode individually.
713            Instruction::Block(_) => Some(costs.control_flow.block),
714            Instruction::Loop(_) => Some(costs.control_flow.op_loop),
715            Instruction::If(_) => Some(costs.control_flow.op_if),
716            Instruction::Else => Some(costs.control_flow.op_else),
717            Instruction::End => Some(costs.control_flow.end),
718            Instruction::Br(_) => Some(costs.control_flow.br),
719            Instruction::BrIf(_) => Some(costs.control_flow.br_if),
720            Instruction::BrTable(br_table_data) => {
721                // If we're unable to fit table size in `u32` to measure the cost, then such wasm
722                // would be rejected. This is unlikely scenario as we impose a limit
723                // for the amount of targets a `br_table` opcode can contain.
724                let br_table_size: u32 = br_table_data.table.len().try_into().ok()?;
725
726                let br_table_cost = costs.control_flow.br_table.cost;
727
728                let table_size_part =
729                    br_table_size.checked_mul(costs.control_flow.br_table.size_multiplier)?;
730
731                let br_table_cost = br_table_cost.checked_add(table_size_part)?;
732                Some(br_table_cost)
733            }
734            Instruction::Return => Some(costs.control_flow.op_return),
735            Instruction::Call(_) => Some(costs.control_flow.call),
736            Instruction::CallIndirect(_, _) => Some(costs.control_flow.call_indirect),
737            Instruction::Drop => Some(costs.control_flow.drop),
738            Instruction::Select => Some(costs.control_flow.select),
739
740            Instruction::GetLocal(_) | Instruction::SetLocal(_) | Instruction::TeeLocal(_) => {
741                Some(costs.local)
742            }
743            Instruction::GetGlobal(_) | Instruction::SetGlobal(_) => Some(costs.global),
744
745            Instruction::I32Load(_, _)
746            | Instruction::I64Load(_, _)
747            | Instruction::F32Load(_, _)
748            | Instruction::F64Load(_, _)
749            | Instruction::I32Load8S(_, _)
750            | Instruction::I32Load8U(_, _)
751            | Instruction::I32Load16S(_, _)
752            | Instruction::I32Load16U(_, _)
753            | Instruction::I64Load8S(_, _)
754            | Instruction::I64Load8U(_, _)
755            | Instruction::I64Load16S(_, _)
756            | Instruction::I64Load16U(_, _)
757            | Instruction::I64Load32S(_, _)
758            | Instruction::I64Load32U(_, _) => Some(costs.load),
759
760            Instruction::I32Store(_, _)
761            | Instruction::I64Store(_, _)
762            | Instruction::F32Store(_, _)
763            | Instruction::F64Store(_, _)
764            | Instruction::I32Store8(_, _)
765            | Instruction::I32Store16(_, _)
766            | Instruction::I64Store8(_, _)
767            | Instruction::I64Store16(_, _)
768            | Instruction::I64Store32(_, _) => Some(costs.store),
769
770            Instruction::CurrentMemory(_) => Some(costs.current_memory),
771            Instruction::GrowMemory(_) => Some(costs.grow_memory),
772
773            Instruction::I32Const(_) | Instruction::I64Const(_) => Some(costs.op_const),
774
775            Instruction::F32Const(_) | Instruction::F64Const(_) => None, // float_const
776
777            Instruction::I32Eqz
778            | Instruction::I32Eq
779            | Instruction::I32Ne
780            | Instruction::I32LtS
781            | Instruction::I32LtU
782            | Instruction::I32GtS
783            | Instruction::I32GtU
784            | Instruction::I32LeS
785            | Instruction::I32LeU
786            | Instruction::I32GeS
787            | Instruction::I32GeU
788            | Instruction::I64Eqz
789            | Instruction::I64Eq
790            | Instruction::I64Ne
791            | Instruction::I64LtS
792            | Instruction::I64LtU
793            | Instruction::I64GtS
794            | Instruction::I64GtU
795            | Instruction::I64LeS
796            | Instruction::I64LeU
797            | Instruction::I64GeS
798            | Instruction::I64GeU => Some(costs.integer_comparison),
799
800            Instruction::F32Eq
801            | Instruction::F32Ne
802            | Instruction::F32Lt
803            | Instruction::F32Gt
804            | Instruction::F32Le
805            | Instruction::F32Ge
806            | Instruction::F64Eq
807            | Instruction::F64Ne
808            | Instruction::F64Lt
809            | Instruction::F64Gt
810            | Instruction::F64Le
811            | Instruction::F64Ge => None, // Unsupported comparison operators for floats.
812
813            Instruction::I32Clz | Instruction::I32Ctz | Instruction::I32Popcnt => Some(costs.bit),
814
815            Instruction::I32Add | Instruction::I32Sub => Some(costs.add),
816
817            Instruction::I32Mul => Some(costs.mul),
818
819            Instruction::I32DivS
820            | Instruction::I32DivU
821            | Instruction::I32RemS
822            | Instruction::I32RemU => Some(costs.div),
823
824            Instruction::I32And
825            | Instruction::I32Or
826            | Instruction::I32Xor
827            | Instruction::I32Shl
828            | Instruction::I32ShrS
829            | Instruction::I32ShrU
830            | Instruction::I32Rotl
831            | Instruction::I32Rotr
832            | Instruction::I64Clz
833            | Instruction::I64Ctz
834            | Instruction::I64Popcnt => Some(costs.bit),
835
836            Instruction::I64Add | Instruction::I64Sub => Some(costs.add),
837            Instruction::I64Mul => Some(costs.mul),
838
839            Instruction::I64DivS
840            | Instruction::I64DivU
841            | Instruction::I64RemS
842            | Instruction::I64RemU => Some(costs.div),
843
844            Instruction::I64And
845            | Instruction::I64Or
846            | Instruction::I64Xor
847            | Instruction::I64Shl
848            | Instruction::I64ShrS
849            | Instruction::I64ShrU
850            | Instruction::I64Rotl
851            | Instruction::I64Rotr => Some(costs.bit),
852
853            Instruction::F32Abs
854            | Instruction::F32Neg
855            | Instruction::F32Ceil
856            | Instruction::F32Floor
857            | Instruction::F32Trunc
858            | Instruction::F32Nearest
859            | Instruction::F32Sqrt
860            | Instruction::F32Add
861            | Instruction::F32Sub
862            | Instruction::F32Mul
863            | Instruction::F32Div
864            | Instruction::F32Min
865            | Instruction::F32Max
866            | Instruction::F32Copysign
867            | Instruction::F64Abs
868            | Instruction::F64Neg
869            | Instruction::F64Ceil
870            | Instruction::F64Floor
871            | Instruction::F64Trunc
872            | Instruction::F64Nearest
873            | Instruction::F64Sqrt
874            | Instruction::F64Add
875            | Instruction::F64Sub
876            | Instruction::F64Mul
877            | Instruction::F64Div
878            | Instruction::F64Min
879            | Instruction::F64Max
880            | Instruction::F64Copysign => None, // Unsupported math operators for floats.
881
882            Instruction::I32WrapI64 | Instruction::I64ExtendSI32 | Instruction::I64ExtendUI32 => {
883                Some(costs.conversion)
884            }
885
886            Instruction::I32TruncSF32
887            | Instruction::I32TruncUF32
888            | Instruction::I32TruncSF64
889            | Instruction::I32TruncUF64
890            | Instruction::I64TruncSF32
891            | Instruction::I64TruncUF32
892            | Instruction::I64TruncSF64
893            | Instruction::I64TruncUF64
894            | Instruction::F32ConvertSI32
895            | Instruction::F32ConvertUI32
896            | Instruction::F32ConvertSI64
897            | Instruction::F32ConvertUI64
898            | Instruction::F32DemoteF64
899            | Instruction::F64ConvertSI32
900            | Instruction::F64ConvertUI32
901            | Instruction::F64ConvertSI64
902            | Instruction::F64ConvertUI64
903            | Instruction::F64PromoteF32 => None, // Unsupported conversion operators for floats.
904
905            // Unsupported reinterpretation operators for floats.
906            Instruction::I32ReinterpretF32
907            | Instruction::I64ReinterpretF64
908            | Instruction::F32ReinterpretI32
909            | Instruction::F64ReinterpretI64 => None,
910
911            Instruction::SignExt(_) => Some(costs.sign),
912        }
913    }
914}
915
916impl Rules for RuledOpcodeCosts {
917    fn instruction_cost(&self, instruction: &Instruction) -> Option<u32> {
918        // The number of cycles for each instruction correlates, but not directly, to the reference
919        // x86_64 CPU cycles.
920        let cycles = cycles_for_instruction(instruction);
921
922        // The cost of executing an instruction is the number of cycles times the cost of a nop.
923        let multiplier = self.instruction_cost_multiplier(instruction)?;
924
925        cycles.checked_mul(multiplier)
926    }
927
928    fn memory_grow_cost(&self) -> Option<MemoryGrowCost> {
929        NonZeroU32::new(self.0.grow_memory).map(MemoryGrowCost::Linear)
930    }
931}
932
933#[cfg(test)]
934mod tests {
935    use casper_types::addressable_entity::DEFAULT_ENTRY_POINT_NAME;
936    use casper_wasm::{
937        builder,
938        elements::{CodeSection, Instructions},
939    };
940    use walrus::{FunctionBuilder, ModuleConfig, ValType};
941
942    use super::*;
943
944    #[test]
945    fn should_not_panic_on_empty_memory() {
946        // These bytes were generated during fuzz testing and are compiled from Wasm which
947        // deserializes to a `Module` with a memory section containing no entries.
948        const MODULE_BYTES_WITH_EMPTY_MEMORY: [u8; 61] = [
949            0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x09, 0x02, 0x60, 0x01, 0x7f,
950            0x01, 0x7f, 0x60, 0x00, 0x00, 0x03, 0x03, 0x02, 0x00, 0x01, 0x05, 0x01, 0x00, 0x08,
951            0x01, 0x01, 0x0a, 0x1d, 0x02, 0x18, 0x00, 0x20, 0x00, 0x41, 0x80, 0x80, 0x82, 0x80,
952            0x78, 0x70, 0x41, 0x80, 0x82, 0x80, 0x80, 0x7e, 0x4f, 0x22, 0x00, 0x1a, 0x20, 0x00,
953            0x0f, 0x0b, 0x02, 0x00, 0x0b,
954        ];
955
956        match preprocess(WasmConfig::default(), &MODULE_BYTES_WITH_EMPTY_MEMORY).unwrap_err() {
957            PreprocessingError::MissingMemorySection => (),
958            error => panic!("expected MissingMemorySection, got {:?}", error),
959        }
960    }
961
962    #[test]
963    fn should_not_overflow_in_export_section() {
964        let module = builder::module()
965            .function()
966            .signature()
967            .build()
968            .body()
969            .with_instructions(Instructions::new(vec![Instruction::Nop, Instruction::End]))
970            .build()
971            .build()
972            .export()
973            .field(DEFAULT_ENTRY_POINT_NAME)
974            .internal()
975            .func(u32::MAX)
976            .build()
977            // Memory section is mandatory
978            .memory()
979            .build()
980            .build();
981        let module_bytes = casper_wasm::serialize(module).expect("should serialize");
982        let error = preprocess(WasmConfig::default(), &module_bytes)
983            .expect_err("should fail with an error");
984        assert!(
985            matches!(
986                &error,
987                PreprocessingError::WasmValidation(WasmValidationError::MissingFunctionIndex { index: missing_index })
988                if *missing_index == u32::MAX
989            ),
990            "{:?}",
991            error,
992        );
993    }
994
995    #[test]
996    fn should_not_overflow_in_element_section() {
997        const CALL_FN_IDX: u32 = 0;
998
999        let module = builder::module()
1000            .function()
1001            .signature()
1002            .build()
1003            .body()
1004            .with_instructions(Instructions::new(vec![Instruction::Nop, Instruction::End]))
1005            .build()
1006            .build()
1007            // Export above function
1008            .export()
1009            .field(DEFAULT_ENTRY_POINT_NAME)
1010            .internal()
1011            .func(CALL_FN_IDX)
1012            .build()
1013            .table()
1014            .with_element(u32::MAX, vec![u32::MAX])
1015            .build()
1016            // Memory section is mandatory
1017            .memory()
1018            .build()
1019            .build();
1020        let module_bytes = casper_wasm::serialize(module).expect("should serialize");
1021        let error = preprocess(WasmConfig::default(), &module_bytes)
1022            .expect_err("should fail with an error");
1023        assert!(
1024            matches!(
1025                &error,
1026                PreprocessingError::WasmValidation(WasmValidationError::MissingFunctionIndex { index: missing_index })
1027                if *missing_index == u32::MAX
1028            ),
1029            "{:?}",
1030            error,
1031        );
1032    }
1033
1034    #[test]
1035    fn should_not_overflow_in_call_opcode() {
1036        let module = builder::module()
1037            .function()
1038            .signature()
1039            .build()
1040            .body()
1041            .with_instructions(Instructions::new(vec![
1042                Instruction::Call(u32::MAX),
1043                Instruction::End,
1044            ]))
1045            .build()
1046            .build()
1047            // Export above function
1048            .export()
1049            .field(DEFAULT_ENTRY_POINT_NAME)
1050            .build()
1051            // .with_sections(vec![Section::Start(u32::MAX)])
1052            // Memory section is mandatory
1053            .memory()
1054            .build()
1055            .build();
1056        let module_bytes = casper_wasm::serialize(module).expect("should serialize");
1057        let error = preprocess(WasmConfig::default(), &module_bytes)
1058            .expect_err("should fail with an error");
1059        assert!(
1060            matches!(
1061                &error,
1062                PreprocessingError::WasmValidation(WasmValidationError::MissingFunctionIndex { index: missing_index })
1063                if *missing_index == u32::MAX
1064            ),
1065            "{:?}",
1066            error,
1067        );
1068    }
1069
1070    #[test]
1071    fn should_not_overflow_in_start_section_without_code_section() {
1072        let module = builder::module()
1073            .with_section(Section::Start(u32::MAX))
1074            .memory()
1075            .build()
1076            .build();
1077        let module_bytes = casper_wasm::serialize(module).expect("should serialize");
1078
1079        let error = preprocess(WasmConfig::default(), &module_bytes)
1080            .expect_err("should fail with an error");
1081        assert!(
1082            matches!(
1083                &error,
1084                PreprocessingError::WasmValidation(WasmValidationError::MissingFunctionIndex { index: missing_index })
1085                if *missing_index == u32::MAX
1086            ),
1087            "{:?}",
1088            error,
1089        );
1090    }
1091
1092    #[test]
1093    fn should_not_overflow_in_start_section_with_code() {
1094        let module = builder::module()
1095            .with_section(Section::Start(u32::MAX))
1096            .with_section(Section::Code(CodeSection::with_bodies(Vec::new())))
1097            .memory()
1098            .build()
1099            .build();
1100        let module_bytes = casper_wasm::serialize(module).expect("should serialize");
1101        let error = preprocess(WasmConfig::default(), &module_bytes)
1102            .expect_err("should fail with an error");
1103        assert!(
1104            matches!(
1105                &error,
1106                PreprocessingError::WasmValidation(WasmValidationError::MissingFunctionIndex { index: missing_index })
1107                if *missing_index == u32::MAX
1108            ),
1109            "{:?}",
1110            error,
1111        );
1112    }
1113
1114    #[test]
1115    fn should_not_accept_multi_value_proposal_wasm() {
1116        let module_bytes = {
1117            let mut module = walrus::Module::with_config(ModuleConfig::new());
1118
1119            let _memory_id = module.memories.add_local(false, 11, None);
1120
1121            let mut func_with_locals =
1122                FunctionBuilder::new(&mut module.types, &[], &[ValType::I32, ValType::I64]);
1123
1124            func_with_locals.func_body().i64_const(0).i32_const(1);
1125
1126            let func_with_locals = func_with_locals.finish(vec![], &mut module.funcs);
1127
1128            let mut call_func = FunctionBuilder::new(&mut module.types, &[], &[]);
1129
1130            call_func.func_body().call(func_with_locals);
1131
1132            let call = call_func.finish(Vec::new(), &mut module.funcs);
1133
1134            module.exports.add(DEFAULT_ENTRY_POINT_NAME, call);
1135
1136            module.emit_wasm()
1137        };
1138        let error = preprocess(WasmConfig::default(), &module_bytes)
1139            .expect_err("should fail with an error");
1140        assert!(
1141            matches!(&error, PreprocessingError::Deserialize(msg)
1142            if msg == "Multi value extension is not supported"),
1143            "{:?}",
1144            error,
1145        );
1146    }
1147
1148    #[test]
1149    fn should_not_accept_atomics_proposal_wasm() {
1150        let module_bytes = {
1151            let mut module = walrus::Module::with_config(ModuleConfig::new());
1152
1153            let _memory_id = module.memories.add_local(false, 11, None);
1154
1155            let mut func_with_atomics = FunctionBuilder::new(&mut module.types, &[], &[]);
1156
1157            func_with_atomics.func_body().atomic_fence();
1158
1159            let func_with_atomics = func_with_atomics.finish(vec![], &mut module.funcs);
1160
1161            let mut call_func = FunctionBuilder::new(&mut module.types, &[], &[]);
1162
1163            call_func.func_body().call(func_with_atomics);
1164
1165            let call = call_func.finish(Vec::new(), &mut module.funcs);
1166
1167            module.exports.add(DEFAULT_ENTRY_POINT_NAME, call);
1168
1169            module.emit_wasm()
1170        };
1171        let error = preprocess(WasmConfig::default(), &module_bytes)
1172            .expect_err("should fail with an error");
1173        assert!(
1174            matches!(&error, PreprocessingError::Deserialize(msg)
1175            if msg == "Atomic operations are not supported"),
1176            "{:?}",
1177            error,
1178        );
1179    }
1180
1181    #[test]
1182    fn should_not_accept_bulk_proposal_wasm() {
1183        let module_bytes = {
1184            let mut module = walrus::Module::with_config(ModuleConfig::new());
1185
1186            let memory_id = module.memories.add_local(false, 11, None);
1187
1188            let mut func_with_bulk = FunctionBuilder::new(&mut module.types, &[], &[]);
1189
1190            func_with_bulk.func_body().memory_copy(memory_id, memory_id);
1191
1192            let func_with_bulk = func_with_bulk.finish(vec![], &mut module.funcs);
1193
1194            let mut call_func = FunctionBuilder::new(&mut module.types, &[], &[]);
1195
1196            call_func.func_body().call(func_with_bulk);
1197
1198            let call = call_func.finish(Vec::new(), &mut module.funcs);
1199
1200            module.exports.add(DEFAULT_ENTRY_POINT_NAME, call);
1201
1202            module.emit_wasm()
1203        };
1204        let error = preprocess(WasmConfig::default(), &module_bytes)
1205            .expect_err("should fail with an error");
1206        assert!(
1207            matches!(&error, PreprocessingError::Deserialize(msg)
1208            if msg == "Bulk memory operations are not supported"),
1209            "{:?}",
1210            error,
1211        );
1212    }
1213
1214    #[test]
1215    fn should_not_accept_simd_proposal_wasm() {
1216        let module_bytes = {
1217            let mut module = walrus::Module::with_config(ModuleConfig::new());
1218
1219            let _memory_id = module.memories.add_local(false, 11, None);
1220
1221            let mut func_with_simd = FunctionBuilder::new(&mut module.types, &[], &[]);
1222
1223            func_with_simd.func_body().v128_bitselect();
1224
1225            let func_with_simd = func_with_simd.finish(vec![], &mut module.funcs);
1226
1227            let mut call_func = FunctionBuilder::new(&mut module.types, &[], &[]);
1228
1229            call_func.func_body().call(func_with_simd);
1230
1231            let call = call_func.finish(Vec::new(), &mut module.funcs);
1232
1233            module.exports.add(DEFAULT_ENTRY_POINT_NAME, call);
1234
1235            module.emit_wasm()
1236        };
1237        let error = preprocess(WasmConfig::default(), &module_bytes)
1238            .expect_err("should fail with an error");
1239        assert!(
1240            matches!(&error, PreprocessingError::Deserialize(msg)
1241            if msg == "SIMD operations are not supported"),
1242            "{:?}",
1243            error,
1244        );
1245    }
1246}