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