Skip to content

Commit

Permalink
Support Wasm custom sections parsing and processing (#1085)
Browse files Browse the repository at this point in the history
* add Config::ignore_custom_sections

* add custom sections processing to Wasm module parser

* differentiate between CustomSections[Builder]

* add Module::custom_sections iterator getter

* remove unused append method

* optimize memory allocations of custom sections

Now all names and data of all custom sections are stored in a single vector instead of having many small allocations for many custom sections. This is a guard against malicious Wasm inputs with tons of tiny custom sections putting heavy strain on the memory allocator. Also with this implementation Drop can be simpler, only having to deallocate two vectors.

* add test to check if the optimization works

* change docs

* improve documentation

* apply rustfmt
  • Loading branch information
Robbepop authored Jun 24, 2024
1 parent f1f92e4 commit 37ae60d
Show file tree
Hide file tree
Showing 8 changed files with 281 additions and 20 deletions.
16 changes: 16 additions & 0 deletions crates/wasmi/src/engine/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ pub struct Config {
floats: bool,
/// Is `true` if Wasmi executions shall consume fuel.
consume_fuel: bool,
/// Is `true` if Wasmi shall ignore Wasm custom sections when parsing Wasm modules.
ignore_custom_sections: bool,
/// The configured fuel costs of all Wasmi bytecode instructions.
fuel_costs: FuelCosts,
/// The mode of Wasm to Wasmi bytecode compilation.
Expand Down Expand Up @@ -180,6 +182,7 @@ impl Default for Config {
extended_const: true,
floats: true,
consume_fuel: false,
ignore_custom_sections: false,
fuel_costs: FuelCosts::default(),
compilation_mode: CompilationMode::default(),
limits: EnforcedLimits::default(),
Expand Down Expand Up @@ -348,6 +351,19 @@ impl Config {
self.consume_fuel
}

/// Configures whether Wasmi will ignore custom sections when parsing Wasm modules.
///
/// Default value: `false`
pub fn ignore_custom_sections(&mut self, enable: bool) -> &mut Self {
self.ignore_custom_sections = enable;
self
}

/// Returns `true` if the [`Config`] mandates to ignore Wasm custom sections when parsing Wasm modules.
pub(crate) fn get_ignore_custom_sections(&self) -> bool {
self.ignore_custom_sections
}

/// Returns the configured [`FuelCosts`].
pub(crate) fn fuel_costs(&self) -> &FuelCosts {
&self.fuel_costs
Expand Down
2 changes: 2 additions & 0 deletions crates/wasmi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,8 @@ pub use self::{
linker::{state, Linker, LinkerBuilder},
memory::{Memory, MemoryType},
module::{
CustomSection,
CustomSectionsIter,
ExportType,
ImportType,
InstancePre,
Expand Down
6 changes: 5 additions & 1 deletion crates/wasmi/src/module/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use super::{
export::ExternIdx,
import::FuncTypeIdx,
ConstExpr,
CustomSectionsBuilder,
DataSegments,
ElementSegment,
ExternTypeIdx,
Expand Down Expand Up @@ -33,6 +34,7 @@ use std::{boxed::Box, collections::BTreeMap, sync::Arc, vec::Vec};
pub struct ModuleBuilder {
pub header: ModuleHeader,
pub data_segments: DataSegmentsBuilder,
pub custom_sections: CustomSectionsBuilder,
}

/// A builder for a WebAssembly [`Module`] header.
Expand Down Expand Up @@ -132,10 +134,11 @@ impl ModuleImportsBuilder {

impl ModuleBuilder {
/// Creates a new [`ModuleBuilder`] for the given [`Engine`].
pub fn new(header: ModuleHeader) -> Self {
pub fn new(header: ModuleHeader, custom_sections: CustomSectionsBuilder) -> Self {
Self {
header,
data_segments: DataSegments::build(),
custom_sections,
}
}
}
Expand Down Expand Up @@ -419,6 +422,7 @@ impl ModuleBuilder {
engine: engine.clone(),
header: self.header,
data_segments: self.data_segments.finish(),
custom_sections: self.custom_sections.finish(),
}
}
}
166 changes: 166 additions & 0 deletions crates/wasmi/src/module/custom_section.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
use core::slice;
use std::{str, vec::Vec};

/// Wasm custom sections.
#[derive(Default, Debug)]
pub struct CustomSections {
inner: CustomSectionsInner,
}

impl CustomSections {
/// Returns an iterator over the [`CustomSection`]s stored in `self`.
#[inline]
pub fn iter(&self) -> CustomSectionsIter {
self.inner.iter()
}
}

/// A builder for [`CustomSections`].
#[derive(Default, Debug)]
pub struct CustomSectionsBuilder {
inner: CustomSectionsInner,
}

impl CustomSectionsBuilder {
/// Pushes a new custom section segment to the [`CustomSectionsBuilder`].
#[inline]
pub fn push(&mut self, name: &str, data: &[u8]) {
self.inner.push(name, data);
}

/// Finalize construction of the [`CustomSections`].
#[inline]
pub fn finish(self) -> CustomSections {
CustomSections { inner: self.inner }
}
}

/// Internal representation of [`CustomSections`].
#[derive(Debug, Default)]
pub struct CustomSectionsInner {
/// The name and data lengths of each Wasm custom section.
items: Vec<CustomSectionInner>,
/// The combined name and data of all Wasm custom sections.
names_and_data: Vec<u8>,
}

/// Internal representation of a Wasm [`CustomSection`].
#[derive(Debug, Copy, Clone)]
pub struct CustomSectionInner {
/// The length in bytes of the Wasm custom section name.
len_name: usize,
/// The length in bytes of the Wasm custom section data.
len_data: usize,
}

impl CustomSectionsInner {
/// Pushes a new custom section segment to the [`CustomSectionsBuilder`].
#[inline]
pub fn push(&mut self, name: &str, data: &[u8]) {
let name_bytes = name.as_bytes();
self.names_and_data.extend_from_slice(name_bytes);
self.names_and_data.extend_from_slice(data);
self.items.push(CustomSectionInner {
len_name: name_bytes.len(),
len_data: data.len(),
})
}

/// Returns an iterator over the [`CustomSection`]s stored in `self`.
#[inline]
pub fn iter(&self) -> CustomSectionsIter {
CustomSectionsIter {
items: self.items.iter(),
names_and_data: &self.names_and_data[..],
}
}
}

/// A Wasm custom section.
#[derive(Debug)]
pub struct CustomSection<'a> {
/// The name of the custom section.
name: &'a str,
/// The undecoded data of the custom section.
data: &'a [u8],
}

impl<'a> CustomSection<'a> {
/// Returns the name or identifier of the [`CustomSection`].
#[inline]
pub fn name(&self) -> &'a str {
self.name
}

/// Returns a shared reference to the data of the [`CustomSection`].
#[inline]
pub fn data(&self) -> &'a [u8] {
self.data
}
}

/// An iterator over the custom sections of a Wasm module.
#[derive(Debug)]
pub struct CustomSectionsIter<'a> {
items: slice::Iter<'a, CustomSectionInner>,
names_and_data: &'a [u8],
}

impl<'a> Iterator for CustomSectionsIter<'a> {
type Item = CustomSection<'a>;

#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.items.size_hint()
}

#[inline]
fn next(&mut self) -> Option<Self::Item> {
let item = self.items.next()?;
let names_and_data = self.names_and_data;
let (name, names_and_data) = names_and_data.split_at(item.len_name);
let (data, names_and_data) = names_and_data.split_at(item.len_data);
self.names_and_data = names_and_data;
// Safety: We encoded this part of the data buffer from the bytes of a string previously.
let name = unsafe { str::from_utf8_unchecked(name) };
Some(CustomSection { name, data })
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn it_works() {
let mut builder = CustomSectionsBuilder::default();
builder.push("A", b"first");
builder.push("B", b"second");
builder.push("C", b"third");
builder.push("", b"fourth"); // empty name
builder.push("E", &[]); // empty data
let custom_sections = builder.finish();
let mut iter = custom_sections.iter();
assert_eq!(
iter.next().map(|s| (s.name(), s.data())),
Some(("A", &b"first"[..]))
);
assert_eq!(
iter.next().map(|s| (s.name(), s.data())),
Some(("B", &b"second"[..]))
);
assert_eq!(
iter.next().map(|s| (s.name(), s.data())),
Some(("C", &b"third"[..]))
);
assert_eq!(
iter.next().map(|s| (s.name(), s.data())),
Some(("", &b"fourth"[..]))
);
assert_eq!(
iter.next().map(|s| (s.name(), s.data())),
Some(("E", &b""[..]))
);
assert_eq!(iter.next().map(|s| (s.name(), s.data())), None);
}
}
30 changes: 24 additions & 6 deletions crates/wasmi/src/module/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod builder;
mod custom_section;
mod data;
mod element;
mod export;
Expand All @@ -12,24 +13,26 @@ pub(crate) mod utils;

use self::{
builder::ModuleBuilder,
custom_section::{CustomSections, CustomSectionsBuilder},
export::ExternIdx,
global::Global,
import::{ExternTypeIdx, Import},
parser::ModuleParser,
};
pub(crate) use self::{
data::{DataSegment, DataSegments, InitDataSegment, PassiveDataSegmentBytes},
element::{ElementSegment, ElementSegmentItems, ElementSegmentKind},
init_expr::ConstExpr,
utils::WasmiValueType,
};
pub use self::{
custom_section::{CustomSection, CustomSectionsIter},
export::{ExportType, FuncIdx, MemoryIdx, ModuleExportsIter, TableIdx},
global::GlobalIdx,
import::{FuncTypeIdx, ImportName},
instantiate::{InstancePre, InstantiationError},
read::{Read, ReadError},
};
pub(crate) use self::{
data::{DataSegment, DataSegments, InitDataSegment, PassiveDataSegmentBytes},
element::{ElementSegment, ElementSegmentItems, ElementSegmentKind},
init_expr::ConstExpr,
utils::WasmiValueType,
};
use crate::{
collections::Map,
engine::{CompiledFunc, DedupFuncType, EngineWeak},
Expand All @@ -51,6 +54,7 @@ pub struct Module {
engine: Engine,
header: ModuleHeader,
data_segments: DataSegments,
custom_sections: CustomSections,
}

/// A parsed and validated WebAssembly module header.
Expand Down Expand Up @@ -446,6 +450,20 @@ impl Module {
}
}
}

/// Returns an iterator yielding the custom sections of the Wasm [`Module`].
///
/// # Note
///
/// The returned iterator will yield no items if [`Config::ignore_custom_sections`]
/// is set to `true` even if the original Wasm module contains custom sections.
///
///
/// [`Config::ignore_custom_sections`]: crate::Config::ignore_custom_sections
#[inline]
pub fn custom_sections(&self) -> CustomSectionsIter {
self.custom_sections.iter()
}
}

/// An iterator over the imports of a [`Module`].
Expand Down
15 changes: 15 additions & 0 deletions crates/wasmi/src/module/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use super::{
export::ExternIdx,
global::Global,
import::{FuncTypeIdx, Import},
CustomSectionsBuilder,
ElementSegment,
FuncIdx,
ModuleBuilder,
Expand All @@ -19,6 +20,7 @@ use crate::{
use core::ops::Range;
use std::boxed::Box;
use wasmparser::{
CustomSectionReader,
DataSectionReader,
ElementSectionReader,
Encoding,
Expand Down Expand Up @@ -493,6 +495,19 @@ impl ModuleParser {
Ok(())
}

/// Process a single Wasm custom section.
fn process_custom_section(
&mut self,
custom_sections: &mut CustomSectionsBuilder,
reader: CustomSectionReader,
) -> Result<(), Error> {
if self.engine.config().get_ignore_custom_sections() {
return Ok(());
}
custom_sections.push(reader.name(), reader.data());
Ok(())
}

/// Process an unexpected, unsupported or malformed Wasm module section payload.
fn process_invalid_payload(&mut self, payload: Payload<'_>) -> Result<(), Error> {
if let Some(validator) = &mut self.validator {
Expand Down
Loading

0 comments on commit 37ae60d

Please sign in to comment.