CVE-2023-30624 (Rust, published 2023-04-27T17:15Z)
Description: value: Wasmtime is a standalone runtime for WebAssembly. Prior to versions 6.0.2, 7.0.1, and 8.0.1, Wasmtime's implementation of managing per-instance state, such as tables and memories, contains LLVM-level undefined behavior. This undefined behavior was found to cause runtime-level issues when compiled with LLVM 16 which causes some writes, which are critical for correctness, to be optimized away. Vulnerable versions of Wasmtime compiled with Rust 1.70, which is currently in beta, or later are known to have incorrectly compiled functions. Versions of Wasmtime compiled with the current Rust stable release, 1.69, and prior are not known at this time to have any issues, but can theoretically exhibit potential issues.

The underlying problem is that Wasmtime's runtime state for an instance involves a Rust-defined structure called `Instance` which has a trailing `VMContext` structure after it. This `VMContext` structure has a runtime-defined layout that is unique per-module. This representation cannot be expressed with safe code in Rust so `unsafe` code is required to maintain this state. The code doing this, however, has methods which take `&self` as an argument but modify data in the `VMContext` part of the allocation. This means that pointers derived from `&self` are mutated. This is typically not allowed, except in the presence of `UnsafeCell`, in Rust. When compiled to LLVM these functions have `noalias readonly` parameters which means it's UB to write through the pointers.

Wasmtime's internal representation and management of `VMContext` has been updated to use `&mut self` methods where appropriate. Additionally verification tools for `unsafe` code in Rust, such as `cargo miri`, are planned to be executed on the `main` branch soon to fix any Rust-level issues that may be exploited in future compiler versions.

Precomplied binaries available for Wasmtime from GitHub releases have been compiled with at most LLVM 15 so are not known to be vulnerable. As mentioned above, however, it's still recommended to update.

Wasmtime version 6.0.2, 7.0.1, and 8.0.1 have been issued which contain the patch necessary to work correctly on LLVM 16 and have no known UB on LLVM 15 and earlier. If Wasmtime is compiled with Rust 1.69 and prior, which use LLVM 15, then there are no known issues. There is a theoretical possibility for undefined behavior to exploited, however, so it's recommended that users upgrade to a patched version of Wasmtime. Users using beta Rust (1.70 at this time) or nightly Rust (1.71 at this time) must update to a patched version to work correctly.
Severity: nan (CVSS v3 base score: 8.8)
Weakness: CWE-758 \u2014 Reliance on Undefined, Unspecified, or Implementation-Defined Behavior
The product uses an API function, data structure, or other entity in a way that relies on properties that are not always guaranteed to hold for that entity.
Repository: https://github.com/bytecodealliance/wasmtime
Files changed:
RELEASES.md
crates/environ/src/module.rs
crates/runtime/src/instance.rs
crates/runtime/src/instance/allocator.rs
crates/runtime/src/libcalls.rs
crates/runtime/src/traphandlers.rs
Commit message: Merge pull request from GHSA-ch89-5g45-qwc7

* Fix miscompile from functions mutating `VMContext`

This commit fixes a miscompilation in Wasmtime on LLVM 16 where methods
on `Instance` which mutated the state of the internal `VMContext` were
optimized to not actually mutate the state. The root cause of this issue
is a change in LLVM which takes advantage of `noalias readonly` pointers
which is how `&self` methods are translated. This means that `Instance`
methods which take `&self` but actually mutate the `VMContext` end up
being undefined behavior from LLVM's point of view, meaning that the
writes are candidate for removal.

The fix applied here is intended to be a temporary one while a more
formal fix, ideally backed by `cargo miri` verification, is implemented
on `main`. The fix here is to change the return value of
`vmctx_plus_offset` to return `*const T` instead of `*mut T`. This
caused lots of portions of the runtime code to stop compiling because
mutations were indeed happening. To cover these a new
`vmctx_plus_offset_mut` method was added which notably takes `&mut self`
instead of `&self`. This forced all callers which may mutate to reflect
the `&mut self` requirement, propagating that outwards.

This fixes the miscompilation with LLVM 16 in the immediate future and
should be at least a meager line of defense against issues like this in
the future. This is not a long-term fix, though, since `cargo miri`
still does not like what's being done in `Instance` and with
`VMContext`. That fix is likely to be more invasive, though, so it's
being deferred to later.

