Skip to content

Commit 452e2cd

Browse files
authored
Always use fallible accesses of the GC heap (#13320)
This commit is an attempt to harden Wasmtime in the face of GC heap corruption to downgrade panics to an error being returned instead. Normal operation should never hit any of these paths and in theory this is all dead code. The intention, however, is to further downgrade the severity of GC heaps from a DoS to, in theory, maybe not even a CVE at all. This commit is inspired by the transition done for component-model-async recently too where many `assert!`'d conditions and panics were translated into `bail_bug!` within Wasmtime. This returns a special kind of error in release mode and panics in debug mode. The rationale behind this is that, like component-model-async, the GC implementation is the intersection of: * Easy for guests to control. * Difficult to guarantee 100% correctness of the host. * Low consequences if corruption is detected. * Easy to generate a trap via `?` to propagate upwards. In this situation the goal here is to more aggressively return errors, in release mode, rather than panic which risks a quick DoS of embedders. The ideal goal is for GC heap corruption to not be a DoS at all, but we're not quite ready to make that commitment just yet. Many methods in this commit were refactored to return `Result`, and many implementations internally within the GC implementation have been updated to use `bail_bug!` or similar to downgrade panics to errors. Note that in debug mode (or `cfg(debug_assertions)`) all of these are still panics. cc #13216
1 parent 5fb6a94 commit 452e2cd

38 files changed

Lines changed: 1100 additions & 1038 deletions

crates/core/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,5 @@ pub mod math;
2424
pub mod mpk;
2525
pub mod non_max;
2626
pub mod slab;
27+
pub mod truncate;
2728
pub mod undo;

crates/core/src/truncate.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
//! Explicit methods to clearly indicate that truncation is desired when used.
2+
3+
/// Explicitly truncate an `i32` to an `i16`.
4+
pub fn truncate_i32_to_i16(a: i32) -> i16 {
5+
a as i16
6+
}
7+
8+
/// Explicitly truncate an `i32` to an `i8`.
9+
pub fn truncate_i32_to_i8(a: i32) -> i8 {
10+
a as i8
11+
}

crates/environ/src/builtin.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -391,10 +391,12 @@ impl BuiltinFunctionIndex {
391391
// The final epoch represents a trap
392392
(@get new_epoch u64) => (TrapSentinel::NegativeOne);
393393

394+
// Failure here indicates GC heap corruption.
395+
(@get get_interned_func_ref pointer) => (TrapSentinel::NegativeOne);
396+
394397
// These libcalls can't trap
395398
(@get ref_func pointer) => (return None);
396399
(@get table_get_lazy_init_func_ref pointer) => (return None);
397-
(@get get_interned_func_ref pointer) => (return None);
398400
(@get intern_func_ref_for_gc_heap u64) => (return None);
399401
(@get is_subtype u32) => (return None);
400402
(@get ceil_f32 f32) => (return None);

crates/wasmtime/src/runtime/externals/global.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -270,27 +270,27 @@ impl Global {
270270
Some(e) => Some(e.try_gc_ref(&store)?.unchecked_copy()),
271271
};
272272
let new = new.as_ref();
273-
definition.write_gc_ref(&mut store, new);
273+
definition.write_gc_ref(&mut store, new)?;
274274
}
275275
Val::AnyRef(a) => {
276276
let new = match a {
277277
None => None,
278278
Some(a) => Some(a.try_gc_ref(&store)?.unchecked_copy()),
279279
};
280280
let new = new.as_ref();
281-
definition.write_gc_ref(&mut store, new);
281+
definition.write_gc_ref(&mut store, new)?;
282282
}
283283
Val::ExnRef(e) => {
284284
let new = match e {
285285
None => None,
286286
Some(e) => Some(e.try_gc_ref(&store)?.unchecked_copy()),
287287
};
288288
let new = new.as_ref();
289-
definition.write_gc_ref(&mut store, new);
289+
definition.write_gc_ref(&mut store, new)?;
290290
}
291291
Val::ContRef(None) => {
292292
// Allow null continuation references for globals - these are just placeholders
293-
definition.write_gc_ref(&mut store, None);
293+
definition.write_gc_ref(&mut store, None)?;
294294
}
295295
Val::ContRef(Some(_)) => {
296296
// TODO(#10248): Implement non-null global continuation reference handling

crates/wasmtime/src/runtime/func.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2157,11 +2157,11 @@ impl<T> Caller<'_, T> {
21572157
///
21582158
/// Same as [`Store::gc_async`](crate::Store::gc_async).
21592159
#[cfg(all(feature = "async", feature = "gc"))]
2160-
pub async fn gc_async(&mut self, why: Option<&crate::GcHeapOutOfMemory<()>>)
2160+
pub async fn gc_async(&mut self, why: Option<&crate::GcHeapOutOfMemory<()>>) -> Result<()>
21612161
where
21622162
T: Send + 'static,
21632163
{
2164-
self.store.gc_async(why).await;
2164+
self.store.gc_async(why).await
21652165
}
21662166

21672167
/// Returns the remaining fuel in the store.

crates/wasmtime/src/runtime/gc/disabled/rooting.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,10 @@ impl<T: GcRef> Rooted<T> {
118118
pub(crate) fn try_gc_ref<'a>(&self, _store: &'a StoreOpaque) -> Result<&'a VMGcRef> {
119119
match self.inner {}
120120
}
121+
122+
pub(crate) fn try_clone_gc_ref(&self, _: &mut AutoAssertNoGc<'_>) -> Result<VMGcRef> {
123+
match self.inner {}
124+
}
121125
}
122126

