wasmtime_fuzzing/generators/
memory.rs

1//! Generate various kinds of Wasm memory.
2
3use anyhow::Result;
4use arbitrary::{Arbitrary, Unstructured};
5use wasmtime::{LinearMemory, MemoryCreator, MemoryType};
6
7/// A description of a memory config, image, etc... that can be used to test
8/// memory accesses.
9#[derive(Debug)]
10pub struct MemoryAccesses {
11    /// The configuration to use with this test case.
12    pub config: crate::generators::Config,
13    /// The heap image to use with this test case.
14    pub image: HeapImage,
15    /// The offset immediate to encode in the `load{8,16,32,64}` functions'
16    /// various load instructions.
17    pub offset: u32,
18    /// The amount (in pages) to grow the memory.
19    pub growth: u32,
20}
21
22impl<'a> Arbitrary<'a> for MemoryAccesses {
23    fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
24        let image = HeapImage::arbitrary(u)?;
25
26        // Don't grow too much, since oss-fuzz/asan get upset if we try,
27        // even if we allow it to fail.
28        let one_mib = 1 << 20; // 1 MiB
29        let max_growth = one_mib / (1 << image.page_size_log2.unwrap_or(16));
30        let mut growth: u32 = u.int_in_range(0..=max_growth)?;
31
32        // Occasionally, round to a power of two, since these tend to be
33        // interesting numbers that overlap with the host page size and things
34        // like that.
35        if growth > 0 && u.ratio(1, 20)? {
36            growth = (growth - 1).next_power_of_two();
37        }
38
39        Ok(MemoryAccesses {
40            config: u.arbitrary()?,
41            image,
42            offset: u.arbitrary()?,
43            growth,
44        })
45    }
46}
47
48/// A memory heap image.
49pub struct HeapImage {
50    /// The minimum size (in pages) of this memory.
51    pub minimum: u32,
52    /// The maximum size (in pages) of this memory.
53    pub maximum: Option<u32>,
54    /// Whether this memory should be indexed with `i64` (rather than `i32`).
55    pub memory64: bool,
56    /// The log2 of the page size for this memory.
57    pub page_size_log2: Option<u32>,
58    /// Data segments for this memory.
59    pub segments: Vec<(u32, Vec<u8>)>,
60}
61
62impl std::fmt::Debug for HeapImage {
63    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64        struct Segments<'a>(&'a [(u32, Vec<u8>)]);
65        impl std::fmt::Debug for Segments<'_> {
66            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67                write!(f, "[..; {}]", self.0.len())
68            }
69        }
70
71        f.debug_struct("HeapImage")
72            .field("minimum", &self.minimum)
73            .field("maximum", &self.maximum)
74            .field("memory64", &self.memory64)
75            .field("page_size_log2", &self.page_size_log2)
76            .field("segments", &Segments(&self.segments))
77            .finish()
78    }
79}
80
81impl<'a> Arbitrary<'a> for HeapImage {
82    fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
83        let minimum = u.int_in_range(0..=4)?;
84        let maximum = if u.arbitrary()? {
85            Some(u.int_in_range(minimum..=10)?)
86        } else {
87            None
88        };
89        let memory64 = u.arbitrary()?;
90        let page_size_log2 = match u.int_in_range(0..=2)? {
91            0 => None,
92            1 => Some(0),
93            2 => Some(16),
94            _ => unreachable!(),
95        };
96        let mut segments = vec![];
97        if minimum > 0 {
98            for _ in 0..u.int_in_range(0..=4)? {
99                let last_addressable = (1u32 << page_size_log2.unwrap_or(16)) * minimum - 1;
100                let offset = u.int_in_range(0..=last_addressable)?;
101                let max_len =
102                    std::cmp::min(u.len(), usize::try_from(last_addressable - offset).unwrap());
103                let len = u.int_in_range(0..=max_len)?;
104                let data = u.bytes(len)?.to_vec();
105                segments.push((offset, data));
106            }
107        }
108        Ok(HeapImage {
109            minimum,
110            maximum,
111            memory64,
112            page_size_log2,
113            segments,
114        })
115    }
116}
117
118/// Configuration for linear memories in Wasmtime.
119#[derive(Arbitrary, Clone, Debug, Eq, Hash, PartialEq)]
120pub enum MemoryConfig {
121    /// Configuration for linear memories which correspond to normal
122    /// configuration settings in `wasmtime` itself. This will tweak various
123    /// parameters about static/dynamic memories.
124    Normal(NormalMemoryConfig),
125
126    /// Configuration to force use of a linear memory that's unaligned at its
127    /// base address to force all wasm addresses to be unaligned at the hardware
128    /// level, even if the wasm itself correctly aligns everything internally.
129    CustomUnaligned,
130}
131
132/// Represents a normal memory configuration for Wasmtime with the given
133/// static and dynamic memory sizes.
134#[derive(Clone, Debug, Eq, Hash, PartialEq)]
135#[expect(missing_docs, reason = "self-describing fields")]
136pub struct NormalMemoryConfig {
137    pub memory_reservation: Option<u64>,
138    pub memory_guard_size: Option<u64>,
139    pub memory_reservation_for_growth: Option<u64>,
140    pub guard_before_linear_memory: bool,
141    pub cranelift_enable_heap_access_spectre_mitigations: Option<bool>,
142    pub memory_init_cow: bool,
143}
144
145impl<'a> Arbitrary<'a> for NormalMemoryConfig {
146    fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
147        Ok(Self {
148            // Allow up to 8GiB reservations of the virtual address space for
149            // the initial memory reservation.
150            memory_reservation: interesting_virtual_memory_size(u, 33)?,
151
152            // Allow up to 4GiB guard page reservations to be made.
153            memory_guard_size: interesting_virtual_memory_size(u, 32)?,
154
155            // Allow up up to 1GiB extra memory to grow into for dynamic
156            // memories.
157            memory_reservation_for_growth: interesting_virtual_memory_size(u, 30)?,
158
159            guard_before_linear_memory: u.arbitrary()?,
160            cranelift_enable_heap_access_spectre_mitigations: u.arbitrary()?,
161            memory_init_cow: u.arbitrary()?,
162        })
163    }
164}
165
166/// Helper function to generate "interesting numbers" for virtual memory
167/// configuration options that `Config` supports.
168fn interesting_virtual_memory_size(
169    u: &mut Unstructured<'_>,
170    max_log2: u32,
171) -> arbitrary::Result<Option<u64>> {
172    // Most of the time return "none" meaning "use the default settings".
173    if u.ratio(3, 4)? {
174        return Ok(None);
175    }
176
177    // Otherwise do a split between various strategies.
178    #[derive(Arbitrary)]
179    enum Interesting {
180        Zero,
181        PowerOfTwo,
182        Arbitrary,
183    }
184
185    let size = match u.arbitrary()? {
186        Interesting::Zero => 0,
187        Interesting::PowerOfTwo => 1 << u.int_in_range(0..=max_log2)?,
188        Interesting::Arbitrary => u.int_in_range(0..=1 << max_log2)?,
189    };
190    Ok(Some(size))
191}
192
193impl NormalMemoryConfig {
194    /// Apply this memory configuration to the given config.
195    pub fn configure(&self, cfg: &mut wasmtime_cli_flags::CommonOptions) {
196        cfg.opts.memory_reservation = self.memory_reservation;
197        cfg.opts.memory_guard_size = self.memory_guard_size;
198        cfg.opts.memory_reservation_for_growth = self.memory_reservation_for_growth;
199        cfg.opts.guard_before_linear_memory = Some(self.guard_before_linear_memory);
200        cfg.opts.memory_init_cow = Some(self.memory_init_cow);
201
202        if let Some(enable) = self.cranelift_enable_heap_access_spectre_mitigations {
203            cfg.codegen.cranelift.push((
204                "enable_heap_access_spectre_mitigation".to_string(),
205                Some(enable.to_string()),
206            ));
207        }
208    }
209}
210
211/// A custom "linear memory allocator" for wasm which only works with the
212/// "dynamic" mode of configuration where wasm always does explicit bounds
213/// checks.
214///
215/// This memory attempts to always use unaligned host addresses for the base
216/// address of linear memory with wasm. This means that all jit loads/stores
217/// should be unaligned, which is a "big hammer way" of testing that all our JIT
218/// code works with unaligned addresses since alignment is not required for
219/// correctness in wasm itself.
220pub struct UnalignedMemory {
221    /// This memory is always one byte larger than the actual size of linear
222    /// memory.
223    src: Vec<u8>,
224}
225
226unsafe impl LinearMemory for UnalignedMemory {
227    fn byte_size(&self) -> usize {
228        // Chop off the extra byte reserved for the true byte size of this
229        // linear memory.
230        self.src.len() - 1
231    }
232
233    fn byte_capacity(&self) -> usize {
234        self.src.capacity() - 1
235    }
236
237    fn grow_to(&mut self, new_size: usize) -> Result<()> {
238        // Make sure to allocate an extra byte for our "unalignment"
239        self.src.resize(new_size + 1, 0);
240        Ok(())
241    }
242
243    fn as_ptr(&self) -> *mut u8 {
244        // Return our allocated memory, offset by one, so that the base address
245        // of memory is always unaligned.
246        self.src[1..].as_ptr() as *mut _
247    }
248}
249
250/// A mechanism to generate [`UnalignedMemory`] at runtime.
251pub struct UnalignedMemoryCreator;
252
253unsafe impl MemoryCreator for UnalignedMemoryCreator {
254    fn new_memory(
255        &self,
256        _ty: MemoryType,
257        minimum: usize,
258        _maximum: Option<usize>,
259        reserved_size_in_bytes: Option<usize>,
260        guard_size_in_bytes: usize,
261    ) -> Result<Box<dyn LinearMemory>, String> {
262        assert_eq!(guard_size_in_bytes, 0);
263        assert!(reserved_size_in_bytes.is_none() || reserved_size_in_bytes == Some(0));
264        Ok(Box::new(UnalignedMemory {
265            src: vec![0; minimum + 1],
266        }))
267    }
268}