Skip to content

Commit 8a0b0df

Browse files
committed
Harden Wasmtime's compiled GC code against corruption
In the spirit of #13320 this commit goes through the compiled code for the GC proposal to ensure that, in the face of GC corruption, Wasmtime by default can recover and return a "bug" to the embedder. This was also discussed a bit in #13112 as well, and the changes made here are: * Plumbing traps from translation into the runtime now uses a new `CompiledTrap` enum instead of just the normal `Trap`. This new enum has branches for `InternalAssert` (not previously present) and additionally `GcHeapCorrupted` (now added). * Whether or not `CompiledTrap::{InternalAssert,GcHeapCorrupted}` is encoded into the final `*.cwasm` is now a `Tunables` configuration option. Internal asserts are not encoded by default but GC heap corruption is. * Traps caught as `CompiledTrap::{InternalAssert,GcHeapCorrupted}` are turned into `WasmtimeBug` and propagated upwards. Traps stay as normal traps. * All memory accesses to the GC heap now use `CompiledTrap::GcHeapCorrupted` as their trap code. Additionally they're also no longer marked as `readonly` in a few places. * A few locations in GC translation using `InternalAssert` now use `GcHeapCorrupted`, such as the checked arithmetic around array lengths. Other assertions which are about control flow are left untouched. The end state is that faults in the GC heap in compiled code itself should show up as a `bug!` on the other end by default. This requires extra metadata in `*.cwasm`s mapping traps, but this is similar to linear-memory-using-wasms which have lots of trap metadata for loads/stores. Being able to catch `InternalAssert` as a first-class error (as opposed to a signal) is a debugging nicety I've added here but remains off-by-default to avoid bloating `*.cwasm`s for internal debugging. Closes #13112
1 parent def85dd commit 8a0b0df

148 files changed

Lines changed: 911 additions & 747 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

crates/cli-flags/src/lib.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,13 @@ wasmtime_option_group! {
266266
#[serde(deserialize_with = "crate::opt::cli_parse_wrapper")]
267267
pub inlining: Option<wasmtime::Inlining>,
268268

269+
/// Whether or not trap metadata is present for wasm internal assertions
270+
/// in compiled code.
271+
pub metadata_for_internal_asserts: Option<bool>,
272+
/// Whether or not trap metadata is present for detection of gc
273+
/// corruption in compiled code.
274+
pub metadata_for_gc_heap_corruption: Option<bool>,
275+
269276
#[prefixed = "cranelift"]
270277
#[serde(default)]
271278
/// Set a cranelift-specific option. Use `wasmtime settings` to see
@@ -975,6 +982,12 @@ impl CommonOptions {
975982
if let Some(enable) = self.codegen.inlining {
976983
config.compiler_inlining(enable);
977984
}
985+
if let Some(enable) = self.codegen.metadata_for_internal_asserts {
986+
config.metadata_for_internal_asserts(enable);
987+
}
988+
if let Some(enable) = self.codegen.metadata_for_gc_heap_corruption {
989+
config.metadata_for_gc_heap_corruption(enable);
990+
}
978991

979992
// async_stack_size enabled by either async or stack-switching, so
980993
// cannot directly use match_feature!

crates/cranelift/src/compiled_function.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use cranelift_codegen::{
77
};
88
use wasmtime_environ::{
99
FilePos, FrameStateSlotBuilder, InstructionAddressMap, ModulePC, PrimaryMap, TrapInformation,
10+
Tunables,
1011
};
1112

1213
#[derive(Debug, Clone, PartialEq, Eq, Default)]
@@ -144,8 +145,14 @@ impl CompiledFunction {
144145
}
145146

146147
/// Returns an iterator to the function's trap information.
147-
pub fn traps(&self) -> impl Iterator<Item = TrapInformation> + '_ {
148-
self.buffer.traps().iter().filter_map(mach_trap_to_trap)
148+
pub fn traps<'a>(
149+
&'a self,
150+
tunables: &'a Tunables,
151+
) -> impl Iterator<Item = TrapInformation> + 'a {
152+
self.buffer
153+
.traps()
154+
.iter()
155+
.filter_map(move |t| mach_trap_to_trap(t, tunables))
149156
}
150157

