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