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 static_memory_maximum_size: Option<u64>, 139 pub static_memory_guard_size: Option<u64>, 140 pub dynamic_memory_guard_size: Option<u64>, 141 pub dynamic_memory_reserved_for_growth: Option<u64>, 142 pub guard_before_linear_memory: bool, 143 pub cranelift_enable_heap_access_spectre_mitigations: Option<bool>, 144 pub memory_init_cow: bool, 145 } 146 147 impl<'a> Arbitrary<'a> for NormalMemoryConfig { 148 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> { 149 // This attempts to limit memory and guard sizes to 32-bit ranges so 150 // we don't exhaust a 64-bit address space easily. 151 let mut ret = Self { 152 static_memory_maximum_size: <Option<u32> as Arbitrary>::arbitrary(u)?.map(Into::into), 153 static_memory_guard_size: <Option<u32> as Arbitrary>::arbitrary(u)?.map(Into::into), 154 dynamic_memory_guard_size: <Option<u32> as Arbitrary>::arbitrary(u)?.map(Into::into), 155 dynamic_memory_reserved_for_growth: <Option<u32> as Arbitrary>::arbitrary(u)? 156 .map(Into::into), 157 guard_before_linear_memory: u.arbitrary()?, 158 cranelift_enable_heap_access_spectre_mitigations: u.arbitrary()?, 159 memory_init_cow: u.arbitrary()?, 160 }; 161 162 if let Some(dynamic) = ret.dynamic_memory_guard_size { 163 let statik = ret.static_memory_guard_size.unwrap_or(2 << 30); 164 ret.static_memory_guard_size = Some(statik.max(dynamic)); 165 } 166 167 Ok(ret) 168 } 169 } 170 171 impl NormalMemoryConfig { 172 /// Apply this memory configuration to the given `wasmtime::Config`. 173 pub fn apply_to(&self, config: &mut wasmtime::Config) { 174 config 175 .static_memory_maximum_size(self.static_memory_maximum_size.unwrap_or(0)) 176 .static_memory_guard_size(self.static_memory_guard_size.unwrap_or(0)) 177 .dynamic_memory_guard_size(self.dynamic_memory_guard_size.unwrap_or(0)) 178 .dynamic_memory_reserved_for_growth( 179 self.dynamic_memory_reserved_for_growth.unwrap_or(0), 180 ) 181 .guard_before_linear_memory(self.guard_before_linear_memory) 182 .memory_init_cow(self.memory_init_cow); 183 184 if let Some(enable) = self.cranelift_enable_heap_access_spectre_mitigations { 185 unsafe { 186 config.cranelift_flag_set( 187 "enable_heap_access_spectre_mitigation", 188 &enable.to_string(), 189 ); 190 } 191 } 192 } 193 } 194 195 /// A custom "linear memory allocator" for wasm which only works with the 196 /// "dynamic" mode of configuration where wasm always does explicit bounds 197 /// checks. 198 /// 199 /// This memory attempts to always use unaligned host addresses for the base 200 /// address of linear memory with wasm. This means that all jit loads/stores 201 /// should be unaligned, which is a "big hammer way" of testing that all our JIT 202 /// code works with unaligned addresses since alignment is not required for 203 /// correctness in wasm itself. 204 pub struct UnalignedMemory { 205 /// This memory is always one byte larger than the actual size of linear 206 /// memory. 207 src: Vec<u8>, 208 maximum: Option<usize>, 209 } 210 211 unsafe impl LinearMemory for UnalignedMemory { 212 fn byte_size(&self) -> usize { 213 // Chop off the extra byte reserved for the true byte size of this 214 // linear memory. 215 self.src.len() - 1 216 } 217 218 fn maximum_byte_size(&self) -> Option<usize> { 219 self.maximum 220 } 221 222 fn grow_to(&mut self, new_size: usize) -> Result<()> { 223 // Make sure to allocate an extra byte for our "unalignment" 224 self.src.resize(new_size + 1, 0); 225 Ok(()) 226 } 227 228 fn as_ptr(&self) -> *mut u8 { 229 // Return our allocated memory, offset by one, so that the base address 230 // of memory is always unaligned. 231 self.src[1..].as_ptr() as *mut _ 232 } 233 234 fn wasm_accessible(&self) -> Range<usize> { 235 let base = self.as_ptr() as usize; 236 let len = self.byte_size(); 237 base..base + len 238 } 239 } 240 241 /// A mechanism to generate [`UnalignedMemory`] at runtime. 242 pub struct UnalignedMemoryCreator; 243 244 unsafe impl MemoryCreator for UnalignedMemoryCreator { 245 fn new_memory( 246 &self, 247 _ty: MemoryType, 248 minimum: usize, 249 maximum: Option<usize>, 250 reserved_size_in_bytes: Option<usize>, 251 guard_size_in_bytes: usize, 252 ) -> Result<Box<dyn LinearMemory>, String> { 253 assert_eq!(guard_size_in_bytes, 0); 254 assert!(reserved_size_in_bytes.is_none() || reserved_size_in_bytes == Some(0)); 255 Ok(Box::new(UnalignedMemory { 256 src: vec![0; minimum + 1], 257 maximum, 258 })) 259 } 260 } 261