Skip to main content

wasmtime_fuzzing/generators/
memory.rs

1//! Generate various kinds of Wasm memory.
2
3use arbitrary::{Arbitrary, Unstructured};
4
5/// A description of a memory config, image, etc... that can be used to test
6/// memory accesses.
7#[derive(Debug)]
8pub struct MemoryAccesses {
9    /// The configuration to use with this test case.
10    pub config: crate::generators::Config,
11    /// The heap image to use with this test case.
12    pub image: HeapImage,
13    /// The offset immediate to encode in the `load{8,16,32,64}` functions'
14    /// various load instructions.
15    pub offset: u32,
16    /// The amount (in pages) to grow the memory.
17    pub growth: u32,
18}
19
20impl<'a> Arbitrary<'a> for MemoryAccesses {
21    fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
22        let image = HeapImage::arbitrary(u)?;
23
24        // Don't grow too much, since oss-fuzz/asan get upset if we try,
25        // even if we allow it to fail.
26        let one_mib = 1 << 20; // 1 MiB
27        let max_growth = one_mib / (1 << image.page_size_log2.unwrap_or(16));
28        let mut growth: u32 = u.int_in_range(0..=max_growth)?;
29
30        // Occasionally, round to a power of two, since these tend to be
31        // interesting numbers that overlap with the host page size and things
32        // like that.
33        if growth > 0 && u.ratio(1, 20)? {
34            growth = (growth - 1).next_power_of_two();
35        }
36
37        Ok(MemoryAccesses {
38            config: u.arbitrary()?,
39            image,
40            offset: u.arbitrary()?,
41            growth,
42        })
43    }
44}
45
46/// A memory heap image.
47pub struct HeapImage {
48    /// The minimum size (in pages) of this memory.
49    pub minimum: u32,
50    /// The maximum size (in pages) of this memory.
51    pub maximum: Option<u32>,
52    /// Whether this memory should be indexed with `i64` (rather than `i32`).
53    pub memory64: bool,
54    /// The log2 of the page size for this memory.
55    pub page_size_log2: Option<u32>,
56    /// Data segments for this memory.
57    pub segments: Vec<(u32, Vec<u8>)>,
58}
59
60impl std::fmt::Debug for HeapImage {
61    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62        struct Segments<'a>(&'a [(u32, Vec<u8>)]);
63        impl std::fmt::Debug for Segments<'_> {
64            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65                write!(f, "[..; {}]", self.0.len())
66            }
67        }
68
69        f.debug_struct("HeapImage")
70            .field("minimum", &self.minimum)
71            .field("maximum", &self.maximum)
72            .field("memory64", &self.memory64)
73            .field("page_size_log2", &self.page_size_log2)
74            .field("segments", &Segments(&self.segments))
75            .finish()
76    }
77}
78
79impl<'a> Arbitrary<'a> for HeapImage {
80    fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
81        let minimum = u.int_in_range(0..=4)?;
82        let maximum = if u.arbitrary()? {
83            Some(u.int_in_range(minimum..=10)?)
84        } else {
85            None
86        };
87        let memory64 = u.arbitrary()?;
88        let page_size_log2 = match u.int_in_range(0..=2)? {
89            0 => None,
90            1 => Some(0),
91            2 => Some(16),
92            _ => unreachable!(),
93        };
94        let mut segments = vec![];
95        if minimum > 0 {
96            for _ in 0..u.int_in_range(0..=4)? {
97                let last_addressable = (1u32 << page_size_log2.unwrap_or(16)) * minimum - 1;
98                let offset = u.int_in_range(0..=last_addressable)?;
99                let max_len =
100                    std::cmp::min(u.len(), usize::try_from(last_addressable - offset).unwrap());
101                let len = u.int_in_range(0..=max_len)?;
102                let data = u.bytes(len)?.to_vec();
103                segments.push((offset, data));
104            }
105        }
106        Ok(HeapImage {
107            minimum,
108            maximum,
109            memory64,
110            page_size_log2,
111            segments,
112        })
113    }
114}
115
116/// Represents a normal memory configuration for Wasmtime with the given
117/// static and dynamic memory sizes.
118#[derive(Clone, Debug, Eq, Hash, PartialEq)]
119#[expect(missing_docs, reason = "self-describing fields")]
120pub struct MemoryConfig {
121    pub memory_reservation: Option<u64>,
122    pub memory_guard_size: Option<u64>,
123    pub memory_reservation_for_growth: Option<u64>,
124    pub guard_before_linear_memory: bool,
125    pub cranelift_enable_heap_access_spectre_mitigations: Option<bool>,
126    pub memory_init_cow: bool,
127    pub gc_heap_reservation: Option<u64>,
128    pub gc_heap_guard_size: Option<u64>,
129    pub gc_heap_reservation_for_growth: Option<u64>,
130    pub gc_heap_may_move: Option<bool>,
131    pub gc_heap_initial_size: Option<u64>,
132}
133
134impl<'a> Arbitrary<'a> for MemoryConfig {
135    fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
136        let max_log2 = if cfg!(target_pointer_width = "32") {
137            25
138        } else {
139            63
140        };
141        Ok(Self {
142            // Allow up to 8GiB reservations of the virtual address space for
143            // the initial memory reservation.
144            memory_reservation: interesting_virtual_memory_size(u, 33.min(max_log2))?,
145
146            // Allow up to 4GiB guard page reservations to be made.
147            memory_guard_size: interesting_virtual_memory_size(u, 32.min(max_log2))?,
148
149            // Allow up to 1GiB extra memory to grow into for dynamic memories.
150            memory_reservation_for_growth: interesting_virtual_memory_size(u, 30.min(max_log2))?,
151
152            guard_before_linear_memory: u.arbitrary()?,
153            cranelift_enable_heap_access_spectre_mitigations: u.arbitrary()?,
154            memory_init_cow: u.arbitrary()?,
155
156            // GC heap tunables, same ranges as corresponding memory tunables.
157            gc_heap_reservation: interesting_virtual_memory_size(u, 33.min(max_log2))?,
158            gc_heap_guard_size: interesting_virtual_memory_size(u, 32.min(max_log2))?,
159            gc_heap_reservation_for_growth: interesting_virtual_memory_size(u, 30.min(max_log2))?,
160            gc_heap_may_move: u.arbitrary()?,
161            gc_heap_initial_size: interesting_virtual_memory_size(u, 22.min(max_log2))?,
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 MemoryConfig {
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        cfg.opts.gc_heap_reservation = self.gc_heap_reservation;
202        cfg.opts.gc_heap_guard_size = self.gc_heap_guard_size;
203        cfg.opts.gc_heap_reservation_for_growth = self.gc_heap_reservation_for_growth;
204        cfg.opts.gc_heap_may_move = self.gc_heap_may_move;
205        cfg.opts.gc_heap_initial_size = self.gc_heap_initial_size;
206
207        if let Some(enable) = self.cranelift_enable_heap_access_spectre_mitigations {
208            cfg.codegen.cranelift.push((
209                "enable_heap_access_spectre_mitigation".to_string(),
210                Some(enable.to_string()),
211            ));
212        }
213    }
214}