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