151158
/// Get the function's address map from the metadata.

crates/cranelift/src/compiler.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -540,7 +540,10 @@ impl wasmtime_environ::Compiler for Compiler {
540540
func.buffer.user_stack_maps(),
541541
);
542542

543-
traps.push(range.clone(), &func.traps().collect::<Vec<_>>());
543+
traps.push(
544+
range.clone(),
545+
&func.traps(&self.tunables).collect::<Vec<_>>(),
546+
);
544547
clif_to_env_exception_tables(
545548
&mut exception_tables,
546549
range.clone(),

crates/cranelift/src/func_environ/gc/enabled.rs

Lines changed: 21 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use crate::bounds_checks::BoundsCheck;
33
use crate::func_environ::{Extension, FuncEnvironment};
44
use crate::translate::{Heap, HeapData, MemoryKind, StructFieldsVec, TargetEnvironment};
55
use crate::trap::TranslateTrap;
6-
use crate::{Reachability, TRAP_INTERNAL_ASSERT};
6+
use crate::{Reachability, TRAP_GC_HEAP_CORRUPT, TRAP_INTERNAL_ASSERT};
77
use cranelift_codegen::ir::immediates::Offset32;
88
use cranelift_codegen::ir::{BlockArg, ExceptionTableData, ExceptionTableItem};
99
use cranelift_codegen::{
@@ -27,6 +27,9 @@ mod null;
2727
#[cfg(feature = "gc-copying")]
2828
mod copying;
2929

30+
const GC_MEMFLAGS: ir::MemFlags =
31+
ir::MemFlags::new().with_trap_code(Some(crate::TRAP_GC_HEAP_CORRUPT));
32+
3033
/// Get the default GC compiler.
3134
pub fn gc_compiler(func_env: &mut FuncEnvironment<'_>) -> WasmResult<Box<dyn GcCompiler>> {
3235
// If this function requires a GC compiler, that is not too bad of an
@@ -161,12 +164,9 @@ fn emit_gc_kind_assert(
161164
object_size: wasmtime_environ::VM_GC_HEADER_SIZE,
162165
},
163166
);
164-
let kind_and_reserved_bits = builder.ins().load(
165-
ir::types::I32,
166-
ir::MemFlags::trusted().with_readonly(),
167-
kind_addr,
168-
0,
169-
);
167+
let kind_and_reserved_bits = builder
168+
.ins()
169+
.load(ir::types::I32, GC_MEMFLAGS, kind_addr, 0);
170170
let kind_mask = builder
171171
.ins()
172172
.iconst(ir::types::I32, i64::from(VMGcKind::MASK));
@@ -202,7 +202,7 @@ fn read_field_at_addr(
202202
);
203203

204204
// Data inside GC objects is always little endian.
205-
let flags = ir::MemFlags::trusted().with_endianness(ir::Endianness::Little);
205+
let flags = GC_MEMFLAGS.with_endianness(ir::Endianness::Little);
206206

207207
let value = match ty {
208208
WasmStorageType::I8 => builder.ins().load(ir::types::I8, flags, addr, 0),
@@ -321,7 +321,7 @@ fn write_field_at_addr(
321321
new_val: ir::Value,
322322
) -> WasmResult<()> {
323323
// Data inside GC objects is always little endian.
324-
let flags = ir::MemFlags::trusted().with_endianness(ir::Endianness::Little);
324+
let flags = GC_MEMFLAGS.with_endianness(ir::Endianness::Little);
325325

326326
match field_ty {
327327
WasmStorageType::I8 => {
@@ -928,12 +928,9 @@ pub fn translate_array_len(
928928
access_size: u8::try_from(ir::types::I32.bytes()).unwrap(),
929929
},
930930
);
931-
let result = builder.ins().load(
932-
ir::types::I32,
933-
ir::MemFlags::trusted().with_readonly(),
934-
len_field,
935-
0,
936-
);
931+
let result = builder
932+
.ins()
933+
.load(ir::types::I32, GC_MEMFLAGS, len_field, 0);
937934
log::trace!("translate_array_len(..) -> {result:?}");
938935
Ok(result)
939936
}
@@ -973,7 +970,7 @@ fn emit_array_size_info(
973970
let all_elems_size = builder.ins().imul(one_elem_size, array_len);
974971

975972
let high_bits = builder.ins().ushr_imm(all_elems_size, 32);
976-
builder.ins().trapnz(high_bits, TRAP_INTERNAL_ASSERT);
973+
builder.ins().trapnz(high_bits, TRAP_GC_HEAP_CORRUPT);
977974

978975
let all_elems_size = builder.ins().ireduce(ir::types::I32, all_elems_size);
979976
let base_size = builder
@@ -982,7 +979,7 @@ fn emit_array_size_info(
982979
let obj_size =
983980
builder
984981
.ins()
985-
.uadd_overflow_trap(all_elems_size, base_size, TRAP_INTERNAL_ASSERT);
982+
.uadd_overflow_trap(all_elems_size, base_size, TRAP_GC_HEAP_CORRUPT);
986983

987984
let one_elem_size = builder.ins().ireduce(ir::types::I32, one_elem_size);
988985

@@ -1244,12 +1241,9 @@ pub fn translate_ref_test(
12441241
object_size: wasmtime_environ::VM_GC_HEADER_SIZE,
12451242
},
12461243
);
1247-
let actual_kind = builder.ins().load(
1248-
ir::types::I32,
1249-
ir::MemFlags::trusted().with_readonly(),
1250-
kind_addr,
1251-
0,
1252-
);
1244+
let actual_kind = builder
1245+
.ins()
1246+
.load(ir::types::I32, GC_MEMFLAGS, kind_addr, 0);
12531247
let expected_kind = builder
12541248
.ins()
12551249
.iconst(ir::types::I32, i64::from(expected_kind.as_u32()));
@@ -1301,12 +1295,7 @@ pub fn translate_ref_test(
13011295
access_size: func_env.offsets.size_of_vmshared_type_index(),
13021296
},
13031297
);
1304-
let actual_shared_ty = builder.ins().load(
1305-
ir::types::I32,
1306-
ir::MemFlags::trusted().with_readonly(),
1307-
ty_addr,
1308-
0,
1309-
);
1298+
let actual_shared_ty = builder.ins().load(ir::types::I32, GC_MEMFLAGS, ty_addr, 0);
13101299

13111300
func_env.is_subtype(builder, actual_shared_ty, expected_shared_ty)
13121301
}
@@ -1319,11 +1308,8 @@ pub fn translate_ref_test(
13191308
let expected_shared_ty =
13201309
func_env.module_interned_to_shared_ty(&mut builder.cursor(), expected_interned_ty);
13211310

1322-
let actual_shared_ty = func_env.load_funcref_type_index(
1323-
&mut builder.cursor(),
1324-
ir::MemFlags::trusted().with_readonly(),
1325-
val,
1326-
);
1311+
let actual_shared_ty =
1312+
func_env.load_funcref_type_index(&mut builder.cursor(), GC_MEMFLAGS, val);
13271313

13281314
func_env.is_subtype(builder, actual_shared_ty, expected_shared_ty)
13291315
}
@@ -1594,7 +1580,7 @@ impl FuncEnvironment<'_> {
15941580
&gc_heap,
15951581
gc_ref,
15961582
bounds_check,
1597-
crate::TRAP_INTERNAL_ASSERT,
1583+
crate::TRAP_GC_HEAP_CORRUPT,
15981584
) {
15991585
Reachability::Reachable(v) => v,
16001586
Reachability::Unreachable => {

crates/cranelift/src/func_environ/gc/enabled/copying.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ impl CopyingCompiler {
3333
val: ir::Value,
3434
) -> WasmResult<()> {
3535
// Data inside GC objects is always little endian.
36-
let flags = ir::MemFlags::trusted().with_endianness(ir::Endianness::Little);
36+
let flags = GC_MEMFLAGS.with_endianness(ir::Endianness::Little);
3737

3838
match ty {
3939
WasmStorageType::Val(WasmValType::Ref(r)) => match r.heap_type.top() {
@@ -109,9 +109,7 @@ impl GcCompiler for CopyingCompiler {
109109
let object_addr = builder.ins().iadd(base, extended_array_ref);
110110
let len_addr = builder.ins().iadd_imm(object_addr, i64::from(len_offset));
111111
let len = init.len(&mut builder.cursor());
112-
builder
113-
.ins()
114-
.store(ir::MemFlags::trusted(), len, len_addr, 0);
112+
builder.ins().store(GC_MEMFLAGS, len, len_addr, 0);
115113

116114
// Initialize elements.
117115
let len_to_elems_delta = builder.ins().iconst(ptr_ty, i64::from(len_to_elems_delta));

crates/cranelift/src/func_environ/gc/enabled/drc.rs

Lines changed: 10 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,7 @@ impl DrcCompiler {
3939
access_size: u8::try_from(ir::types::I64.bytes()).unwrap(),
4040
},
4141
);
42-
builder
43-
.ins()
44-
.load(ir::types::I64, ir::MemFlags::trusted(), pointer, 0)
42+
builder.ins().load(ir::types::I64, GC_MEMFLAGS, pointer, 0)
4543
}
4644

4745
/// Generate code to update the given GC reference's ref count to the new
@@ -64,9 +62,7 @@ impl DrcCompiler {
6462
access_size: u8::try_from(ir::types::I64.bytes()).unwrap(),
6563
},
6664
);
67-
builder
68-
.ins()
69-
.store(ir::MemFlags::trusted(), new_ref_count, pointer, 0);
65+
builder.ins().store(GC_MEMFLAGS, new_ref_count, pointer, 0);
7066
}
7167

7268
/// Generate code to increment or decrement the given GC reference's ref
@@ -108,9 +104,7 @@ impl DrcCompiler {
108104

109105
// Load the current first list element, which will be our new next list
110106
// element.
111-
let next = builder
112-
.ins()
113-
.load(ir::types::I32, ir::MemFlags::trusted(), head, 0);
107+
let next = builder.ins().load(ir::types::I32, GC_MEMFLAGS, head, 0);
114108

115109
// Update our object's header to point to `next` and consider itself part of the list.
116110
self.set_next_over_approximated_stack_root(func_env, builder, gc_ref, next);
@@ -120,9 +114,7 @@ impl DrcCompiler {
120114
self.mutate_ref_count(func_env, builder, gc_ref, 1);
121115

122116
// Commit this object as the new head of the list.
123-
builder
124-
.ins()
125-
.store(ir::MemFlags::trusted(), gc_ref, head, 0);
117+
builder.ins().store(GC_MEMFLAGS, gc_ref, head, 0);
126118
}
127119

128120
/// Load a pointer to the first element of the DRC heap's
@@ -137,7 +129,7 @@ impl DrcCompiler {
137129
let vmctx = builder.ins().global_value(ptr_ty, vmctx);
138130
builder.ins().load(
139131
ptr_ty,
140-
ir::MemFlags::trusted().with_readonly(),
132+
GC_MEMFLAGS,
141133
vmctx,
142134
i32::from(func_env.offsets.ptr.vmctx_gc_heap_data()),
143135
)
@@ -163,7 +155,7 @@ impl DrcCompiler {
163155
access_size: u8::try_from(ir::types::I32.bytes()).unwrap(),
164156
},
165157
);
166-
builder.ins().store(ir::MemFlags::trusted(), next, ptr, 0);
158+
builder.ins().store(GC_MEMFLAGS, next, ptr, 0);
167159
}
168160

169161
/// Set the in-over-approximated-stack-roots list bit in a `VMDrcHeader`'s
@@ -199,9 +191,7 @@ impl DrcCompiler {
199191
access_size: u8::try_from(ir::types::I32.bytes()).unwrap(),
200192
},
201193
);
202-
builder
203-
.ins()
204-
.store(ir::MemFlags::trusted(), new_reserved, ptr, 0);
194+
builder.ins().store(GC_MEMFLAGS, new_reserved, ptr, 0);
205195
}
206196

207197
/// Write to an uninitialized field or element inside a GC object.
@@ -214,7 +204,7 @@ impl DrcCompiler {
214204
val: ir::Value,
215205
) -> WasmResult<()> {
216206
// Data inside GC objects is always little endian.
217-
let flags = ir::MemFlags::trusted().with_endianness(ir::Endianness::Little);
207+
let flags = GC_MEMFLAGS.with_endianness(ir::Endianness::Little);
218208

219209
match ty {
220210
WasmStorageType::Val(WasmValType::Ref(r)) => match r.heap_type.top() {
@@ -396,9 +386,7 @@ impl GcCompiler for DrcCompiler {
396386
let object_addr = builder.ins().iadd(base, extended_array_ref);
397387
let len_addr = builder.ins().iadd_imm(object_addr, i64::from(len_offset));
398388
let len = init.len(&mut builder.cursor());
399-
builder
400-
.ins()
401-
.store(ir::MemFlags::trusted(), len, len_addr, 0);
389+
builder.ins().store(GC_MEMFLAGS, len, len_addr, 0);
402390

403391
// Finally, initialize the elements.
404392
let len_to_elems_delta = builder.ins().iconst(ptr_ty, i64::from(len_to_elems_delta));
@@ -666,9 +654,7 @@ impl GcCompiler for DrcCompiler {
666654
access_size: u8::try_from(ir::types::I32.bytes()).unwrap(),
667655
},
668656
);
669-
let reserved = builder
670-
.ins()
671-
.load(ir::types::I32, ir::MemFlags::trusted(), ptr, 0);
657+
let reserved = builder.ins().load(ir::types::I32, GC_MEMFLAGS, ptr, 0);
672658
let in_set_bit = builder.ins().iconst(
673659
ir::types::I32,
674660
i64::from(wasmtime_environ::drc::HEADER_IN_OVER_APPROX_LIST_BIT),

crates/cranelift/src/func_environ/gc/enabled/null.rs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -73,13 +73,13 @@ impl NullCompiler {
7373
let vmctx = func_env.vmctx_val(&mut builder.cursor());
7474
let ptr_to_next = builder.ins().load(
7575
pointer_type,
76-
ir::MemFlags::trusted().with_readonly(),
76+
GC_MEMFLAGS,
7777
vmctx,
7878
i32::from(func_env.offsets.ptr.vmctx_gc_heap_data()),
7979
);
8080
let next = builder
8181
.ins()
82-
.load(ir::types::I32, ir::MemFlags::trusted(), ptr_to_next, 0);
82+
.load(ir::types::I32, GC_MEMFLAGS, ptr_to_next, 0);
8383

8484
// Increment the bump "pointer" to the requested alignment:
8585
//
@@ -157,20 +157,20 @@ impl NullCompiler {
157157
),
158158
};
159159
builder.ins().store(
160-
ir::MemFlags::trusted(),
160+
GC_MEMFLAGS,
161161
kind_and_size,
162162
ptr_to_object,
163163
i32::try_from(wasmtime_environ::VM_GC_HEADER_KIND_OFFSET).unwrap(),
164164
);
165165
builder.ins().store(
166-
ir::MemFlags::trusted(),
166+
GC_MEMFLAGS,
167167
ty,
168168
ptr_to_object,
169169
i32::try_from(wasmtime_environ::VM_GC_HEADER_TYPE_INDEX_OFFSET).unwrap(),
170170
);
171171
builder
172172
.ins()
173-
.store(ir::MemFlags::trusted(), end_of_object, ptr_to_next, 0);
173+
.store(GC_MEMFLAGS, end_of_object, ptr_to_next, 0);
174174

175175
log::trace!("emit_inline_alloc(..) -> ({aligned}, {ptr_to_object})");
176176
(aligned, ptr_to_object)
@@ -223,9 +223,7 @@ impl GcCompiler for NullCompiler {
223223
// any pointers or offsets out from the (untrusted) GC heap.
224224
let len_addr = builder.ins().iadd_imm(ptr_to_object, i64::from(len_offset));
225225
let len = init.len(&mut builder.cursor());
226-
builder
227-
.ins()
228-
.store(ir::MemFlags::trusted(), len, len_addr, 0);
226+
builder.ins().store(GC_MEMFLAGS, len, len_addr, 0);
229227

230228
// Finally, initialize the elements.
231229
let len_to_elems_delta = builder.ins().iconst(ptr_ty, i64::from(len_to_elems_delta));

0 commit comments

Comments
 (0)