wasmtime_cranelift/translate/code_translator/bounds_checks.rs
1//! Implementation of Wasm to CLIF memory access translation.
2//!
3//! Given
4//!
5//! * a dynamic Wasm memory index operand,
6//! * a static offset immediate, and
7//! * a static access size,
8//!
9//! bounds check the memory access and translate it into a native memory access.
10//!
11//! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
12//! !!! !!!
13//! !!! THIS CODE IS VERY SUBTLE, HAS MANY SPECIAL CASES, AND IS ALSO !!!
14//! !!! ABSOLUTELY CRITICAL FOR MAINTAINING THE SAFETY OF THE WASM HEAP !!!
15//! !!! SANDBOX. !!!
16//! !!! !!!
17//! !!! A good rule of thumb is to get two reviews on any substantive !!!
18//! !!! changes in here. !!!
19//! !!! !!!
20//! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
21
22use super::Reachability;
23use crate::func_environ::FuncEnvironment;
24use crate::translate::{HeapData, TargetEnvironment};
25use cranelift_codegen::{
26 cursor::{Cursor, FuncCursor},
27 ir::{self, condcodes::IntCC, InstBuilder, RelSourceLoc},
28 ir::{Expr, Fact},
29};
30use cranelift_frontend::FunctionBuilder;
31use wasmtime_environ::{Unsigned, WasmResult};
32use Reachability::*;
33
34/// Helper used to emit bounds checks (as necessary) and compute the native
35/// address of a heap access.
36///
37/// Returns the `ir::Value` holding the native address of the heap access, or
38/// `None` if the heap access will unconditionally trap.
39pub fn bounds_check_and_compute_addr(
40 builder: &mut FunctionBuilder,
41 env: &mut FuncEnvironment<'_>,
42 heap: &HeapData,
43 // Dynamic operand indexing into the heap.
44 index: ir::Value,
45 // Static immediate added to the index.
46 offset: u32,
47 // Static size of the heap access.
48 access_size: u8,
49) -> WasmResult<Reachability<ir::Value>> {
50 let pointer_bit_width = u16::try_from(env.pointer_type().bits()).unwrap();
51 let bound_gv = heap.bound;
52 let orig_index = index;
53 let offset_and_size = offset_plus_size(offset, access_size);
54 let clif_memory_traps_enabled = env.clif_memory_traps_enabled();
55 let spectre_mitigations_enabled =
56 env.heap_access_spectre_mitigation() && clif_memory_traps_enabled;
57 let pcc = env.proof_carrying_code();
58
59 let host_page_size_log2 = env.target_config().page_size_align_log2;
60 let can_use_virtual_memory = heap
61 .memory
62 .can_use_virtual_memory(env.tunables(), host_page_size_log2)
63 && clif_memory_traps_enabled;
64 let can_elide_bounds_check = heap
65 .memory
66 .can_elide_bounds_check(env.tunables(), host_page_size_log2)
67 && clif_memory_traps_enabled;
68 let memory_guard_size = env.tunables().memory_guard_size;
69 let memory_reservation = env.tunables().memory_reservation;
70
71 let statically_in_bounds = statically_in_bounds(&builder.func, heap, index, offset_and_size);
72
73 let index = cast_index_to_pointer_ty(
74 index,
75 heap.index_type(),
76 env.pointer_type(),
77 heap.pcc_memory_type.is_some(),
78 &mut builder.cursor(),
79 );
80
81 let oob_behavior = if spectre_mitigations_enabled {
82 OobBehavior::ConditionallyLoadFromZero {
83 select_spectre_guard: true,
84 }
85 } else if env.load_from_zero_allowed() {
86 OobBehavior::ConditionallyLoadFromZero {
87 select_spectre_guard: false,
88 }
89 } else {
90 OobBehavior::ExplicitTrap
91 };
92
93 let make_compare = |builder: &mut FunctionBuilder,
94 compare_kind: IntCC,
95 lhs: ir::Value,
96 lhs_off: Option<i64>,
97 rhs: ir::Value,
98 rhs_off: Option<i64>| {
99 let result = builder.ins().icmp(compare_kind, lhs, rhs);
100 if pcc {
101 // Name the original value as a def of the SSA value;
102 // if the value was extended, name that as well with a
103 // dynamic range, overwriting the basic full-range
104 // fact that we previously put on the uextend.
105 builder.func.dfg.facts[orig_index] = Some(Fact::Def { value: orig_index });
106 if index != orig_index {
107 builder.func.dfg.facts[index] = Some(Fact::value(pointer_bit_width, orig_index));
108 }
109
110 // Create a fact on the LHS that is a "trivial symbolic
111 // fact": v1 has range v1+LHS_off..=v1+LHS_off
112 builder.func.dfg.facts[lhs] = Some(Fact::value_offset(
113 pointer_bit_width,
114 orig_index,
115 lhs_off.unwrap(),
116 ));
117 // If the RHS is a symbolic value (v1 or gv1), we can
118 // emit a Compare fact.
119 if let Some(rhs) = builder.func.dfg.facts[rhs]
120 .as_ref()
121 .and_then(|f| f.as_symbol())
122 {
123 builder.func.dfg.facts[result] = Some(Fact::Compare {
124 kind: compare_kind,
125 lhs: Expr::offset(&Expr::value(orig_index), lhs_off.unwrap()).unwrap(),
126 rhs: Expr::offset(rhs, rhs_off.unwrap()).unwrap(),
127 });
128 }
129 // Likewise, if the RHS is a constant, we can emit a
130 // Compare fact.
131 if let Some(k) = builder.func.dfg.facts[rhs]
132 .as_ref()
133 .and_then(|f| f.as_const(pointer_bit_width))
134 {
135 builder.func.dfg.facts[result] = Some(Fact::Compare {
136 kind: compare_kind,
137 lhs: Expr::offset(&Expr::value(orig_index), lhs_off.unwrap()).unwrap(),
138 rhs: Expr::constant((k as i64).checked_add(rhs_off.unwrap()).unwrap()),
139 });
140 }
141 }
142 result
143 };
144
145 // We need to emit code that will trap (or compute an address that will trap
146 // when accessed) if
147 //
148 // index + offset + access_size > bound
149 //
150 // or if the `index + offset + access_size` addition overflows.
151 //
152 // Note that we ultimately want a 64-bit integer (we only target 64-bit
153 // architectures at the moment) and that `offset` is a `u32` and
154 // `access_size` is a `u8`. This means that we can add the latter together
155 // as `u64`s without fear of overflow, and we only have to be concerned with
156 // whether adding in `index` will overflow.
157 //
158 // Finally, the following if/else chains do have a little
159 // bit of duplicated code across them, but I think writing it this way is
160 // worth it for readability and seeing very clearly each of our cases for
161 // different bounds checks and optimizations of those bounds checks. It is
162 // intentionally written in a straightforward case-matching style that will
163 // hopefully make it easy to port to ISLE one day.
164 if offset_and_size > heap.memory.maximum_byte_size().unwrap_or(u64::MAX) {
165 // Special case: trap immediately if `offset + access_size >
166 // max_memory_size`, since we will end up being out-of-bounds regardless
167 // of the given `index`.
168 env.before_unconditionally_trapping_memory_access(builder)?;
169 env.trap(builder, ir::TrapCode::HEAP_OUT_OF_BOUNDS);
170 return Ok(Unreachable);
171 }
172
173 // Special case: if this is a 32-bit platform and the `offset_and_size`
174 // overflows the 32-bit address space then there's no hope of this ever
175 // being in-bounds. We can't represent `offset_and_size` in CLIF as the
176 // native pointer type anyway, so this is an unconditional trap.
177 if pointer_bit_width < 64 && offset_and_size >= (1 << pointer_bit_width) {
178 env.before_unconditionally_trapping_memory_access(builder)?;
179 env.trap(builder, ir::TrapCode::HEAP_OUT_OF_BOUNDS);
180 return Ok(Unreachable);
181 }
182
183 // Special case for when we can completely omit explicit
184 // bounds checks for 32-bit memories.
185 //
186 // First, let's rewrite our comparison to move all of the constants
187 // to one side:
188 //
189 // index + offset + access_size > bound
190 // ==> index > bound - (offset + access_size)
191 //
192 // We know the subtraction on the right-hand side won't wrap because
193 // we didn't hit the unconditional trap case above.
194 //
195 // Additionally, we add our guard pages (if any) to the right-hand
196 // side, since we can rely on the virtual memory subsystem at runtime
197 // to catch out-of-bound accesses within the range `bound .. bound +
198 // guard_size`. So now we are dealing with
199 //
200 // index > bound + guard_size - (offset + access_size)
201 //
202 // Note that `bound + guard_size` cannot overflow for
203 // correctly-configured heaps, as otherwise the heap wouldn't fit in
204 // a 64-bit memory space.
205 //
206 // The complement of our should-this-trap comparison expression is
207 // the should-this-not-trap comparison expression:
208 //
209 // index <= bound + guard_size - (offset + access_size)
210 //
211 // If we know the right-hand side is greater than or equal to
212 // `u32::MAX`, then
213 //
214 // index <= u32::MAX <= bound + guard_size - (offset + access_size)
215 //
216 // This expression is always true when the heap is indexed with
217 // 32-bit integers because `index` cannot be larger than
218 // `u32::MAX`. This means that `index` is always either in bounds or
219 // within the guard page region, neither of which require emitting an
220 // explicit bounds check.
221 if can_elide_bounds_check
222 && u64::from(u32::MAX) <= memory_reservation + memory_guard_size - offset_and_size
223 {
224 assert!(heap.index_type() == ir::types::I32);
225 assert!(
226 can_use_virtual_memory,
227 "static memories require the ability to use virtual memory"
228 );
229 return Ok(Reachable(compute_addr(
230 &mut builder.cursor(),
231 heap,
232 env.pointer_type(),
233 index,
234 offset,
235 AddrPcc::static32(heap.pcc_memory_type, memory_reservation + memory_guard_size),
236 )));
237 }
238
239 // Special case when the `index` is a constant and statically known to be
240 // in-bounds on this memory, no bounds checks necessary.
241 if statically_in_bounds {
242 return Ok(Reachable(compute_addr(
243 &mut builder.cursor(),
244 heap,
245 env.pointer_type(),
246 index,
247 offset,
248 AddrPcc::static32(heap.pcc_memory_type, memory_reservation + memory_guard_size),
249 )));
250 }
251
252 // Special case for when we can rely on virtual memory, the minimum
253 // byte size of this memory fits within the memory reservation, and
254 // memory isn't allowed to move. In this situation we know that
255 // memory will statically not grow beyond `memory_reservation` so we
256 // and we know that memory from 0 to that limit is guaranteed to be
257 // valid or trap. Here we effectively assume that the dynamic size
258 // of linear memory is its maximal value, `memory_reservation`, and
259 // we can avoid loading the actual length of memory.
260 //
261 // We have to explicitly test whether
262 //
263 // index > bound - (offset + access_size)
264 //
265 // and trap if so.
266 //
267 // Since we have to emit explicit bounds checks, we might as well be
268 // precise, not rely on the virtual memory subsystem at all, and not
269 // factor in the guard pages here.
270 if can_use_virtual_memory
271 && heap.memory.minimum_byte_size().unwrap_or(u64::MAX) <= memory_reservation
272 && !heap.memory.memory_may_move(env.tunables())
273 {
274 let adjusted_bound = memory_reservation.checked_sub(offset_and_size).unwrap();
275 let adjusted_bound_value = builder
276 .ins()
277 .iconst(env.pointer_type(), adjusted_bound as i64);
278 if pcc {
279 builder.func.dfg.facts[adjusted_bound_value] =
280 Some(Fact::constant(pointer_bit_width, adjusted_bound));
281 }
282 let oob = make_compare(
283 builder,
284 IntCC::UnsignedGreaterThan,
285 index,
286 Some(0),
287 adjusted_bound_value,
288 Some(0),
289 );
290 return Ok(Reachable(explicit_check_oob_condition_and_compute_addr(
291 env,
292 builder,
293 heap,
294 index,
295 offset,
296 access_size,
297 oob_behavior,
298 AddrPcc::static32(heap.pcc_memory_type, memory_reservation),
299 oob,
300 )));
301 }
302
303 // Special case for when `offset + access_size == 1`:
304 //
305 // index + 1 > bound
306 // ==> index >= bound
307 //
308 // Note that this special case is skipped for Pulley targets to assist with
309 // pattern-matching bounds checks into single instructions. Otherwise more
310 // patterns/instructions would have to be added to match this. In the end
311 // the goal is to emit one instruction anyway, so this optimization is
312 // largely only applicable for native platforms.
313 if offset_and_size == 1 && !env.is_pulley() {
314 let bound = get_dynamic_heap_bound(builder, env, heap);
315 let oob = make_compare(
316 builder,
317 IntCC::UnsignedGreaterThanOrEqual,
318 index,
319 Some(0),
320 bound,
321 Some(0),
322 );
323 return Ok(Reachable(explicit_check_oob_condition_and_compute_addr(
324 env,
325 builder,
326 heap,
327 index,
328 offset,
329 access_size,
330 oob_behavior,
331 AddrPcc::dynamic(heap.pcc_memory_type, bound_gv),
332 oob,
333 )));
334 }
335
336 // Special case for when we know that there are enough guard
337 // pages to cover the offset and access size.
338 //
339 // The precise should-we-trap condition is
340 //
341 // index + offset + access_size > bound
342 //
343 // However, if we instead check only the partial condition
344 //
345 // index > bound
346 //
347 // then the most out of bounds that the access can be, while that
348 // partial check still succeeds, is `offset + access_size`.
349 //
350 // However, when we have a guard region that is at least as large as
351 // `offset + access_size`, we can rely on the virtual memory
352 // subsystem handling these out-of-bounds errors at
353 // runtime. Therefore, the partial `index > bound` check is
354 // sufficient for this heap configuration.
355 //
356 // Additionally, this has the advantage that a series of Wasm loads
357 // that use the same dynamic index operand but different static
358 // offset immediates -- which is a common code pattern when accessing
359 // multiple fields in the same struct that is in linear memory --
360 // will all emit the same `index > bound` check, which we can GVN.
361 if can_use_virtual_memory && offset_and_size <= memory_guard_size {
362 let bound = get_dynamic_heap_bound(builder, env, heap);
363 let oob = make_compare(
364 builder,
365 IntCC::UnsignedGreaterThan,
366 index,
367 Some(0),
368 bound,
369 Some(0),
370 );
371 return Ok(Reachable(explicit_check_oob_condition_and_compute_addr(
372 env,
373 builder,
374 heap,
375 index,
376 offset,
377 access_size,
378 oob_behavior,
379 AddrPcc::dynamic(heap.pcc_memory_type, bound_gv),
380 oob,
381 )));
382 }
383
384 // Special case for when `offset + access_size <= min_size`.
385 //
386 // We know that `bound >= min_size`, so we can do the following
387 // comparison, without fear of the right-hand side wrapping around:
388 //
389 // index + offset + access_size > bound
390 // ==> index > bound - (offset + access_size)
391 if offset_and_size <= heap.memory.minimum_byte_size().unwrap_or(u64::MAX) {
392 let bound = get_dynamic_heap_bound(builder, env, heap);
393 let adjustment = offset_and_size as i64;
394 let adjustment_value = builder.ins().iconst(env.pointer_type(), adjustment);
395 if pcc {
396 builder.func.dfg.facts[adjustment_value] =
397 Some(Fact::constant(pointer_bit_width, offset_and_size));
398 }
399 let adjusted_bound = builder.ins().isub(bound, adjustment_value);
400 if pcc {
401 builder.func.dfg.facts[adjusted_bound] = Some(Fact::global_value_offset(
402 pointer_bit_width,
403 bound_gv,
404 -adjustment,
405 ));
406 }
407 let oob = make_compare(
408 builder,
409 IntCC::UnsignedGreaterThan,
410 index,
411 Some(0),
412 adjusted_bound,
413 Some(adjustment),
414 );
415 return Ok(Reachable(explicit_check_oob_condition_and_compute_addr(
416 env,
417 builder,
418 heap,
419 index,
420 offset,
421 access_size,
422 oob_behavior,
423 AddrPcc::dynamic(heap.pcc_memory_type, bound_gv),
424 oob,
425 )));
426 }
427
428 // General case for dynamic bounds checks:
429 //
430 // index + offset + access_size > bound
431 //
432 // And we have to handle the overflow case in the left-hand side.
433 let access_size_val = builder
434 .ins()
435 // Explicit cast from u64 to i64: we just want the raw
436 // bits, and iconst takes an `Imm64`.
437 .iconst(env.pointer_type(), offset_and_size as i64);
438 if pcc {
439 builder.func.dfg.facts[access_size_val] =
440 Some(Fact::constant(pointer_bit_width, offset_and_size));
441 }
442 let adjusted_index = env.uadd_overflow_trap(
443 builder,
444 index,
445 access_size_val,
446 ir::TrapCode::HEAP_OUT_OF_BOUNDS,
447 );
448 if pcc {
449 builder.func.dfg.facts[adjusted_index] = Some(Fact::value_offset(
450 pointer_bit_width,
451 index,
452 i64::try_from(offset_and_size).unwrap(),
453 ));
454 }
455 let bound = get_dynamic_heap_bound(builder, env, heap);
456 let oob = make_compare(
457 builder,
458 IntCC::UnsignedGreaterThan,
459 adjusted_index,
460 i64::try_from(offset_and_size).ok(),
461 bound,
462 Some(0),
463 );
464 Ok(Reachable(explicit_check_oob_condition_and_compute_addr(
465 env,
466 builder,
467 heap,
468 index,
469 offset,
470 access_size,
471 oob_behavior,
472 AddrPcc::dynamic(heap.pcc_memory_type, bound_gv),
473 oob,
474 )))
475}
476
477/// Get the bound of a dynamic heap as an `ir::Value`.
478fn get_dynamic_heap_bound(
479 builder: &mut FunctionBuilder,
480 env: &mut FuncEnvironment<'_>,
481 heap: &HeapData,
482) -> ir::Value {
483 let enable_pcc = heap.pcc_memory_type.is_some();
484
485 let (value, gv) = match heap.memory.static_heap_size() {
486 // The heap has a constant size, no need to actually load the
487 // bound. TODO: this is currently disabled for PCC because we
488 // can't easily prove that the GV load indeed results in a
489 // constant (that information is lost in the CLIF). We'll want
490 // to create an `iconst` GV expression kind to reify this fact
491 // in the GV, then re-enable this opt. (Or, alternately,
492 // compile such memories with a static-bound memtype and
493 // facts.)
494 Some(max_size) if !enable_pcc => (
495 builder.ins().iconst(env.pointer_type(), max_size as i64),
496 heap.bound,
497 ),
498
499 // Load the heap bound from its global variable.
500 _ => (
501 builder.ins().global_value(env.pointer_type(), heap.bound),
502 heap.bound,
503 ),
504 };
505
506 // If proof-carrying code is enabled, apply a fact to the range to
507 // tie it to the GV.
508 if enable_pcc {
509 builder.func.dfg.facts[value] = Some(Fact::global_value(
510 u16::try_from(env.pointer_type().bits()).unwrap(),
511 gv,
512 ));
513 }
514
515 value
516}
517
518fn cast_index_to_pointer_ty(
519 index: ir::Value,
520 index_ty: ir::Type,
521 pointer_ty: ir::Type,
522 pcc: bool,
523 pos: &mut FuncCursor,
524) -> ir::Value {
525 if index_ty == pointer_ty {
526 return index;
527 }
528
529 // If the index size is larger than the pointer, that means that this is a
530 // 32-bit host platform with a 64-bit wasm linear memory. If the index is
531 // larger than 2**32 then that's guaranteed to be out-of-bounds, otherwise we
532 // `ireduce` the index.
533 //
534 // Also note that at this time this branch doesn't support pcc nor the
535 // value-label-ranges of the below path.
536 //
537 // Finally, note that the returned `low_bits` here are still subject to an
538 // explicit bounds check in wasm so in terms of Spectre speculation on
539 // either side of the `trapnz` should be ok.
540 if index_ty.bits() > pointer_ty.bits() {
541 assert_eq!(index_ty, ir::types::I64);
542 assert_eq!(pointer_ty, ir::types::I32);
543 let low_bits = pos.ins().ireduce(pointer_ty, index);
544 let c32 = pos.ins().iconst(pointer_ty, 32);
545 let high_bits = pos.ins().ushr(index, c32);
546 let high_bits = pos.ins().ireduce(pointer_ty, high_bits);
547 pos.ins()
548 .trapnz(high_bits, ir::TrapCode::HEAP_OUT_OF_BOUNDS);
549 return low_bits;
550 }
551
552 // Convert `index` to `addr_ty`.
553 let extended_index = pos.ins().uextend(pointer_ty, index);
554
555 // Add a range fact on the extended value.
556 if pcc {
557 pos.func.dfg.facts[extended_index] = Some(Fact::max_range_for_width_extended(
558 u16::try_from(index_ty.bits()).unwrap(),
559 u16::try_from(pointer_ty.bits()).unwrap(),
560 ));
561 }
562
563 // Add debug value-label alias so that debuginfo can name the extended
564 // value as the address
565 let loc = pos.srcloc();
566 let loc = RelSourceLoc::from_base_offset(pos.func.params.base_srcloc(), loc);
567 pos.func
568 .stencil
569 .dfg
570 .add_value_label_alias(extended_index, loc, index);
571
572 extended_index
573}
574
575/// Which facts do we want to emit for proof-carrying code, if any, on
576/// address computations?
577#[derive(Clone, Copy, Debug)]
578enum AddrPcc {
579 /// A 32-bit static memory with the given size.
580 Static32(ir::MemoryType, u64),
581 /// Dynamic bounds-check, with actual memory size (the `GlobalValue`)
582 /// expressed symbolically.
583 Dynamic(ir::MemoryType, ir::GlobalValue),
584}
585impl AddrPcc {
586 fn static32(memory_type: Option<ir::MemoryType>, size: u64) -> Option<Self> {
587 memory_type.map(|ty| AddrPcc::Static32(ty, size))
588 }
589 fn dynamic(memory_type: Option<ir::MemoryType>, bound: ir::GlobalValue) -> Option<Self> {
590 memory_type.map(|ty| AddrPcc::Dynamic(ty, bound))
591 }
592}
593
594/// What to do on out-of-bounds for the
595/// `explicit_check_oob_condition_and_compute_addr` function below.
596enum OobBehavior {
597 /// An explicit `trapnz` instruction should be used.
598 ExplicitTrap,
599 /// A load from NULL should be issued if the address is out-of-bounds.
600 ConditionallyLoadFromZero {
601 /// Whether or not to use `select_spectre_guard` to choose the address
602 /// to load from. If `false` then a normal `select` is used.
603 select_spectre_guard: bool,
604 },
605}
606
607/// Emit explicit checks on the given out-of-bounds condition for the Wasm
608/// address and return the native address.
609///
610/// This function deduplicates explicit bounds checks and Spectre mitigations
611/// that inherently also implement bounds checking.
612fn explicit_check_oob_condition_and_compute_addr(
613 env: &mut FuncEnvironment<'_>,
614 builder: &mut FunctionBuilder,
615 heap: &HeapData,
616 index: ir::Value,
617 offset: u32,
618 access_size: u8,
619 oob_behavior: OobBehavior,
620 // Whether we're emitting PCC facts.
621 pcc: Option<AddrPcc>,
622 // The `i8` boolean value that is non-zero when the heap access is out of
623 // bounds (and therefore we should trap) and is zero when the heap access is
624 // in bounds (and therefore we can proceed).
625 oob_condition: ir::Value,
626) -> ir::Value {
627 if let OobBehavior::ExplicitTrap = oob_behavior {
628 env.trapnz(builder, oob_condition, ir::TrapCode::HEAP_OUT_OF_BOUNDS);
629 }
630 let addr_ty = env.pointer_type();
631
632 let mut addr = compute_addr(&mut builder.cursor(), heap, addr_ty, index, offset, pcc);
633
634 if let OobBehavior::ConditionallyLoadFromZero {
635 select_spectre_guard,
636 } = oob_behavior
637 {
638 // These mitigations rely on trapping when loading from NULL so
639 // CLIF memory instruction traps must be allowed for this to be
640 // generated.
641 assert!(env.load_from_zero_allowed());
642 let null = builder.ins().iconst(addr_ty, 0);
643 addr = if select_spectre_guard {
644 builder
645 .ins()
646 .select_spectre_guard(oob_condition, null, addr)
647 } else {
648 builder.ins().select(oob_condition, null, addr)
649 };
650
651 match pcc {
652 None => {}
653 Some(AddrPcc::Static32(ty, size)) => {
654 builder.func.dfg.facts[null] =
655 Some(Fact::constant(u16::try_from(addr_ty.bits()).unwrap(), 0));
656 builder.func.dfg.facts[addr] = Some(Fact::Mem {
657 ty,
658 min_offset: 0,
659 max_offset: size.checked_sub(u64::from(access_size)).unwrap(),
660 nullable: true,
661 });
662 }
663 Some(AddrPcc::Dynamic(ty, gv)) => {
664 builder.func.dfg.facts[null] =
665 Some(Fact::constant(u16::try_from(addr_ty.bits()).unwrap(), 0));
666 builder.func.dfg.facts[addr] = Some(Fact::DynamicMem {
667 ty,
668 min: Expr::constant(0),
669 max: Expr::offset(
670 &Expr::global_value(gv),
671 i64::try_from(env.tunables().memory_guard_size)
672 .unwrap()
673 .checked_sub(i64::from(access_size))
674 .unwrap(),
675 )
676 .unwrap(),
677 nullable: true,
678 });
679 }
680 }
681 }
682
683 addr
684}
685
686/// Emit code for the native address computation of a Wasm address,
687/// without any bounds checks or overflow checks.
688///
689/// It is the caller's responsibility to ensure that any necessary bounds and
690/// overflow checks are emitted, and that the resulting address is never used
691/// unless they succeed.
692fn compute_addr(
693 pos: &mut FuncCursor,
694 heap: &HeapData,
695 addr_ty: ir::Type,
696 index: ir::Value,
697 offset: u32,
698 pcc: Option<AddrPcc>,
699) -> ir::Value {
700 debug_assert_eq!(pos.func.dfg.value_type(index), addr_ty);
701
702 let heap_base = pos.ins().global_value(addr_ty, heap.base);
703
704 match pcc {
705 None => {}
706 Some(AddrPcc::Static32(ty, _size)) => {
707 pos.func.dfg.facts[heap_base] = Some(Fact::Mem {
708 ty,
709 min_offset: 0,
710 max_offset: 0,
711 nullable: false,
712 });
713 }
714 Some(AddrPcc::Dynamic(ty, _limit)) => {
715 pos.func.dfg.facts[heap_base] = Some(Fact::dynamic_base_ptr(ty));
716 }
717 }
718
719 let base_and_index = pos.ins().iadd(heap_base, index);
720
721 match pcc {
722 None => {}
723 Some(AddrPcc::Static32(ty, _) | AddrPcc::Dynamic(ty, _)) => {
724 if let Some(idx) = pos.func.dfg.facts[index]
725 .as_ref()
726 .and_then(|f| f.as_symbol())
727 .cloned()
728 {
729 pos.func.dfg.facts[base_and_index] = Some(Fact::DynamicMem {
730 ty,
731 min: idx.clone(),
732 max: idx,
733 nullable: false,
734 });
735 } else {
736 pos.func.dfg.facts[base_and_index] = Some(Fact::Mem {
737 ty,
738 min_offset: 0,
739 max_offset: u64::from(u32::MAX),
740 nullable: false,
741 });
742 }
743 }
744 }
745
746 if offset == 0 {
747 base_and_index
748 } else {
749 // NB: The addition of the offset immediate must happen *before* the
750 // `select_spectre_guard`, if any. If it happens after, then we
751 // potentially are letting speculative execution read the whole first
752 // 4GiB of memory.
753 let offset_val = pos.ins().iconst(addr_ty, i64::from(offset));
754
755 if pcc.is_some() {
756 pos.func.dfg.facts[offset_val] = Some(Fact::constant(
757 u16::try_from(addr_ty.bits()).unwrap(),
758 u64::from(offset),
759 ));
760 }
761
762 let result = pos.ins().iadd(base_and_index, offset_val);
763
764 match pcc {
765 None => {}
766 Some(AddrPcc::Static32(ty, _) | AddrPcc::Dynamic(ty, _)) => {
767 if let Some(idx) = pos.func.dfg.facts[index]
768 .as_ref()
769 .and_then(|f| f.as_symbol())
770 {
771 pos.func.dfg.facts[result] = Some(Fact::DynamicMem {
772 ty,
773 min: idx.clone(),
774 // Safety: adding an offset to an expression with
775 // zero offset -- add cannot wrap, so `unwrap()`
776 // cannot fail.
777 max: Expr::offset(idx, i64::from(offset)).unwrap(),
778 nullable: false,
779 });
780 } else {
781 pos.func.dfg.facts[result] = Some(Fact::Mem {
782 ty,
783 min_offset: u64::from(offset),
784 // Safety: can't overflow -- two u32s summed in a
785 // 64-bit add. TODO: when memory64 is supported here,
786 // `u32::MAX` is no longer true, and we'll need to
787 // handle overflow here.
788 max_offset: u64::from(u32::MAX) + u64::from(offset),
789 nullable: false,
790 });
791 }
792 }
793 }
794 result
795 }
796}
797
798#[inline]
799fn offset_plus_size(offset: u32, size: u8) -> u64 {
800 // Cannot overflow because we are widening to `u64`.
801 offset as u64 + size as u64
802}
803
804/// Returns whether `index` is statically in-bounds with respect to this
805/// `heap`'s configuration.
806///
807/// This is `true` when `index` is a constant and when the offset/size are added
808/// in it's all still less than the minimum byte size of the heap.
809///
810/// The `offset_and_size` here are the static offset that was listed on the wasm
811/// instruction plus the size of the access being made.
812fn statically_in_bounds(
813 func: &ir::Function,
814 heap: &HeapData,
815 index: ir::Value,
816 offset_and_size: u64,
817) -> bool {
818 func.dfg
819 .value_def(index)
820 .inst()
821 .and_then(|i| {
822 let imm = match func.dfg.insts[i] {
823 ir::InstructionData::UnaryImm {
824 opcode: ir::Opcode::Iconst,
825 imm,
826 } => imm,
827 _ => return None,
828 };
829 let ty = func.dfg.value_type(index);
830 let index = imm.zero_extend_from_width(ty.bits()).bits().unsigned();
831 let final_addr = index.checked_add(offset_and_size)?;
832 Some(final_addr <= heap.memory.minimum_byte_size().unwrap_or(u64::MAX))
833 })
834 .unwrap_or(false)
835}