Skip to content

Commit

Permalink
Fix miri reported UB in FuncRef and ExternRef conversions (#1201)
Browse files Browse the repository at this point in the history
* add #[repr(C)] to Transposer as mandated by the Rust reference

* add getters for Transposer::{value,reftype}

* make the transmute explicit

* fix miri reported UB in reftype conversions

* remove reftype.rs utility

No longer needed.

* fix unrelated clippy warning
  • Loading branch information
Robbepop authored Sep 26, 2024
1 parent c0f79e1 commit b918b25
Show file tree
Hide file tree
Showing 5 changed files with 29 additions and 96 deletions.
2 changes: 0 additions & 2 deletions crates/cli/tests/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,6 @@ fn test_verbose() {
assert!(contains_slice(stdout, b"proc_exit.wat\")::()"));
}

/// UTILS

/// gets the path to a wasm binary given it's name
fn get_bin_path(name: &str) -> PathBuf {
let mut path = PathBuf::new();
Expand Down
44 changes: 15 additions & 29 deletions crates/wasmi/src/externref.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
use crate::{
collections::arena::ArenaIndex,
core::UntypedVal,
reftype::Transposer,
store::Stored,
AsContextMut,
StoreContext,
};
use core::{any::Any, num::NonZeroU32};
use core::{any::Any, mem, num::NonZeroU32};
use std::boxed::Box;

/// A raw index to a function entity.
Expand Down Expand Up @@ -117,24 +116,30 @@ fn externref_null_to_zero() {

impl From<UntypedVal> for ExternRef {
fn from(untyped: UntypedVal) -> Self {
if u64::from(untyped) == 0 {
return ExternRef::null();
}
// Safety: This operation is safe since there are no invalid
// bit patterns for [`ExternRef`] instances. Therefore
// this operation cannot produce invalid [`ExternRef`]
// instances even though the input [`UntypedVal`]
// was modified arbitrarily.
unsafe { <Transposer<Self>>::from(untyped).reftype }.canonicalize()
unsafe { mem::transmute::<u64, Self>(untyped.into()) }
}
}

impl From<ExternRef> for UntypedVal {
fn from(externref: ExternRef) -> Self {
let externref = externref.canonicalize();
if externref.is_null() {
return UntypedVal::from(0_u64);
}
// Safety: This operation is safe since there are no invalid
// bit patterns for [`UntypedVal`] instances. Therefore
// this operation cannot produce invalid [`UntypedVal`]
// instances even if it was possible to arbitrarily modify
// the input [`ExternRef`] instance.
Self::from(unsafe { <Transposer<ExternRef>>::new(externref).value })
let bits = unsafe { mem::transmute::<ExternRef, u64>(externref) };
UntypedVal::from(bits)
}
}

Expand All @@ -149,25 +154,6 @@ impl ExternRef {
.map(|object| ExternObject::new(ctx, object))
.map(Self::from_object)
.unwrap_or_else(Self::null)
.canonicalize()
}

/// Canonicalize `self` so that all `null` values have the same representation.
///
/// # Note
///
/// The underlying issue is that `ExternRef` has many possible values for the
/// `null` value. However, to simplify operating on encoded `ExternRef` instances
/// (encoded as `UntypedValue`) we want it to encode to exactly one `null`
/// value. The most trivial of all possible `null` values is `0_u64`, therefore
/// we canonicalize all `null` values to be represented by `0_u64`.
fn canonicalize(self) -> Self {
if self.is_null() {
// Safety: This is safe since `0u64` can be bit
// interpreted as a valid `ExternRef` value.
return unsafe { <Transposer<Self>>::null().reftype };
}
self
}

/// Creates a new [`ExternRef`] to the given [`ExternObject`].
Expand All @@ -177,16 +163,16 @@ impl ExternRef {
}
}

/// Creates a new [`ExternRef`] which is `null`.
pub fn null() -> Self {
Self { inner: None }.canonicalize()
}

/// Returns `true` if [`ExternRef`] is `null`.
pub fn is_null(&self) -> bool {
self.inner.is_none()
}

/// Creates a new [`ExternRef`] which is `null`.
pub fn null() -> Self {
Self { inner: None }
}

/// Returns a shared reference to the underlying data for this [`ExternRef`].
///
/// # Panics
Expand Down
40 changes: 14 additions & 26 deletions crates/wasmi/src/func/funcref.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use super::Func;
use crate::{core::UntypedVal, reftype::Transposer};
use crate::core::UntypedVal;
use core::mem;

/// A nullable [`Func`] reference.
#[derive(Debug, Default, Copy, Clone)]
Expand Down Expand Up @@ -35,24 +36,30 @@ fn funcref_null_to_zero() {

impl From<UntypedVal> for FuncRef {
fn from(untyped: UntypedVal) -> Self {
if u64::from(untyped) == 0 {
return FuncRef::null();
}
// Safety: This union access is safe since there are no invalid
// bit patterns for [`FuncRef`] instances. Therefore
// this operation cannot produce invalid [`FuncRef`]
// instances even though the input [`UntypedVal`]
// was modified arbitrarily.
unsafe { <Transposer<Self>>::from(untyped).reftype }.canonicalize()
unsafe { mem::transmute::<u64, Self>(untyped.into()) }
}
}

impl From<FuncRef> for UntypedVal {
fn from(funcref: FuncRef) -> Self {
let funcref = funcref.canonicalize();
if funcref.is_null() {
return UntypedVal::from(0_u64);
}
// Safety: This operation is safe since there are no invalid
// bit patterns for [`UntypedVal`] instances. Therefore
// this operation cannot produce invalid [`UntypedVal`]
// instances even if it was possible to arbitrarily modify
// the input [`FuncRef`] instance.
Self::from(unsafe { <Transposer<FuncRef>>::new(funcref).value })
let bits = unsafe { mem::transmute::<FuncRef, u64>(funcref) };
UntypedVal::from(bits)
}
}

Expand All @@ -62,22 +69,9 @@ impl FuncRef {
self.inner.is_none()
}

/// Canonicalize `self` so that all `null` values have the same representation.
///
/// # Note
///
/// The underlying issue is that `FuncRef` has many possible values for the
/// `null` value. However, to simplify operating on encoded `FuncRef` instances
/// (encoded as `UntypedValue`) we want it to encode to exactly one `null`
/// value. The most trivial of all possible `null` values is `0_u64`, therefore
/// we canonicalize all `null` values to be represented by `0_u64`.
fn canonicalize(self) -> Self {
if self.is_null() {
// Safety: This is safe since `0u64` can be bit
// interpreted as a valid `FuncRef` value.
return unsafe { <Transposer<Self>>::null().reftype };
}
self
/// Creates a `null` [`FuncRef`].
pub fn null() -> Self {
Self::new(None)
}

/// Creates a new [`FuncRef`].
Expand All @@ -95,7 +89,6 @@ impl FuncRef {
Self {
inner: nullable_func.into(),
}
.canonicalize()
}

/// Returns the inner [`Func`] if [`FuncRef`] is not `null`.
Expand All @@ -104,9 +97,4 @@ impl FuncRef {
pub fn func(&self) -> Option<&Func> {
self.inner.as_ref()
}

/// Creates a `null` [`FuncRef`].
pub fn null() -> Self {
Self::new(None).canonicalize()
}
}
1 change: 0 additions & 1 deletion crates/wasmi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,6 @@ mod limits;
mod linker;
mod memory;
mod module;
mod reftype;
mod store;
mod table;
mod value;
Expand Down
38 changes: 0 additions & 38 deletions crates/wasmi/src/reftype.rs

This file was deleted.

0 comments on commit b918b25

Please sign in to comment.