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