123127
/// This type has been disabled because the `gc` cargo feature was not enabled

crates/wasmtime/src/runtime/gc/enabled/anyref.rs

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
//! Implementation of `anyref` in Wasmtime.
22
3-
use super::{ExternRef, RootedGcRefImpl};
3+
use super::ExternRef;
44
use crate::prelude::*;
55
use crate::runtime::vm::VMGcRef;
66
use crate::{
77
ArrayRef, ArrayType, AsContext, AsContextMut, EqRef, GcRefImpl, GcRootIndex, HeapType, I31,
8-
OwnedRooted, RefType, Result, Rooted, StructRef, StructType, ValRaw, ValType, WasmTy,
8+
OwnedRooted, RefType, Result, Rooted, StructRef, StructType, ValRaw, ValType, WasmTy, bail_bug,
99
store::{AutoAssertNoGc, StoreOpaque},
1010
};
1111
use core::mem;
@@ -298,11 +298,13 @@ impl AnyRef {
298298
|| store
299299
.unwrap_gc_store()
300300
.header(&gc_ref)
301+
.unwrap()
301302
.kind()
302303
.matches(VMGcKind::AnyRef)
303304
|| store
304305
.unwrap_gc_store()
305306
.header(&gc_ref)
307+
.unwrap()
306308
.kind()
307309
.matches(VMGcKind::ExternRef)
308310
);
@@ -336,7 +338,9 @@ impl AnyRef {
336338
let raw = if gc_ref.is_i31() {
337339
gc_ref.as_raw_non_zero_u32()
338340
} else {
339-
store.require_gc_store_mut()?.expose_gc_ref_to_wasm(gc_ref)
341+
store
342+
.require_gc_store_mut()?
343+
.expose_gc_ref_to_wasm(gc_ref)?
340344
};
341345
Ok(raw.get())
342346
}
@@ -360,29 +364,33 @@ impl AnyRef {
360364
return Ok(HeapType::I31);
361365
}
362366

363-
let header = store.require_gc_store()?.header(gc_ref);
367+
let header = store.require_gc_store()?.header(gc_ref)?;
364368

365369
if header.kind().matches(VMGcKind::ExternRef) {
366370
return Ok(HeapType::Any);
367371
}
368372

369373
debug_assert!(header.kind().matches(VMGcKind::AnyRef));
370374
debug_assert!(header.kind().matches(VMGcKind::EqRef));
375+
let ty = match header.ty() {
376+
Some(ty) => ty,
377+
None => bail_bug!("ty should be present"),
378+
};
371379

372380
if header.kind().matches(VMGcKind::StructRef) {
373381
return Ok(HeapType::ConcreteStruct(
374-
StructType::from_shared_type_index(store.engine(), header.ty().unwrap()),
382+
StructType::from_shared_type_index(store.engine(), ty),
375383
));
376384
}
377385

378386
if header.kind().matches(VMGcKind::ArrayRef) {
379387
return Ok(HeapType::ConcreteArray(ArrayType::from_shared_type_index(
380388
store.engine(),
381-
header.ty().unwrap(),
389+
ty,
382390
)));
383391
}
384392

