Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions crates/fiber/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ pub type Result<T, E = imp::Error> = core::result::Result<T, E>;

impl FiberStack {
/// Creates a new fiber stack of the given size.
pub fn new(size: usize) -> Result<Self> {
Ok(Self(imp::FiberStack::new(size)?))
pub fn new(size: usize, zeroed: bool) -> Result<Self> {
Ok(Self(imp::FiberStack::new(size, zeroed)?))
}

/// Creates a new fiber stack of the given size.
Expand Down Expand Up @@ -108,7 +108,7 @@ pub unsafe trait RuntimeFiberStackCreator: Send + Sync {
///
/// This is useful to plugin previously allocated memory instead of mmap'ing a new stack for
/// every instance.
fn new_stack(&self, size: usize) -> Result<Box<dyn RuntimeFiberStack>, Error>;
fn new_stack(&self, size: usize, zeroed: bool) -> Result<Box<dyn RuntimeFiberStack>, Error>;
}

/// A fiber stack backed by custom memory.
Expand Down
8 changes: 6 additions & 2 deletions crates/fiber/src/nostd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,14 @@ fn align_ptr(ptr: *mut u8, len: usize, align: usize) -> (*mut u8, usize) {
}

impl FiberStack {
pub fn new(size: usize) -> Result<Self> {
pub fn new(size: usize, zeroed: bool) -> Result<Self> {
// Round up the size to at least one page.
let size = core::cmp::max(4096, size);
let mut storage = vec![0; size];
let mut storage = Vec::new();
storage.reserve_exact(size);
if zeroed {
storage.resize(size, 0);
}
let (base, len) = align_ptr(storage.as_mut_ptr(), size, STACK_ALIGN);
Ok(FiberStack {
storage,
Expand Down
6 changes: 5 additions & 1 deletion crates/fiber/src/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,11 @@ enum FiberStackStorage {
}

impl FiberStack {
pub fn new(size: usize) -> io::Result<Self> {
pub fn new(size: usize, zeroed: bool) -> io::Result<Self> {
// The anonymous `mmap`s we use for `FiberStackStorage` are alawys
// zeroed.
let _ = zeroed;

// See comments in `mod asan` below for why asan has a different stack
// allocation strategy.
if cfg!(asan) {
Expand Down
5 changes: 4 additions & 1 deletion crates/fiber/src/windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ pub type Error = io::Error;
pub struct FiberStack(usize);

impl FiberStack {
pub fn new(size: usize) -> io::Result<Self> {
pub fn new(size: usize, zeroed: bool) -> io::Result<Self> {
// We don't support fiber stack zeroing on windows.
let _ = zeroed;

Ok(Self(size))
}

Expand Down
69 changes: 42 additions & 27 deletions crates/wasmtime/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,8 @@ pub struct Config {
#[cfg(feature = "async")]
pub(crate) async_stack_size: usize,
#[cfg(feature = "async")]
pub(crate) async_stack_zeroing: bool,
#[cfg(feature = "async")]
pub(crate) stack_creator: Option<Arc<dyn RuntimeFiberStackCreator>>,
pub(crate) async_support: bool,
pub(crate) module_version: ModuleVersionStrategy,
Expand Down Expand Up @@ -257,6 +259,8 @@ impl Config {
#[cfg(feature = "async")]
async_stack_size: 2 << 20,
#[cfg(feature = "async")]
async_stack_zeroing: true,
#[cfg(feature = "async")]
stack_creator: None,
async_support: false,
module_version: ModuleVersionStrategy::default(),
Expand Down Expand Up @@ -728,6 +732,40 @@ impl Config {
self
}

/// Configures whether or not stacks used for async futures are zeroed
/// before (re)use.
///
/// When the [`async_support`](Config::async_support) method is enabled for
/// Wasmtime and the [`call_async`] variant of calling WebAssembly is used
/// then Wasmtime will create a separate runtime execution stack for each
/// future produced by [`call_async`]. By default upon allocation, depending
/// on the platform, these stacks might be filled with uninitialized
/// memory. This is safe and correct because, modulo bugs in Wasmtime,
/// compiled Wasm code will never read from a stack slot before it
/// initializes the stack slot.
///
/// However, as a defense-in-depth mechanism, you may configure Wasmtime to
/// ensure that these stacks are zeroed before they are used. Notably, if
/// you are using the pooling allocator, stacks can be pooled and reused
/// across different Wasm guests; ensuring that stacks are zeroed can
/// prevent data leakage between Wasm guests even in the face of potential
/// read-of-stack-slot-before-initialization bugs in Wasmtime's compiler.
///
/// Stack zeroing can be a costly operation in highly concurrent
/// environments due to modifications of the virtual address space requiring
/// process-wide synchronization. It can also be costly in `no-std`
/// environments that must manually zero memory, and cannot rely on an OS
/// and virtual memory to provide zeroed pages.
///
/// This option defaults to `false`.
///
/// [`call_async`]: crate::TypedFunc::call_async
#[cfg(feature = "async")]
pub fn async_stack_zeroing(&mut self, enable: bool) -> &mut Self {
self.async_stack_zeroing = enable;
self
}

fn wasm_feature(&mut self, flag: WasmFeatures, enable: bool) -> &mut Self {
self.enabled_features.set(flag, enable);
self.disabled_features.set(flag, !enable);
Expand Down Expand Up @@ -2159,10 +2197,10 @@ impl Config {
tunables: &Tunables,
) -> Result<Box<dyn InstanceAllocator + Send + Sync>> {
#[cfg(feature = "async")]
let stack_size = self.async_stack_size;
let (stack_size, stack_zeroing) = (self.async_stack_size, self.async_stack_zeroing);

#[cfg(not(feature = "async"))]
let stack_size = 0;
let (stack_size, stack_zeroing) = (0, false);

let _ = tunables;

Expand All @@ -2172,6 +2210,7 @@ impl Config {
let mut allocator = Box::new(OnDemandInstanceAllocator::new(
self.mem_creator.clone(),
stack_size,
stack_zeroing,
));
#[cfg(feature = "async")]
if let Some(stack_creator) = &self.stack_creator {
Expand All @@ -2183,6 +2222,7 @@ impl Config {
InstanceAllocationStrategy::Pooling(config) => {
let mut config = config.config;
config.stack_size = stack_size;
config.async_stack_zeroing = stack_zeroing;
Ok(Box::new(crate::runtime::vm::PoolingInstanceAllocator::new(
&config, tunables,
)?))
Expand Down Expand Up @@ -2952,31 +2992,6 @@ impl PoolingAllocationConfig {
self
}

/// Configures whether or not stacks used for async futures are reset to
/// zero after usage.
///
/// When the [`async_support`](Config::async_support) method is enabled for
/// Wasmtime and the [`call_async`] variant
/// of calling WebAssembly is used then Wasmtime will create a separate
/// runtime execution stack for each future produced by [`call_async`].
/// During the deallocation process Wasmtime won't by default reset the
/// contents of the stack back to zero.
///
/// When this option is enabled it can be seen as a defense-in-depth
/// mechanism to reset a stack back to zero. This is not required for
/// correctness and can be a costly operation in highly concurrent
/// environments due to modifications of the virtual address space requiring
/// process-wide synchronization.
///
/// This option defaults to `false`.
///
/// [`call_async`]: crate::TypedFunc::call_async
#[cfg(feature = "async")]
pub fn async_stack_zeroing(&mut self, enable: bool) -> &mut Self {
self.config.async_stack_zeroing = enable;
self
}

/// How much memory, in bytes, to keep resident for async stacks allocated
/// with the pooling allocator.
///
Expand Down
9 changes: 6 additions & 3 deletions crates/wasmtime/src/runtime/stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,21 @@ pub unsafe trait StackCreator: Send + Sync {
///
/// The `size` parameter is the expected size of the stack without any guard pages.
///
/// The `zeroed` parameter is whether the stack's memory should be zeroed,
/// as a defense-in-depth measure.
///
/// Note there should be at least one guard page of protected memory at the bottom
/// of the stack to catch potential stack overflow scenarios. Additionally, stacks should be
/// page aligned and zero filled.
fn new_stack(&self, size: usize) -> Result<Box<dyn StackMemory>, Error>;
fn new_stack(&self, size: usize, zeroed: bool) -> Result<Box<dyn StackMemory>, Error>;
}

#[derive(Clone)]
pub(crate) struct StackCreatorProxy(pub Arc<dyn StackCreator>);

unsafe impl RuntimeFiberStackCreator for StackCreatorProxy {
fn new_stack(&self, size: usize) -> Result<Box<dyn RuntimeFiberStack>, Error> {
let stack = self.0.new_stack(size)?;
fn new_stack(&self, size: usize, zeroed: bool) -> Result<Box<dyn RuntimeFiberStack>, Error> {
let stack = self.0.new_stack(size, zeroed)?;
Ok(Box::new(FiberStackProxy(stack)) as Box<dyn RuntimeFiberStack>)
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/wasmtime/src/runtime/trampoline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ fn create_handle(
// as we don't want host objects to count towards instance limits.
let module = Arc::new(module);
let runtime_info = &ModuleRuntimeInfo::bare_maybe_imported_func(module, one_signature);
let allocator = OnDemandInstanceAllocator::new(config.mem_creator.clone(), 0);
let allocator = OnDemandInstanceAllocator::new(config.mem_creator.clone(), 0, false);
let handle = allocator.allocate_module(InstanceAllocationRequest {
imports,
host_state,
Expand Down
18 changes: 14 additions & 4 deletions crates/wasmtime/src/runtime/vm/instance/allocator/on_demand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,26 @@ pub struct OnDemandInstanceAllocator {
stack_creator: Option<Arc<dyn RuntimeFiberStackCreator>>,
#[cfg(feature = "async")]
stack_size: usize,
#[cfg(feature = "async")]
stack_zeroing: bool,
}

impl OnDemandInstanceAllocator {
/// Creates a new on-demand instance allocator.
pub fn new(mem_creator: Option<Arc<dyn RuntimeMemoryCreator>>, stack_size: usize) -> Self {
let _ = stack_size; // suppress warnings when async feature is disabled.
pub fn new(
mem_creator: Option<Arc<dyn RuntimeMemoryCreator>>,
stack_size: usize,
stack_zeroing: bool,
) -> Self {
let _ = (stack_size, stack_zeroing); // suppress warnings when async feature is disabled.
Self {
mem_creator,
#[cfg(feature = "async")]
stack_creator: None,
#[cfg(feature = "async")]
stack_size,
#[cfg(feature = "async")]
stack_zeroing,
}
}

Expand All @@ -62,6 +70,8 @@ impl Default for OnDemandInstanceAllocator {
stack_creator: None,
#[cfg(feature = "async")]
stack_size: 0,
#[cfg(feature = "async")]
stack_zeroing: false,
}
}
}
Expand Down Expand Up @@ -165,10 +175,10 @@ unsafe impl InstanceAllocatorImpl for OnDemandInstanceAllocator {
}
let stack = match &self.stack_creator {
Some(stack_creator) => {
let stack = stack_creator.new_stack(self.stack_size)?;
let stack = stack_creator.new_stack(self.stack_size, self.stack_zeroing)?;
wasmtime_fiber::FiberStack::from_custom(stack)
}
None => wasmtime_fiber::FiberStack::new(self.stack_size),
None => wasmtime_fiber::FiberStack::new(self.stack_size, self.stack_zeroing),
}?;
Ok(stack)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use std::sync::atomic::{AtomicU64, Ordering};
#[derive(Debug)]
pub struct StackPool {
stack_size: usize,
stack_zeroing: bool,
live_stacks: AtomicU64,
stack_limit: u64,
}
Expand All @@ -26,6 +27,7 @@ impl StackPool {
pub fn new(config: &PoolingInstanceAllocatorConfig) -> Result<Self> {
Ok(StackPool {
stack_size: config.stack_size,
stack_zeroing: config.async_stack_zeroing,
live_stacks: AtomicU64::new(0),
stack_limit: config.limits.total_stacks.into(),
})
Expand All @@ -51,7 +53,7 @@ impl StackPool {
.into());
}

match wasmtime_fiber::FiberStack::new(self.stack_size) {
match wasmtime_fiber::FiberStack::new(self.stack_size, self.stack_zeroing) {
Ok(stack) => Ok(stack),
Err(e) => {
self.live_stacks.fetch_sub(1, Ordering::AcqRel);
Expand Down
Loading