replace Config::deserialize_check_wasmtime_version with Config::module_version
which is more expressive than the former. Instead of just configuring Module::deserialize to ignore version information, we can configure Module::serialize to emit a custom version string, and Module::deserialize to check for that string. A new enum ModuleVersionStrategy is declared, and Config::deserialize_check_wasmtime_version:bool is replaced with Config::module_version:ModuleVersionStrategy.
This commit is contained in:
@@ -330,6 +330,23 @@ impl Default for InstanceAllocationStrategy {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
/// Configure the strategy used for versioning in serializing and deserializing [`crate::Module`].
|
||||
pub enum ModuleVersionStrategy {
|
||||
/// Use the wasmtime crate's Cargo package version.
|
||||
WasmtimeVersion,
|
||||
/// Use a custom version string. Must be at most 255 bytes.
|
||||
Custom(String),
|
||||
/// Emit no version string in serialization, and accept all version strings in deserialization.
|
||||
None,
|
||||
}
|
||||
|
||||
impl Default for ModuleVersionStrategy {
|
||||
fn default() -> Self {
|
||||
ModuleVersionStrategy::WasmtimeVersion
|
||||
}
|
||||
}
|
||||
|
||||
/// Global configuration options used to create an [`Engine`](crate::Engine)
|
||||
/// and customize its behavior.
|
||||
///
|
||||
@@ -350,7 +367,7 @@ pub struct Config {
|
||||
#[cfg(feature = "async")]
|
||||
pub(crate) async_stack_size: usize,
|
||||
pub(crate) async_support: bool,
|
||||
pub(crate) deserialize_check_wasmtime_version: bool,
|
||||
pub(crate) module_version: ModuleVersionStrategy,
|
||||
pub(crate) parallel_compilation: bool,
|
||||
pub(crate) paged_memory_initialization: bool,
|
||||
}
|
||||
@@ -374,7 +391,7 @@ impl Config {
|
||||
#[cfg(feature = "async")]
|
||||
async_stack_size: 2 << 20,
|
||||
async_support: false,
|
||||
deserialize_check_wasmtime_version: true,
|
||||
module_version: ModuleVersionStrategy::default(),
|
||||
parallel_compilation: true,
|
||||
// Default to paged memory initialization when using uffd on linux
|
||||
paged_memory_initialization: cfg!(all(target_os = "linux", feature = "uffd")),
|
||||
@@ -1254,18 +1271,23 @@ impl Config {
|
||||
self
|
||||
}
|
||||
|
||||
/// Configure whether deserialized modules should validate version
|
||||
/// information. This only effects [`crate::Module::deserialize()`], which is
|
||||
/// used to load compiled code from trusted sources. When true,
|
||||
/// [`crate::Module::deserialize()`] verifies that the wasmtime crate's
|
||||
/// `CARGO_PKG_VERSION` matches with the version in the binary, which was
|
||||
/// produced by [`crate::Module::serialize`] or
|
||||
/// [`crate::Engine::precompile_module`].
|
||||
/// Configure the version information used in serialized and deserialzied [`crate::Module`]s.
|
||||
/// This effects the behavior of [`crate::Module::serialize()`], as well as
|
||||
/// [`crate::Module::deserialize()`] and related functions.
|
||||
///
|
||||
/// This value defaults to true.
|
||||
pub fn deserialize_check_wasmtime_version(&mut self, check: bool) -> &mut Self {
|
||||
self.deserialize_check_wasmtime_version = check;
|
||||
self
|
||||
/// The default strategy is to use the wasmtime crate's Cargo package version.
|
||||
pub fn module_version(&mut self, strategy: ModuleVersionStrategy) -> Result<&mut Self> {
|
||||
match strategy {
|
||||
// This case requires special precondition for assertion in SerializedModule::to_bytes
|
||||
ModuleVersionStrategy::Custom(ref v) => {
|
||||
if v.as_bytes().len() > 255 {
|
||||
bail!("custom module version cannot be more than 255 bytes: {}", v);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
self.module_version = strategy;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Configure wether wasmtime should compile a module using multiple threads.
|
||||
@@ -1351,7 +1373,7 @@ impl Clone for Config {
|
||||
async_support: self.async_support,
|
||||
#[cfg(feature = "async")]
|
||||
async_stack_size: self.async_stack_size,
|
||||
deserialize_check_wasmtime_version: self.deserialize_check_wasmtime_version,
|
||||
module_version: self.module_version.clone(),
|
||||
parallel_compilation: self.parallel_compilation,
|
||||
paged_memory_initialization: self.paged_memory_initialization,
|
||||
}
|
||||
|
||||
@@ -148,7 +148,8 @@ impl Engine {
|
||||
let bytes = wat::parse_bytes(&bytes)?;
|
||||
let (_, artifacts, types) = crate::Module::build_artifacts(self, &bytes)?;
|
||||
let artifacts = artifacts.into_iter().map(|i| i.0).collect::<Vec<_>>();
|
||||
crate::module::SerializedModule::from_artifacts(self, &artifacts, &types).to_bytes()
|
||||
crate::module::SerializedModule::from_artifacts(self, &artifacts, &types)
|
||||
.to_bytes(&self.config().module_version)
|
||||
}
|
||||
|
||||
pub(crate) fn run_maybe_parallel<
|
||||
|
||||
@@ -316,12 +316,12 @@ impl Module {
|
||||
engine.0,
|
||||
artifacts.iter().map(|p| &p.0),
|
||||
types,
|
||||
).to_bytes().ok()
|
||||
).to_bytes(&engine.0.config().module_version).ok()
|
||||
},
|
||||
|
||||
// Cache hit, deserialize the provided artifacts
|
||||
|(engine, _wasm), serialized_bytes| {
|
||||
let (i, m, t, upvars) = SerializedModule::from_bytes(&serialized_bytes, true)
|
||||
let (i, m, t, upvars) = SerializedModule::from_bytes(&serialized_bytes, &engine.0.config().module_version)
|
||||
.ok()?
|
||||
.into_parts(engine.0)
|
||||
.ok()?;
|
||||
@@ -467,10 +467,7 @@ impl Module {
|
||||
/// blobs across versions of wasmtime you can be safely guaranteed that
|
||||
/// future versions of wasmtime will reject old cache entries).
|
||||
pub unsafe fn deserialize(engine: &Engine, bytes: impl AsRef<[u8]>) -> Result<Module> {
|
||||
let module = SerializedModule::from_bytes(
|
||||
bytes.as_ref(),
|
||||
engine.config().deserialize_check_wasmtime_version,
|
||||
)?;
|
||||
let module = SerializedModule::from_bytes(bytes.as_ref(), &engine.config().module_version)?;
|
||||
module.into_module(engine)
|
||||
}
|
||||
|
||||
@@ -486,10 +483,7 @@ impl Module {
|
||||
///
|
||||
/// [`deserialize`]: Module::deserialize
|
||||
pub unsafe fn deserialize_file(engine: &Engine, path: impl AsRef<Path>) -> Result<Module> {
|
||||
let module = SerializedModule::from_file(
|
||||
path.as_ref(),
|
||||
engine.config().deserialize_check_wasmtime_version,
|
||||
)?;
|
||||
let module = SerializedModule::from_file(path.as_ref(), &engine.config().module_version)?;
|
||||
module.into_module(engine)
|
||||
}
|
||||
|
||||
@@ -625,7 +619,7 @@ impl Module {
|
||||
#[cfg(compiler)]
|
||||
#[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs
|
||||
pub fn serialize(&self) -> Result<Vec<u8>> {
|
||||
SerializedModule::new(self).to_bytes()
|
||||
SerializedModule::new(self).to_bytes(&self.inner.engine.config().module_version)
|
||||
}
|
||||
|
||||
/// Creates a submodule `Module` value from the specified parameters.
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
//!
|
||||
//! This format is implemented by the `to_bytes` and `from_mmap` function.
|
||||
|
||||
use crate::{Engine, Module};
|
||||
use crate::{Engine, Module, ModuleVersionStrategy};
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use object::read::elf::FileHeader;
|
||||
use object::{Bytes, File, Object, ObjectSection};
|
||||
@@ -325,7 +325,7 @@ impl<'a> SerializedModule<'a> {
|
||||
))
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Result<Vec<u8>> {
|
||||
pub fn to_bytes(&self, version_strat: &ModuleVersionStrategy) -> Result<Vec<u8>> {
|
||||
// First up, create a linked-ish list of ELF files. For more
|
||||
// information on this format, see the doc comment on this module.
|
||||
// The only semi-tricky bit here is that we leave an
|
||||
@@ -352,7 +352,12 @@ impl<'a> SerializedModule<'a> {
|
||||
// The last part of our artifact is the bincode-encoded `Metadata`
|
||||
// section with a few other guards to help give better error messages.
|
||||
ret.extend_from_slice(HEADER);
|
||||
let version = env!("CARGO_PKG_VERSION");
|
||||
let version = match version_strat {
|
||||
ModuleVersionStrategy::WasmtimeVersion => env!("CARGO_PKG_VERSION"),
|
||||
ModuleVersionStrategy::Custom(c) => &c,
|
||||
ModuleVersionStrategy::None => "",
|
||||
};
|
||||
// This precondition is checked in Config::module_version:
|
||||
assert!(
|
||||
version.len() < 256,
|
||||
"package version must be less than 256 bytes"
|
||||
@@ -364,20 +369,20 @@ impl<'a> SerializedModule<'a> {
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8], check_version: bool) -> Result<Self> {
|
||||
Self::from_mmap(MmapVec::from_slice(bytes)?, check_version)
|
||||
pub fn from_bytes(bytes: &[u8], version_strat: &ModuleVersionStrategy) -> Result<Self> {
|
||||
Self::from_mmap(MmapVec::from_slice(bytes)?, version_strat)
|
||||
}
|
||||
|
||||
pub fn from_file(path: &Path, check_version: bool) -> Result<Self> {
|
||||
pub fn from_file(path: &Path, version_strat: &ModuleVersionStrategy) -> Result<Self> {
|
||||
Self::from_mmap(
|
||||
MmapVec::from_file(path).with_context(|| {
|
||||
format!("failed to create file mapping for: {}", path.display())
|
||||
})?,
|
||||
check_version,
|
||||
version_strat,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn from_mmap(mut mmap: MmapVec, check_version: bool) -> Result<Self> {
|
||||
pub fn from_mmap(mut mmap: MmapVec, version_strat: &ModuleVersionStrategy) -> Result<Self> {
|
||||
// Artifacts always start with an ELF file, so read that first.
|
||||
// Afterwards we continually read ELF files until we see the `u64::MAX`
|
||||
// marker, meaning we've reached the end.
|
||||
@@ -419,7 +424,8 @@ impl<'a> SerializedModule<'a> {
|
||||
bail!("serialized data is malformed");
|
||||
}
|
||||
|
||||
if check_version {
|
||||
match version_strat {
|
||||
ModuleVersionStrategy::WasmtimeVersion => {
|
||||
let version = std::str::from_utf8(&metadata[1..1 + version_len])?;
|
||||
if version != env!("CARGO_PKG_VERSION") {
|
||||
bail!(
|
||||
@@ -428,6 +434,17 @@ impl<'a> SerializedModule<'a> {
|
||||
);
|
||||
}
|
||||
}
|
||||
ModuleVersionStrategy::Custom(v) => {
|
||||
let version = std::str::from_utf8(&metadata[1..1 + version_len])?;
|
||||
if version != v {
|
||||
bail!(
|
||||
"Module was compiled with incompatible version '{}'",
|
||||
version
|
||||
);
|
||||
}
|
||||
}
|
||||
ModuleVersionStrategy::None => { /* ignore the version info, accept all */ }
|
||||
}
|
||||
|
||||
let metadata = bincode::deserialize::<Metadata>(&metadata[1 + version_len..])
|
||||
.context("deserialize compilation artifacts")?;
|
||||
|
||||
Reference in New Issue
Block a user