Skip to content

Commit

Permalink
feat: Implement safe, checked module artifact deserialization
Browse files Browse the repository at this point in the history
Enable rkyv validation of serialized module artifacts.

Required additions:
* derive the required CheckBytes trait for all types
* Add `_checked` variants of all the deserialization functions

Also enables the `strict` feature of rkyv by default.
This will ensure consistent archive binary layout across architectures
and Rust compiler versions.
  • Loading branch information
theduke committed Mar 31, 2023
1 parent 9cb4bf3 commit 47cc5bb
Show file tree
Hide file tree
Showing 29 changed files with 327 additions and 28 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions lib/api/src/js/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,18 @@ impl Module {
return Err(DeserializeError::Generic("You need to enable the `js-serializable-module` feature flag to deserialize a `Module`".to_string()));
}

pub fn deserialize_checked(
_engine: &impl AsEngineRef,
_bytes: impl IntoBytes,
) -> Result<Self, DeserializeError> {
#[cfg(feature = "js-serializable-module")]
return Self::from_binary(_engine, &_bytes.into_bytes())
.map_err(|e| DeserializeError::Compiler(e));

#[cfg(not(feature = "js-serializable-module"))]
return Err(DeserializeError::Generic("You need to enable the `js-serializable-module` feature flag to deserialize a `Module`".to_string()));
}

