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         Ok(MemoryAccesses {
26             config: u.arbitrary()?,
27             image: u.arbitrary()?,
28             offset: u.arbitrary()?,
29             // Don't grow too much, since oss-fuzz/asan get upset if we try,
30             // even if we allow it to fail.
31             growth: u.int_in_range(0..=10)?,
32         })
33     }
34 }
35 
36 /// A memory heap image.
37 pub struct HeapImage {
38     /// The minimum size (in pages) of this memory.
39     pub minimum: u32,
40     /// The maximum size (in pages) of this memory.
41     pub maximum: Option<u32>,
42     /// Whether this memory should be indexed with `i64` (rather than `i32`).
43     pub memory64: bool,
44     /// Data segments for this memory.
45     pub segments: Vec<(u32, Vec<u8>)>,
46 }
47 
48 impl std::fmt::Debug for HeapImage {
49     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50         struct Segments<'a>(&'a [(u32, Vec<u8>)]);
51         impl std::fmt::Debug for Segments<'_> {
52             fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53                 write!(f, "[..; {}]", self.0.len())
54             }
55         }
56 
57         f.debug_struct("HeapImage")
58             .field("minimum", &self.minimum)
59             .field("maximum", &self.maximum)
60             .field("memory64", &self.memory64)
61             .field("segments", &Segments(&self.segments))
62             .finish()
63     }
64 }
65 
66 impl<'a> Arbitrary<'a> for HeapImage {
67     fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
68         let minimum = u.int_in_range(0..=4)?;
69         let maximum = if u.arbitrary()? {
70             Some(u.int_in_range(minimum..=10)?)
71         } else {
72             None
73         };
74         let memory64 = u.arbitrary()?;
75         let mut segments = vec![];
76         if minimum > 0 {
77             for _ in 0..u.int_in_range(0..=4)? {
78                 const WASM_PAGE_SIZE: u32 = 65536;
79                 let last_addressable = WASM_PAGE_SIZE * minimum - 1;
80                 let offset = u.int_in_range(0..=last_addressable)?;
81                 let max_len =
82                     std::cmp::min(u.len(), usize::try_from(last_addressable - offset).unwrap());
83                 let len = u.int_in_range(0..=max_len)?;
84                 let data = u.bytes(len)?.to_vec();
85                 segments.push((offset, data));
86             }
87         }
88         Ok(HeapImage {
89             minimum,
90             maximum,
91             memory64,
92             segments,
93         })
94     }
95 }
96 
97 /// Configuration for linear memories in Wasmtime.
98 #[derive(Arbitrary, Clone, Debug, Eq, Hash, PartialEq)]
99 pub enum MemoryConfig {
100     /// Configuration for linear memories which correspond to normal
101     /// configuration settings in `wasmtime` itself. This will tweak various
102     /// parameters about static/dynamic memories.
103     Normal(NormalMemoryConfig),
104 
105     /// Configuration to force use of a linear memory that's unaligned at its
106     /// base address to force all wasm addresses to be unaligned at the hardware
107     /// level, even if the wasm itself correctly aligns everything internally.
108     CustomUnaligned,
109 }
110 
111 /// Represents a normal memory configuration for Wasmtime with the given
112 /// static and dynamic memory sizes.
113 #[derive(Clone, Debug, Eq, Hash, PartialEq)]
114 #[allow(missing_docs)]
115 pub struct NormalMemoryConfig {
116     pub static_memory_maximum_size: Option<u64>,
117     pub static_memory_guard_size: Option<u64>,
118     pub dynamic_memory_guard_size: Option<u64>,
119     pub dynamic_memory_reserved_for_growth: Option<u64>,
120     pub guard_before_linear_memory: bool,
121     pub cranelift_enable_heap_access_spectre_mitigations: Option<bool>,
122     pub memory_init_cow: bool,
123 }
124 
125 impl<'a> Arbitrary<'a> for NormalMemoryConfig {
126     fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
127         // This attempts to limit memory and guard sizes to 32-bit ranges so
128         // we don't exhaust a 64-bit address space easily.
129         let mut ret = Self {
130             static_memory_maximum_size: <Option<u32> as Arbitrary>::arbitrary(u)?.map(Into::into),
131             static_memory_guard_size: <Option<u32> as Arbitrary>::arbitrary(u)?.map(Into::into),
132             dynamic_memory_guard_size: <Option<u32> as Arbitrary>::arbitrary(u)?.map(Into::into),
133             dynamic_memory_reserved_for_growth: <Option<u32> as Arbitrary>::arbitrary(u)?
134                 .map(Into::into),
135             guard_before_linear_memory: u.arbitrary()?,
136             cranelift_enable_heap_access_spectre_mitigations: u.arbitrary()?,
137             memory_init_cow: u.arbitrary()?,
138         };
139 
140         if let Some(dynamic) = ret.dynamic_memory_guard_size {
141             let statik = ret.static_memory_guard_size.unwrap_or(2 << 30);
142             ret.static_memory_guard_size = Some(statik.max(dynamic));
143         }
144 
145         Ok(ret)
146     }
147 }
148 
149 impl NormalMemoryConfig {
150     /// Apply this memory configuration to the given `wasmtime::Config`.
151     pub fn apply_to(&self, config: &mut wasmtime::Config) {
152         config
153             .static_memory_maximum_size(self.static_memory_maximum_size.unwrap_or(0))
154             .static_memory_guard_size(self.static_memory_guard_size.unwrap_or(0))
155             .dynamic_memory_guard_size(self.dynamic_memory_guard_size.unwrap_or(0))
156             .dynamic_memory_reserved_for_growth(
157                 self.dynamic_memory_reserved_for_growth.unwrap_or(0),
158             )
159             .guard_before_linear_memory(self.guard_before_linear_memory)
160             .memory_init_cow(self.memory_init_cow);
161 
162         if let Some(enable) = self.cranelift_enable_heap_access_spectre_mitigations {
163             unsafe {
164                 config.cranelift_flag_set(
165                     "enable_heap_access_spectre_mitigation",
166                     &enable.to_string(),
167                 );
168             }
169         }
170     }
171 }
172 
173 /// A custom "linear memory allocator" for wasm which only works with the
174 /// "dynamic" mode of configuration where wasm always does explicit bounds
175 /// checks.
176 ///
177 /// This memory attempts to always use unaligned host addresses for the base
178 /// address of linear memory with wasm. This means that all jit loads/stores
179 /// should be unaligned, which is a "big hammer way" of testing that all our JIT
180 /// code works with unaligned addresses since alignment is not required for
181 /// correctness in wasm itself.
182 pub struct UnalignedMemory {
183     /// This memory is always one byte larger than the actual size of linear
184     /// memory.
185     src: Vec<u8>,
186     maximum: Option<usize>,
187 }
188 
189 unsafe impl LinearMemory for UnalignedMemory {
190     fn byte_size(&self) -> usize {
191         // Chop off the extra byte reserved for the true byte size of this
192         // linear memory.
193         self.src.len() - 1
194     }
195 
196     fn maximum_byte_size(&self) -> Option<usize> {
197         self.maximum
198     }
199 
200     fn grow_to(&mut self, new_size: usize) -> Result<()> {
201         // Make sure to allocate an extra byte for our "unalignment"
202         self.src.resize(new_size + 1, 0);
203         Ok(())
204     }
205 
206     fn as_ptr(&self) -> *mut u8 {
207         // Return our allocated memory, offset by one, so that the base address
208         // of memory is always unaligned.
209         self.src[1..].as_ptr() as *mut _
210     }
211 
212     fn wasm_accessible(&self) -> Range<usize> {
213         let base = self.as_ptr() as usize;
214         let len = self.byte_size();
215         base..base + len
216     }
217 }
218 
219 /// A mechanism to generate [`UnalignedMemory`] at runtime.
220 pub struct UnalignedMemoryCreator;
221 
222 unsafe impl MemoryCreator for UnalignedMemoryCreator {
223     fn new_memory(
224         &self,
225         _ty: MemoryType,
226         minimum: usize,
227         maximum: Option<usize>,
228         reserved_size_in_bytes: Option<usize>,
229         guard_size_in_bytes: usize,
230     ) -> Result<Box<dyn LinearMemory>, String> {
231         assert_eq!(guard_size_in_bytes, 0);
232         assert!(reserved_size_in_bytes.is_none() || reserved_size_in_bytes == Some(0));
233         Ok(Box::new(UnalignedMemory {
234             src: vec![0; minimum + 1],
235             maximum,
236         }))
237     }
238 }
239