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