pub unsafe fn deserialize_from_file(
engine: &impl AsEngineRef,
path: impl AsRef<Path>,
Expand All @@ -246,6 +258,14 @@ impl Module {
Self::deserialize(engine, bytes)
}

pub fn deserialize_from_file_checked(
engine: &impl AsEngineRef,
path: impl AsRef<Path>,
) -> Result<Self, DeserializeError> {
let bytes = std::fs::read(path.as_ref())?;
Self::deserialize_checked(engine, bytes)
}

pub fn set_name(&mut self, name: &str) -> bool {
self.name = Some(name.to_string());
true
Expand Down
52 changes: 52 additions & 0 deletions lib/api/src/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,8 @@ impl Module {

/// Deserializes a serialized Module binary into a `Module`.
///
/// Note: You should usually prefer the safe [`Module::deserialize_checked`].
///
/// # Important
///
/// This function only accepts a custom binary format, which will be different
Expand Down Expand Up @@ -253,6 +255,56 @@ impl Module {
Ok(Self(module_imp::Module::deserialize(engine, bytes)?))
}

/// Deserializes a serialized Module binary into a `Module`.
///
/// # Important
///
/// This function only accepts a custom binary format, which will be different
/// than the `wasm` binary format and may change among Wasmer versions.
/// (it should be the result of the serialization of a Module via the
/// `Module::serialize` method.).
///
/// # Usage
///
/// ```ignore
/// # use wasmer::*;
/// # fn main() -> anyhow::Result<()> {
/// # let mut store = Store::default();
/// let module = Module::deserialize_checked(&store, serialized_data)?;
/// # Ok(())
/// # }
/// ```
pub fn deserialize_checked(
engine: &impl AsEngineRef,
bytes: impl IntoBytes,
) -> Result<Self, DeserializeError> {
Ok(Self(module_imp::Module::deserialize_checked(
engine, bytes,
)?))
}

/// Deserializes a a serialized Module located in a `Path` into a `Module`.
/// > Note: the module has to be serialized before with the `serialize` method.
///
/// # Usage
///
/// ```ignore
/// # use wasmer::*;
/// # let mut store = Store::default();
/// # fn main() -> anyhow::Result<()> {
/// let module = Module::deserialize_from_file(&store, path)?;
/// # Ok(())
/// # }
/// ```
pub fn deserialize_from_file_checked(
engine: &impl AsEngineRef,
path: impl AsRef<Path>,
) -> Result<Self, DeserializeError> {
Ok(Self(module_imp::Module::deserialize_from_file_checked(
engine, path,
)?))
}

/// Deserializes a a serialized Module located in a `Path` into a `Module`.
/// > Note: the module has to be serialized before with the `serialize` method.
///
Expand Down
25 changes: 25 additions & 0 deletions lib/api/src/sys/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,19 @@ impl Module {
Ok(Self::from_artifact(artifact))
}

pub fn deserialize_checked(
engine: &impl AsEngineRef,
bytes: impl IntoBytes,
) -> Result<Self, DeserializeError> {
let bytes = bytes.into_bytes();
let artifact = engine
.as_engine_ref()
.engine()
.0
.deserialize_checked(&bytes)?;
Ok(Self::from_artifact(artifact))
}

pub unsafe fn deserialize_from_file(
engine: &impl AsEngineRef,
path: impl AsRef<Path>,
Expand All @@ -90,6 +103,18 @@ impl Module {
Ok(Self::from_artifact(artifact))
}

pub fn deserialize_from_file_checked(
engine: &impl AsEngineRef,
path: impl AsRef<Path>,
) -> Result<Self, DeserializeError> {
let artifact = engine
.as_engine_ref()
.engine()
.0
.deserialize_from_file_checked(path.as_ref())?;
Ok(Self::from_artifact(artifact))
}

fn from_artifact(artifact: Arc<Artifact>) -> Self {
Self { artifact }
}
Expand Down
24 changes: 24 additions & 0 deletions lib/compiler/src/engine/artifact.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,30 @@ impl Artifact {
/// # Safety
/// This function is unsafe because rkyv reads directly without validating
/// the data.
pub fn deserialize_checked(engine: &Engine, bytes: &[u8]) -> Result<Self, DeserializeError> {
if !ArtifactBuild::is_deserializable(bytes) {
return Err(DeserializeError::Incompatible(
"Magic header not found".to_string(),
));
}

let bytes = Self::get_byte_slice(bytes, ArtifactBuild::MAGIC_HEADER.len(), bytes.len())?;

let metadata_len = MetadataHeader::parse(bytes)?;
let metadata_slice = Self::get_byte_slice(bytes, MetadataHeader::LEN, bytes.len())?;
let metadata_slice = Self::get_byte_slice(metadata_slice, 0, metadata_len)?;

let serializable = SerializableModule::deserialize_checked(metadata_slice)?;
let artifact = ArtifactBuild::from_serializable(serializable);
let mut inner_engine = engine.inner_mut();
Self::from_parts(&mut inner_engine, artifact, engine.target())
.map_err(DeserializeError::Compiler)
}

/// Deserialize a ArtifactBuild
///
/// # Safety
/// This function is unsafe because rkyv reads directly without validating the data.
pub unsafe fn deserialize(engine: &Engine, bytes: &[u8]) -> Result<Self, DeserializeError> {
if !ArtifactBuild::is_deserializable(bytes) {
let static_artifact = Self::deserialize_object(engine, bytes);
Expand Down
18 changes: 17 additions & 1 deletion lib/compiler/src/engine/inner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,12 +200,28 @@ impl Engine {
Ok(Arc::new(Artifact::deserialize(self, bytes)?))
}

/// Deserializes a WebAssembly module
#[cfg(not(target_arch = "wasm32"))]
pub fn deserialize_checked(&self, bytes: &[u8]) -> Result<Arc<Artifact>, DeserializeError> {
Ok(Arc::new(Artifact::deserialize_checked(self, bytes)?))
}

/// Deserializes a WebAssembly module from a path
#[cfg(not(target_arch = "wasm32"))]
pub fn deserialize_from_file_checked(
&self,
file_ref: &Path,
) -> Result<Arc<Artifact>, DeserializeError> {
let contents = std::fs::read(file_ref)?;
self.deserialize_checked(&contents)
}

/// Deserialize from a file path.
///
/// # Safety
///
/// The file's content must represent a serialized WebAssembly module.
/// See [`crate::Module::deserialize_from_file`].
#[cfg(not(target_arch = "wasm32"))]
pub unsafe fn deserialize_from_file(
&self,
file_ref: &Path,
Expand Down
3 changes: 2 additions & 1 deletion lib/types/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ serde_bytes = { version = "0.11", optional = true }
thiserror = "1.0"
more-asserts = "0.2"
indexmap = { version = "1.6" }
rkyv = { version = "0.7.40", features = ["indexmap"] }
rkyv = { version = "0.7.40", features = ["indexmap", "validation", "strict"] }
enum-iterator = "0.7.0"
target-lexicon = { version = "0.12.2", default-features = false }
enumset = "1.0"
bytecheck = "0.6.8"

[dev-dependencies]
memoffset = "0.6"
Expand Down
2 changes: 2 additions & 0 deletions lib/types/src/compilation/address_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use serde::{Deserialize, Serialize};
/// Single source location to generated address mapping.
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
#[derive(RkyvSerialize, RkyvDeserialize, Archive, Debug, Clone, PartialEq, Eq)]
#[archive_attr(derive(rkyv::CheckBytes))]
pub struct InstructionAddressMap {
/// Original source location.
pub srcloc: SourceLoc,
Expand All @@ -24,6 +25,7 @@ pub struct InstructionAddressMap {
/// Function and its instructions addresses mappings.
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
#[derive(RkyvSerialize, RkyvDeserialize, Archive, Debug, Clone, PartialEq, Eq, Default)]
#[archive_attr(derive(rkyv::CheckBytes))]
pub struct FunctionAddressMap {
/// Instructions maps.
/// The array is sorted by the InstructionAddressMap::code_offset field.
Expand Down
7 changes: 6 additions & 1 deletion lib/types/src/compilation/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use serde::{Deserialize, Serialize};
/// the frame information after a `Trap`.
#[cfg_attr(feature = "enable-serde", derive(Deserialize, Serialize))]
#[derive(RkyvSerialize, RkyvDeserialize, Archive, Debug, Clone, PartialEq, Eq, Default)]
#[archive_attr(derive(rkyv::CheckBytes))]
pub struct CompiledFunctionFrameInfo {
/// The traps (in the function body).
///
Expand All @@ -34,6 +35,7 @@ pub struct CompiledFunctionFrameInfo {
/// The function body.
#[cfg_attr(feature = "enable-serde", derive(Deserialize, Serialize))]
#[derive(RkyvSerialize, RkyvDeserialize, Archive, Debug, Clone, PartialEq, Eq)]
#[archive_attr(derive(rkyv::CheckBytes))]
pub struct FunctionBody {
/// The function body bytes.
#[cfg_attr(feature = "enable-serde", serde(with = "serde_bytes"))]
Expand All @@ -50,6 +52,7 @@ pub struct FunctionBody {
/// and unwind information).
#[cfg_attr(feature = "enable-serde", derive(Deserialize, Serialize))]
#[derive(RkyvSerialize, RkyvDeserialize, Archive, Debug, Clone, PartialEq, Eq)]
#[archive_attr(derive(rkyv::CheckBytes))]
pub struct CompiledFunction {
/// The function body.
pub body: FunctionBody,
Expand All @@ -74,7 +77,9 @@ pub type CustomSections = PrimaryMap<SectionIndex, CustomSection>;
/// In the future this structure may also hold other information useful
/// for debugging.
#[cfg_attr(feature = "enable-serde", derive(Deserialize, Serialize))]
#[derive(RkyvSerialize, RkyvDeserialize, Archive, Debug, PartialEq, Eq, Clone)]
#[derive(
RkyvSerialize, RkyvDeserialize, Archive, rkyv::CheckBytes, Debug, PartialEq, Eq, Clone,
)]
#[archive(as = "Self")]
pub struct Dwarf {
/// The section index in the [`Compilation`] that corresponds to the exception frames.
Expand Down
1 change: 1 addition & 0 deletions lib/types/src/compilation/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use std::sync::Arc;
/// or the `MemoryStyle` and `TableStyle`).
#[cfg_attr(feature = "enable-serde", derive(Deserialize, Serialize))]
#[derive(Debug, Clone, PartialEq, Eq, RkyvSerialize, RkyvDeserialize, Archive)]
#[archive_attr(derive(rkyv::CheckBytes))]
pub struct CompileModuleInfo {
/// The features used for compiling the module
pub features: Features,
Expand Down
11 changes: 9 additions & 2 deletions lib/types/src/compilation/relocation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,11 @@ use serde::{Deserialize, Serialize};

/// Relocation kinds for every ISA.
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
#[derive(RkyvSerialize, RkyvDeserialize, Archive, Copy, Clone, Debug, PartialEq, Eq)]
#[derive(
RkyvSerialize, RkyvDeserialize, Archive, rkyv::CheckBytes, Copy, Clone, Debug, PartialEq, Eq,
)]
#[archive(as = "Self")]
#[repr(u8)]
pub enum RelocationKind {
/// absolute 4-byte
Abs4,
Expand Down Expand Up @@ -90,6 +93,7 @@ impl fmt::Display for RelocationKind {
/// A record of a relocation to perform.
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
#[derive(RkyvSerialize, RkyvDeserialize, Archive, Debug, Clone, PartialEq, Eq)]
#[archive_attr(derive(rkyv::CheckBytes))]
pub struct Relocation {
/// The relocation kind.
pub kind: RelocationKind,
Expand All @@ -103,8 +107,11 @@ pub struct Relocation {

/// Destination function. Can be either user function or some special one, like `memory.grow`.
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
#[derive(RkyvSerialize, RkyvDeserialize, Archive, Debug, Copy, Clone, PartialEq, Eq)]
#[derive(
RkyvSerialize, RkyvDeserialize, Archive, rkyv::CheckBytes, Debug, Copy, Clone, PartialEq, Eq,
)]
#[archive(as = "Self")]
#[repr(u8)]
pub enum RelocationTarget {
/// A relocation to a function defined locally in the wasm (not an imported one).
LocalFunc(LocalFunctionIndex),
Expand Down
8 changes: 7 additions & 1 deletion lib/types/src/compilation/section.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use serde::{Deserialize, Serialize};
RkyvSerialize,
RkyvDeserialize,
Archive,
rkyv::CheckBytes,
Copy,
Clone,
PartialEq,
Expand All @@ -37,8 +38,11 @@ entity_impl!(SectionIndex);
///
/// Determines how a custom section may be used.
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
#[derive(RkyvSerialize, RkyvDeserialize, Archive, Debug, Clone, PartialEq, Eq)]
#[derive(
RkyvSerialize, RkyvDeserialize, Archive, rkyv::CheckBytes, Debug, Clone, PartialEq, Eq,
)]
#[archive(as = "Self")]
#[repr(u8)]
pub enum CustomSectionProtection {
/// A custom section with read permission.
Read,
Expand All @@ -53,6 +57,7 @@ pub enum CustomSectionProtection {
/// in the emitted module.
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
#[derive(RkyvSerialize, RkyvDeserialize, Archive, Debug, Clone, PartialEq, Eq)]
#[archive_attr(derive(rkyv::CheckBytes))]
pub struct CustomSection {
/// Memory protection that applies to this section.
pub protection: CustomSectionProtection,
Expand All @@ -72,6 +77,7 @@ pub struct CustomSection {
/// The bytes in the section.
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
#[derive(RkyvSerialize, RkyvDeserialize, Archive, Debug, Clone, PartialEq, Eq, Default)]
#[archive_attr(derive(rkyv::CheckBytes))]
pub struct SectionBody(#[cfg_attr(feature = "enable-serde", serde(with = "serde_bytes"))] Vec<u8>);

impl SectionBody {
Expand Down
2 changes: 1 addition & 1 deletion lib/types/src/compilation/sourceloc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use serde::{Deserialize, Serialize};
)]
#[derive(RkyvSerialize, RkyvDeserialize, Archive)]
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, rkyv::CheckBytes)]
#[archive(as = "Self")]
pub struct SourceLoc(u32);

Expand Down
Loading

0 comments on commit 47cc5bb

Please sign in to comment.