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