385-
unreachable!("no other kinds of `anyref`s")
393+
bail_bug!("no other kinds of `anyref`s")
386394
}
387395

388396
/// Does this `anyref` match the given type?
@@ -436,7 +444,7 @@ impl AnyRef {
436444
Ok(gc_ref.is_i31()
437445
|| store
438446
.require_gc_store()?
439-
.kind(gc_ref)
447+
.kind(gc_ref)?
440448
.matches(VMGcKind::EqRef))
441449
}
442450

@@ -564,7 +572,7 @@ impl AnyRef {
564572
Ok(!gc_ref.is_i31()
565573
&& store
566574
.require_gc_store()?
567-
.kind(gc_ref)
575+
.kind(gc_ref)?
568576
.matches(VMGcKind::StructRef))
569577
}
570578

@@ -632,7 +640,7 @@ impl AnyRef {
632640
Ok(!gc_ref.is_i31()
633641
&& store
634642
.require_gc_store()?
635-
.kind(gc_ref)
643+
.kind(gc_ref)?
636644
.matches(VMGcKind::ArrayRef))
637645
}
638646

crates/wasmtime/src/runtime/gc/enabled/arrayref.rs

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,7 @@ impl ArrayRef {
393393
"attempted to use a `ArrayRefPre` with the wrong store"
394394
);
395395

396-
let len = u32::try_from(elems.len()).unwrap();
396+
let len = u32::try_from(elems.len())?;
397397

