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