// Section order for WebAssembly modules. // // Component sections are unordered and allow for duplicates, // so this isn't used for components. #[derive(Copy, Clone, Default, PartialOrd, Ord, PartialEq, Eq, Debug)] pubenum Order { #[default]
Initial, Type,
Import,
Function,
Table,
Memory,
Tag,
Global,
Export,
Start,
Element,
DataCount,
Code,
Data,
}
#[derive(Default)] pub(crate) struct ModuleState { /// Internal state that is incrementally built-up for the module being /// validated. This houses type information for all wasm items, like /// functions. Note that this starts out as a solely owned `Arc<T>` so we can /// get mutable access, but after we get to the code section this is never /// mutated to we can clone it cheaply and hand it to sub-validators. pub module: arc::MaybeOwned<Module>,
/// Where we are, order-wise, in the wasm binary.
order: Order,
/// The number of data segments in the data section (if present). pub data_segment_count: u32,
/// The number of functions we expect to be defined in the code section, or /// basically the length of the function section if it was found. The next /// index is where we are, in the code section index space, for the next /// entry in the code section (used to figure out what type is next for the /// function being validated). pub expected_code_bodies: Option<u32>,
const_expr_allocs: OperatorValidatorAllocations,
/// When parsing the code section, represents the current index in the section.
code_section_index: Option<usize>,
}
impl ModuleState { pubfn update_order(&mutself, order: Order, offset: usize) -> Result<()> { ifself.order >= order { return Err(BinaryReaderError::new("section out of order", offset));
}
self.order = order;
Ok(())
}
pubfn validate_end(&self, offset: usize) -> Result<()> { // Ensure that the data count section, if any, was correct. iflet Some(data_count) = self.module.data_count { if data_count != self.data_segment_count { return Err(BinaryReaderError::new( "data count and data section have inconsistent lengths",
offset,
));
}
} // Ensure that the function section, if nonzero, was paired with a code // section with the appropriate length. iflet Some(n) = self.expected_code_bodies { if n > 0 { return Err(BinaryReaderError::new( "function and code section have inconsistent lengths",
offset,
));
}
}
Ok(())
}
pubfn next_code_index_and_type(&mutself, offset: usize) -> Result<(u32, u32)> { let index = self
.code_section_index
.get_or_insert(self.module.num_imported_functions as usize);
if *index >= self.module.functions.len() { return Err(BinaryReaderError::new( "code section entry exceeds number of functions",
offset,
));
}
let ty = self.module.functions[*index];
*index += 1;
pubfn add_element_segment(
&mutself, mut e: Element,
features: &WasmFeatures,
types: &TypeList,
offset: usize,
) -> Result<()> { // the `funcref` value type is allowed all the way back to the MVP, so // don't check it here let element_ty = match &mut e.items { crate::ElementItems::Functions(_) => RefType::FUNC, crate::ElementItems::Expressions(ty, _) => { self.module.check_ref_type(ty, features, offset)?;
*ty
}
};
match e.kind {
ElementKind::Active {
table_index,
offset_expr,
} => { let table = self.module.table_at(table_index.unwrap_or(0), offset)?; if !types.reftype_is_subtype(element_ty, table.element_type) { return Err(BinaryReaderError::new(
format!( "type mismatch: invalid element type `{}` for table type `{}`",
ty_to_str(element_ty.into()),
ty_to_str(table.element_type.into()),
),
offset,
));
}
self.check_const_expr(&offset_expr, table.index_type(), features, types)?;
}
ElementKind::Passive | ElementKind::Declared => { if !features.bulk_memory() { return Err(BinaryReaderError::new( "bulk memory must be enabled",
offset,
));
}
}
}
let validate_count = |count: u32| -> Result<(), BinaryReaderError> { if count > MAX_WASM_TABLE_ENTRIES as u32 {
Err(BinaryReaderError::new( "number of elements is out of bounds",
offset,
))
} else {
Ok(())
}
}; match e.items { crate::ElementItems::Functions(reader) => { let count = reader.count();
validate_count(count)?; for f in reader.into_iter_with_offsets() { let (offset, f) = f?; self.module.get_func_type(f, types, offset)?; self.module.assert_mut().function_references.insert(f);
}
} crate::ElementItems::Expressions(ty, reader) => {
validate_count(reader.count())?; for expr in reader { self.check_const_expr(&expr?, ValType::Ref(ty), features, types)?;
}
}
} self.module.assert_mut().element_types.push(element_ty);
Ok(())
}
fn validate_global(&mutself, index: u32) -> Result<()> { let module = &self.resources.module; let global = module.global_at(index, self.offset)?;
if index >= module.num_imported_globals && !self.features.gc() { return Err(BinaryReaderError::new( "constant expression required: global.get of locally defined global", self.offset,
));
} if global.mutable { return Err(BinaryReaderError::new( "constant expression required: global.get of mutable global", self.offset,
));
}
Ok(())
}
// Functions in initialization expressions are only valid in // element segment initialization expressions and globals. In // these contexts we want to record all function references. // // Initialization expressions can also be found in the data // section, however. A `RefFunc` instruction in those situations // is always invalid and needs to produce a validation error. In // this situation, though, we can no longer modify // the state since it's been "snapshot" already for // parallel validation of functions. // // If we cannot modify the function references then this function // *should* result in a validation error, but we defer that // validation error to happen later. The `uninserted_funcref` // boolean here is used to track this and will cause a panic // (aka a fuzz bug) if we somehow forget to emit an error somewhere // else. fn insert_ref_func(&mutself, index: u32) { ifself.order == Order::Data { self.uninserted_funcref = true;
} else { self.resources
.module
.assert_mut()
.function_references
.insert(index);
}
}
}
// `global.get` is a valid const expression for imported, immutable // globals.
(@visit $self:ident visit_global_get $idx:ident) => {{
$self.validate_global($idx)?;
$self.validator().visit_global_get($idx)
}}; // `ref.func`, if it's in a `global` initializer, will insert into // the set of referenced functions so it's processed here.
(@visit $self:ident visit_ref_func $idx:ident) => {{
$self.insert_ref_func($idx);
$self.validator().visit_ref_func($idx)
}};
fn check_table_type(
&self,
ty: &mut TableType,
features: &WasmFeatures,
types: &TypeList,
offset: usize,
) -> Result<()> { // The `funcref` value type is allowed all the way back to the MVP, so // don't check it here. if ty.element_type != RefType::FUNCREF { self.check_ref_type(&mut ty.element_type, features, offset)?
}
if ty.table64 && !features.memory64() { return Err(BinaryReaderError::new( "memory64 must be enabled for 64-bit tables",
offset,
));
}
self.check_limits(ty.initial, ty.maximum, offset)?; if ty.initial > MAX_WASM_TABLE_ENTRIES as u64 { return Err(BinaryReaderError::new( "minimum table size is out of bounds",
offset,
));
}
if ty.shared { if !features.shared_everything_threads() { return Err(BinaryReaderError::new( "shared tables require the shared-everything-threads proposal",
offset,
));
}
if !types.reftype_is_shared(ty.element_type) { return Err(BinaryReaderError::new( "shared tables must have a shared element type",
offset,
));
}
}
Ok(())
}
fn check_memory_type(
&self,
ty: &MemoryType,
features: &WasmFeatures,
offset: usize,
) -> Result<()> { self.check_limits(ty.initial, ty.maximum, offset)?; let (page_size, page_size_log2) = iflet Some(page_size_log2) = ty.page_size_log2 { if !features.custom_page_sizes() { return Err(BinaryReaderError::new( "the custom page sizes proposal must be enabled to \
customize a memory's page size",
offset,
));
} // Currently 2**0 and 2**16 are the only valid page sizes, but this // may be relaxed to allow any power of two in the future. if page_size_log2 != 0 && page_size_log2 != 16 { return Err(BinaryReaderError::new("invalid custom page size", offset));
} let page_size = 1_u64 << page_size_log2;
debug_assert!(page_size.is_power_of_two());
debug_assert!(page_size == DEFAULT_WASM_PAGE_SIZE || page_size == 1);
(page_size, page_size_log2)
} else { let page_size_log2 = 16;
debug_assert_eq!(DEFAULT_WASM_PAGE_SIZE, 1 << page_size_log2);
(DEFAULT_WASM_PAGE_SIZE, page_size_log2)
}; let (true_maximum, err) = if ty.memory64 { if !features.memory64() { return Err(BinaryReaderError::new( "memory64 must be enabled for 64-bit memories",
offset,
));
}
(
max_wasm_memory64_pages(page_size),
format!( "memory size must be at most 2**{} pages", 64 - page_size_log2
),
)
} else { let max = max_wasm_memory32_pages(page_size);
(
max,
format!("memory size must be at most {max} pages (4GiB)"),
)
}; if ty.initial > true_maximum { return Err(BinaryReaderError::new(err, offset));
} iflet Some(maximum) = ty.maximum { if maximum > true_maximum { return Err(BinaryReaderError::new(err, offset));
}
} if ty.shared { if !features.threads() { return Err(BinaryReaderError::new( "threads must be enabled for shared memories",
offset,
));
} if ty.maximum.is_none() { return Err(BinaryReaderError::new( "shared memory must have maximum size",
offset,
));
}
}
Ok(())
}
#[cfg(feature = "component-model")] pub(crate) fn imports_for_module_type(
&self,
offset: usize,
) -> Result<IndexMap<(String, String), EntityType>> { // Ensure imports are unique, which is a requirement of the component model self.imports
.iter()
.map(|((module, name), types)| { if types.len() != 1 {
bail!(
offset, "module has a duplicate import name `{module}:{name}` \
that is not allowed in components",
);
}
Ok(((module.clone(), name.clone()), types[0]))
})
.collect::<Result<_>>()
}
fn check_value_type(
&self,
ty: &mut ValType,
features: &WasmFeatures,
offset: usize,
) -> Result<()> { // The above only checks the value type for features. // We must check it if it's a reference. match ty {
ValType::Ref(rt) => self.check_ref_type(rt, features, offset),
_ => features
.check_value_type(*ty)
.map_err(|e| BinaryReaderError::new(e, offset)),
}
}
fn check_heap_type(&self, ty: &mut HeapType, offset: usize) -> Result<()> { // Check that the heap type is valid. let type_index = match ty {
HeapType::Abstract { .. } => return Ok(()),
HeapType::Concrete(type_index) => type_index,
}; match type_index {
UnpackedIndex::Module(idx) => { let id = self.type_id_at(*idx, offset)?;
*type_index = UnpackedIndex::Id(id);
Ok(())
} // Types at this stage should not be canonicalized. All // canonicalized types should already be validated meaning they // shouldn't be double-checked here again.
UnpackedIndex::RecGroup(_) | UnpackedIndex::Id(_) => unreachable!(),
}
}
fn check_tag_type(
&self,
ty: &TagType,
features: &WasmFeatures,
types: &TypeList,
offset: usize,
) -> Result<()> { if !features.exceptions() { return Err(BinaryReaderError::new( "exceptions proposal not enabled",
offset,
));
} let ty = self.func_type_at(ty.func_type_idx, types, offset)?; if !ty.results().is_empty() && !features.stack_switching() { return Err(BinaryReaderError::new( "invalid exception type: non-empty tag result type",
offset,
));
}
Ok(())
}
fn check_global_type(
&self,
ty: &mut GlobalType,
features: &WasmFeatures,
types: &TypeList,
offset: usize,
) -> Result<()> { self.check_value_type(&mut ty.content_type, features, offset)?; if ty.shared { if !features.shared_everything_threads() { return Err(BinaryReaderError::new( "shared globals require the shared-everything-threads proposal",
offset,
));
} if !types.valtype_is_shared(ty.content_type) { return Err(BinaryReaderError::new( "shared globals must have a shared value type",
offset,
));
}
}
Ok(())
}
fn check_limits<T>(&self, initial: T, maximum: Option<T>, offset: usize) -> Result<()> where
T: Into<u64>,
{ iflet Some(max) = maximum { if initial.into() > max.into() { return Err(BinaryReaderError::new( "size minimum must not be greater than maximum",
offset,
));
}
}
Ok(())
}
fn type_id_at(&self, idx: u32, offset: usize) -> Result<CoreTypeId> { self.types
.get(idx as usize)
.copied()
.ok_or_else(|| format_err!(offset, "unknown type {idx}: type index out of bounds"))
}
/// The implementation of [`WasmModuleResources`] used by /// [`Validator`](crate::Validator). #[derive(Debug)] pubstruct ValidatorResources(pub(crate) Arc<Module>);
impl WasmModuleResources for ValidatorResources { fn table_at(&self, at: u32) -> Option<TableType> { self.0.tables.get(at as usize).cloned()
}
fn sub_type_at(&self, at: u32) -> Option<&SubType> { let id = *self.0.types.get(at as usize)?; let types = self.0.snapshot.as_ref().unwrap();
Some(&types[id])
}
let inner = core::mem::replace(&mutself.inner, Inner::Empty); let x = match inner {
Inner::Owned(x) => x,
_ => Self::unreachable(),
}; let x = Arc::new(x); self.inner = Inner::Shared(x);
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.