398398
// Allocate the array.
399399
let arrayref = store
@@ -416,15 +416,17 @@ impl ArrayRef {
416416
match (|| {
417417
let elem_ty = allocator.ty.element_type();
418418
for (i, elem) in elems.enumerate() {
419-
let i = u32::try_from(i).unwrap();
419+
let i = u32::try_from(i)?;
420420
debug_assert!(i < len);
421421
arrayref.initialize_elem(&mut store, allocator.layout(), &elem_ty, i, *elem)?;
422422
}
423423
Ok(())
424424
})() {
425425
Ok(()) => Ok(Rooted::new(&mut store, arrayref.into())),
426426
Err(e) => {
427-
store.require_gc_store_mut()?.dealloc_uninit_array(arrayref);
427+
store
428+
.require_gc_store_mut()?
429+
.dealloc_uninit_array(arrayref)?;
428430
Err(e)
429431
}
430432
}
@@ -613,11 +615,11 @@ impl ArrayRef {
613615
assert!(self.comes_from_same_store(store));
614616
let gc_ref = self.inner.try_gc_ref(store)?;
615617
debug_assert!({
616-
let header = store.require_gc_store()?.header(gc_ref);
618+
let header = store.require_gc_store()?.header(gc_ref)?;
617619
header.kind().matches(VMGcKind::ArrayRef)
618620
});
619621
let arrayref = gc_ref.as_arrayref_unchecked();
620-
Ok(arrayref.len(store))
622+
arrayref.len(store)
621623
}
622624

623625
/// Get the values of this array's elements.
@@ -647,7 +649,7 @@ impl ArrayRef {
647649
let store = AutoAssertNoGc::new(store);
648650

649651
let gc_ref = self.inner.try_gc_ref(&store)?;
650-
let header = store.require_gc_store()?.header(gc_ref);
652+
let header = store.require_gc_store()?.header(gc_ref)?;
651653
debug_assert!(header.kind().matches(VMGcKind::ArrayRef));
652654

653655
let len = self._len(&store)?;
@@ -677,7 +679,7 @@ impl ArrayRef {
677679
return None;
678680
}
679681
self.index += 1;
680-
Some(self.arrayref._get(&mut self.store, i).unwrap())
682+
self.arrayref._get(&mut self.store, i).ok()
681683
}
682684

683685
#[inline]
@@ -700,7 +702,7 @@ impl ArrayRef {
700702
fn header<'a>(&self, store: &'a AutoAssertNoGc<'_>) -> Result<&'a VMGcHeader> {
701703
assert!(self.comes_from_same_store(&store));
702704
let gc_ref = self.inner.try_gc_ref(store)?;
703-
Ok(store.require_gc_store()?.header(gc_ref))
705+
Ok(store.require_gc_store()?.header(gc_ref)?)
704706
}
705707

706708
fn arrayref<'a>(&self, store: &'a AutoAssertNoGc<'_>) -> Result<&'a VMArrayRef> {
@@ -755,12 +757,12 @@ impl ArrayRef {
755757
let arrayref = self.arrayref(store)?.unchecked_copy();
756758
let field_ty = self.field_ty(store)?;
757759
let layout = self.layout(store)?;
758-
let len = arrayref.len(store);
760+
let len = arrayref.len(store)?;
759761
ensure!(
760762
index < len,
761763
"index out of bounds: the length is {len} but the index is {index}"
762764
);
763-
Ok(arrayref.read_elem(store, &layout, field_ty.element_type(), index))
765+
arrayref.read_elem(store, &layout, field_ty.element_type(), index)
764766
}
765767

766768
/// Set this array's `index`th element.
@@ -811,7 +813,7 @@ impl ArrayRef {
811813
let layout = self.layout(&store)?;
812814
let arrayref = self.arrayref(&store)?.unchecked_copy();
813815

814-
let len = arrayref.len(&store);
816+
let len = arrayref.len(&store)?;
815817
ensure!(
816818
index < len,
817819
"index out of bounds: the length is {len} but the index is {index}"
@@ -822,7 +824,7 @@ impl ArrayRef {
822824

823825
pub(crate) fn type_index(&self, store: &StoreOpaque) -> Result<VMSharedTypeIndex> {
824826
let gc_ref = self.inner.try_gc_ref(store)?;
825-
let header = store.require_gc_store()?.header(gc_ref);
827+
let header = store.require_gc_store()?.header(gc_ref)?;
826828
debug_assert!(header.kind().matches(VMGcKind::ArrayRef));
827829
Ok(header.ty().expect("arrayrefs should have concrete types"))
828830
}

crates/wasmtime/src/runtime/gc/enabled/eqref.rs

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
#![cfg(feature = "gc")]
33
use crate::{
44
AnyRef, ArrayRef, ArrayType, AsContext, AsContextMut, GcRefImpl, GcRootIndex, HeapType, I31,
5-
OwnedRooted, RefType, Rooted, StructRef, StructType, ValRaw, ValType, WasmTy,
5+
OwnedRooted, RefType, Rooted, StructRef, StructType, ValRaw, ValType, WasmTy, bail_bug,
66
prelude::*,
77
runtime::vm::VMGcRef,
88
store::{AutoAssertNoGc, StoreOpaque},
@@ -168,6 +168,7 @@ impl EqRef {
168168
|| store
169169
.unwrap_gc_store()
170170
.header(&gc_ref)
171+
.unwrap()
171172
.kind()
172173
.matches(VMGcKind::EqRef)
173174
);
@@ -198,22 +199,26 @@ impl EqRef {
198199
return Ok(HeapType::I31);
199200
}
200201

201-
let header = store.require_gc_store()?.header(gc_ref);
202+
let header = store.require_gc_store()?.header(gc_ref)?;
203+
let ty = match header.ty() {
204+
Some(ty) => ty,
205+
None => bail_bug!("ty should be present"),
206+
};
202207

203208
if header.kind().matches(VMGcKind::StructRef) {
204209
return Ok(HeapType::ConcreteStruct(
205-
StructType::from_shared_type_index(store.engine(), header.ty().unwrap()),
210+
StructType::from_shared_type_index(store.engine(), ty),
206211
));
207212
}
208213

209214
if header.kind().matches(VMGcKind::ArrayRef) {
210215
return Ok(HeapType::ConcreteArray(ArrayType::from_shared_type_index(
211216
store.engine(),
212-
header.ty().unwrap(),
217+
ty,
213218
)));
214219
}
215220

216-
unreachable!("no other kinds of `eqref`s")
221+
bail_bug!("no other kinds of `eqref`s")
217222
}
218223

219224
/// Does this `eqref` match the given type?
@@ -350,7 +355,7 @@ impl EqRef {
350355
Ok(!gc_ref.is_i31()
351356
&& store
352357
.require_gc_store()?
353-
.kind(gc_ref)
358+
.kind(gc_ref)?
354359
.matches(VMGcKind::StructRef))
355360
}
356361

@@ -418,7 +423,7 @@ impl EqRef {
418423
Ok(!gc_ref.is_i31()
419424
&& store
420425
.require_gc_store()?
421-
.kind(gc_ref)
426+
.kind(gc_ref)?
422427
.matches(VMGcKind::ArrayRef))
423428
}
424429

0 commit comments

Comments
 (0)