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