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 let image = HeapImage::arbitrary(u)?; 26 27 // Don't grow too much, since oss-fuzz/asan get upset if we try, 28 // even if we allow it to fail. 29 let one_mib = 1 << 20; // 1 MiB 30 let max_growth = one_mib / (1 << image.page_size_log2.unwrap_or(16)); 31 let mut growth: u32 = u.int_in_range(0..=max_growth)?; 32 33 // Occasionally, round to a power of two, since these tend to be 34 // interesting numbers that overlap with the host page size and things 35 // like that. 36 if growth > 0 && u.ratio(1, 20)? { 37 growth = (growth - 1).next_power_of_two(); 38 } 39 40 Ok(MemoryAccesses { 41 config: u.arbitrary()?, 42 image, 43 offset: u.arbitrary()?, 44 growth, 45 }) 46 } 47 } 48 49 /// A memory heap image. 50 pub struct HeapImage { 51 /// The minimum size (in pages) of this memory. 52 pub minimum: u32, 53 /// The maximum size (in pages) of this memory. 54 pub maximum: Option<u32>, 55 /// Whether this memory should be indexed with `i64` (rather than `i32`). 56 pub memory64: bool, 57 /// The log2 of the page size for this memory. 58 pub page_size_log2: Option<u32>, 59 /// Data segments for this memory. 60 pub segments: Vec<(u32, Vec<u8>)>, 61 } 62 63 impl std::fmt::Debug for HeapImage { 64 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 65 struct Segments<'a>(&'a [(u32, Vec<u8>)]); 66 impl std::fmt::Debug for Segments<'_> { 67 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 68 write!(f, "[..; {}]", self.0.len()) 69 } 70 } 71 72 f.debug_struct("HeapImage") 73 .field("minimum", &self.minimum) 74 .field("maximum", &self.maximum) 75 .field("memory64", &self.memory64) 76 .field("page_size_log2", &self.page_size_log2) 77 .field("segments", &Segments(&self.segments)) 78 .finish() 79 } 80 } 81 82 impl<'a> Arbitrary<'a> for HeapImage { 83 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> { 84 let minimum = u.int_in_range(0..=4)?; 85 let maximum = if u.arbitrary()? { 86 Some(u.int_in_range(minimum..=10)?) 87 } else { 88 None 89 }; 90 let memory64 = u.arbitrary()?; 91 let page_size_log2 = match u.int_in_range(0..=2)? { 92 0 => None, 93 1 => Some(0), 94 2 => Some(16), 95 _ => unreachable!(), 96 }; 97 let mut segments = vec![]; 98 if minimum > 0 { 99 for _ in 0..u.int_in_range(0..=4)? { 100 let last_addressable = (1u32 << page_size_log2.unwrap_or(16)) * minimum - 1; 101 let offset = u.int_in_range(0..=last_addressable)?; 102 let max_len = 103 std::cmp::min(u.len(), usize::try_from(last_addressable - offset).unwrap()); 104 let len = u.int_in_range(0..=max_len)?; 105 let data = u.bytes(len)?.to_vec(); 106 segments.push((offset, data)); 107 } 108 } 109 Ok(HeapImage { 110 minimum, 111 maximum, 112 memory64, 113 page_size_log2, 114 segments, 115 }) 116 } 117 } 118 119 /// Configuration for linear memories in Wasmtime. 120 #[derive(Arbitrary, Clone, Debug, Eq, Hash, PartialEq)] 121 pub enum MemoryConfig { 122 /// Configuration for linear memories which correspond to normal 123 /// configuration settings in `wasmtime` itself. This will tweak various 124 /// parameters about static/dynamic memories. 125 Normal(NormalMemoryConfig), 126 127 /// Configuration to force use of a linear memory that's unaligned at its 128 /// base address to force all wasm addresses to be unaligned at the hardware 129 /// level, even if the wasm itself correctly aligns everything internally. 130 CustomUnaligned, 131 } 132 133 /// Represents a normal memory configuration for Wasmtime with the given 134 /// static and dynamic memory sizes. 135 #[derive(Clone, Debug, Eq, Hash, PartialEq)] 136 #[allow(missing_docs)] 137 pub struct NormalMemoryConfig { 138 pub memory_reservation: Option<u64>, 139 pub memory_guard_size: Option<u64>, 140 pub memory_reservation_for_growth: Option<u64>, 141 pub guard_before_linear_memory: bool, 142 pub cranelift_enable_heap_access_spectre_mitigations: Option<bool>, 143 pub memory_init_cow: bool, 144 } 145 146 impl<'a> Arbitrary<'a> for NormalMemoryConfig { 147 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> { 148 Ok(Self { 149 // Allow up to 8GiB reservations of the virtual address space for 150 // the initial memory reservation. 151 memory_reservation: interesting_virtual_memory_size(u, 33)?, 152 153 // Allow up to 4GiB guard page reservations to be made. 154 memory_guard_size: interesting_virtual_memory_size(u, 32)?, 155 156 // Allow up up to 1GiB extra memory to grow into for dynamic 157 // memories. 158 memory_reservation_for_growth: interesting_virtual_memory_size(u, 30)?, 159 160 guard_before_linear_memory: u.arbitrary()?, 161 cranelift_enable_heap_access_spectre_mitigations: u.arbitrary()?, 162 memory_init_cow: u.arbitrary()?, 163 }) 164 } 165 } 166 167 /// Helper function to generate "interesting numbers" for virtual memory 168 /// configuration options that `Config` supports. 169 fn interesting_virtual_memory_size( 170 u: &mut Unstructured<'_>, 171 max_log2: u32, 172 ) -> arbitrary::Result<Option<u64>> { 173 // Most of the time return "none" meaning "use the default settings". 174 if u.ratio(3, 4)? { 175 return Ok(None); 176 } 177 178 // Otherwise do a split between various strategies. 179 #[derive(Arbitrary)] 180 enum Interesting { 181 Zero, 182 PowerOfTwo, 183 Arbitrary, 184 } 185 186 let size = match u.arbitrary()? { 187 Interesting::Zero => 0, 188 Interesting::PowerOfTwo => 1 << u.int_in_range(0..=max_log2)?, 189 Interesting::Arbitrary => u.int_in_range(0..=1 << max_log2)?, 190 }; 191 Ok(Some(size)) 192 } 193 194 impl NormalMemoryConfig { 195 /// Apply this memory configuration to the given `wasmtime::Config`. 196 pub fn apply_to(&self, config: &mut wasmtime::Config) { 197 if let Some(n) = self.memory_reservation { 198 config.memory_reservation(n); 199 } 200 if let Some(n) = self.memory_guard_size { 201 config.memory_guard_size(n); 202 } 203 if let Some(n) = self.memory_reservation_for_growth { 204 config.memory_reservation_for_growth(n); 205 } 206 207 config 208 .guard_before_linear_memory(self.guard_before_linear_memory) 209 .memory_init_cow(self.memory_init_cow); 210 211 if let Some(enable) = self.cranelift_enable_heap_access_spectre_mitigations { 212 unsafe { 213 config.cranelift_flag_set( 214 "enable_heap_access_spectre_mitigation", 215 &enable.to_string(), 216 ); 217 } 218 } 219 } 220 } 221 222 /// A custom "linear memory allocator" for wasm which only works with the 223 /// "dynamic" mode of configuration where wasm always does explicit bounds 224 /// checks. 225 /// 226 /// This memory attempts to always use unaligned host addresses for the base 227 /// address of linear memory with wasm. This means that all jit loads/stores 228 /// should be unaligned, which is a "big hammer way" of testing that all our JIT 229 /// code works with unaligned addresses since alignment is not required for 230 /// correctness in wasm itself. 231 pub struct UnalignedMemory { 232 /// This memory is always one byte larger than the actual size of linear 233 /// memory. 234 src: Vec<u8>, 235 maximum: Option<usize>, 236 } 237 238 unsafe impl LinearMemory for UnalignedMemory { 239 fn byte_size(&self) -> usize { 240 // Chop off the extra byte reserved for the true byte size of this 241 // linear memory. 242 self.src.len() - 1 243 } 244 245 fn maximum_byte_size(&self) -> Option<usize> { 246 self.maximum 247 } 248 249 fn grow_to(&mut self, new_size: usize) -> Result<()> { 250 // Make sure to allocate an extra byte for our "unalignment" 251 self.src.resize(new_size + 1, 0); 252 Ok(()) 253 } 254 255 fn as_ptr(&self) -> *mut u8 { 256 // Return our allocated memory, offset by one, so that the base address 257 // of memory is always unaligned. 258 self.src[1..].as_ptr() as *mut _ 259 } 260 261 fn wasm_accessible(&self) -> Range<usize> { 262 let base = self.as_ptr() as usize; 263 let len = self.byte_size(); 264 base..base + len 265 } 266 } 267 268 /// A mechanism to generate [`UnalignedMemory`] at runtime. 269 pub struct UnalignedMemoryCreator; 270 271 unsafe impl MemoryCreator for UnalignedMemoryCreator { 272 fn new_memory( 273 &self, 274 _ty: MemoryType, 275 minimum: usize, 276 maximum: Option<usize>, 277 reserved_size_in_bytes: Option<usize>, 278 guard_size_in_bytes: usize, 279 ) -> Result<Box<dyn LinearMemory>, String> { 280 assert_eq!(guard_size_in_bytes, 0); 281 assert!(reserved_size_in_bytes.is_none() || reserved_size_in_bytes == Some(0)); 282 Ok(Box::new(UnalignedMemory { 283 src: vec![0; minimum + 1], 284 maximum, 285 })) 286 } 287 } 288