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