1 //! Generate various kinds of Wasm memory. 2 3 use anyhow::Result; 4 use arbitrary::{Arbitrary, Unstructured}; 5 use std::ops::Range; 6 use wasmtime::{LinearMemory, MemoryCreator, MemoryType}; 7 8 /// A description of a memory config, image, etc... that can be used to test 9 /// memory accesses. 10 #[derive(Debug)] 11 pub struct MemoryAccesses { 12 /// The configuration to use with this test case. 13 pub config: crate::generators::Config, 14 /// The heap image to use with this test case. 15 pub image: HeapImage, 16 /// The offset immediate to encode in the `load{8,16,32,64}` functions' 17 /// various load instructions. 18 pub offset: u32, 19 /// The amount (in pages) to grow the memory. 20 pub growth: u32, 21 } 22 23 impl<'a> Arbitrary<'a> for MemoryAccesses { 24 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> { 25 Ok(MemoryAccesses { 26 config: u.arbitrary()?, 27 image: u.arbitrary()?, 28 offset: u.arbitrary()?, 29 // Don't grow too much, since oss-fuzz/asan get upset if we try, 30 // even if we allow it to fail. 31 growth: u.int_in_range(0..=10)?, 32 }) 33 } 34 } 35 36 /// A memory heap image. 37 pub struct HeapImage { 38 /// The minimum size (in pages) of this memory. 39 pub minimum: u32, 40 /// The maximum size (in pages) of this memory. 41 pub maximum: Option<u32>, 42 /// Whether this memory should be indexed with `i64` (rather than `i32`). 43 pub memory64: bool, 44 /// Data segments for this memory. 45 pub segments: Vec<(u32, Vec<u8>)>, 46 } 47 48 impl std::fmt::Debug for HeapImage { 49 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 50 struct Segments<'a>(&'a [(u32, Vec<u8>)]); 51 impl std::fmt::Debug for Segments<'_> { 52 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 53 write!(f, "[..; {}]", self.0.len()) 54 } 55 } 56 57 f.debug_struct("HeapImage") 58 .field("minimum", &self.minimum) 59 .field("maximum", &self.maximum) 60 .field("memory64", &self.memory64) 61 .field("segments", &Segments(&self.segments)) 62 .finish() 63 } 64 } 65 66 impl<'a> Arbitrary<'a> for HeapImage { 67 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> { 68 let minimum = u.int_in_range(0..=4)?; 69 let maximum = if u.arbitrary()? { 70 Some(u.int_in_range(minimum..=10)?) 71 } else { 72 None 73 }; 74 let memory64 = u.arbitrary()?; 75 let mut segments = vec![]; 76 if minimum > 0 { 77 for _ in 0..u.int_in_range(0..=4)? { 78 const WASM_PAGE_SIZE: u32 = 65536; 79 let last_addressable = WASM_PAGE_SIZE * minimum - 1; 80 let offset = u.int_in_range(0..=last_addressable)?; 81 let max_len = 82 std::cmp::min(u.len(), usize::try_from(last_addressable - offset).unwrap()); 83 let len = u.int_in_range(0..=max_len)?; 84 let data = u.bytes(len)?.to_vec(); 85 segments.push((offset, data)); 86 } 87 } 88 Ok(HeapImage { 89 minimum, 90 maximum, 91 memory64, 92 segments, 93 }) 94 } 95 } 96 97 /// Configuration for linear memories in Wasmtime. 98 #[derive(Arbitrary, Clone, Debug, Eq, Hash, PartialEq)] 99 pub enum MemoryConfig { 100 /// Configuration for linear memories which correspond to normal 101 /// configuration settings in `wasmtime` itself. This will tweak various 102 /// parameters about static/dynamic memories. 103 Normal(NormalMemoryConfig), 104 105 /// Configuration to force use of a linear memory that's unaligned at its 106 /// base address to force all wasm addresses to be unaligned at the hardware 107 /// level, even if the wasm itself correctly aligns everything internally. 108 CustomUnaligned, 109 } 110 111 /// Represents a normal memory configuration for Wasmtime with the given 112 /// static and dynamic memory sizes. 113 #[derive(Clone, Debug, Eq, Hash, PartialEq)] 114 #[allow(missing_docs)] 115 pub struct NormalMemoryConfig { 116 pub static_memory_maximum_size: Option<u64>, 117 pub static_memory_guard_size: Option<u64>, 118 pub dynamic_memory_guard_size: Option<u64>, 119 pub dynamic_memory_reserved_for_growth: Option<u64>, 120 pub guard_before_linear_memory: bool, 121 pub cranelift_enable_heap_access_spectre_mitigations: Option<bool>, 122 pub memory_init_cow: bool, 123 } 124 125 impl<'a> Arbitrary<'a> for NormalMemoryConfig { 126 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> { 127 // This attempts to limit memory and guard sizes to 32-bit ranges so 128 // we don't exhaust a 64-bit address space easily. 129 let mut ret = Self { 130 static_memory_maximum_size: <Option<u32> as Arbitrary>::arbitrary(u)?.map(Into::into), 131 static_memory_guard_size: <Option<u32> as Arbitrary>::arbitrary(u)?.map(Into::into), 132 dynamic_memory_guard_size: <Option<u32> as Arbitrary>::arbitrary(u)?.map(Into::into), 133 dynamic_memory_reserved_for_growth: <Option<u32> as Arbitrary>::arbitrary(u)? 134 .map(Into::into), 135 guard_before_linear_memory: u.arbitrary()?, 136 cranelift_enable_heap_access_spectre_mitigations: u.arbitrary()?, 137 memory_init_cow: u.arbitrary()?, 138 }; 139 140 if let Some(dynamic) = ret.dynamic_memory_guard_size { 141 let statik = ret.static_memory_guard_size.unwrap_or(2 << 30); 142 ret.static_memory_guard_size = Some(statik.max(dynamic)); 143 } 144 145 Ok(ret) 146 } 147 } 148 149 impl NormalMemoryConfig { 150 /// Apply this memory configuration to the given `wasmtime::Config`. 151 pub fn apply_to(&self, config: &mut wasmtime::Config) { 152 config 153 .static_memory_maximum_size(self.static_memory_maximum_size.unwrap_or(0)) 154 .static_memory_guard_size(self.static_memory_guard_size.unwrap_or(0)) 155 .dynamic_memory_guard_size(self.dynamic_memory_guard_size.unwrap_or(0)) 156 .dynamic_memory_reserved_for_growth( 157 self.dynamic_memory_reserved_for_growth.unwrap_or(0), 158 ) 159 .guard_before_linear_memory(self.guard_before_linear_memory) 160 .memory_init_cow(self.memory_init_cow); 161 162 if let Some(enable) = self.cranelift_enable_heap_access_spectre_mitigations { 163 unsafe { 164 config.cranelift_flag_set( 165 "enable_heap_access_spectre_mitigation", 166 &enable.to_string(), 167 ); 168 } 169 } 170 } 171 } 172 173 /// A custom "linear memory allocator" for wasm which only works with the 174 /// "dynamic" mode of configuration where wasm always does explicit bounds 175 /// checks. 176 /// 177 /// This memory attempts to always use unaligned host addresses for the base 178 /// address of linear memory with wasm. This means that all jit loads/stores 179 /// should be unaligned, which is a "big hammer way" of testing that all our JIT 180 /// code works with unaligned addresses since alignment is not required for 181 /// correctness in wasm itself. 182 pub struct UnalignedMemory { 183 /// This memory is always one byte larger than the actual size of linear 184 /// memory. 185 src: Vec<u8>, 186 maximum: Option<usize>, 187 } 188 189 unsafe impl LinearMemory for UnalignedMemory { 190 fn byte_size(&self) -> usize { 191 // Chop off the extra byte reserved for the true byte size of this 192 // linear memory. 193 self.src.len() - 1 194 } 195 196 fn maximum_byte_size(&self) -> Option<usize> { 197 self.maximum 198 } 199 200 fn grow_to(&mut self, new_size: usize) -> Result<()> { 201 // Make sure to allocate an extra byte for our "unalignment" 202 self.src.resize(new_size + 1, 0); 203 Ok(()) 204 } 205 206 fn as_ptr(&self) -> *mut u8 { 207 // Return our allocated memory, offset by one, so that the base address 208 // of memory is always unaligned. 209 self.src[1..].as_ptr() as *mut _ 210 } 211 212 fn wasm_accessible(&self) -> Range<usize> { 213 let base = self.as_ptr() as usize; 214 let len = self.byte_size(); 215 base..base + len 216 } 217 } 218 219 /// A mechanism to generate [`UnalignedMemory`] at runtime. 220 pub struct UnalignedMemoryCreator; 221 222 unsafe impl MemoryCreator for UnalignedMemoryCreator { 223 fn new_memory( 224 &self, 225 _ty: MemoryType, 226 minimum: usize, 227 maximum: Option<usize>, 228 reserved_size_in_bytes: Option<usize>, 229 guard_size_in_bytes: usize, 230 ) -> Result<Box<dyn LinearMemory>, String> { 231 assert_eq!(guard_size_in_bytes, 0); 232 assert!(reserved_size_in_bytes.is_none() || reserved_size_in_bytes == Some(0)); 233 Ok(Box::new(UnalignedMemory { 234 src: vec![0; minimum + 1], 235 maximum, 236 })) 237 } 238 } 239