wasmtime_fuzzing/generators/
memory.rs

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