Path: blob/main/crates/cranelift/src/bounds_checks.rs
3092 views
//! Implementation of Wasm to CLIF memory access translation.1//!2//! Given3//!4//! * a dynamic Wasm memory index operand,5//! * a static offset immediate, and6//! * a static access size,7//!8//! bounds check the memory access and translate it into a native memory access.9//!10//! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!11//! !!! !!!12//! !!! THIS CODE IS VERY SUBTLE, HAS MANY SPECIAL CASES, AND IS ALSO !!!13//! !!! ABSOLUTELY CRITICAL FOR MAINTAINING THE SAFETY OF THE WASM HEAP !!!14//! !!! SANDBOX. !!!15//! !!! !!!16//! !!! A good rule of thumb is to get two reviews on any substantive !!!17//! !!! changes in here. !!!18//! !!! !!!19//! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!2021use crate::{22Reachability,23func_environ::FuncEnvironment,24translate::{HeapData, TargetEnvironment},25};26use Reachability::*;27use cranelift_codegen::{28cursor::{Cursor, FuncCursor},29ir::{self, InstBuilder, RelSourceLoc, condcodes::IntCC},30ir::{Expr, Fact},31};32use cranelift_frontend::FunctionBuilder;3334/// The kind of bounds check to perform when accessing a Wasm linear memory or35/// GC heap.36///37/// Prefer `BoundsCheck::*WholeObject` over `BoundsCheck::Field` when possible,38/// as that approach allows the mid-end to deduplicate bounds checks across39/// multiple accesses to the same GC object.40#[derive(Debug)]41pub enum BoundsCheck {42/// Check that this one access in particular is in bounds:43///44/// ```ignore45/// index + offset + access_size <= bound46/// ```47StaticOffset { offset: u32, access_size: u8 },4849/// Assuming the precondition `offset + access_size <= object_size`, check50/// that this whole object is in bounds:51///52/// ```ignore53/// index + object_size <= bound54/// ```55#[cfg(feature = "gc")]56StaticObjectField {57offset: u32,58access_size: u8,59object_size: u32,60},6162/// Like `StaticWholeObject` but with dynamic offset and object size.63///64/// It is *your* responsibility to ensure that the `offset + access_size <=65/// object_size` precondition holds.66#[cfg(feature = "gc")]67DynamicObjectField {68offset: ir::Value,69object_size: ir::Value,70},71}7273/// Helper used to emit bounds checks (as necessary) and compute the native74/// address of a heap access.75///76/// Returns the `ir::Value` holding the native address of the heap access, or77/// `Reachability::Unreachable` if the heap access will unconditionally trap and78/// any subsequent code in this basic block is unreachable.79pub fn bounds_check_and_compute_addr(80builder: &mut FunctionBuilder,81env: &mut FuncEnvironment<'_>,82heap: &HeapData,83index: ir::Value,84bounds_check: BoundsCheck,85trap: ir::TrapCode,86) -> Reachability<ir::Value> {87match bounds_check {88BoundsCheck::StaticOffset {89offset,90access_size,91} => bounds_check_field_access(builder, env, heap, index, offset, access_size, trap),9293#[cfg(feature = "gc")]94BoundsCheck::StaticObjectField {95offset,96access_size,97object_size,98} => {99// Assert that the precondition holds.100let offset_and_access_size = offset.checked_add(access_size.into()).unwrap();101assert!(offset_and_access_size <= object_size);102103// When we can, pretend that we are doing one big access of the104// whole object all at once. This enables better GVN for repeated105// accesses of the same object.106if let Ok(object_size) = u8::try_from(object_size) {107let obj_ptr = match bounds_check_field_access(108builder,109env,110heap,111index,1120,113object_size,114trap,115) {116Reachable(v) => v,117u @ Unreachable => return u,118};119let offset = builder.ins().iconst(env.pointer_type(), i64::from(offset));120let field_ptr = builder.ins().iadd(obj_ptr, offset);121return Reachable(field_ptr);122}123124// Otherwise, bounds check just this one field's access.125bounds_check_field_access(builder, env, heap, index, offset, access_size, trap)126}127128// Compute the index of the end of the object, bounds check that and get129// a pointer to just after the object, and then reverse offset from that130// to get the pointer to the field being accessed.131#[cfg(feature = "gc")]132BoundsCheck::DynamicObjectField {133offset,134object_size,135} => {136assert_eq!(heap.index_type(), ir::types::I32);137assert_eq!(builder.func.dfg.value_type(index), ir::types::I32);138assert_eq!(builder.func.dfg.value_type(offset), ir::types::I32);139assert_eq!(builder.func.dfg.value_type(object_size), ir::types::I32);140141let index_and_object_size = builder.ins().uadd_overflow_trap(index, object_size, trap);142let ptr_just_after_obj = match bounds_check_field_access(143builder,144env,145heap,146index_and_object_size,1470,1480,149trap,150) {151Reachable(v) => v,152u @ Unreachable => return u,153};154155let backwards_offset = builder.ins().isub(object_size, offset);156let backwards_offset = cast_index_to_pointer_ty(157backwards_offset,158ir::types::I32,159env.pointer_type(),160false,161&mut builder.cursor(),162trap,163);164165let field_ptr = builder.ins().isub(ptr_just_after_obj, backwards_offset);166Reachable(field_ptr)167}168}169}170171fn bounds_check_field_access(172builder: &mut FunctionBuilder,173env: &mut FuncEnvironment<'_>,174heap: &HeapData,175index: ir::Value,176offset: u32,177access_size: u8,178trap: ir::TrapCode,179) -> Reachability<ir::Value> {180let pointer_bit_width = u16::try_from(env.pointer_type().bits()).unwrap();181let bound_gv = heap.bound;182let orig_index = index;183let clif_memory_traps_enabled = env.clif_memory_traps_enabled();184let spectre_mitigations_enabled =185env.heap_access_spectre_mitigation() && clif_memory_traps_enabled;186let pcc = env.proof_carrying_code();187188let host_page_size_log2 = env.target_config().page_size_align_log2;189let can_use_virtual_memory = heap190.memory191.can_use_virtual_memory(env.tunables(), host_page_size_log2)192&& clif_memory_traps_enabled;193let can_elide_bounds_check = heap194.memory195.can_elide_bounds_check(env.tunables(), host_page_size_log2)196&& clif_memory_traps_enabled;197let memory_guard_size = env.tunables().memory_guard_size;198let memory_reservation = env.tunables().memory_reservation;199200let offset_and_size = offset_plus_size(offset, access_size);201let statically_in_bounds = statically_in_bounds(&builder.func, heap, index, offset_and_size);202203let index = cast_index_to_pointer_ty(204index,205heap.index_type(),206env.pointer_type(),207heap.pcc_memory_type.is_some(),208&mut builder.cursor(),209trap,210);211212let oob_behavior = if spectre_mitigations_enabled {213OobBehavior::ConditionallyLoadFromZero {214select_spectre_guard: true,215}216} else if env.load_from_zero_allowed() {217OobBehavior::ConditionallyLoadFromZero {218select_spectre_guard: false,219}220} else {221OobBehavior::ExplicitTrap222};223224let make_compare = |builder: &mut FunctionBuilder,225compare_kind: IntCC,226lhs: ir::Value,227lhs_off: Option<i64>,228rhs: ir::Value,229rhs_off: Option<i64>| {230let result = builder.ins().icmp(compare_kind, lhs, rhs);231if pcc {232// Name the original value as a def of the SSA value;233// if the value was extended, name that as well with a234// dynamic range, overwriting the basic full-range235// fact that we previously put on the uextend.236builder.func.dfg.facts[orig_index] = Some(Fact::Def { value: orig_index });237if index != orig_index {238builder.func.dfg.facts[index] = Some(Fact::value(pointer_bit_width, orig_index));239}240241// Create a fact on the LHS that is a "trivial symbolic242// fact": v1 has range v1+LHS_off..=v1+LHS_off243builder.func.dfg.facts[lhs] = Some(Fact::value_offset(244pointer_bit_width,245orig_index,246lhs_off.unwrap(),247));248// If the RHS is a symbolic value (v1 or gv1), we can249// emit a Compare fact.250if let Some(rhs) = builder.func.dfg.facts[rhs]251.as_ref()252.and_then(|f| f.as_symbol())253{254builder.func.dfg.facts[result] = Some(Fact::Compare {255kind: compare_kind,256lhs: Expr::offset(&Expr::value(orig_index), lhs_off.unwrap()).unwrap(),257rhs: Expr::offset(rhs, rhs_off.unwrap()).unwrap(),258});259}260// Likewise, if the RHS is a constant, we can emit a261// Compare fact.262if let Some(k) = builder.func.dfg.facts[rhs]263.as_ref()264.and_then(|f| f.as_const(pointer_bit_width))265{266builder.func.dfg.facts[result] = Some(Fact::Compare {267kind: compare_kind,268lhs: Expr::offset(&Expr::value(orig_index), lhs_off.unwrap()).unwrap(),269rhs: Expr::constant((k as i64).checked_add(rhs_off.unwrap()).unwrap()),270});271}272}273result274};275276// We need to emit code that will trap (or compute an address that will trap277// when accessed) if278//279// index + offset + access_size > bound280//281// or if the `index + offset + access_size` addition overflows.282//283// Note that we ultimately want a 64-bit integer (we only target 64-bit284// architectures at the moment) and that `offset` is a `u32` and285// `access_size` is a `u8`. This means that we can add the latter together286// as `u64`s without fear of overflow, and we only have to be concerned with287// whether adding in `index` will overflow.288//289// Finally, the following if/else chains do have a little290// bit of duplicated code across them, but I think writing it this way is291// worth it for readability and seeing very clearly each of our cases for292// different bounds checks and optimizations of those bounds checks. It is293// intentionally written in a straightforward case-matching style that will294// hopefully make it easy to port to ISLE one day.295if offset_and_size > heap.memory.maximum_byte_size().unwrap_or(u64::MAX) {296// Special case: trap immediately if `offset + access_size >297// max_memory_size`, since we will end up being out-of-bounds regardless298// of the given `index`.299env.before_unconditionally_trapping_memory_access(builder);300env.trap(builder, trap);301return Unreachable;302}303304// Special case: if this is a 32-bit platform and the `offset_and_size`305// overflows the 32-bit address space then there's no hope of this ever306// being in-bounds. We can't represent `offset_and_size` in CLIF as the307// native pointer type anyway, so this is an unconditional trap.308if pointer_bit_width < 64 && offset_and_size >= (1 << pointer_bit_width) {309env.before_unconditionally_trapping_memory_access(builder);310env.trap(builder, trap);311return Unreachable;312}313314// Special case for when we can completely omit explicit315// bounds checks for 32-bit memories.316//317// First, let's rewrite our comparison to move all of the constants318// to one side:319//320// index + offset + access_size > bound321// ==> index > bound - (offset + access_size)322//323// We know the subtraction on the right-hand side won't wrap because324// we didn't hit the unconditional trap case above.325//326// Additionally, we add our guard pages (if any) to the right-hand327// side, since we can rely on the virtual memory subsystem at runtime328// to catch out-of-bound accesses within the range `bound .. bound +329// guard_size`. So now we are dealing with330//331// index > bound + guard_size - (offset + access_size)332//333// Note that `bound + guard_size` cannot overflow for334// correctly-configured heaps, as otherwise the heap wouldn't fit in335// a 64-bit memory space.336//337// The complement of our should-this-trap comparison expression is338// the should-this-not-trap comparison expression:339//340// index <= bound + guard_size - (offset + access_size)341//342// If we know the right-hand side is greater than or equal to343// `u32::MAX`, then344//345// index <= u32::MAX <= bound + guard_size - (offset + access_size)346//347// This expression is always true when the heap is indexed with348// 32-bit integers because `index` cannot be larger than349// `u32::MAX`. This means that `index` is always either in bounds or350// within the guard page region, neither of which require emitting an351// explicit bounds check.352if can_elide_bounds_check353&& u64::from(u32::MAX) <= memory_reservation + memory_guard_size - offset_and_size354{355assert!(heap.index_type() == ir::types::I32);356assert!(357can_use_virtual_memory,358"static memories require the ability to use virtual memory"359);360return Reachable(compute_addr(361&mut builder.cursor(),362heap,363env.pointer_type(),364index,365offset,366AddrPcc::static32(heap.pcc_memory_type, memory_reservation + memory_guard_size),367));368}369370// Special case when the `index` is a constant and statically known to be371// in-bounds on this memory, no bounds checks necessary.372if statically_in_bounds {373return Reachable(compute_addr(374&mut builder.cursor(),375heap,376env.pointer_type(),377index,378offset,379AddrPcc::static32(heap.pcc_memory_type, memory_reservation + memory_guard_size),380));381}382383// Special case for when we can rely on virtual memory, the minimum384// byte size of this memory fits within the memory reservation, and385// memory isn't allowed to move. In this situation we know that386// memory will statically not grow beyond `memory_reservation` so we387// and we know that memory from 0 to that limit is guaranteed to be388// valid or trap. Here we effectively assume that the dynamic size389// of linear memory is its maximal value, `memory_reservation`, and390// we can avoid loading the actual length of memory.391//392// We have to explicitly test whether393//394// index > bound - (offset + access_size)395//396// and trap if so.397//398// Since we have to emit explicit bounds checks, we might as well be399// precise, not rely on the virtual memory subsystem at all, and not400// factor in the guard pages here.401if can_use_virtual_memory402&& heap.memory.minimum_byte_size().unwrap_or(u64::MAX) <= memory_reservation403&& !heap.memory.memory_may_move(env.tunables())404&& memory_reservation >= offset_and_size405{406let adjusted_bound = memory_reservation.checked_sub(offset_and_size).unwrap();407let adjusted_bound_value = builder408.ins()409.iconst(env.pointer_type(), adjusted_bound as i64);410if pcc {411builder.func.dfg.facts[adjusted_bound_value] =412Some(Fact::constant(pointer_bit_width, adjusted_bound));413}414let oob = make_compare(415builder,416IntCC::UnsignedGreaterThan,417index,418Some(0),419adjusted_bound_value,420Some(0),421);422return Reachable(explicit_check_oob_condition_and_compute_addr(423env,424builder,425heap,426index,427offset,428access_size,429oob_behavior,430AddrPcc::static32(heap.pcc_memory_type, memory_reservation),431oob,432trap,433));434}435436// Special case for when `offset + access_size == 1`:437//438// index + 1 > bound439// ==> index >= bound440//441// Note that this special case is skipped for Pulley targets to assist with442// pattern-matching bounds checks into single instructions. Otherwise more443// patterns/instructions would have to be added to match this. In the end444// the goal is to emit one instruction anyway, so this optimization is445// largely only applicable for native platforms.446if offset_and_size == 1 && !env.is_pulley() {447let bound = get_dynamic_heap_bound(builder, env, heap);448let oob = make_compare(449builder,450IntCC::UnsignedGreaterThanOrEqual,451index,452Some(0),453bound,454Some(0),455);456return Reachable(explicit_check_oob_condition_and_compute_addr(457env,458builder,459heap,460index,461offset,462access_size,463oob_behavior,464AddrPcc::dynamic(heap.pcc_memory_type, bound_gv),465oob,466trap,467));468}469470// Special case for when we know that there are enough guard471// pages to cover the offset and access size.472//473// The precise should-we-trap condition is474//475// index + offset + access_size > bound476//477// However, if we instead check only the partial condition478//479// index > bound480//481// then the most out of bounds that the access can be, while that482// partial check still succeeds, is `offset + access_size`.483//484// However, when we have a guard region that is at least as large as485// `offset + access_size`, we can rely on the virtual memory486// subsystem handling these out-of-bounds errors at487// runtime. Therefore, the partial `index > bound` check is488// sufficient for this heap configuration.489//490// Additionally, this has the advantage that a series of Wasm loads491// that use the same dynamic index operand but different static492// offset immediates -- which is a common code pattern when accessing493// multiple fields in the same struct that is in linear memory --494// will all emit the same `index > bound` check, which we can GVN.495if can_use_virtual_memory && offset_and_size <= memory_guard_size {496let bound = get_dynamic_heap_bound(builder, env, heap);497let oob = make_compare(498builder,499IntCC::UnsignedGreaterThan,500index,501Some(0),502bound,503Some(0),504);505return Reachable(explicit_check_oob_condition_and_compute_addr(506env,507builder,508heap,509index,510offset,511access_size,512oob_behavior,513AddrPcc::dynamic(heap.pcc_memory_type, bound_gv),514oob,515trap,516));517}518519// Special case for when `offset + access_size <= min_size`.520//521// We know that `bound >= min_size`, so we can do the following522// comparison, without fear of the right-hand side wrapping around:523//524// index + offset + access_size > bound525// ==> index > bound - (offset + access_size)526if offset_and_size <= heap.memory.minimum_byte_size().unwrap_or(u64::MAX) {527let bound = get_dynamic_heap_bound(builder, env, heap);528let adjustment = offset_and_size as i64;529let adjustment_value = builder.ins().iconst(env.pointer_type(), adjustment);530if pcc {531builder.func.dfg.facts[adjustment_value] =532Some(Fact::constant(pointer_bit_width, offset_and_size));533}534let adjusted_bound = builder.ins().isub(bound, adjustment_value);535if pcc {536builder.func.dfg.facts[adjusted_bound] = Some(Fact::global_value_offset(537pointer_bit_width,538bound_gv,539-adjustment,540));541}542let oob = make_compare(543builder,544IntCC::UnsignedGreaterThan,545index,546Some(0),547adjusted_bound,548Some(adjustment),549);550return Reachable(explicit_check_oob_condition_and_compute_addr(551env,552builder,553heap,554index,555offset,556access_size,557oob_behavior,558AddrPcc::dynamic(heap.pcc_memory_type, bound_gv),559oob,560trap,561));562}563564// General case for dynamic bounds checks:565//566// index + offset + access_size > bound567//568// And we have to handle the overflow case in the left-hand side.569let access_size_val = builder570.ins()571// Explicit cast from u64 to i64: we just want the raw572// bits, and iconst takes an `Imm64`.573.iconst(env.pointer_type(), offset_and_size as i64);574if pcc {575builder.func.dfg.facts[access_size_val] =576Some(Fact::constant(pointer_bit_width, offset_and_size));577}578let adjusted_index = env.uadd_overflow_trap(builder, index, access_size_val, trap);579if pcc {580builder.func.dfg.facts[adjusted_index] = Some(Fact::value_offset(581pointer_bit_width,582index,583i64::try_from(offset_and_size).unwrap(),584));585}586let bound = get_dynamic_heap_bound(builder, env, heap);587let oob = make_compare(588builder,589IntCC::UnsignedGreaterThan,590adjusted_index,591i64::try_from(offset_and_size).ok(),592bound,593Some(0),594);595Reachable(explicit_check_oob_condition_and_compute_addr(596env,597builder,598heap,599index,600offset,601access_size,602oob_behavior,603AddrPcc::dynamic(heap.pcc_memory_type, bound_gv),604oob,605trap,606))607}608609/// Get the bound of a dynamic heap as an `ir::Value`.610fn get_dynamic_heap_bound(611builder: &mut FunctionBuilder,612env: &mut FuncEnvironment<'_>,613heap: &HeapData,614) -> ir::Value {615let enable_pcc = heap.pcc_memory_type.is_some();616617let (value, gv) = match heap.memory.static_heap_size() {618// The heap has a constant size, no need to actually load the619// bound. TODO: this is currently disabled for PCC because we620// can't easily prove that the GV load indeed results in a621// constant (that information is lost in the CLIF). We'll want622// to create an `iconst` GV expression kind to reify this fact623// in the GV, then re-enable this opt. (Or, alternately,624// compile such memories with a static-bound memtype and625// facts.)626Some(max_size) if !enable_pcc => (627builder.ins().iconst(env.pointer_type(), max_size as i64),628heap.bound,629),630631// Load the heap bound from its global variable.632_ => (633builder.ins().global_value(env.pointer_type(), heap.bound),634heap.bound,635),636};637638// If proof-carrying code is enabled, apply a fact to the range to639// tie it to the GV.640if enable_pcc {641builder.func.dfg.facts[value] = Some(Fact::global_value(642u16::try_from(env.pointer_type().bits()).unwrap(),643gv,644));645}646647value648}649650fn cast_index_to_pointer_ty(651index: ir::Value,652index_ty: ir::Type,653pointer_ty: ir::Type,654pcc: bool,655pos: &mut FuncCursor,656trap: ir::TrapCode,657) -> ir::Value {658if index_ty == pointer_ty {659return index;660}661662// If the index size is larger than the pointer, that means that this is a663// 32-bit host platform with a 64-bit wasm linear memory. If the index is664// larger than 2**32 then that's guaranteed to be out-of-bounds, otherwise we665// `ireduce` the index.666//667// Also note that at this time this branch doesn't support pcc nor the668// value-label-ranges of the below path.669//670// Finally, note that the returned `low_bits` here are still subject to an671// explicit bounds check in wasm so in terms of Spectre speculation on672// either side of the `trapnz` should be ok.673if index_ty.bits() > pointer_ty.bits() {674assert_eq!(index_ty, ir::types::I64);675assert_eq!(pointer_ty, ir::types::I32);676let low_bits = pos.ins().ireduce(pointer_ty, index);677let c32 = pos.ins().iconst(pointer_ty, 32);678let high_bits = pos.ins().ushr(index, c32);679let high_bits = pos.ins().ireduce(pointer_ty, high_bits);680pos.ins().trapnz(high_bits, trap);681return low_bits;682}683684// Convert `index` to `addr_ty`.685let extended_index = pos.ins().uextend(pointer_ty, index);686687// Add a range fact on the extended value.688if pcc {689pos.func.dfg.facts[extended_index] = Some(Fact::max_range_for_width_extended(690u16::try_from(index_ty.bits()).unwrap(),691u16::try_from(pointer_ty.bits()).unwrap(),692));693}694695// Add debug value-label alias so that debuginfo can name the extended696// value as the address697let loc = pos.srcloc();698let loc = RelSourceLoc::from_base_offset(pos.func.params.base_srcloc(), loc);699pos.func700.stencil701.dfg702.add_value_label_alias(extended_index, loc, index);703704extended_index705}706707/// Which facts do we want to emit for proof-carrying code, if any, on708/// address computations?709#[derive(Clone, Copy, Debug)]710enum AddrPcc {711/// A 32-bit static memory with the given size.712Static32(ir::MemoryType, u64),713/// Dynamic bounds-check, with actual memory size (the `GlobalValue`)714/// expressed symbolically.715Dynamic(ir::MemoryType, ir::GlobalValue),716}717impl AddrPcc {718fn static32(memory_type: Option<ir::MemoryType>, size: u64) -> Option<Self> {719memory_type.map(|ty| AddrPcc::Static32(ty, size))720}721fn dynamic(memory_type: Option<ir::MemoryType>, bound: ir::GlobalValue) -> Option<Self> {722memory_type.map(|ty| AddrPcc::Dynamic(ty, bound))723}724}725726/// What to do on out-of-bounds for the727/// `explicit_check_oob_condition_and_compute_addr` function below.728enum OobBehavior {729/// An explicit `trapnz` instruction should be used.730ExplicitTrap,731/// A load from NULL should be issued if the address is out-of-bounds.732ConditionallyLoadFromZero {733/// Whether or not to use `select_spectre_guard` to choose the address734/// to load from. If `false` then a normal `select` is used.735select_spectre_guard: bool,736},737}738739/// Emit explicit checks on the given out-of-bounds condition for the Wasm740/// address and return the native address.741///742/// This function deduplicates explicit bounds checks and Spectre mitigations743/// that inherently also implement bounds checking.744fn explicit_check_oob_condition_and_compute_addr(745env: &mut FuncEnvironment<'_>,746builder: &mut FunctionBuilder,747heap: &HeapData,748index: ir::Value,749offset: u32,750access_size: u8,751oob_behavior: OobBehavior,752// Whether we're emitting PCC facts.753pcc: Option<AddrPcc>,754// The `i8` boolean value that is non-zero when the heap access is out of755// bounds (and therefore we should trap) and is zero when the heap access is756// in bounds (and therefore we can proceed).757oob_condition: ir::Value,758trap: ir::TrapCode,759) -> ir::Value {760if let OobBehavior::ExplicitTrap = oob_behavior {761env.trapnz(builder, oob_condition, trap);762}763let addr_ty = env.pointer_type();764765let mut addr = compute_addr(&mut builder.cursor(), heap, addr_ty, index, offset, pcc);766767if let OobBehavior::ConditionallyLoadFromZero {768select_spectre_guard,769} = oob_behavior770{771// These mitigations rely on trapping when loading from NULL so772// CLIF memory instruction traps must be allowed for this to be773// generated.774assert!(env.load_from_zero_allowed());775let null = builder.ins().iconst(addr_ty, 0);776addr = if select_spectre_guard {777builder778.ins()779.select_spectre_guard(oob_condition, null, addr)780} else {781builder.ins().select(oob_condition, null, addr)782};783784match pcc {785None => {}786Some(AddrPcc::Static32(ty, size)) => {787builder.func.dfg.facts[null] =788Some(Fact::constant(u16::try_from(addr_ty.bits()).unwrap(), 0));789builder.func.dfg.facts[addr] = Some(Fact::Mem {790ty,791min_offset: 0,792max_offset: size.checked_sub(u64::from(access_size)).unwrap(),793nullable: true,794});795}796Some(AddrPcc::Dynamic(ty, gv)) => {797builder.func.dfg.facts[null] =798Some(Fact::constant(u16::try_from(addr_ty.bits()).unwrap(), 0));799builder.func.dfg.facts[addr] = Some(Fact::DynamicMem {800ty,801min: Expr::constant(0),802max: Expr::offset(803&Expr::global_value(gv),804i64::try_from(env.tunables().memory_guard_size)805.unwrap()806.checked_sub(i64::from(access_size))807.unwrap(),808)809.unwrap(),810nullable: true,811});812}813}814}815816addr817}818819/// Emit code for the native address computation of a Wasm address,820/// without any bounds checks or overflow checks.821///822/// It is the caller's responsibility to ensure that any necessary bounds and823/// overflow checks are emitted, and that the resulting address is never used824/// unless they succeed.825fn compute_addr(826pos: &mut FuncCursor,827heap: &HeapData,828addr_ty: ir::Type,829index: ir::Value,830offset: u32,831pcc: Option<AddrPcc>,832) -> ir::Value {833debug_assert_eq!(pos.func.dfg.value_type(index), addr_ty);834835let heap_base = pos.ins().global_value(addr_ty, heap.base);836837match pcc {838None => {}839Some(AddrPcc::Static32(ty, _size)) => {840pos.func.dfg.facts[heap_base] = Some(Fact::Mem {841ty,842min_offset: 0,843max_offset: 0,844nullable: false,845});846}847Some(AddrPcc::Dynamic(ty, _limit)) => {848pos.func.dfg.facts[heap_base] = Some(Fact::dynamic_base_ptr(ty));849}850}851852let base_and_index = pos.ins().iadd(heap_base, index);853854match pcc {855None => {}856Some(AddrPcc::Static32(ty, _) | AddrPcc::Dynamic(ty, _)) => {857if let Some(idx) = pos.func.dfg.facts[index]858.as_ref()859.and_then(|f| f.as_symbol())860.cloned()861{862pos.func.dfg.facts[base_and_index] = Some(Fact::DynamicMem {863ty,864min: idx.clone(),865max: idx,866nullable: false,867});868} else {869pos.func.dfg.facts[base_and_index] = Some(Fact::Mem {870ty,871min_offset: 0,872max_offset: u64::from(u32::MAX),873nullable: false,874});875}876}877}878879if offset == 0 {880base_and_index881} else {882// NB: The addition of the offset immediate must happen *before* the883// `select_spectre_guard`, if any. If it happens after, then we884// potentially are letting speculative execution read the whole first885// 4GiB of memory.886let offset_val = pos.ins().iconst(addr_ty, i64::from(offset));887888if pcc.is_some() {889pos.func.dfg.facts[offset_val] = Some(Fact::constant(890u16::try_from(addr_ty.bits()).unwrap(),891u64::from(offset),892));893}894895let result = pos.ins().iadd(base_and_index, offset_val);896897match pcc {898None => {}899Some(AddrPcc::Static32(ty, _) | AddrPcc::Dynamic(ty, _)) => {900if let Some(idx) = pos.func.dfg.facts[index]901.as_ref()902.and_then(|f| f.as_symbol())903{904pos.func.dfg.facts[result] = Some(Fact::DynamicMem {905ty,906min: idx.clone(),907// Safety: adding an offset to an expression with908// zero offset -- add cannot wrap, so `unwrap()`909// cannot fail.910max: Expr::offset(idx, i64::from(offset)).unwrap(),911nullable: false,912});913} else {914pos.func.dfg.facts[result] = Some(Fact::Mem {915ty,916min_offset: u64::from(offset),917// Safety: can't overflow -- two u32s summed in a918// 64-bit add. TODO: when memory64 is supported here,919// `u32::MAX` is no longer true, and we'll need to920// handle overflow here.921max_offset: u64::from(u32::MAX) + u64::from(offset),922nullable: false,923});924}925}926}927result928}929}930931#[inline]932fn offset_plus_size(offset: u32, size: u8) -> u64 {933// Cannot overflow because we are widening to `u64`.934offset as u64 + size as u64935}936937/// Returns whether `index` is statically in-bounds with respect to this938/// `heap`'s configuration.939///940/// This is `true` when `index` is a constant and when the offset/size are added941/// in it's all still less than the minimum byte size of the heap.942///943/// The `offset_and_size` here are the static offset that was listed on the wasm944/// instruction plus the size of the access being made.945fn statically_in_bounds(946func: &ir::Function,947heap: &HeapData,948index: ir::Value,949offset_and_size: u64,950) -> bool {951func.dfg952.value_def(index)953.inst()954.and_then(|i| {955let imm = match func.dfg.insts[i] {956ir::InstructionData::UnaryImm {957opcode: ir::Opcode::Iconst,958imm,959} => imm,960_ => return None,961};962let ty = func.dfg.value_type(index);963let index = imm.zero_extend_from_width(ty.bits()).bits().cast_unsigned();964let final_addr = index.checked_add(offset_and_size)?;965Some(final_addr <= heap.memory.minimum_byte_size().unwrap_or(u64::MAX))966})967.unwrap_or(false)968}969970971