* Update release notes

* Fix dates and fill out more notes

Vulnerable code:
            &mut |memory, init| {
    pub fn init_memory(
        state: InitMemory<'_>,
        write: &mut dyn FnMut(MemoryIndex, &StaticMemoryInitializer) -> bool,
                        let result = write(index, init);
                Some(index) => match &state {
                    } => get_global_as_u64(index),
            let cur_size_in_pages = match &state {
                } => memory_size_in_pages(memory_index),
            let result = write(memory_index, &init);
pub enum InitMemory<'a> {
        memory_size_in_pages: &'a dyn Fn(MemoryIndex) -> u64,
        get_global_as_u64: &'a dyn Fn(GlobalIndex) -> u64,
    unsafe fn vmctx_plus_offset<T>(&self, offset: u32) -> *mut T {
        (self.vmctx_ptr().cast::<u8>())
    fn table(&self, index: DefinedTableIndex) -> VMTableDefinition {
    fn set_table(&self, index: DefinedTableIndex, table: VMTableDefinition) {
    fn table_ptr(&self, index: DefinedTableIndex) -> *mut VMTableDefinition {
        unsafe { self.vmctx_plus_offset(self.offsets().vmctx_vmtable_definition(index)) }
    fn global(&self, index: DefinedGlobalIndex) -> &VMGlobalDefinition {
    fn global_ptr(&self, index: DefinedGlobalIndex) -> *mut VMGlobalDefinition {
        unsafe { self.vmctx_plus_offset(self.offsets().vmctx_vmglobal_definition(index)) }
        &self,
    pub fn runtime_limits(&self) -> *mut *const VMRuntimeLimits {
        unsafe { self.vmctx_plus_offset(self.offsets().vmctx_runtime_limits()) }
    pub fn epoch_ptr(&self) -> *mut *const AtomicU64 {
        unsafe { self.vmctx_plus_offset(self.offsets().vmctx_epoch_ptr()) }
    pub fn externref_activations_table(&self) -> *mut *mut VMExternRefActivationsTable {
        unsafe { self.vmctx_plus_offset(self.offsets().vmctx_externref_activations_table()) }
            *self.vmctx_plus_offset(self.offsets().vmctx_store()) = store;
            *self.vmctx_plus_offset::<[*mut (); 2]>(self.offsets().vmctx_store()) =
        *self.vmctx_plus_offset(self.offsets().vmctx_callee()) =
    unsafe fn table_index(&self, table: &VMTableDefinition) -> DefinedTableIndex {
                *self.vmctx_plus_offset(self.offsets().vmctx_signature_ids_array());
                .vmctx_plus_offset::<VMCallerCheckedFuncRef>(
        *self.vmctx_plus_offset(offsets.vmctx_magic()) = VMCONTEXT_MAGIC;
        *self.vmctx_plus_offset(offsets.vmctx_signature_ids_array()) = signatures.as_ptr();
        *self.vmctx_plus_offset(offsets.vmctx_builtin_functions()) = &VMBuiltinFunctionsArray::INIT;
            self.vmctx_plus_offset(offsets.vmctx_imported_functions_begin()),
            self.vmctx_plus_offset(offsets.vmctx_imported_tables_begin()),
            self.vmctx_plus_offset(offsets.vmctx_imported_memories_begin()),
            self.vmctx_plus_offset(offsets.vmctx_imported_globals_begin()),
        let mut ptr = self.vmctx_plus_offset(offsets.vmctx_tables_begin());
        let mut ptr = self.vmctx_plus_offset(offsets.vmctx_memories_begin());
        let mut owned_ptr = self.vmctx_plus_offset(offsets.vmctx_owned_memories_begin());
        for (idx, global) in self.module().globals.iter() {
            let idx = match self.module().defined_global_index(idx) {
    pub unsafe fn table_index(&self, table: &VMTableDefinition) -> DefinedTableIndex {
        self.instance().table_index(table)
fn get_table_init_start(init: &TableInitializer, instance: &Instance) -> Result<u32> {
            let val = unsafe {
                if let Some(def_index) = instance.module().defined_global_index(base) {
                    *instance.global(def_index).as_u32()
                } else {
                    *(*instance.imported_global(base).from).as_u32()
                }
            };
                    get_table_init_start(segment, instance)?,
fn get_memory_init_start(init: &MemoryInitializer, instance: &Instance) -> Result<u64> {
                let global = if let Some(def_index) = instance.module().defined_global_index(base) {
                    instance.global(def_index)
                } else {
                    &*instance.imported_global(base).from
                };
                    *global.as_u64()
                    u64::from(*global.as_u32())
fn check_memory_init_bounds(instance: &Instance, initializers: &[MemoryInitializer]) -> Result<()> {
    let memory_size_in_pages =
        &|memory| (instance.get_memory(memory).current_length() as u64) / u64::from(WASM_PAGE_SIZE);
    let get_global_as_u64 = &|global| unsafe {
        let def = if let Some(def_index) = instance.module().defined_global_index(global) {
            instance.global(def_index)
        } else {
            &*instance.imported_global(global).from
        };
            *def.as_u64()
            u64::from(*def.as_u32())
        &mut |memory_index, init| {
    match &instance.module().memory_initialization {
    let instance = (*vmctx).instance();
    let instance = (*vmctx).instance();
    let limits = (*caller).instance().runtime_limits();

Fixed code:
## 8.0.1

Released 2023-04-27.

### Changed

* Breaking: Files opened using Wasmtime's implementation of WASI on Windows now
  cannot be deleted until the file handle is closed. This was already true for
  open directories. The change was necessary for the bug fix in
  [#6163](https://github.com/bytecodealliance/wasmtime/pull/6163).

### Fixed

* Fixed wasi-common's implementation of the `O_DIRECTORY` flag to match POSIX.
  [#6163](https://github.com/bytecodealliance/wasmtime/pull/6163)

* Undefined Behavior in Rust runtime functions
  [GHSA-ch89-5g45-qwc7](https://github.com/bytecodealliance/wasmtime/security/advisories/GHSA-ch89-5g45-qwc7)

--------------------------------------------------------------------------------

## 7.0.1

Released 2023-04-27.

### Fixed

* Undefined Behavior in Rust runtime functions
  [GHSA-ch89-5g45-qwc7](https://github.com/bytecodealliance/wasmtime/security/advisories/GHSA-ch89-5g45-qwc7)

--------------------------------------------------------------------------------

## 6.0.2

Released 2023-04-27.

### Fixed

* Undefined Behavior in Rust runtime functions
  [GHSA-ch89-5g45-qwc7](https://github.com/bytecodealliance/wasmtime/security/advisories/GHSA-ch89-5g45-qwc7)

--------------------------------------------------------------------------------

            &mut (),
            |(), memory, init| {
    pub fn init_memory<T>(
        state: &mut T,
        init: InitMemory<'_, T>,
        mut write: impl FnMut(&mut T, MemoryIndex, &StaticMemoryInitializer) -> bool,
                        let result = write(state, index, init);
                Some(index) => match &init {
                    } => get_global_as_u64(state, index),
            let cur_size_in_pages = match &init {
                } => memory_size_in_pages(state, memory_index),
            let result = write(state, memory_index, &init);
pub enum InitMemory<'a, T> {
        memory_size_in_pages: &'a dyn Fn(&mut T, MemoryIndex) -> u64,
        get_global_as_u64: &'a dyn Fn(&mut T, GlobalIndex) -> u64,
    unsafe fn vmctx_plus_offset<T>(&self, offset: u32) -> *const T {
        (std::ptr::addr_of!(self.vmctx).cast::<u8>())
            .add(usize::try_from(offset).unwrap())
            .cast()
    }

    unsafe fn vmctx_plus_offset_mut<T>(&mut self, offset: u32) -> *mut T {
        (std::ptr::addr_of_mut!(self.vmctx).cast::<u8>())
    fn table(&mut self, index: DefinedTableIndex) -> VMTableDefinition {
    fn set_table(&mut self, index: DefinedTableIndex, table: VMTableDefinition) {
    fn table_ptr(&mut self, index: DefinedTableIndex) -> *mut VMTableDefinition {
        unsafe { self.vmctx_plus_offset_mut(self.offsets().vmctx_vmtable_definition(index)) }
    fn global(&mut self, index: DefinedGlobalIndex) -> &VMGlobalDefinition {
    fn global_ptr(&mut self, index: DefinedGlobalIndex) -> *mut VMGlobalDefinition {
        unsafe { self.vmctx_plus_offset_mut(self.offsets().vmctx_vmglobal_definition(index)) }
        &mut self,
    pub fn runtime_limits(&mut self) -> *mut *const VMRuntimeLimits {
        unsafe { self.vmctx_plus_offset_mut(self.offsets().vmctx_runtime_limits()) }
    pub fn epoch_ptr(&mut self) -> *mut *const AtomicU64 {
        unsafe { self.vmctx_plus_offset_mut(self.offsets().vmctx_epoch_ptr()) }
    pub fn externref_activations_table(&mut self) -> *mut *mut VMExternRefActivationsTable {
        unsafe { self.vmctx_plus_offset_mut(self.offsets().vmctx_externref_activations_table()) }
            *self.vmctx_plus_offset_mut(self.offsets().vmctx_store()) = store;
            *self.vmctx_plus_offset_mut::<[*mut (); 2]>(self.offsets().vmctx_store()) =
        *self.vmctx_plus_offset_mut(self.offsets().vmctx_callee()) =
    unsafe fn table_index(&mut self, table: &VMTableDefinition) -> DefinedTableIndex {
                *self.vmctx_plus_offset_mut(self.offsets().vmctx_signature_ids_array());
                .vmctx_plus_offset_mut::<VMCallerCheckedFuncRef>(
        *self.vmctx_plus_offset_mut(offsets.vmctx_magic()) = VMCONTEXT_MAGIC;
        *self.vmctx_plus_offset_mut(offsets.vmctx_signature_ids_array()) = signatures.as_ptr();
        *self.vmctx_plus_offset_mut(offsets.vmctx_builtin_functions()) =
            &VMBuiltinFunctionsArray::INIT;
            self.vmctx_plus_offset_mut(offsets.vmctx_imported_functions_begin()),
            self.vmctx_plus_offset_mut(offsets.vmctx_imported_tables_begin()),
            self.vmctx_plus_offset_mut(offsets.vmctx_imported_memories_begin()),
            self.vmctx_plus_offset_mut(offsets.vmctx_imported_globals_begin()),
        let mut ptr = self.vmctx_plus_offset_mut(offsets.vmctx_tables_begin());
        let mut ptr = self.vmctx_plus_offset_mut(offsets.vmctx_memories_begin());
        let mut owned_ptr = self.vmctx_plus_offset_mut(offsets.vmctx_owned_memories_begin());
        let module = self.module().clone();
        for (idx, global) in module.globals.iter() {
            let idx = match module.defined_global_index(idx) {
    pub unsafe fn table_index(&mut self, table: &VMTableDefinition) -> DefinedTableIndex {
        self.instance_mut().table_index(table)
fn get_table_init_start(init: &TableInitializer, instance: &mut Instance) -> Result<u32> {
            let val = unsafe { *(*instance.defined_or_imported_global_ptr(base)).as_u32() };
                let start = get_table_init_start(segment, instance)?;
                    start,
fn get_memory_init_start(init: &MemoryInitializer, instance: &mut Instance) -> Result<u64> {
                let global = instance.defined_or_imported_global_ptr(base);
                    *(*global).as_u64()
                    u64::from(*(*global).as_u32())
fn check_memory_init_bounds(
    instance: &mut Instance,
    initializers: &[MemoryInitializer],
) -> Result<()> {
    let memory_size_in_pages = &|instance: &mut Instance, memory| {
        (instance.get_memory(memory).current_length() as u64) / u64::from(WASM_PAGE_SIZE)
    };
    let get_global_as_u64 = &mut |instance: &mut Instance, global| unsafe {
        let def = instance.defined_or_imported_global_ptr(global);
            *(*def).as_u64()
            u64::from(*(*def).as_u32())
        instance,
        |instance, memory_index, init| {
    match &module.memory_initialization {
    let instance = (*vmctx).instance_mut();
    let instance = (*vmctx).instance_mut();
    let limits = (*caller).instance_mut().runtime_limits();